3d9a304fda4df37b67079bc280721a7c9fd2bb87
[ghc.git] / rts / win32 / OSMem.c
1 /* -----------------------------------------------------------------------------
2 *
3 * (c) The University of Glasgow 2006-2007
4 *
5 * OS-specific memory management
6 *
7 * ---------------------------------------------------------------------------*/
8
9 #include "Rts.h"
10 #include "sm/OSMem.h"
11 #include "sm/HeapAlloc.h"
12 #include "RtsUtils.h"
13
14 #if HAVE_WINDOWS_H
15 #include <windows.h>
16 #endif
17
18 typedef struct alloc_rec_ {
19 char* base; // non-aligned base address, directly from VirtualAlloc
20 W_ size; // Size in bytes
21 struct alloc_rec_* next;
22 } alloc_rec;
23
24 typedef struct block_rec_ {
25 char* base; // base address, non-MBLOCK-aligned
26 W_ size; // size in bytes
27 struct block_rec_* next;
28 } block_rec;
29
30 /* allocs are kept in ascending order, and are the memory regions as
31 returned by the OS as we need to have matching VirtualAlloc and
32 VirtualFree calls.
33
34 If USE_LARGE_ADDRESS_SPACE is defined, this list will contain only
35 one element.
36 */
37 static alloc_rec* allocs = NULL;
38
39 /* free_blocks are kept in ascending order, and adjacent blocks are merged */
40 static block_rec* free_blocks = NULL;
41
42 void
43 osMemInit(void)
44 {
45 allocs = NULL;
46 free_blocks = NULL;
47 }
48
49 static
50 alloc_rec*
51 allocNew(uint32_t n) {
52 alloc_rec* rec;
53 rec = (alloc_rec*)stgMallocBytes(sizeof(alloc_rec),"getMBlocks: allocNew");
54 rec->size = ((W_)n+1)*MBLOCK_SIZE;
55 rec->base =
56 VirtualAlloc(NULL, rec->size, MEM_RESERVE, PAGE_READWRITE);
57 if(rec->base==0) {
58 stgFree((void*)rec);
59 rec=0;
60 if (GetLastError() == ERROR_NOT_ENOUGH_MEMORY) {
61
62 errorBelch("Out of memory");
63 stg_exit(EXIT_HEAPOVERFLOW);
64 } else {
65 sysErrorBelch(
66 "getMBlocks: VirtualAlloc MEM_RESERVE %d blocks failed", n);
67 }
68 } else {
69 alloc_rec temp;
70 temp.base=0; temp.size=0; temp.next=allocs;
71
72 alloc_rec* it;
73 it=&temp;
74 for(; it->next!=0 && it->next->base<rec->base; it=it->next) ;
75 rec->next=it->next;
76 it->next=rec;
77
78 allocs=temp.next;
79 }
80 return rec;
81 }
82
83 static
84 void
85 insertFree(char* alloc_base, W_ alloc_size) {
86 block_rec temp;
87 block_rec* it;
88 block_rec* prev;
89
90 temp.base=0; temp.size=0; temp.next=free_blocks;
91 it = free_blocks;
92 prev = &temp;
93 for( ; it!=0 && it->base<alloc_base; prev=it, it=it->next) {}
94
95 if(it!=0 && alloc_base+alloc_size == it->base) {
96 if(prev->base + prev->size == alloc_base) { /* Merge it, alloc, prev */
97 prev->size += alloc_size + it->size;
98 prev->next = it->next;
99 stgFree(it);
100 } else { /* Merge it, alloc */
101 it->base = alloc_base;
102 it->size += alloc_size;
103 }
104 } else if(prev->base + prev->size == alloc_base) { /* Merge alloc, prev */
105 prev->size += alloc_size;
106 } else { /* Merge none */
107 block_rec* rec;
108 rec = (block_rec*)stgMallocBytes(sizeof(block_rec),
109 "getMBlocks: insertFree");
110 rec->base=alloc_base;
111 rec->size=alloc_size;
112 rec->next = it;
113 prev->next=rec;
114 }
115 free_blocks=temp.next;
116 }
117
118 static
119 void*
120 findFreeBlocks(uint32_t n) {
121 void* ret=0;
122 block_rec* it;
123 block_rec temp;
124 block_rec* prev;
125
126 W_ required_size;
127 it=free_blocks;
128 required_size = n*MBLOCK_SIZE;
129 temp.next=free_blocks; temp.base=0; temp.size=0;
130 prev=&temp;
131 /* TODO: Don't just take first block, find smallest sufficient block */
132 for( ; it!=0 && it->size<required_size; prev=it, it=it->next ) {}
133 if(it!=0) {
134 if( (((W_)it->base) & MBLOCK_MASK) == 0) { /* MBlock aligned */
135 ret = (void*)it->base;
136 if(it->size==required_size) {
137 prev->next=it->next;
138 stgFree(it);
139 } else {
140 it->base += required_size;
141 it->size -=required_size;
142 }
143 } else {
144 char* need_base;
145 block_rec* next;
146 int new_size;
147 need_base =
148 (char*)(((W_)it->base) & ((W_)~MBLOCK_MASK)) + MBLOCK_SIZE;
149 next = (block_rec*)stgMallocBytes(
150 sizeof(block_rec)
151 , "getMBlocks: findFreeBlocks: splitting");
152 new_size = need_base - it->base;
153 next->base = need_base +required_size;
154 next->size = it->size - (new_size+required_size);
155 it->size = new_size;
156 next->next = it->next;
157 it->next = next;
158 ret=(void*)need_base;
159 }
160 }
161 free_blocks=temp.next;
162 return ret;
163 }
164
165 /* VirtualAlloc MEM_COMMIT can't cross boundaries of VirtualAlloc MEM_RESERVE,
166 so we might need to do many VirtualAlloc MEM_COMMITs. We simply walk the
167 (ordered) allocated blocks. */
168 static void
169 commitBlocks(char* base, W_ size) {
170 alloc_rec* it;
171 it=allocs;
172 for( ; it!=0 && (it->base+it->size)<=base; it=it->next ) {}
173 for( ; it!=0 && size>0; it=it->next ) {
174 W_ size_delta;
175 void* temp;
176 size_delta = it->size - (base-it->base);
177 if(size_delta>size) size_delta=size;
178 temp = VirtualAlloc(base, size_delta, MEM_COMMIT, PAGE_READWRITE);
179 if(temp==0) {
180 sysErrorBelch("getMBlocks: VirtualAlloc MEM_COMMIT failed");
181 stg_exit(EXIT_HEAPOVERFLOW);
182 }
183 size-=size_delta;
184 base+=size_delta;
185 }
186 }
187
188 void *
189 osGetMBlocks(uint32_t n) {
190 void* ret;
191 ret = findFreeBlocks(n);
192 if(ret==0) {
193 alloc_rec* alloc;
194 alloc = allocNew(n);
195 /* We already belch in allocNew if it fails */
196 if (alloc == 0) {
197 stg_exit(EXIT_FAILURE);
198 } else {
199 insertFree(alloc->base, alloc->size);
200 ret = findFreeBlocks(n);
201 }
202 }
203
204 if(ret!=0) {
205 /* (In)sanity tests */
206 if (((W_)ret & MBLOCK_MASK) != 0) {
207 barf("getMBlocks: misaligned block returned");
208 }
209
210 commitBlocks(ret, (W_)MBLOCK_SIZE*n);
211 }
212
213 return ret;
214 }
215
216 static void decommitBlocks(char *addr, W_ nBytes)
217 {
218 alloc_rec *p;
219
220 p = allocs;
221 while ((p != NULL) && (addr >= (p->base + p->size))) {
222 p = p->next;
223 }
224 while (nBytes > 0) {
225 if ((p == NULL) || (p->base > addr)) {
226 errorBelch("Memory to be freed isn't allocated\n");
227 stg_exit(EXIT_FAILURE);
228 }
229 if (p->base + p->size >= addr + nBytes) {
230 if (!VirtualFree(addr, nBytes, MEM_DECOMMIT)) {
231 sysErrorBelch("osFreeMBlocks: VirtualFree MEM_DECOMMIT failed");
232 stg_exit(EXIT_FAILURE);
233 }
234 nBytes = 0;
235 }
236 else {
237 W_ bytesToFree = p->base + p->size - addr;
238 if (!VirtualFree(addr, bytesToFree, MEM_DECOMMIT)) {
239 sysErrorBelch("osFreeMBlocks: VirtualFree MEM_DECOMMIT failed");
240 stg_exit(EXIT_FAILURE);
241 }
242 addr += bytesToFree;
243 nBytes -= bytesToFree;
244 p = p->next;
245 }
246 }
247 }
248
249 void osFreeMBlocks(void *addr, uint32_t n)
250 {
251 W_ nBytes = (W_)n * MBLOCK_SIZE;
252
253 insertFree(addr, nBytes);
254 decommitBlocks(addr, nBytes);
255 }
256
257 void osReleaseFreeMemory(void)
258 {
259 alloc_rec *prev_a, *a;
260 alloc_rec head_a;
261 block_rec *prev_fb, *fb;
262 block_rec head_fb;
263 char *a_end, *fb_end;
264
265 /* go through allocs and free_blocks in lockstep, looking for allocs
266 that are completely free, and uncommit them */
267
268 head_a.base = 0;
269 head_a.size = 0;
270 head_a.next = allocs;
271 head_fb.base = 0;
272 head_fb.size = 0;
273 head_fb.next = free_blocks;
274 prev_a = &head_a;
275 a = allocs;
276 prev_fb = &head_fb;
277 fb = free_blocks;
278
279 while (a != NULL) {
280 a_end = a->base + a->size;
281 /* If a is freeable then there is a single freeblock in fb that
282 covers it. The end of this free block must be >= the end of
283 a, so skip anything in fb that ends before a. */
284 while (fb != NULL && fb->base + fb->size < a_end) {
285 prev_fb = fb;
286 fb = fb->next;
287 }
288
289 if (fb == NULL) {
290 /* If we have nothing left in fb, then neither a nor
291 anything later in the list is freeable, so we are done. */
292 break;
293 }
294 else {
295 fb_end = fb->base + fb->size;
296 /* We have a candidate fb. But does it really cover a? */
297 if (fb->base <= a->base) {
298 /* Yes, the alloc is within the free block. Now we need
299 to know if it sticks out at either end. */
300 if (fb_end == a_end) {
301 if (fb->base == a->base) {
302 /* fb and a are identical, so just free fb */
303 prev_fb->next = fb->next;
304 stgFree(fb);
305 fb = prev_fb->next;
306 }
307 else {
308 /* fb begins earlier, so truncate it to not include a */
309 fb->size = a->base - fb->base;
310 }
311 }
312 else {
313 /* fb ends later, so we'll make fb just be the part
314 after a. First though, if it also starts earlier,
315 we make a new free block record for the before bit. */
316 if (fb->base != a->base) {
317 block_rec *new_fb;
318
319 new_fb =
320 (block_rec *)stgMallocBytes(sizeof(block_rec),
321 "osReleaseFreeMemory");
322 new_fb->base = fb->base;
323 new_fb->size = a->base - fb->base;
324 new_fb->next = fb;
325 prev_fb->next = new_fb;
326 }
327 fb->size = fb_end - a_end;
328 fb->base = a_end;
329 }
330 /* Now we can free the alloc */
331 prev_a->next = a->next;
332 if(!VirtualFree((void *)a->base, 0, MEM_RELEASE)) {
333 sysErrorBelch("freeAllMBlocks: VirtualFree MEM_RELEASE "
334 "failed");
335 stg_exit(EXIT_FAILURE);
336 }
337 stgFree(a);
338 a = prev_a->next;
339 }
340 else {
341 /* Otherwise this alloc is not freeable, so go on to the
342 next one */
343 prev_a = a;
344 a = a->next;
345 }
346 }
347 }
348
349 allocs = head_a.next;
350 free_blocks = head_fb.next;
351 }
352
353 void
354 osFreeAllMBlocks(void)
355 {
356 {
357 block_rec* next;
358 block_rec* it;
359 next=0;
360 it = free_blocks;
361 for(; it!=0; ) {
362 next = it->next;
363 stgFree(it);
364 it=next;
365 }
366 }
367 {
368 alloc_rec* next;
369 alloc_rec* it;
370 next=0;
371 it=allocs;
372 for(; it!=0; ) {
373 if(!VirtualFree((void*)it->base, 0, MEM_RELEASE)) {
374 sysErrorBelch("freeAllMBlocks: VirtualFree MEM_RELEASE failed");
375 stg_exit(EXIT_FAILURE);
376 }
377 next = it->next;
378 stgFree(it);
379 it=next;
380 }
381 }
382 }
383
384 size_t getPageSize (void)
385 {
386 static size_t pagesize = 0;
387
388 if (pagesize == 0) {
389 SYSTEM_INFO sSysInfo;
390 GetSystemInfo(&sSysInfo);
391 pagesize = sSysInfo.dwPageSize;
392 }
393
394 return pagesize;
395 }
396
397 /* Returns 0 if physical memory size cannot be identified */
398 StgWord64 getPhysicalMemorySize (void)
399 {
400 static StgWord64 physMemSize = 0;
401 if (!physMemSize) {
402 MEMORYSTATUSEX status;
403 status.dwLength = sizeof(status);
404 if (!GlobalMemoryStatusEx(&status)) {
405 #if defined(DEBUG)
406 errorBelch("warning: getPhysicalMemorySize: cannot get physical "
407 "memory size");
408 #endif
409 return 0;
410 }
411 physMemSize = status.ullTotalPhys;
412 }
413 return physMemSize;
414 }
415
416 void setExecutable (void *p, W_ len, rtsBool exec)
417 {
418 DWORD dwOldProtect = 0;
419 if (VirtualProtect (p, len,
420 exec ? PAGE_EXECUTE_READWRITE : PAGE_READWRITE,
421 &dwOldProtect) == 0)
422 {
423 sysErrorBelch("setExecutable: failed to protect 0x%p; old protection: "
424 "%lu\n", p, (unsigned long)dwOldProtect);
425 stg_exit(EXIT_FAILURE);
426 }
427 }
428
429 #ifdef USE_LARGE_ADDRESS_SPACE
430
431 static void* heap_base = NULL;
432
433 void *osReserveHeapMemory (void *startAddress, W_ *len)
434 {
435 void *start;
436
437 heap_base = VirtualAlloc(startAddress, *len + MBLOCK_SIZE,
438 MEM_RESERVE, PAGE_READWRITE);
439 if (heap_base == NULL) {
440 if (GetLastError() == ERROR_NOT_ENOUGH_MEMORY) {
441 errorBelch("out of memory");
442 } else {
443 sysErrorBelch(
444 "osReserveHeapMemory: VirtualAlloc MEM_RESERVE %llu bytes \
445 at address %p bytes failed",
446 len + MBLOCK_SIZE, startAddress);
447 }
448 stg_exit(EXIT_FAILURE);
449 }
450
451 // VirtualFree MEM_RELEASE must always match a
452 // previous MEM_RESERVE call, in address and size
453 // so we necessarily leak some address space here,
454 // before and after the aligned area
455 // It is not a huge problem because we never commit
456 // that memory
457 start = MBLOCK_ROUND_UP(heap_base);
458
459 return start;
460 }
461
462 void osCommitMemory (void *at, W_ size)
463 {
464 void *temp;
465 temp = VirtualAlloc(at, size, MEM_COMMIT, PAGE_READWRITE);
466 if (temp == NULL) {
467 sysErrorBelch("osCommitMemory: VirtualAlloc MEM_COMMIT failed");
468 stg_exit(EXIT_FAILURE);
469 }
470 }
471
472 void osDecommitMemory (void *at, W_ size)
473 {
474 if (!VirtualFree(at, size, MEM_DECOMMIT)) {
475 sysErrorBelch("osDecommitMemory: VirtualFree MEM_DECOMMIT failed");
476 stg_exit(EXIT_FAILURE);
477 }
478 }
479
480 void osReleaseHeapMemory (void)
481 {
482 VirtualFree(heap_base, 0, MEM_RELEASE);
483 }
484
485 #endif
486
487 rtsBool osNumaAvailable(void)
488 {
489 return rtsFalse;
490 }
491
492 uint32_t osNumaNodes(void)
493 {
494 return 1;
495 }
496
497 StgWord osNumaMask(void)
498 {
499 return 1;
500 }
501
502 void osBindMBlocksToNode(
503 void *addr STG_UNUSED,
504 StgWord size STG_UNUSED,
505 uint32_t node STG_UNUSED)
506 {
507 }