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