merge upstream HEAD
[ghc-hetmet.git] / rts / posix / OSMem.c
1 /* -----------------------------------------------------------------------------
2  *
3  * (c) The University of Glasgow 2006-2007
4  *
5  * OS-specific memory management
6  *
7  * ---------------------------------------------------------------------------*/
8
9 // This is non-posix compliant.
10 // #include "PosixSource.h"
11
12 #include "Rts.h"
13
14 #include "RtsUtils.h"
15 #include "sm/OSMem.h"
16
17 #ifdef HAVE_UNISTD_H
18 #include <unistd.h>
19 #endif
20 #ifdef HAVE_SYS_TYPES_H
21 #include <sys/types.h>
22 #endif
23 #ifdef HAVE_SYS_MMAN_H
24 #include <sys/mman.h>
25 #endif
26 #ifdef HAVE_STRING_H
27 #include <string.h>
28 #endif
29 #ifdef HAVE_FCNTL_H
30 #include <fcntl.h>
31 #endif
32
33 #include <errno.h>
34
35 #if darwin_HOST_OS
36 #include <mach/mach.h>
37 #include <mach/vm_map.h>
38 #endif
39
40 static caddr_t next_request = 0;
41
42 void osMemInit(void)
43 {
44     next_request = (caddr_t)RtsFlags.GcFlags.heapBase;
45 }
46
47 /* -----------------------------------------------------------------------------
48    The mmap() method
49
50    On Unix-like systems, we use mmap() to allocate our memory.  We
51    want memory in chunks of MBLOCK_SIZE, and aligned on an MBLOCK_SIZE
52    boundary.  The mmap() interface doesn't give us this level of
53    control, so we have to use some heuristics.
54
55    In the general case, if we want a block of n megablocks, then we
56    allocate n+1 and trim off the slop from either side (using
57    munmap()) to get an aligned chunk of size n.  However, the next
58    time we'll try to allocate directly after the previously allocated
59    chunk, on the grounds that this is aligned and likely to be free.
60    If it turns out that we were wrong, we have to munmap() and try
61    again using the general method.
62
63    Note on posix_memalign(): this interface is available on recent
64    systems and appears to provide exactly what we want.  However, it
65    turns out not to be as good as our mmap() implementation, because
66    it wastes extra space (using double the address space, in a test on
67    x86_64/Linux).  The problem seems to be that posix_memalign()
68    returns memory that can be free()'d, so the library must store
69    extra information along with the allocated block, thus messing up
70    the alignment.  Hence, we don't use posix_memalign() for now.
71
72    -------------------------------------------------------------------------- */
73
74 // A wrapper around mmap(), to abstract away from OS differences in
75 // the mmap() interface.
76
77 static void *
78 my_mmap (void *addr, lnat size)
79 {
80     void *ret;
81
82 #if defined(solaris2_HOST_OS) || defined(irix_HOST_OS)
83     { 
84         int fd = open("/dev/zero",O_RDONLY);
85         ret = mmap(addr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
86         close(fd);
87     }
88 #elif hpux_HOST_OS
89     ret = mmap(addr, size, PROT_READ | PROT_WRITE, 
90                MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
91 #elif darwin_HOST_OS
92     // Without MAP_FIXED, Apple's mmap ignores addr.
93     // With MAP_FIXED, it overwrites already mapped regions, whic
94     // mmap(0, ... MAP_FIXED ...) is worst of all: It unmaps the program text
95     // and replaces it with zeroes, causing instant death.
96     // This behaviour seems to be conformant with IEEE Std 1003.1-2001.
97     // Let's just use the underlying Mach Microkernel calls directly,
98     // they're much nicer.
99     
100     kern_return_t err = 0;
101     ret = addr;
102     if(addr)    // try to allocate at adress
103         err = vm_allocate(mach_task_self(),(vm_address_t*) &ret, size, FALSE);
104     if(!addr || err)    // try to allocate anywhere
105         err = vm_allocate(mach_task_self(),(vm_address_t*) &ret, size, TRUE);
106         
107     if(err) {
108         // don't know what the error codes mean exactly, assume it's
109         // not our problem though.
110         errorBelch("memory allocation failed (requested %lu bytes)", size);
111         stg_exit(EXIT_FAILURE);
112     } else {
113         vm_protect(mach_task_self(),(vm_address_t)ret,size,FALSE,VM_PROT_READ|VM_PROT_WRITE);
114     }
115 #else
116     ret = mmap(addr, size, PROT_READ | PROT_WRITE, 
117                MAP_ANON | MAP_PRIVATE, -1, 0);
118 #endif
119
120     if (ret == (void *)-1) {
121         if (errno == ENOMEM || 
122             (errno == EINVAL && sizeof(void*)==4 && size >= 0xc0000000)) {
123             // If we request more than 3Gig, then we get EINVAL
124             // instead of ENOMEM (at least on Linux).
125             errorBelch("out of memory (requested %lu bytes)", size);
126             stg_exit(EXIT_FAILURE);
127         } else {
128             barf("getMBlock: mmap: %s", strerror(errno));
129         }
130     }
131
132     return ret;
133 }
134
135 // Implements the general case: allocate a chunk of memory of 'size'
136 // mblocks.
137
138 static void *
139 gen_map_mblocks (lnat size)
140 {
141     int slop;
142     StgWord8 *ret;
143
144     // Try to map a larger block, and take the aligned portion from
145     // it (unmap the rest).
146     size += MBLOCK_SIZE;
147     ret = my_mmap(0, size);
148     
149     // unmap the slop bits around the chunk we allocated
150     slop = (W_)ret & MBLOCK_MASK;
151     
152     if (munmap((void*)ret, MBLOCK_SIZE - slop) == -1) {
153       barf("gen_map_mblocks: munmap failed");
154     }
155     if (slop > 0 && munmap((void*)(ret+size-slop), slop) == -1) {
156       barf("gen_map_mblocks: munmap failed");
157     }
158
159     // ToDo: if we happened to get an aligned block, then don't
160     // unmap the excess, just use it. For this to work, you
161     // need to keep in mind the following:
162     //     * Calling my_mmap() with an 'addr' arg pointing to
163     //       already my_mmap()ed space is OK and won't fail.
164     //     * If my_mmap() can't satisfy the request at the
165     //       given 'next_request' address in getMBlocks(), that
166     //       you unmap the extra mblock mmap()ed here (or simply
167     //       satisfy yourself that the slop introduced isn't worth
168     //       salvaging.)
169     // 
170
171     // next time, try after the block we just got.
172     ret += MBLOCK_SIZE - slop;
173     return ret;
174 }
175
176 void *
177 osGetMBlocks(nat n)
178 {
179   caddr_t ret;
180   lnat size = MBLOCK_SIZE * (lnat)n;
181
182   if (next_request == 0) {
183       // use gen_map_mblocks the first time.
184       ret = gen_map_mblocks(size);
185   } else {
186       ret = my_mmap(next_request, size);
187
188       if (((W_)ret & MBLOCK_MASK) != 0) {
189           // misaligned block!
190 #if 0 // defined(DEBUG)
191           errorBelch("warning: getMBlock: misaligned block %p returned when allocating %d megablock(s) at %p", ret, n, next_request);
192 #endif
193
194           // unmap this block...
195           if (munmap(ret, size) == -1) {
196               barf("getMBlock: munmap failed");
197           }
198           // and do it the hard way
199           ret = gen_map_mblocks(size);
200       }
201   }
202   // Next time, we'll try to allocate right after the block we just got.
203   // ToDo: check that we haven't already grabbed the memory at next_request
204   next_request = ret + size;
205
206   return ret;
207 }
208
209 void osFreeMBlocks(char *addr, nat n)
210 {
211     munmap(addr, n * MBLOCK_SIZE);
212 }
213
214 void osReleaseFreeMemory(void) {
215     /* Nothing to do on POSIX */
216 }
217
218 void osFreeAllMBlocks(void)
219 {
220     void *mblock;
221
222     for (mblock = getFirstMBlock();
223          mblock != NULL;
224          mblock = getNextMBlock(mblock)) {
225         munmap(mblock, MBLOCK_SIZE);
226     }
227 }
228
229 lnat getPageSize (void)
230 {
231     static lnat pageSize = 0;
232     if (pageSize) {
233         return pageSize;
234     } else {
235         long ret;
236         ret = sysconf(_SC_PAGESIZE);
237         if (ret == -1) {
238             barf("getPageSize: cannot get page size");
239         }
240         return ret;
241     }
242 }
243
244 void setExecutable (void *p, lnat len, rtsBool exec)
245 {
246     StgWord pageSize = getPageSize();
247
248     /* malloced memory isn't executable by default on OpenBSD */
249     StgWord mask             = ~(pageSize - 1);
250     StgWord startOfFirstPage = ((StgWord)p          ) & mask;
251     StgWord startOfLastPage  = ((StgWord)p + len - 1) & mask;
252     StgWord size             = startOfLastPage - startOfFirstPage + pageSize;
253     if (mprotect((void*)startOfFirstPage, (size_t)size, 
254                  (exec ? PROT_EXEC : 0) | PROT_READ | PROT_WRITE) != 0) {
255         barf("setExecutable: failed to protect 0x%p\n", p);
256     }
257 }