lib/std/lang/alloc.rad 5.3 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
export fn alloc(arena: &mut Arena, size: u32, alignment: u32) -> *mut opaque throws (AllocError) {
37
    assert alignment > 0;
38
    assert size > 0;
39
40
    let aligned64 = (arena.offset as u64 + alignment as u64 - 1) & ~(alignment as u64 - 1);
41
    if aligned64 > arena.data.len as u64 or size as u64 > arena.data.len as u64 - aligned64 {
42
        throw AllocError::OutOfMemory;
43
    }
44
    let aligned = aligned64 as u32;
45
    let newOffset = aligned + size;
46
47
    let base: *mut u8 = &mut arena.data[aligned];
48
    set arena.offset = newOffset;
49
50
    return base as *mut opaque;
51
}
52
53
/// Reset the arena, allowing all memory to be reused.
54
///
55
/// Does not zero the memory.
56
export fn reset(arena: &mut Arena) {
57
    set arena.offset = 0;
58
}
59
60
/// Save the current arena state for later restoration.
61
export fn save(arena: &Arena) -> u32 {
62
    return arena.offset;
63
}
64
65
/// Restore the arena to a previously saved state, reclaiming all
66
/// allocations made since that point.
67
export fn restore(arena: &mut Arena, savedOffset: u32) {
68
    set arena.offset = savedOffset;
69
}
70
71
/// Returns the number of bytes currently allocated.
72
export fn used(arena: &Arena) -> u32 {
73
    return arena.offset;
74
}
75
76
/// Returns the number of bytes remaining in the arena.
77
export fn remaining(arena: &Arena) -> u32 {
78
    return arena.data.len as u32 - arena.offset;
79
}
80
81
/// Returns the remaining buffer as a mutable slice.
82
export fn remainingBuf(arena: &mut Arena) -> *mut [u8] {
83
    return &mut arena.data[arena.offset..];
84
}
85
86
/// Commits `size` bytes of allocation, advancing the offset.
87
/// Use after writing to the buffer returned by [`remainingBuf`].
88
export fn commit(arena: &mut Arena, size: u32) {
89
    set arena.offset += size;
90
}
91
92
/// Allocate a slice of `count` elements, each of `size` bytes with given alignment.
93
///
94
/// Returns a type-erased slice that should be cast to the appropriate `*[T]`.
95
/// The slice length is set to `count` (element count, not bytes).
96
/// Throws `AllocError` if the arena is exhausted.
97
export unsafe fn allocSlice(arena: &mut Arena, size: u32, alignment: u32, count: u32) -> *mut [opaque] throws (AllocError) {
98
    if count == 0 {
99
        return &mut [];
100
    }
101
    if size > 0xffffffff / count {
102
        throw AllocError::OutOfMemory;
103
    }
104
    let ptr = try alloc(arena, size * count, alignment);
105
106
    return @sliceOf(ptr, count);
107
}
108
109
/// Generic allocator interface.
110
///
111
/// Bundles an allocation function with an opaque context pointer so that
112
/// any allocation strategy (arena, free-list, mmap, pool) can be used
113
/// through a uniform interface. The `func` field is called with the context
114
/// pointer, a byte size and an alignment, and must return a pointer to
115
/// the allocated memory or panic on failure.
116
export record Allocator: Copy {
117
    /// Allocation function. Returns a pointer to `size` bytes
118
    /// aligned to `alignment`, or panics on failure.
119
    func: unsafe fn(*unsafe mut opaque, u32, u32) -> *mut opaque,
120
    /// Opaque context pointer passed to `func`.
121
    ctx: *unsafe mut opaque,
122
}
123
124
/// Create an allocator whose arena must outlive all uses of the allocator.
125
export unsafe fn arenaAllocator(arena: &mut Arena) -> Allocator {
126
    return Allocator {
127
        func: arenaAllocFn,
128
        ctx: (arena as *unsafe mut Arena) as *unsafe mut opaque,
129
    };
130
}
131
132
/// Arena allocation function conforming to the `Allocator` interface.
133
unsafe fn arenaAllocFn(ctx: *unsafe mut opaque, size: u32, alignment: u32) -> *mut opaque {
134
    let arena = ctx as *unsafe mut Arena;
135
    return try! alloc(arena, size, alignment);
136
}
137
138
/// Allocate raw storage that remains valid until the arena is reset.
139
export unsafe fn allocRaw(arena: &mut Arena, size: u32, alignment: u32) -> *unsafe mut opaque throws (AllocError) {
140
    let owner = try alloc(arena, size, alignment);
141
    let byte = owner as *mut u8;
142
    let raw: *unsafe mut u8 = &mut *byte;
143
    return raw as *unsafe mut opaque;
144
}
145
146
/// Allocate a raw slice that remains valid until the arena is reset.
147
export unsafe fn allocRawSlice(arena: &mut Arena, size: u32, alignment: u32, count: u32) -> *unsafe mut [opaque] throws (AllocError) {
148
    if count == 0 {
149
        return &mut [];
150
    }
151
    let raw = try allocRaw(arena, size * count, alignment);
152
    return @sliceOf(raw, count);
153
}