forked from emscripten-core/emscripten
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsplit_malloc.cpp
More file actions
250 lines (223 loc) · 6.77 KB
/
split_malloc.cpp
File metadata and controls
250 lines (223 loc) · 6.77 KB
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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
/*
malloc/free for SPLIT_MEMORY
*/
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <emscripten.h>
extern "C" {
typedef void* mspace;
mspace create_mspace_with_base(void* base, size_t capacity, int locked);
size_t destroy_mspace(mspace msp);
void* mspace_malloc(mspace space, size_t size);
void mspace_free(mspace space, void* ptr);
void* mspace_realloc(mspace msp, void* oldmem, size_t bytes);
void* mspace_memalign(mspace msp, size_t alignment, size_t bytes);
}
#define MAX_SPACES 1000
static bool initialized = false;
static size_t total_memory = 0;
static size_t split_memory = 0;
static size_t num_spaces = 0;
enum AllocateResult {
OK = 0,
NO_MEMORY = 1,
ALREADY_USED = 2
};
struct Space {
mspace space;
bool allocated; // whether storage is allocated for this chunk, both an ArrayBuffer in JS and an mspace here
size_t count; // how many allocations are in the space
size_t index; // the index of this space, it then represents memory at SPLIT_MEMORY*index
void init(int i) {
space = 0;
allocated = false;
count = 0;
index = i;
}
AllocateResult allocate() {
assert(!allocated);
assert(count == 0);
allocated = true;
int start;
if (index > 0) {
if (int(split_memory*(index+1)) < 0) {
// 32-bit pointer overflow. we could support more than this 2G, up to 4GB, if we made all pointer shifts >>>. likely slower though
return NO_MEMORY;
}
AllocateResult result = (AllocateResult)EM_ASM_INT({
// can fail due to the browser not have enough memory for the chunk, or if the slice
// is already used, which can happen if other code allocated it
try {
return allocateSplitChunk($0) ? $1 : $3;
} catch(e) {
return $2; // failed to allocate
}
}, index, OK, NO_MEMORY, ALREADY_USED);
if (result != OK) return result;
start = split_memory*index;
} else {
// small area in existing chunk 0
start = EM_ASM_INT_V({ return (DYNAMICTOP+3)&-4; });
assert(start < split_memory);
}
int size = (split_memory*(index+1)) - start;
if (index > 0) assert(size == split_memory);
space = create_mspace_with_base((void*)start, size, 0);
if (index == 0) {
if (!space) {
EM_ASM({ Module.printErr("failed to create space in the first split memory chunk - SPLIT_MEMORY might need to be larger"); });
}
}
assert(space);
return OK;
}
void free() {
assert(allocated);
assert(count == 0);
allocated = false;
destroy_mspace((void*)(split_memory*index));
if (index > 0) {
EM_ASM_({ freeSplitChunk($0) }, index);
}
}
};
static Space spaces[MAX_SPACES];
static void init() {
total_memory = EM_ASM_INT_V({ return TOTAL_MEMORY; });
split_memory = EM_ASM_INT_V({ return SPLIT_MEMORY; });
num_spaces = EM_ASM_INT_V({ return HEAPU8s.length; });
if (num_spaces >= MAX_SPACES) abort();
for (int i = 0; i < num_spaces; i++) {
spaces[i].init(i);
}
initialized = true;
}
// TODO: optimize, these are powers of 2
// TODO: add optional asserts in these
#define space_index(ptr) (((unsigned)ptr) / split_memory)
#define space_relative(ptr) (((unsigned)ptr) % split_memory)
static mspace get_space(void* ptr) { // for a valid pointer, so the space must already exist
int index = space_index(ptr);
Space& space = spaces[index];
assert(space.allocated);
assert(space.count > 0);
return space.space;
}
static void* get_memory(size_t size, bool malloc=true, size_t alignment=-1, bool must_succeed=false) {
if (!initialized) {
init();
}
if (size >= split_memory) {
static bool warned = false;
if (!warned) {
EM_ASM_({
Module.print("trying to get " + $0 + ", a size >= than SPLIT_MEMORY (" + $1 + "), increase SPLIT_MEMORY if you want that to work");
}, size, split_memory);
warned = true;
}
return 0;
}
static int next = 0;
int start = next;
while (1) { // simple round-robin, while keeping to use the same one as long as it keeps succeeding
AllocateResult result = OK;
if (!spaces[next].allocated) {
result = spaces[next].allocate();
if (result == NO_MEMORY) return 0; // mallocation failure
}
if (result == OK) {
void *ret;
if (malloc) {
ret = mspace_malloc(spaces[next].space, size);
} else {
ret = mspace_memalign(spaces[next].space, alignment, size);
}
if (ret) {
spaces[next].count++;
return ret;
}
if (must_succeed) {
EM_ASM({ Module.printErr("failed to allocate in a new space after memory growth, perhaps increase SPLIT_MEMORY?"); });
abort();
}
} else {
assert(result == ALREADY_USED); // continue on to the next space
}
next++;
if (next == num_spaces) next = 0;
if (next == start) break;
}
// we cycled, so none of them can allocate
int returnNull = EM_ASM_INT_V({
if (!ABORTING_MALLOC && !ALLOW_MEMORY_GROWTH) return 1; // malloc can return 0, and we cannot grow
if (!ALLOW_MEMORY_GROWTH) {
abortOnCannotGrowMemory();
}
return 0;
});
if (returnNull) return 0;
// memory growth is on, add another chunk
if (num_spaces + 1 >= MAX_SPACES) abort();
spaces[num_spaces].init(num_spaces);
next = num_spaces;
num_spaces++;
return get_memory(size, malloc, alignment, true);
}
extern "C" {
void* malloc(size_t size) {
return get_memory(size);
}
void* memalign(size_t alignment, size_t size) {
return get_memory(size, false, alignment);
}
void free(void* ptr) {
if (ptr == 0) return;
int index = space_index(ptr);
Space& space = spaces[index];
assert(space.count > 0);
mspace_free(get_space(ptr), ptr);
space.count--;
if (space.count == 0) {
spaces[index].free();
}
}
void* realloc(void* ptr, size_t newsize) {
if (!ptr) return malloc(newsize);
void* ret = mspace_realloc(get_space(ptr), ptr, newsize);
if (ret) return ret;
ret = malloc(newsize);
if (!ret) return 0;
size_t copysize = newsize;
if (((unsigned)ptr) + copysize > total_memory) copysize = total_memory - ((unsigned)ptr);
memcpy(ret, ptr, copysize);
free(ptr);
return ret;
}
void* calloc(size_t num, size_t size) {
size_t bytes = num * size;
void* ret = malloc(bytes);
if (!ret) return 0;
memset(ret, 0, bytes);
return ret;
}
// very minimal sbrk, within one chunk
void* sbrk(intptr_t increment) {
const int SBRK_CHUNK = split_memory - 1024;
static size_t start = -1;
static size_t curr = 0;
if (start == -1) {
start = (size_t)malloc(SBRK_CHUNK);
if (!start) {
EM_ASM({ Module.printErr("sbrk() failed to get space"); });
abort();
}
curr = start;
}
if (curr - start + increment >= SBRK_CHUNK || curr + increment < start) return (void*)-1;
size_t ret = curr;
curr += increment;
return (void*)ret;
}
}