Bug 559408: Arena macros to methods. (r=galish)
[mozilla-central.git] / js / src / jsarena.cpp
blobb53188d9fcbaa5a5f371d9a2076a5bbf1ecf3ec9
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
3 * ***** BEGIN LICENSE BLOCK *****
4 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6 * The contents of this file are subject to the Mozilla Public License Version
7 * 1.1 (the "License"); you may not use this file except in compliance with
8 * the License. You may obtain a copy of the License at
9 * http://www.mozilla.org/MPL/
11 * Software distributed under the License is distributed on an "AS IS" basis,
12 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 * for the specific language governing rights and limitations under the
14 * License.
16 * The Original Code is Mozilla Communicator client code, released
17 * March 31, 1998.
19 * The Initial Developer of the Original Code is
20 * Netscape Communications Corporation.
21 * Portions created by the Initial Developer are Copyright (C) 1998
22 * the Initial Developer. All Rights Reserved.
24 * Contributor(s):
26 * Alternatively, the contents of this file may be used under the terms of
27 * either of the GNU General Public License Version 2 or later (the "GPL"),
28 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 * in which case the provisions of the GPL or the LGPL are applicable instead
30 * of those above. If you wish to allow use of your version of this file only
31 * under the terms of either the GPL or the LGPL, and not to allow others to
32 * use your version of this file under the terms of the MPL, indicate your
33 * decision by deleting the provisions above and replace them with the notice
34 * and other provisions required by the GPL or the LGPL. If you do not delete
35 * the provisions above, a recipient may use your version of this file under
36 * the terms of any one of the MPL, the GPL or the LGPL.
38 * ***** END LICENSE BLOCK ***** */
41 * Lifetime-based fast allocation, inspired by much prior art, including
42 * "Fast Allocation and Deallocation of Memory Based on Object Lifetimes"
43 * David R. Hanson, Software -- Practice and Experience, Vol. 20(1).
45 #include <stdlib.h>
46 #include <string.h>
47 #include "jstypes.h"
48 #include "jsstdint.h"
49 #include "jsbit.h"
50 #include "jsarenainlines.h"
51 #include "jsutil.h"
53 #ifdef DEBUG
54 static JSArenaStats *arena_stats_list;
55 #define COUNT(pool,what) (pool)->stats.what++
56 #else
57 #define COUNT(pool,what) /* nothing */
58 #endif /* DEBUG */
60 #define JS_ARENA_DEFAULT_ALIGN sizeof(double)
62 void
63 JSArenaPool::init(const char *name, size_t size, size_t align, size_t *quotap)
65 if (align == 0)
66 align = JS_ARENA_DEFAULT_ALIGN;
67 mask = JS_BITMASK(JS_CeilingLog2(align));
68 first.next = NULL;
69 first.base = first.avail = first.limit = this->align(jsuword(&first + 1));
70 current = &first;
71 arenasize = size;
72 this->quotap = quotap;
73 #ifdef DEBUG
74 stats.init(name, arena_stats_list);
75 arena_stats_list = &stats;
76 #endif
80 * An allocation that consumes more than pool->arenasize also has a header
81 * pointing back to its previous arena's next member. This header is not
82 * included in [a->base, a->limit), so its space can't be wrongly claimed.
84 * As the header is a pointer, it must be well-aligned. If pool->mask is
85 * greater than or equal to POINTER_MASK, the header just preceding a->base
86 * for an oversized arena a is well-aligned, because a->base is well-aligned.
87 * However, we may need to add more space to pad the JSArena ** back-pointer
88 * so that it lies just behind a->base, because a might not be aligned such
89 * that (jsuword)(a + 1) is on a pointer boundary.
91 * By how much must we pad? Let M be the alignment modulus for pool and P
92 * the modulus for a pointer. Given M >= P, the base of an oversized arena
93 * that satisfies M is well-aligned for P.
95 * On the other hand, if M < P, we must include enough space in the header
96 * size to align the back-pointer on a P boundary so that it can be found by
97 * subtracting P from a->base. This means a->base must be on a P boundary,
98 * even though subsequent allocations from a may be aligned on a lesser (M)
99 * boundary. Given powers of two M and P as above, the extra space needed
100 * when M < P is P-M or POINTER_MASK - pool->mask.
102 * The size of a header including padding is given by the headerSize method
103 * for any pool (for any value of M).
106 void *
107 JSArenaPool::allocate(size_t nb, bool limitCheck)
109 countAllocation(nb);
110 size_t alignedNB = align(nb);
111 jsuword p = current->avail;
113 * NB: always subtract nb from limit rather than adding nb to p to avoid overflowing a
114 * 32-bit address space. See bug 279273.
116 if ((limitCheck && alignedNB > current->limit) || p > current->limit - alignedNB)
117 p = jsuword(allocateInternal(alignedNB));
118 else
119 current->avail = p + alignedNB;
120 return (void *) p;
123 void *
124 JSArenaPool::allocateInternal(size_t nb)
127 * Search pool from current forward till we find or make enough space.
129 * NB: subtract nb from a->limit in the loop condition, instead of adding
130 * nb to a->avail, to avoid overflowing a 32-bit address space (possible
131 * when running a 32-bit program on a 64-bit system where the kernel maps
132 * the heap up against the top of the 32-bit address space).
134 * Thanks to Juergen Kreileder <jk@blackdown.de>, who brought this up in
135 * https://bugzilla.mozilla.org/show_bug.cgi?id=279273.
137 JS_ASSERT((nb & mask) == 0);
138 JSArena *a;
139 for (a = current; nb > a->limit || a->avail > a->limit - nb; current = a) {
140 JSArena **ap = &a->next;
141 if (!*ap) {
142 /* Not enough space in pool, so we must malloc. */
143 jsuword extra = (nb > arenasize) ? headerSize() : 0;
144 jsuword hdrsz = sizeof *a + extra + mask;
145 jsuword gross = hdrsz + JS_MAX(nb, arenasize);
146 if (gross < nb)
147 return NULL;
148 JSArena *b;
149 if (quotap) {
150 if (gross > *quotap)
151 return NULL;
152 b = (JSArena *) js_malloc(gross);
153 if (!b)
154 return NULL;
155 *quotap -= gross;
156 } else {
157 b = (JSArena *) js_malloc(gross);
158 if (!b)
159 return NULL;
162 b->next = NULL;
163 b->limit = (jsuword)b + gross;
164 incArenaCount();
165 COUNT(this, nmallocs);
167 /* If oversized, store ap in the header, just before a->base. */
168 *ap = a = b;
169 JS_ASSERT(gross <= JS_UPTRDIFF(a->limit, a));
170 if (extra) {
171 a->base = a->avail =
172 ((jsuword)a + hdrsz) & ~headerBaseMask();
173 setHeader(a, ap);
174 } else {
175 a->base = a->avail = align(jsuword(a + 1));
177 continue;
179 a = *ap; /* move to next arena */
182 void *p = (void *) a->avail;
183 a->avail += nb;
184 JS_ASSERT(a->base <= a->avail && a->avail <= a->limit);
185 return p;
188 void *
189 JSArenaPool::reallocInternal(void *p, size_t size, size_t incr)
191 JSArena **ap, *a, *b;
192 jsuword boff, aoff, extra, hdrsz, gross, growth;
195 * Use the oversized-single-allocation header to avoid searching for ap.
196 * See JS_ArenaAllocate, the SET_HEADER call.
198 if (size > arenasize) {
199 ap = *ptrToHeader(p);
200 a = *ap;
201 } else {
202 ap = &first.next;
203 while ((a = *ap) != current)
204 ap = &a->next;
207 JS_ASSERT(a->base == (jsuword)p);
208 boff = JS_UPTRDIFF(a->base, a);
209 aoff = align(size + incr);
210 JS_ASSERT(aoff > arenasize);
211 extra = headerSize(); /* oversized header holds ap */
212 hdrsz = sizeof *a + extra + mask; /* header and alignment slop */
213 gross = hdrsz + aoff;
214 JS_ASSERT(gross > aoff);
215 if (quotap) {
216 growth = gross - (a->limit - (jsuword) a);
217 if (growth > *quotap)
218 return NULL;
219 a = (JSArena *) js_realloc(a, gross);
220 if (!a)
221 return NULL;
222 *quotap -= growth;
223 } else {
224 a = (JSArena *) js_realloc(a, gross);
225 if (!a)
226 return NULL;
228 incReallocCount();
230 if (a != *ap) {
231 /* Oops, realloc moved the allocation: update other pointers to a. */
232 if (current == *ap)
233 current = a;
234 b = a->next;
235 if (b && b->avail - b->base > arenasize) {
236 JS_ASSERT(getHeader(b) == &(*ap)->next);
237 setHeader(b, &a->next);
240 /* Now update *ap, the next link of the arena before a. */
241 *ap = a;
244 a->base = ((jsuword)a + hdrsz) & ~headerBaseMask();
245 a->limit = (jsuword)a + gross;
246 a->avail = a->base + aoff;
247 JS_ASSERT(a->base <= a->avail && a->avail <= a->limit);
249 /* Check whether realloc aligned differently, and copy if necessary. */
250 if (boff != JS_UPTRDIFF(a->base, a))
251 memmove((void *) a->base, (char *) a + boff, size);
253 /* Store ap in the oversized-load arena header. */
254 setHeader(a, ap);
255 return (void *) a->base;
258 void
259 JSArenaPool::finish()
261 freeArenaList(&first);
262 #ifdef DEBUG
264 JSArenaStats *stats, **statsp;
266 this->stats.finish();
267 for (statsp = &arena_stats_list; (stats = *statsp) != 0;
268 statsp = &stats->next) {
269 if (stats == &this->stats) {
270 *statsp = stats->next;
271 return;
275 #endif
278 #ifdef DEBUG
279 void
280 JSArenaPool::countAllocation(size_t nb)
282 stats.nallocs++;
283 stats.nbytes += nb;
284 if (nb > stats.maxalloc)
285 stats.maxalloc = nb;
286 stats.variance += nb * nb;
289 void
290 JSArenaPool::countGrowth(size_t size, size_t incr)
292 stats.ngrows++;
293 stats.nbytes += incr;
294 stats.variance -= size * size;
295 size += incr;
296 if (size > stats.maxalloc)
297 stats.maxalloc = size;
298 stats.variance += size * size;
301 JS_FRIEND_API(void)
302 JS_DumpArenaStats()
304 const char *filename = getenv("JS_ARENA_STATFILE");
305 if (!filename)
306 return;
307 FILE *arenaStatFile = strcmp(filename, "stdout")
308 ? stdout : strcmp(filename, "stderr")
309 ? stderr : fopen(filename, "w");
310 for (const JSArenaStats *stats = arena_stats_list; stats; stats = stats->getNext())
311 stats->dump(arenaStatFile);
312 fclose(arenaStatFile);
315 void
316 JSArenaStats::dump(FILE *fp) const
318 double sigma;
319 double mean = JS_MeanAndStdDev(nallocs, nbytes, variance, &sigma);
321 fprintf(fp, "\n%s allocation statistics:\n", name);
322 fprintf(fp, " number of arenas: %u\n", narenas);
323 fprintf(fp, " number of allocations: %u\n", nallocs);
324 fprintf(fp, " number of malloc calls: %u\n", nmallocs);
325 fprintf(fp, " number of deallocations: %u\n", ndeallocs);
326 fprintf(fp, " number of allocation growths: %u\n", ngrows);
327 fprintf(fp, " number of in-place growths: %u\n", ninplace);
328 fprintf(fp, " number of realloc'ing growths: %u\n", nreallocs);
329 fprintf(fp, "number of released allocations: %u\n", nreleases);
330 fprintf(fp, " number of fast releases: %u\n", nfastrels);
331 fprintf(fp, " total bytes allocated: %u\n", unsigned(nbytes));
332 fprintf(fp, " mean allocation size: %g\n", mean);
333 fprintf(fp, " standard deviation: %g\n", sigma);
334 fprintf(fp, " maximum allocation size: %u\n", unsigned(maxalloc));
336 #endif /* DEBUG */
338 /* Backwards compatibility. */
340 JS_FRIEND_API(void *)
341 JS_ARENA_MARK(const JSArenaPool *pool)
343 return pool->getMark();
346 JS_FRIEND_API(void)
347 JS_ARENA_RELEASE(JSArenaPool *pool, void *mark)
349 pool->release(mark);
352 JS_FRIEND_API(void *)
353 JS_ARENA_ALLOCATE_COMMON_SANE(jsuword p, JSArenaPool *pool, size_t nb, bool limitCheck)
355 return pool->allocate(nb, limitCheck);