2237972837d747c41af4bfd3cf65681d8ac3bf6b
[ghc.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, W_ 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 %" FMT_Word " 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 %" FMT_Word " 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 (W_ 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 W_ size = MBLOCK_SIZE * (W_)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 W_ getPageSize (void)
230 {
231 static W_ 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, W_ 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 }