lib/std/lang/alloc.rad 5.4 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
/// Largest byte extent representable by allocator APIs.
10
constant MAX_EXTENT: u32 = 0xFFFFFFFF;
11
12
/// Error thrown by allocator.
13
export union AllocError {
14
    /// Allocator is out of memory.
15
    OutOfMemory,
16
}
17
18
/// Bump allocator backed by a byte slice.
19
///
20
/// Allocations are made by advancing an offset pointer. Individual allocations
21
/// cannot be freed; instead, the entire arena is reset at once via [`reset`].
22
export record Arena {
23
    /// Backing storage.
24
    data: *mut [u8],
25
    /// Current allocation offset in bytes.
26
    offset: u32,
27
}
28
29
/// Create a new arena backed by the given byte slice.
30
export fn new(data: *mut [u8]) -> Arena {
31
    return Arena { data, offset: 0 };
32
}
33
34
/// Allocate `size` bytes with the given alignment.
35
///
36
/// `size` must be nonzero and `alignment` must be a nonzero power of two.
37
/// Returns an opaque pointer to the allocated memory. Throws `AllocError` if
38
/// the arena is exhausted or its offset lies beyond the backing storage. The
39
/// caller is responsible for casting to the appropriate type and initializing
40
/// the memory.
41
export fn alloc(arena: *mut Arena, size: u32, alignment: u32) -> *mut opaque throws (AllocError) {
42
    assert alignment > 0;
43
    assert (alignment & (alignment - 1)) == 0;
44
    assert size > 0;
45
46
    let capacity = arena.data.len as u32;
47
    if arena.offset > capacity {
48
        throw AllocError::OutOfMemory;
49
    }
50
    let available = capacity - arena.offset;
51
    let padding = (0 - arena.offset) & (alignment - 1);
52
    if padding > available {
53
        throw AllocError::OutOfMemory;
54
    }
55
    let alignedAvailable = available - padding;
56
    if size > alignedAvailable {
57
        throw AllocError::OutOfMemory;
58
    }
59
    let aligned = arena.offset + padding;
60
    let newOffset = aligned + size;
61
    let base: *mut u8 = &mut arena.data[aligned];
62
    set arena.offset = newOffset;
63
64
    return base as *mut opaque;
65
}
66
67
/// Reset the arena, allowing all memory to be reused.
68
///
69
/// The caller must not use any allocation from this arena after the reset.
70
/// Does not zero the memory.
71
export unsafe fn reset(arena: *mut Arena) {
72
    set arena.offset = 0;
73
}
74
75
/// Save the current arena state for later restoration.
76
export fn save(arena: *Arena) -> u32 {
77
    return arena.offset;
78
}
79
80
/// Restore the arena to a previously saved state, reclaiming all
81
/// allocations made since that point.
82
///
83
/// The caller must not use an allocation reclaimed by this operation.
84
export unsafe fn restore(arena: *mut Arena, savedOffset: u32) {
85
    set arena.offset = savedOffset;
86
}
87
88
/// Returns the number of bytes currently allocated.
89
export fn used(arena: *Arena) -> u32 {
90
    return arena.offset;
91
}
92
93
/// Returns the number of bytes remaining in the arena.
94
export fn remaining(arena: *Arena) -> u32 {
95
    return arena.data.len as u32 - arena.offset;
96
}
97
98
/// Return an exclusive view of the remaining buffer.
99
///
100
/// The caller must end access through the view before another arena operation.
101
export unsafe fn remainingBuf(arena: *mut Arena) -> *mut [u8] {
102
    return &mut arena.data[arena.offset..];
103
}
104
105
/// Commit `size` bytes of allocation and advance the offset.
106
///
107
/// The caller must provide a size within the view returned by [`remainingBuf`]
108
/// and must end access through that view before another arena operation.
109
export unsafe fn commit(arena: *mut Arena, size: u32) {
110
    assert size <= remaining(arena), "alloc::commit: size exceeds remaining capacity";
111
    set arena.offset += size;
112
}
113
114
/// Allocate a slice of `count` elements, each of `size` bytes with given alignment.
115
///
116
/// Returns a type-erased slice that should be cast to the appropriate `*[T]`.
117
/// The slice length is set to `count` (element count, not bytes).
118
/// Throws `AllocError` if the arena is exhausted or the total byte extent
119
/// cannot be represented by `u32`.
120
export fn allocSlice(arena: *mut Arena, size: u32, alignment: u32, count: u32) -> *mut [opaque] throws (AllocError) {
121
    if count == 0 {
122
        return &mut [];
123
    }
124
    if size > MAX_EXTENT / count {
125
        throw AllocError::OutOfMemory;
126
    }
127
    let ptr = try alloc(arena, size * count, alignment);
128
129
    return @sliceOf(ptr, count);
130
}
131
132
/// Generic allocator interface.
133
///
134
/// Bundles an allocation function with an opaque context pointer so that
135
/// any allocation strategy (arena, free-list, mmap, pool) can be used
136
/// through a uniform interface. The `func` field is called with the context
137
/// pointer, a byte size and an alignment, and must return a pointer to
138
/// the allocated memory or panic on failure.
139
export record Allocator {
140
    /// Allocation function. Returns a pointer to `size` bytes
141
    /// aligned to `alignment`, or panics on failure.
142
    func: fn(*mut opaque, u32, u32) -> *mut opaque,
143
    /// Opaque context pointer passed to `func`.
144
    ctx: *mut opaque,
145
}
146
147
/// Create an `Allocator` backed by an `Arena`.
148
export fn arenaAllocator(arena: *mut Arena) -> Allocator {
149
    return Allocator {
150
        func: arenaAllocFn,
151
        ctx: arena as *mut opaque,
152
    };
153
}
154
155
/// Arena allocation function conforming to the `Allocator` interface.
156
fn arenaAllocFn(ctx: *mut opaque, size: u32, alignment: u32) -> *mut opaque {
157
    let arena = ctx as *mut Arena;
158
    return try! alloc(arena, size, alignment);
159
}