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
use std::mem;
10
11
/// Error thrown by allocator.
12
export union AllocError: Copy {
13
    /// Allocator is out of memory.
14
    OutOfMemory,
15
}
16
17
/// Bump allocator backed by a byte slice.
18
///
19
/// Allocations are made by advancing an offset pointer. Individual allocations
20
/// cannot be freed; instead, the entire arena is reset at once via [`reset`].
21
export record Arena {
22
    /// Backing storage.
23
    data: *mut [u8],
24
    /// Current allocation offset in bytes.
25
    offset: u32,
26
}
27
28
/// Create a new arena backed by the given byte slice.
29
export fn new(data: *mut [u8]) -> Arena {
30
    return Arena { data, offset: 0 };
31
}
32
33
/// Allocate `size` bytes with the given alignment.
34
///
35
/// Returns an opaque pointer to the allocated memory. Throws `AllocError` if
36
/// the arena is exhausted. The caller is responsible for casting to the
37
/// appropriate type and initializing the memory.
38
export fn alloc(arena: &mut Arena, size: u32, alignment: u32) -> *mut opaque throws (AllocError) {
39
    assert alignment > 0;
40
    assert size > 0;
41
42
    let aligned64 = (arena.offset as u64 + alignment as u64 - 1) & ~(alignment as u64 - 1);
43
    if aligned64 > arena.data.len as u64 or size as u64 > arena.data.len as u64 - aligned64 {
44
        throw AllocError::OutOfMemory;
45
    }
46
    let aligned = aligned64 as u32;
47
    let newOffset = aligned + size;
48
49
    let base: *mut u8 = &mut arena.data[aligned];
50
    set arena.offset = newOffset;
51
52
    return base as *mut opaque;
53
}
54
55
/// Reset the arena, allowing all memory to be reused.
56
///
57
/// Does not zero the memory.
58
export fn reset(arena: &mut Arena) {
59
    set arena.offset = 0;
60
}
61
62
/// Save the current arena state for later restoration.
63
export fn save(arena: &Arena) -> u32 {
64
    return arena.offset;
65
}
66
67
/// Restore the arena to a previously saved state, reclaiming all
68
/// allocations made since that point.
69
export fn restore(arena: &mut Arena, savedOffset: u32) {
70
    set arena.offset = savedOffset;
71
}
72
73
/// Returns the number of bytes currently allocated.
74
export fn used(arena: &Arena) -> u32 {
75
    return arena.offset;
76
}
77
78
/// Returns the number of bytes remaining in the arena.
79
export fn remaining(arena: &Arena) -> u32 {
80
    return arena.data.len as u32 - arena.offset;
81
}
82
83
/// Returns the remaining buffer as a mutable slice.
84
export fn remainingBuf(arena: &mut Arena) -> *mut [u8] {
85
    return &mut arena.data[arena.offset..];
86
}
87
88
/// Commits `size` bytes of allocation, advancing the offset.
89
/// Use after writing to the buffer returned by [`remainingBuf`].
90
export fn commit(arena: &mut Arena, size: u32) {
91
    set arena.offset += size;
92
}
93
94
/// Allocate a slice of `count` elements, each of `size` bytes with given alignment.
95
///
96
/// Returns a type-erased slice that should be cast to the appropriate `*[T]`.
97
/// The slice length is set to `count` (element count, not bytes).
98
/// Throws `AllocError` if the arena is exhausted.
99
export unsafe fn allocSlice(arena: &mut Arena, size: u32, alignment: u32, count: u32) -> *mut [opaque] throws (AllocError) {
100
    if count == 0 {
101
        return &mut [];
102
    }
103
    if size > 0xffffffff / count { throw AllocError::OutOfMemory; }
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
}