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