lib/std/lang/alloc.rad 7.1 KiB 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
}