michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * vim: set ts=8 sts=4 et sw=4 tw=99: michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "gc/Tracer.h" michael@0: michael@0: #include "mozilla/DebugOnly.h" michael@0: michael@0: #include "jsapi.h" michael@0: #include "jsfun.h" michael@0: #include "jsgc.h" michael@0: #include "jsprf.h" michael@0: #include "jsscript.h" michael@0: #include "jsutil.h" michael@0: #include "NamespaceImports.h" michael@0: michael@0: #include "gc/GCInternals.h" michael@0: #include "gc/Marking.h" michael@0: michael@0: #include "jsgcinlines.h" michael@0: michael@0: using namespace js; michael@0: using namespace js::gc; michael@0: using mozilla::DebugOnly; michael@0: michael@0: JS_PUBLIC_API(void) michael@0: JS_CallValueTracer(JSTracer *trc, Value *valuep, const char *name) michael@0: { michael@0: MarkValueUnbarriered(trc, valuep, name); michael@0: } michael@0: michael@0: JS_PUBLIC_API(void) michael@0: JS_CallIdTracer(JSTracer *trc, jsid *idp, const char *name) michael@0: { michael@0: MarkIdUnbarriered(trc, idp, name); michael@0: } michael@0: michael@0: JS_PUBLIC_API(void) michael@0: JS_CallObjectTracer(JSTracer *trc, JSObject **objp, const char *name) michael@0: { michael@0: MarkObjectUnbarriered(trc, objp, name); michael@0: } michael@0: michael@0: JS_PUBLIC_API(void) michael@0: JS_CallStringTracer(JSTracer *trc, JSString **strp, const char *name) michael@0: { michael@0: MarkStringUnbarriered(trc, strp, name); michael@0: } michael@0: michael@0: JS_PUBLIC_API(void) michael@0: JS_CallScriptTracer(JSTracer *trc, JSScript **scriptp, const char *name) michael@0: { michael@0: MarkScriptUnbarriered(trc, scriptp, name); michael@0: } michael@0: michael@0: JS_PUBLIC_API(void) michael@0: JS_CallHeapValueTracer(JSTracer *trc, JS::Heap *valuep, const char *name) michael@0: { michael@0: MarkValueUnbarriered(trc, valuep->unsafeGet(), name); michael@0: } michael@0: michael@0: JS_PUBLIC_API(void) michael@0: JS_CallHeapIdTracer(JSTracer *trc, JS::Heap *idp, const char *name) michael@0: { michael@0: MarkIdUnbarriered(trc, idp->unsafeGet(), name); michael@0: } michael@0: michael@0: JS_PUBLIC_API(void) michael@0: JS_CallHeapObjectTracer(JSTracer *trc, JS::Heap *objp, const char *name) michael@0: { michael@0: MarkObjectUnbarriered(trc, objp->unsafeGet(), name); michael@0: } michael@0: michael@0: JS_PUBLIC_API(void) michael@0: JS_CallHeapStringTracer(JSTracer *trc, JS::Heap *strp, const char *name) michael@0: { michael@0: MarkStringUnbarriered(trc, strp->unsafeGet(), name); michael@0: } michael@0: michael@0: JS_PUBLIC_API(void) michael@0: JS_CallHeapScriptTracer(JSTracer *trc, JS::Heap *scriptp, const char *name) michael@0: { michael@0: MarkScriptUnbarriered(trc, scriptp->unsafeGet(), name); michael@0: } michael@0: michael@0: JS_PUBLIC_API(void) michael@0: JS_CallHeapFunctionTracer(JSTracer *trc, JS::Heap *funp, const char *name) michael@0: { michael@0: MarkObjectUnbarriered(trc, funp->unsafeGet(), name); michael@0: } michael@0: michael@0: JS_PUBLIC_API(void) michael@0: JS_CallTenuredObjectTracer(JSTracer *trc, JS::TenuredHeap *objp, const char *name) michael@0: { michael@0: JSObject *obj = objp->getPtr(); michael@0: if (!obj) michael@0: return; michael@0: michael@0: trc->setTracingLocation((void*)objp); michael@0: MarkObjectUnbarriered(trc, &obj, name); michael@0: michael@0: objp->setPtr(obj); michael@0: } michael@0: michael@0: JS_PUBLIC_API(void) michael@0: JS_TraceChildren(JSTracer *trc, void *thing, JSGCTraceKind kind) michael@0: { michael@0: js::TraceChildren(trc, thing, kind); michael@0: } michael@0: michael@0: JS_PUBLIC_API(void) michael@0: JS_TraceRuntime(JSTracer *trc) michael@0: { michael@0: AssertHeapIsIdle(trc->runtime()); michael@0: TraceRuntime(trc); michael@0: } michael@0: michael@0: static size_t michael@0: CountDecimalDigits(size_t num) michael@0: { michael@0: size_t numDigits = 0; michael@0: do { michael@0: num /= 10; michael@0: numDigits++; michael@0: } while (num > 0); michael@0: michael@0: return numDigits; michael@0: } michael@0: michael@0: JS_PUBLIC_API(void) michael@0: JS_GetTraceThingInfo(char *buf, size_t bufsize, JSTracer *trc, void *thing, michael@0: JSGCTraceKind kind, bool details) michael@0: { michael@0: const char *name = nullptr; /* silence uninitialized warning */ michael@0: size_t n; michael@0: michael@0: if (bufsize == 0) michael@0: return; michael@0: michael@0: switch (kind) { michael@0: case JSTRACE_OBJECT: michael@0: { michael@0: name = static_cast(thing)->getClass()->name; michael@0: break; michael@0: } michael@0: michael@0: case JSTRACE_STRING: michael@0: name = ((JSString *)thing)->isDependent() michael@0: ? "substring" michael@0: : "string"; michael@0: break; michael@0: michael@0: case JSTRACE_SCRIPT: michael@0: name = "script"; michael@0: break; michael@0: michael@0: case JSTRACE_LAZY_SCRIPT: michael@0: name = "lazyscript"; michael@0: break; michael@0: michael@0: case JSTRACE_JITCODE: michael@0: name = "jitcode"; michael@0: break; michael@0: michael@0: case JSTRACE_SHAPE: michael@0: name = "shape"; michael@0: break; michael@0: michael@0: case JSTRACE_BASE_SHAPE: michael@0: name = "base_shape"; michael@0: break; michael@0: michael@0: case JSTRACE_TYPE_OBJECT: michael@0: name = "type_object"; michael@0: break; michael@0: } michael@0: michael@0: n = strlen(name); michael@0: if (n > bufsize - 1) michael@0: n = bufsize - 1; michael@0: js_memcpy(buf, name, n + 1); michael@0: buf += n; michael@0: bufsize -= n; michael@0: *buf = '\0'; michael@0: michael@0: if (details && bufsize > 2) { michael@0: switch (kind) { michael@0: case JSTRACE_OBJECT: michael@0: { michael@0: JSObject *obj = (JSObject *)thing; michael@0: if (obj->is()) { michael@0: JSFunction *fun = &obj->as(); michael@0: if (fun->displayAtom()) { michael@0: *buf++ = ' '; michael@0: bufsize--; michael@0: PutEscapedString(buf, bufsize, fun->displayAtom(), 0); michael@0: } michael@0: } else if (obj->getClass()->flags & JSCLASS_HAS_PRIVATE) { michael@0: JS_snprintf(buf, bufsize, " %p", obj->getPrivate()); michael@0: } else { michael@0: JS_snprintf(buf, bufsize, " "); michael@0: } michael@0: break; michael@0: } michael@0: michael@0: case JSTRACE_STRING: michael@0: { michael@0: *buf++ = ' '; michael@0: bufsize--; michael@0: JSString *str = (JSString *)thing; michael@0: michael@0: if (str->isLinear()) { michael@0: bool willFit = str->length() + strlen(" ") + michael@0: CountDecimalDigits(str->length()) < bufsize; michael@0: michael@0: n = JS_snprintf(buf, bufsize, " ", michael@0: (int)str->length(), michael@0: willFit ? "" : " (truncated)"); michael@0: buf += n; michael@0: bufsize -= n; michael@0: michael@0: PutEscapedString(buf, bufsize, &str->asLinear(), 0); michael@0: } michael@0: else michael@0: JS_snprintf(buf, bufsize, "", (int)str->length()); michael@0: break; michael@0: } michael@0: michael@0: case JSTRACE_SCRIPT: michael@0: { michael@0: JSScript *script = static_cast(thing); michael@0: JS_snprintf(buf, bufsize, " %s:%u", script->filename(), unsigned(script->lineno())); michael@0: break; michael@0: } michael@0: michael@0: case JSTRACE_LAZY_SCRIPT: michael@0: case JSTRACE_JITCODE: michael@0: case JSTRACE_SHAPE: michael@0: case JSTRACE_BASE_SHAPE: michael@0: case JSTRACE_TYPE_OBJECT: michael@0: break; michael@0: } michael@0: } michael@0: buf[bufsize - 1] = '\0'; michael@0: } michael@0: michael@0: JSTracer::JSTracer(JSRuntime *rt, JSTraceCallback traceCallback, michael@0: WeakMapTraceKind weakTraceKind /* = TraceWeakMapValues */) michael@0: : callback(traceCallback) michael@0: , runtime_(rt) michael@0: , debugPrinter_(nullptr) michael@0: , debugPrintArg_(nullptr) michael@0: , debugPrintIndex_(size_t(-1)) michael@0: , eagerlyTraceWeakMaps_(weakTraceKind) michael@0: #ifdef JS_GC_ZEAL michael@0: , realLocation_(nullptr) michael@0: #endif michael@0: { michael@0: } michael@0: michael@0: bool michael@0: JSTracer::hasTracingDetails() const michael@0: { michael@0: return debugPrinter_ || debugPrintArg_; michael@0: } michael@0: michael@0: const char * michael@0: JSTracer::tracingName(const char *fallback) const michael@0: { michael@0: JS_ASSERT(hasTracingDetails()); michael@0: return debugPrinter_ ? fallback : (const char *)debugPrintArg_; michael@0: } michael@0: michael@0: const char * michael@0: JSTracer::getTracingEdgeName(char *buffer, size_t bufferSize) michael@0: { michael@0: if (debugPrinter_) { michael@0: debugPrinter_(this, buffer, bufferSize); michael@0: return buffer; michael@0: } michael@0: if (debugPrintIndex_ != size_t(-1)) { michael@0: JS_snprintf(buffer, bufferSize, "%s[%lu]", michael@0: (const char *)debugPrintArg_, michael@0: debugPrintIndex_); michael@0: return buffer; michael@0: } michael@0: return (const char*)debugPrintArg_; michael@0: } michael@0: michael@0: JSTraceNamePrinter michael@0: JSTracer::debugPrinter() const michael@0: { michael@0: return debugPrinter_; michael@0: } michael@0: michael@0: const void * michael@0: JSTracer::debugPrintArg() const michael@0: { michael@0: return debugPrintArg_; michael@0: } michael@0: michael@0: size_t michael@0: JSTracer::debugPrintIndex() const michael@0: { michael@0: return debugPrintIndex_; michael@0: } michael@0: michael@0: void michael@0: JSTracer::setTraceCallback(JSTraceCallback traceCallback) michael@0: { michael@0: callback = traceCallback; michael@0: } michael@0: michael@0: #ifdef JS_GC_ZEAL michael@0: void michael@0: JSTracer::setTracingLocation(void *location) michael@0: { michael@0: if (!realLocation_ || !location) michael@0: realLocation_ = location; michael@0: } michael@0: michael@0: void michael@0: JSTracer::unsetTracingLocation() michael@0: { michael@0: realLocation_ = nullptr; michael@0: } michael@0: michael@0: void ** michael@0: JSTracer::tracingLocation(void **thingp) michael@0: { michael@0: return realLocation_ ? (void **)realLocation_ : thingp; michael@0: } michael@0: #endif michael@0: michael@0: bool michael@0: MarkStack::init(JSGCMode gcMode) michael@0: { michael@0: setBaseCapacity(gcMode); michael@0: michael@0: JS_ASSERT(!stack_); michael@0: uintptr_t *newStack = js_pod_malloc(baseCapacity_); michael@0: if (!newStack) michael@0: return false; michael@0: michael@0: setStack(newStack, 0, baseCapacity_); michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: MarkStack::setBaseCapacity(JSGCMode mode) michael@0: { michael@0: switch (mode) { michael@0: case JSGC_MODE_GLOBAL: michael@0: case JSGC_MODE_COMPARTMENT: michael@0: baseCapacity_ = NON_INCREMENTAL_MARK_STACK_BASE_CAPACITY; michael@0: break; michael@0: case JSGC_MODE_INCREMENTAL: michael@0: baseCapacity_ = INCREMENTAL_MARK_STACK_BASE_CAPACITY; michael@0: break; michael@0: default: michael@0: MOZ_ASSUME_UNREACHABLE("bad gc mode"); michael@0: } michael@0: michael@0: if (baseCapacity_ > maxCapacity_) michael@0: baseCapacity_ = maxCapacity_; michael@0: } michael@0: michael@0: void michael@0: MarkStack::setMaxCapacity(size_t maxCapacity) michael@0: { michael@0: JS_ASSERT(isEmpty()); michael@0: maxCapacity_ = maxCapacity; michael@0: if (baseCapacity_ > maxCapacity_) michael@0: baseCapacity_ = maxCapacity_; michael@0: michael@0: reset(); michael@0: } michael@0: michael@0: void michael@0: MarkStack::reset() michael@0: { michael@0: if (capacity() == baseCapacity_) { michael@0: // No size change; keep the current stack. michael@0: setStack(stack_, 0, baseCapacity_); michael@0: return; michael@0: } michael@0: michael@0: uintptr_t *newStack = (uintptr_t *)js_realloc(stack_, sizeof(uintptr_t) * baseCapacity_); michael@0: if (!newStack) { michael@0: // If the realloc fails, just keep using the existing stack; it's michael@0: // not ideal but better than failing. michael@0: newStack = stack_; michael@0: baseCapacity_ = capacity(); michael@0: } michael@0: setStack(newStack, 0, baseCapacity_); michael@0: } michael@0: michael@0: bool michael@0: MarkStack::enlarge(unsigned count) michael@0: { michael@0: size_t newCapacity = Min(maxCapacity_, capacity() * 2); michael@0: if (newCapacity < capacity() + count) michael@0: return false; michael@0: michael@0: size_t tosIndex = position(); michael@0: michael@0: uintptr_t *newStack = (uintptr_t *)js_realloc(stack_, sizeof(uintptr_t) * newCapacity); michael@0: if (!newStack) michael@0: return false; michael@0: michael@0: setStack(newStack, tosIndex, newCapacity); michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: MarkStack::setGCMode(JSGCMode gcMode) michael@0: { michael@0: // The mark stack won't be resized until the next call to reset(), but michael@0: // that will happen at the end of the next GC. michael@0: setBaseCapacity(gcMode); michael@0: } michael@0: michael@0: size_t michael@0: MarkStack::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const michael@0: { michael@0: return mallocSizeOf(stack_); michael@0: } michael@0: michael@0: /* michael@0: * DoNotTraceWeakMaps: the GC is recomputing the liveness of WeakMap entries, michael@0: * so we delay visting entries. michael@0: */ michael@0: GCMarker::GCMarker(JSRuntime *rt) michael@0: : JSTracer(rt, nullptr, DoNotTraceWeakMaps), michael@0: stack(size_t(-1)), michael@0: color(BLACK), michael@0: unmarkedArenaStackTop(nullptr), michael@0: markLaterArenas(0), michael@0: grayBufferState(GRAY_BUFFER_UNUSED), michael@0: started(false) michael@0: { michael@0: } michael@0: michael@0: bool michael@0: GCMarker::init(JSGCMode gcMode) michael@0: { michael@0: return stack.init(gcMode); michael@0: } michael@0: michael@0: void michael@0: GCMarker::start() michael@0: { michael@0: JS_ASSERT(!started); michael@0: started = true; michael@0: color = BLACK; michael@0: michael@0: JS_ASSERT(!unmarkedArenaStackTop); michael@0: JS_ASSERT(markLaterArenas == 0); michael@0: michael@0: } michael@0: michael@0: void michael@0: GCMarker::stop() michael@0: { michael@0: JS_ASSERT(isDrained()); michael@0: michael@0: JS_ASSERT(started); michael@0: started = false; michael@0: michael@0: JS_ASSERT(!unmarkedArenaStackTop); michael@0: JS_ASSERT(markLaterArenas == 0); michael@0: michael@0: /* Free non-ballast stack memory. */ michael@0: stack.reset(); michael@0: michael@0: resetBufferedGrayRoots(); michael@0: grayBufferState = GRAY_BUFFER_UNUSED; michael@0: } michael@0: michael@0: void michael@0: GCMarker::reset() michael@0: { michael@0: color = BLACK; michael@0: michael@0: stack.reset(); michael@0: JS_ASSERT(isMarkStackEmpty()); michael@0: michael@0: while (unmarkedArenaStackTop) { michael@0: ArenaHeader *aheader = unmarkedArenaStackTop; michael@0: JS_ASSERT(aheader->hasDelayedMarking); michael@0: JS_ASSERT(markLaterArenas); michael@0: unmarkedArenaStackTop = aheader->getNextDelayedMarking(); michael@0: aheader->unsetDelayedMarking(); michael@0: aheader->markOverflow = 0; michael@0: aheader->allocatedDuringIncremental = 0; michael@0: markLaterArenas--; michael@0: } michael@0: JS_ASSERT(isDrained()); michael@0: JS_ASSERT(!markLaterArenas); michael@0: } michael@0: michael@0: void michael@0: GCMarker::markDelayedChildren(ArenaHeader *aheader) michael@0: { michael@0: if (aheader->markOverflow) { michael@0: bool always = aheader->allocatedDuringIncremental; michael@0: aheader->markOverflow = 0; michael@0: michael@0: for (CellIterUnderGC i(aheader); !i.done(); i.next()) { michael@0: Cell *t = i.getCell(); michael@0: if (always || t->isMarked()) { michael@0: t->markIfUnmarked(); michael@0: JS_TraceChildren(this, t, MapAllocToTraceKind(aheader->getAllocKind())); michael@0: } michael@0: } michael@0: } else { michael@0: JS_ASSERT(aheader->allocatedDuringIncremental); michael@0: PushArena(this, aheader); michael@0: } michael@0: aheader->allocatedDuringIncremental = 0; michael@0: /* michael@0: * Note that during an incremental GC we may still be allocating into michael@0: * aheader. However, prepareForIncrementalGC sets the michael@0: * allocatedDuringIncremental flag if we continue marking. michael@0: */ michael@0: } michael@0: michael@0: bool michael@0: GCMarker::markDelayedChildren(SliceBudget &budget) michael@0: { michael@0: gcstats::MaybeAutoPhase ap; michael@0: if (runtime()->gcIncrementalState == MARK) michael@0: ap.construct(runtime()->gcStats, gcstats::PHASE_MARK_DELAYED); michael@0: michael@0: JS_ASSERT(unmarkedArenaStackTop); michael@0: do { michael@0: /* michael@0: * If marking gets delayed at the same arena again, we must repeat michael@0: * marking of its things. For that we pop arena from the stack and michael@0: * clear its hasDelayedMarking flag before we begin the marking. michael@0: */ michael@0: ArenaHeader *aheader = unmarkedArenaStackTop; michael@0: JS_ASSERT(aheader->hasDelayedMarking); michael@0: JS_ASSERT(markLaterArenas); michael@0: unmarkedArenaStackTop = aheader->getNextDelayedMarking(); michael@0: aheader->unsetDelayedMarking(); michael@0: markLaterArenas--; michael@0: markDelayedChildren(aheader); michael@0: michael@0: budget.step(150); michael@0: if (budget.isOverBudget()) michael@0: return false; michael@0: } while (unmarkedArenaStackTop); michael@0: JS_ASSERT(!markLaterArenas); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: void michael@0: GCMarker::checkZone(void *p) michael@0: { michael@0: JS_ASSERT(started); michael@0: DebugOnly cell = static_cast(p); michael@0: JS_ASSERT_IF(cell->isTenured(), cell->tenuredZone()->isCollecting()); michael@0: } michael@0: #endif michael@0: michael@0: bool michael@0: GCMarker::hasBufferedGrayRoots() const michael@0: { michael@0: return grayBufferState == GRAY_BUFFER_OK; michael@0: } michael@0: michael@0: void michael@0: GCMarker::startBufferingGrayRoots() michael@0: { michael@0: JS_ASSERT(grayBufferState == GRAY_BUFFER_UNUSED); michael@0: grayBufferState = GRAY_BUFFER_OK; michael@0: for (GCZonesIter zone(runtime()); !zone.done(); zone.next()) michael@0: JS_ASSERT(zone->gcGrayRoots.empty()); michael@0: michael@0: JS_ASSERT(!callback); michael@0: callback = GrayCallback; michael@0: JS_ASSERT(IS_GC_MARKING_TRACER(this)); michael@0: } michael@0: michael@0: void michael@0: GCMarker::endBufferingGrayRoots() michael@0: { michael@0: JS_ASSERT(callback == GrayCallback); michael@0: callback = nullptr; michael@0: JS_ASSERT(IS_GC_MARKING_TRACER(this)); michael@0: JS_ASSERT(grayBufferState == GRAY_BUFFER_OK || michael@0: grayBufferState == GRAY_BUFFER_FAILED); michael@0: } michael@0: michael@0: void michael@0: GCMarker::resetBufferedGrayRoots() michael@0: { michael@0: for (GCZonesIter zone(runtime()); !zone.done(); zone.next()) michael@0: zone->gcGrayRoots.clearAndFree(); michael@0: } michael@0: michael@0: void michael@0: GCMarker::markBufferedGrayRoots(JS::Zone *zone) michael@0: { michael@0: JS_ASSERT(grayBufferState == GRAY_BUFFER_OK); michael@0: JS_ASSERT(zone->isGCMarkingGray()); michael@0: michael@0: for (GrayRoot *elem = zone->gcGrayRoots.begin(); elem != zone->gcGrayRoots.end(); elem++) { michael@0: #ifdef DEBUG michael@0: setTracingDetails(elem->debugPrinter, elem->debugPrintArg, elem->debugPrintIndex); michael@0: #endif michael@0: void *tmp = elem->thing; michael@0: setTracingLocation((void *)&elem->thing); michael@0: MarkKind(this, &tmp, elem->kind); michael@0: JS_ASSERT(tmp == elem->thing); michael@0: } michael@0: } michael@0: michael@0: void michael@0: GCMarker::appendGrayRoot(void *thing, JSGCTraceKind kind) michael@0: { michael@0: JS_ASSERT(started); michael@0: michael@0: if (grayBufferState == GRAY_BUFFER_FAILED) michael@0: return; michael@0: michael@0: GrayRoot root(thing, kind); michael@0: #ifdef DEBUG michael@0: root.debugPrinter = debugPrinter(); michael@0: root.debugPrintArg = debugPrintArg(); michael@0: root.debugPrintIndex = debugPrintIndex(); michael@0: #endif michael@0: michael@0: Zone *zone = static_cast(thing)->tenuredZone(); michael@0: if (zone->isCollecting()) { michael@0: zone->maybeAlive = true; michael@0: if (!zone->gcGrayRoots.append(root)) { michael@0: resetBufferedGrayRoots(); michael@0: grayBufferState = GRAY_BUFFER_FAILED; michael@0: } michael@0: } michael@0: } michael@0: michael@0: void michael@0: GCMarker::GrayCallback(JSTracer *trc, void **thingp, JSGCTraceKind kind) michael@0: { michael@0: JS_ASSERT(thingp); michael@0: JS_ASSERT(*thingp); michael@0: GCMarker *gcmarker = static_cast(trc); michael@0: gcmarker->appendGrayRoot(*thingp, kind); michael@0: } michael@0: michael@0: size_t michael@0: GCMarker::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const michael@0: { michael@0: size_t size = stack.sizeOfExcludingThis(mallocSizeOf); michael@0: for (ZonesIter zone(runtime(), WithAtoms); !zone.done(); zone.next()) michael@0: size += zone->gcGrayRoots.sizeOfExcludingThis(mallocSizeOf); michael@0: return size; michael@0: } michael@0: michael@0: void michael@0: js::SetMarkStackLimit(JSRuntime *rt, size_t limit) michael@0: { michael@0: JS_ASSERT(!rt->isHeapBusy()); michael@0: AutoStopVerifyingBarriers pauseVerification(rt, false); michael@0: rt->gcMarker.setMaxCapacity(limit); michael@0: } michael@0: