Files
ww/rt/ensure.ww
Hojun-Cho 61705fb39e cmd+rt+selfhost+test: graduate alloc to (*T | nomem) / ([]T | nomem)
Per Hare convention, alloc is a typed builtin that returns a tagged
union carrying nomem as the OOM variant. Callers spell their policy:
`alloc(T)!` aborts on OOM (the old behavior), `alloc(T)?` propagates
when the enclosing fn already returns nomem.

cstage: check builds TY_TAGGED{*T | nomem} (or {[]T | nomem}); cgen
emits AX=tag, DX=ptr per the general tagged-return ABI (the (*T|!void)
nullable-ptr fold gated in ea76ee4 keeps this clean). wwstage cgalloc
mirrors. rt/alloc.s zeroes AX on syscall error so the builtin's null
check sees a clean 0 instead of mmap's -errno leaking through as a
poisoned pointer.

Migration: 3 `!` sites in test/wcc/700_e2e.c, 1 `!` site in
rt/ensure.ww (preserves the pre-existing sizeof bug tracked by #27),
1 `?` site in selfhost/test/tagged_ptr_ret.ww (allocbox exercises
real `?` propagation against a (*T | nomem) return).

130/130 tests green, 994_w6c_ww + 995_self_rebuild stage byte-identity
preserved. Follow-ups #31 (wwstage checkletassign leniency), #32
(wwstage slice-form gap), #33 (tagged_ptr_ret.ww make-test wiring).
2026-05-19 20:25:14 +09:00

58 lines
1.9 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_alloc 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_alloc") fn alloc(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: the alloc builtin returns `(*T | nomem)`; `!` aborts
// on OOM. The longstanding sizeof-only allocation bug (task #27)
// stays unfixed here — rt_ensure presumes a same-module `fn
// alloc(n)` resolution that the bare cur_mod=NULL gate at
// cmd/wcc/check.c:984 doesn't honour.
let np: *u8 = alloc((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;
};