lib/rt: rename rt_alloc → rt_malloc; rt.alloc → rt.malloc

Hare's canonical runtime allocator is rt::malloc with linker symbol
rt.malloc (ref/hare/rt/malloc.ha:27,78). ww kept the dot→underscore
Plan 9 convention (CLAUDE.md rule 4) so the linker symbol becomes
rt_malloc; the lib/rt exported function name becomes malloc; ww
callers say rt.malloc(...).

The language builtin keyword stays `alloc(T)!` — unchanged from Hare
(ref/hare/hare/lex/token.ha:21 ltok::ALLOC, parse/expr.ha:398
builtin()). The rename only touches the lowered linker symbol and the
exported function name behind it; the user-facing syntax for
heap-allocation is identical to Hare.

Surface:
- rt/alloc.s: TEXT rt_alloc → TEXT rt_malloc, labels updated
- lib/rt/malloc.ww: @symbol("rt_malloc") fn malloc(...) (was rt_alloc/alloc)
- rt/ensure.ww: local FFI decl + call site updated to malloc; `!` dropped
  on the direct FFI call (rt_malloc returns *void, not a tagged union)
- 18 .ww callers: rt.alloc(...) → rt.malloc(...)
- cstage cmd/wcc/check.c + wwstage selfhost/cmd/wcc/check.ww
  alloc-builtin suppression gate routes through ffi_resolve("malloc")
  for the lowering; the user-shadow check still keys on the BUILTIN
  KEYWORD "alloc" since that is what `alloc(...)` parses as. Adding
  "malloc" to the user-shadow check was unnecessary and was reverted
  during pre-commit review.
- cstage cmd/w6c/cgen.c: 2× ffi_resolve("alloc") → ffi_resolve("malloc")
- wwstage cgenexpr/cgenstmt: 2× ffiresolve(c, "alloc") → ffiresolve(c, "malloc")
- Test fixtures (700_e2e, 758_cgalloc_str_field, 990_selfhost, 992_w6l_ww,
  selfhost/test/tagged_ptr_ret.ww): updated inline ww sources to the new
  decl + call form

This is commit 2 of 3 in the lib/rt extraction (#38). Commit 3 closes
the OOM contract — return type becomes nullable *void and the builtin
lowering null-checks + propagates nomem.

Verified 132/132 + 995_self_rebuild byte-identity (5 wwstage tools
round-trip identical) + make clean cold rebuild.
This commit is contained in:
2026-05-20 22:11:34 +09:00
parent d68d3c7eb4
commit a376ec89eb
38 changed files with 277 additions and 283 deletions

View File

@@ -438,7 +438,7 @@ export fn getdents64(fd: i32, buf: *u8, n: u64) i64 = {
// rt_envp — runtime-side getter. rt/start.s captures envp into a DATAW
// slot before calling main; this binding lifts the captured pointer
// into ww. Same FFI shape as rt_syscall / rt_alloc / rt_abort: a TEXT
// into ww. Same FFI shape as rt_syscall / rt_malloc / rt_abort: a TEXT
// symbol the linker resolves. The returned `**u8` is a NUL-terminated
// table of `*u8` entries, each pointing at a NUL-terminated
// "NAME=VALUE" byte sequence.
@@ -732,7 +732,7 @@ export fn exists(path: str) bool = {
package rt;
// alloc — mmap-backed page allocator. Untyped: `alloc(n)` returns a
// malloc — mmap-backed page allocator. Untyped: `malloc(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,
@@ -740,20 +740,20 @@ package rt;
// 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
// OOM: rt_malloc 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
// rt_malloc 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;
@symbol("rt_malloc") export fn malloc(n: u64) *void;
// selfhost/cmd/wcc/mem.ww — port of cmd/wcc/mem.c.
//
// Bump arena allocator. Backed by the runtime page allocator
// (rt_alloc / rt_free), no libc. Each chunk is mmap'd; when the
// (rt_malloc / rt_free), no libc. Each chunk is mmap'd; when the
// current chunk runs out we link a fresh one. Freeing the arena
// unmaps the chain.
//
@@ -783,8 +783,8 @@ fn roundup(n: u64, a: u64) u64 = {
};
export fn newarena() *arena = {
let a: *arena = rt.alloc(ARENA_SZ): *arena;
a.buf = rt.alloc(INIT_CHUNK): *u8;
let a: *arena = rt.malloc(ARENA_SZ): *arena;
a.buf = rt.malloc(INIT_CHUNK): *u8;
a.off = 0u64;
a.cap = INIT_CHUNK;
a.next = nil;
@@ -801,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 = rt.alloc(ARENA_SZ): *arena;
let old: *arena = rt.malloc(ARENA_SZ): *arena;
old.buf = a.buf;
old.off = a.off;
old.cap = a.cap;
old.next = a.next;
old.total = 0u64;
a.buf = rt.alloc(want): *u8;
a.buf = rt.malloc(want): *u8;
a.off = 0u64;
a.cap = want;
a.next = old;
@@ -1938,7 +1938,7 @@ export fn dup(s: str) str = {
r.ptr = nil;
r.len = 0;
if (s.len == 0) { return r; };
let buf: *u8 = rt.alloc(s.len: u64): *u8;
let buf: *u8 = rt.malloc(s.len: u64): *u8;
let i: i32 = 0;
for (i < s.len) { buf[i] = s[i]; i += 1; };
r.ptr = buf;
@@ -1960,7 +1960,7 @@ export fn dup(s: str) str = {
// (#46). The pre-allocated slice has `cap == s.len`, so appendstr's
// rt_ensure call never reaches the grow branch.
//
// Empty input bypasses the alloc: rt_alloc(0) is an mmap of 0 bytes
// Empty input bypasses the alloc: rt_malloc(0) is an mmap of 0 bytes
// which returns -EINVAL, and the alloc-slice `?` shortcut routes
// that through nomem — Hare's heap allocator hands back a sentinel
// instead (#47). Return `{nil, 0, 0}` directly so callers get the
@@ -2016,7 +2016,7 @@ export fn concat(strs: str...) str = {
r.ptr = nil;
r.len = 0;
if (total == 0) { return r; };
let buf: *u8 = rt.alloc(total: u64): *u8;
let buf: *u8 = rt.malloc(total: u64): *u8;
let off: i32 = 0;
i = 0;
for (i < strs.len) {
@@ -2049,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 = rt.alloc(total: u64): *u8;
let buf: *u8 = rt.malloc(total: u64): *u8;
let off: i32 = 0;
i = 0;
for (i < strs.len) {
@@ -2726,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 = rt.alloc(maxlen: u64): *u8;
let buf: *u8 = rt.malloc(maxlen: u64): *u8;
let padwrite: i32 = (maxlen - s.len) * pad.len;
if (padwrite > maxlen) { padwrite = maxlen; };
let off: i32 = 0;
@@ -2809,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 = rt.alloc(maxlen: u64): *u8;
let buf: *u8 = rt.malloc(maxlen: u64): *u8;
let k: i32 = 0;
for (k < s.len) {
buf[k] = s[k];
@@ -15924,14 +15924,14 @@ fn cgbin(c: *cgen, n: *node) void = {
};
// cgalloc — `alloc(value)` builtin lowering. Allocate sizeof(value)
// bytes via rt_alloc, then write the value's bytes into the new
// bytes via rt_malloc, then write the value's bytes into the new
// region. For an N_STRUCTLIT arg, allocate the struct's totsize and
// emit per-field stores at each field's offset. For a scalar/ptr,
// allocate 8 bytes and store one word. Mirrors cmd/w6c/cgen.c's
// alloc-special branch in N_CALL.
//
// Task #30: result is the graduated `(*T | nomem)` tagged-pointer
// pair (AX=tag, DX=ptr). rt_alloc now returns 0 on OOM
// pair (AX=tag, DX=ptr). rt_malloc now returns 0 on OOM
// (rt/alloc.s); branch on AX to emit the nomem variant (tag=1,
// DX=0) or the success variant (tag=0, DX=ptr) after the
// value-init stores complete. Callers wrap with `!` / `?` to
@@ -15957,7 +15957,7 @@ fn cgalloc(c: *cgen, n: *node) void = {
emitint(sz: i64);
emitline(", DI\n");
emitline("\tCALL\t");
emitline(ffiresolve(c, "alloc"));
emitline(ffiresolve(c, "malloc"));
emitline("(SB)\n");
emitline("\tCMPQ\t$0, AX\n");
emitline("\tJNE\t"); emitline(okl); emitline("\n");
@@ -16184,7 +16184,7 @@ fn cgcall(c: *cgen, n: *node) void = {
};
};
// `alloc(value)` builtin: heap-init a fresh *T with the
// value's bytes. For struct literals, lower to rt_alloc
// value's bytes. For struct literals, lower to rt_malloc
// + per-field stores. Mirrors cmd/w6c/cgen.c's N_CALL
// alloc path.
//
@@ -19588,7 +19588,7 @@ fn cglet(c: *cgen, n: *node) void = {
let rhs: *node = n.rhs;
// `let s: []T = alloc([], n)!;` / `?` shortcut (#32, #45).
// Mirror of cstage cgen.c N_LET arrlit-empty branch: allocate
// n*esz bytes via rt_alloc, then build the {ptr, 0, n} slice
// n*esz bytes via rt_malloc, then build the {ptr, 0, n} slice
// header in the let slot. The `!`/`?` wraps the builtin's
// `([]T | nomem)` return; walk into the N_TRYUNW / N_TRYPROP
// to keep the direct-store fast path rather than falling
@@ -19657,7 +19657,7 @@ fn cglet(c: *cgen, n: *node) void = {
};
emitline("\tMOVQ\tAX, DI\n");
emitline("\tCALL\t");
emitline(ffiresolve(c, "alloc"));
emitline(ffiresolve(c, "malloc"));
emitline("(SB)\n");
if (viatryunw) {
let okl: str = mklabel(c, "tryunw_ok");