1 /* -----------------------------------------------------------------------------
3 * (c) The GHC Team 1998-1999
5 * MegaBlock Allocator Interface. This file contains all the dirty
6 * architecture-dependent hackery required to get a chunk of aligned
7 * memory from the operating system.
9 * ---------------------------------------------------------------------------*/
11 /* This is non-posix compliant. */
12 /* #include "PosixSource.h" */
18 #include "BlockAlloc.h"
30 #ifdef HAVE_SYS_TYPES_H
31 #include <sys/types.h>
33 #ifndef mingw32_HOST_OS
34 # ifdef HAVE_SYS_MMAN_H
35 # include <sys/mman.h>
45 #include <mach/mach.h>
46 #include <mach/vm_map.h>
51 lnat mblocks_allocated = 0;
53 #if !defined(mingw32_HOST_OS) && !defined(cygwin32_HOST_OS)
54 static caddr_t next_request = 0;
60 #if !defined(mingw32_HOST_OS) && !defined(cygwin32_HOST_OS)
61 next_request = (caddr_t)RtsFlags.GcFlags.heapBase;
65 /* -----------------------------------------------------------------------------
66 The MBlock Map: provides our implementation of HEAP_ALLOCED()
67 -------------------------------------------------------------------------- */
69 #if SIZEOF_VOID_P == 4
70 StgWord8 mblock_map[MBLOCK_MAP_SIZE]; // initially all zeros
71 #elif SIZEOF_VOID_P == 8
72 static MBlockMap dummy_mblock_map;
73 MBlockMap *mblock_cache = &dummy_mblock_map;
74 int mblock_map_count = 0;
75 MBlockMap **mblock_maps = NULL;
78 findMBlockMap(void *p)
81 StgWord32 hi = (StgWord32) (((StgWord)p) >> 32);
82 for( i = 0; i < mblock_map_count; i++ )
84 if(mblock_maps[i]->addrHigh32 == hi)
86 return mblock_maps[i];
93 slowIsHeapAlloced(void *p)
95 MBlockMap *map = findMBlockMap(p);
99 return map->mblocks[MBLOCK_MAP_ENTRY(p)];
107 markHeapAlloced(void *p)
109 #if SIZEOF_VOID_P == 4
110 mblock_map[MBLOCK_MAP_ENTRY(p)] = 1;
111 #elif SIZEOF_VOID_P == 8
112 MBlockMap *map = findMBlockMap(p);
116 mblock_maps = realloc(mblock_maps,
117 sizeof(MBlockMap*) * mblock_map_count);
118 map = mblock_maps[mblock_map_count-1] = calloc(1,sizeof(MBlockMap));
119 map->addrHigh32 = (StgWord32) (((StgWord)p) >> 32);
121 map->mblocks[MBLOCK_MAP_ENTRY(p)] = 1;
126 /* -----------------------------------------------------------------------------
127 Allocate new mblock(s)
128 -------------------------------------------------------------------------- */
133 return getMBlocks(1);
136 /* -----------------------------------------------------------------------------
139 On Unix-like systems, we use mmap() to allocate our memory. We
140 want memory in chunks of MBLOCK_SIZE, and aligned on an MBLOCK_SIZE
141 boundary. The mmap() interface doesn't give us this level of
142 control, so we have to use some heuristics.
144 In the general case, if we want a block of n megablocks, then we
145 allocate n+1 and trim off the slop from either side (using
146 munmap()) to get an aligned chunk of size n. However, the next
147 time we'll try to allocate directly after the previously allocated
148 chunk, on the grounds that this is aligned and likely to be free.
149 If it turns out that we were wrong, we have to munmap() and try
150 again using the general method.
152 Note on posix_memalign(): this interface is available on recent
153 systems and appears to provide exactly what we want. However, it
154 turns out not to be as good as our mmap() implementation, because
155 it wastes extra space (using double the address space, in a test on
156 x86_64/Linux). The problem seems to be that posix_memalign()
157 returns memory that can be free()'d, so the library must store
158 extra information along with the allocated block, thus messing up
159 the alignment. Hence, we don't use posix_memalign() for now.
161 -------------------------------------------------------------------------- */
163 #if !defined(mingw32_HOST_OS) && !defined(cygwin32_HOST_OS)
165 // A wrapper around mmap(), to abstract away from OS differences in
166 // the mmap() interface.
169 my_mmap (void *addr, lnat size)
173 #if defined(solaris2_HOST_OS) || defined(irix_HOST_OS)
175 int fd = open("/dev/zero",O_RDONLY);
176 ret = mmap(addr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
180 ret = mmap(addr, size, PROT_READ | PROT_WRITE,
181 MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
183 // Without MAP_FIXED, Apple's mmap ignores addr.
184 // With MAP_FIXED, it overwrites already mapped regions, whic
185 // mmap(0, ... MAP_FIXED ...) is worst of all: It unmaps the program text
186 // and replaces it with zeroes, causing instant death.
187 // This behaviour seems to be conformant with IEEE Std 1003.1-2001.
188 // Let's just use the underlying Mach Microkernel calls directly,
189 // they're much nicer.
193 if(addr) // try to allocate at adress
194 err = vm_allocate(mach_task_self(),(vm_address_t*) &ret, size, FALSE);
195 if(!addr || err) // try to allocate anywhere
196 err = vm_allocate(mach_task_self(),(vm_address_t*) &ret, size, TRUE);
199 // don't know what the error codes mean exactly, assume it's
200 // not our problem though.
201 errorBelch("memory allocation failed (requested %lu bytes)", size);
202 stg_exit(EXIT_FAILURE);
204 vm_protect(mach_task_self(),(vm_address_t)ret,size,FALSE,VM_PROT_READ|VM_PROT_WRITE);
207 ret = mmap(addr, size, PROT_READ | PROT_WRITE | PROT_EXEC,
208 MAP_ANON | MAP_PRIVATE, -1, 0);
211 if (ret == (void *)-1) {
212 if (errno == ENOMEM ||
213 (errno == EINVAL && sizeof(void*)==4 && size >= 0xc0000000)) {
214 // If we request more than 3Gig, then we get EINVAL
215 // instead of ENOMEM (at least on Linux).
216 errorBelch("out of memory (requested %lu bytes)", size);
217 stg_exit(EXIT_FAILURE);
219 barf("getMBlock: mmap: %s", strerror(errno));
226 // Implements the general case: allocate a chunk of memory of 'size'
230 gen_map_mblocks (lnat size)
235 // Try to map a larger block, and take the aligned portion from
236 // it (unmap the rest).
238 ret = my_mmap(0, size);
240 // unmap the slop bits around the chunk we allocated
241 slop = (W_)ret & MBLOCK_MASK;
243 if (munmap(ret, MBLOCK_SIZE - slop) == -1) {
244 barf("gen_map_mblocks: munmap failed");
246 if (slop > 0 && munmap(ret+size-slop, slop) == -1) {
247 barf("gen_map_mblocks: munmap failed");
250 // ToDo: if we happened to get an aligned block, then don't
251 // unmap the excess, just use it. For this to work, you
252 // need to keep in mind the following:
253 // * Calling my_mmap() with an 'addr' arg pointing to
254 // already my_mmap()ed space is OK and won't fail.
255 // * If my_mmap() can't satisfy the request at the
256 // given 'next_request' address in getMBlocks(), that
257 // you unmap the extra mblock mmap()ed here (or simply
258 // satisfy yourself that the slop introduced isn't worth
262 // next time, try after the block we just got.
263 ret += MBLOCK_SIZE - slop;
268 // The external interface: allocate 'n' mblocks, and return the
275 lnat size = MBLOCK_SIZE * n;
278 if (next_request == 0) {
279 // use gen_map_mblocks the first time.
280 ret = gen_map_mblocks(size);
282 ret = my_mmap(next_request, size);
284 if (((W_)ret & MBLOCK_MASK) != 0) {
286 #if 0 // defined(DEBUG)
287 errorBelch("warning: getMBlock: misaligned block %p returned when allocating %d megablock(s) at %p", ret, n, next_request);
290 // unmap this block...
291 if (munmap(ret, size) == -1) {
292 barf("getMBlock: munmap failed");
294 // and do it the hard way
295 ret = gen_map_mblocks(size);
299 // Next time, we'll try to allocate right after the block we just got.
300 // ToDo: check that we haven't already grabbed the memory at next_request
301 next_request = ret + size;
303 debugTrace(DEBUG_gc, "allocated %d megablock(s) at %p",n,ret);
306 for (i = 0; i < n; i++) {
307 markHeapAlloced( ret + i * MBLOCK_SIZE );
310 mblocks_allocated += n;
318 /* XXX Do something here */
321 #else /* defined(mingw32_HOST_OS) || defined(cygwin32_HOST_OS) */
323 /* alloc_rec keeps the info we need to have matching VirtualAlloc and
326 typedef struct alloc_rec_ {
327 char* base; /* non-aligned base address, directly from VirtualAlloc */
328 int size; /* Size in bytes */
329 struct alloc_rec_* next;
332 typedef struct block_rec_ {
333 char* base; /* base address, non-MBLOCK-aligned */
334 int size; /* size in bytes */
335 struct block_rec_* next;
338 static alloc_rec* allocs = 0;
339 static block_rec* free_blocks = 0;
345 rec = (alloc_rec*)stgMallocBytes(sizeof(alloc_rec),"getMBlocks: allocNew");
346 rec->size = (n+1)*MBLOCK_SIZE;
348 VirtualAlloc(NULL, rec->size, MEM_RESERVE, PAGE_READWRITE);
352 if (GetLastError() == ERROR_NOT_ENOUGH_MEMORY) {
354 errorBelch("out of memory");
357 "getMBlocks: VirtualAlloc MEM_RESERVE %d blocks failed", n);
361 temp.base=0; temp.size=0; temp.next=allocs;
365 for(; it->next!=0 && it->next->base<rec->base; it=it->next) ;
370 debugTrace(DEBUG_gc, "allocated %d megablock(s) at 0x%x",n,(nat)rec->base);
377 insertFree(char* alloc_base, int alloc_size) {
382 temp.base=0; temp.size=0; temp.next=free_blocks;
385 for( ; it!=0 && it->base<alloc_base; prev=it, it=it->next) {}
387 if(it!=0 && alloc_base+alloc_size == it->base) {
388 if(prev->base + prev->size == alloc_base) { /* Merge it, alloc, prev */
389 prev->size += alloc_size + it->size;
390 prev->next = it->next;
392 } else { /* Merge it, alloc */
393 it->base = alloc_base;
394 it->size += alloc_size;
396 } else if(prev->base + prev->size == alloc_base) { /* Merge alloc, prev */
397 prev->size += alloc_size;
398 } else { /* Merge none */
400 rec = (block_rec*)stgMallocBytes(sizeof(block_rec),"getMBlocks: insertFree");
401 rec->base=alloc_base;
402 rec->size=alloc_size;
406 free_blocks=temp.next;
411 findFreeBlocks(nat n) {
419 required_size = n*MBLOCK_SIZE;
420 temp.next=free_blocks; temp.base=0; temp.size=0;
422 /* TODO: Don't just take first block, find smallest sufficient block */
423 for( ; it!=0 && it->size<required_size; prev=it, it=it->next ) {}
425 if( (((unsigned long)it->base) & MBLOCK_MASK) == 0) { /* MBlock aligned */
426 ret = (void*)it->base;
427 if(it->size==required_size) {
431 it->base += required_size;
432 it->size -=required_size;
438 need_base = (char*)(((unsigned long)it->base) & ((unsigned long)~MBLOCK_MASK)) + MBLOCK_SIZE;
439 next = (block_rec*)stgMallocBytes(
441 , "getMBlocks: findFreeBlocks: splitting");
442 new_size = need_base - it->base;
443 next->base = need_base +required_size;
444 next->size = it->size - (new_size+required_size);
446 next->next = it->next;
448 ret=(void*)need_base;
451 free_blocks=temp.next;
455 /* VirtualAlloc MEM_COMMIT can't cross boundaries of VirtualAlloc MEM_RESERVE,
456 so we might need to do many VirtualAlloc MEM_COMMITs. We simply walk the
457 (ordered) allocated blocks. */
459 commitBlocks(char* base, int size) {
462 for( ; it!=0 && (it->base+it->size)<=base; it=it->next ) {}
463 for( ; it!=0 && size>0; it=it->next ) {
466 size_delta = it->size - (base-it->base);
467 if(size_delta>size) size_delta=size;
468 temp = VirtualAlloc(base, size_delta, MEM_COMMIT, PAGE_READWRITE);
470 sysErrorBelch("getMBlocks: VirtualAlloc MEM_COMMIT failed");
471 stg_exit(EXIT_FAILURE);
481 ret = findFreeBlocks(n);
485 /* We already belch in allocNew if it fails */
487 stg_exit(EXIT_FAILURE);
489 insertFree(alloc->base, alloc->size);
490 ret = findFreeBlocks(n);
495 /* (In)sanity tests */
496 if (((W_)ret & MBLOCK_MASK) != 0) {
497 barf("getMBlocks: misaligned block returned");
500 commitBlocks(ret, MBLOCK_SIZE*n);
502 /* Global bookkeeping */
503 mblocks_allocated += n;
505 for(i=0; i<(int)n; ++i) {
506 markHeapAlloced( ret + i * MBLOCK_SIZE );
533 if(!VirtualFree((void*)it->base, 0, MEM_RELEASE)) {
534 sysErrorBelch("freeAllMBlocks: VirtualFree MEM_RELEASE failed");
535 stg_exit(EXIT_FAILURE);