compiler/
lib/
examples/
std/
arch/
char/
collections/
graph/
lang/
alloc/
ast/
gen/
il/
module/
parser/
resolver/
scanner/
alloc.rad
7.1 KiB
ast.rad
26.9 KiB
gen.rad
513 B
il.rad
20.4 KiB
lower.rad
321.7 KiB
module.rad
17.3 KiB
package.rad
1.3 KiB
parser.rad
92.2 KiB
resolver.rad
511.1 KiB
scanner.rad
17.9 KiB
sexpr.rad
6.7 KiB
strings.rad
2.2 KiB
types.rad
1.6 KiB
sys/
arch.rad
68 B
char.rad
855 B
collections.rad
39 B
fmt.rad
8.3 KiB
graph.rad
4.3 KiB
intrinsics.rad
467 B
io.rad
1.7 KiB
lang.rad
276 B
mem.rad
2.3 KiB
sys.rad
179 B
testing.rad
2.4 KiB
tests.rad
15.7 KiB
vec.rad
3.2 KiB
std.rad
299 B
scripts/
seed/
sublime/
test/
vim/
.gitignore
336 B
.gitsigners
112 B
CELL_PERMISSIONS
6.8 KiB
CONTRIBUTING
2.1 KiB
LICENSE
1.1 KiB
Makefile
5.4 KiB
README
2.5 KiB
STYLE
2.5 KiB
std.lib
1.5 KiB
std.lib.test
808 B
lib/std/lang/alloc.rad
raw
| 1 | //! Bump allocator for compiler data structures. |
| 2 | //! |
| 3 | //! Provides a simple arena-style allocator that allocates from a contiguous |
| 4 | //! byte buffer. Memory is never freed individually - the entire arena is |
| 5 | //! reset at once. This is ideal for compiler passes where all allocations |
| 6 | //! have the same lifetime. |
| 7 | @test mod tests; |
| 8 | |
| 9 | /// Error thrown by allocator. |
| 10 | export union AllocError: Copy { |
| 11 | /// Allocator is out of memory. |
| 12 | OutOfMemory, |
| 13 | } |
| 14 | |
| 15 | /// Bump allocator backed by a byte slice. |
| 16 | /// |
| 17 | /// Allocations are made by advancing an offset pointer. Individual allocations |
| 18 | /// cannot be freed; instead, the entire arena is reset at once via [`reset`]. |
| 19 | export record Arena { |
| 20 | /// Backing storage. |
| 21 | data: *mut [u8], |
| 22 | /// Current allocation offset in bytes. |
| 23 | offset: u32, |
| 24 | } |
| 25 | |
| 26 | /// Create a new arena backed by the given byte slice. |
| 27 | export fn new(data: *mut [u8]) -> Arena { |
| 28 | return Arena { data, offset: 0 }; |
| 29 | } |
| 30 | |
| 31 | /// Allocate `size` bytes with the given alignment. |
| 32 | /// |
| 33 | /// Returns an opaque pointer to the allocated memory. Throws `AllocError` if |
| 34 | /// the arena is exhausted. The caller is responsible for casting to the |
| 35 | /// appropriate type and initializing the memory. |
| 36 | /// Size must be positive. Alignment must be a positive power of two. |
| 37 | /// Failure leaves the allocation offset unchanged. |
| 38 | /// The caller must keep the storage live and must not reclaim live allocations. |
| 39 | export unsafe fn alloc(arena: *unsafe mut Arena, size: u32, alignment: u32) -> *mut opaque throws (AllocError) { |
| 40 | assert alignment > 0; |
| 41 | assert size > 0; |
| 42 | |
| 43 | assert (alignment & (alignment - 1)) == 0; |
| 44 | |
| 45 | let capacity = arena.data.len; |
| 46 | if arena.offset >= capacity { |
| 47 | throw AllocError::OutOfMemory; |
| 48 | } |
| 49 | let address = (&arena.data[0]) as u64; |
| 50 | let mask = (alignment - 1) as u64; |
| 51 | let remainder = ((address & mask) + (arena.offset as u64 & mask)) & mask; |
| 52 | let padding = ((alignment as u64 - remainder) & mask) as u32; |
| 53 | let available = capacity - arena.offset; |
| 54 | if padding > available or size > available - padding { |
| 55 | throw AllocError::OutOfMemory; |
| 56 | } |
| 57 | let aligned = arena.offset + padding; |
| 58 | let newOffset = aligned + size; |
| 59 | |
| 60 | let base: *mut u8 = &mut arena.data[aligned]; |
| 61 | set arena.offset = newOffset; |
| 62 | |
| 63 | return base as *mut opaque; |
| 64 | } |
| 65 | |
| 66 | /// Reset the arena, allowing all memory to be reused. |
| 67 | /// |
| 68 | /// Does not zero the memory. |
| 69 | export fn reset(arena: &mut Arena) { |
| 70 | set arena.offset = 0; |
| 71 | } |
| 72 | |
| 73 | /// Save the current arena state for later restoration. |
| 74 | export fn save(arena: &Arena) -> u32 { |
| 75 | return arena.offset; |
| 76 | } |
| 77 | |
| 78 | /// Restore the arena to a previously saved state, reclaiming all |
| 79 | /// allocations made since that point. |
| 80 | export fn restore(arena: &mut Arena, savedOffset: u32) { |
| 81 | set arena.offset = savedOffset; |
| 82 | } |
| 83 | |
| 84 | /// Returns the number of bytes currently allocated. |
| 85 | export fn used(arena: &Arena) -> u32 { |
| 86 | return arena.offset; |
| 87 | } |
| 88 | |
| 89 | /// Returns the number of bytes remaining in the arena. |
| 90 | export fn remaining(arena: &Arena) -> u32 { |
| 91 | return arena.data.len as u32 - arena.offset; |
| 92 | } |
| 93 | |
| 94 | /// Returns the remaining buffer as a mutable slice. |
| 95 | /// The caller must keep the storage live and must commit each written prefix |
| 96 | /// before another allocation can use that prefix. |
| 97 | export unsafe fn remainingBuf(arena: *unsafe mut Arena) -> *mut [u8] { |
| 98 | return &mut arena.data[arena.offset..]; |
| 99 | } |
| 100 | |
| 101 | /// Commits `size` bytes of allocation, advancing the offset. |
| 102 | /// Use after writing to the buffer returned by [`remainingBuf`]. |
| 103 | export fn commit(arena: &mut Arena, size: u32) { |
| 104 | set arena.offset += size; |
| 105 | } |
| 106 | |
| 107 | /// Allocate a slice of `count` elements, each of `size` bytes with given alignment. |
| 108 | /// |
| 109 | /// Returns a type-erased slice that should be cast to the appropriate `*[T]`. |
| 110 | /// The slice length is set to `count` (element count, not bytes). |
| 111 | /// Throws `AllocError` if the arena is exhausted. |
| 112 | export unsafe fn allocSlice(arena: &mut Arena, size: u32, alignment: u32, count: u32) -> *mut [opaque] throws (AllocError) { |
| 113 | if count == 0 { |
| 114 | return &mut []; |
| 115 | } |
| 116 | let bytes = try allocationSize(size, count); |
| 117 | let ptr = try alloc(&mut *arena, bytes, alignment); |
| 118 | |
| 119 | return @sliceOf(ptr, count); |
| 120 | } |
| 121 | |
| 122 | /// Generic allocator interface. |
| 123 | /// |
| 124 | /// Bundles an allocation function with an opaque context pointer so that |
| 125 | /// any allocation strategy (arena, free-list, mmap, pool) can be used |
| 126 | /// through a uniform interface. The `func` field is called with the context |
| 127 | /// pointer, a byte size and an alignment, and must return a pointer to |
| 128 | /// the allocated memory or panic on failure. |
| 129 | export record Allocator: Copy { |
| 130 | /// Allocation function. Returns a pointer to `size` bytes |
| 131 | /// aligned to `alignment`, or panics on failure. |
| 132 | func: unsafe fn(*unsafe mut opaque, u32, u32) -> *mut opaque, |
| 133 | /// Opaque context pointer passed to `func`. |
| 134 | ctx: *unsafe mut opaque, |
| 135 | } |
| 136 | |
| 137 | /// Create an allocator whose arena must outlive all uses of the allocator. |
| 138 | export unsafe fn arenaAllocator(arena: &mut Arena) -> Allocator { |
| 139 | return Allocator { |
| 140 | func: arenaAllocFn, |
| 141 | ctx: (arena as *unsafe mut Arena) as *unsafe mut opaque, |
| 142 | }; |
| 143 | } |
| 144 | |
| 145 | /// Arena allocation function conforming to the `Allocator` interface. |
| 146 | unsafe fn arenaAllocFn(ctx: *unsafe mut opaque, size: u32, alignment: u32) -> *mut opaque { |
| 147 | let arena = ctx as *unsafe mut Arena; |
| 148 | return try! alloc(&mut *arena, size, alignment); |
| 149 | } |
| 150 | |
| 151 | /// Allocate raw storage that remains valid until the arena is reset. |
| 152 | export unsafe fn allocRaw(arena: &mut Arena, size: u32, alignment: u32) -> *unsafe mut opaque throws (AllocError) { |
| 153 | let owner = try alloc(&mut *arena, size, alignment); |
| 154 | let byte = owner as *mut u8; |
| 155 | let raw: *unsafe mut u8 = &mut *byte; |
| 156 | return raw as *unsafe mut opaque; |
| 157 | } |
| 158 | |
| 159 | /// Allocate a raw slice that remains valid until the arena is reset. |
| 160 | export unsafe fn allocRawSlice(arena: &mut Arena, size: u32, alignment: u32, count: u32) -> *unsafe mut [opaque] throws (AllocError) { |
| 161 | if count == 0 { |
| 162 | return &mut []; |
| 163 | } |
| 164 | let bytes = try allocationSize(size, count); |
| 165 | let raw = try allocRaw(arena, bytes, alignment); |
| 166 | return @sliceOf(raw, count); |
| 167 | } |
| 168 | |
| 169 | /// Raw storage provider for allocation sessions. |
| 170 | export trait Alloc { |
| 171 | /// Reserve one uninitialized object. |
| 172 | unsafe fn (&mut Alloc) reserve(size: u32, alignment: u32) -> *unsafe mut opaque throws (AllocError); |
| 173 | /// Reserve an uninitialized slice. |
| 174 | unsafe fn (&mut Alloc) reserveSlice(itemSize: u32, itemAlignment: u32, count: u32) -> *unsafe mut [opaque] throws (AllocError); |
| 175 | } |
| 176 | |
| 177 | instance Alloc for Arena { |
| 178 | /// Reserve one uninitialized object in the arena. |
| 179 | unsafe fn (arena: &mut Arena) reserve(size: u32, alignment: u32) -> *unsafe mut opaque throws (AllocError) { |
| 180 | return try allocRaw(arena, size, alignment); |
| 181 | } |
| 182 | |
| 183 | /// Reserve an uninitialized slice in the arena. |
| 184 | unsafe fn (arena: &mut Arena) reserveSlice(size: u32, alignment: u32, count: u32) -> *unsafe mut [opaque] throws (AllocError) { |
| 185 | return try allocRawSlice(arena, size, alignment, count); |
| 186 | } |
| 187 | } |
| 188 | |
| 189 | /// Compute a slice allocation size without unsigned multiplication overflow. |
| 190 | fn allocationSize(size: u32, count: u32) -> u32 throws (AllocError) { |
| 191 | if size <> 0 and count > 4294967295 / size { |
| 192 | throw AllocError::OutOfMemory; |
| 193 | } |
| 194 | return size * count; |
| 195 | } |