Revert "rts: add Emacs 'Local Variables' to every .c file"
[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 #include <sys/sysctl.h>
39 #endif
40
41 static caddr_t next_request = 0;
42
43 void osMemInit(void)
44 {
45 next_request = (caddr_t)RtsFlags.GcFlags.heapBase;
46 }
47
48 /* -----------------------------------------------------------------------------
49 The mmap() method
50
51 On Unix-like systems, we use mmap() to allocate our memory. We
52 want memory in chunks of MBLOCK_SIZE, and aligned on an MBLOCK_SIZE
53 boundary. The mmap() interface doesn't give us this level of
54 control, so we have to use some heuristics.
55
56 In the general case, if we want a block of n megablocks, then we
57 allocate n+1 and trim off the slop from either side (using
58 munmap()) to get an aligned chunk of size n. However, the next
59 time we'll try to allocate directly after the previously allocated
60 chunk, on the grounds that this is aligned and likely to be free.
61 If it turns out that we were wrong, we have to munmap() and try
62 again using the general method.
63
64 Note on posix_memalign(): this interface is available on recent
65 systems and appears to provide exactly what we want. However, it
66 turns out not to be as good as our mmap() implementation, because
67 it wastes extra space (using double the address space, in a test on
68 x86_64/Linux). The problem seems to be that posix_memalign()
69 returns memory that can be free()'d, so the library must store
70 extra information along with the allocated block, thus messing up
71 the alignment. Hence, we don't use posix_memalign() for now.
72
73 -------------------------------------------------------------------------- */
74
75 // A wrapper around mmap(), to abstract away from OS differences in
76 // the mmap() interface.
77
78 static void *
79 my_mmap (void *addr, W_ size)
80 {
81 void *ret;
82
83 #if defined(solaris2_HOST_OS) || defined(irix_HOST_OS)
84 {
85 int fd = open("/dev/zero",O_RDONLY);
86 ret = mmap(addr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
87 close(fd);
88 }
89 #elif hpux_HOST_OS
90 ret = mmap(addr, size, PROT_READ | PROT_WRITE,
91 MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
92 #elif darwin_HOST_OS
93 // Without MAP_FIXED, Apple's mmap ignores addr.
94 // With MAP_FIXED, it overwrites already mapped regions, whic
95 // mmap(0, ... MAP_FIXED ...) is worst of all: It unmaps the program text
96 // and replaces it with zeroes, causing instant death.
97 // This behaviour seems to be conformant with IEEE Std 1003.1-2001.
98 // Let's just use the underlying Mach Microkernel calls directly,
99 // they're much nicer.
100
101 kern_return_t err = 0;
102 ret = addr;
103 if(addr) // try to allocate at address
104 err = vm_allocate(mach_task_self(),(vm_address_t*) &ret, size, FALSE);
105 if(!addr || err) // try to allocate anywhere
106 err = vm_allocate(mach_task_self(),(vm_address_t*) &ret, size, TRUE);
107
108 if(err) {
109 // don't know what the error codes mean exactly, assume it's
110 // not our problem though.
111 errorBelch("memory allocation failed (requested %" FMT_Word " bytes)",
112 size);
113 stg_exit(EXIT_FAILURE);
114 } else {
115 vm_protect(mach_task_self(), (vm_address_t)ret, size, FALSE,
116 VM_PROT_READ|VM_PROT_WRITE);
117 }
118 #elif linux_HOST_OS
119 ret = mmap(addr, size, PROT_READ | PROT_WRITE,
120 MAP_ANON | MAP_PRIVATE, -1, 0);
121 if (ret == (void *)-1 && errno == EPERM) {
122 // Linux may return EPERM if it tried to give us
123 // a chunk of address space below mmap_min_addr,
124 // See Trac #7500.
125 if (addr != 0) {
126 // Try again with no hint address.
127 // It's not clear that this can ever actually help,
128 // but since our alternative is to abort, we may as well try.
129 ret = mmap(0, size, PROT_READ | PROT_WRITE,
130 MAP_ANON | MAP_PRIVATE, -1, 0);
131 }
132 if (ret == (void *)-1 && errno == EPERM) {
133 // Linux is not willing to give us any mapping,
134 // so treat this as an out-of-memory condition
135 // (really out of virtual address space).
136 errno = ENOMEM;
137 }
138 }
139 #else
140 ret = mmap(addr, size, PROT_READ | PROT_WRITE,
141 MAP_ANON | MAP_PRIVATE, -1, 0);
142 #endif
143
144 if (ret == (void *)-1) {
145 if (errno == ENOMEM ||
146 (errno == EINVAL && sizeof(void*)==4 && size >= 0xc0000000)) {
147 // If we request more than 3Gig, then we get EINVAL
148 // instead of ENOMEM (at least on Linux).
149 errorBelch("out of memory (requested %" FMT_Word " bytes)", size);
150 stg_exit(EXIT_FAILURE);
151 } else {
152 barf("getMBlock: mmap: %s", strerror(errno));
153 }
154 }
155
156 return ret;
157 }
158
159 // Implements the general case: allocate a chunk of memory of 'size'
160 // mblocks.
161
162 static void *
163 gen_map_mblocks (W_ size)
164 {
165 int slop;
166 StgWord8 *ret;
167
168 // Try to map a larger block, and take the aligned portion from
169 // it (unmap the rest).
170 size += MBLOCK_SIZE;
171 ret = my_mmap(0, size);
172
173 // unmap the slop bits around the chunk we allocated
174 slop = (W_)ret & MBLOCK_MASK;
175
176 if (munmap((void*)ret, MBLOCK_SIZE - slop) == -1) {
177 barf("gen_map_mblocks: munmap failed");
178 }
179 if (slop > 0 && munmap((void*)(ret+size-slop), slop) == -1) {
180 barf("gen_map_mblocks: munmap failed");
181 }
182
183 // ToDo: if we happened to get an aligned block, then don't
184 // unmap the excess, just use it. For this to work, you
185 // need to keep in mind the following:
186 // * Calling my_mmap() with an 'addr' arg pointing to
187 // already my_mmap()ed space is OK and won't fail.
188 // * If my_mmap() can't satisfy the request at the
189 // given 'next_request' address in getMBlocks(), that
190 // you unmap the extra mblock mmap()ed here (or simply
191 // satisfy yourself that the slop introduced isn't worth
192 // salvaging.)
193 //
194
195 // next time, try after the block we just got.
196 ret += MBLOCK_SIZE - slop;
197 return ret;
198 }
199
200 void *
201 osGetMBlocks(nat n)
202 {
203 caddr_t ret;
204 W_ size = MBLOCK_SIZE * (W_)n;
205
206 if (next_request == 0) {
207 // use gen_map_mblocks the first time.
208 ret = gen_map_mblocks(size);
209 } else {
210 ret = my_mmap(next_request, size);
211
212 if (((W_)ret & MBLOCK_MASK) != 0) {
213 // misaligned block!
214 #if 0 // defined(DEBUG)
215 errorBelch("warning: getMBlock: misaligned block %p returned "
216 "when allocating %d megablock(s) at %p",
217 ret, n, next_request);
218 #endif
219
220 // unmap this block...
221 if (munmap(ret, size) == -1) {
222 barf("getMBlock: munmap failed");
223 }
224 // and do it the hard way
225 ret = gen_map_mblocks(size);
226 }
227 }
228 // Next time, we'll try to allocate right after the block we just got.
229 // ToDo: check that we haven't already grabbed the memory at next_request
230 next_request = ret + size;
231
232 return ret;
233 }
234
235 void osFreeMBlocks(char *addr, nat n)
236 {
237 munmap(addr, n * MBLOCK_SIZE);
238 }
239
240 void osReleaseFreeMemory(void) {
241 /* Nothing to do on POSIX */
242 }
243
244 void osFreeAllMBlocks(void)
245 {
246 void *mblock;
247
248 for (mblock = getFirstMBlock();
249 mblock != NULL;
250 mblock = getNextMBlock(mblock)) {
251 munmap(mblock, MBLOCK_SIZE);
252 }
253 }
254
255 W_ getPageSize (void)
256 {
257 static W_ pageSize = 0;
258 if (pageSize) {
259 return pageSize;
260 } else {
261 long ret;
262 ret = sysconf(_SC_PAGESIZE);
263 if (ret == -1) {
264 barf("getPageSize: cannot get page size");
265 }
266 pageSize = ret;
267 return ret;
268 }
269 }
270
271 /* Returns 0 if physical memory size cannot be identified */
272 StgWord64 getPhysicalMemorySize (void)
273 {
274 static StgWord64 physMemSize = 0;
275 if (!physMemSize) {
276 #if defined(darwin_HOST_OS) || defined(ios_HOST_OS)
277 /* So, darwin doesn't support _SC_PHYS_PAGES, but it does
278 support getting the raw memory size in bytes through
279 sysctlbyname(hw.memsize); */
280 size_t len = sizeof(physMemSize);
281 int ret = -1;
282
283 /* Note hw.memsize is in bytes, so no need to multiply by page size. */
284 ret = sysctlbyname("hw.memsize", &physMemSize, &len, NULL, 0);
285 if (ret == -1) {
286 physMemSize = 0;
287 return 0;
288 }
289 #else
290 /* We'll politely assume we have a system supporting _SC_PHYS_PAGES
291 * otherwise. */
292 W_ pageSize = getPageSize();
293 long ret = sysconf(_SC_PHYS_PAGES);
294 if (ret == -1) {
295 #if defined(DEBUG)
296 errorBelch("warning: getPhysicalMemorySize: cannot get "
297 "physical memory size");
298 #endif
299 return 0;
300 }
301 physMemSize = ret * pageSize;
302 #endif /* darwin_HOST_OS */
303 }
304 return physMemSize;
305 }
306
307 void setExecutable (void *p, W_ len, rtsBool exec)
308 {
309 StgWord pageSize = getPageSize();
310
311 /* malloced memory isn't executable by default on OpenBSD */
312 StgWord mask = ~(pageSize - 1);
313 StgWord startOfFirstPage = ((StgWord)p ) & mask;
314 StgWord startOfLastPage = ((StgWord)p + len - 1) & mask;
315 StgWord size = startOfLastPage - startOfFirstPage + pageSize;
316 if (mprotect((void*)startOfFirstPage, (size_t)size,
317 (exec ? PROT_EXEC : 0) | PROT_READ | PROT_WRITE) != 0) {
318 barf("setExecutable: failed to protect 0x%p\n", p);
319 }
320 }