Line data Source code
1 : // wok_arena -- bump allocator for one parse job.
2 : //
3 : // Nodes are immutable, built once, and die together, which is an arena's exact
4 : // shape. One job owns one arena; teardown frees the block list and there is
5 : // nothing else to run. This is a SEPARATE, simpler allocator from
6 : // runtime/wok_rc.c's arena -- no refcounts, no free lists, no cascade --
7 : // because coupling a front end to the shipping runtime's ABI buys nothing.
8 :
9 : #pragma once
10 :
11 : // The prelude comes FIRST: it carries the POSIX feature-test macros, which
12 : // have no effect once a system header has been read. wok_base.h hard-errors
13 : // if it is reached too late.
14 : #include "wok_base.h"
15 :
16 : #include <assert.h>
17 : #include <stddef.h>
18 : #include <stdint.h>
19 :
20 : // The layouts live HERE, not behind an opaque pointer, for one reason: the
21 : // allocation fast path below must inline. Every WOK_NEW site knows size and
22 : // align at compile time, and an out-of-line call throws that knowledge away
23 : // -- inlined, the align-up folds to a constant mask, the power-of-two assert
24 : // folds to nothing, and the whole hot path is a compare and two adds
25 : // (profiled at ~8% of a parse as a call). Nothing else may touch these
26 : // fields; the API below is still the whole contract.
27 : typedef struct WokArenaBlock {
28 : struct WokArenaBlock *next;
29 : usize capacity;
30 : usize used;
31 : alignas(max_align_t) unsigned char payload[];
32 : } WokArenaBlock;
33 :
34 : struct WokArena {
35 : WokArenaBlock *head;
36 : usize block_count;
37 : usize bytes_out; // bytes handed to callers, excluding alignment pad
38 : usize growth_capacity; // capacity of the last *normal* growth block
39 : };
40 : typedef struct WokArena WokArena;
41 :
42 : // first_block is a hint in bytes; 0 selects the default (64 KiB).
43 : WokArena *wok_arena_new(usize first_block);
44 : void wok_arena_free(WokArena *);
45 :
46 : // The GROWTH path: the current block cannot hold the request, so a new block
47 : // is chained on. Out of line and cold -- it runs once per block, not once
48 : // per allocation. Never returns NULL: allocation failure aborts, because a
49 : // front end that cannot get memory has nothing useful to report.
50 : [[gnu::cold, gnu::malloc, gnu::alloc_size(2), gnu::alloc_align(3),
51 : gnu::returns_nonnull]]
52 : void *wok_arena_grow(WokArena *, usize size, usize align);
53 :
54 : // The allocation fast path. The attributes are the STATIC INFORMATION the
55 : // call sites act on: the pointer is fresh (no aliasing), `size` bytes big,
56 : // and `align` aligned -- which is what lets a following memset compile to
57 : // aligned stores and a caller drop its null paths.
58 : [[gnu::malloc, gnu::alloc_size(2), gnu::alloc_align(3), gnu::returns_nonnull]]
59 2104487 : static inline void *wok_arena_alloc(WokArena *a, usize size, usize align) {
60 2104487 : assert(align != 0 && (align & (align - 1)) == 0);
61 :
62 : // Zero-size requests still consume (and bump past) one byte so that two
63 : // successive zero-size allocations do not alias the same address.
64 2104487 : usize footprint = size == 0 ? (usize)1 : size;
65 :
66 2104487 : WokArenaBlock *blk = a->head;
67 2104487 : uptr cur = (uptr)blk->payload + (uptr)blk->used;
68 2104487 : uptr aligned = (cur + (uptr)align - 1) & ~((uptr)align - 1);
69 2104487 : usize pad = (usize)(aligned - cur);
70 2104487 : usize remaining = blk->capacity - blk->used;
71 :
72 2104487 : if (pad <= remaining && footprint <= remaining - pad) {
73 2102942 : blk->used += pad + footprint;
74 2102942 : a->bytes_out += size;
75 2102942 : return (void *)aligned;
76 : }
77 1545 : return wok_arena_grow(a, size, align);
78 : }
79 :
80 : // Copies n bytes and NUL-terminates. Used for diagnostic text only; token text
81 : // is always a view into the source buffer.
82 : char *wok_arena_copy(WokArena *, const char *src, usize n);
83 :
84 : // Live bytes handed out, and blocks reserved. Used by the leak invariant:
85 : // arena bytes must return to baseline after every job.
86 : usize wok_arena_bytes(const WokArena *);
87 : usize wok_arena_blocks(const WokArena *);
88 :
89 : #define WOK_NEW(a, T) ((T *)wok_arena_alloc((a), sizeof(T), alignof(T)))
90 : #define WOK_NEW_N(a, T, n) \
91 : ((T *)wok_arena_alloc((a), sizeof(T) * (usize)(n), alignof(T)))
|