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.
55 lines
1.7 KiB
Plaintext
55 lines
1.7 KiB
Plaintext
// rt/ensure.ww — slice growth helper, archived into libwwrt.a.
|
|
//
|
|
// Companion to the `append(s, v)` builtin. The compiler lowers
|
|
// `append(s, v)` to:
|
|
//
|
|
// ; push v
|
|
// ; s.len += 1
|
|
// ; CALL rt_ensure(&s, sizeof(elem))
|
|
// ; ; ensure may have realloc'd, so re-read s.ptr
|
|
// ; pop v
|
|
// ; *(s.ptr + (s.len - 1) * elem_size) = v
|
|
//
|
|
// One helper handles every element width via the membsz parameter —
|
|
// no per-type wrapper functions (appendu8 / appendi64) needed.
|
|
//
|
|
// User code never `use`s this — the symbol is resolved at link time
|
|
// from libwwrt.a, like rt_malloc and rt_streq. No `module` declaration:
|
|
// rt/ensure.ww is compiled standalone via `w6c rt/ensure.ww` (not
|
|
// through the driver), and its `export fn rt_ensure` must keep its
|
|
// bare symbol name so the linker resolves it.
|
|
|
|
@symbol("rt_malloc") fn malloc(n: u64) *void;
|
|
@symbol("rt_free") fn free(p: *void, n: u64) void;
|
|
|
|
// Mirrors ww's []T header layout: 24 bytes with 8-byte slots.
|
|
// ww's source uses i32 for len/cap but the compiler stores them in
|
|
// 8-byte slots; declaring as i64 here keeps the field offsets right
|
|
// for this polymorphic alias.
|
|
type slice = struct {
|
|
ptr: *u8,
|
|
len: i64,
|
|
cap: i64,
|
|
};
|
|
|
|
export fn rt_ensure(s: *slice, membsz: u64) void = {
|
|
if (s.cap >= s.len) { return; };
|
|
let nc: i64 = s.cap * 2i64;
|
|
if (nc < 8i64) { nc = 8i64; };
|
|
for (nc < s.len) { nc *= 2i64; };
|
|
// Task #30 (commit 3) upgrades to nullable *void + null-check.
|
|
// For now, rt_malloc returns plain *void; OOM faults on deref.
|
|
let np: *u8 = malloc((nc: u64) * membsz): *u8;
|
|
let n: u64 = (s.cap: u64) * membsz;
|
|
let i: u64 = 0u64;
|
|
for (i < n) {
|
|
np[i] = s.ptr[i];
|
|
i += 1u64;
|
|
};
|
|
if (s.cap > 0i64) {
|
|
free(s.ptr: *void, (s.cap: u64) * membsz);
|
|
};
|
|
s.ptr = np;
|
|
s.cap = nc;
|
|
};
|