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:
@@ -4132,7 +4132,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
* `alloc` shadows the builtin (task #23).
|
||||
*
|
||||
* Result is the graduated `(*T | nomem)` tagged-pointer
|
||||
* ABI (AX=tag, DX=ptr) — task #30. rt_alloc returns 0
|
||||
* ABI (AX=tag, DX=ptr) — task #30. rt_malloc returns 0
|
||||
* on OOM (rt/alloc.s); we branch on AX, building tag=1
|
||||
* (nomem, DX=0) on null and tag=0 (success, DX=ptr)
|
||||
* after the value-init stores complete. Callers wrap
|
||||
@@ -4146,7 +4146,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
char *alloc_ok = mklabel(c, "alloc_ok");
|
||||
char *alloc_done = mklabel(c, "alloc_done");
|
||||
ins2(c, A_MOVQ, aimm(sz), areg(D_DI));
|
||||
ins1(c, A_CALL, asym(ffi_resolve("alloc")));
|
||||
ins1(c, A_CALL, asym(ffi_resolve("malloc")));
|
||||
ins2(c, A_CMPQ, aimm(0), areg(D_AX));
|
||||
ins1(c, A_JNE, abranch(alloc_ok));
|
||||
ins2(c, A_MOVQ, aimm(1), areg(D_AX));
|
||||
@@ -6403,7 +6403,7 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
ins2(c, A_IMULQ, areg(D_BX), areg(D_AX));
|
||||
}
|
||||
ins2(c, A_MOVQ, areg(D_AX), areg(D_DI));
|
||||
ins1(c, A_CALL, asym(ffi_resolve("alloc")));
|
||||
ins1(c, A_CALL, asym(ffi_resolve("malloc")));
|
||||
if (via_tryunw) {
|
||||
char *ok = mklabel(c, "tryunw_ok");
|
||||
ins2(c, A_CMPQ, aimm(0), areg(D_AX));
|
||||
|
||||
@@ -968,16 +968,13 @@ cexpr(Checker *c, Node *n)
|
||||
return n->type;
|
||||
}
|
||||
/* `alloc(value)` Hare-style builtin — suppressed when the
|
||||
* current module declares its own `alloc` (lib/os/os.ww,
|
||||
* rt/ensure.ww). Without the gate, the bare same-module call
|
||||
* lands in the typed-builtin path and silently allocates
|
||||
* sizeof(arg-type) bytes against rt_alloc, shadowing the
|
||||
* user decl. Strict same-module check (not scope_lookup_prefer):
|
||||
* `use os;` in a primary brings os.alloc into the flat scope
|
||||
* as a fallback match — that's what the `abort` precedent
|
||||
* sidesteps by leaving os.abort un-exported, but os.alloc IS
|
||||
* exported. c->cur_mod==NULL is the primary unit; gate only
|
||||
* fires when a same-module decl is registered. Task #23. */
|
||||
* current module declares its own `alloc` (user shadow, task
|
||||
* #23). Strict same-module check (not scope_lookup_prefer):
|
||||
* `import rt;` brings rt.malloc into a separate qualified
|
||||
* scope, not flat — only a same-module `fn alloc` registers
|
||||
* here. The Hare builtin name is `alloc` (ref/hare/hare/lex/
|
||||
* token.ha:21, ltok::ALLOC); a hypothetical user `fn malloc`
|
||||
* does not shadow it. Task #23. */
|
||||
if (n->lhs && n->lhs->kind == N_IDENT &&
|
||||
n->lhs->str && strcmp(n->lhs->str, "alloc") == 0 &&
|
||||
n->list != NULL && n->list->next == NULL &&
|
||||
@@ -987,7 +984,7 @@ cexpr(Checker *c, Node *n)
|
||||
Type *def = type_default(t);
|
||||
Type *pt = type_ptr(c->a, def ? def : ty_void);
|
||||
/* Task #30 — graduate to Hare's `(*T | nomem)` shape;
|
||||
* the cgen branches on rt_alloc's null return to emit
|
||||
* the cgen branches on rt_malloc's null return to emit
|
||||
* the nomem variant. Two-variant union with TY_PTR +
|
||||
* TY_NAMED(nomem) does not trip the nullable-pointer
|
||||
* fold (#25), so the result rides the general AX=tag,
|
||||
@@ -1053,7 +1050,7 @@ cexpr(Checker *c, Node *n)
|
||||
/* alloc([], n) — Hare-style fresh slice with cap n. We pin
|
||||
* the element type to u8 by default; the caller's declared
|
||||
* slice type drives the actual element size at codegen.
|
||||
* Same scope_lookup_in_module gate as the value-form (#23). */
|
||||
* Same single-key `alloc` gate as the value-form above. */
|
||||
if (n->lhs && n->lhs->kind == N_IDENT &&
|
||||
n->lhs->str && strcmp(n->lhs->str, "alloc") == 0 &&
|
||||
n->list && n->list->kind == N_ARRLIT &&
|
||||
|
||||
@@ -201,18 +201,18 @@ fn now_ns() u64 = {
|
||||
// ---- cmat allocate / free ---------------------------------------------
|
||||
|
||||
fn cmat_alloc(w: i32, h: i32, r: *rng) *cmat = {
|
||||
let m = rt.alloc(CMAT_SZ): *cmat;
|
||||
let m = rt.malloc(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 = 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;
|
||||
m.head = rt.malloc(n4): *i32;
|
||||
m.length = rt.malloc(n4): *i32;
|
||||
m.speed = rt.malloc(n4): *i32;
|
||||
m.counter = rt.malloc(n4): *i32;
|
||||
m.glyphs = rt.malloc((w: u64) * (MAX_TRAIL: u64)): *u8;
|
||||
let c = 0;
|
||||
for (c < w) {
|
||||
m.head[c] = -rng_range(r, 0, h);
|
||||
|
||||
@@ -124,7 +124,7 @@ def ENV_SZ: u64 = 32u64;
|
||||
// result). repl() resets the trans arena between top-level forms.
|
||||
//
|
||||
// Pre-arena every cell paid the 4 KiB page-per-call cost of
|
||||
// rt_alloc; test_huge.lisp peaked at ~525 MB under massif
|
||||
// rt_malloc; test_huge.lisp peaked at ~525 MB under massif
|
||||
// --pages-as-heap=yes. With chunking alone Pass A brought that to
|
||||
// ~253 MB; per-form trans reset (this pass) drops it further by
|
||||
// reclaiming the trans high-water mark after each form.
|
||||
@@ -159,7 +159,7 @@ let perm_arena: arena;
|
||||
let trans_arena: arena;
|
||||
|
||||
fn arena_new_chunk(prev: *chunk) *chunk = {
|
||||
let raw: *u8 = rt.alloc(ARENA_CHUNK): *u8;
|
||||
let raw: *u8 = rt.malloc(ARENA_CHUNK): *u8;
|
||||
let c: *chunk = raw: *chunk;
|
||||
c.next = prev;
|
||||
c.cur = 0u64;
|
||||
@@ -240,7 +240,7 @@ export type eof = !void; // parser: end of input — distinct from a parse
|
||||
//
|
||||
// Every v* constructor allocates one VALUE_SZ slab from the arena and
|
||||
// initialises the fields the kind needs. The cgen's
|
||||
// `alloc(value{...})` struct-literal sugar lowers to rt_alloc directly
|
||||
// `alloc(value{...})` struct-literal sugar lowers to rt_malloc directly
|
||||
// (one mmap per cell), so the constructors hand-roll the alloc + per-
|
||||
// field stores instead. Same shape as `new T(...)` in other languages.
|
||||
|
||||
@@ -343,7 +343,7 @@ def SYM_BLOBSZ: i32 = 8192;
|
||||
// a top-level `[N]T` global, because the wwstage cgen still mis-lowers
|
||||
// `globalarr[i] = …` (variable index) to `LEAQ (BP), BX` — it treats
|
||||
// the global address as if it were on the stack. Pointers indexed
|
||||
// through `[i]` go through the cmatrix path (LEAQ from rt_alloc result)
|
||||
// through `[i]` go through the cmatrix path (LEAQ from rt_malloc result)
|
||||
// which works.
|
||||
let sym_off: *i32 = nil;
|
||||
let sym_len: *i32 = nil;
|
||||
@@ -1566,10 +1566,10 @@ fn bind_one(e: **env, name: str, id: i32) void = {
|
||||
|
||||
export fn initsyms() void = {
|
||||
arena_init();
|
||||
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;
|
||||
sym_off = rt.malloc((SYM_CAP: u64) * 4u64): *i32;
|
||||
sym_len = rt.malloc((SYM_CAP: u64) * 4u64): *i32;
|
||||
sym_blob = rt.malloc(SYM_BLOBSZ: u64): *u8;
|
||||
obuf = rt.malloc(OBUF_SZ: u64): *u8;
|
||||
|
||||
// Stash the special-form ids so classify_sform sees them.
|
||||
SID_QUOTE = intern("quote");
|
||||
|
||||
@@ -886,7 +886,7 @@ export fn asprintf(fmt: str, args: field...) str = {
|
||||
match (cres) { case void => {}; case io.closed => {}; };
|
||||
return out;
|
||||
};
|
||||
let tight: *u8 = rt.alloc(view.len: u64): *u8;
|
||||
let tight: *u8 = rt.malloc(view.len: u64): *u8;
|
||||
let i: i32 = 0;
|
||||
for (i < view.len) {
|
||||
tight[i] = view.ptr[i];
|
||||
|
||||
@@ -197,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 = rt.alloc(newcap: u64): *u8;
|
||||
let nbuf: *u8 = rt.malloc(newcap: u64): *u8;
|
||||
let i: i32 = 0;
|
||||
for (i < m.len) {
|
||||
nbuf[i] = m.ptr[i];
|
||||
|
||||
@@ -340,7 +340,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.
|
||||
|
||||
@@ -103,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 = rt.alloc(4096u64): *u8;
|
||||
let p: *u8 = rt.malloc(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.)
|
||||
|
||||
@@ -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 = rt.alloc(b.len: u64): *u8;
|
||||
let buf: *u8 = rt.malloc(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 = rt.alloc(b.len: u64): *u8;
|
||||
let buf: *u8 = rt.malloc(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 = rt.alloc(a.len: u64): *u8;
|
||||
let buf: *u8 = rt.malloc(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 = rt.alloc(total: u64): *u8;
|
||||
let buf: *u8 = rt.malloc(total: u64): *u8;
|
||||
let i: i32 = 0;
|
||||
for (i < an) { buf[i] = a[i]; i += 1; };
|
||||
buf[an] = SEP;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
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,
|
||||
@@ -11,12 +11,12 @@ 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;
|
||||
|
||||
@@ -126,7 +126,7 @@ fn dupstr(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;
|
||||
|
||||
@@ -99,7 +99,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;
|
||||
@@ -121,7 +121,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
|
||||
@@ -177,7 +177,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) {
|
||||
@@ -210,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 = rt.alloc(total: u64): *u8;
|
||||
let buf: *u8 = rt.malloc(total: u64): *u8;
|
||||
let off: i32 = 0;
|
||||
i = 0;
|
||||
for (i < strs.len) {
|
||||
@@ -887,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 = 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;
|
||||
@@ -970,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 = 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];
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// rt/alloc.s — page allocator via the mmap syscall.
|
||||
//
|
||||
// rt_alloc(n: u64) returns a *void aligned at a page boundary, sized
|
||||
// rt_malloc(n: u64) returns a *void aligned at a page boundary, sized
|
||||
// to the next page multiple. Pair with rt_free(p, n).
|
||||
//
|
||||
// We pin to PROT_READ|PROT_WRITE and MAP_PRIVATE|MAP_ANONYMOUS so
|
||||
@@ -12,7 +12,7 @@
|
||||
// the failure path here returns 0 instead of a poisoned pointer. The
|
||||
// builtin's caller is expected to `!`/`?` the result.
|
||||
|
||||
TEXT rt_alloc,$0
|
||||
TEXT rt_malloc,$0
|
||||
MOVQ DI, SI // arg 1: length = caller's n
|
||||
MOVQ $0, DI // arg 0: addr = NULL (kernel chooses)
|
||||
MOVQ $3, DX // arg 2: prot = R|W
|
||||
@@ -22,9 +22,9 @@ TEXT rt_alloc,$0
|
||||
MOVQ $9, AX // syscall: mmap
|
||||
SYSCALL
|
||||
CMPQ $0, AX
|
||||
JGE rt_alloc_ok
|
||||
JGE rt_malloc_ok
|
||||
XORQ AX, AX
|
||||
rt_alloc_ok:
|
||||
rt_malloc_ok:
|
||||
RET
|
||||
|
||||
TEXT rt_free,$0
|
||||
|
||||
13
rt/ensure.ww
13
rt/ensure.ww
@@ -14,12 +14,12 @@
|
||||
// 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:
|
||||
// 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_alloc") fn alloc(n: u64) *void;
|
||||
@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.
|
||||
@@ -37,12 +37,9 @@ export fn rt_ensure(s: *slice, membsz: u64) void = {
|
||||
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;
|
||||
// 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) {
|
||||
|
||||
@@ -32,7 +32,7 @@ TEXT _start,$0
|
||||
|
||||
// rt_envp — getter that returns the envp pointer captured at process
|
||||
// entry. Bound from lib/os via `@symbol("rt_envp") fn rtenvp() **u8;`,
|
||||
// matching the rt_syscall / rt_alloc / rt_abort pattern. Read-only
|
||||
// matching the rt_syscall / rt_malloc / rt_abort pattern. Read-only
|
||||
// view of the kernel-supplied table; the bytes live for the process
|
||||
// lifetime. A future setenv that grows the table re-points the slot
|
||||
// (separate task).
|
||||
|
||||
@@ -23,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 = rt.alloc(nc): *u8;
|
||||
let nb: *u8 = rt.malloc(nc): *u8;
|
||||
let i: u64 = 0u64;
|
||||
for (i < a.textlen) { nb[i] = a.text[i]; i += 1u64; };
|
||||
a.text = nb;
|
||||
@@ -60,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 = rt.alloc(nc): *u8;
|
||||
let nb: *u8 = rt.malloc(nc): *u8;
|
||||
let i: u64 = 0u64;
|
||||
for (i < a.datalen) { nb[i] = a.data[i]; i += 1u64; };
|
||||
a.data = nb;
|
||||
|
||||
@@ -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;
|
||||
@@ -1760,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 = rt.alloc(nc): *u8;
|
||||
let nb: *u8 = rt.malloc(nc): *u8;
|
||||
let i: u64 = 0u64;
|
||||
for (i < a.textlen) { nb[i] = a.text[i]; i += 1u64; };
|
||||
a.text = nb;
|
||||
@@ -1797,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 = rt.alloc(nc): *u8;
|
||||
let nb: *u8 = rt.malloc(nc): *u8;
|
||||
let i: u64 = 0u64;
|
||||
for (i < a.datalen) { nb[i] = a.data[i]; i += 1u64; };
|
||||
a.data = nb;
|
||||
@@ -3058,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 = rt.alloc(nz + 1u64): *u8;
|
||||
let buf: *u8 = rt.malloc(nz + 1u64): *u8;
|
||||
let rr: (i64 | os.oserror) = os.readall(fd, buf, nz);
|
||||
os.close(fd);
|
||||
let got: i64 = 0i64;
|
||||
|
||||
@@ -53,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 = rt.alloc(nz + 1u64): *u8;
|
||||
let buf: *u8 = rt.malloc(nz + 1u64): *u8;
|
||||
let rr: (i64 | os.oserror) = os.readall(fd, buf, nz);
|
||||
os.close(fd);
|
||||
let got: i64 = 0i64;
|
||||
|
||||
@@ -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");
|
||||
@@ -23217,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 = rt.alloc(nz + 1u64): *u8;
|
||||
let buf: *u8 = rt.malloc(nz + 1u64): *u8;
|
||||
let rr: (i64 | os.oserror) = os.readall(fd, buf, nz);
|
||||
os.close(fd);
|
||||
let got: i64 = 0i64;
|
||||
|
||||
@@ -62,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 = rt.alloc(nz + 1u64): *u8;
|
||||
let buf: *u8 = rt.malloc(nz + 1u64): *u8;
|
||||
let rr: (i64 | os.oserror) = os.readall(fd, buf, nz);
|
||||
os.close(fd);
|
||||
let got: i64 = 0i64;
|
||||
|
||||
@@ -135,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 = rt.alloc(n: u64): *u8;
|
||||
let buf: *u8 = rt.malloc(n: u64): *u8;
|
||||
let rr: (i64 | os.oserror) = os.readall(fd, buf, n: u64);
|
||||
os.close(fd);
|
||||
let got: i64 = 0i64;
|
||||
|
||||
@@ -658,7 +658,7 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
|
||||
};
|
||||
|
||||
// ---- assemble file buffer ----
|
||||
let filebuf: *u8 = rt.alloc(fileend): *u8;
|
||||
let filebuf: *u8 = rt.malloc(fileend): *u8;
|
||||
if (filebuf == nil) {
|
||||
os.write(2, "w6l: out of memory\n".ptr, 18u64);
|
||||
return 1;
|
||||
|
||||
@@ -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;
|
||||
@@ -1061,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 = rt.alloc(n: u64): *u8;
|
||||
let buf: *u8 = rt.malloc(n: u64): *u8;
|
||||
let rr: (i64 | os.oserror) = os.readall(fd, buf, n: u64);
|
||||
os.close(fd);
|
||||
let got: i64 = 0i64;
|
||||
@@ -1083,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 = rt.alloc(nc): *u8;
|
||||
let nb: *u8 = rt.malloc(nc): *u8;
|
||||
let i: u64 = 0u64;
|
||||
for (i < l.textlen) {
|
||||
nb[i] = l.text[i];
|
||||
@@ -1105,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 = rt.alloc(nc): *u8;
|
||||
let nb: *u8 = rt.malloc(nc): *u8;
|
||||
let i: u64 = 0u64;
|
||||
for (i < l.datalen) {
|
||||
nb[i] = l.data[i];
|
||||
@@ -1721,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 = rt.alloc(n: u64): *u8;
|
||||
let buf: *u8 = rt.malloc(n: u64): *u8;
|
||||
let rr: (i64 | os.oserror) = os.readall(fd, buf, n: u64);
|
||||
os.close(fd);
|
||||
let got: i64 = 0i64;
|
||||
@@ -2782,7 +2782,7 @@ export fn emitdynelf(l: *lnk, fd: i32, base: u64, entry: u64) i32 = {
|
||||
};
|
||||
|
||||
// ---- assemble file buffer ----
|
||||
let filebuf: *u8 = rt.alloc(fileend): *u8;
|
||||
let filebuf: *u8 = rt.malloc(fileend): *u8;
|
||||
if (filebuf == nil) {
|
||||
os.write(2, "w6l: out of memory\n".ptr, 18u64);
|
||||
return 1;
|
||||
@@ -2973,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 = rt.alloc(TEXT_OFF): *u8; // zero-initialised by mmap
|
||||
let hdr: *u8 = rt.malloc(TEXT_OFF): *u8; // zero-initialised by mmap
|
||||
|
||||
// --- Ehdr (64 bytes) ---
|
||||
hdr[0u64] = 127u8; // 0x7f
|
||||
@@ -3128,7 +3128,7 @@ fn appenddec(dst: *u8, i: u64, n: u64) u64 = {
|
||||
dst[i] = 48u8;
|
||||
return i + 1u64;
|
||||
};
|
||||
let buf: *u8 = rt.alloc(16u64): *u8;
|
||||
let buf: *u8 = rt.malloc(16u64): *u8;
|
||||
let k: u64 = 0u64;
|
||||
let v: u64 = n;
|
||||
for (v > 0u64) {
|
||||
@@ -3167,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 = rt.alloc(8u64): *u8;
|
||||
let mp: *u8 = rt.malloc(8u64): *u8;
|
||||
let n: i64 = os.read(fd, mp, 8u64);
|
||||
os.close(fd);
|
||||
if (n < 4i64) { return false; };
|
||||
@@ -3195,7 +3195,7 @@ fn islinkable(path: *u8) bool = {
|
||||
// then lib<name>.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 = rt.alloc(1024u64): *u8;
|
||||
let bufp: *u8 = rt.malloc(1024u64): *u8;
|
||||
let i: i32 = 0;
|
||||
for (i < nlibdirs) {
|
||||
let dir: *u8 = libdirs[i];
|
||||
@@ -3236,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 = rt.alloc(20u64): *u8;
|
||||
let mp: *u8 = rt.malloc(20u64): *u8;
|
||||
let n: i64 = os.read(fd, mp, 20u64);
|
||||
os.close(fd);
|
||||
if (n < 20i64) { return 0; };
|
||||
@@ -3253,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 = rt.alloc((maxinputs: u64) * 8u64): **u8;
|
||||
let inputs: **u8 = rt.malloc((maxinputs: u64) * 8u64): **u8;
|
||||
let ninputs: i32 = 0;
|
||||
let libdirs: **u8 = rt.alloc((maxinputs: u64) * 8u64): **u8;
|
||||
let libdirs: **u8 = rt.malloc((maxinputs: u64) * 8u64): **u8;
|
||||
let nlibdirs: i32 = 0;
|
||||
let lflags: **u8 = rt.alloc((maxinputs: u64) * 8u64): **u8;
|
||||
let lflags: **u8 = rt.malloc((maxinputs: u64) * 8u64): **u8;
|
||||
let nlflags: i32 = 0;
|
||||
|
||||
let i: i32 = 1;
|
||||
|
||||
@@ -64,7 +64,7 @@ fn appenddec(dst: *u8, i: u64, n: u64) u64 = {
|
||||
dst[i] = 48u8;
|
||||
return i + 1u64;
|
||||
};
|
||||
let buf: *u8 = rt.alloc(16u64): *u8;
|
||||
let buf: *u8 = rt.malloc(16u64): *u8;
|
||||
let k: u64 = 0u64;
|
||||
let v: u64 = n;
|
||||
for (v > 0u64) {
|
||||
@@ -103,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 = rt.alloc(8u64): *u8;
|
||||
let mp: *u8 = rt.malloc(8u64): *u8;
|
||||
let n: i64 = os.read(fd, mp, 8u64);
|
||||
os.close(fd);
|
||||
if (n < 4i64) { return false; };
|
||||
@@ -131,7 +131,7 @@ fn islinkable(path: *u8) bool = {
|
||||
// then lib<name>.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 = rt.alloc(1024u64): *u8;
|
||||
let bufp: *u8 = rt.malloc(1024u64): *u8;
|
||||
let i: i32 = 0;
|
||||
for (i < nlibdirs) {
|
||||
let dir: *u8 = libdirs[i];
|
||||
@@ -172,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 = rt.alloc(20u64): *u8;
|
||||
let mp: *u8 = rt.malloc(20u64): *u8;
|
||||
let n: i64 = os.read(fd, mp, 20u64);
|
||||
os.close(fd);
|
||||
if (n < 20i64) { return 0; };
|
||||
@@ -189,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 = rt.alloc((maxinputs: u64) * 8u64): **u8;
|
||||
let inputs: **u8 = rt.malloc((maxinputs: u64) * 8u64): **u8;
|
||||
let ninputs: i32 = 0;
|
||||
let libdirs: **u8 = rt.alloc((maxinputs: u64) * 8u64): **u8;
|
||||
let libdirs: **u8 = rt.malloc((maxinputs: u64) * 8u64): **u8;
|
||||
let nlibdirs: i32 = 0;
|
||||
let lflags: **u8 = rt.alloc((maxinputs: u64) * 8u64): **u8;
|
||||
let lflags: **u8 = rt.malloc((maxinputs: u64) * 8u64): **u8;
|
||||
let nlflags: i32 = 0;
|
||||
|
||||
let i: i32 = 1;
|
||||
|
||||
@@ -87,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 = rt.alloc(n: u64): *u8;
|
||||
let buf: *u8 = rt.malloc(n: u64): *u8;
|
||||
let rr: (i64 | os.oserror) = os.readall(fd, buf, n: u64);
|
||||
os.close(fd);
|
||||
let got: i64 = 0i64;
|
||||
@@ -109,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 = rt.alloc(nc): *u8;
|
||||
let nb: *u8 = rt.malloc(nc): *u8;
|
||||
let i: u64 = 0u64;
|
||||
for (i < l.textlen) {
|
||||
nb[i] = l.text[i];
|
||||
@@ -131,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 = rt.alloc(nc): *u8;
|
||||
let nb: *u8 = rt.malloc(nc): *u8;
|
||||
let i: u64 = 0u64;
|
||||
for (i < l.datalen) {
|
||||
nb[i] = l.data[i];
|
||||
|
||||
@@ -89,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 = rt.alloc(TEXT_OFF): *u8; // zero-initialised by mmap
|
||||
let hdr: *u8 = rt.malloc(TEXT_OFF): *u8; // zero-initialised by mmap
|
||||
|
||||
// --- Ehdr (64 bytes) ---
|
||||
hdr[0u64] = 127u8; // 0x7f
|
||||
|
||||
@@ -2712,14 +2712,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
|
||||
@@ -2745,7 +2745,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");
|
||||
@@ -2972,7 +2972,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.
|
||||
//
|
||||
|
||||
@@ -576,7 +576,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
|
||||
@@ -645,7 +645,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");
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// 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.
|
||||
//
|
||||
@@ -31,8 +31,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;
|
||||
@@ -49,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 = 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;
|
||||
|
||||
@@ -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;
|
||||
@@ -1001,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 = rt.alloc(PATH_MAX): *u8;
|
||||
let buf: *u8 = rt.malloc(PATH_MAX): *u8;
|
||||
let off: u64 = cstrinto(buf, 0u64, dir);
|
||||
off = byteinto(buf, off, 47u8);
|
||||
off = cstrinto(buf, off, name);
|
||||
@@ -1011,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 = rt.alloc(PATH_MAX): *u8;
|
||||
let buf: *u8 = rt.malloc(PATH_MAX): *u8;
|
||||
let off: u64 = cstrinto(buf, 0u64, dir);
|
||||
off = byteinto(buf, off, 47u8);
|
||||
off = strinto(buf, off, name);
|
||||
@@ -1262,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 = rt.alloc(8192u64): *u8;
|
||||
let buf: *u8 = rt.malloc(8192u64): *u8;
|
||||
let r: i64 = os.getdents64(fd, buf, 8192u64);
|
||||
for (r > 0i64) {
|
||||
let off: u64 = 0u64;
|
||||
@@ -1324,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 = rt.alloc(nu + 1u64): *u8;
|
||||
let buf: *u8 = rt.malloc(nu + 1u64): *u8;
|
||||
let rr: (i64 | os.oserror) = os.readall(fd, buf, nu);
|
||||
os.close(fd);
|
||||
let got: i64 = 0i64;
|
||||
@@ -1431,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 = rt.alloc(2048u64): *u8;
|
||||
let buf: *u8 = rt.malloc(2048u64): *u8;
|
||||
let n: i64 = os.read(fd, buf, 2048u64);
|
||||
os.close(fd);
|
||||
if (n <= 0i64) { return nil; };
|
||||
@@ -1578,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 = rt.alloc(PATH_MAX): *u8;
|
||||
let buf: *u8 = rt.malloc(PATH_MAX): *u8;
|
||||
let off: u64 = cstrinto(buf, 0u64, stem);
|
||||
off = strinto(buf, off, suffix);
|
||||
cstrseal(buf, off);
|
||||
@@ -1616,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: <selfdir>/../../lib
|
||||
let dotdotlib: *u8 = rt.alloc(PATH_MAX): *u8;
|
||||
let dotdotlib: *u8 = rt.malloc(PATH_MAX): *u8;
|
||||
{
|
||||
let off: u64 = cstrinto(dotdotlib, 0u64, selfdir);
|
||||
off = strinto(dotdotlib, off, "/../../lib");
|
||||
@@ -1629,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 = rt.alloc(PATH_MAX): *u8;
|
||||
let srcd: *u8 = rt.malloc(PATH_MAX): *u8;
|
||||
if (entryisdir != 0) {
|
||||
let slen: u64 = cstrlen(src);
|
||||
let k: u64 = 0u64;
|
||||
@@ -1663,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 = rt.alloc(PATH_MAX * 3u64): *u8;
|
||||
let searchpath: *u8 = rt.malloc(PATH_MAX * 3u64): *u8;
|
||||
{
|
||||
let off: u64 = cstrinto(searchpath, 0u64, srcd);
|
||||
off = byteinto(searchpath, off, 58u8); // ':'
|
||||
@@ -1677,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: <dir>/<base>;
|
||||
// file entry: src stripped of .ww.
|
||||
let stem: *u8 = rt.alloc(PATH_MAX): *u8;
|
||||
let stem: *u8 = rt.malloc(PATH_MAX): *u8;
|
||||
if (entryisdir != 0) {
|
||||
let dlen: u64 = cstrlen(srcd);
|
||||
let bo: u64 = basenameoff(srcd, dlen);
|
||||
@@ -1694,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: <selfdir>/../lib/libwwrt.a
|
||||
let libwwrt: *u8 = rt.alloc(PATH_MAX): *u8;
|
||||
let libwwrt: *u8 = rt.malloc(PATH_MAX): *u8;
|
||||
{
|
||||
let off: u64 = cstrinto(libwwrt, 0u64, selfdir);
|
||||
off = strinto(libwwrt, off, "/../lib/libwwrt.a");
|
||||
@@ -1721,7 +1721,7 @@ fn buildone(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8, incs: *u8, lf: *l
|
||||
|
||||
// Step 2: w6c -o <stem>.s <stem>.combined.ww
|
||||
{
|
||||
let argv: **u8 = rt.alloc(40u64): **u8;
|
||||
let argv: **u8 = rt.malloc(40u64): **u8;
|
||||
argv[0] = "w6c\0".ptr;
|
||||
argv[1] = "-o\0".ptr;
|
||||
argv[2] = asmf;
|
||||
@@ -1735,7 +1735,7 @@ fn buildone(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8, incs: *u8, lf: *l
|
||||
|
||||
// Step 3: w6a -o <stem>.o <stem>.s
|
||||
{
|
||||
let argv: **u8 = rt.alloc(40u64): **u8;
|
||||
let argv: **u8 = rt.malloc(40u64): **u8;
|
||||
argv[0] = "w6a\0".ptr;
|
||||
argv[1] = "-o\0".ptr;
|
||||
argv[2] = objf;
|
||||
@@ -1764,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 = rt.alloc((total: u64) * 8u64): **u8;
|
||||
let argv: **u8 = rt.malloc((total: u64) * 8u64): **u8;
|
||||
argv[0] = "w6l\0".ptr;
|
||||
argv[1] = "-o\0".ptr;
|
||||
argv[2] = out;
|
||||
@@ -1936,7 +1936,7 @@ fn defaultoutpath(src: *u8) *u8 = {
|
||||
if (src[i] == 47u8) { start = i + 1u64; }; // '/'
|
||||
i += 1u64;
|
||||
};
|
||||
let out: *u8 = rt.alloc(PATH_MAX): *u8;
|
||||
let out: *u8 = rt.malloc(PATH_MAX): *u8;
|
||||
let off: u64 = 0u64;
|
||||
let j: u64 = start;
|
||||
for (j < n) {
|
||||
@@ -1961,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 = rt.alloc(PATH_MAX * 2u64): *u8;
|
||||
let incs: *u8 = rt.malloc(PATH_MAX * 2u64): *u8;
|
||||
let incoff: u64 = 0u64;
|
||||
cstrseal(incs, 0u64);
|
||||
|
||||
let maxlflags: i32 = 32;
|
||||
let libdirs: **u8 = rt.alloc((maxlflags: u64) * 8u64): **u8;
|
||||
let libdirs: **u8 = rt.malloc((maxlflags: u64) * 8u64): **u8;
|
||||
let nlibdirs: i32 = 0;
|
||||
let libs: **u8 = rt.alloc((maxlflags: u64) * 8u64): **u8;
|
||||
let libs: **u8 = rt.malloc((maxlflags: u64) * 8u64): **u8;
|
||||
let nlibs: i32 = 0;
|
||||
|
||||
let i: i32 = start;
|
||||
@@ -2058,7 +2058,7 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
rlen -= 1u64;
|
||||
};
|
||||
let bo: u64 = basenameoff(resolved, rlen);
|
||||
out = rt.alloc(PATH_MAX): *u8;
|
||||
out = rt.malloc(PATH_MAX): *u8;
|
||||
let i: u64 = bo;
|
||||
let off: u64 = 0u64;
|
||||
for (i < rlen) { out[off] = resolved[i]; off += 1u64; i += 1u64; };
|
||||
@@ -2108,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 = rt.alloc(PATH_MAX * 2u64): *u8;
|
||||
let incs: *u8 = rt.malloc(PATH_MAX * 2u64): *u8;
|
||||
let incoff: u64 = 0u64;
|
||||
cstrseal(incs, 0u64);
|
||||
|
||||
let maxlflags: i32 = 32;
|
||||
let libdirs: **u8 = rt.alloc((maxlflags: u64) * 8u64): **u8;
|
||||
let libdirs: **u8 = rt.malloc((maxlflags: u64) * 8u64): **u8;
|
||||
let nlibdirs: i32 = 0;
|
||||
let libs: **u8 = rt.alloc((maxlflags: u64) * 8u64): **u8;
|
||||
let libs: **u8 = rt.malloc((maxlflags: u64) * 8u64): **u8;
|
||||
let nlibs: i32 = 0;
|
||||
|
||||
let i: i32 = start;
|
||||
@@ -2205,7 +2205,7 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
return 1;
|
||||
};
|
||||
|
||||
let tmp: *u8 = rt.alloc(PATH_MAX): *u8;
|
||||
let tmp: *u8 = rt.malloc(PATH_MAX): *u8;
|
||||
makeruntmp(tmp);
|
||||
let lf: lflags;
|
||||
lf.libdirs = libdirs;
|
||||
@@ -2221,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 = rt.alloc((total: u64) * 8u64): **u8;
|
||||
let execargv: **u8 = rt.malloc((total: u64) * 8u64): **u8;
|
||||
execargv[0] = tmp;
|
||||
let k: i32 = 0;
|
||||
for (k < nextra) {
|
||||
@@ -2242,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 = rt.alloc(PATH_MAX): *u8;
|
||||
let tmp: *u8 = rt.malloc(PATH_MAX): *u8;
|
||||
makeruntmp(tmp);
|
||||
if (buildone(selfdir, src, 0, tmp, "\0".ptr, nil) != 0) {
|
||||
os.remove(pathstr(tmp));
|
||||
return 1;
|
||||
};
|
||||
let execargv: **u8 = rt.alloc(16u64): **u8;
|
||||
let execargv: **u8 = rt.malloc(16u64): **u8;
|
||||
execargv[0] = tmp;
|
||||
execargv[1] = nil;
|
||||
let rc: i32 = procrun(tmp, execargv);
|
||||
@@ -2265,7 +2265,7 @@ fn rundirtests(selfdir: *u8, dir: *u8) i32 = {
|
||||
|
||||
let pass: i32 = 0;
|
||||
let fail: i32 = 0;
|
||||
let buf: *u8 = rt.alloc(8192u64): *u8;
|
||||
let buf: *u8 = rt.malloc(8192u64): *u8;
|
||||
let dirlen: u64 = cstrlen(dir);
|
||||
|
||||
let n: i64 = os.getdents64(fd, buf, 8192u64);
|
||||
@@ -2281,7 +2281,7 @@ fn rundirtests(selfdir: *u8, dir: *u8) i32 = {
|
||||
if (cstrendswithlit(name, "_test.ww")) {
|
||||
let nlen: u64 = cstrlen(name);
|
||||
// path = <dir>/<name>
|
||||
let path: *u8 = rt.alloc(PATH_MAX): *u8;
|
||||
let path: *u8 = rt.malloc(PATH_MAX): *u8;
|
||||
let poff: u64 = cstrinto(path, 0u64, dir);
|
||||
path[poff] = 47u8; poff += 1u64;
|
||||
let i: u64 = 0u64;
|
||||
@@ -2289,10 +2289,10 @@ fn rundirtests(selfdir: *u8, dir: *u8) i32 = {
|
||||
poff += nlen;
|
||||
cstrseal(path, poff);
|
||||
// incs = <dir> so test files can `use ` siblings
|
||||
let tincs: *u8 = rt.alloc(PATH_MAX): *u8;
|
||||
let tincs: *u8 = rt.malloc(PATH_MAX): *u8;
|
||||
let ic: u64 = cstrinto(tincs, 0u64, dir);
|
||||
cstrseal(tincs, ic);
|
||||
let tmp: *u8 = rt.alloc(PATH_MAX): *u8;
|
||||
let tmp: *u8 = rt.malloc(PATH_MAX): *u8;
|
||||
makeruntmp(tmp);
|
||||
let bres: i32 = buildone(selfdir, path, 0, tmp, tincs, nil);
|
||||
if (bres != 0) {
|
||||
@@ -2301,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 = rt.alloc(16u64): **u8;
|
||||
let execargv: **u8 = rt.malloc(16u64): **u8;
|
||||
execargv[0] = tmp;
|
||||
execargv[1] = nil;
|
||||
let rc: i32 = procrun(tmp, execargv);
|
||||
@@ -2358,7 +2358,7 @@ export fn main(argc: i32, argv: **u8) i32 = {
|
||||
};
|
||||
|
||||
// selfdir = dirname(argv[0])
|
||||
let selfdir: *u8 = rt.alloc(PATH_MAX): *u8;
|
||||
let selfdir: *u8 = rt.malloc(PATH_MAX): *u8;
|
||||
selfdirinto(selfdir, PATH_MAX, argv[0]);
|
||||
|
||||
if (argc < 2) {
|
||||
|
||||
@@ -139,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 = rt.alloc(PATH_MAX): *u8;
|
||||
let buf: *u8 = rt.malloc(PATH_MAX): *u8;
|
||||
let off: u64 = cstrinto(buf, 0u64, dir);
|
||||
off = byteinto(buf, off, 47u8);
|
||||
off = cstrinto(buf, off, name);
|
||||
@@ -149,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 = rt.alloc(PATH_MAX): *u8;
|
||||
let buf: *u8 = rt.malloc(PATH_MAX): *u8;
|
||||
let off: u64 = cstrinto(buf, 0u64, dir);
|
||||
off = byteinto(buf, off, 47u8);
|
||||
off = strinto(buf, off, name);
|
||||
@@ -400,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 = rt.alloc(8192u64): *u8;
|
||||
let buf: *u8 = rt.malloc(8192u64): *u8;
|
||||
let r: i64 = os.getdents64(fd, buf, 8192u64);
|
||||
for (r > 0i64) {
|
||||
let off: u64 = 0u64;
|
||||
@@ -462,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 = rt.alloc(nu + 1u64): *u8;
|
||||
let buf: *u8 = rt.malloc(nu + 1u64): *u8;
|
||||
let rr: (i64 | os.oserror) = os.readall(fd, buf, nu);
|
||||
os.close(fd);
|
||||
let got: i64 = 0i64;
|
||||
@@ -569,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 = rt.alloc(2048u64): *u8;
|
||||
let buf: *u8 = rt.malloc(2048u64): *u8;
|
||||
let n: i64 = os.read(fd, buf, 2048u64);
|
||||
os.close(fd);
|
||||
if (n <= 0i64) { return nil; };
|
||||
@@ -716,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 = rt.alloc(PATH_MAX): *u8;
|
||||
let buf: *u8 = rt.malloc(PATH_MAX): *u8;
|
||||
let off: u64 = cstrinto(buf, 0u64, stem);
|
||||
off = strinto(buf, off, suffix);
|
||||
cstrseal(buf, off);
|
||||
@@ -754,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: <selfdir>/../../lib
|
||||
let dotdotlib: *u8 = rt.alloc(PATH_MAX): *u8;
|
||||
let dotdotlib: *u8 = rt.malloc(PATH_MAX): *u8;
|
||||
{
|
||||
let off: u64 = cstrinto(dotdotlib, 0u64, selfdir);
|
||||
off = strinto(dotdotlib, off, "/../../lib");
|
||||
@@ -767,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 = rt.alloc(PATH_MAX): *u8;
|
||||
let srcd: *u8 = rt.malloc(PATH_MAX): *u8;
|
||||
if (entryisdir != 0) {
|
||||
let slen: u64 = cstrlen(src);
|
||||
let k: u64 = 0u64;
|
||||
@@ -801,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 = rt.alloc(PATH_MAX * 3u64): *u8;
|
||||
let searchpath: *u8 = rt.malloc(PATH_MAX * 3u64): *u8;
|
||||
{
|
||||
let off: u64 = cstrinto(searchpath, 0u64, srcd);
|
||||
off = byteinto(searchpath, off, 58u8); // ':'
|
||||
@@ -815,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: <dir>/<base>;
|
||||
// file entry: src stripped of .ww.
|
||||
let stem: *u8 = rt.alloc(PATH_MAX): *u8;
|
||||
let stem: *u8 = rt.malloc(PATH_MAX): *u8;
|
||||
if (entryisdir != 0) {
|
||||
let dlen: u64 = cstrlen(srcd);
|
||||
let bo: u64 = basenameoff(srcd, dlen);
|
||||
@@ -832,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: <selfdir>/../lib/libwwrt.a
|
||||
let libwwrt: *u8 = rt.alloc(PATH_MAX): *u8;
|
||||
let libwwrt: *u8 = rt.malloc(PATH_MAX): *u8;
|
||||
{
|
||||
let off: u64 = cstrinto(libwwrt, 0u64, selfdir);
|
||||
off = strinto(libwwrt, off, "/../lib/libwwrt.a");
|
||||
@@ -859,7 +859,7 @@ fn buildone(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8, incs: *u8, lf: *l
|
||||
|
||||
// Step 2: w6c -o <stem>.s <stem>.combined.ww
|
||||
{
|
||||
let argv: **u8 = rt.alloc(40u64): **u8;
|
||||
let argv: **u8 = rt.malloc(40u64): **u8;
|
||||
argv[0] = "w6c\0".ptr;
|
||||
argv[1] = "-o\0".ptr;
|
||||
argv[2] = asmf;
|
||||
@@ -873,7 +873,7 @@ fn buildone(selfdir: *u8, src: *u8, entryisdir: i32, out: *u8, incs: *u8, lf: *l
|
||||
|
||||
// Step 3: w6a -o <stem>.o <stem>.s
|
||||
{
|
||||
let argv: **u8 = rt.alloc(40u64): **u8;
|
||||
let argv: **u8 = rt.malloc(40u64): **u8;
|
||||
argv[0] = "w6a\0".ptr;
|
||||
argv[1] = "-o\0".ptr;
|
||||
argv[2] = objf;
|
||||
@@ -902,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 = rt.alloc((total: u64) * 8u64): **u8;
|
||||
let argv: **u8 = rt.malloc((total: u64) * 8u64): **u8;
|
||||
argv[0] = "w6l\0".ptr;
|
||||
argv[1] = "-o\0".ptr;
|
||||
argv[2] = out;
|
||||
@@ -1074,7 +1074,7 @@ fn defaultoutpath(src: *u8) *u8 = {
|
||||
if (src[i] == 47u8) { start = i + 1u64; }; // '/'
|
||||
i += 1u64;
|
||||
};
|
||||
let out: *u8 = rt.alloc(PATH_MAX): *u8;
|
||||
let out: *u8 = rt.malloc(PATH_MAX): *u8;
|
||||
let off: u64 = 0u64;
|
||||
let j: u64 = start;
|
||||
for (j < n) {
|
||||
@@ -1099,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 = rt.alloc(PATH_MAX * 2u64): *u8;
|
||||
let incs: *u8 = rt.malloc(PATH_MAX * 2u64): *u8;
|
||||
let incoff: u64 = 0u64;
|
||||
cstrseal(incs, 0u64);
|
||||
|
||||
let maxlflags: i32 = 32;
|
||||
let libdirs: **u8 = rt.alloc((maxlflags: u64) * 8u64): **u8;
|
||||
let libdirs: **u8 = rt.malloc((maxlflags: u64) * 8u64): **u8;
|
||||
let nlibdirs: i32 = 0;
|
||||
let libs: **u8 = rt.alloc((maxlflags: u64) * 8u64): **u8;
|
||||
let libs: **u8 = rt.malloc((maxlflags: u64) * 8u64): **u8;
|
||||
let nlibs: i32 = 0;
|
||||
|
||||
let i: i32 = start;
|
||||
@@ -1196,7 +1196,7 @@ fn dobuild(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
rlen -= 1u64;
|
||||
};
|
||||
let bo: u64 = basenameoff(resolved, rlen);
|
||||
out = rt.alloc(PATH_MAX): *u8;
|
||||
out = rt.malloc(PATH_MAX): *u8;
|
||||
let i: u64 = bo;
|
||||
let off: u64 = 0u64;
|
||||
for (i < rlen) { out[off] = resolved[i]; off += 1u64; i += 1u64; };
|
||||
@@ -1246,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 = rt.alloc(PATH_MAX * 2u64): *u8;
|
||||
let incs: *u8 = rt.malloc(PATH_MAX * 2u64): *u8;
|
||||
let incoff: u64 = 0u64;
|
||||
cstrseal(incs, 0u64);
|
||||
|
||||
let maxlflags: i32 = 32;
|
||||
let libdirs: **u8 = rt.alloc((maxlflags: u64) * 8u64): **u8;
|
||||
let libdirs: **u8 = rt.malloc((maxlflags: u64) * 8u64): **u8;
|
||||
let nlibdirs: i32 = 0;
|
||||
let libs: **u8 = rt.alloc((maxlflags: u64) * 8u64): **u8;
|
||||
let libs: **u8 = rt.malloc((maxlflags: u64) * 8u64): **u8;
|
||||
let nlibs: i32 = 0;
|
||||
|
||||
let i: i32 = start;
|
||||
@@ -1343,7 +1343,7 @@ fn dorun(selfdir: *u8, argv: **u8, argc: i32, start: i32) i32 = {
|
||||
return 1;
|
||||
};
|
||||
|
||||
let tmp: *u8 = rt.alloc(PATH_MAX): *u8;
|
||||
let tmp: *u8 = rt.malloc(PATH_MAX): *u8;
|
||||
makeruntmp(tmp);
|
||||
let lf: lflags;
|
||||
lf.libdirs = libdirs;
|
||||
@@ -1359,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 = rt.alloc((total: u64) * 8u64): **u8;
|
||||
let execargv: **u8 = rt.malloc((total: u64) * 8u64): **u8;
|
||||
execargv[0] = tmp;
|
||||
let k: i32 = 0;
|
||||
for (k < nextra) {
|
||||
@@ -1380,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 = rt.alloc(PATH_MAX): *u8;
|
||||
let tmp: *u8 = rt.malloc(PATH_MAX): *u8;
|
||||
makeruntmp(tmp);
|
||||
if (buildone(selfdir, src, 0, tmp, "\0".ptr, nil) != 0) {
|
||||
os.remove(pathstr(tmp));
|
||||
return 1;
|
||||
};
|
||||
let execargv: **u8 = rt.alloc(16u64): **u8;
|
||||
let execargv: **u8 = rt.malloc(16u64): **u8;
|
||||
execargv[0] = tmp;
|
||||
execargv[1] = nil;
|
||||
let rc: i32 = procrun(tmp, execargv);
|
||||
@@ -1403,7 +1403,7 @@ fn rundirtests(selfdir: *u8, dir: *u8) i32 = {
|
||||
|
||||
let pass: i32 = 0;
|
||||
let fail: i32 = 0;
|
||||
let buf: *u8 = rt.alloc(8192u64): *u8;
|
||||
let buf: *u8 = rt.malloc(8192u64): *u8;
|
||||
let dirlen: u64 = cstrlen(dir);
|
||||
|
||||
let n: i64 = os.getdents64(fd, buf, 8192u64);
|
||||
@@ -1419,7 +1419,7 @@ fn rundirtests(selfdir: *u8, dir: *u8) i32 = {
|
||||
if (cstrendswithlit(name, "_test.ww")) {
|
||||
let nlen: u64 = cstrlen(name);
|
||||
// path = <dir>/<name>
|
||||
let path: *u8 = rt.alloc(PATH_MAX): *u8;
|
||||
let path: *u8 = rt.malloc(PATH_MAX): *u8;
|
||||
let poff: u64 = cstrinto(path, 0u64, dir);
|
||||
path[poff] = 47u8; poff += 1u64;
|
||||
let i: u64 = 0u64;
|
||||
@@ -1427,10 +1427,10 @@ fn rundirtests(selfdir: *u8, dir: *u8) i32 = {
|
||||
poff += nlen;
|
||||
cstrseal(path, poff);
|
||||
// incs = <dir> so test files can `use ` siblings
|
||||
let tincs: *u8 = rt.alloc(PATH_MAX): *u8;
|
||||
let tincs: *u8 = rt.malloc(PATH_MAX): *u8;
|
||||
let ic: u64 = cstrinto(tincs, 0u64, dir);
|
||||
cstrseal(tincs, ic);
|
||||
let tmp: *u8 = rt.alloc(PATH_MAX): *u8;
|
||||
let tmp: *u8 = rt.malloc(PATH_MAX): *u8;
|
||||
makeruntmp(tmp);
|
||||
let bres: i32 = buildone(selfdir, path, 0, tmp, tincs, nil);
|
||||
if (bres != 0) {
|
||||
@@ -1439,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 = rt.alloc(16u64): **u8;
|
||||
let execargv: **u8 = rt.malloc(16u64): **u8;
|
||||
execargv[0] = tmp;
|
||||
execargv[1] = nil;
|
||||
let rc: i32 = procrun(tmp, execargv);
|
||||
@@ -1496,7 +1496,7 @@ export fn main(argc: i32, argv: **u8) i32 = {
|
||||
};
|
||||
|
||||
// selfdir = dirname(argv[0])
|
||||
let selfdir: *u8 = rt.alloc(PATH_MAX): *u8;
|
||||
let selfdir: *u8 = rt.malloc(PATH_MAX): *u8;
|
||||
selfdirinto(selfdir, PATH_MAX, argv[0]);
|
||||
|
||||
if (argc < 2) {
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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.
|
||||
@@ -1709,7 +1709,7 @@ export fn position(d: *decoder) i32 = {
|
||||
|
||||
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,
|
||||
@@ -1717,15 +1717,15 @@ 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;
|
||||
|
||||
// strings — operations over str ({ptr,len}). Hare port; see
|
||||
// ref/hare/strings/.
|
||||
@@ -1828,7 +1828,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;
|
||||
@@ -1850,7 +1850,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
|
||||
@@ -1906,7 +1906,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) {
|
||||
@@ -1939,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 = rt.alloc(total: u64): *u8;
|
||||
let buf: *u8 = rt.malloc(total: u64): *u8;
|
||||
let off: i32 = 0;
|
||||
i = 0;
|
||||
for (i < strs.len) {
|
||||
@@ -2616,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 = 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;
|
||||
@@ -2699,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 = 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];
|
||||
|
||||
@@ -44,12 +44,12 @@ fn allocbox() (*point | nomem) = {
|
||||
};
|
||||
|
||||
// Task #32: slice-form `let s: []T = alloc([], n)!;` shortcut. Both
|
||||
// stages must lower to `n*esz` bytes via rt_alloc, abort on null, and
|
||||
// stages must lower to `n*esz` bytes via rt_malloc, abort on null, and
|
||||
// build a {ptr, 0, n} header in the let slot. Pre-#32 wwstage fell
|
||||
// through to cgalloc, allocating 8B and dropping the slice header
|
||||
// entirely — silent miscompile. Cap-only would pass on a junk header
|
||||
// pointing to dead memory; write-then-read on s[0]/s[cap-1] proves
|
||||
// the ptr field is a real rt_alloc'd region (would SIGSEGV otherwise).
|
||||
// the ptr field is a real rt_malloc'd region (would SIGSEGV otherwise).
|
||||
// IMULQ esz path is currently unreachable from user code — check.c
|
||||
// pins the alloc shape to []u8 (cstage check.c:1052-1082) — so this
|
||||
// row only exercises esz=1; the cgen elemsizeofc resolution stays
|
||||
|
||||
@@ -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). */
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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" },
|
||||
|
||||
Reference in New Issue
Block a user