Imported Upstream version 1.0.0
[platform/upstream/js.git] / js / src / jsgcchunk.cpp
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2  * vim: set ts=4 sw=4 et tw=99 ft=cpp:
3  *
4  * ***** BEGIN LICENSE BLOCK *****
5  * Copyright (C) 2006-2008 Jason Evans <jasone@FreeBSD.org>.
6  * All rights reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice(s), this list of conditions and the following disclaimer as
13  *    the first lines of this file unmodified other than the possible
14  *    addition of one or more copyright notices.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice(s), this list of conditions and the following disclaimer in
17  *    the documentation and/or other materials provided with the
18  *    distribution.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) ``AS IS'' AND ANY
21  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
23  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) BE
24  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
27  * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
28  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
29  * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
30  * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  *
32  * ***** END LICENSE BLOCK ***** */
33
34 #include <stdlib.h>
35 #include "jstypes.h"
36 #include "jsstdint.h"
37 #include "jsgcchunk.h"
38
39 #ifdef XP_WIN
40 # include "jswin.h"
41
42 # ifdef _MSC_VER
43 #  pragma warning( disable: 4267 4996 4146 )
44 # endif
45
46 #elif defined(XP_OS2)
47
48 # define INCL_DOSMEMMGR
49 # include <os2.h>
50
51 #elif defined(XP_MACOSX) || defined(DARWIN)
52
53 # include <libkern/OSAtomic.h>
54 # include <mach/mach_error.h>
55 # include <mach/mach_init.h>
56 # include <mach/vm_map.h>
57 # include <malloc/malloc.h>
58
59 #elif defined(XP_UNIX) || defined(XP_BEOS)
60
61 # include <unistd.h>
62 # include <sys/mman.h>
63
64 # ifndef MAP_NOSYNC
65 #  define MAP_NOSYNC    0
66 # endif
67
68 #endif
69
70 #ifdef XP_WIN
71
72 /*
73  * On Windows CE < 6 we must use separated MEM_RESERVE and MEM_COMMIT
74  * VirtualAlloc calls and we cannot use MEM_RESERVE to allocate at the given
75  * address. So we use a workaround based on oversized allocation.
76  */
77 # if defined(WINCE) && !defined(MOZ_MEMORY_WINCE6)
78
79 #  define JS_GC_HAS_MAP_ALIGN
80
81 static void
82 UnmapPagesAtBase(void *p)
83 {
84     JS_ALWAYS_TRUE(VirtualFree(p, 0, MEM_RELEASE));
85 }
86
87 static void *
88 MapAlignedPages(size_t size, size_t alignment)
89 {
90     JS_ASSERT(size % alignment == 0);
91     JS_ASSERT(size >= alignment);
92
93     void *reserve = VirtualAlloc(NULL, size, MEM_RESERVE, PAGE_NOACCESS);
94     if (!reserve)
95         return NULL;
96
97     void *p = VirtualAlloc(reserve, size, MEM_COMMIT, PAGE_READWRITE);
98     JS_ASSERT(p == reserve);
99
100     size_t mask = alignment - 1;
101     size_t offset = (uintptr_t) p & mask;
102     if (!offset)
103         return p;
104
105     /* Try to extend the initial allocation. */
106     UnmapPagesAtBase(reserve);
107     reserve = VirtualAlloc(NULL, size + alignment - offset, MEM_RESERVE,
108                            PAGE_NOACCESS);
109     if (!reserve)
110         return NULL;
111     if (offset == ((uintptr_t) reserve & mask)) {
112         void *aligned = (void *) ((uintptr_t) reserve + alignment - offset);
113         p = VirtualAlloc(aligned, size, MEM_COMMIT, PAGE_READWRITE);
114         JS_ASSERT(p == aligned);
115         return p;
116     }
117
118     /* over allocate to ensure we have an aligned region */
119     UnmapPagesAtBase(reserve);
120     reserve = VirtualAlloc(NULL, size + alignment, MEM_RESERVE, PAGE_NOACCESS);
121     if (!reserve)
122         return NULL;
123
124     offset = (uintptr_t) reserve & mask;
125     void *aligned = (void *) ((uintptr_t) reserve + alignment - offset);
126     p = VirtualAlloc(aligned, size, MEM_COMMIT, PAGE_READWRITE);
127     JS_ASSERT(p == aligned);
128
129     return p;
130 }
131
132 static void
133 UnmapPages(void *p, size_t size)
134 {
135     if (VirtualFree(p, 0, MEM_RELEASE))
136         return;
137
138     /* We could have used the over allocation. */
139     JS_ASSERT(GetLastError() == ERROR_INVALID_PARAMETER);
140     MEMORY_BASIC_INFORMATION info;
141     VirtualQuery(p, &info, sizeof(info));
142
143     UnmapPagesAtBase(info.AllocationBase);
144 }
145
146 # else /* WINCE */
147
148 static void *
149 MapPages(void *addr, size_t size)
150 {
151     void *p = VirtualAlloc(addr, size, MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE);
152     JS_ASSERT_IF(p && addr, p == addr);
153     return p;
154 }
155
156 static void
157 UnmapPages(void *addr, size_t size)
158 {
159     JS_ALWAYS_TRUE(VirtualFree(addr, 0, MEM_RELEASE));
160 }
161
162 # endif /* !WINCE */
163
164 #elif defined(XP_OS2)
165
166 #define JS_GC_HAS_MAP_ALIGN 1
167 #define OS2_MAX_RECURSIONS  16
168
169 static void
170 UnmapPages(void *addr, size_t size)
171 {
172     if (!DosFreeMem(addr))
173         return;
174
175     /* if DosFreeMem() failed, 'addr' is probably part of an "expensive"
176      * allocation, so calculate the base address and try again
177      */
178     unsigned long cb = 2 * size;
179     unsigned long flags;
180     if (DosQueryMem(addr, &cb, &flags) || cb < size)
181         return;
182
183     jsuword base = reinterpret_cast<jsuword>(addr) - ((2 * size) - cb);
184     DosFreeMem(reinterpret_cast<void*>(base));
185
186     return;
187 }
188
189 static void *
190 MapAlignedPagesRecursively(size_t size, size_t alignment, int& recursions)
191 {
192     if (++recursions >= OS2_MAX_RECURSIONS)
193         return NULL;
194
195     void *tmp;
196     if (DosAllocMem(&tmp, size,
197                     OBJ_ANY | PAG_COMMIT | PAG_READ | PAG_WRITE)) {
198         JS_ALWAYS_TRUE(DosAllocMem(&tmp, size,
199                                    PAG_COMMIT | PAG_READ | PAG_WRITE) == 0);
200     }
201     size_t offset = reinterpret_cast<jsuword>(tmp) & (alignment - 1);
202     if (!offset)
203         return tmp;
204
205     /* if there are 'filler' bytes of free space above 'tmp', free 'tmp',
206      * then reallocate it as a 'filler'-sized block;  assuming we're not
207      * in a race with another thread, the next recursion should succeed
208      */
209     size_t filler = size + alignment - offset;
210     unsigned long cb = filler;
211     unsigned long flags = 0;
212     unsigned long rc = DosQueryMem(&(static_cast<char*>(tmp))[size],
213                                    &cb, &flags);
214     if (!rc && (flags & PAG_FREE) && cb >= filler) {
215         UnmapPages(tmp, 0);
216         if (DosAllocMem(&tmp, filler,
217                         OBJ_ANY | PAG_COMMIT | PAG_READ | PAG_WRITE)) {
218             JS_ALWAYS_TRUE(DosAllocMem(&tmp, filler,
219                                        PAG_COMMIT | PAG_READ | PAG_WRITE) == 0);
220         }
221     }
222
223     void *p = MapAlignedPagesRecursively(size, alignment, recursions);
224     UnmapPages(tmp, 0);
225
226     return p;
227 }
228
229 static void *
230 MapAlignedPages(size_t size, size_t alignment)
231 {
232     int recursions = -1;
233
234     /* make up to OS2_MAX_RECURSIONS attempts to get an aligned block
235      * of the right size by recursively allocating blocks of unaligned
236      * free memory until only an aligned allocation is possible
237      */
238     void *p = MapAlignedPagesRecursively(size, alignment, recursions);
239     if (p)
240         return p;
241
242     /* if memory is heavily fragmented, the recursive strategy may fail;
243      * instead, use the "expensive" strategy:  allocate twice as much
244      * as requested and return an aligned address within this block
245      */
246     if (DosAllocMem(&p, 2 * size,
247                     OBJ_ANY | PAG_COMMIT | PAG_READ | PAG_WRITE)) {
248         JS_ALWAYS_TRUE(DosAllocMem(&p, 2 * size,
249                                    PAG_COMMIT | PAG_READ | PAG_WRITE) == 0);
250     }
251
252     jsuword addr = reinterpret_cast<jsuword>(p);
253     addr = (addr + (alignment - 1)) & ~(alignment - 1);
254
255     return reinterpret_cast<void *>(addr);
256 }
257
258 #elif defined(XP_MACOSX) || defined(DARWIN)
259
260 static void *
261 MapPages(void *addr, size_t size)
262 {
263     vm_address_t p;
264     int flags;
265     if (addr) {
266         p = (vm_address_t) addr;
267         flags = 0;
268     } else {
269         flags = VM_FLAGS_ANYWHERE;
270     }
271
272     kern_return_t err = vm_allocate((vm_map_t) mach_task_self(),
273                                     &p, (vm_size_t) size, flags);
274     if (err != KERN_SUCCESS)
275         return NULL;
276
277     JS_ASSERT(p);
278     JS_ASSERT_IF(addr, p == (vm_address_t) addr);
279     return (void *) p;
280 }
281
282 static void
283 UnmapPages(void *addr, size_t size)
284 {
285     JS_ALWAYS_TRUE(vm_deallocate((vm_map_t) mach_task_self(),
286                                  (vm_address_t) addr,
287                                  (vm_size_t) size)
288                    == KERN_SUCCESS);
289 }
290
291 #elif defined(XP_UNIX) || defined(XP_BEOS)
292
293 /* Required on Solaris 10. Might improve performance elsewhere. */
294 # if defined(SOLARIS) && defined(MAP_ALIGN)
295 #  define JS_GC_HAS_MAP_ALIGN
296
297 static void *
298 MapAlignedPages(size_t size, size_t alignment)
299 {
300     /*
301      * We don't use MAP_FIXED here, because it can cause the *replacement*
302      * of existing mappings, and we only want to create new mappings.
303      */
304 #ifdef SOLARIS
305     void *p = mmap((caddr_t) alignment, size, PROT_READ | PROT_WRITE,
306                      MAP_PRIVATE | MAP_NOSYNC | MAP_ALIGN | MAP_ANON, -1, 0);
307 #else
308     void *p = mmap((void *) alignment, size, PROT_READ | PROT_WRITE,
309                      MAP_PRIVATE | MAP_NOSYNC | MAP_ALIGN | MAP_ANON, -1, 0);
310 #endif
311     if (p == MAP_FAILED)
312         return NULL;
313     return p;
314 }
315
316 # else /* JS_GC_HAS_MAP_ALIGN */
317
318 static void *
319 MapPages(void *addr, size_t size)
320 {
321     /*
322      * We don't use MAP_FIXED here, because it can cause the *replacement*
323      * of existing mappings, and we only want to create new mappings.
324      */
325     void *p = mmap(addr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON,
326                    -1, 0);
327     if (p == MAP_FAILED)
328         return NULL;
329     if (addr && p != addr) {
330         /* We succeeded in mapping memory, but not in the right place. */
331         JS_ALWAYS_TRUE(munmap(p, size) == 0);
332         return NULL;
333     }
334     return p;
335 }
336
337 # endif /* !JS_GC_HAS_MAP_ALIGN */
338
339 static void
340 UnmapPages(void *addr, size_t size)
341 {
342 #ifdef SOLARIS
343     JS_ALWAYS_TRUE(munmap((caddr_t) addr, size) == 0);
344 #else
345     JS_ALWAYS_TRUE(munmap(addr, size) == 0);
346 #endif
347 }
348
349 #endif
350
351 namespace js {
352
353 GCChunkAllocator defaultGCChunkAllocator;
354
355 inline void *
356 FindChunkStart(void *p)
357 {
358     jsuword addr = reinterpret_cast<jsuword>(p);
359     addr = (addr + GC_CHUNK_MASK) & ~GC_CHUNK_MASK;
360     return reinterpret_cast<void *>(addr);
361 }
362
363 JS_FRIEND_API(void *)
364 AllocGCChunk()
365 {
366     void *p;
367
368 #ifdef JS_GC_HAS_MAP_ALIGN
369     p = MapAlignedPages(GC_CHUNK_SIZE, GC_CHUNK_SIZE);
370     if (!p)
371         return NULL;
372 #else
373     /*
374      * Windows requires that there be a 1:1 mapping between VM allocation
375      * and deallocation operations.  Therefore, take care here to acquire the
376      * final result via one mapping operation.  This means unmapping any
377      * preliminary result that is not correctly aligned.
378      */
379     p = MapPages(NULL, GC_CHUNK_SIZE);
380     if (!p)
381         return NULL;
382
383     if (reinterpret_cast<jsuword>(p) & GC_CHUNK_MASK) {
384         UnmapPages(p, GC_CHUNK_SIZE);
385         p = MapPages(FindChunkStart(p), GC_CHUNK_SIZE);
386         while (!p) {
387             /*
388              * Over-allocate in order to map a memory region that is
389              * definitely large enough then deallocate and allocate again the
390              * correct size, within the over-sized mapping.
391              */
392             p = MapPages(NULL, GC_CHUNK_SIZE * 2);
393             if (!p)
394                 return 0;
395             UnmapPages(p, GC_CHUNK_SIZE * 2);
396             p = MapPages(FindChunkStart(p), GC_CHUNK_SIZE);
397
398             /*
399              * Failure here indicates a race with another thread, so
400              * try again.
401              */
402         }
403     }
404 #endif /* !JS_GC_HAS_MAP_ALIGN */
405
406     JS_ASSERT(!(reinterpret_cast<jsuword>(p) & GC_CHUNK_MASK));
407     return p;
408 }
409
410 JS_FRIEND_API(void)
411 FreeGCChunk(void *p)
412 {
413     JS_ASSERT(p);
414     JS_ASSERT(!(reinterpret_cast<jsuword>(p) & GC_CHUNK_MASK));
415     UnmapPages(p, GC_CHUNK_SIZE);
416 }
417
418 } /* namespace js */
419