-
Notifications
You must be signed in to change notification settings - Fork 0
/
page_allocator.odin
144 lines (121 loc) · 4.58 KB
/
page_allocator.odin
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
package page_allocator
import "base:runtime"
import "base:intrinsics"
import "core:mem"
import "core:mem/virtual"
// The idea behind the Page_Allocator is to have a low-level lookup-less allocator
// over the operating system functions. We can use the provided size to infer the
// actual page aligned size. We can also *pretend* to have a different page size by
// setting the granularity. This provides greater control over alignment while still
// being able to infer the correct size of the allocation.
//
// NOTE: Because the size is being inferred, allocations that come from new, must
// provide size with something like mem.free_with_size to release the memory.
Page_Allocator_Flag :: enum {
Never_Free,
Allow_Large_Pages,
Uninitialized_Memory,
No_Commit, // TODO
}
Page_Allocator_Flags :: bit_set[Page_Allocator_Flag; u8]
Page_Allocator :: struct {
flags: Page_Allocator_Flags,
granularity_log2: u8,
}
GRANULARITY_MIN :: _GRANULARITY_MIN
GRANULARITY_MAX :: _GRANULARITY_MAX
@(require_results)
page_aligned_alloc :: proc(size: int,
alignment: int = GRANULARITY_MIN,
granularity: int = GRANULARITY_MIN,
flags: Page_Allocator_Flags = {}) -> (memory: []byte, err: mem.Allocator_Error) {
return _page_aligned_alloc(size, alignment, granularity, flags)
}
@(require_results)
page_aligned_resize :: proc(old_ptr: rawptr,
old_size, new_size: int,
new_alignment: int = GRANULARITY_MIN,
granularity: int = GRANULARITY_MIN,
flags: Page_Allocator_Flags = {}) -> (memory: []byte, err: mem.Allocator_Error) {
return _page_aligned_resize(old_ptr, old_size, new_size, new_alignment, granularity, flags)
}
page_free :: proc(p: rawptr, size: int, granularity := GRANULARITY_MIN, flags: Page_Allocator_Flags = {}) -> mem.Allocator_Error {
assert(granularity >= GRANULARITY_MIN)
assert(runtime.is_power_of_two(granularity))
if p == nil || !mem.is_aligned(p, granularity) {
return .Invalid_Pointer
}
if size <= 0 {
// NOTE: This would actually work fine on Windows, but we'd need to track
// allocations on every other system.
unimplemented("Page allocator does not track size. Try mem.free_with_size().")
}
size_full := mem.align_forward_int(size, granularity)
virtual.release(p, uint(size_full))
return nil
}
@(require_results)
page_allocator_make :: proc(granularity := GRANULARITY_MIN, flags: Page_Allocator_Flags = {}) -> Page_Allocator {
assert(granularity >= GRANULARITY_MIN)
assert(granularity <= GRANULARITY_MAX)
assert(runtime.is_power_of_two(granularity))
return Page_Allocator {
flags = flags,
granularity_log2 = u8(intrinsics.count_trailing_zeros(granularity)),
}
}
// The Page_Allocator does not track individual allocations, so the struct itself
// only contains configuration. If the data is nil, we will just use the defaults.
@(require_results)
page_allocator :: proc(allocator: ^Page_Allocator = nil)-> mem.Allocator {
return mem.Allocator {
procedure = page_allocator_proc,
data = allocator,
}
}
page_allocator_proc :: proc(allocator_data: rawptr, mode: mem.Allocator_Mode,
size, alignment: int,
old_memory: rawptr, old_size: int, loc := #caller_location) -> ([]byte, mem.Allocator_Error) {
flags: Page_Allocator_Flags
granularity := GRANULARITY_MIN
if allocator := (^Page_Allocator)(allocator_data); allocator != nil {
flags = allocator.flags
if allocator.granularity_log2 != 0 {
granularity = 1 << allocator.granularity_log2
}
}
switch mode {
case .Alloc_Non_Zeroed:
flags += {.Uninitialized_Memory}
return page_aligned_alloc(size, alignment, granularity, flags)
case .Alloc:
flags -= {.Uninitialized_Memory}
return page_aligned_alloc(size, alignment, granularity, flags)
case .Free:
return nil, page_free(old_memory, old_size, granularity, flags)
case .Free_All:
return nil, .Mode_Not_Implemented
case .Resize_Non_Zeroed:
flags += {.Uninitialized_Memory}
break
case .Resize:
flags -= {.Uninitialized_Memory}
break
case .Query_Features:
set := (^mem.Allocator_Mode_Set)(old_memory)
if set != nil {
set^ = {.Alloc, .Alloc_Non_Zeroed, .Free, .Resize, .Resize_Non_Zeroed, .Query_Features}
}
return nil, nil
case .Query_Info:
return nil, .Mode_Not_Implemented
}
// resizing
if old_memory == nil {
return page_aligned_alloc(size, alignment, granularity, flags)
}
if size == 0 {
return nil, page_free(old_memory, old_size, granularity, flags)
}
return page_aligned_resize(old_memory, old_size, size, alignment, granularity, flags)
}