From d68d3c7eb4c44318fc89bde307ac39be27d138a2 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Wed, 20 May 2026 20:39:52 +0900 Subject: [PATCH] lib: extract rt module from os, sweep imports MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hare puts runtime allocation in rt::, not os:: (ref/hare/rt/malloc.ha:27, README). ww's `@symbol("rt_alloc") fn alloc(n: u64) *void;` lived at lib/os/os.ww as a historical bootstrap shortcut; this commit relocates it to a new lib/rt/malloc.ww and sweeps every site that depended on `import os` for the alloc decl over to `import rt`. This is commit 1 of 3 in the lib/rt extraction (#35): 1. (this) move decl, sweep imports — preserves shape 2. rename rt_alloc → rt_malloc (#38) 3. nullable return type + OOM-propagating builtin lowering (#39) No rename here. Symbol stays rt_alloc, function stays `alloc`, return stays *void. Behavior identical — same ffi resolution outcome, just sourced from a different module file. The rt::ensure runtime helper at selfhost/rt/ensure.ww is its own compilation unit with a local decl and is untouched. Side effect: every wcc cgen file used `rt` as a local *node variable name for "return type." `import rt;` shadows the module, so each selfhost/cmd/wcc/{check,cgenstmt,cgenexpr,cgenutil}.ww site renamed to `rtyp`. Mechanical follow-through; only the wcc module-import was forced to do this rename. Verified 132/132 + 995_self_rebuild byte-identity (5 wwstage tools round-trip byte-identical). --- Makefile | 10 +- examples/cmatrix/cmatrix.ww | 13 +- examples/lisp/lispcore.ww | 11 +- lib/fmt/fmt.ww | 3 +- lib/memio/memio.ww | 3 +- lib/os/os.ww | 22 ---- lib/os/ostest.ww | 3 +- lib/path/path.ww | 10 +- lib/rt/malloc.ww | 22 ++++ lib/shlex/shlex.ww | 3 +- lib/strings/strings.ww | 11 +- selfhost/cmd/w6a/asm.ww | 5 +- selfhost/cmd/w6a/main.combined.ww | 62 +++++----- selfhost/cmd/w6a/main.ww | 3 +- selfhost/cmd/w6c/main.combined.ww | 170 ++++++++++++++------------- selfhost/cmd/w6c/main.ww | 3 +- selfhost/cmd/w6l/dyn.ww | 3 +- selfhost/cmd/w6l/dynout.ww | 3 +- selfhost/cmd/w6l/main.combined.ww | 85 ++++++++------ selfhost/cmd/w6l/main.ww | 15 +-- selfhost/cmd/w6l/obj.ww | 7 +- selfhost/cmd/w6l/out.ww | 3 +- selfhost/cmd/wcc/cgenexpr.ww | 24 ++-- selfhost/cmd/wcc/cgenstmt.ww | 12 +- selfhost/cmd/wcc/cgenutil.ww | 52 ++++---- selfhost/cmd/wcc/check.ww | 14 +-- selfhost/cmd/wcc/mem.ww | 9 +- selfhost/cmd/ww/main.combined.ww | 119 ++++++++++--------- selfhost/cmd/ww/main.ww | 65 +++++----- selfhost/cmd/wwdump/main.combined.ww | 167 +++++++++++++------------- selfhost/test/smoke.combined.ww | 56 ++++----- test/wcc/700_e2e.c | 14 ++- test/wcc/758_cgalloc_str_field.c | 12 +- test/wcc/994_w6c_ww.c | 4 +- 34 files changed, 530 insertions(+), 488 deletions(-) create mode 100644 lib/rt/malloc.ww diff --git a/Makefile b/Makefile index 3ffb472c..d3460fba 100644 --- a/Makefile +++ b/Makefile @@ -113,7 +113,7 @@ $(BIN)/wwdump_ww: selfhost/cmd/wwdump/main.ww \ selfhost/cmd/wcc/cgen.ww selfhost/cmd/wcc/cgenexpr.ww \ selfhost/cmd/wcc/cgenstmt.ww selfhost/cmd/wcc/cgenutil.ww \ selfhost/cmd/wcc/cgendecl.ww \ - lib/os/os.ww lib/time/time.ww lib/strconv/strconv.ww lib/strings/strings.ww lib/bytes/bytes.ww lib/encoding/utf8/utf8.ww lib/ascii/ascii.ww lib/io/io.ww lib/memio/memio.ww lib/fmt/fmt.ww \ + lib/os/os.ww lib/rt/malloc.ww lib/time/time.ww lib/strconv/strconv.ww lib/strings/strings.ww lib/bytes/bytes.ww lib/encoding/utf8/utf8.ww lib/ascii/ascii.ww lib/io/io.ww lib/memio/memio.ww lib/fmt/fmt.ww \ $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ $(LIB)/libwwrt.a | $(BIN) @mkdir -p $(BIN)/wwdump_ww.d @@ -135,7 +135,7 @@ $(BIN)/w6c_ww: selfhost/cmd/w6c/main.ww \ selfhost/cmd/wcc/cgen.ww selfhost/cmd/wcc/cgenexpr.ww \ selfhost/cmd/wcc/cgenstmt.ww selfhost/cmd/wcc/cgenutil.ww \ selfhost/cmd/wcc/cgendecl.ww \ - lib/os/os.ww lib/time/time.ww lib/strconv/strconv.ww lib/strings/strings.ww lib/bytes/bytes.ww lib/encoding/utf8/utf8.ww lib/ascii/ascii.ww lib/io/io.ww lib/memio/memio.ww lib/fmt/fmt.ww \ + lib/os/os.ww lib/rt/malloc.ww lib/time/time.ww lib/strconv/strconv.ww lib/strings/strings.ww lib/bytes/bytes.ww lib/encoding/utf8/utf8.ww lib/ascii/ascii.ww lib/io/io.ww lib/memio/memio.ww lib/fmt/fmt.ww \ $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ $(LIB)/libwwrt.a | $(BIN) @mkdir -p $(BIN)/w6c_ww.d @@ -154,7 +154,7 @@ $(BIN)/w6a_ww: selfhost/cmd/w6a/main.ww selfhost/cmd/w6a/types.ww \ selfhost/cmd/w6a/lex.ww selfhost/cmd/w6a/parse.ww \ selfhost/cmd/w6a/asm.ww selfhost/cmd/w6a/obj.ww \ selfhost/cmd/wcc/mem.ww \ - lib/os/os.ww lib/time/time.ww \ + lib/os/os.ww lib/rt/malloc.ww lib/time/time.ww \ $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ $(LIB)/libwwrt.a | $(BIN) @mkdir -p $(BIN)/w6a_ww.d @@ -171,7 +171,7 @@ $(BIN)/w6l_ww: selfhost/cmd/w6l/main.ww selfhost/cmd/w6l/sym.ww \ selfhost/cmd/w6l/obj.ww selfhost/cmd/w6l/dyn.ww \ selfhost/cmd/w6l/pass.ww selfhost/cmd/w6l/dynout.ww \ selfhost/cmd/w6l/out.ww selfhost/cmd/wcc/mem.ww \ - lib/os/os.ww lib/time/time.ww \ + lib/os/os.ww lib/rt/malloc.ww lib/time/time.ww \ $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ $(LIB)/libwwrt.a | $(BIN) @mkdir -p $(BIN)/w6l_ww.d @@ -184,7 +184,7 @@ $(BIN)/w6l_ww: selfhost/cmd/w6l/main.ww selfhost/cmd/w6l/sym.ww \ # ---- ww-side ww driver (exercised by 993_ww_ww) ------------------------ # The driver pulls in lib/os (default search path) and selfhost/cmd/wcc # (for the bump arena). It then orchestrates w6c/w6a/w6l like the C driver. -$(BIN)/ww_ww: selfhost/cmd/ww/main.ww selfhost/cmd/wcc/mem.ww lib/os/os.ww \ +$(BIN)/ww_ww: selfhost/cmd/ww/main.ww selfhost/cmd/wcc/mem.ww lib/os/os.ww lib/rt/malloc.ww \ lib/time/time.ww lib/strconv/strconv.ww \ $(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ $(LIB)/libwwrt.a | $(BIN) diff --git a/examples/cmatrix/cmatrix.ww b/examples/cmatrix/cmatrix.ww index 2927a1c0..5ce9bd07 100644 --- a/examples/cmatrix/cmatrix.ww +++ b/examples/cmatrix/cmatrix.ww @@ -29,6 +29,7 @@ package cmatrix; import os; +import rt; import time; import fmt; @@ -200,18 +201,18 @@ fn now_ns() u64 = { // ---- cmat allocate / free --------------------------------------------- fn cmat_alloc(w: i32, h: i32, r: *rng) *cmat = { - let m = os.alloc(CMAT_SZ): *cmat; + let m = rt.alloc(CMAT_SZ): *cmat; m.w = w; m.h = h; m.gspeed = -1; m.gtheme = theme.GREEN: i32; m.flash = 0; let n4 = (w: u64) * 4u64; - m.head = os.alloc(n4): *i32; - m.length = os.alloc(n4): *i32; - m.speed = os.alloc(n4): *i32; - m.counter = os.alloc(n4): *i32; - m.glyphs = os.alloc((w: u64) * (MAX_TRAIL: u64)): *u8; + m.head = rt.alloc(n4): *i32; + m.length = rt.alloc(n4): *i32; + m.speed = rt.alloc(n4): *i32; + m.counter = rt.alloc(n4): *i32; + m.glyphs = rt.alloc((w: u64) * (MAX_TRAIL: u64)): *u8; let c = 0; for (c < w) { m.head[c] = -rng_range(r, 0, h); diff --git a/examples/lisp/lispcore.ww b/examples/lisp/lispcore.ww index 914976c3..53ec9aec 100644 --- a/examples/lisp/lispcore.ww +++ b/examples/lisp/lispcore.ww @@ -62,6 +62,7 @@ package lisp; import os; +import rt; import fmt; import ascii; import strconv; @@ -158,7 +159,7 @@ let perm_arena: arena; let trans_arena: arena; fn arena_new_chunk(prev: *chunk) *chunk = { - let raw: *u8 = os.alloc(ARENA_CHUNK): *u8; + let raw: *u8 = rt.alloc(ARENA_CHUNK): *u8; let c: *chunk = raw: *chunk; c.next = prev; c.cur = 0u64; @@ -1565,10 +1566,10 @@ fn bind_one(e: **env, name: str, id: i32) void = { export fn initsyms() void = { arena_init(); - sym_off = os.alloc((SYM_CAP: u64) * 4u64): *i32; - sym_len = os.alloc((SYM_CAP: u64) * 4u64): *i32; - sym_blob = os.alloc(SYM_BLOBSZ: u64): *u8; - obuf = os.alloc(OBUF_SZ: u64): *u8; + sym_off = rt.alloc((SYM_CAP: u64) * 4u64): *i32; + sym_len = rt.alloc((SYM_CAP: u64) * 4u64): *i32; + sym_blob = rt.alloc(SYM_BLOBSZ: u64): *u8; + obuf = rt.alloc(OBUF_SZ: u64): *u8; // Stash the special-form ids so classify_sform sees them. SID_QUOTE = intern("quote"); diff --git a/lib/fmt/fmt.ww b/lib/fmt/fmt.ww index 94211f86..1162c676 100644 --- a/lib/fmt/fmt.ww +++ b/lib/fmt/fmt.ww @@ -29,6 +29,7 @@ package fmt; import io; import memio; import os; +import rt; import strconv; // i64dec_buf — scratch buffer for [[i64dec]] below. Module-level @@ -885,7 +886,7 @@ export fn asprintf(fmt: str, args: field...) str = { match (cres) { case void => {}; case io.closed => {}; }; return out; }; - let tight: *u8 = os.alloc(view.len: u64): *u8; + let tight: *u8 = rt.alloc(view.len: u64): *u8; let i: i32 = 0; for (i < view.len) { tight[i] = view.ptr[i]; diff --git a/lib/memio/memio.ww b/lib/memio/memio.ww index 60ae0395..720619fe 100644 --- a/lib/memio/memio.ww +++ b/lib/memio/memio.ww @@ -30,6 +30,7 @@ package memio; import io; import os; +import rt; // state — memio's per-stream bookkeeping. The caller owns the slot // and passes its address into a constructor. `ptr/len/cap` are the @@ -196,7 +197,7 @@ fn grow(m: *state, need: i32) void = { let newcap: i32 = m.cap; if (newcap < 8) { newcap = 8; }; for (newcap < need) { newcap *= 2; }; - let nbuf: *u8 = os.alloc(newcap: u64): *u8; + let nbuf: *u8 = rt.alloc(newcap: u64): *u8; let i: i32 = 0; for (i < m.len) { nbuf[i] = m.ptr[i]; diff --git a/lib/os/os.ww b/lib/os/os.ww index 450af172..49fab36d 100644 --- a/lib/os/os.ww +++ b/lib/os/os.ww @@ -12,28 +12,6 @@ import time; @symbol("rt_syscall") fn syscall3(num: nr, a: i64, b: i64, c: i64) i64; @symbol("rt_syscall") fn syscall4(num: nr, a: i64, b: i64, c: i64, d: i64) i64; -// alloc / free — runtime mmap-backed page allocator. Untyped: -// `alloc(n)` returns a `*void` and `free(p, n)` requires the byte -// count back because rt_free is munmap-based and doesn't track -// mapping sizes (the kernel needs the length to release the -// reservation). -// -// Diverges from Hare. Hare exposes `alloc` / `free` as typed -// language builtins (`alloc(value, cap)?` / `free(ptr)`) that the -// compiler lowers to rt::malloc/rt::free; ww has no such builtins, -// so the rt-symbol surface is exposed directly. Stdlib callers -// that need a typed allocation pattern wrap this with a cast plus -// a stored capacity (see [[strings.dup]], [[memio.dynamic]]). -// -// OOM: rt_alloc is a bare mmap(MAP_ANON|MAP_PRIVATE) wrapper with -// no error path. The raw Linux mmap syscall returns a negative -// errno cast to `*void` on failure (e.g. `(void*)-12` for ENOMEM); -// the `MAP_FAILED` (`(void*)-1`) value is a libc-wrapper convention -// that rt_alloc doesn't apply. Neither `== nil` nor `== (void*)-1` -// catches it; any deref of such a return faults. Today the stdlib -// does not check; OOM faults on first dereference. A typed -// fallible variant is a future task. -@symbol("rt_alloc") export fn alloc(n: u64) *void; @symbol("rt_free") export fn free(p: *void, n: u64) void; @symbol("rt_abort") fn abort(msg: str) void; diff --git a/lib/os/ostest.ww b/lib/os/ostest.ww index d98d9b57..977d4f8a 100644 --- a/lib/os/ostest.ww +++ b/lib/os/ostest.ww @@ -26,6 +26,7 @@ package os; import os; +import rt; let signalled: i32 = 0; @@ -102,7 +103,7 @@ fn streq(a: str, b: str) bool = { // dereferenceable, not just non-nil). @test fn test_alloc_free_roundtrip() void = { - let p: *u8 = os.alloc(4096u64): *u8; + let p: *u8 = rt.alloc(4096u64): *u8; if (p == nil: *u8) { fail(); }; // Write a sentinel at the head and tail of the page, read it // back. A miscompiled binding (wrong arg order, wrong ABI, etc.) diff --git a/lib/path/path.ww b/lib/path/path.ww index 16ec0c85..40fbfb61 100644 --- a/lib/path/path.ww +++ b/lib/path/path.ww @@ -5,7 +5,7 @@ package path; -import os; +import rt; def SEP: u8 = 47u8; // '/' @@ -95,7 +95,7 @@ export fn extension(p: str) str = { // two-arg join (no variadic). export fn join(a: str, b: str) str = { if (abs(b)) { - let buf: *u8 = os.alloc(b.len: u64): *u8; + let buf: *u8 = rt.alloc(b.len: u64): *u8; let i: i32 = 0; for (i < b.len) { buf[i] = b[i]; i += 1; }; let r: str; @@ -104,7 +104,7 @@ export fn join(a: str, b: str) str = { return r; }; if (a.len == 0) { - let buf: *u8 = os.alloc(b.len: u64): *u8; + let buf: *u8 = rt.alloc(b.len: u64): *u8; let i: i32 = 0; for (i < b.len) { buf[i] = b[i]; i += 1; }; let r: str; @@ -113,7 +113,7 @@ export fn join(a: str, b: str) str = { return r; }; if (b.len == 0) { - let buf: *u8 = os.alloc(a.len: u64): *u8; + let buf: *u8 = rt.alloc(a.len: u64): *u8; let i: i32 = 0; for (i < a.len) { buf[i] = a[i]; i += 1; }; let r: str; @@ -129,7 +129,7 @@ export fn join(a: str, b: str) str = { an -= 1; }; let total: i32 = an + 1 + b.len; - let buf: *u8 = os.alloc(total: u64): *u8; + let buf: *u8 = rt.alloc(total: u64): *u8; let i: i32 = 0; for (i < an) { buf[i] = a[i]; i += 1; }; buf[an] = SEP; diff --git a/lib/rt/malloc.ww b/lib/rt/malloc.ww new file mode 100644 index 00000000..fdee8a6e --- /dev/null +++ b/lib/rt/malloc.ww @@ -0,0 +1,22 @@ +// rt — runtime primitives exposed to ww programs. +// Mirrors Hare's rt:: module placement (ref/hare/rt/). + +package rt; + +// alloc — mmap-backed page allocator. Untyped: `alloc(n)` returns a +// `*void`; callers cast to the target type. Diverges from Hare: Hare +// exposes `alloc` / `free` as typed language builtins that the +// compiler lowers to rt::malloc/rt::free; ww has no such builtins, +// so the rt-symbol surface is exposed directly. Stdlib callers that +// need a typed allocation pattern wrap this with a cast plus a stored +// capacity (see [[strings.dup]], [[memio.dynamic]]). +// +// OOM: rt_alloc is a bare mmap(MAP_ANON|MAP_PRIVATE) wrapper with no +// error path. The raw Linux mmap syscall returns a negative errno cast +// to `*void` on failure (e.g. `(void*)-12` for ENOMEM); the +// `MAP_FAILED` (`(void*)-1`) value is a libc-wrapper convention that +// rt_alloc doesn't apply. Neither `== nil` nor `== (void*)-1` catches +// it; any deref of such a return faults. Today the stdlib does not +// check; OOM faults on first dereference. A typed fallible variant is +// a future task (task #39). ref/hare/rt/malloc.ha:27. +@symbol("rt_alloc") export fn alloc(n: u64) *void; diff --git a/lib/shlex/shlex.ww b/lib/shlex/shlex.ww index b1287870..c452ce96 100644 --- a/lib/shlex/shlex.ww +++ b/lib/shlex/shlex.ww @@ -97,6 +97,7 @@ package shlex; import io; import memio; import os; +import rt; // rt_ensure is the runtime slice-growth helper invoked by the // `append(s, v)` builtin. We bind it directly because the builtin's @@ -125,7 +126,7 @@ fn dupstr(s: str) str = { r.ptr = nil; r.len = 0; if (s.len == 0) { return r; }; - let buf: *u8 = os.alloc(s.len: u64): *u8; + let buf: *u8 = rt.alloc(s.len: u64): *u8; let i: i32 = 0; for (i < s.len) { buf[i] = s[i]; i += 1; }; r.ptr = buf; diff --git a/lib/strings/strings.ww b/lib/strings/strings.ww index f32d1dc5..5df40a8a 100644 --- a/lib/strings/strings.ww +++ b/lib/strings/strings.ww @@ -27,6 +27,7 @@ package strings; import bytes; import encoding.utf8; import os; +import rt; import types; // toutf8 — borrowed []u8 view of `s`. ref/hare/strings/utf8.ha:29. @@ -98,7 +99,7 @@ export fn dup(s: str) str = { r.ptr = nil; r.len = 0; if (s.len == 0) { return r; }; - let buf: *u8 = os.alloc(s.len: u64): *u8; + let buf: *u8 = rt.alloc(s.len: u64): *u8; let i: i32 = 0; for (i < s.len) { buf[i] = s[i]; i += 1; }; r.ptr = buf; @@ -176,7 +177,7 @@ export fn concat(strs: str...) str = { r.ptr = nil; r.len = 0; if (total == 0) { return r; }; - let buf: *u8 = os.alloc(total: u64): *u8; + let buf: *u8 = rt.alloc(total: u64): *u8; let off: i32 = 0; i = 0; for (i < strs.len) { @@ -209,7 +210,7 @@ export fn join(delim: str, strs: str...) str = { r.ptr = nil; r.len = 0; if (total == 0) { return r; }; - let buf: *u8 = os.alloc(total: u64): *u8; + let buf: *u8 = rt.alloc(total: u64): *u8; let off: i32 = 0; i = 0; for (i < strs.len) { @@ -886,7 +887,7 @@ export fn lpad(s: str, p: rune, maxlen: i32) str = { if (s.len >= maxlen) { return dup(s); }; let scratch: [4]u8; let pad: []u8 = runebytes(scratch[0:4], p); - let buf: *u8 = os.alloc(maxlen: u64): *u8; + let buf: *u8 = rt.alloc(maxlen: u64): *u8; let padwrite: i32 = (maxlen - s.len) * pad.len; if (padwrite > maxlen) { padwrite = maxlen; }; let off: i32 = 0; @@ -969,7 +970,7 @@ export fn rpad(s: str, p: rune, maxlen: i32) str = { if (s.len >= maxlen) { return dup(s); }; let scratch: [4]u8; let pad: []u8 = runebytes(scratch[0:4], p); - let buf: *u8 = os.alloc(maxlen: u64): *u8; + let buf: *u8 = rt.alloc(maxlen: u64): *u8; let k: i32 = 0; for (k < s.len) { buf[k] = s[k]; diff --git a/selfhost/cmd/w6a/asm.ww b/selfhost/cmd/w6a/asm.ww index d7def543..05fa9133 100644 --- a/selfhost/cmd/w6a/asm.ww +++ b/selfhost/cmd/w6a/asm.ww @@ -12,6 +12,7 @@ package w6a; import os; +import rt; import mem; import types; @@ -22,7 +23,7 @@ export fn emitbyte(a: *asm_, b: u8) void = { let nc: u64 = a.textcap; if (nc == 0u64) { nc = 4096u64; }; nc = nc * 2u64; - let nb: *u8 = os.alloc(nc): *u8; + let nb: *u8 = rt.alloc(nc): *u8; let i: u64 = 0u64; for (i < a.textlen) { nb[i] = a.text[i]; i += 1u64; }; a.text = nb; @@ -59,7 +60,7 @@ export fn emitdatabyte(a: *asm_, b: u8) void = { let nc: u64 = a.datacap; if (nc == 0u64) { nc = 256u64; }; nc = nc * 2u64; - let nb: *u8 = os.alloc(nc): *u8; + let nb: *u8 = rt.alloc(nc): *u8; let i: u64 = 0u64; for (i < a.datalen) { nb[i] = a.data[i]; i += 1u64; }; a.data = nb; diff --git a/selfhost/cmd/w6a/main.combined.ww b/selfhost/cmd/w6a/main.combined.ww index ed8cb8d0..783cfccb 100644 --- a/selfhost/cmd/w6a/main.combined.ww +++ b/selfhost/cmd/w6a/main.combined.ww @@ -110,28 +110,6 @@ import time; @symbol("rt_syscall") fn syscall3(num: nr, a: i64, b: i64, c: i64) i64; @symbol("rt_syscall") fn syscall4(num: nr, a: i64, b: i64, c: i64, d: i64) i64; -// alloc / free — runtime mmap-backed page allocator. Untyped: -// `alloc(n)` returns a `*void` and `free(p, n)` requires the byte -// count back because rt_free is munmap-based and doesn't track -// mapping sizes (the kernel needs the length to release the -// reservation). -// -// Diverges from Hare. Hare exposes `alloc` / `free` as typed -// language builtins (`alloc(value, cap)?` / `free(ptr)`) that the -// compiler lowers to rt::malloc/rt::free; ww has no such builtins, -// so the rt-symbol surface is exposed directly. Stdlib callers -// that need a typed allocation pattern wrap this with a cast plus -// a stored capacity (see [[strings.dup]], [[memio.dynamic]]). -// -// OOM: rt_alloc is a bare mmap(MAP_ANON|MAP_PRIVATE) wrapper with -// no error path. The raw Linux mmap syscall returns a negative -// errno cast to `*void` on failure (e.g. `(void*)-12` for ENOMEM); -// the `MAP_FAILED` (`(void*)-1`) value is a libc-wrapper convention -// that rt_alloc doesn't apply. Neither `== nil` nor `== (void*)-1` -// catches it; any deref of such a return faults. Today the stdlib -// does not check; OOM faults on first dereference. A typed -// fallible variant is a future task. -@symbol("rt_alloc") export fn alloc(n: u64) *void; @symbol("rt_free") export fn free(p: *void, n: u64) void; @symbol("rt_abort") fn abort(msg: str) void; @@ -749,6 +727,29 @@ export fn exists(path: str) bool = { return r >= 0i64; }; +// rt — runtime primitives exposed to ww programs. +// Mirrors Hare's rt:: module placement (ref/hare/rt/). + +package rt; + +// alloc — mmap-backed page allocator. Untyped: `alloc(n)` returns a +// `*void`; callers cast to the target type. Diverges from Hare: Hare +// exposes `alloc` / `free` as typed language builtins that the +// compiler lowers to rt::malloc/rt::free; ww has no such builtins, +// so the rt-symbol surface is exposed directly. Stdlib callers that +// need a typed allocation pattern wrap this with a cast plus a stored +// capacity (see [[strings.dup]], [[memio.dynamic]]). +// +// OOM: rt_alloc is a bare mmap(MAP_ANON|MAP_PRIVATE) wrapper with no +// error path. The raw Linux mmap syscall returns a negative errno cast +// to `*void` on failure (e.g. `(void*)-12` for ENOMEM); the +// `MAP_FAILED` (`(void*)-1`) value is a libc-wrapper convention that +// rt_alloc doesn't apply. Neither `== nil` nor `== (void*)-1` catches +// it; any deref of such a return faults. Today the stdlib does not +// check; OOM faults on first dereference. A typed fallible variant is +// a future task (task #39). ref/hare/rt/malloc.ha:27. +@symbol("rt_alloc") export fn alloc(n: u64) *void; + // selfhost/cmd/wcc/mem.ww — port of cmd/wcc/mem.c. // // Bump arena allocator. Backed by the runtime page allocator @@ -762,6 +763,7 @@ export fn exists(path: str) bool = { package wcc; import os; +import rt; def ALIGN: u64 = 16u64; def INIT_CHUNK: u64 = 65536u64; @@ -781,8 +783,8 @@ fn roundup(n: u64, a: u64) u64 = { }; export fn newarena() *arena = { - let a: *arena = os.alloc(ARENA_SZ): *arena; - a.buf = os.alloc(INIT_CHUNK): *u8; + let a: *arena = rt.alloc(ARENA_SZ): *arena; + a.buf = rt.alloc(INIT_CHUNK): *u8; a.off = 0u64; a.cap = INIT_CHUNK; a.next = nil; @@ -799,14 +801,14 @@ fn grow(a: *arena, need: u64) bool = { if (want > MAX_CHUNK) { want = MAX_CHUNK; }; if (want < need) { return false; }; // single allocation too big - let old: *arena = os.alloc(ARENA_SZ): *arena; + let old: *arena = rt.alloc(ARENA_SZ): *arena; old.buf = a.buf; old.off = a.off; old.cap = a.cap; old.next = a.next; old.total = 0u64; - a.buf = os.alloc(want): *u8; + a.buf = rt.alloc(want): *u8; a.off = 0u64; a.cap = want; a.next = old; @@ -1747,6 +1749,7 @@ export fn parse(a: *asm_) i32 = { package w6a; import os; +import rt; import mem; import types; @@ -1757,7 +1760,7 @@ export fn emitbyte(a: *asm_, b: u8) void = { let nc: u64 = a.textcap; if (nc == 0u64) { nc = 4096u64; }; nc = nc * 2u64; - let nb: *u8 = os.alloc(nc): *u8; + let nb: *u8 = rt.alloc(nc): *u8; let i: u64 = 0u64; for (i < a.textlen) { nb[i] = a.text[i]; i += 1u64; }; a.text = nb; @@ -1794,7 +1797,7 @@ export fn emitdatabyte(a: *asm_, b: u8) void = { let nc: u64 = a.datacap; if (nc == 0u64) { nc = 256u64; }; nc = nc * 2u64; - let nb: *u8 = os.alloc(nc): *u8; + let nb: *u8 = rt.alloc(nc): *u8; let i: u64 = 0u64; for (i < a.datalen) { nb[i] = a.data[i]; i += 1u64; }; a.data = nb; @@ -3009,6 +3012,7 @@ export fn emitelf(a: *asm_, fd: i32) i32 = { package main; import os; +import rt; import mem; import types; import lex; @@ -3054,7 +3058,7 @@ fn slurp(path: *u8) (*u8, u64) = { case let e: os.oserror => { os.close(fd); return nil, 0u64; }; }; let nz: u64 = n: u64; - let buf: *u8 = os.alloc(nz + 1u64): *u8; + let buf: *u8 = rt.alloc(nz + 1u64): *u8; let rr: (i64 | os.oserror) = os.readall(fd, buf, nz); os.close(fd); let got: i64 = 0i64; diff --git a/selfhost/cmd/w6a/main.ww b/selfhost/cmd/w6a/main.ww index db0920be..e9a4eda6 100644 --- a/selfhost/cmd/w6a/main.ww +++ b/selfhost/cmd/w6a/main.ww @@ -7,6 +7,7 @@ package main; import os; +import rt; import mem; import types; import lex; @@ -52,7 +53,7 @@ fn slurp(path: *u8) (*u8, u64) = { case let e: os.oserror => { os.close(fd); return nil, 0u64; }; }; let nz: u64 = n: u64; - let buf: *u8 = os.alloc(nz + 1u64): *u8; + let buf: *u8 = rt.alloc(nz + 1u64): *u8; let rr: (i64 | os.oserror) = os.readall(fd, buf, nz); os.close(fd); let got: i64 = 0i64; diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 39a9967b..ac6c39a4 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -110,28 +110,6 @@ import time; @symbol("rt_syscall") fn syscall3(num: nr, a: i64, b: i64, c: i64) i64; @symbol("rt_syscall") fn syscall4(num: nr, a: i64, b: i64, c: i64, d: i64) i64; -// alloc / free — runtime mmap-backed page allocator. Untyped: -// `alloc(n)` returns a `*void` and `free(p, n)` requires the byte -// count back because rt_free is munmap-based and doesn't track -// mapping sizes (the kernel needs the length to release the -// reservation). -// -// Diverges from Hare. Hare exposes `alloc` / `free` as typed -// language builtins (`alloc(value, cap)?` / `free(ptr)`) that the -// compiler lowers to rt::malloc/rt::free; ww has no such builtins, -// so the rt-symbol surface is exposed directly. Stdlib callers -// that need a typed allocation pattern wrap this with a cast plus -// a stored capacity (see [[strings.dup]], [[memio.dynamic]]). -// -// OOM: rt_alloc is a bare mmap(MAP_ANON|MAP_PRIVATE) wrapper with -// no error path. The raw Linux mmap syscall returns a negative -// errno cast to `*void` on failure (e.g. `(void*)-12` for ENOMEM); -// the `MAP_FAILED` (`(void*)-1`) value is a libc-wrapper convention -// that rt_alloc doesn't apply. Neither `== nil` nor `== (void*)-1` -// catches it; any deref of such a return faults. Today the stdlib -// does not check; OOM faults on first dereference. A typed -// fallible variant is a future task. -@symbol("rt_alloc") export fn alloc(n: u64) *void; @symbol("rt_free") export fn free(p: *void, n: u64) void; @symbol("rt_abort") fn abort(msg: str) void; @@ -749,6 +727,29 @@ export fn exists(path: str) bool = { return r >= 0i64; }; +// rt — runtime primitives exposed to ww programs. +// Mirrors Hare's rt:: module placement (ref/hare/rt/). + +package rt; + +// alloc — mmap-backed page allocator. Untyped: `alloc(n)` returns a +// `*void`; callers cast to the target type. Diverges from Hare: Hare +// exposes `alloc` / `free` as typed language builtins that the +// compiler lowers to rt::malloc/rt::free; ww has no such builtins, +// so the rt-symbol surface is exposed directly. Stdlib callers that +// need a typed allocation pattern wrap this with a cast plus a stored +// capacity (see [[strings.dup]], [[memio.dynamic]]). +// +// OOM: rt_alloc is a bare mmap(MAP_ANON|MAP_PRIVATE) wrapper with no +// error path. The raw Linux mmap syscall returns a negative errno cast +// to `*void` on failure (e.g. `(void*)-12` for ENOMEM); the +// `MAP_FAILED` (`(void*)-1`) value is a libc-wrapper convention that +// rt_alloc doesn't apply. Neither `== nil` nor `== (void*)-1` catches +// it; any deref of such a return faults. Today the stdlib does not +// check; OOM faults on first dereference. A typed fallible variant is +// a future task (task #39). ref/hare/rt/malloc.ha:27. +@symbol("rt_alloc") export fn alloc(n: u64) *void; + // selfhost/cmd/wcc/mem.ww — port of cmd/wcc/mem.c. // // Bump arena allocator. Backed by the runtime page allocator @@ -762,6 +763,7 @@ export fn exists(path: str) bool = { package wcc; import os; +import rt; def ALIGN: u64 = 16u64; def INIT_CHUNK: u64 = 65536u64; @@ -781,8 +783,8 @@ fn roundup(n: u64, a: u64) u64 = { }; export fn newarena() *arena = { - let a: *arena = os.alloc(ARENA_SZ): *arena; - a.buf = os.alloc(INIT_CHUNK): *u8; + let a: *arena = rt.alloc(ARENA_SZ): *arena; + a.buf = rt.alloc(INIT_CHUNK): *u8; a.off = 0u64; a.cap = INIT_CHUNK; a.next = nil; @@ -799,14 +801,14 @@ fn grow(a: *arena, need: u64) bool = { if (want > MAX_CHUNK) { want = MAX_CHUNK; }; if (want < need) { return false; }; // single allocation too big - let old: *arena = os.alloc(ARENA_SZ): *arena; + let old: *arena = rt.alloc(ARENA_SZ): *arena; old.buf = a.buf; old.off = a.off; old.cap = a.cap; old.next = a.next; old.total = 0u64; - a.buf = os.alloc(want): *u8; + a.buf = rt.alloc(want): *u8; a.off = 0u64; a.cap = want; a.next = old; @@ -1864,6 +1866,7 @@ package strings; import bytes; import encoding.utf8; import os; +import rt; import types; // toutf8 — borrowed []u8 view of `s`. ref/hare/strings/utf8.ha:29. @@ -1935,7 +1938,7 @@ export fn dup(s: str) str = { r.ptr = nil; r.len = 0; if (s.len == 0) { return r; }; - let buf: *u8 = os.alloc(s.len: u64): *u8; + let buf: *u8 = rt.alloc(s.len: u64): *u8; let i: i32 = 0; for (i < s.len) { buf[i] = s[i]; i += 1; }; r.ptr = buf; @@ -2013,7 +2016,7 @@ export fn concat(strs: str...) str = { r.ptr = nil; r.len = 0; if (total == 0) { return r; }; - let buf: *u8 = os.alloc(total: u64): *u8; + let buf: *u8 = rt.alloc(total: u64): *u8; let off: i32 = 0; i = 0; for (i < strs.len) { @@ -2046,7 +2049,7 @@ export fn join(delim: str, strs: str...) str = { r.ptr = nil; r.len = 0; if (total == 0) { return r; }; - let buf: *u8 = os.alloc(total: u64): *u8; + let buf: *u8 = rt.alloc(total: u64): *u8; let off: i32 = 0; i = 0; for (i < strs.len) { @@ -2723,7 +2726,7 @@ export fn lpad(s: str, p: rune, maxlen: i32) str = { if (s.len >= maxlen) { return dup(s); }; let scratch: [4]u8; let pad: []u8 = runebytes(scratch[0:4], p); - let buf: *u8 = os.alloc(maxlen: u64): *u8; + let buf: *u8 = rt.alloc(maxlen: u64): *u8; let padwrite: i32 = (maxlen - s.len) * pad.len; if (padwrite > maxlen) { padwrite = maxlen; }; let off: i32 = 0; @@ -2806,7 +2809,7 @@ export fn rpad(s: str, p: rune, maxlen: i32) str = { if (s.len >= maxlen) { return dup(s); }; let scratch: [4]u8; let pad: []u8 = runebytes(scratch[0:4], p); - let buf: *u8 = os.alloc(maxlen: u64): *u8; + let buf: *u8 = rt.alloc(maxlen: u64): *u8; let k: i32 = 0; for (k < s.len) { buf[k] = s[k]; @@ -7901,15 +7904,15 @@ fn astoffset(c: *checker, dot: *node) i64 = { if (dot.kind != nkind.N_DOT) { return -1i64; }; let recv: *node = scruttype(c, dot.lhs); if (recv == nil) { return -1i64; }; - let rt: *node = resolvealias(c, unwrapbang(recv)); - if (rt == nil) { return -1i64; }; - if (rt.kind == nkind.N_TPTR) { - rt = resolvealias(c, unwrapbang(rt.lhs)); + let rtyp: *node = resolvealias(c, unwrapbang(recv)); + if (rtyp == nil) { return -1i64; }; + if (rtyp.kind == nkind.N_TPTR) { + rtyp = resolvealias(c, unwrapbang(rtyp.lhs)); }; - if (rt == nil) { return -1i64; }; - if (rt.kind != nkind.N_TSTRUCT) { return -1i64; }; + if (rtyp == nil) { return -1i64; }; + if (rtyp.kind != nkind.N_TSTRUCT) { return -1i64; }; let off: i64 = 0i64; - let f: *node = rt.list; + let f: *node = rtyp.list; for (f != nil) { if (f.kind == nkind.N_TFIELD) { let fa: i64 = astalign(c, f.lhs); @@ -9727,9 +9730,9 @@ export fn taggedcallslot(c: *cgen, n: *node) i32 = { let callee: *node = n.lhs; if (callee == nil) { return 0; }; if (callee.kind != nkind.N_IDENT) { return 0; }; - let rt: *node = fnretlookup(c, callee.str); - if (!istaggedtype(c, rt)) { return 0; }; - return slotsize(c, rt); + let rtyp: *node = fnretlookup(c, callee.str); + if (!istaggedtype(c, rtyp)) { return 0; }; + return slotsize(c, rtyp); }; fn nodeisslice(c: *cgen, n: *node) bool = { @@ -9758,8 +9761,8 @@ fn nodeisslice(c: *cgen, n: *node) bool = { let callee: *node = n.lhs; if (callee != nil) { if (callee.kind == nkind.N_IDENT) { - let rt: *node = fnretlookupmod(c, callee.str, c.curmod); - return isslicetype(c, rt); + let rtyp: *node = fnretlookupmod(c, callee.str, c.curmod); + return isslicetype(c, rtyp); }; if (callee.kind == nkind.N_DOT) { let cmod: str; @@ -9769,8 +9772,8 @@ fn nodeisslice(c: *cgen, n: *node) bool = { cmod = callee.lhs.str; }; }; - let rt: *node = fnretlookupmod(c, callee.str, cmod); - return isslicetype(c, rt); + let rtyp: *node = fnretlookupmod(c, callee.str, cmod); + return isslicetype(c, rtyp); }; }; return false; @@ -9885,8 +9888,8 @@ fn nodeisstr(c: *cgen, n: *node) bool = { let callee: *node = n.lhs; if (callee != nil) { if (callee.kind == nkind.N_IDENT) { - let rt: *node = fnretlookupmod(c, callee.str, c.curmod); - return isstrtype(c, rt); + let rtyp: *node = fnretlookupmod(c, callee.str, c.curmod); + return isstrtype(c, rtyp); }; // #34: cross-module N_DOT — route through fnretlookupmod // so a same-leaf caller-module fn (different return shape) @@ -9899,8 +9902,8 @@ fn nodeisstr(c: *cgen, n: *node) bool = { cmod = callee.lhs.str; }; }; - let rt: *node = fnretlookupmod(c, callee.str, cmod); - return isstrtype(c, rt); + let rtyp: *node = fnretlookupmod(c, callee.str, cmod); + return isstrtype(c, rtyp); }; }; return false; @@ -10816,8 +10819,8 @@ export fn callsretsize(c: *cgen, n: *node) i32 = { }; }; if (cn.len == 0) { return 0; }; - let rt: *node = fnretlookupmod(c, cn, cmod); - return sretretsize(c, rt); + let rtyp: *node = fnretlookupmod(c, cn, cmod); + return sretretsize(c, rtyp); }; fn structlookup(c: *cgen, name: str) *structinfo = { @@ -11074,16 +11077,16 @@ fn inferletcalltype(c: *cgen, rhs: *node) *node = { }; }; if (cname.len == 0) { return nil; }; - let rt: *node = fnretlookupmod(c, cname, cmod); - if (rt == nil) { return nil; }; + let rtyp: *node = fnretlookupmod(c, cname, cmod); + if (rtyp == nil) { return nil; }; if (unwrap) { // Strip error variants — success type is the first // variant of the tagged return. - if (rt.kind != nkind.N_TTAGGED) { return nil; }; - return rt.list; + if (rtyp.kind != nkind.N_TTAGGED) { return nil; }; + return rtyp.list; }; // Plain call: declared return type is the local's type. - return rt; + return rtyp; }; // letslotsize — slot size for a `let` binding. Like slotsize, but @@ -11550,8 +11553,8 @@ fn matchscrutt(c: *cgen, scrut: *node) *node = { }; }; if (cnm.len > 0) { - let rt: *node = fnretlookupmod(c, cnm, cmod); - if (rt != nil) { return resolvetagged(c, rt); }; + let rtyp: *node = fnretlookupmod(c, cnm, cmod); + if (rtyp != nil) { return resolvetagged(c, rtyp); }; }; }; return nil; @@ -11745,9 +11748,9 @@ export fn exprfloatkind(c: *cgen, n: *node) i32 = { if (n.lhs.kind == nkind.N_IDENT) { nm = n.lhs.str; }; }; if (nm.len > 0) { - let rt: *node = fnretlookup(c, nm); - if (isf32type(c, rt)) { return 1; }; - if (isfloattype(c, rt)) { return 2; }; + let rtyp: *node = fnretlookup(c, nm); + if (isf32type(c, rtyp)) { return 1; }; + if (isfloattype(c, rtyp)) { return 2; }; }; return 0; }; @@ -12266,9 +12269,9 @@ fn rhstaggedabicall(c: *cgen, src: *node) bool = { }; }; if (calleename.len > 0) { - let rt: *node = fnretlookupmod(c, calleename, cmod); - if (rt != nil) { - if (istaggedtype(c, rt)) { return true; }; + let rtyp: *node = fnretlookupmod(c, calleename, cmod); + if (rtyp != nil) { + if (istaggedtype(c, rtyp)) { return true; }; }; }; }; @@ -13380,10 +13383,10 @@ fn cgtryprop(c: *cgen, n: *node) void = { }; }; if (cname.len > 0) { - let rt: *node = fnretlookupmod(c, cname, cmod); - if (rt != nil) { - if (rt.kind == nkind.N_TTAGGED) { - let first: *node = rt.list; + let rtyp: *node = fnretlookupmod(c, cname, cmod); + if (rtyp != nil) { + if (rtyp.kind == nkind.N_TTAGGED) { + let first: *node = rtyp.list; if (first != nil) { if (isstrtype(c, first)) { succisstr = true; @@ -13436,10 +13439,10 @@ fn cgtryunw(c: *cgen, n: *node) void = { }; }; if (cname.len > 0) { - let rt: *node = fnretlookupmod(c, cname, cmod); - if (rt != nil) { - if (rt.kind == nkind.N_TTAGGED) { - let first: *node = rt.list; + let rtyp: *node = fnretlookupmod(c, cname, cmod); + if (rtyp != nil) { + if (rtyp.kind == nkind.N_TTAGGED) { + let first: *node = rtyp.list; if (first != nil) { if (isstrtype(c, first)) { succisstr = true; @@ -13816,8 +13819,8 @@ fn cgident(c: *cgen, n: *node) void = { // in one go, so a body-less FFI binding emits the C symbol // it was declared with via @symbol(), not the ww-side ident. // Bare ident → same-module by ww's resolver, hint with c.curmod. - let rt: *node = fnretlookup(c, nm); - if (rt != nil) { + let rtyp: *node = fnretlookup(c, nm); + if (rtyp != nil) { emitline("\tLEAQ\t"); emitfnname(c, nm, c.curmod); emitline("(SB), AX\n"); @@ -16630,8 +16633,8 @@ fn cgcall(c: *cgen, n: *node) void = { }; }; }; - let rt: *node = fnretlookupmod(c, calleename, cmod); - if (isstrtype(c, rt)) { + let rtyp: *node = fnretlookupmod(c, calleename, cmod); + if (isstrtype(c, rtyp)) { emitline("\tMOVQ\tDX, BX\n"); }; }; @@ -19193,8 +19196,8 @@ fn cgreturn(c: *cgen, n: *node) void = { }; }; if (calleename.len > 0) { - let rt: *node = fnretlookupmod(c, calleename, cmod); - if (istaggedtype(c, rt)) { forwardtagged = true; }; + let rtyp: *node = fnretlookupmod(c, calleename, cmod); + if (istaggedtype(c, rtyp)) { forwardtagged = true; }; }; }; }; @@ -20234,10 +20237,10 @@ fn cgmlet(c: *cgen, n: *node) void = { }; }; if (cnm.len > 0) { - let rt: *node = fnretlookupmod(c, cnm, cmod); - if (rt != nil) { - if (rt.kind == nkind.N_TTUPLE) { - p0t = rt.list; + let rtyp: *node = fnretlookupmod(c, cnm, cmod); + if (rtyp != nil) { + if (rtyp.kind == nkind.N_TTUPLE) { + p0t = rtyp.list; if (p0t != nil) { p1t = p0t.next; }; }; }; @@ -23165,6 +23168,7 @@ export fn fargregname(i: i32) str = { package main; import os; +import rt; import mem; import tok; import lex; @@ -23213,7 +23217,7 @@ fn slurp(path: *u8) (*u8, u64) = { case let e: os.oserror => { os.close(fd); return nil, 0u64; }; }; let nz: u64 = n: u64; - let buf: *u8 = os.alloc(nz + 1u64): *u8; + let buf: *u8 = rt.alloc(nz + 1u64): *u8; let rr: (i64 | os.oserror) = os.readall(fd, buf, nz); os.close(fd); let got: i64 = 0i64; diff --git a/selfhost/cmd/w6c/main.ww b/selfhost/cmd/w6c/main.ww index 6db6ed5b..e4a33ab0 100644 --- a/selfhost/cmd/w6c/main.ww +++ b/selfhost/cmd/w6c/main.ww @@ -13,6 +13,7 @@ package main; import os; +import rt; import mem; import tok; import lex; @@ -61,7 +62,7 @@ fn slurp(path: *u8) (*u8, u64) = { case let e: os.oserror => { os.close(fd); return nil, 0u64; }; }; let nz: u64 = n: u64; - let buf: *u8 = os.alloc(nz + 1u64): *u8; + let buf: *u8 = rt.alloc(nz + 1u64): *u8; let rr: (i64 | os.oserror) = os.readall(fd, buf, nz); os.close(fd); let got: i64 = 0i64; diff --git a/selfhost/cmd/w6l/dyn.ww b/selfhost/cmd/w6l/dyn.ww index 135f88fd..778651d8 100644 --- a/selfhost/cmd/w6l/dyn.ww +++ b/selfhost/cmd/w6l/dyn.ww @@ -11,6 +11,7 @@ package w6l; import os; +import rt; import mem; import sym; @@ -134,7 +135,7 @@ fn slurpso(path: *u8) (*u8, u64) = { case let v: i64 => n = v; case let e: os.oserror => { os.close(fd); return nil, 0u64; }; }; - let buf: *u8 = os.alloc(n: u64): *u8; + let buf: *u8 = rt.alloc(n: u64): *u8; let rr: (i64 | os.oserror) = os.readall(fd, buf, n: u64); os.close(fd); let got: i64 = 0i64; diff --git a/selfhost/cmd/w6l/dynout.ww b/selfhost/cmd/w6l/dynout.ww index 8b767039..5574a1da 100644 --- a/selfhost/cmd/w6l/dynout.ww +++ b/selfhost/cmd/w6l/dynout.ww @@ -25,6 +25,7 @@ package w6l; import os; +import rt; import mem; import sym; @@ -657,7 +658,7 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = { }; // ---- assemble file buffer ---- - let filebuf: *u8 = os.alloc(fileend): *u8; + let filebuf: *u8 = rt.alloc(fileend): *u8; if (filebuf == nil) { os.write(2, "w6l: out of memory\n".ptr, 18u64); return 1; diff --git a/selfhost/cmd/w6l/main.combined.ww b/selfhost/cmd/w6l/main.combined.ww index dacadd86..3d3774af 100644 --- a/selfhost/cmd/w6l/main.combined.ww +++ b/selfhost/cmd/w6l/main.combined.ww @@ -110,28 +110,6 @@ import time; @symbol("rt_syscall") fn syscall3(num: nr, a: i64, b: i64, c: i64) i64; @symbol("rt_syscall") fn syscall4(num: nr, a: i64, b: i64, c: i64, d: i64) i64; -// alloc / free — runtime mmap-backed page allocator. Untyped: -// `alloc(n)` returns a `*void` and `free(p, n)` requires the byte -// count back because rt_free is munmap-based and doesn't track -// mapping sizes (the kernel needs the length to release the -// reservation). -// -// Diverges from Hare. Hare exposes `alloc` / `free` as typed -// language builtins (`alloc(value, cap)?` / `free(ptr)`) that the -// compiler lowers to rt::malloc/rt::free; ww has no such builtins, -// so the rt-symbol surface is exposed directly. Stdlib callers -// that need a typed allocation pattern wrap this with a cast plus -// a stored capacity (see [[strings.dup]], [[memio.dynamic]]). -// -// OOM: rt_alloc is a bare mmap(MAP_ANON|MAP_PRIVATE) wrapper with -// no error path. The raw Linux mmap syscall returns a negative -// errno cast to `*void` on failure (e.g. `(void*)-12` for ENOMEM); -// the `MAP_FAILED` (`(void*)-1`) value is a libc-wrapper convention -// that rt_alloc doesn't apply. Neither `== nil` nor `== (void*)-1` -// catches it; any deref of such a return faults. Today the stdlib -// does not check; OOM faults on first dereference. A typed -// fallible variant is a future task. -@symbol("rt_alloc") export fn alloc(n: u64) *void; @symbol("rt_free") export fn free(p: *void, n: u64) void; @symbol("rt_abort") fn abort(msg: str) void; @@ -749,6 +727,29 @@ export fn exists(path: str) bool = { return r >= 0i64; }; +// rt — runtime primitives exposed to ww programs. +// Mirrors Hare's rt:: module placement (ref/hare/rt/). + +package rt; + +// alloc — mmap-backed page allocator. Untyped: `alloc(n)` returns a +// `*void`; callers cast to the target type. Diverges from Hare: Hare +// exposes `alloc` / `free` as typed language builtins that the +// compiler lowers to rt::malloc/rt::free; ww has no such builtins, +// so the rt-symbol surface is exposed directly. Stdlib callers that +// need a typed allocation pattern wrap this with a cast plus a stored +// capacity (see [[strings.dup]], [[memio.dynamic]]). +// +// OOM: rt_alloc is a bare mmap(MAP_ANON|MAP_PRIVATE) wrapper with no +// error path. The raw Linux mmap syscall returns a negative errno cast +// to `*void` on failure (e.g. `(void*)-12` for ENOMEM); the +// `MAP_FAILED` (`(void*)-1`) value is a libc-wrapper convention that +// rt_alloc doesn't apply. Neither `== nil` nor `== (void*)-1` catches +// it; any deref of such a return faults. Today the stdlib does not +// check; OOM faults on first dereference. A typed fallible variant is +// a future task (task #39). ref/hare/rt/malloc.ha:27. +@symbol("rt_alloc") export fn alloc(n: u64) *void; + // selfhost/cmd/wcc/mem.ww — port of cmd/wcc/mem.c. // // Bump arena allocator. Backed by the runtime page allocator @@ -762,6 +763,7 @@ export fn exists(path: str) bool = { package wcc; import os; +import rt; def ALIGN: u64 = 16u64; def INIT_CHUNK: u64 = 65536u64; @@ -781,8 +783,8 @@ fn roundup(n: u64, a: u64) u64 = { }; export fn newarena() *arena = { - let a: *arena = os.alloc(ARENA_SZ): *arena; - a.buf = os.alloc(INIT_CHUNK): *u8; + let a: *arena = rt.alloc(ARENA_SZ): *arena; + a.buf = rt.alloc(INIT_CHUNK): *u8; a.off = 0u64; a.cap = INIT_CHUNK; a.next = nil; @@ -799,14 +801,14 @@ fn grow(a: *arena, need: u64) bool = { if (want > MAX_CHUNK) { want = MAX_CHUNK; }; if (want < need) { return false; }; // single allocation too big - let old: *arena = os.alloc(ARENA_SZ): *arena; + let old: *arena = rt.alloc(ARENA_SZ): *arena; old.buf = a.buf; old.off = a.off; old.cap = a.cap; old.next = a.next; old.total = 0u64; - a.buf = os.alloc(want): *u8; + a.buf = rt.alloc(want): *u8; a.off = 0u64; a.cap = want; a.next = old; @@ -983,6 +985,7 @@ export fn lookup(l: *lnk, name: str) *lsym = { package w6l; import os; +import rt; import mem; import sym; @@ -1058,7 +1061,7 @@ fn slurp(path: *u8) (*u8, u64) = { case let v: i64 => n = v; case let e: os.oserror => { os.close(fd); return nil, 0u64; }; }; - let buf: *u8 = os.alloc(n: u64): *u8; + let buf: *u8 = rt.alloc(n: u64): *u8; let rr: (i64 | os.oserror) = os.readall(fd, buf, n: u64); os.close(fd); let got: i64 = 0i64; @@ -1080,7 +1083,7 @@ fn emittext(l: *lnk, src: *u8, n: u64) void = { // Grow by mmap'ing a fresh region and copying. The old buffer // is leaked into the page allocator; for a linker run this is // trivial waste. - let nb: *u8 = os.alloc(nc): *u8; + let nb: *u8 = rt.alloc(nc): *u8; let i: u64 = 0u64; for (i < l.textlen) { nb[i] = l.text[i]; @@ -1102,7 +1105,7 @@ fn emitdata(l: *lnk, src: *u8, n: u64) void = { let nc: u64 = l.datacap; if (nc == 0u64) { nc = 256u64; }; for (nc < l.datalen + n) { nc = nc * 2u64; }; - let nb: *u8 = os.alloc(nc): *u8; + let nb: *u8 = rt.alloc(nc): *u8; let i: u64 = 0u64; for (i < l.datalen) { nb[i] = l.data[i]; @@ -1594,6 +1597,7 @@ fn loadimage(l: *lnk, path: *u8, buf: *u8, len: u64) i32 = { package w6l; import os; +import rt; import mem; import sym; @@ -1717,7 +1721,7 @@ fn slurpso(path: *u8) (*u8, u64) = { case let v: i64 => n = v; case let e: os.oserror => { os.close(fd); return nil, 0u64; }; }; - let buf: *u8 = os.alloc(n: u64): *u8; + let buf: *u8 = rt.alloc(n: u64): *u8; let rr: (i64 | os.oserror) = os.readall(fd, buf, n: u64); os.close(fd); let got: i64 = 0i64; @@ -2145,6 +2149,7 @@ export fn relocate(l: *lnk, textva: u64, datava: u64) i32 = { package w6l; import os; +import rt; import mem; import sym; @@ -2777,7 +2782,7 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = { }; // ---- assemble file buffer ---- - let filebuf: *u8 = os.alloc(fileend): *u8; + let filebuf: *u8 = rt.alloc(fileend): *u8; if (filebuf == nil) { os.write(2, "w6l: out of memory\n".ptr, 18u64); return 1; @@ -2889,6 +2894,7 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = { package w6l; import os; +import rt; import sym; import dynout; @@ -2967,7 +2973,7 @@ export fn emitelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = { let datafilelen: u64 = l.datalen - bsslen; // One contiguous header buffer covering [0..0x1000), then .text. - let hdr: *u8 = os.alloc(TEXT_OFF): *u8; // zero-initialised by mmap + let hdr: *u8 = rt.alloc(TEXT_OFF): *u8; // zero-initialised by mmap // --- Ehdr (64 bytes) --- hdr[0u64] = 127u8; // 0x7f @@ -3067,6 +3073,7 @@ export fn emitelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = { package main; import os; +import rt; import mem; import sym; import obj; @@ -3121,7 +3128,7 @@ fn appenddec(dst: *u8, i: u64, n: u64) u64 = { dst[i] = 48u8; return i + 1u64; }; - let buf: *u8 = os.alloc(16u64): *u8; + let buf: *u8 = rt.alloc(16u64): *u8; let k: u64 = 0u64; let v: u64 = n; for (v > 0u64) { @@ -3160,7 +3167,7 @@ fn buildpathv(dst: *u8, dir: *u8, name: *u8, v: u64) u64 = { fn islinkable(path: *u8) bool = { let fd: i32 = os.open(pathstr(path), os.flag.RDONLY, 0i32); if (fd < 0) { return false; }; - let mp: *u8 = os.alloc(8u64): *u8; + let mp: *u8 = rt.alloc(8u64): *u8; let n: i64 = os.read(fd, mp, 8u64); os.close(fd); if (n < 4i64) { return false; }; @@ -3188,7 +3195,7 @@ fn islinkable(path: *u8) bool = { // then lib.a. Return arena-owned NUL-terminated path on success, // nil on miss. fn resolvelib(a: *arena, name: *u8, libdirs: **u8, nlibdirs: i32) *u8 = { - let bufp: *u8 = os.alloc(1024u64): *u8; + let bufp: *u8 = rt.alloc(1024u64): *u8; let i: i32 = 0; for (i < nlibdirs) { let dir: *u8 = libdirs[i]; @@ -3229,7 +3236,7 @@ fn resolvelib(a: *arena, name: *u8, libdirs: **u8, nlibdirs: i32) *u8 = { fn isso(path: *u8) i32 = { let fd: i32 = os.open(pathstr(path), os.flag.RDONLY, 0i32); if (fd < 0) { return 0; }; - let mp: *u8 = os.alloc(20u64): *u8; + let mp: *u8 = rt.alloc(20u64): *u8; let n: i64 = os.read(fd, mp, 20u64); os.close(fd); if (n < 20i64) { return 0; }; @@ -3246,11 +3253,11 @@ fn isso(path: *u8) i32 = { export fn main(argc: i32, argv: **u8) i32 = { let outpath: *u8 = nil; let maxinputs: i32 = 64; - let inputs: **u8 = os.alloc((maxinputs: u64) * 8u64): **u8; + let inputs: **u8 = rt.alloc((maxinputs: u64) * 8u64): **u8; let ninputs: i32 = 0; - let libdirs: **u8 = os.alloc((maxinputs: u64) * 8u64): **u8; + let libdirs: **u8 = rt.alloc((maxinputs: u64) * 8u64): **u8; let nlibdirs: i32 = 0; - let lflags: **u8 = os.alloc((maxinputs: u64) * 8u64): **u8; + let lflags: **u8 = rt.alloc((maxinputs: u64) * 8u64): **u8; let nlflags: i32 = 0; let i: i32 = 1; diff --git a/selfhost/cmd/w6l/main.ww b/selfhost/cmd/w6l/main.ww index 13cb7af9..301d9a5f 100644 --- a/selfhost/cmd/w6l/main.ww +++ b/selfhost/cmd/w6l/main.ww @@ -9,6 +9,7 @@ package main; import os; +import rt; import mem; import sym; import obj; @@ -63,7 +64,7 @@ fn appenddec(dst: *u8, i: u64, n: u64) u64 = { dst[i] = 48u8; return i + 1u64; }; - let buf: *u8 = os.alloc(16u64): *u8; + let buf: *u8 = rt.alloc(16u64): *u8; let k: u64 = 0u64; let v: u64 = n; for (v > 0u64) { @@ -102,7 +103,7 @@ fn buildpathv(dst: *u8, dir: *u8, name: *u8, v: u64) u64 = { fn islinkable(path: *u8) bool = { let fd: i32 = os.open(pathstr(path), os.flag.RDONLY, 0i32); if (fd < 0) { return false; }; - let mp: *u8 = os.alloc(8u64): *u8; + let mp: *u8 = rt.alloc(8u64): *u8; let n: i64 = os.read(fd, mp, 8u64); os.close(fd); if (n < 4i64) { return false; }; @@ -130,7 +131,7 @@ fn islinkable(path: *u8) bool = { // then lib.a. Return arena-owned NUL-terminated path on success, // nil on miss. fn resolvelib(a: *arena, name: *u8, libdirs: **u8, nlibdirs: i32) *u8 = { - let bufp: *u8 = os.alloc(1024u64): *u8; + let bufp: *u8 = rt.alloc(1024u64): *u8; let i: i32 = 0; for (i < nlibdirs) { let dir: *u8 = libdirs[i]; @@ -171,7 +172,7 @@ fn resolvelib(a: *arena, name: *u8, libdirs: **u8, nlibdirs: i32) *u8 = { fn isso(path: *u8) i32 = { let fd: i32 = os.open(pathstr(path), os.flag.RDONLY, 0i32); if (fd < 0) { return 0; }; - let mp: *u8 = os.alloc(20u64): *u8; + let mp: *u8 = rt.alloc(20u64): *u8; let n: i64 = os.read(fd, mp, 20u64); os.close(fd); if (n < 20i64) { return 0; }; @@ -188,11 +189,11 @@ fn isso(path: *u8) i32 = { export fn main(argc: i32, argv: **u8) i32 = { let outpath: *u8 = nil; let maxinputs: i32 = 64; - let inputs: **u8 = os.alloc((maxinputs: u64) * 8u64): **u8; + let inputs: **u8 = rt.alloc((maxinputs: u64) * 8u64): **u8; let ninputs: i32 = 0; - let libdirs: **u8 = os.alloc((maxinputs: u64) * 8u64): **u8; + let libdirs: **u8 = rt.alloc((maxinputs: u64) * 8u64): **u8; let nlibdirs: i32 = 0; - let lflags: **u8 = os.alloc((maxinputs: u64) * 8u64): **u8; + let lflags: **u8 = rt.alloc((maxinputs: u64) * 8u64): **u8; let nlflags: i32 = 0; let i: i32 = 1; diff --git a/selfhost/cmd/w6l/obj.ww b/selfhost/cmd/w6l/obj.ww index 86a53d3a..1022116b 100644 --- a/selfhost/cmd/w6l/obj.ww +++ b/selfhost/cmd/w6l/obj.ww @@ -11,6 +11,7 @@ package w6l; import os; +import rt; import mem; import sym; @@ -86,7 +87,7 @@ fn slurp(path: *u8) (*u8, u64) = { case let v: i64 => n = v; case let e: os.oserror => { os.close(fd); return nil, 0u64; }; }; - let buf: *u8 = os.alloc(n: u64): *u8; + let buf: *u8 = rt.alloc(n: u64): *u8; let rr: (i64 | os.oserror) = os.readall(fd, buf, n: u64); os.close(fd); let got: i64 = 0i64; @@ -108,7 +109,7 @@ fn emittext(l: *lnk, src: *u8, n: u64) void = { // Grow by mmap'ing a fresh region and copying. The old buffer // is leaked into the page allocator; for a linker run this is // trivial waste. - let nb: *u8 = os.alloc(nc): *u8; + let nb: *u8 = rt.alloc(nc): *u8; let i: u64 = 0u64; for (i < l.textlen) { nb[i] = l.text[i]; @@ -130,7 +131,7 @@ fn emitdata(l: *lnk, src: *u8, n: u64) void = { let nc: u64 = l.datacap; if (nc == 0u64) { nc = 256u64; }; for (nc < l.datalen + n) { nc = nc * 2u64; }; - let nb: *u8 = os.alloc(nc): *u8; + let nb: *u8 = rt.alloc(nc): *u8; let i: u64 = 0u64; for (i < l.datalen) { nb[i] = l.data[i]; diff --git a/selfhost/cmd/w6l/out.ww b/selfhost/cmd/w6l/out.ww index ba3b3c3f..98693a28 100644 --- a/selfhost/cmd/w6l/out.ww +++ b/selfhost/cmd/w6l/out.ww @@ -10,6 +10,7 @@ package w6l; import os; +import rt; import sym; import dynout; @@ -88,7 +89,7 @@ export fn emitelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = { let datafilelen: u64 = l.datalen - bsslen; // One contiguous header buffer covering [0..0x1000), then .text. - let hdr: *u8 = os.alloc(TEXT_OFF): *u8; // zero-initialised by mmap + let hdr: *u8 = rt.alloc(TEXT_OFF): *u8; // zero-initialised by mmap // --- Ehdr (64 bytes) --- hdr[0u64] = 127u8; // 0x7f diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index e6a91ecd..0c31c537 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -171,10 +171,10 @@ fn cgtryprop(c: *cgen, n: *node) void = { }; }; if (cname.len > 0) { - let rt: *node = fnretlookupmod(c, cname, cmod); - if (rt != nil) { - if (rt.kind == nkind.N_TTAGGED) { - let first: *node = rt.list; + let rtyp: *node = fnretlookupmod(c, cname, cmod); + if (rtyp != nil) { + if (rtyp.kind == nkind.N_TTAGGED) { + let first: *node = rtyp.list; if (first != nil) { if (isstrtype(c, first)) { succisstr = true; @@ -227,10 +227,10 @@ fn cgtryunw(c: *cgen, n: *node) void = { }; }; if (cname.len > 0) { - let rt: *node = fnretlookupmod(c, cname, cmod); - if (rt != nil) { - if (rt.kind == nkind.N_TTAGGED) { - let first: *node = rt.list; + let rtyp: *node = fnretlookupmod(c, cname, cmod); + if (rtyp != nil) { + if (rtyp.kind == nkind.N_TTAGGED) { + let first: *node = rtyp.list; if (first != nil) { if (isstrtype(c, first)) { succisstr = true; @@ -607,8 +607,8 @@ fn cgident(c: *cgen, n: *node) void = { // in one go, so a body-less FFI binding emits the C symbol // it was declared with via @symbol(), not the ww-side ident. // Bare ident → same-module by ww's resolver, hint with c.curmod. - let rt: *node = fnretlookup(c, nm); - if (rt != nil) { + let rtyp: *node = fnretlookup(c, nm); + if (rtyp != nil) { emitline("\tLEAQ\t"); emitfnname(c, nm, c.curmod); emitline("(SB), AX\n"); @@ -3421,8 +3421,8 @@ fn cgcall(c: *cgen, n: *node) void = { }; }; }; - let rt: *node = fnretlookupmod(c, calleename, cmod); - if (isstrtype(c, rt)) { + let rtyp: *node = fnretlookupmod(c, calleename, cmod); + if (isstrtype(c, rtyp)) { emitline("\tMOVQ\tDX, BX\n"); }; }; diff --git a/selfhost/cmd/wcc/cgenstmt.ww b/selfhost/cmd/wcc/cgenstmt.ww index 228e9068..f1ed4ad6 100644 --- a/selfhost/cmd/wcc/cgenstmt.ww +++ b/selfhost/cmd/wcc/cgenstmt.ww @@ -184,8 +184,8 @@ fn cgreturn(c: *cgen, n: *node) void = { }; }; if (calleename.len > 0) { - let rt: *node = fnretlookupmod(c, calleename, cmod); - if (istaggedtype(c, rt)) { forwardtagged = true; }; + let rtyp: *node = fnretlookupmod(c, calleename, cmod); + if (istaggedtype(c, rtyp)) { forwardtagged = true; }; }; }; }; @@ -1225,10 +1225,10 @@ fn cgmlet(c: *cgen, n: *node) void = { }; }; if (cnm.len > 0) { - let rt: *node = fnretlookupmod(c, cnm, cmod); - if (rt != nil) { - if (rt.kind == nkind.N_TTUPLE) { - p0t = rt.list; + let rtyp: *node = fnretlookupmod(c, cnm, cmod); + if (rtyp != nil) { + if (rtyp.kind == nkind.N_TTUPLE) { + p0t = rtyp.list; if (p0t != nil) { p1t = p0t.next; }; }; }; diff --git a/selfhost/cmd/wcc/cgenutil.ww b/selfhost/cmd/wcc/cgenutil.ww index 63e85775..bccebbfb 100644 --- a/selfhost/cmd/wcc/cgenutil.ww +++ b/selfhost/cmd/wcc/cgenutil.ww @@ -516,9 +516,9 @@ export fn taggedcallslot(c: *cgen, n: *node) i32 = { let callee: *node = n.lhs; if (callee == nil) { return 0; }; if (callee.kind != nkind.N_IDENT) { return 0; }; - let rt: *node = fnretlookup(c, callee.str); - if (!istaggedtype(c, rt)) { return 0; }; - return slotsize(c, rt); + let rtyp: *node = fnretlookup(c, callee.str); + if (!istaggedtype(c, rtyp)) { return 0; }; + return slotsize(c, rtyp); }; fn nodeisslice(c: *cgen, n: *node) bool = { @@ -547,8 +547,8 @@ fn nodeisslice(c: *cgen, n: *node) bool = { let callee: *node = n.lhs; if (callee != nil) { if (callee.kind == nkind.N_IDENT) { - let rt: *node = fnretlookupmod(c, callee.str, c.curmod); - return isslicetype(c, rt); + let rtyp: *node = fnretlookupmod(c, callee.str, c.curmod); + return isslicetype(c, rtyp); }; if (callee.kind == nkind.N_DOT) { let cmod: str; @@ -558,8 +558,8 @@ fn nodeisslice(c: *cgen, n: *node) bool = { cmod = callee.lhs.str; }; }; - let rt: *node = fnretlookupmod(c, callee.str, cmod); - return isslicetype(c, rt); + let rtyp: *node = fnretlookupmod(c, callee.str, cmod); + return isslicetype(c, rtyp); }; }; return false; @@ -674,8 +674,8 @@ fn nodeisstr(c: *cgen, n: *node) bool = { let callee: *node = n.lhs; if (callee != nil) { if (callee.kind == nkind.N_IDENT) { - let rt: *node = fnretlookupmod(c, callee.str, c.curmod); - return isstrtype(c, rt); + let rtyp: *node = fnretlookupmod(c, callee.str, c.curmod); + return isstrtype(c, rtyp); }; // #34: cross-module N_DOT — route through fnretlookupmod // so a same-leaf caller-module fn (different return shape) @@ -688,8 +688,8 @@ fn nodeisstr(c: *cgen, n: *node) bool = { cmod = callee.lhs.str; }; }; - let rt: *node = fnretlookupmod(c, callee.str, cmod); - return isstrtype(c, rt); + let rtyp: *node = fnretlookupmod(c, callee.str, cmod); + return isstrtype(c, rtyp); }; }; return false; @@ -1605,8 +1605,8 @@ export fn callsretsize(c: *cgen, n: *node) i32 = { }; }; if (cn.len == 0) { return 0; }; - let rt: *node = fnretlookupmod(c, cn, cmod); - return sretretsize(c, rt); + let rtyp: *node = fnretlookupmod(c, cn, cmod); + return sretretsize(c, rtyp); }; fn structlookup(c: *cgen, name: str) *structinfo = { @@ -1863,16 +1863,16 @@ fn inferletcalltype(c: *cgen, rhs: *node) *node = { }; }; if (cname.len == 0) { return nil; }; - let rt: *node = fnretlookupmod(c, cname, cmod); - if (rt == nil) { return nil; }; + let rtyp: *node = fnretlookupmod(c, cname, cmod); + if (rtyp == nil) { return nil; }; if (unwrap) { // Strip error variants — success type is the first // variant of the tagged return. - if (rt.kind != nkind.N_TTAGGED) { return nil; }; - return rt.list; + if (rtyp.kind != nkind.N_TTAGGED) { return nil; }; + return rtyp.list; }; // Plain call: declared return type is the local's type. - return rt; + return rtyp; }; // letslotsize — slot size for a `let` binding. Like slotsize, but @@ -2339,8 +2339,8 @@ fn matchscrutt(c: *cgen, scrut: *node) *node = { }; }; if (cnm.len > 0) { - let rt: *node = fnretlookupmod(c, cnm, cmod); - if (rt != nil) { return resolvetagged(c, rt); }; + let rtyp: *node = fnretlookupmod(c, cnm, cmod); + if (rtyp != nil) { return resolvetagged(c, rtyp); }; }; }; return nil; @@ -2534,9 +2534,9 @@ export fn exprfloatkind(c: *cgen, n: *node) i32 = { if (n.lhs.kind == nkind.N_IDENT) { nm = n.lhs.str; }; }; if (nm.len > 0) { - let rt: *node = fnretlookup(c, nm); - if (isf32type(c, rt)) { return 1; }; - if (isfloattype(c, rt)) { return 2; }; + let rtyp: *node = fnretlookup(c, nm); + if (isf32type(c, rtyp)) { return 1; }; + if (isfloattype(c, rtyp)) { return 2; }; }; return 0; }; @@ -3055,9 +3055,9 @@ fn rhstaggedabicall(c: *cgen, src: *node) bool = { }; }; if (calleename.len > 0) { - let rt: *node = fnretlookupmod(c, calleename, cmod); - if (rt != nil) { - if (istaggedtype(c, rt)) { return true; }; + let rtyp: *node = fnretlookupmod(c, calleename, cmod); + if (rtyp != nil) { + if (istaggedtype(c, rtyp)) { return true; }; }; }; }; diff --git a/selfhost/cmd/wcc/check.ww b/selfhost/cmd/wcc/check.ww index 68a7e01d..7799ffa0 100644 --- a/selfhost/cmd/wcc/check.ww +++ b/selfhost/cmd/wcc/check.ww @@ -812,15 +812,15 @@ fn astoffset(c: *checker, dot: *node) i64 = { if (dot.kind != nkind.N_DOT) { return -1i64; }; let recv: *node = scruttype(c, dot.lhs); if (recv == nil) { return -1i64; }; - let rt: *node = resolvealias(c, unwrapbang(recv)); - if (rt == nil) { return -1i64; }; - if (rt.kind == nkind.N_TPTR) { - rt = resolvealias(c, unwrapbang(rt.lhs)); + let rtyp: *node = resolvealias(c, unwrapbang(recv)); + if (rtyp == nil) { return -1i64; }; + if (rtyp.kind == nkind.N_TPTR) { + rtyp = resolvealias(c, unwrapbang(rtyp.lhs)); }; - if (rt == nil) { return -1i64; }; - if (rt.kind != nkind.N_TSTRUCT) { return -1i64; }; + if (rtyp == nil) { return -1i64; }; + if (rtyp.kind != nkind.N_TSTRUCT) { return -1i64; }; let off: i64 = 0i64; - let f: *node = rt.list; + let f: *node = rtyp.list; for (f != nil) { if (f.kind == nkind.N_TFIELD) { let fa: i64 = astalign(c, f.lhs); diff --git a/selfhost/cmd/wcc/mem.ww b/selfhost/cmd/wcc/mem.ww index 46848f79..b37fa14f 100644 --- a/selfhost/cmd/wcc/mem.ww +++ b/selfhost/cmd/wcc/mem.ww @@ -11,6 +11,7 @@ package wcc; import os; +import rt; def ALIGN: u64 = 16u64; def INIT_CHUNK: u64 = 65536u64; @@ -30,8 +31,8 @@ fn roundup(n: u64, a: u64) u64 = { }; export fn newarena() *arena = { - let a: *arena = os.alloc(ARENA_SZ): *arena; - a.buf = os.alloc(INIT_CHUNK): *u8; + let a: *arena = rt.alloc(ARENA_SZ): *arena; + a.buf = rt.alloc(INIT_CHUNK): *u8; a.off = 0u64; a.cap = INIT_CHUNK; a.next = nil; @@ -48,14 +49,14 @@ fn grow(a: *arena, need: u64) bool = { if (want > MAX_CHUNK) { want = MAX_CHUNK; }; if (want < need) { return false; }; // single allocation too big - let old: *arena = os.alloc(ARENA_SZ): *arena; + let old: *arena = rt.alloc(ARENA_SZ): *arena; old.buf = a.buf; old.off = a.off; old.cap = a.cap; old.next = a.next; old.total = 0u64; - a.buf = os.alloc(want): *u8; + a.buf = rt.alloc(want): *u8; a.off = 0u64; a.cap = want; a.next = old; diff --git a/selfhost/cmd/ww/main.combined.ww b/selfhost/cmd/ww/main.combined.ww index 03d6fe1d..9e3bb6e0 100644 --- a/selfhost/cmd/ww/main.combined.ww +++ b/selfhost/cmd/ww/main.combined.ww @@ -110,28 +110,6 @@ import time; @symbol("rt_syscall") fn syscall3(num: nr, a: i64, b: i64, c: i64) i64; @symbol("rt_syscall") fn syscall4(num: nr, a: i64, b: i64, c: i64, d: i64) i64; -// alloc / free — runtime mmap-backed page allocator. Untyped: -// `alloc(n)` returns a `*void` and `free(p, n)` requires the byte -// count back because rt_free is munmap-based and doesn't track -// mapping sizes (the kernel needs the length to release the -// reservation). -// -// Diverges from Hare. Hare exposes `alloc` / `free` as typed -// language builtins (`alloc(value, cap)?` / `free(ptr)`) that the -// compiler lowers to rt::malloc/rt::free; ww has no such builtins, -// so the rt-symbol surface is exposed directly. Stdlib callers -// that need a typed allocation pattern wrap this with a cast plus -// a stored capacity (see [[strings.dup]], [[memio.dynamic]]). -// -// OOM: rt_alloc is a bare mmap(MAP_ANON|MAP_PRIVATE) wrapper with -// no error path. The raw Linux mmap syscall returns a negative -// errno cast to `*void` on failure (e.g. `(void*)-12` for ENOMEM); -// the `MAP_FAILED` (`(void*)-1`) value is a libc-wrapper convention -// that rt_alloc doesn't apply. Neither `== nil` nor `== (void*)-1` -// catches it; any deref of such a return faults. Today the stdlib -// does not check; OOM faults on first dereference. A typed -// fallible variant is a future task. -@symbol("rt_alloc") export fn alloc(n: u64) *void; @symbol("rt_free") export fn free(p: *void, n: u64) void; @symbol("rt_abort") fn abort(msg: str) void; @@ -749,6 +727,29 @@ export fn exists(path: str) bool = { return r >= 0i64; }; +// rt — runtime primitives exposed to ww programs. +// Mirrors Hare's rt:: module placement (ref/hare/rt/). + +package rt; + +// alloc — mmap-backed page allocator. Untyped: `alloc(n)` returns a +// `*void`; callers cast to the target type. Diverges from Hare: Hare +// exposes `alloc` / `free` as typed language builtins that the +// compiler lowers to rt::malloc/rt::free; ww has no such builtins, +// so the rt-symbol surface is exposed directly. Stdlib callers that +// need a typed allocation pattern wrap this with a cast plus a stored +// capacity (see [[strings.dup]], [[memio.dynamic]]). +// +// OOM: rt_alloc is a bare mmap(MAP_ANON|MAP_PRIVATE) wrapper with no +// error path. The raw Linux mmap syscall returns a negative errno cast +// to `*void` on failure (e.g. `(void*)-12` for ENOMEM); the +// `MAP_FAILED` (`(void*)-1`) value is a libc-wrapper convention that +// rt_alloc doesn't apply. Neither `== nil` nor `== (void*)-1` catches +// it; any deref of such a return faults. Today the stdlib does not +// check; OOM faults on first dereference. A typed fallible variant is +// a future task (task #39). ref/hare/rt/malloc.ha:27. +@symbol("rt_alloc") export fn alloc(n: u64) *void; + // selfhost/cmd/wcc/mem.ww — port of cmd/wcc/mem.c. // // Bump arena allocator. Backed by the runtime page allocator @@ -762,6 +763,7 @@ export fn exists(path: str) bool = { package wcc; import os; +import rt; def ALIGN: u64 = 16u64; def INIT_CHUNK: u64 = 65536u64; @@ -781,8 +783,8 @@ fn roundup(n: u64, a: u64) u64 = { }; export fn newarena() *arena = { - let a: *arena = os.alloc(ARENA_SZ): *arena; - a.buf = os.alloc(INIT_CHUNK): *u8; + let a: *arena = rt.alloc(ARENA_SZ): *arena; + a.buf = rt.alloc(INIT_CHUNK): *u8; a.off = 0u64; a.cap = INIT_CHUNK; a.next = nil; @@ -799,14 +801,14 @@ fn grow(a: *arena, need: u64) bool = { if (want > MAX_CHUNK) { want = MAX_CHUNK; }; if (want < need) { return false; }; // single allocation too big - let old: *arena = os.alloc(ARENA_SZ): *arena; + let old: *arena = rt.alloc(ARENA_SZ): *arena; old.buf = a.buf; old.off = a.off; old.cap = a.cap; old.next = a.next; old.total = 0u64; - a.buf = os.alloc(want): *u8; + a.buf = rt.alloc(want): *u8; a.off = 0u64; a.cap = want; a.next = old; @@ -874,6 +876,7 @@ export fn freearena(a: *arena) void = { package main; import os; +import rt; import mem; // All path/string scratch buffers go on the runtime page allocator. @@ -998,7 +1001,7 @@ fn selfdirinto(dst: *u8, dstsz: u64, argv0: *u8) void = { // Build "$dir/$name" (NUL-terminated) into a fresh page-sized buffer. fn joinpath(dir: *u8, name: *u8) *u8 = { - let buf: *u8 = os.alloc(PATH_MAX): *u8; + let buf: *u8 = rt.alloc(PATH_MAX): *u8; let off: u64 = cstrinto(buf, 0u64, dir); off = byteinto(buf, off, 47u8); off = cstrinto(buf, off, name); @@ -1008,7 +1011,7 @@ fn joinpath(dir: *u8, name: *u8) *u8 = { // Same, but the second component is a ww `str` literal. fn joinpathlit(dir: *u8, name: str) *u8 = { - let buf: *u8 = os.alloc(PATH_MAX): *u8; + let buf: *u8 = rt.alloc(PATH_MAX): *u8; let off: u64 = cstrinto(buf, 0u64, dir); off = byteinto(buf, off, 47u8); off = strinto(buf, off, name); @@ -1259,7 +1262,7 @@ fn enumeratedir(a: *arena, dirpath: *u8) (**u8, i32) = { let names: **u8 = amalloc(a, (maxnames: u64) * 8u64): **u8; let nlens: *u64 = amalloc(a, (maxnames: u64) * 8u64): *u64; let n: i32 = 0; - let buf: *u8 = os.alloc(8192u64): *u8; + let buf: *u8 = rt.alloc(8192u64): *u8; let r: i64 = os.getdents64(fd, buf, 8192u64); for (r > 0i64) { let off: u64 = 0u64; @@ -1321,7 +1324,7 @@ fn slurp(pathcs: *u8) (*u8, u64) = { case let e: os.oserror => { os.close(fd); return nil, 0u64; }; }; let nu: u64 = n: u64; - let buf: *u8 = os.alloc(nu + 1u64): *u8; + let buf: *u8 = rt.alloc(nu + 1u64): *u8; let rr: (i64 | os.oserror) = os.readall(fd, buf, nu); os.close(fd); let got: i64 = 0i64; @@ -1428,7 +1431,7 @@ fn expand(c: *expctx, pathcs: *u8) void = { fn peekpackage(a: *arena, pathcs: *u8) *u8 = { let fd: i32 = os.open(pathstr(pathcs), os.flag.RDONLY, 0i32); if (fd < 0) { return nil; }; - let buf: *u8 = os.alloc(2048u64): *u8; + let buf: *u8 = rt.alloc(2048u64): *u8; let n: i64 = os.read(fd, buf, 2048u64); os.close(fd); if (n <= 0i64) { return nil; }; @@ -1575,7 +1578,7 @@ fn makestem(stem: *u8, src: *u8) void = { // Append a literal suffix to `stem` (which already lives in a buffer). fn appendlit(stem: *u8, suffix: str) *u8 = { - let buf: *u8 = os.alloc(PATH_MAX): *u8; + let buf: *u8 = rt.alloc(PATH_MAX): *u8; let off: u64 = cstrinto(buf, 0u64, stem); off = strinto(buf, off, suffix); cstrseal(buf, off); @@ -1613,7 +1616,7 @@ fn buildone(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8, incs: *u8, lf: *l let l6: *u8 = joinpathlit(selfdir, "w6l_ww"); // Default lib search path: /../../lib - let dotdotlib: *u8 = os.alloc(PATH_MAX): *u8; + let dotdotlib: *u8 = rt.alloc(PATH_MAX): *u8; { let off: u64 = cstrinto(dotdotlib, 0u64, selfdir); off = strinto(dotdotlib, off, "/../../lib"); @@ -1626,7 +1629,7 @@ fn buildone(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8, incs: *u8, lf: *l // convention assumes you're running from the module dir; our // wrappers don't cd, so dirname(src) stands in as the closest // analog. Source-dir wins ties over the system path (cc -I.). - let srcd: *u8 = os.alloc(PATH_MAX): *u8; + let srcd: *u8 = rt.alloc(PATH_MAX): *u8; if (entryisdir != 0) { let slen: u64 = cstrlen(src); let k: u64 = 0u64; @@ -1660,7 +1663,7 @@ fn buildone(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8, incs: *u8, lf: *l }; // Compose searchpath: srcd + ':' + incs + ':' + dotdotlib. - let searchpath: *u8 = os.alloc(PATH_MAX * 3u64): *u8; + let searchpath: *u8 = rt.alloc(PATH_MAX * 3u64): *u8; { let off: u64 = cstrinto(searchpath, 0u64, srcd); off = byteinto(searchpath, off, 58u8); // ':' @@ -1674,7 +1677,7 @@ fn buildone(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8, incs: *u8, lf: *l // Stem for .s/.o/.combined.ww side files. Dir entry: /; // file entry: src stripped of .ww. - let stem: *u8 = os.alloc(PATH_MAX): *u8; + let stem: *u8 = rt.alloc(PATH_MAX): *u8; if (entryisdir != 0) { let dlen: u64 = cstrlen(srcd); let bo: u64 = basenameoff(srcd, dlen); @@ -1691,7 +1694,7 @@ fn buildone(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8, incs: *u8, lf: *l let combined: *u8 = appendlit(stem, ".combined.ww"); // libwwrt.a path: /../lib/libwwrt.a - let libwwrt: *u8 = os.alloc(PATH_MAX): *u8; + let libwwrt: *u8 = rt.alloc(PATH_MAX): *u8; { let off: u64 = cstrinto(libwwrt, 0u64, selfdir); off = strinto(libwwrt, off, "/../lib/libwwrt.a"); @@ -1718,7 +1721,7 @@ fn buildone(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8, incs: *u8, lf: *l // Step 2: w6c -o .s .combined.ww { - let argv: **u8 = os.alloc(40u64): **u8; + let argv: **u8 = rt.alloc(40u64): **u8; argv[0] = "w6c\0".ptr; argv[1] = "-o\0".ptr; argv[2] = asmf; @@ -1732,7 +1735,7 @@ fn buildone(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8, incs: *u8, lf: *l // Step 3: w6a -o .o .s { - let argv: **u8 = os.alloc(40u64): **u8; + let argv: **u8 = rt.alloc(40u64): **u8; argv[0] = "w6a\0".ptr; argv[1] = "-o\0".ptr; argv[2] = objf; @@ -1761,7 +1764,7 @@ fn buildone(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8, incs: *u8, lf: *l // + 2 * nlibs (-l, name) // + 1 nil terminator. let total: i32 = 5 + 2 * nldirs + 2 * nllibs + 1; - let argv: **u8 = os.alloc((total: u64) * 8u64): **u8; + let argv: **u8 = rt.alloc((total: u64) * 8u64): **u8; argv[0] = "w6l\0".ptr; argv[1] = "-o\0".ptr; argv[2] = out; @@ -1933,7 +1936,7 @@ fn defaultoutpath(src: *u8) *u8 = { if (src[i] == 47u8) { start = i + 1u64; }; // '/' i += 1u64; }; - let out: *u8 = os.alloc(PATH_MAX): *u8; + let out: *u8 = rt.alloc(PATH_MAX): *u8; let off: u64 = 0u64; let j: u64 = start; for (j < n) { @@ -1958,14 +1961,14 @@ fn defaultoutpath(src: *u8) *u8 = { fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { let a: *arena = newarena(); let src: *u8 = nil; - let incs: *u8 = os.alloc(PATH_MAX * 2u64): *u8; + let incs: *u8 = rt.alloc(PATH_MAX * 2u64): *u8; let incoff: u64 = 0u64; cstrseal(incs, 0u64); let maxlflags: i32 = 32; - let libdirs: **u8 = os.alloc((maxlflags: u64) * 8u64): **u8; + let libdirs: **u8 = rt.alloc((maxlflags: u64) * 8u64): **u8; let nlibdirs: i32 = 0; - let libs: **u8 = os.alloc((maxlflags: u64) * 8u64): **u8; + let libs: **u8 = rt.alloc((maxlflags: u64) * 8u64): **u8; let nlibs: i32 = 0; let i: i32 = start; @@ -2055,7 +2058,7 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { rlen -= 1u64; }; let bo: u64 = basenameoff(resolved, rlen); - out = os.alloc(PATH_MAX): *u8; + out = rt.alloc(PATH_MAX): *u8; let i: u64 = bo; let off: u64 = 0u64; for (i < rlen) { out[off] = resolved[i]; off += 1u64; i += 1u64; }; @@ -2105,14 +2108,14 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { let a: *arena = newarena(); let src: *u8 = nil; let passstart: i32 = -1; // first argv idx to pass through to program - let incs: *u8 = os.alloc(PATH_MAX * 2u64): *u8; + let incs: *u8 = rt.alloc(PATH_MAX * 2u64): *u8; let incoff: u64 = 0u64; cstrseal(incs, 0u64); let maxlflags: i32 = 32; - let libdirs: **u8 = os.alloc((maxlflags: u64) * 8u64): **u8; + let libdirs: **u8 = rt.alloc((maxlflags: u64) * 8u64): **u8; let nlibdirs: i32 = 0; - let libs: **u8 = os.alloc((maxlflags: u64) * 8u64): **u8; + let libs: **u8 = rt.alloc((maxlflags: u64) * 8u64): **u8; let nlibs: i32 = 0; let i: i32 = start; @@ -2202,7 +2205,7 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { return 1; }; - let tmp: *u8 = os.alloc(PATH_MAX): *u8; + let tmp: *u8 = rt.alloc(PATH_MAX): *u8; makeruntmp(tmp); let lf: lflags; lf.libdirs = libdirs; @@ -2218,7 +2221,7 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { let nextra: i32 = 0; if (passstart >= 0) { nextra = argc - passstart; }; let total: i32 = nextra + 2; - let execargv: **u8 = os.alloc((total: u64) * 8u64): **u8; + let execargv: **u8 = rt.alloc((total: u64) * 8u64): **u8; execargv[0] = tmp; let k: i32 = 0; for (k < nextra) { @@ -2239,13 +2242,13 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { // report ok/FAIL per file, return 0 iff all pass. fn runsingletest(selfdir: *u8, src: *u8) i32 = { - let tmp: *u8 = os.alloc(PATH_MAX): *u8; + let tmp: *u8 = rt.alloc(PATH_MAX): *u8; makeruntmp(tmp); if (buildone(selfdir, src, 0, tmp, "\0".ptr, nil) != 0) { os.remove(pathstr(tmp)); return 1; }; - let execargv: **u8 = os.alloc(16u64): **u8; + let execargv: **u8 = rt.alloc(16u64): **u8; execargv[0] = tmp; execargv[1] = nil; let rc: i32 = procrun(tmp, execargv); @@ -2262,7 +2265,7 @@ fn rundirtests(selfdir: *u8, dir: *u8) i32 = { let pass: i32 = 0; let fail: i32 = 0; - let buf: *u8 = os.alloc(8192u64): *u8; + let buf: *u8 = rt.alloc(8192u64): *u8; let dirlen: u64 = cstrlen(dir); let n: i64 = os.getdents64(fd, buf, 8192u64); @@ -2278,7 +2281,7 @@ fn rundirtests(selfdir: *u8, dir: *u8) i32 = { if (cstrendswithlit(name, "_test.ww")) { let nlen: u64 = cstrlen(name); // path = / - let path: *u8 = os.alloc(PATH_MAX): *u8; + let path: *u8 = rt.alloc(PATH_MAX): *u8; let poff: u64 = cstrinto(path, 0u64, dir); path[poff] = 47u8; poff += 1u64; let i: u64 = 0u64; @@ -2286,10 +2289,10 @@ fn rundirtests(selfdir: *u8, dir: *u8) i32 = { poff += nlen; cstrseal(path, poff); // incs = so test files can `use ` siblings - let tincs: *u8 = os.alloc(PATH_MAX): *u8; + let tincs: *u8 = rt.alloc(PATH_MAX): *u8; let ic: u64 = cstrinto(tincs, 0u64, dir); cstrseal(tincs, ic); - let tmp: *u8 = os.alloc(PATH_MAX): *u8; + let tmp: *u8 = rt.alloc(PATH_MAX): *u8; makeruntmp(tmp); let bres: i32 = buildone(selfdir, path, 0, tmp, tincs, nil); if (bres != 0) { @@ -2298,7 +2301,7 @@ fn rundirtests(selfdir: *u8, dir: *u8) i32 = { os.write(2, name, nlen); os.write(2, " (build)\n".ptr, 9u64); } else { - let execargv: **u8 = os.alloc(16u64): **u8; + let execargv: **u8 = rt.alloc(16u64): **u8; execargv[0] = tmp; execargv[1] = nil; let rc: i32 = procrun(tmp, execargv); @@ -2355,7 +2358,7 @@ export fn main(argc: i32, argv: **u8) i32 = { }; // selfdir = dirname(argv[0]) - let selfdir: *u8 = os.alloc(PATH_MAX): *u8; + let selfdir: *u8 = rt.alloc(PATH_MAX): *u8; selfdirinto(selfdir, PATH_MAX, argv[0]); if (argc < 2) { diff --git a/selfhost/cmd/ww/main.ww b/selfhost/cmd/ww/main.ww index af5ff430..bada56a5 100644 --- a/selfhost/cmd/ww/main.ww +++ b/selfhost/cmd/ww/main.ww @@ -14,6 +14,7 @@ package main; import os; +import rt; import mem; // All path/string scratch buffers go on the runtime page allocator. @@ -138,7 +139,7 @@ fn selfdirinto(dst: *u8, dstsz: u64, argv0: *u8) void = { // Build "$dir/$name" (NUL-terminated) into a fresh page-sized buffer. fn joinpath(dir: *u8, name: *u8) *u8 = { - let buf: *u8 = os.alloc(PATH_MAX): *u8; + let buf: *u8 = rt.alloc(PATH_MAX): *u8; let off: u64 = cstrinto(buf, 0u64, dir); off = byteinto(buf, off, 47u8); off = cstrinto(buf, off, name); @@ -148,7 +149,7 @@ fn joinpath(dir: *u8, name: *u8) *u8 = { // Same, but the second component is a ww `str` literal. fn joinpathlit(dir: *u8, name: str) *u8 = { - let buf: *u8 = os.alloc(PATH_MAX): *u8; + let buf: *u8 = rt.alloc(PATH_MAX): *u8; let off: u64 = cstrinto(buf, 0u64, dir); off = byteinto(buf, off, 47u8); off = strinto(buf, off, name); @@ -399,7 +400,7 @@ fn enumeratedir(a: *arena, dirpath: *u8) (**u8, i32) = { let names: **u8 = amalloc(a, (maxnames: u64) * 8u64): **u8; let nlens: *u64 = amalloc(a, (maxnames: u64) * 8u64): *u64; let n: i32 = 0; - let buf: *u8 = os.alloc(8192u64): *u8; + let buf: *u8 = rt.alloc(8192u64): *u8; let r: i64 = os.getdents64(fd, buf, 8192u64); for (r > 0i64) { let off: u64 = 0u64; @@ -461,7 +462,7 @@ fn slurp(pathcs: *u8) (*u8, u64) = { case let e: os.oserror => { os.close(fd); return nil, 0u64; }; }; let nu: u64 = n: u64; - let buf: *u8 = os.alloc(nu + 1u64): *u8; + let buf: *u8 = rt.alloc(nu + 1u64): *u8; let rr: (i64 | os.oserror) = os.readall(fd, buf, nu); os.close(fd); let got: i64 = 0i64; @@ -568,7 +569,7 @@ fn expand(c: *expctx, pathcs: *u8) void = { fn peekpackage(a: *arena, pathcs: *u8) *u8 = { let fd: i32 = os.open(pathstr(pathcs), os.flag.RDONLY, 0i32); if (fd < 0) { return nil; }; - let buf: *u8 = os.alloc(2048u64): *u8; + let buf: *u8 = rt.alloc(2048u64): *u8; let n: i64 = os.read(fd, buf, 2048u64); os.close(fd); if (n <= 0i64) { return nil; }; @@ -715,7 +716,7 @@ fn makestem(stem: *u8, src: *u8) void = { // Append a literal suffix to `stem` (which already lives in a buffer). fn appendlit(stem: *u8, suffix: str) *u8 = { - let buf: *u8 = os.alloc(PATH_MAX): *u8; + let buf: *u8 = rt.alloc(PATH_MAX): *u8; let off: u64 = cstrinto(buf, 0u64, stem); off = strinto(buf, off, suffix); cstrseal(buf, off); @@ -753,7 +754,7 @@ fn buildone(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8, incs: *u8, lf: *l let l6: *u8 = joinpathlit(selfdir, "w6l_ww"); // Default lib search path: /../../lib - let dotdotlib: *u8 = os.alloc(PATH_MAX): *u8; + let dotdotlib: *u8 = rt.alloc(PATH_MAX): *u8; { let off: u64 = cstrinto(dotdotlib, 0u64, selfdir); off = strinto(dotdotlib, off, "/../../lib"); @@ -766,7 +767,7 @@ fn buildone(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8, incs: *u8, lf: *l // convention assumes you're running from the module dir; our // wrappers don't cd, so dirname(src) stands in as the closest // analog. Source-dir wins ties over the system path (cc -I.). - let srcd: *u8 = os.alloc(PATH_MAX): *u8; + let srcd: *u8 = rt.alloc(PATH_MAX): *u8; if (entryisdir != 0) { let slen: u64 = cstrlen(src); let k: u64 = 0u64; @@ -800,7 +801,7 @@ fn buildone(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8, incs: *u8, lf: *l }; // Compose searchpath: srcd + ':' + incs + ':' + dotdotlib. - let searchpath: *u8 = os.alloc(PATH_MAX * 3u64): *u8; + let searchpath: *u8 = rt.alloc(PATH_MAX * 3u64): *u8; { let off: u64 = cstrinto(searchpath, 0u64, srcd); off = byteinto(searchpath, off, 58u8); // ':' @@ -814,7 +815,7 @@ fn buildone(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8, incs: *u8, lf: *l // Stem for .s/.o/.combined.ww side files. Dir entry: /; // file entry: src stripped of .ww. - let stem: *u8 = os.alloc(PATH_MAX): *u8; + let stem: *u8 = rt.alloc(PATH_MAX): *u8; if (entryisdir != 0) { let dlen: u64 = cstrlen(srcd); let bo: u64 = basenameoff(srcd, dlen); @@ -831,7 +832,7 @@ fn buildone(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8, incs: *u8, lf: *l let combined: *u8 = appendlit(stem, ".combined.ww"); // libwwrt.a path: /../lib/libwwrt.a - let libwwrt: *u8 = os.alloc(PATH_MAX): *u8; + let libwwrt: *u8 = rt.alloc(PATH_MAX): *u8; { let off: u64 = cstrinto(libwwrt, 0u64, selfdir); off = strinto(libwwrt, off, "/../lib/libwwrt.a"); @@ -858,7 +859,7 @@ fn buildone(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8, incs: *u8, lf: *l // Step 2: w6c -o .s .combined.ww { - let argv: **u8 = os.alloc(40u64): **u8; + let argv: **u8 = rt.alloc(40u64): **u8; argv[0] = "w6c\0".ptr; argv[1] = "-o\0".ptr; argv[2] = asmf; @@ -872,7 +873,7 @@ fn buildone(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8, incs: *u8, lf: *l // Step 3: w6a -o .o .s { - let argv: **u8 = os.alloc(40u64): **u8; + let argv: **u8 = rt.alloc(40u64): **u8; argv[0] = "w6a\0".ptr; argv[1] = "-o\0".ptr; argv[2] = objf; @@ -901,7 +902,7 @@ fn buildone(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8, incs: *u8, lf: *l // + 2 * nlibs (-l, name) // + 1 nil terminator. let total: i32 = 5 + 2 * nldirs + 2 * nllibs + 1; - let argv: **u8 = os.alloc((total: u64) * 8u64): **u8; + let argv: **u8 = rt.alloc((total: u64) * 8u64): **u8; argv[0] = "w6l\0".ptr; argv[1] = "-o\0".ptr; argv[2] = out; @@ -1073,7 +1074,7 @@ fn defaultoutpath(src: *u8) *u8 = { if (src[i] == 47u8) { start = i + 1u64; }; // '/' i += 1u64; }; - let out: *u8 = os.alloc(PATH_MAX): *u8; + let out: *u8 = rt.alloc(PATH_MAX): *u8; let off: u64 = 0u64; let j: u64 = start; for (j < n) { @@ -1098,14 +1099,14 @@ fn defaultoutpath(src: *u8) *u8 = { fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { let a: *arena = newarena(); let src: *u8 = nil; - let incs: *u8 = os.alloc(PATH_MAX * 2u64): *u8; + let incs: *u8 = rt.alloc(PATH_MAX * 2u64): *u8; let incoff: u64 = 0u64; cstrseal(incs, 0u64); let maxlflags: i32 = 32; - let libdirs: **u8 = os.alloc((maxlflags: u64) * 8u64): **u8; + let libdirs: **u8 = rt.alloc((maxlflags: u64) * 8u64): **u8; let nlibdirs: i32 = 0; - let libs: **u8 = os.alloc((maxlflags: u64) * 8u64): **u8; + let libs: **u8 = rt.alloc((maxlflags: u64) * 8u64): **u8; let nlibs: i32 = 0; let i: i32 = start; @@ -1195,7 +1196,7 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { rlen -= 1u64; }; let bo: u64 = basenameoff(resolved, rlen); - out = os.alloc(PATH_MAX): *u8; + out = rt.alloc(PATH_MAX): *u8; let i: u64 = bo; let off: u64 = 0u64; for (i < rlen) { out[off] = resolved[i]; off += 1u64; i += 1u64; }; @@ -1245,14 +1246,14 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { let a: *arena = newarena(); let src: *u8 = nil; let passstart: i32 = -1; // first argv idx to pass through to program - let incs: *u8 = os.alloc(PATH_MAX * 2u64): *u8; + let incs: *u8 = rt.alloc(PATH_MAX * 2u64): *u8; let incoff: u64 = 0u64; cstrseal(incs, 0u64); let maxlflags: i32 = 32; - let libdirs: **u8 = os.alloc((maxlflags: u64) * 8u64): **u8; + let libdirs: **u8 = rt.alloc((maxlflags: u64) * 8u64): **u8; let nlibdirs: i32 = 0; - let libs: **u8 = os.alloc((maxlflags: u64) * 8u64): **u8; + let libs: **u8 = rt.alloc((maxlflags: u64) * 8u64): **u8; let nlibs: i32 = 0; let i: i32 = start; @@ -1342,7 +1343,7 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { return 1; }; - let tmp: *u8 = os.alloc(PATH_MAX): *u8; + let tmp: *u8 = rt.alloc(PATH_MAX): *u8; makeruntmp(tmp); let lf: lflags; lf.libdirs = libdirs; @@ -1358,7 +1359,7 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { let nextra: i32 = 0; if (passstart >= 0) { nextra = argc - passstart; }; let total: i32 = nextra + 2; - let execargv: **u8 = os.alloc((total: u64) * 8u64): **u8; + let execargv: **u8 = rt.alloc((total: u64) * 8u64): **u8; execargv[0] = tmp; let k: i32 = 0; for (k < nextra) { @@ -1379,13 +1380,13 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = { // report ok/FAIL per file, return 0 iff all pass. fn runsingletest(selfdir: *u8, src: *u8) i32 = { - let tmp: *u8 = os.alloc(PATH_MAX): *u8; + let tmp: *u8 = rt.alloc(PATH_MAX): *u8; makeruntmp(tmp); if (buildone(selfdir, src, 0, tmp, "\0".ptr, nil) != 0) { os.remove(pathstr(tmp)); return 1; }; - let execargv: **u8 = os.alloc(16u64): **u8; + let execargv: **u8 = rt.alloc(16u64): **u8; execargv[0] = tmp; execargv[1] = nil; let rc: i32 = procrun(tmp, execargv); @@ -1402,7 +1403,7 @@ fn rundirtests(selfdir: *u8, dir: *u8) i32 = { let pass: i32 = 0; let fail: i32 = 0; - let buf: *u8 = os.alloc(8192u64): *u8; + let buf: *u8 = rt.alloc(8192u64): *u8; let dirlen: u64 = cstrlen(dir); let n: i64 = os.getdents64(fd, buf, 8192u64); @@ -1418,7 +1419,7 @@ fn rundirtests(selfdir: *u8, dir: *u8) i32 = { if (cstrendswithlit(name, "_test.ww")) { let nlen: u64 = cstrlen(name); // path = / - let path: *u8 = os.alloc(PATH_MAX): *u8; + let path: *u8 = rt.alloc(PATH_MAX): *u8; let poff: u64 = cstrinto(path, 0u64, dir); path[poff] = 47u8; poff += 1u64; let i: u64 = 0u64; @@ -1426,10 +1427,10 @@ fn rundirtests(selfdir: *u8, dir: *u8) i32 = { poff += nlen; cstrseal(path, poff); // incs = so test files can `use ` siblings - let tincs: *u8 = os.alloc(PATH_MAX): *u8; + let tincs: *u8 = rt.alloc(PATH_MAX): *u8; let ic: u64 = cstrinto(tincs, 0u64, dir); cstrseal(tincs, ic); - let tmp: *u8 = os.alloc(PATH_MAX): *u8; + let tmp: *u8 = rt.alloc(PATH_MAX): *u8; makeruntmp(tmp); let bres: i32 = buildone(selfdir, path, 0, tmp, tincs, nil); if (bres != 0) { @@ -1438,7 +1439,7 @@ fn rundirtests(selfdir: *u8, dir: *u8) i32 = { os.write(2, name, nlen); os.write(2, " (build)\n".ptr, 9u64); } else { - let execargv: **u8 = os.alloc(16u64): **u8; + let execargv: **u8 = rt.alloc(16u64): **u8; execargv[0] = tmp; execargv[1] = nil; let rc: i32 = procrun(tmp, execargv); @@ -1495,7 +1496,7 @@ export fn main(argc: i32, argv: **u8) i32 = { }; // selfdir = dirname(argv[0]) - let selfdir: *u8 = os.alloc(PATH_MAX): *u8; + let selfdir: *u8 = rt.alloc(PATH_MAX): *u8; selfdirinto(selfdir, PATH_MAX, argv[0]); if (argc < 2) { diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 81010388..c09f341c 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -110,28 +110,6 @@ import time; @symbol("rt_syscall") fn syscall3(num: nr, a: i64, b: i64, c: i64) i64; @symbol("rt_syscall") fn syscall4(num: nr, a: i64, b: i64, c: i64, d: i64) i64; -// alloc / free — runtime mmap-backed page allocator. Untyped: -// `alloc(n)` returns a `*void` and `free(p, n)` requires the byte -// count back because rt_free is munmap-based and doesn't track -// mapping sizes (the kernel needs the length to release the -// reservation). -// -// Diverges from Hare. Hare exposes `alloc` / `free` as typed -// language builtins (`alloc(value, cap)?` / `free(ptr)`) that the -// compiler lowers to rt::malloc/rt::free; ww has no such builtins, -// so the rt-symbol surface is exposed directly. Stdlib callers -// that need a typed allocation pattern wrap this with a cast plus -// a stored capacity (see [[strings.dup]], [[memio.dynamic]]). -// -// OOM: rt_alloc is a bare mmap(MAP_ANON|MAP_PRIVATE) wrapper with -// no error path. The raw Linux mmap syscall returns a negative -// errno cast to `*void` on failure (e.g. `(void*)-12` for ENOMEM); -// the `MAP_FAILED` (`(void*)-1`) value is a libc-wrapper convention -// that rt_alloc doesn't apply. Neither `== nil` nor `== (void*)-1` -// catches it; any deref of such a return faults. Today the stdlib -// does not check; OOM faults on first dereference. A typed -// fallible variant is a future task. -@symbol("rt_alloc") export fn alloc(n: u64) *void; @symbol("rt_free") export fn free(p: *void, n: u64) void; @symbol("rt_abort") fn abort(msg: str) void; @@ -749,6 +727,29 @@ export fn exists(path: str) bool = { return r >= 0i64; }; +// rt — runtime primitives exposed to ww programs. +// Mirrors Hare's rt:: module placement (ref/hare/rt/). + +package rt; + +// alloc — mmap-backed page allocator. Untyped: `alloc(n)` returns a +// `*void`; callers cast to the target type. Diverges from Hare: Hare +// exposes `alloc` / `free` as typed language builtins that the +// compiler lowers to rt::malloc/rt::free; ww has no such builtins, +// so the rt-symbol surface is exposed directly. Stdlib callers that +// need a typed allocation pattern wrap this with a cast plus a stored +// capacity (see [[strings.dup]], [[memio.dynamic]]). +// +// OOM: rt_alloc is a bare mmap(MAP_ANON|MAP_PRIVATE) wrapper with no +// error path. The raw Linux mmap syscall returns a negative errno cast +// to `*void` on failure (e.g. `(void*)-12` for ENOMEM); the +// `MAP_FAILED` (`(void*)-1`) value is a libc-wrapper convention that +// rt_alloc doesn't apply. Neither `== nil` nor `== (void*)-1` catches +// it; any deref of such a return faults. Today the stdlib does not +// check; OOM faults on first dereference. A typed fallible variant is +// a future task (task #39). ref/hare/rt/malloc.ha:27. +@symbol("rt_alloc") export fn alloc(n: u64) *void; + // selfhost/cmd/wcc/mem.ww — port of cmd/wcc/mem.c. // // Bump arena allocator. Backed by the runtime page allocator @@ -762,6 +763,7 @@ export fn exists(path: str) bool = { package wcc; import os; +import rt; def ALIGN: u64 = 16u64; def INIT_CHUNK: u64 = 65536u64; @@ -781,8 +783,8 @@ fn roundup(n: u64, a: u64) u64 = { }; export fn newarena() *arena = { - let a: *arena = os.alloc(ARENA_SZ): *arena; - a.buf = os.alloc(INIT_CHUNK): *u8; + let a: *arena = rt.alloc(ARENA_SZ): *arena; + a.buf = rt.alloc(INIT_CHUNK): *u8; a.off = 0u64; a.cap = INIT_CHUNK; a.next = nil; @@ -799,14 +801,14 @@ fn grow(a: *arena, need: u64) bool = { if (want > MAX_CHUNK) { want = MAX_CHUNK; }; if (want < need) { return false; }; // single allocation too big - let old: *arena = os.alloc(ARENA_SZ): *arena; + let old: *arena = rt.alloc(ARENA_SZ): *arena; old.buf = a.buf; old.off = a.off; old.cap = a.cap; old.next = a.next; old.total = 0u64; - a.buf = os.alloc(want): *u8; + a.buf = rt.alloc(want): *u8; a.off = 0u64; a.cap = want; a.next = old; @@ -1864,6 +1866,7 @@ package strings; import bytes; import encoding.utf8; import os; +import rt; import types; // toutf8 — borrowed []u8 view of `s`. ref/hare/strings/utf8.ha:29. @@ -1935,7 +1938,7 @@ export fn dup(s: str) str = { r.ptr = nil; r.len = 0; if (s.len == 0) { return r; }; - let buf: *u8 = os.alloc(s.len: u64): *u8; + let buf: *u8 = rt.alloc(s.len: u64): *u8; let i: i32 = 0; for (i < s.len) { buf[i] = s[i]; i += 1; }; r.ptr = buf; @@ -2013,7 +2016,7 @@ export fn concat(strs: str...) str = { r.ptr = nil; r.len = 0; if (total == 0) { return r; }; - let buf: *u8 = os.alloc(total: u64): *u8; + let buf: *u8 = rt.alloc(total: u64): *u8; let off: i32 = 0; i = 0; for (i < strs.len) { @@ -2046,7 +2049,7 @@ export fn join(delim: str, strs: str...) str = { r.ptr = nil; r.len = 0; if (total == 0) { return r; }; - let buf: *u8 = os.alloc(total: u64): *u8; + let buf: *u8 = rt.alloc(total: u64): *u8; let off: i32 = 0; i = 0; for (i < strs.len) { @@ -2723,7 +2726,7 @@ export fn lpad(s: str, p: rune, maxlen: i32) str = { if (s.len >= maxlen) { return dup(s); }; let scratch: [4]u8; let pad: []u8 = runebytes(scratch[0:4], p); - let buf: *u8 = os.alloc(maxlen: u64): *u8; + let buf: *u8 = rt.alloc(maxlen: u64): *u8; let padwrite: i32 = (maxlen - s.len) * pad.len; if (padwrite > maxlen) { padwrite = maxlen; }; let off: i32 = 0; @@ -2806,7 +2809,7 @@ export fn rpad(s: str, p: rune, maxlen: i32) str = { if (s.len >= maxlen) { return dup(s); }; let scratch: [4]u8; let pad: []u8 = runebytes(scratch[0:4], p); - let buf: *u8 = os.alloc(maxlen: u64): *u8; + let buf: *u8 = rt.alloc(maxlen: u64): *u8; let k: i32 = 0; for (k < s.len) { buf[k] = s[k]; @@ -7901,15 +7904,15 @@ fn astoffset(c: *checker, dot: *node) i64 = { if (dot.kind != nkind.N_DOT) { return -1i64; }; let recv: *node = scruttype(c, dot.lhs); if (recv == nil) { return -1i64; }; - let rt: *node = resolvealias(c, unwrapbang(recv)); - if (rt == nil) { return -1i64; }; - if (rt.kind == nkind.N_TPTR) { - rt = resolvealias(c, unwrapbang(rt.lhs)); + let rtyp: *node = resolvealias(c, unwrapbang(recv)); + if (rtyp == nil) { return -1i64; }; + if (rtyp.kind == nkind.N_TPTR) { + rtyp = resolvealias(c, unwrapbang(rtyp.lhs)); }; - if (rt == nil) { return -1i64; }; - if (rt.kind != nkind.N_TSTRUCT) { return -1i64; }; + if (rtyp == nil) { return -1i64; }; + if (rtyp.kind != nkind.N_TSTRUCT) { return -1i64; }; let off: i64 = 0i64; - let f: *node = rt.list; + let f: *node = rtyp.list; for (f != nil) { if (f.kind == nkind.N_TFIELD) { let fa: i64 = astalign(c, f.lhs); @@ -9727,9 +9730,9 @@ export fn taggedcallslot(c: *cgen, n: *node) i32 = { let callee: *node = n.lhs; if (callee == nil) { return 0; }; if (callee.kind != nkind.N_IDENT) { return 0; }; - let rt: *node = fnretlookup(c, callee.str); - if (!istaggedtype(c, rt)) { return 0; }; - return slotsize(c, rt); + let rtyp: *node = fnretlookup(c, callee.str); + if (!istaggedtype(c, rtyp)) { return 0; }; + return slotsize(c, rtyp); }; fn nodeisslice(c: *cgen, n: *node) bool = { @@ -9758,8 +9761,8 @@ fn nodeisslice(c: *cgen, n: *node) bool = { let callee: *node = n.lhs; if (callee != nil) { if (callee.kind == nkind.N_IDENT) { - let rt: *node = fnretlookupmod(c, callee.str, c.curmod); - return isslicetype(c, rt); + let rtyp: *node = fnretlookupmod(c, callee.str, c.curmod); + return isslicetype(c, rtyp); }; if (callee.kind == nkind.N_DOT) { let cmod: str; @@ -9769,8 +9772,8 @@ fn nodeisslice(c: *cgen, n: *node) bool = { cmod = callee.lhs.str; }; }; - let rt: *node = fnretlookupmod(c, callee.str, cmod); - return isslicetype(c, rt); + let rtyp: *node = fnretlookupmod(c, callee.str, cmod); + return isslicetype(c, rtyp); }; }; return false; @@ -9885,8 +9888,8 @@ fn nodeisstr(c: *cgen, n: *node) bool = { let callee: *node = n.lhs; if (callee != nil) { if (callee.kind == nkind.N_IDENT) { - let rt: *node = fnretlookupmod(c, callee.str, c.curmod); - return isstrtype(c, rt); + let rtyp: *node = fnretlookupmod(c, callee.str, c.curmod); + return isstrtype(c, rtyp); }; // #34: cross-module N_DOT — route through fnretlookupmod // so a same-leaf caller-module fn (different return shape) @@ -9899,8 +9902,8 @@ fn nodeisstr(c: *cgen, n: *node) bool = { cmod = callee.lhs.str; }; }; - let rt: *node = fnretlookupmod(c, callee.str, cmod); - return isstrtype(c, rt); + let rtyp: *node = fnretlookupmod(c, callee.str, cmod); + return isstrtype(c, rtyp); }; }; return false; @@ -10816,8 +10819,8 @@ export fn callsretsize(c: *cgen, n: *node) i32 = { }; }; if (cn.len == 0) { return 0; }; - let rt: *node = fnretlookupmod(c, cn, cmod); - return sretretsize(c, rt); + let rtyp: *node = fnretlookupmod(c, cn, cmod); + return sretretsize(c, rtyp); }; fn structlookup(c: *cgen, name: str) *structinfo = { @@ -11074,16 +11077,16 @@ fn inferletcalltype(c: *cgen, rhs: *node) *node = { }; }; if (cname.len == 0) { return nil; }; - let rt: *node = fnretlookupmod(c, cname, cmod); - if (rt == nil) { return nil; }; + let rtyp: *node = fnretlookupmod(c, cname, cmod); + if (rtyp == nil) { return nil; }; if (unwrap) { // Strip error variants — success type is the first // variant of the tagged return. - if (rt.kind != nkind.N_TTAGGED) { return nil; }; - return rt.list; + if (rtyp.kind != nkind.N_TTAGGED) { return nil; }; + return rtyp.list; }; // Plain call: declared return type is the local's type. - return rt; + return rtyp; }; // letslotsize — slot size for a `let` binding. Like slotsize, but @@ -11550,8 +11553,8 @@ fn matchscrutt(c: *cgen, scrut: *node) *node = { }; }; if (cnm.len > 0) { - let rt: *node = fnretlookupmod(c, cnm, cmod); - if (rt != nil) { return resolvetagged(c, rt); }; + let rtyp: *node = fnretlookupmod(c, cnm, cmod); + if (rtyp != nil) { return resolvetagged(c, rtyp); }; }; }; return nil; @@ -11745,9 +11748,9 @@ export fn exprfloatkind(c: *cgen, n: *node) i32 = { if (n.lhs.kind == nkind.N_IDENT) { nm = n.lhs.str; }; }; if (nm.len > 0) { - let rt: *node = fnretlookup(c, nm); - if (isf32type(c, rt)) { return 1; }; - if (isfloattype(c, rt)) { return 2; }; + let rtyp: *node = fnretlookup(c, nm); + if (isf32type(c, rtyp)) { return 1; }; + if (isfloattype(c, rtyp)) { return 2; }; }; return 0; }; @@ -12266,9 +12269,9 @@ fn rhstaggedabicall(c: *cgen, src: *node) bool = { }; }; if (calleename.len > 0) { - let rt: *node = fnretlookupmod(c, calleename, cmod); - if (rt != nil) { - if (istaggedtype(c, rt)) { return true; }; + let rtyp: *node = fnretlookupmod(c, calleename, cmod); + if (rtyp != nil) { + if (istaggedtype(c, rtyp)) { return true; }; }; }; }; @@ -13380,10 +13383,10 @@ fn cgtryprop(c: *cgen, n: *node) void = { }; }; if (cname.len > 0) { - let rt: *node = fnretlookupmod(c, cname, cmod); - if (rt != nil) { - if (rt.kind == nkind.N_TTAGGED) { - let first: *node = rt.list; + let rtyp: *node = fnretlookupmod(c, cname, cmod); + if (rtyp != nil) { + if (rtyp.kind == nkind.N_TTAGGED) { + let first: *node = rtyp.list; if (first != nil) { if (isstrtype(c, first)) { succisstr = true; @@ -13436,10 +13439,10 @@ fn cgtryunw(c: *cgen, n: *node) void = { }; }; if (cname.len > 0) { - let rt: *node = fnretlookupmod(c, cname, cmod); - if (rt != nil) { - if (rt.kind == nkind.N_TTAGGED) { - let first: *node = rt.list; + let rtyp: *node = fnretlookupmod(c, cname, cmod); + if (rtyp != nil) { + if (rtyp.kind == nkind.N_TTAGGED) { + let first: *node = rtyp.list; if (first != nil) { if (isstrtype(c, first)) { succisstr = true; @@ -13816,8 +13819,8 @@ fn cgident(c: *cgen, n: *node) void = { // in one go, so a body-less FFI binding emits the C symbol // it was declared with via @symbol(), not the ww-side ident. // Bare ident → same-module by ww's resolver, hint with c.curmod. - let rt: *node = fnretlookup(c, nm); - if (rt != nil) { + let rtyp: *node = fnretlookup(c, nm); + if (rtyp != nil) { emitline("\tLEAQ\t"); emitfnname(c, nm, c.curmod); emitline("(SB), AX\n"); @@ -16630,8 +16633,8 @@ fn cgcall(c: *cgen, n: *node) void = { }; }; }; - let rt: *node = fnretlookupmod(c, calleename, cmod); - if (isstrtype(c, rt)) { + let rtyp: *node = fnretlookupmod(c, calleename, cmod); + if (isstrtype(c, rtyp)) { emitline("\tMOVQ\tDX, BX\n"); }; }; @@ -19193,8 +19196,8 @@ fn cgreturn(c: *cgen, n: *node) void = { }; }; if (calleename.len > 0) { - let rt: *node = fnretlookupmod(c, calleename, cmod); - if (istaggedtype(c, rt)) { forwardtagged = true; }; + let rtyp: *node = fnretlookupmod(c, calleename, cmod); + if (istaggedtype(c, rtyp)) { forwardtagged = true; }; }; }; }; @@ -20234,10 +20237,10 @@ fn cgmlet(c: *cgen, n: *node) void = { }; }; if (cnm.len > 0) { - let rt: *node = fnretlookupmod(c, cnm, cmod); - if (rt != nil) { - if (rt.kind == nkind.N_TTUPLE) { - p0t = rt.list; + let rtyp: *node = fnretlookupmod(c, cnm, cmod); + if (rtyp != nil) { + if (rtyp.kind == nkind.N_TTUPLE) { + p0t = rtyp.list; if (p0t != nil) { p1t = p0t.next; }; }; }; diff --git a/selfhost/test/smoke.combined.ww b/selfhost/test/smoke.combined.ww index ff746d86..1a2934b7 100644 --- a/selfhost/test/smoke.combined.ww +++ b/selfhost/test/smoke.combined.ww @@ -110,28 +110,6 @@ import time; @symbol("rt_syscall") fn syscall3(num: nr, a: i64, b: i64, c: i64) i64; @symbol("rt_syscall") fn syscall4(num: nr, a: i64, b: i64, c: i64, d: i64) i64; -// alloc / free — runtime mmap-backed page allocator. Untyped: -// `alloc(n)` returns a `*void` and `free(p, n)` requires the byte -// count back because rt_free is munmap-based and doesn't track -// mapping sizes (the kernel needs the length to release the -// reservation). -// -// Diverges from Hare. Hare exposes `alloc` / `free` as typed -// language builtins (`alloc(value, cap)?` / `free(ptr)`) that the -// compiler lowers to rt::malloc/rt::free; ww has no such builtins, -// so the rt-symbol surface is exposed directly. Stdlib callers -// that need a typed allocation pattern wrap this with a cast plus -// a stored capacity (see [[strings.dup]], [[memio.dynamic]]). -// -// OOM: rt_alloc is a bare mmap(MAP_ANON|MAP_PRIVATE) wrapper with -// no error path. The raw Linux mmap syscall returns a negative -// errno cast to `*void` on failure (e.g. `(void*)-12` for ENOMEM); -// the `MAP_FAILED` (`(void*)-1`) value is a libc-wrapper convention -// that rt_alloc doesn't apply. Neither `== nil` nor `== (void*)-1` -// catches it; any deref of such a return faults. Today the stdlib -// does not check; OOM faults on first dereference. A typed -// fallible variant is a future task. -@symbol("rt_alloc") export fn alloc(n: u64) *void; @symbol("rt_free") export fn free(p: *void, n: u64) void; @symbol("rt_abort") fn abort(msg: str) void; @@ -1726,6 +1704,29 @@ export fn position(d: *decoder) i32 = { }; +// rt — runtime primitives exposed to ww programs. +// Mirrors Hare's rt:: module placement (ref/hare/rt/). + +package rt; + +// alloc — mmap-backed page allocator. Untyped: `alloc(n)` returns a +// `*void`; callers cast to the target type. Diverges from Hare: Hare +// exposes `alloc` / `free` as typed language builtins that the +// compiler lowers to rt::malloc/rt::free; ww has no such builtins, +// so the rt-symbol surface is exposed directly. Stdlib callers that +// need a typed allocation pattern wrap this with a cast plus a stored +// capacity (see [[strings.dup]], [[memio.dynamic]]). +// +// OOM: rt_alloc is a bare mmap(MAP_ANON|MAP_PRIVATE) wrapper with no +// error path. The raw Linux mmap syscall returns a negative errno cast +// to `*void` on failure (e.g. `(void*)-12` for ENOMEM); the +// `MAP_FAILED` (`(void*)-1`) value is a libc-wrapper convention that +// rt_alloc doesn't apply. Neither `== nil` nor `== (void*)-1` catches +// it; any deref of such a return faults. Today the stdlib does not +// check; OOM faults on first dereference. A typed fallible variant is +// a future task (task #39). ref/hare/rt/malloc.ha:27. +@symbol("rt_alloc") export fn alloc(n: u64) *void; + // strings — operations over str ({ptr,len}). Hare port; see // ref/hare/strings/. // @@ -1755,6 +1756,7 @@ package strings; import bytes; import encoding.utf8; import os; +import rt; import types; // toutf8 — borrowed []u8 view of `s`. ref/hare/strings/utf8.ha:29. @@ -1826,7 +1828,7 @@ export fn dup(s: str) str = { r.ptr = nil; r.len = 0; if (s.len == 0) { return r; }; - let buf: *u8 = os.alloc(s.len: u64): *u8; + let buf: *u8 = rt.alloc(s.len: u64): *u8; let i: i32 = 0; for (i < s.len) { buf[i] = s[i]; i += 1; }; r.ptr = buf; @@ -1904,7 +1906,7 @@ export fn concat(strs: str...) str = { r.ptr = nil; r.len = 0; if (total == 0) { return r; }; - let buf: *u8 = os.alloc(total: u64): *u8; + let buf: *u8 = rt.alloc(total: u64): *u8; let off: i32 = 0; i = 0; for (i < strs.len) { @@ -1937,7 +1939,7 @@ export fn join(delim: str, strs: str...) str = { r.ptr = nil; r.len = 0; if (total == 0) { return r; }; - let buf: *u8 = os.alloc(total: u64): *u8; + let buf: *u8 = rt.alloc(total: u64): *u8; let off: i32 = 0; i = 0; for (i < strs.len) { @@ -2614,7 +2616,7 @@ export fn lpad(s: str, p: rune, maxlen: i32) str = { if (s.len >= maxlen) { return dup(s); }; let scratch: [4]u8; let pad: []u8 = runebytes(scratch[0:4], p); - let buf: *u8 = os.alloc(maxlen: u64): *u8; + let buf: *u8 = rt.alloc(maxlen: u64): *u8; let padwrite: i32 = (maxlen - s.len) * pad.len; if (padwrite > maxlen) { padwrite = maxlen; }; let off: i32 = 0; @@ -2697,7 +2699,7 @@ export fn rpad(s: str, p: rune, maxlen: i32) str = { if (s.len >= maxlen) { return dup(s); }; let scratch: [4]u8; let pad: []u8 = runebytes(scratch[0:4], p); - let buf: *u8 = os.alloc(maxlen: u64): *u8; + let buf: *u8 = rt.alloc(maxlen: u64): *u8; let k: i32 = 0; for (k < s.len) { buf[k] = s[k]; diff --git a/test/wcc/700_e2e.c b/test/wcc/700_e2e.c index e9fdfe25..2a368375 100644 --- a/test/wcc/700_e2e.c +++ b/test/wcc/700_e2e.c @@ -152,8 +152,9 @@ static const struct row rows[] = { /* alloc + free via mmap-backed runtime — write through allocated * memory and free it. exit = 0 if the allocation succeeded. */ { "import os;\n" + "import rt;\n" "fn main() i32 = {\n" - " let p: *void = os.alloc(4096u64);\n" + " let p: *void = rt.alloc(4096u64);\n" " if (p == nil) { return 1; };\n" " let bp: *u8 = p: *u8;\n" " bp[0] = 65u8;\n" @@ -280,7 +281,7 @@ static const struct row rows[] = { * builtin via the inherited os.alloc decl. Task #30 graduated * the builtin to `(*T | nomem)`; the `!` aborts on OOM. */ { "package main;\n" - "import os;\n" + "import rt;\n" "type point = struct { x: i32, y: i32 };\n" "fn main() i32 = {\n" " let p: *point = alloc(point { x = 3, y = 4 })!;\n" @@ -301,7 +302,7 @@ static const struct row rows[] = { * Task #30 graduated the slice form to `([]T | nomem)`; the `!` * aborts on OOM. */ { "package main;\n" - "import os;\n" + "import rt;\n" "fn main() i32 = {\n" " let s: []u8 = alloc([], 16)!;\n" " append(s, 72u8, 105u8);\n" @@ -311,14 +312,14 @@ static const struct row rows[] = { * `[]rune` is 4B-per-element; the cgen shortcut scales count by * size(T). Returns s.cap = 8. */ { "package main;\n" - "import os;\n" + "import rt;\n" "fn main() i32 = {\n" " let s: []rune = alloc([], 8)!;\n" " return s.cap;\n" "};", 8 }, /* #45: same as above for `[]str` (16B-per-element). */ { "package main;\n" - "import os;\n" + "import rt;\n" "fn main() i32 = {\n" " let s: []str = alloc([], 4)!;\n" " return s.cap;\n" @@ -327,7 +328,7 @@ static const struct row rows[] = { * return; the alloc-slice shortcut emits MOVQ $nomem_tag, AX + * propret on null. doit returns 12 on success; main unwraps. */ { "package main;\n" - "import os;\n" + "import rt;\n" "fn doit() (i32 | nomem) = {\n" " let s: []str = alloc([], 12)?;\n" " return s.cap: i32;\n" @@ -393,6 +394,7 @@ static const struct row rows[] = { * builtin to a fallible signature; the `!` aborts on OOM. */ { "package main;\n" "import os;\n" + "import rt;\n" "type point = struct { x: i64, y: i64 };\n" "fn main() i32 = {\n" " let p: *point = alloc(point { x = 7, y = 35 })!;\n" diff --git a/test/wcc/758_cgalloc_str_field.c b/test/wcc/758_cgalloc_str_field.c index 1077869f..2470fe3a 100644 --- a/test/wcc/758_cgalloc_str_field.c +++ b/test/wcc/758_cgalloc_str_field.c @@ -60,7 +60,7 @@ static const struct row rows[] = { /* Singleton str field. Pre-fix p.s.len stayed 0; post-fix 5. */ { "alloc_str_singleton", "package main;\n" - "import os;\n" + "import rt;\n" "type holder = struct { s: str };\n" "fn main() i32 = {\n" " let p: *holder = alloc(holder { s = \"hello\" })!;\n" @@ -72,7 +72,7 @@ static const struct row rows[] = { * unaffected. Pre-fix: 100 + 0 = 100; post-fix: 100 + 2 = 102. */ { "alloc_i32_then_str", "package main;\n" - "import os;\n" + "import rt;\n" "type holder = struct { n: i32, s: str };\n" "fn main() i32 = {\n" " let p: *holder = alloc(holder { n = 100, s = \"hi\" })!;\n" @@ -84,7 +84,7 @@ static const struct row rows[] = { * post-fix: 5 + 7 = 12. */ { "alloc_str_then_i32", "package main;\n" - "import os;\n" + "import rt;\n" "type holder = struct { s: str, n: i32 };\n" "fn main() i32 = {\n" " let p: *holder = alloc(holder { s = \"world\", n = 7 })!;\n" @@ -95,7 +95,7 @@ static const struct row rows[] = { * Pins that `fi = nil` advances correctly between str fields. */ { "alloc_two_str", "package main;\n" - "import os;\n" + "import rt;\n" "type holder = struct { a: str, b: str };\n" "fn main() i32 = {\n" " let p: *holder = alloc(holder { a = \"foo\", b = \"barbaz\" })!;\n" @@ -108,7 +108,7 @@ static const struct row rows[] = { * without disrupting either. */ { "alloc_f64_str_i32", "package main;\n" - "import os;\n" + "import rt;\n" "type holder = struct { f: f64, s: str, n: i32 };\n" "fn main() i32 = {\n" " let p: *holder = alloc(holder { f = 1.5f64, s = \"abc\", n = 10 })!;\n" @@ -121,7 +121,7 @@ static const struct row rows[] = { * Indexes the string by hand to avoid pulling in stdlib. */ { "alloc_str_readback", "package main;\n" - "import os;\n" + "import rt;\n" "type holder = struct { s: str };\n" "fn main() i32 = {\n" " let p: *holder = alloc(holder { s = \"hi\" })!;\n" diff --git a/test/wcc/994_w6c_ww.c b/test/wcc/994_w6c_ww.c index 5e6c55a0..844beef9 100644 --- a/test/wcc/994_w6c_ww.c +++ b/test/wcc/994_w6c_ww.c @@ -202,7 +202,7 @@ main(void) /* #45: alloc-slice `!` form, []T element ≠ u8. */ { "alloc_slice_rune_unw", "package main;\n" - "import os;\n" + "import rt;\n" "fn main() i32 = {\n" " let s: []rune = alloc([], 8)!;\n" " return s.cap;\n" @@ -211,7 +211,7 @@ main(void) * enclosing fn's tagged return. */ { "alloc_slice_str_prop", "package main;\n" - "import os;\n" + "import rt;\n" "fn doit() (i32 | nomem) = {\n" " let s: []str = alloc([], 4)?;\n" " return s.cap: i32;\n"