1 /* -----------------------------------------------------------------------------
3 * (c) The University of Glasgow 2006-2007
5 * OS-specific memory management
7 * ---------------------------------------------------------------------------*/
17 typedef struct alloc_rec_
{
18 char* base
; /* non-aligned base address, directly from VirtualAlloc */
19 W_ size
; /* Size in bytes */
20 struct alloc_rec_
* next
;
23 typedef struct block_rec_
{
24 char* base
; /* base address, non-MBLOCK-aligned */
25 W_ size
; /* size in bytes */
26 struct block_rec_
* next
;
29 /* allocs are kept in ascending order, and are the memory regions as
30 returned by the OS as we need to have matching VirtualAlloc and
32 static alloc_rec
* allocs
= NULL
;
34 /* free_blocks are kept in ascending order, and adjacent blocks are merged */
35 static block_rec
* free_blocks
= NULL
;
48 rec
= (alloc_rec
*)stgMallocBytes(sizeof(alloc_rec
),"getMBlocks: allocNew");
49 rec
->size
= ((W_
)n
+1)*MBLOCK_SIZE
;
51 VirtualAlloc(NULL
, rec
->size
, MEM_RESERVE
, PAGE_READWRITE
);
55 if (GetLastError() == ERROR_NOT_ENOUGH_MEMORY
) {
57 errorBelch("out of memory");
60 "getMBlocks: VirtualAlloc MEM_RESERVE %d blocks failed", n
);
64 temp
.base
=0; temp
.size
=0; temp
.next
=allocs
;
68 for(; it
->next
!=0 && it
->next
->base
<rec
->base
; it
=it
->next
) ;
79 insertFree(char* alloc_base
, W_ alloc_size
) {
84 temp
.base
=0; temp
.size
=0; temp
.next
=free_blocks
;
87 for( ; it
!=0 && it
->base
<alloc_base
; prev
=it
, it
=it
->next
) {}
89 if(it
!=0 && alloc_base
+alloc_size
== it
->base
) {
90 if(prev
->base
+ prev
->size
== alloc_base
) { /* Merge it, alloc, prev */
91 prev
->size
+= alloc_size
+ it
->size
;
92 prev
->next
= it
->next
;
94 } else { /* Merge it, alloc */
95 it
->base
= alloc_base
;
96 it
->size
+= alloc_size
;
98 } else if(prev
->base
+ prev
->size
== alloc_base
) { /* Merge alloc, prev */
99 prev
->size
+= alloc_size
;
100 } else { /* Merge none */
102 rec
= (block_rec
*)stgMallocBytes(sizeof(block_rec
),"getMBlocks: insertFree");
103 rec
->base
=alloc_base
;
104 rec
->size
=alloc_size
;
108 free_blocks
=temp
.next
;
113 findFreeBlocks(nat n
) {
121 required_size
= n
*MBLOCK_SIZE
;
122 temp
.next
=free_blocks
; temp
.base
=0; temp
.size
=0;
124 /* TODO: Don't just take first block, find smallest sufficient block */
125 for( ; it
!=0 && it
->size
<required_size
; prev
=it
, it
=it
->next
) {}
127 if( (((W_
)it
->base
) & MBLOCK_MASK
) == 0) { /* MBlock aligned */
128 ret
= (void*)it
->base
;
129 if(it
->size
==required_size
) {
133 it
->base
+= required_size
;
134 it
->size
-=required_size
;
140 need_base
= (char*)(((W_
)it
->base
) & ((W_
)~MBLOCK_MASK
)) + MBLOCK_SIZE
;
141 next
= (block_rec
*)stgMallocBytes(
143 , "getMBlocks: findFreeBlocks: splitting");
144 new_size
= need_base
- it
->base
;
145 next
->base
= need_base
+required_size
;
146 next
->size
= it
->size
- (new_size
+required_size
);
148 next
->next
= it
->next
;
150 ret
=(void*)need_base
;
153 free_blocks
=temp
.next
;
157 /* VirtualAlloc MEM_COMMIT can't cross boundaries of VirtualAlloc MEM_RESERVE,
158 so we might need to do many VirtualAlloc MEM_COMMITs. We simply walk the
159 (ordered) allocated blocks. */
161 commitBlocks(char* base
, W_ size
) {
164 for( ; it
!=0 && (it
->base
+it
->size
)<=base
; it
=it
->next
) {}
165 for( ; it
!=0 && size
>0; it
=it
->next
) {
168 size_delta
= it
->size
- (base
-it
->base
);
169 if(size_delta
>size
) size_delta
=size
;
170 temp
= VirtualAlloc(base
, size_delta
, MEM_COMMIT
, PAGE_READWRITE
);
172 sysErrorBelch("getMBlocks: VirtualAlloc MEM_COMMIT failed");
173 stg_exit(EXIT_FAILURE
);
181 osGetMBlocks(nat n
) {
183 ret
= findFreeBlocks(n
);
187 /* We already belch in allocNew if it fails */
189 stg_exit(EXIT_FAILURE
);
191 insertFree(alloc
->base
, alloc
->size
);
192 ret
= findFreeBlocks(n
);
197 /* (In)sanity tests */
198 if (((W_
)ret
& MBLOCK_MASK
) != 0) {
199 barf("getMBlocks: misaligned block returned");
202 commitBlocks(ret
, (W_
)MBLOCK_SIZE
*n
);
208 void osFreeMBlocks(char *addr
, nat n
)
211 W_ nBytes
= (W_
)n
* MBLOCK_SIZE
;
213 insertFree(addr
, nBytes
);
216 while ((p
!= NULL
) && (addr
>= (p
->base
+ p
->size
))) {
220 if ((p
== NULL
) || (p
->base
> addr
)) {
221 errorBelch("Memory to be freed isn't allocated\n");
222 stg_exit(EXIT_FAILURE
);
224 if (p
->base
+ p
->size
>= addr
+ nBytes
) {
225 if (!VirtualFree(addr
, nBytes
, MEM_DECOMMIT
)) {
226 sysErrorBelch("osFreeMBlocks: VirtualFree MEM_DECOMMIT failed");
227 stg_exit(EXIT_FAILURE
);
232 W_ bytesToFree
= p
->base
+ p
->size
- addr
;
233 if (!VirtualFree(addr
, bytesToFree
, MEM_DECOMMIT
)) {
234 sysErrorBelch("osFreeMBlocks: VirtualFree MEM_DECOMMIT failed");
235 stg_exit(EXIT_FAILURE
);
238 nBytes
-= bytesToFree
;
244 void osReleaseFreeMemory(void)
246 alloc_rec
*prev_a
, *a
;
248 block_rec
*prev_fb
, *fb
;
250 char *a_end
, *fb_end
;
252 /* go through allocs and free_blocks in lockstep, looking for allocs
253 that are completely free, and uncommit them */
257 head_a
.next
= allocs
;
260 head_fb
.next
= free_blocks
;
267 a_end
= a
->base
+ a
->size
;
268 /* If a is freeable then there is a single freeblock in fb that
269 covers it. The end of this free block must be >= the end of
270 a, so skip anything in fb that ends before a. */
271 while (fb
!= NULL
&& fb
->base
+ fb
->size
< a_end
) {
277 /* If we have nothing left in fb, then neither a nor
278 anything later in the list is freeable, so we are done. */
282 fb_end
= fb
->base
+ fb
->size
;
283 /* We have a candidate fb. But does it really cover a? */
284 if (fb
->base
<= a
->base
) {
285 /* Yes, the alloc is within the free block. Now we need
286 to know if it sticks out at either end. */
287 if (fb_end
== a_end
) {
288 if (fb
->base
== a
->base
) {
289 /* fb and a are identical, so just free fb */
290 prev_fb
->next
= fb
->next
;
295 /* fb begins earlier, so truncate it to not include a */
296 fb
->size
= a
->base
- fb
->base
;
300 /* fb ends later, so we'll make fb just be the part
301 after a. First though, if it also starts earlier,
302 we make a new free block record for the before bit. */
303 if (fb
->base
!= a
->base
) {
306 new_fb
= (block_rec
*)stgMallocBytes(sizeof(block_rec
),"osReleaseFreeMemory");
307 new_fb
->base
= fb
->base
;
308 new_fb
->size
= a
->base
- fb
->base
;
310 prev_fb
->next
= new_fb
;
312 fb
->size
= fb_end
- a_end
;
315 /* Now we can free the alloc */
316 prev_a
->next
= a
->next
;
317 if(!VirtualFree((void *)a
->base
, 0, MEM_RELEASE
)) {
318 sysErrorBelch("freeAllMBlocks: VirtualFree MEM_RELEASE failed");
319 stg_exit(EXIT_FAILURE
);
325 /* Otherwise this alloc is not freeable, so go on to the
333 allocs
= head_a
.next
;
334 free_blocks
= head_fb
.next
;
338 osFreeAllMBlocks(void)
357 if(!VirtualFree((void*)it
->base
, 0, MEM_RELEASE
)) {
358 sysErrorBelch("freeAllMBlocks: VirtualFree MEM_RELEASE failed");
359 stg_exit(EXIT_FAILURE
);
368 W_
getPageSize (void)
370 static W_ pagesize
= 0;
374 SYSTEM_INFO sSysInfo
;
375 GetSystemInfo(&sSysInfo
);
376 pagesize
= sSysInfo
.dwPageSize
;
381 void setExecutable (void *p
, W_ len
, rtsBool exec
)
383 DWORD dwOldProtect
= 0;
384 if (VirtualProtect (p
, len
,
385 exec ? PAGE_EXECUTE_READWRITE
: PAGE_READWRITE
,
388 sysErrorBelch("setExecutable: failed to protect 0x%p; old protection: %lu\n",
389 p
, (unsigned long)dwOldProtect
);
390 stg_exit(EXIT_FAILURE
);