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

@@ -154,7 +154,7 @@ static const struct row rows[] = {
{ "import os;\n"
"import rt;\n"
"fn main() i32 = {\n"
" let p: *void = rt.alloc(4096u64);\n"
" let p: *void = rt.malloc(4096u64);\n"
" if (p == nil) { return 1; };\n"
" let bp: *u8 = p: *u8;\n"
" bp[0] = 65u8;\n"
@@ -341,7 +341,7 @@ static const struct row rows[] = {
* must suppress the builtin and dispatch to the user fn so the
* call returns n+100. Pre-gate this site lands in the typed-builtin
* path: arg is i64 → returns *i64 → init-type fails against the
* declared i64 (and would over-allocate against rt_alloc anyway).
* declared i64 (and would over-allocate against rt_malloc anyway).
* Inline multi-`package` mirrors driver-concatenated layout; the
* `import myos;` directive is silently skipped by locate_import
* (no external module by that name). */

View File

@@ -5,7 +5,7 @@
* Pre-fix (#22): wwstage cgalloc's per-field walk routed str-typed
* fields through the generic `MOVQ (SP), BX ; MOVQ AX, foff(BX)` path,
* which landed only AX (str.ptr) and clobbered BX (str.len) with the
* heap base. str.len silently stayed zero (rt_alloc is MAP_ANON-backed,
* heap base. str.len silently stayed zero (rt_malloc is MAP_ANON-backed,
* so the slot was zero-init rather than garbage — but still wrong).
* 990 and 995 byte-identity didn't catch this because nothing in the
* bootstrapped selfhost source uses `alloc(T { strfield = "..." })!`.
@@ -16,15 +16,15 @@
* multi-word fields) is the broader follow-up; this row pins str.
*
* Pre-fix (#25): wwstage's cgalloc emitted a hardcoded `CALL
* rt_alloc(SB)` while cstage routed the same site through
* rt_malloc(SB)` while cstage routed the same site through
* ffi_resolve("alloc"), so direct `w6c` vs `w6c_ww` on a fixture
* without the @symbol decl in scope diverged (cstage: `CALL
* alloc(SB)`; wwstage: `CALL rt_alloc(SB)`). The fix swaps both
* alloc(SB)`; wwstage: `CALL rt_malloc(SB)`). The fix swaps both
* cgenexpr.ww and cgenstmt.ww cgalloc CALL sites to
* `ffiresolve(c, "alloc")`, aligning wwstage down to the leaner
* `ffiresolve(c, "malloc")`, aligning wwstage down to the leaner
* cstage shape (CLAUDE.md rule 10). The `asm_rows` table below pins
* the CALL line via single-file `w6c -o` / `w6c_ww -o`; `ww build`
* combines lib/os/os.ww into the fixture and would always supply
* combines lib/rt/malloc.ww into the fixture and would always supply
* the @symbol decl, masking the regression.
*
* The runtime `rows` below run the generated binary under cstage and
@@ -177,20 +177,20 @@ run_driver(const char *driver, const struct row *r, int i)
* both stages' .s output. Single-file `w6c` compile (no `ww build`
* combine), so the @symbol decl is only in scope when the fixture
* writes one — that's how `noscope_*` rows pin the pre-fix wwstage
* hardcoded-`rt_alloc` divergence and `withsym_*` rows pin that
* hardcoded-`rt_malloc` divergence and `withsym_*` rows pin that
* ffiresolve actually hits when the decl is present. */
struct asm_row { const char *label; const char *src; const char *want_sym; };
static const struct asm_row asm_rows[] = {
/* Int-field, no @symbol in scope. ffiresolve("alloc") misses the
* ffi table and returns "alloc" unchanged on both stages. Pre-fix
* wwstage hardcoded `CALL rt_alloc(SB)` → diverged from cstage's
* ffi_resolve-mediated `CALL alloc(SB)`. */
/* Int-field, no @symbol in scope. ffiresolve("malloc") misses the
* ffi table and returns "malloc" unchanged on both stages. Pre-fix
* wwstage hardcoded `CALL rt_malloc(SB)` → diverged from cstage's
* ffi_resolve-mediated `CALL malloc(SB)`. */
{ "noscope_intfield",
"package main;\n"
"type holder = struct { n: i32 };\n"
"fn dummy() *holder = { return alloc(holder { n = 42 })!; };\n",
"alloc" },
"malloc" },
/* Str-field, no @symbol in scope. Confirms the str-field branch
* (the #22 fix) still routes its CALL through ffiresolve, not a
* stray hardcoded literal copied alongside the #22 emit. */
@@ -198,7 +198,7 @@ static const struct asm_row asm_rows[] = {
"package main;\n"
"type holder = struct { s: str };\n"
"fn dummy() *holder = { return alloc(holder { s = \"hi\" })!; };\n",
"alloc" },
"malloc" },
/* Two-step let-then-assign. cglet's CALL site (cgenstmt.ww:647)
* is the second cgalloc emit point; this row covers it. */
{ "noscope_let_then_assign",
@@ -209,16 +209,16 @@ static const struct asm_row asm_rows[] = {
" p.n = 7;\n"
" return p;\n"
"};\n",
"alloc" },
/* Explicit @symbol decl in scope. ffiresolve("alloc") → "rt_alloc"
* so both stages emit `CALL rt_alloc(SB)` — positive confirmation
"malloc" },
/* Explicit @symbol decl in scope. ffiresolve("malloc") → "rt_malloc"
* so both stages emit `CALL rt_malloc(SB)` — positive confirmation
* that the ffi table lookup hits, complementing the noscope rows. */
{ "withsym_intfield",
"package main;\n"
"@symbol(\"rt_alloc\") export fn alloc(n: u64) *void;\n"
"@symbol(\"rt_malloc\") export fn malloc(n: u64) *void;\n"
"type holder = struct { n: i32 };\n"
"fn dummy() *holder = { return alloc(holder { n = 42 })!; };\n",
"rt_alloc" },
"rt_malloc" },
};
/* asm_disp_row — pins the foff=0 displacement formatting (#24 part B).
@@ -268,7 +268,7 @@ static const struct asm_disp_row asm_disp_rows[] = {
};
/* asm_call_check — compile via direct w6c / w6c_ww (no `ww build`),
* extract the `CALL\t<sym>(SB)` line referencing alloc/rt_alloc from
* extract the `CALL\t<sym>(SB)` line referencing alloc/rt_malloc from
* each .s file, and verify (a) both stages emit the same line and
* (b) the symbol matches r->want_sym. Returns 0 on success. */
static int
@@ -313,7 +313,7 @@ asm_call_check(const char *bin, const struct asm_row *r, int i)
while (fgets(buf, sizeof buf, fc)) {
if (strstr(buf, "\tCALL\t")
&& (strstr(buf, "alloc(SB)")
|| strstr(buf, "rt_alloc(SB)"))) {
|| strstr(buf, "rt_malloc(SB)"))) {
strncpy(cline, buf, sizeof cline - 1);
break;
}
@@ -321,7 +321,7 @@ asm_call_check(const char *bin, const struct asm_row *r, int i)
while (fgets(buf, sizeof buf, fw)) {
if (strstr(buf, "\tCALL\t")
&& (strstr(buf, "alloc(SB)")
|| strstr(buf, "rt_alloc(SB)"))) {
|| strstr(buf, "rt_malloc(SB)"))) {
strncpy(wline, buf, sizeof wline - 1);
break;
}

View File

@@ -437,13 +437,13 @@ probe_ww_compile(const char *bin)
* pointer-to-struct field). Pre-fix the cgen fell through
* silently and returned the inner pointer instead of the
* dereferenced field. Surfaced porting w6l/pass.ww. */
{ "@symbol(\"rt_alloc\") fn rt_alloc(n: u64) *void;\n"
{ "@symbol(\"rt_malloc\") fn rt_malloc(n: u64) *void;\n"
"type inner = struct { val: u64 };\n"
"type outer = struct { p: *inner };\n"
"fn main() i32 = {\n"
" let in_: *inner = rt_alloc(8u64): *inner;\n"
" let in_: *inner = rt_malloc(8u64): *inner;\n"
" in_.val = 42u64;\n"
" let o: *outer = rt_alloc(8u64): *outer;\n"
" let o: *outer = rt_malloc(8u64): *outer;\n"
" o.p = in_;\n"
" return o.p.val: i32;\n"
"};", 42 },

View File

@@ -108,7 +108,7 @@ main(void)
} cases[] = {
/* Real bootstrap-style links: every selfhost main.o paired
* with libwwrt.a. Exercises both the .o code path and the
* archive two-pass loader (rt_alloc / rt_syscall / rt_abort
* archive two-pass loader (rt_malloc / rt_syscall / rt_abort
* are pulled from libwwrt.a as undefs are encountered). */
{ "wwdump+libwwrt",
"%s/selfhost/cmd/wwdump/main.o %s/out/lib/libwwrt.a" },