From d3e5fc8cf6488c147e74338ce7228c0a0afef0ab Mon Sep 17 00:00:00 2001 From: Roy Tam Date: Tue, 7 May 2019 21:18:18 +0800 Subject: [PATCH] import change from rmottola/Arctic-Fox: - more pointerstyle to apply patches (bfb888a02) - Bug 1144331 - Assert that gray buffering does not depend on isMarking (775d1d6b3) - Bug 1144789 - Strongly type GrayBufferState enum and move to GCRuntime (48db96a71) - pointer style (8e1f6b47f) - Bug 1144794 - Move markBufferedGrayRoots to the GCRuntime (82a65b5d4) - Bug 1144811 - Inline the start and end buffering gray roots methods on GCMarker (e5fa2fa45) - Bug 1144817 - Move hasBufferedGrayRoots to GCRuntime (360528a61) - Bug 1144832 - Move grayBufferState manipulation out of GCMarker (aae607d5b) - Bug 1144834 - Move resetBufferedGrayRoots to GCRuntime (c80e490e3) - Bug 1144920 - Move gray buffer clearing out of GCMarker (99495ce33) - Bug 1144925 - Buffer gray roots using a CallbackTracer instead of the GCMarker (faae3bca3) - Bug 1144931 - Move gray buffering code to RootMarking.cpp (c279e36bd) - Bug 1144369 - Add a GC phase to track time spent buffering gray roots (e05c2eece) - Bug 1142669 part 1 - Fix inliningMaxCallerBytecodeLength to return the correct value. (d5e4d1a84) - Bug 1129977 - Fix bogus MarkOffThreadNurseryObjects assert when post-barrier verifier is used. (4d204fb5e) - pointer style (8a3a7e129) - pointer style (f6db66131) - Bug 1142669 part 2 - Lower the script inlining size limit if off-thread compilation is not available. (ce4e3c5e6) - Bug 1142669 part 3 - Limit the total inlined bytecode size to avoid excessive inlining. (a57fab6e2) - Bug 1142669 part 4 - Fix some inlining issues and inline scripts with loops. (777fb2ec6) - Bug 1142669 followup - Move OffThreadCompilationAvailable definition outside namespace block. (c4fd10799) - Bug 1142669 part 5 - Lower inliningMaxCallerBytecodeLength from 10000 to 1500. (9f1c704a2) - pointer style (d70a2a7be) - Bug 1144743 part 1. Add a hasPollutedGlobalScope flag to scripts. (da965507f) - Bug 1144743 part 2. Add an option to JS::CompileOptions for hasPollutedGlobalScope. (df6324dd4) - Bug 1144743 part 3. Set hasPollutedGlobalScope when we're compiling scripts we know will be executed with a non-global scope without cloning them. (c2f264683) - Bug 1144743 part 4. Set the hasPollutedGlobalScope flag correctly when compiling functions. (39fff6585) - Bug 1144743 part 5. Set the hasPollutedGlobalScope flag correctly when cloning functions. (738f1d18a) - Bug 1144743 part 6. Set the hasPollutedGlobalScope flag correctly when executing scripts. (b05d04d63) --- dom/base/nsFrameMessageManager.cpp | 5 +- js/public/TrackedOptimizationInfo.h | 4 +- js/src/builtin/Eval.cpp | 4 + js/src/builtin/TestingFunctions.cpp | 1 + js/src/frontend/BytecodeCompiler.cpp | 1 + js/src/frontend/BytecodeEmitter.cpp | 1 + js/src/gc/GCRuntime.h | 24 ++++ js/src/gc/Iteration.cpp | 2 +- js/src/gc/Marking.cpp | 37 ++--- js/src/gc/RootMarking.cpp | 80 ++++++++++- js/src/gc/Statistics.cpp | 1 + js/src/gc/Statistics.h | 1 + js/src/gc/Tracer.cpp | 104 +------------- js/src/gc/Tracer.h | 72 +++++----- js/src/jit-test/tests/ion/bug1129977.js | 10 ++ js/src/jit/BaselineJIT.cpp | 3 +- js/src/jit/BaselineJIT.h | 18 +++ js/src/jit/CodeGenerator.cpp | 4 +- js/src/jit/CompileWrappers.cpp | 4 +- js/src/jit/CompileWrappers.h | 5 + js/src/jit/Ion.cpp | 44 +++--- js/src/jit/Ion.h | 16 ++- js/src/jit/IonBuilder.cpp | 135 +++++++++++-------- js/src/jit/IonBuilder.h | 4 + js/src/jit/IonOptimizationLevels.cpp | 6 +- js/src/jit/IonOptimizationLevels.h | 16 ++- js/src/jit/JitFrames.cpp | 20 +-- js/src/jit/JitFrames.h | 14 +- js/src/jit/JitOptions.cpp | 4 - js/src/jit/MacroAssembler.h | 2 +- js/src/jsapi.cpp | 40 +++--- js/src/jsapi.h | 12 +- js/src/jsfun.cpp | 56 +++++--- js/src/jsfun.h | 7 +- js/src/jsgc.cpp | 15 ++- js/src/jsobj.cpp | 2 +- js/src/jsscript.cpp | 44 ++++-- js/src/jsscript.h | 40 ++++-- js/src/jsweakmap.cpp | 4 +- js/src/vm/Debugger.cpp | 1 + js/src/vm/Interpreter.cpp | 8 ++ js/src/vm/RegExpObject.cpp | 8 +- js/src/vm/Stack.cpp | 7 +- js/src/vm/Xdr.h | 2 +- js/xpconnect/loader/mozJSSubScriptLoader.cpp | 4 +- 45 files changed, 542 insertions(+), 350 deletions(-) create mode 100644 js/src/jit-test/tests/ion/bug1129977.js diff --git a/dom/base/nsFrameMessageManager.cpp b/dom/base/nsFrameMessageManager.cpp index 02dee3d712..aa23f85bb7 100644 --- a/dom/base/nsFrameMessageManager.cpp +++ b/dom/base/nsFrameMessageManager.cpp @@ -1607,8 +1607,9 @@ nsMessageManagerScriptExecutor::TryCacheLoadAndCompileScript( return; } } else { - // We can't clone compile-and-go scripts. - options.setCompileAndGo(false); + // We're going to run these against some non-global scope. + options.setCompileAndGo(false) + .setHasPollutedScope(true); if (!JS::Compile(cx, options, srcBuf, &script)) { return; } diff --git a/js/public/TrackedOptimizationInfo.h b/js/public/TrackedOptimizationInfo.h index ad347bbb2c..c3a06b062f 100644 --- a/js/public/TrackedOptimizationInfo.h +++ b/js/public/TrackedOptimizationInfo.h @@ -205,8 +205,8 @@ namespace JS { "can't inline: type has unknown properties") \ _(CantInlineExceededDepth, \ "can't inline: exceeded inlining depth") \ - _(CantInlineBigLoop, \ - "can't inline: big function with a loop") \ + _(CantInlineExceededTotalBytecodeLength, \ + "can't inline: exceeded max total bytecode length") \ _(CantInlineBigCaller, \ "can't inline: big caller") \ _(CantInlineBigCallee, \ diff --git a/js/src/builtin/Eval.cpp b/js/src/builtin/Eval.cpp index d62347f48a..53ab5dcabe 100644 --- a/js/src/builtin/Eval.cpp +++ b/js/src/builtin/Eval.cpp @@ -317,6 +317,8 @@ EvalKernel(JSContext* cx, const CallArgs& args, EvalType evalType, AbstractFrame CompileOptions options(cx); options.setFileAndLine(filename, 1) .setCompileAndGo(true) + .setHasPollutedScope(evalType == DIRECT_EVAL && + callerScript->hasPollutedGlobalScope()) .setForEval(true) .setNoScriptRval(false) .setMutedErrors(mutedErrors) @@ -399,6 +401,7 @@ js::DirectEvalStringFromIon(JSContext* cx, CompileOptions options(cx); options.setFileAndLine(filename, 1) .setCompileAndGo(true) + .setHasPollutedScope(callerScript->hasPollutedGlobalScope()) .setForEval(true) .setNoScriptRval(false) .setMutedErrors(mutedErrors) @@ -502,6 +505,7 @@ js::ExecuteInGlobalAndReturnScope(JSContext* cx, HandleObject global, HandleScri CHECK_REQUEST(cx); assertSameCompartment(cx, global); MOZ_ASSERT(global->is()); + MOZ_RELEASE_ASSERT(scriptArg->hasPollutedGlobalScope()); RootedScript script(cx, scriptArg); if (script->compartment() != cx->compartment()) { diff --git a/js/src/builtin/TestingFunctions.cpp b/js/src/builtin/TestingFunctions.cpp index 305ed44150..35c6945145 100644 --- a/js/src/builtin/TestingFunctions.cpp +++ b/js/src/builtin/TestingFunctions.cpp @@ -2271,6 +2271,7 @@ EvalReturningScope(JSContext* cx, unsigned argc, jsval* vp) options.setFileAndLine(filename.get(), lineno); options.setNoScriptRval(true); options.setCompileAndGo(false); + options.setHasPollutedScope(true); JS::SourceBufferHolder srcBuf(src, srclen, JS::SourceBufferHolder::NoOwnership); RootedScript script(cx); diff --git a/js/src/frontend/BytecodeCompiler.cpp b/js/src/frontend/BytecodeCompiler.cpp index 6fabccde43..78fd0717c5 100644 --- a/js/src/frontend/BytecodeCompiler.cpp +++ b/js/src/frontend/BytecodeCompiler.cpp @@ -134,6 +134,7 @@ CanLazilyParse(ExclusiveContext* cx, const ReadOnlyCompileOptions& options) { return options.canLazilyParse && options.compileAndGo && + !options.hasPollutedGlobalScope && !cx->compartment()->options().discardSource() && !options.sourceIsLazy; } diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index 414eb648de..ef17a1a686 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -5408,6 +5408,7 @@ EmitFunc(ExclusiveContext *cx, BytecodeEmitter *bce, ParseNode *pn, bool needsPr CompileOptions options(cx, bce->parser->options()); options.setMutedErrors(parent->mutedErrors()) .setCompileAndGo(parent->compileAndGo()) + .setHasPollutedScope(parent->hasPollutedGlobalScope()) .setSelfHostingMode(parent->selfHosted()) .setNoScriptRval(false) .setForEval(false) diff --git a/js/src/gc/GCRuntime.h b/js/src/gc/GCRuntime.h index a623065550..be5874a442 100644 --- a/js/src/gc/GCRuntime.h +++ b/js/src/gc/GCRuntime.h @@ -894,6 +894,7 @@ class GCRuntime template void markWeakReferences(gcstats::Phase phase); void markWeakReferencesInCurrentGroup(gcstats::Phase phase); template void markGrayReferences(gcstats::Phase phase); + void markBufferedGrayRoots(JS::Zone *zone); void markGrayReferencesInCurrentGroup(gcstats::Phase phase); void markAllWeakReferences(gcstats::Phase phase); void markAllGrayReferences(gcstats::Phase phase); @@ -1007,6 +1008,29 @@ class GCRuntime /* During shutdown, the GC needs to clean up every possible object. */ bool cleanUpEverything; + // Gray marking must be done after all black marking is complete. However, + // we do not have write barriers on XPConnect roots. Therefore, XPConnect + // roots must be accumulated in the first slice of incremental GC. We + // accumulate these roots in each zone's gcGrayRoots vector and then mark + // them later, after black marking is complete for each compartment. This + // accumulation can fail, but in that case we switch to non-incremental GC. + enum class GrayBufferState { + Unused, + Okay, + Failed + }; + GrayBufferState grayBufferState; + bool hasBufferedGrayRoots() const { return grayBufferState == GrayBufferState::Okay; } + + // Clear each zone's gray buffers, but do not change the current state. + void resetBufferedGrayRoots() const; + + // Reset the gray buffering state to Unused. + void clearBufferedGrayRoots() { + grayBufferState = GrayBufferState::Unused; + resetBufferedGrayRoots(); + } + /* * The gray bits can become invalid if UnmarkGray overflows the stack. A * full GC will reset this bit, since it fills in all the gray bits. diff --git a/js/src/gc/Iteration.cpp b/js/src/gc/Iteration.cpp index e349b4bf5b..e029e6257f 100644 --- a/js/src/gc/Iteration.cpp +++ b/js/src/gc/Iteration.cpp @@ -20,7 +20,7 @@ using namespace js::gc; void js::TraceRuntime(JSTracer* trc) { - MOZ_ASSERT(!IS_GC_MARKING_TRACER(trc)); + MOZ_ASSERT(!IsMarkingTracer(trc)); JSRuntime* rt = trc->runtime(); rt->gc.evictNursery(); diff --git a/js/src/gc/Marking.cpp b/js/src/gc/Marking.cpp index f34a772bf2..d450cd276c 100644 --- a/js/src/gc/Marking.cpp +++ b/js/src/gc/Marking.cpp @@ -140,11 +140,11 @@ IsThingPoisoned(T *thing) } #endif -static GCMarker* -AsGCMarker(JSTracer* trc) +static GCMarker * +AsGCMarker(JSTracer *trc) { - MOZ_ASSERT(IS_GC_MARKING_TRACER(trc)); - return static_cast(trc); + MOZ_ASSERT(IsMarkingTracer(trc)); + return static_cast(trc); } template bool ThingIsPermanentAtom(T* thing) { return false; } @@ -202,16 +202,19 @@ CheckMarkedThing(JSTracer* trc, T** thingp) MOZ_ASSERT(zone->runtimeFromAnyThread() == trc->runtime()); MOZ_ASSERT(trc->hasTracingDetails()); - bool isGcMarkingTracer = IS_GC_MARKING_TRACER(trc); - - MOZ_ASSERT_IF(zone->requireGCTracer(), isGcMarkingTracer); - MOZ_ASSERT(thing->isAligned()); - MOZ_ASSERT(MapTypeToTraceKind::kind == GetGCThingTraceKind(thing)); + /* + * Do not check IsMarkingTracer directly -- it should only be used in paths + * where we cannot be the gray buffering tracer. + */ + bool isGcMarkingTracer = (trc->callback == nullptr); + + MOZ_ASSERT_IF(zone->requireGCTracer(), isGcMarkingTracer || IsBufferingGrayRoots(trc)); + if (isGcMarkingTracer) { - GCMarker* gcMarker = static_cast(trc); + GCMarker *gcMarker = static_cast(trc); MOZ_ASSERT_IF(gcMarker->shouldCheckCompartments(), zone->isCollecting() || rt->isAtomsZone(zone)); @@ -310,9 +313,9 @@ MarkInternal(JSTracer* trc, T** thingp) trc->clearTracingDetails(); } -#define JS_ROOT_MARKING_ASSERT(trc) \ - MOZ_ASSERT_IF(IS_GC_MARKING_TRACER(trc), \ - trc->runtime()->gc.state() == NO_INCREMENTAL || \ +#define JS_ROOT_MARKING_ASSERT(trc) \ + MOZ_ASSERT_IF(IsMarkingTracer(trc), \ + trc->runtime()->gc.state() == NO_INCREMENTAL || \ trc->runtime()->gc.state() == MARK_ROOTS); namespace js { @@ -949,9 +952,9 @@ gc::MarkObjectSlots(JSTracer* trc, NativeObject* obj, uint32_t start, uint32_t n } static bool -ShouldMarkCrossCompartment(JSTracer* trc, JSObject* src, Cell* cell) +ShouldMarkCrossCompartment(JSTracer *trc, JSObject *src, Cell *cell) { - if (!IS_GC_MARKING_TRACER(trc)) + if (!IsMarkingTracer(trc)) return true; uint32_t color = AsGCMarker(trc)->markColor(); @@ -961,9 +964,9 @@ ShouldMarkCrossCompartment(JSTracer* trc, JSObject* src, Cell* cell) MOZ_ASSERT(color == BLACK); return false; } - TenuredCell& tenured = cell->asTenured(); + TenuredCell &tenured = cell->asTenured(); - JS::Zone* zone = tenured.zone(); + JS::Zone *zone = tenured.zone(); if (color == BLACK) { /* * Having black->gray edges violates our promise to the cycle diff --git a/js/src/gc/RootMarking.cpp b/js/src/gc/RootMarking.cpp index 67d471a255..4890365e93 100644 --- a/js/src/gc/RootMarking.cpp +++ b/js/src/gc/RootMarking.cpp @@ -418,7 +418,6 @@ js::gc::GCRuntime::markRuntime(JSTracer* trc, { gcstats::AutoPhase ap(stats, gcstats::PHASE_MARK_ROOTS); - MOZ_ASSERT(trc->callback != GCMarker::GrayCallback); MOZ_ASSERT(traceOrMark == TraceRuntime || traceOrMark == MarkRuntime); MOZ_ASSERT(rootsSource == TraceRoots || rootsSource == UseSavedRoots); @@ -556,8 +555,81 @@ js::gc::GCRuntime::markRuntime(JSTracer* trc, void js::gc::GCRuntime::bufferGrayRoots() { - marker.startBufferingGrayRoots(); + // Precondition: the state has been reset to "unused" after the last GC + // and the zone's buffers have been cleared. + MOZ_ASSERT(grayBufferState == GrayBufferState::Unused); + for (GCZonesIter zone(rt); !zone.done(); zone.next()) + MOZ_ASSERT(zone->gcGrayRoots.empty()); + + + BufferGrayRootsTracer grayBufferer(rt); if (JSTraceDataOp op = grayRootTracer.op) - (*op)(&marker, grayRootTracer.data); - marker.endBufferingGrayRoots(); + (*op)(&grayBufferer, grayRootTracer.data); + + // Propagate the failure flag from the marker to the runtime. + if (grayBufferer.failed()) { + grayBufferState = GrayBufferState::Failed; + resetBufferedGrayRoots(); + } else { + grayBufferState = GrayBufferState::Okay; + } +} + +void +BufferGrayRootsTracer::appendGrayRoot(void *thing, JSGCTraceKind kind) +{ + MOZ_ASSERT(runtime()->isHeapBusy()); + + if (bufferingGrayRootsFailed) + return; + + GrayRoot root(thing, kind); +#ifdef DEBUG + root.debugPrinter = debugPrinter(); + root.debugPrintArg = debugPrintArg(); + root.debugPrintIndex = debugPrintIndex(); +#endif + + Zone *zone = TenuredCell::fromPointer(thing)->zone(); + if (zone->isCollecting()) { + // See the comment on SetMaybeAliveFlag to see why we only do this for + // objects and scripts. We rely on gray root buffering for this to work, + // but we only need to worry about uncollected dead compartments during + // incremental GCs (when we do gray root buffering). + switch (kind) { + case JSTRACE_OBJECT: + static_cast(thing)->compartment()->maybeAlive = true; + break; + case JSTRACE_SCRIPT: + static_cast(thing)->compartment()->maybeAlive = true; + break; + default: + break; + } + if (!zone->gcGrayRoots.append(root)) + bufferingGrayRootsFailed = true; + } +} + +void +GCRuntime::markBufferedGrayRoots(JS::Zone *zone) +{ + MOZ_ASSERT(grayBufferState == GrayBufferState::Okay); + MOZ_ASSERT(zone->isGCMarkingGray() || zone->isGCCompacting()); + + for (GrayRoot *elem = zone->gcGrayRoots.begin(); elem != zone->gcGrayRoots.end(); elem++) { +#ifdef DEBUG + marker.setTracingDetails(elem->debugPrinter, elem->debugPrintArg, elem->debugPrintIndex); +#endif + MarkKind(&marker, &elem->thing, elem->kind); + } +} + +void +GCRuntime::resetBufferedGrayRoots() const +{ + MOZ_ASSERT(grayBufferState != GrayBufferState::Okay, + "Do not clear the gray buffers unless we are Failed or becoming Unused"); + for (GCZonesIter zone(rt); !zone.done(); zone.next()) + zone->gcGrayRoots.clearAndFree(); } diff --git a/js/src/gc/Statistics.cpp b/js/src/gc/Statistics.cpp index 429c1340f8..794035ae5e 100644 --- a/js/src/gc/Statistics.cpp +++ b/js/src/gc/Statistics.cpp @@ -367,6 +367,7 @@ static const PhaseInfo phases[] = { { PHASE_TRACE_HEAP, "Trace Heap", PHASE_NO_PARENT }, /* PHASE_MARK_ROOTS */ { PHASE_MARK_ROOTS, "Mark Roots", PHASE_MULTI_PARENTS }, + { PHASE_BUFFER_GRAY_ROOTS, "Buffer Gray Roots", PHASE_MARK_ROOTS }, { PHASE_MARK_CCWS, "Mark Cross Compartment Wrappers", PHASE_MARK_ROOTS }, { PHASE_MARK_ROOTERS, "Mark Rooters", PHASE_MARK_ROOTS }, { PHASE_MARK_RUNTIME_DATA, "Mark Runtime-wide Data", PHASE_MARK_ROOTS }, diff --git a/js/src/gc/Statistics.h b/js/src/gc/Statistics.h index 304c15f03a..4fdd86cadc 100644 --- a/js/src/gc/Statistics.h +++ b/js/src/gc/Statistics.h @@ -75,6 +75,7 @@ enum Phase { PHASE_EVICT_NURSERY, PHASE_TRACE_HEAP, PHASE_MARK_ROOTS, + PHASE_BUFFER_GRAY_ROOTS, PHASE_MARK_CCWS, PHASE_MARK_ROOTERS, PHASE_MARK_RUNTIME_DATA, diff --git a/js/src/gc/Tracer.cpp b/js/src/gc/Tracer.cpp index ae7953805e..ed8e0ce250 100644 --- a/js/src/gc/Tracer.cpp +++ b/js/src/gc/Tracer.cpp @@ -510,7 +510,6 @@ GCMarker::GCMarker(JSRuntime* rt) color(BLACK), unmarkedArenaStackTop(nullptr), markLaterArenas(0), - grayBufferState(GRAY_BUFFER_UNUSED), started(false), strictCompartmentChecking(false) { @@ -547,9 +546,6 @@ GCMarker::stop() /* Free non-ballast stack memory. */ stack.reset(); - - resetBufferedGrayRoots(); - grayBufferState = GRAY_BUFFER_UNUSED; } void @@ -640,103 +636,6 @@ GCMarker::checkZone(void* p) } #endif -bool -GCMarker::hasBufferedGrayRoots() const -{ - return grayBufferState == GRAY_BUFFER_OK; -} - -void -GCMarker::startBufferingGrayRoots() -{ - MOZ_ASSERT(grayBufferState == GRAY_BUFFER_UNUSED); - grayBufferState = GRAY_BUFFER_OK; - for (GCZonesIter zone(runtime()); !zone.done(); zone.next()) - MOZ_ASSERT(zone->gcGrayRoots.empty()); - - MOZ_ASSERT(!callback); - callback = GrayCallback; - MOZ_ASSERT(IS_GC_MARKING_TRACER(this)); -} - -void -GCMarker::endBufferingGrayRoots() -{ - MOZ_ASSERT(callback == GrayCallback); - callback = nullptr; - MOZ_ASSERT(IS_GC_MARKING_TRACER(this)); - MOZ_ASSERT(grayBufferState == GRAY_BUFFER_OK || - grayBufferState == GRAY_BUFFER_FAILED); -} - -void -GCMarker::resetBufferedGrayRoots() -{ - for (GCZonesIter zone(runtime()); !zone.done(); zone.next()) - zone->gcGrayRoots.clearAndFree(); -} - -void -GCMarker::markBufferedGrayRoots(JS::Zone* zone) -{ - MOZ_ASSERT(grayBufferState == GRAY_BUFFER_OK); - MOZ_ASSERT(zone->isGCMarkingGray() || zone->isGCCompacting()); - - for (GrayRoot* elem = zone->gcGrayRoots.begin(); elem != zone->gcGrayRoots.end(); elem++) { -#ifdef DEBUG - setTracingDetails(elem->debugPrinter, elem->debugPrintArg, elem->debugPrintIndex); -#endif - MarkKind(this, &elem->thing, elem->kind); - } -} - -void -GCMarker::appendGrayRoot(void* thing, JSGCTraceKind kind) -{ - MOZ_ASSERT(started); - - if (grayBufferState == GRAY_BUFFER_FAILED) - return; - - GrayRoot root(thing, kind); -#ifdef DEBUG - root.debugPrinter = debugPrinter(); - root.debugPrintArg = debugPrintArg(); - root.debugPrintIndex = debugPrintIndex(); -#endif - - Zone* zone = TenuredCell::fromPointer(thing)->zone(); - if (zone->isCollecting()) { - // See the comment on SetMaybeAliveFlag to see why we only do this for - // objects and scripts. We rely on gray root buffering for this to work, - // but we only need to worry about uncollected dead compartments during - // incremental GCs (when we do gray root buffering). - switch (kind) { - case JSTRACE_OBJECT: - static_cast(thing)->compartment()->maybeAlive = true; - break; - case JSTRACE_SCRIPT: - static_cast(thing)->compartment()->maybeAlive = true; - break; - default: - break; - } - if (!zone->gcGrayRoots.append(root)) { - resetBufferedGrayRoots(); - grayBufferState = GRAY_BUFFER_FAILED; - } - } -} - -void -GCMarker::GrayCallback(JSTracer* trc, void** thingp, JSGCTraceKind kind) -{ - MOZ_ASSERT(thingp); - MOZ_ASSERT(*thingp); - GCMarker* gcmarker = static_cast(trc); - gcmarker->appendGrayRoot(*thingp, kind); -} - size_t GCMarker::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const { @@ -747,8 +646,7 @@ GCMarker::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const } void -js::SetMarkStackLimit(JSRuntime* rt, size_t limit) +js::SetMarkStackLimit(JSRuntime *rt, size_t limit) { rt->gc.setMarkStackLimit(limit); } - diff --git a/js/src/gc/Tracer.h b/js/src/gc/Tracer.h index 578ae07d7a..2f514bc9d7 100644 --- a/js/src/gc/Tracer.h +++ b/js/src/gc/Tracer.h @@ -201,23 +201,6 @@ class GCMarker : public JSTracer bool drainMarkStack(SliceBudget& budget); - /* - * Gray marking must be done after all black marking is complete. However, - * we do not have write barriers on XPConnect roots. Therefore, XPConnect - * roots must be accumulated in the first slice of incremental GC. We - * accumulate these roots in the each compartment's gcGrayRoots vector and - * then mark them later, after black marking is complete for each - * compartment. This accumulation can fail, but in that case we switch to - * non-incremental GC. - */ - bool hasBufferedGrayRoots() const; - void startBufferingGrayRoots(); - void endBufferingGrayRoots(); - void resetBufferedGrayRoots(); - void markBufferedGrayRoots(JS::Zone* zone); - - static void GrayCallback(JSTracer* trc, void** thing, JSGCTraceKind kind); - void setGCMode(JSGCMode mode) { stack.setGCMode(mode); } size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf) const; @@ -332,13 +315,6 @@ class GCMarker : public JSTracer /* Count of arenas that are currently in the stack. */ mozilla::DebugOnly markLaterArenas; - enum GrayBufferState { - GRAY_BUFFER_UNUSED, - GRAY_BUFFER_OK, - GRAY_BUFFER_FAILED - }; - GrayBufferState grayBufferState; - /* Assert that start and stop are called with correct ordering. */ mozilla::DebugOnly started; @@ -349,15 +325,49 @@ class GCMarker : public JSTracer mozilla::DebugOnly strictCompartmentChecking; }; +// Append traced things to a buffer on the zone for use later in the GC. +// See the comment in GCRuntime.h above grayBufferState for details. +class BufferGrayRootsTracer : public JSTracer +{ + // Set to false if we OOM while buffering gray roots. + bool bufferingGrayRootsFailed; + + void appendGrayRoot(void *thing, JSGCTraceKind kind); + + public: + explicit BufferGrayRootsTracer(JSRuntime *rt) + : JSTracer(rt, grayTraceCallback), bufferingGrayRootsFailed(false) + {} + + static void grayTraceCallback(JSTracer *trc, void **thingp, JSGCTraceKind kind) { + static_cast(trc)->appendGrayRoot(*thingp, kind); + } + + bool failed() const { return bufferingGrayRootsFailed; } +}; + void -SetMarkStackLimit(JSRuntime* rt, size_t limit); +SetMarkStackLimit(JSRuntime *rt, size_t limit); + +// Return true if this trace is happening on behalf of gray buffering during +// the marking phase of incremental GC. +inline bool +IsBufferingGrayRoots(JSTracer *trc) +{ + return trc->callback == BufferGrayRootsTracer::grayTraceCallback; +} + +// Return true if this trace is happening on behalf of the marking phase of GC. +inline bool +IsMarkingTracer(JSTracer *trc) +{ + // If we call this on the gray-buffering tracer, then we have encountered a + // marking path that will be wrong when tracing with a callback marker to + // enqueue for deferred gray marking. + MOZ_ASSERT(!IsBufferingGrayRoots(trc)); + return trc->callback == nullptr; +} } /* namespace js */ -/* - * Macro to test if a traversal is the marking phase of the GC. - */ -#define IS_GC_MARKING_TRACER(trc) \ - ((trc)->callback == nullptr || (trc)->callback == GCMarker::GrayCallback) - #endif /* js_Tracer_h */ diff --git a/js/src/jit-test/tests/ion/bug1129977.js b/js/src/jit-test/tests/ion/bug1129977.js new file mode 100644 index 0000000000..b82fe7961f --- /dev/null +++ b/js/src/jit-test/tests/ion/bug1129977.js @@ -0,0 +1,10 @@ +gczeal(11); +function C(a, b) { + this.b=b; +} +evaluate('\ +Object.defineProperty(Object.prototype, "b", {set: function() {}});\ +var f = C.bind(0x2004, 2);\ +'); +for (var i=1; i<5000; ++i) + new f; diff --git a/js/src/jit/BaselineJIT.cpp b/js/src/jit/BaselineJIT.cpp index c9740d40ef..cdd4febbad 100644 --- a/js/src/jit/BaselineJIT.cpp +++ b/js/src/jit/BaselineJIT.cpp @@ -65,7 +65,8 @@ BaselineScript::BaselineScript(uint32_t prologueOffset, uint32_t epilogueOffset, traceLoggerScriptEvent_(), #endif postDebugPrologueOffset_(postDebugPrologueOffset), - flags_(0) + flags_(0), + maxInliningDepth_(UINT8_MAX) { } static const unsigned BASELINE_MAX_ARGS_LENGTH = 20000; diff --git a/js/src/jit/BaselineJIT.h b/js/src/jit/BaselineJIT.h index 96ebcb24e0..a023d94371 100644 --- a/js/src/jit/BaselineJIT.h +++ b/js/src/jit/BaselineJIT.h @@ -212,6 +212,13 @@ struct BaselineScript // instruction. uint32_t yieldEntriesOffset_; + // The max inlining depth where we can still inline all functions we inlined + // when we Ion-compiled this script. This starts as UINT8_MAX, since we have + // no data yet, and won't affect inlining heuristics in that case. The value + // is updated when we Ion-compile this script. See makeInliningDecision for + // more info. + uint8_t maxInliningDepth_; + public: // Do not call directly, use BaselineScript::New. This is public for cx->new_. BaselineScript(uint32_t prologueOffset, uint32_t epilogueOffset, @@ -424,6 +431,17 @@ struct BaselineScript MOZ_ASSERT(bytecodeTypeMapOffset_); return reinterpret_cast(reinterpret_cast(this) + bytecodeTypeMapOffset_); } + + uint8_t maxInliningDepth() const { + return maxInliningDepth_; + } + void setMaxInliningDepth(uint32_t depth) { + MOZ_ASSERT(depth <= UINT8_MAX); + maxInliningDepth_ = depth; + } + void resetMaxInliningDepth() { + maxInliningDepth_ = UINT8_MAX; + } }; static_assert(sizeof(BaselineScript) % sizeof(uintptr_t) == 0, "The data attached to the script must be aligned for fast JIT access."); diff --git a/js/src/jit/CodeGenerator.cpp b/js/src/jit/CodeGenerator.cpp index 1be727d0e0..3ea97e0543 100644 --- a/js/src/jit/CodeGenerator.cpp +++ b/js/src/jit/CodeGenerator.cpp @@ -6112,7 +6112,7 @@ JitRuntime::generateFreeStub(JSContext* cx) } -JitCode* +JitCode * JitRuntime::generateLazyLinkStub(JSContext* cx) { MacroAssembler masm(cx); @@ -6136,7 +6136,7 @@ JitRuntime::generateLazyLinkStub(JSContext* cx) masm.setupUnalignedABICall(1, temp0); masm.loadJSContext(temp0); masm.passABIArg(temp0); - masm.callWithABI(JS_FUNC_TO_DATA_PTR(void*, LazyLinkTopActivation)); + masm.callWithABI(JS_FUNC_TO_DATA_PTR(void *, LazyLinkTopActivation)); masm.leaveExitFrame(/* stub code */ sizeof(JitCode*)); diff --git a/js/src/jit/CompileWrappers.cpp b/js/src/jit/CompileWrappers.cpp index 1243bb2465..ff8e2afce9 100644 --- a/js/src/jit/CompileWrappers.cpp +++ b/js/src/jit/CompileWrappers.cpp @@ -276,7 +276,8 @@ CompileCompartment::setSingletonsAsValues() JitCompileOptions::JitCompileOptions() : cloneSingletons_(false), - spsSlowAssertionsEnabled_(false) + spsSlowAssertionsEnabled_(false), + offThreadCompilationAvailable_(false) { } @@ -286,4 +287,5 @@ JitCompileOptions::JitCompileOptions(JSContext* cx) cloneSingletons_ = options.cloneSingletons(); spsSlowAssertionsEnabled_ = cx->runtime()->spsProfiler.enabled() && cx->runtime()->spsProfiler.slowAssertionsEnabled(); + offThreadCompilationAvailable_ = OffThreadCompilationAvailable(cx); } diff --git a/js/src/jit/CompileWrappers.h b/js/src/jit/CompileWrappers.h index a34a4c8900..3d47881d6c 100644 --- a/js/src/jit/CompileWrappers.h +++ b/js/src/jit/CompileWrappers.h @@ -135,9 +135,14 @@ class JitCompileOptions return spsSlowAssertionsEnabled_; } + bool offThreadCompilationAvailable() const { + return offThreadCompilationAvailable_; + } + private: bool cloneSingletons_; bool spsSlowAssertionsEnabled_; + bool offThreadCompilationAvailable_; }; } // namespace jit diff --git a/js/src/jit/Ion.cpp b/js/src/jit/Ion.cpp index 1daeea49ff..b31a0c9006 100644 --- a/js/src/jit/Ion.cpp +++ b/js/src/jit/Ion.cpp @@ -438,17 +438,17 @@ FinishAllOffThreadCompilations(JSCompartment* comp) } uint8_t* -jit::LazyLinkTopActivation(JSContext* cx) +jit::LazyLinkTopActivation(JSContext *cx) { JitActivationIterator iter(cx->runtime()); // First frame should be an exit frame. JitFrameIterator it(iter); - LazyLinkExitFrameLayout* ll = it.exitFrame()->as(); - JSScript* calleeScript = ScriptFromCalleeToken(ll->jsFrame()->calleeToken()); + LazyLinkExitFrameLayout *ll = it.exitFrame()->as(); + JSScript *calleeScript = ScriptFromCalleeToken(ll->jsFrame()->calleeToken()); // Get the pending builder from the Ion frame. - IonBuilder* builder = calleeScript->ionScript()->pendingBuilder(); + IonBuilder *builder = calleeScript->ionScript()->pendingBuilder(); calleeScript->setPendingIonBuilder(cx, nullptr); AutoEnterAnalysis enterTypes(cx); @@ -1737,8 +1737,12 @@ MarkOffThreadNurseryObjects::mark(JSTracer* trc) { JSRuntime* rt = trc->runtime(); - MOZ_ASSERT(rt->jitRuntime()->hasIonNurseryObjects()); - rt->jitRuntime()->setHasIonNurseryObjects(false); + if (trc->runtime()->isHeapMinorCollecting()) { + // Only reset hasIonNurseryObjects if we're doing an actual minor GC, + // not if we're, for instance, verifying post barriers. + MOZ_ASSERT(rt->jitRuntime()->hasIonNurseryObjects()); + rt->jitRuntime()->setHasIonNurseryObjects(false); + } AutoLockHelperThreadState lock; if (!HelperThreadState().threads) @@ -1776,19 +1780,6 @@ MarkOffThreadNurseryObjects::mark(JSTracer* trc) } } -static inline bool -OffThreadCompilationAvailable(JSContext* cx) -{ - // Even if off thread compilation is enabled, compilation must still occur - // on the main thread in some cases. - // - // Require cpuCount > 1 so that Ion compilation jobs and main-thread - // execution are not competing for the same resources. - return cx->runtime()->canUseOffthreadIonCompilation() - && HelperThreadState().cpuCount > 1 - && CanUseExtraThreads(); -} - static void TrackAllProperties(JSContext* cx, JSObject* obj) { @@ -1970,7 +1961,7 @@ IonCompile(JSContext* cx, JSScript* script, } // If possible, compile the script off thread. - if (OffThreadCompilationAvailable(cx)) { + if (options.offThreadCompilationAvailable()) { if (!recompile) builderScript->setIonScript(cx, ION_COMPILING_SCRIPT); @@ -2180,6 +2171,19 @@ Compile(JSContext* cx, HandleScript script, BaselineFrame* osrFrame, jsbytecode* } // namespace jit } // namespace js +bool +jit::OffThreadCompilationAvailable(JSContext *cx) +{ + // Even if off thread compilation is enabled, compilation must still occur + // on the main thread in some cases. + // + // Require cpuCount > 1 so that Ion compilation jobs and main-thread + // execution are not competing for the same resources. + return cx->runtime()->canUseOffthreadIonCompilation() + && HelperThreadState().cpuCount > 1 + && CanUseExtraThreads(); +} + // Decide if a transition from interpreter execution to Ion code should occur. // May compile or recompile the target JSScript. MethodStatus diff --git a/js/src/jit/Ion.h b/js/src/jit/Ion.h index ddc033746b..1fccf4072e 100644 --- a/js/src/jit/Ion.h +++ b/js/src/jit/Ion.h @@ -182,20 +182,22 @@ TooManyFormalArguments(unsigned nargs) } inline size_t -NumLocalsAndArgs(JSScript* script) +NumLocalsAndArgs(JSScript *script) { size_t num = 1 /* this */ + script->nfixed(); - if (JSFunction* fun = script->functionNonDelazifying()) + if (JSFunction *fun = script->functionNonDelazifying()) num += fun->nargs(); return num; } -void ForbidCompilation(JSContext* cx, JSScript* script); +bool OffThreadCompilationAvailable(JSContext *cx); -void PurgeCaches(JSScript* script); -size_t SizeOfIonData(JSScript* script, mozilla::MallocSizeOf mallocSizeOf); -void DestroyJitScripts(FreeOp* fop, JSScript* script); -void TraceJitScripts(JSTracer* trc, JSScript* script); +void ForbidCompilation(JSContext *cx, JSScript *script); + +void PurgeCaches(JSScript *script); +size_t SizeOfIonData(JSScript *script, mozilla::MallocSizeOf mallocSizeOf); +void DestroyJitScripts(FreeOp *fop, JSScript *script); +void TraceJitScripts(JSTracer* trc, JSScript *script); bool JitSupportsFloatingPoint(); bool JitSupportsSimd(); diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index 65c64a3259..45a7fdfe3e 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -146,6 +146,7 @@ IonBuilder::IonBuilder(JSContext* analysisContext, CompileCompartment* comp, loopHeaders_(*temp), inspector(inspector), inliningDepth_(inliningDepth), + inlinedBytecodeLength_(0), numLoopRestarts_(0), failedBoundsCheck_(info->script()->failedBoundsCheck()), failedShapeGuard_(info->script()->failedShapeGuard()), @@ -775,6 +776,9 @@ IonBuilder::build() if (!init()) return false; + if (script()->hasBaselineScript()) + script()->baselineScript()->resetMaxInliningDepth(); + if (!setCurrentAndSpecializePhis(newBlock(pc))) return false; if (!current) @@ -4859,61 +4863,12 @@ IonBuilder::makeInliningDecision(JSObject* targetArg, CallInfo& callInfo) return decision; // Heuristics! - JSScript* targetScript = target->nonLazyScript(); - - // Cap the inlining depth. - if (js_JitOptions.isSmallFunction(targetScript)) { - if (inliningDepth_ >= optimizationInfo().smallFunctionMaxInlineDepth()) { - trackOptimizationOutcome(TrackedOutcome::CantInlineExceededDepth); - return DontInline(targetScript, "Vetoed: exceeding allowed inline depth"); - } - } else { - if (inliningDepth_ >= optimizationInfo().maxInlineDepth()) { - trackOptimizationOutcome(TrackedOutcome::CantInlineExceededDepth); - return DontInline(targetScript, "Vetoed: exceeding allowed inline depth"); - } - - if (targetScript->hasLoops()) { - // Currently, we are not inlining function which have loops because - // the cost inherent to inlining the function overcome the cost - // calling it. The reason is not yet clear to everybody, and we - // hope that we might be able to remove this restriction in the - // future. - // - // In the mean time, if we have opportunities to optimize the - // loop better, then we should try it. Such opportunity might be - // suggested by: - // - // - Constant as argument. Inlining a function called with a - // constant, might help GVN, as well as UCE, and potentially - // improve bound check removal. - // - // - Inner function as argument. Inlining a function called - // with an inner function might help scalar replacement at - // removing the scope chain, and thus using registers within - // the loop instead of writting everything back to memory. - bool hasOpportunities = false; - for (size_t i = 0, e = callInfo.argv().length(); !hasOpportunities && i < e; i++) { - MDefinition* arg = callInfo.argv()[i]; - hasOpportunities = arg->isLambda() || arg->isConstantValue(); - } - - if (!hasOpportunities) { - trackOptimizationOutcome(TrackedOutcome::CantInlineBigLoop); - return DontInline(targetScript, "Vetoed: big function that contains a loop"); - } - } - - // Caller must not be excessively large. - if (script()->length() >= optimizationInfo().inliningMaxCallerBytecodeLength()) { - trackOptimizationOutcome(TrackedOutcome::CantInlineBigCaller); - return DontInline(targetScript, "Vetoed: caller excessively large"); - } - } + JSScript *targetScript = target->nonLazyScript(); // Callee must not be excessively large. // This heuristic also applies to the callsite as a whole. - if (targetScript->length() > optimizationInfo().inlineMaxTotalBytecodeLength()) { + bool offThread = options.offThreadCompilationAvailable(); + if (targetScript->length() > optimizationInfo().inlineMaxBytecodePerCallSite(offThread)) { trackOptimizationOutcome(TrackedOutcome::CantInlineBigCallee); return DontInline(targetScript, "Vetoed: callee excessively large"); } @@ -4931,10 +4886,81 @@ IonBuilder::makeInliningDecision(JSObject* targetArg, CallInfo& callInfo) return InliningDecision_WarmUpCountTooLow; } + IonBuilder *outerBuilder = outermostBuilder(); + + // Cap the total bytecode length we inline under a single script, to avoid + // excessive inlining in pathological cases. + size_t totalBytecodeLength = outerBuilder->inlinedBytecodeLength_ + targetScript->length(); + if (totalBytecodeLength > optimizationInfo().inlineMaxTotalBytecodeLength()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineExceededTotalBytecodeLength); + return DontInline(targetScript, "Vetoed: exceeding max total bytecode length"); + } + + // Cap the inlining depth. + + uint32_t maxInlineDepth; + if (js_JitOptions.isSmallFunction(targetScript)) { + maxInlineDepth = optimizationInfo().smallFunctionMaxInlineDepth(); + } else { + maxInlineDepth = optimizationInfo().maxInlineDepth(); + + // Caller must not be excessively large. + if (script()->length() >= optimizationInfo().inliningMaxCallerBytecodeLength()) { + trackOptimizationOutcome(TrackedOutcome::CantInlineBigCaller); + return DontInline(targetScript, "Vetoed: caller excessively large"); + } + } + + BaselineScript *outerBaseline = outermostBuilder()->script()->baselineScript(); + if (inliningDepth_ >= maxInlineDepth) { + // We hit the depth limit and won't inline this function. Give the + // outermost script a max inlining depth of 0, so that it won't be + // inlined in other scripts. This heuristic is currently only used + // when we're inlining scripts with loops, see the comment below. + outerBaseline->setMaxInliningDepth(0); + + trackOptimizationOutcome(TrackedOutcome::CantInlineExceededDepth); + return DontInline(targetScript, "Vetoed: exceeding allowed inline depth"); + } + + // Inlining functions with loops can be complicated. For instance, if we're + // close to the inlining depth limit and we inline the function f below, we + // can no longer inline the call to g: + // + // function f() { + // while (cond) { + // g(); + // } + // } + // + // If the loop has many iterations, it's more efficient to call f and inline + // g in f. + // + // To avoid this problem, we record a separate max inlining depth for each + // script, indicating at which depth we won't be able to inline all functions + // we inlined this time. This solves the issue above, because we will only + // inline f if it means we can also inline g. + if (targetScript->hasLoops() && + inliningDepth_ >= targetScript->baselineScript()->maxInliningDepth()) + { + trackOptimizationOutcome(TrackedOutcome::CantInlineExceededDepth); + return DontInline(targetScript, "Vetoed: exceeding allowed script inline depth"); + } + + // Update the max depth at which we can inline the outer script. + MOZ_ASSERT(maxInlineDepth > inliningDepth_); + uint32_t scriptInlineDepth = maxInlineDepth - inliningDepth_ - 1; + if (scriptInlineDepth < outerBaseline->maxInliningDepth()) + outerBaseline->setMaxInliningDepth(scriptInlineDepth); + + // End of heuristics, we will inline this function. + // TI calls ObjectStateChange to trigger invalidation of the caller. - TypeSet::ObjectKey* targetKey = TypeSet::ObjectKey::get(target); + TypeSet::ObjectKey *targetKey = TypeSet::ObjectKey::get(target); targetKey->watchStateChangeForInlinedCall(constraints()); + outerBuilder->inlinedBytecodeLength_ += targetScript->length(); + return InliningDecision_Inline; } @@ -4980,7 +5006,8 @@ IonBuilder::selectInliningTargets(const ObjectVector& targets, CallInfo& callInf // Enforce a maximum inlined bytecode limit at the callsite. if (inlineable && target->as().isInterpreted()) { totalSize += target->as().nonLazyScript()->length(); - if (totalSize > optimizationInfo().inlineMaxTotalBytecodeLength()) + bool offThread = options.offThreadCompilationAvailable(); + if (totalSize > optimizationInfo().inlineMaxBytecodePerCallSite(offThread)) inlineable = false; } } else { diff --git a/js/src/jit/IonBuilder.h b/js/src/jit/IonBuilder.h index 32219e0c1c..80957810a0 100644 --- a/js/src/jit/IonBuilder.h +++ b/js/src/jit/IonBuilder.h @@ -1098,6 +1098,10 @@ class IonBuilder size_t inliningDepth_; + // Total bytecode length of all inlined scripts. Only tracked for the + // outermost builder. + size_t inlinedBytecodeLength_; + // Cutoff to disable compilation if excessive time is spent reanalyzing // loop bodies to compute a fixpoint of the types for loop variables. static const size_t MAX_LOOP_RESTARTS = 40; diff --git a/js/src/jit/IonOptimizationLevels.cpp b/js/src/jit/IonOptimizationLevels.cpp index 6f31680dec..ec688dffd7 100644 --- a/js/src/jit/IonOptimizationLevels.cpp +++ b/js/src/jit/IonOptimizationLevels.cpp @@ -37,8 +37,10 @@ OptimizationInfo::initNormalOptimizationInfo() sink_ = true; registerAllocator_ = RegisterAllocator_LSRA; - inlineMaxTotalBytecodeLength_ = 1000; - inliningMaxCallerBytecodeLength_ = 10000; + inlineMaxBytecodePerCallSiteMainThread_ = 500; + inlineMaxBytecodePerCallSiteOffThread_ = 1000; + inlineMaxTotalBytecodeLength_ = 80000; + inliningMaxCallerBytecodeLength_ = 1500; maxInlineDepth_ = 3; scalarReplacement_ = true; smallFunctionMaxInlineDepth_ = 10; diff --git a/js/src/jit/IonOptimizationLevels.h b/js/src/jit/IonOptimizationLevels.h index c5b8d0be49..b95f9702e0 100644 --- a/js/src/jit/IonOptimizationLevels.h +++ b/js/src/jit/IonOptimizationLevels.h @@ -88,7 +88,13 @@ class OptimizationInfo // Describes which register allocator to use. IonRegisterAllocator registerAllocator_; - // The maximum total bytecode size of an inline call site. + // The maximum total bytecode size of an inline call site. We use a lower + // value if off-thread compilation is not available, to avoid stalling the + // main thread. + uint32_t inlineMaxBytecodePerCallSiteOffThread_; + uint32_t inlineMaxBytecodePerCallSiteMainThread_; + + // The maximum bytecode length we'll inline in a single compilation. uint32_t inlineMaxTotalBytecodeLength_; // The maximum bytecode length the caller may have, @@ -209,12 +215,18 @@ class OptimizationInfo return maxInlineDepth_; } + uint32_t inlineMaxBytecodePerCallSite(bool offThread) const { + return (offThread || !js_JitOptions.limitScriptSize) + ? inlineMaxBytecodePerCallSiteOffThread_ + : inlineMaxBytecodePerCallSiteMainThread_; + } + uint32_t inlineMaxTotalBytecodeLength() const { return inlineMaxTotalBytecodeLength_; } uint32_t inliningMaxCallerBytecodeLength() const { - return inlineMaxTotalBytecodeLength_; + return inliningMaxCallerBytecodeLength_; } uint32_t inliningWarmUpThreshold() const { diff --git a/js/src/jit/JitFrames.cpp b/js/src/jit/JitFrames.cpp index 285ad33a73..a219473b1f 100644 --- a/js/src/jit/JitFrames.cpp +++ b/js/src/jit/JitFrames.cpp @@ -998,7 +998,7 @@ ReadAllocation(const JitFrameIterator& frame, const LAllocation* a) #endif static void -MarkThisAndArguments(JSTracer* trc, JitFrameLayout* layout) +MarkThisAndArguments(JSTracer *trc, JitFrameLayout *layout) { // Mark |this| and any extra actual arguments for an Ion frame. Marking of // formal arguments is taken care of by the frame's safepoint/snapshot, @@ -1008,7 +1008,7 @@ MarkThisAndArguments(JSTracer* trc, JitFrameLayout* layout) size_t nargs = layout->numActualArgs(); size_t nformals = 0; if (CalleeTokenIsFunction(layout->calleeToken())) { - JSFunction* fun = CalleeTokenToFunction(layout->calleeToken()); + JSFunction *fun = CalleeTokenToFunction(layout->calleeToken()); nformals = fun->nonLazyScript()->mayReadFrameArgsDirectly() ? 0 : fun->nargs(); } @@ -1023,15 +1023,15 @@ MarkThisAndArguments(JSTracer* trc, JitFrameLayout* layout) } static void -MarkThisAndArguments(JSTracer* trc, const JitFrameIterator& frame) +MarkThisAndArguments(JSTracer *trc, const JitFrameIterator &frame) { - JitFrameLayout* layout = frame.jsFrame(); + JitFrameLayout *layout = frame.jsFrame(); MarkThisAndArguments(trc, layout); } #ifdef JS_NUNBOX32 static inline void -WriteAllocation(const JitFrameIterator& frame, const LAllocation* a, uintptr_t value) +WriteAllocation(const JitFrameIterator &frame, const LAllocation *a, uintptr_t value) { if (a->isGeneralReg()) { Register reg = a->toGeneralReg()->reg(); @@ -1289,7 +1289,7 @@ MarkJitExitFrameCopiedArguments(JSTracer* trc, const VMFunction* f, ExitFooterFr #endif static void -MarkJitExitFrame(JSTracer* trc, const JitFrameIterator& frame) +MarkJitExitFrame(JSTracer *trc, const JitFrameIterator &frame) { // Ignore fake exit frames created by EnsureExitFrame. if (frame.isFakeExitFrame()) @@ -1308,9 +1308,9 @@ MarkJitExitFrame(JSTracer* trc, const JitFrameIterator& frame) // CodeGenerator.cpp which handle the case of a native function call. We // need to mark the argument vector of the function call. if (frame.isExitFrameLayout()) { - NativeExitFrameLayout* native = frame.exitFrame()->as(); + NativeExitFrameLayout *native = frame.exitFrame()->as(); size_t len = native->argc() + 2; - Value* vp = native->vp(); + Value *vp = native->vp(); gc::MarkValueRootRange(trc, len, vp, "ion-native-args"); return; } @@ -1369,8 +1369,8 @@ MarkJitExitFrame(JSTracer* trc, const JitFrameIterator& frame) } if (frame.isExitFrameLayout()) { - LazyLinkExitFrameLayout* ll = frame.exitFrame()->as(); - JitFrameLayout* layout = ll->jsFrame(); + LazyLinkExitFrameLayout *ll = frame.exitFrame()->as(); + JitFrameLayout *layout = ll->jsFrame(); gc::MarkJitCodeRoot(trc, ll->stubCode(), "lazy-link-code"); layout->replaceCalleeToken(MarkCalleeToken(trc, layout->calleeToken())); diff --git a/js/src/jit/JitFrames.h b/js/src/jit/JitFrames.h index b1416f278d..1a482b3d13 100644 --- a/js/src/jit/JitFrames.h +++ b/js/src/jit/JitFrames.h @@ -880,21 +880,21 @@ struct IonDOMMethodExitFrameLayoutTraits { class LazyLinkExitFrameLayout { protected: // silence clang warning about unused private fields - JitCode* stubCode_; + JitCode *stubCode_; ExitFooterFrame footer_; JitFrameLayout exit_; public: - static JitCode* Token() { return (JitCode*) LazyLinkExitFrameLayoutToken; } + static JitCode *Token() { return (JitCode *) LazyLinkExitFrameLayoutToken; } static inline size_t Size() { return sizeof(LazyLinkExitFrameLayout); } - inline JitCode** stubCode() { + inline JitCode **stubCode() { return &stubCode_; } - inline JitFrameLayout* jsFrame() { + inline JitFrameLayout *jsFrame() { return &exit_; } static size_t offsetOfExitFrame() { @@ -903,13 +903,13 @@ class LazyLinkExitFrameLayout }; template <> -inline LazyLinkExitFrameLayout* +inline LazyLinkExitFrameLayout * ExitFrameLayout::as() { MOZ_ASSERT(is()); - uint8_t* sp = reinterpret_cast(this); + uint8_t *sp = reinterpret_cast(this); sp -= LazyLinkExitFrameLayout::offsetOfExitFrame(); - return reinterpret_cast(sp); + return reinterpret_cast(sp); } class ICStub; diff --git a/js/src/jit/JitOptions.cpp b/js/src/jit/JitOptions.cpp index 53171e99c8..c2cc1b52ef 100644 --- a/js/src/jit/JitOptions.cpp +++ b/js/src/jit/JitOptions.cpp @@ -164,10 +164,6 @@ JitOptions::JitOptions() SET_DEFAULT(osrPcMismatchesBeforeRecompile, 6000); // The bytecode length limit for small function. - // - // The default for this was arrived at empirically via benchmarking. - // We may want to tune it further after other optimizations have gone - // in. SET_DEFAULT(smallFunctionMaxBytecodeLength_, 100); } diff --git a/js/src/jit/MacroAssembler.h b/js/src/jit/MacroAssembler.h index 990918791d..159f43f72e 100644 --- a/js/src/jit/MacroAssembler.h +++ b/js/src/jit/MacroAssembler.h @@ -869,7 +869,7 @@ class MacroAssembler : public MacroAssemblerSpecific exitCodePatch_ = PushWithPatch(ImmWord(-1)); } - void enterExitFrame(const VMFunction* f = nullptr) { + void enterExitFrame(const VMFunction *f = nullptr) { linkExitFrame(); // Push the ioncode. (Bailout or VM wrapper) PushStubCode(); diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index 1cf950f899..f4860de6fa 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -1411,22 +1411,13 @@ JS_RemoveExtraGCRootsTracer(JSRuntime* rt, JSTraceDataOp traceOp, void* data) } extern JS_PUBLIC_API(bool) -JS_IsGCMarkingTracer(JSTracer* trc) +JS_IsGCMarkingTracer(JSTracer *trc) { - return IS_GC_MARKING_TRACER(trc); + return js::IsMarkingTracer(trc); } -#ifdef DEBUG -extern JS_PUBLIC_API(bool) -JS_IsMarkingGray(JSTracer* trc) -{ - MOZ_ASSERT(JS_IsGCMarkingTracer(trc)); - return trc->callback == GCMarker::GrayCallback; -} -#endif - JS_PUBLIC_API(void) -JS_GC(JSRuntime* rt) +JS_GC(JSRuntime *rt) { AssertHeapIsIdle(rt); JS::PrepareForFullGC(rt); @@ -3971,9 +3962,9 @@ JS_GetFunctionScript(JSContext* cx, HandleFunction fun) * enclosingDynamicScope is a dynamic scope to use, if it's not the global. */ static bool -CompileFunction(JSContext* cx, const ReadOnlyCompileOptions& options, - const char* name, unsigned nargs, const char* const* argnames, - SourceBufferHolder& srcBuf, +CompileFunction(JSContext *cx, const ReadOnlyCompileOptions &optionsArg, + const char *name, unsigned nargs, const char *const *argnames, + SourceBufferHolder &srcBuf, HandleObject enclosingDynamicScope, HandleObject enclosingStaticScope, MutableHandleFunction fun) @@ -4005,6 +3996,13 @@ CompileFunction(JSContext* cx, const ReadOnlyCompileOptions& options, if (!fun) return false; + // Make sure to handle cases when we have a polluted scopechain. + OwningCompileOptions options(cx); + if (!options.copy(cx, optionsArg)) + return false; + if (!enclosingDynamicScope->is()) + options.setHasPollutedScope(true); + if (!frontend::CompileFunctionBody(cx, fun, options, formals, srcBuf, enclosingStaticScope)) return false; @@ -4102,6 +4100,13 @@ ExecuteScript(JSContext* cx, HandleObject obj, HandleScript scriptArg, jsval* rv AssertHeapIsIdle(cx); CHECK_REQUEST(cx); assertSameCompartment(cx, obj, scriptArg); + + if (!script->hasPollutedGlobalScope() && !obj->is()) { + script = CloneScript(cx, NullPtr(), NullPtr(), script, HasPollutedGlobalScope); + if (!script) + return false; + js::Debugger::onNewScript(cx, script); + } AutoLastFrameCheck lfc(cx); return Execute(cx, script, *obj, rval); } @@ -4148,7 +4153,9 @@ JS::CloneAndExecuteScript(JSContext* cx, HandleObject obj, HandleScript scriptAr assertSameCompartment(cx, obj); RootedScript script(cx, scriptArg); if (script->compartment() != cx->compartment()) { - script = CloneScript(cx, NullPtr(), NullPtr(), script); + PollutedGlobalScopeOption globalScopeOption = obj->is() ? + HasCleanGlobalScope : HasPollutedGlobalScope; + script = CloneScript(cx, NullPtr(), NullPtr(), script, globalScopeOption); if (!script) return false; @@ -4172,6 +4179,7 @@ Evaluate(JSContext *cx, HandleObject scope, const ReadOnlyCompileOptions &option AutoLastFrameCheck lfc(cx); options.setCompileAndGo(scope->is()); + options.setHasPollutedScope(!scope->is()); SourceCompressionTask sct(cx); RootedScript script(cx, frontend::CompileScript(cx, &cx->tempLifoAlloc(), scope, NullPtr(), NullPtr(), options, diff --git a/js/src/jsapi.h b/js/src/jsapi.h index 4248ac44e3..6e2f529ecd 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -1718,13 +1718,7 @@ extern JS_PUBLIC_API(void) JS_RemoveFinalizeCallback(JSRuntime* rt, JSFinalizeCallback cb); extern JS_PUBLIC_API(bool) -JS_IsGCMarkingTracer(JSTracer* trc); - -/* For assertions only. */ -#ifdef JS_DEBUG -extern JS_PUBLIC_API(bool) -JS_IsMarkingGray(JSTracer* trc); -#endif +JS_IsGCMarkingTracer(JSTracer *trc); /* * Weak pointers and garbage collection @@ -3357,6 +3351,7 @@ class JS_FRIEND_API(ReadOnlyCompileOptions) lineno(1), column(0), compileAndGo(false), + hasPollutedGlobalScope(false), forEval(false), noScriptRval(false), selfHostingMode(false), @@ -3396,6 +3391,7 @@ class JS_FRIEND_API(ReadOnlyCompileOptions) unsigned lineno; unsigned column; bool compileAndGo; + bool hasPollutedGlobalScope; bool forEval; bool noScriptRval; bool selfHostingMode; @@ -3487,6 +3483,7 @@ class JS_FRIEND_API(OwningCompileOptions) : public ReadOnlyCompileOptions OwningCompileOptions& setUTF8(bool u) { utf8 = u; return *this; } OwningCompileOptions& setColumn(unsigned c) { column = c; return *this; } OwningCompileOptions& setCompileAndGo(bool cng) { compileAndGo = cng; return *this; } + OwningCompileOptions& setHasPollutedScope(bool p) { hasPollutedGlobalScope = p; return *this; } OwningCompileOptions& setForEval(bool eval) { forEval = eval; return *this; } OwningCompileOptions& setNoScriptRval(bool nsr) { noScriptRval = nsr; return *this; } OwningCompileOptions& setSelfHostingMode(bool shm) { selfHostingMode = shm; return *this; } @@ -3570,6 +3567,7 @@ class MOZ_STACK_CLASS JS_FRIEND_API(CompileOptions) : public ReadOnlyCompileOpti CompileOptions& setUTF8(bool u) { utf8 = u; return *this; } CompileOptions& setColumn(unsigned c) { column = c; return *this; } CompileOptions& setCompileAndGo(bool cng) { compileAndGo = cng; return *this; } + CompileOptions& setHasPollutedScope(bool p) { hasPollutedGlobalScope = p; return *this; } CompileOptions& setForEval(bool eval) { forEval = eval; return *this; } CompileOptions& setNoScriptRval(bool nsr) { noScriptRval = nsr; return *this; } CompileOptions& setSelfHostingMode(bool shm) { selfHostingMode = shm; return *this; } diff --git a/js/src/jsfun.cpp b/js/src/jsfun.cpp index 31924d06cf..3588335f7f 100644 --- a/js/src/jsfun.cpp +++ b/js/src/jsfun.cpp @@ -759,15 +759,13 @@ js::OrdinaryHasInstance(JSContext* cx, HandleObject objArg, MutableHandleValue v return true; } -// XXX RM 2018-12-10 TODO perhaps this can be removed given the functions above - /* * [[HasInstance]] internal method for Function objects: fetch the .prototype * property of its 'this' parameter, and walks the prototype chain of v (only * if v is an object) returning true if .prototype is found. */ static bool -fun_hasInstance(JSContext* cx, HandleObject objArg, MutableHandleValue v, bool* bp) +fun_hasInstance(JSContext *cx, HandleObject objArg, MutableHandleValue v, bool *bp) { RootedObject obj(cx, objArg); @@ -823,8 +821,8 @@ JSFunction::trace(JSTracer* trc) // This information can either be a LazyScript, or the name of a // self-hosted function which can be cloned over again. The latter // is stored in the first extended slot. - JSRuntime* rt = trc->runtime(); - if (IS_GC_MARKING_TRACER(trc) && + JSRuntime *rt = trc->runtime(); + if (IsMarkingTracer(trc) && (rt->allowRelazificationForTesting || !compartment()->hasBeenEntered()) && !compartment()->isDebuggee() && !compartment()->isSelfHosting && u.i.s.script_->isRelazifiable() && (!isSelfHostedBuiltin() || isExtended())) @@ -2175,11 +2173,33 @@ js::NewFunctionWithProto(ExclusiveContext *cx, Native native, } bool -js::CloneFunctionObjectUseSameScript(JSCompartment* compartment, HandleFunction fun) +js::CloneFunctionObjectUseSameScript(JSCompartment *compartment, HandleFunction fun, + HandleObject newParent) { - return compartment == fun->compartment() && - !fun->isSingleton() && - !ObjectGroup::useSingletonForClone(fun); + if (compartment != fun->compartment() || + fun->isSingleton() || + ObjectGroup::useSingletonForClone(fun)) + { + return false; + } + + if (newParent->is()) + return true; + + // Don't need to clone the script if newParent is not a valid lexical scope + // chain terminator, since in that case we have some actual scope objects on + // our scope chain and whatnot; whoever put them there should be responsible + // for setting our script's flags appropriately. We hit this case for + // JSOP_LAMBDA, for example. + if (!IsValidTerminatingScope(newParent)) + return true; + + // We need to clone the script if we're interpreted and not already marked + // as having a polluted scope. If we're lazy, go ahead and clone the + // script; see the big comment at the end of CloneScript for the explanation + // of what's going on there. + return !fun->isInterpreted() || + (fun->hasScript() && fun->nonLazyScript()->hasPollutedGlobalScope()); } JSFunction * @@ -2191,7 +2211,7 @@ js::CloneFunctionObject(JSContext *cx, HandleFunction fun, HandleObject parent, MOZ_ASSERT(parent); MOZ_ASSERT(!fun->isBoundFunction()); - bool useSameScript = CloneFunctionObjectUseSameScript(cx->compartment(), fun); + bool useSameScript = CloneFunctionObjectUseSameScript(cx->compartment(), fun, parent); if (!useSameScript && fun->isInterpretedLazy()) { JSAutoCompartment ac(cx, fun); @@ -2270,13 +2290,19 @@ js::CloneFunctionObject(JSContext *cx, HandleFunction fun, HandleObject parent, RootedFunction cloneRoot(cx, clone); /* - * Across compartments we have to clone the script for interpreted - * functions. Cross-compartment cloning only happens via JSAPI - * (JS::CloneFunctionObject) which dynamically ensures that 'script' has - * no enclosing lexical scope (only the global scope). + * Across compartments or if we have to introduce a polluted scope we have + * to clone the script for interpreted functions. Cross-compartment cloning + * only happens via JSAPI (JS::CloneFunctionObject) which dynamically + * ensures that 'script' has no enclosing lexical scope (only the global + * scope or other non-lexical scope). */ - if (cloneRoot->isInterpreted() && !CloneFunctionScript(cx, fun, cloneRoot, newKindArg)) + PollutedGlobalScopeOption globalScopeOption = parent->is() ? + HasCleanGlobalScope : HasPollutedGlobalScope; + if (cloneRoot->isInterpreted() && + !CloneFunctionScript(cx, fun, cloneRoot, globalScopeOption, newKindArg)) + { return nullptr; + } return cloneRoot; } diff --git a/js/src/jsfun.h b/js/src/jsfun.h index 0256953cbd..2354b2f84e 100644 --- a/js/src/jsfun.h +++ b/js/src/jsfun.h @@ -617,10 +617,11 @@ class FunctionExtended : public JSFunction }; extern bool -CloneFunctionObjectUseSameScript(JSCompartment* compartment, HandleFunction fun); +CloneFunctionObjectUseSameScript(JSCompartment *compartment, HandleFunction fun, + HandleObject newParent); -extern JSFunction* -CloneFunctionObject(JSContext* cx, HandleFunction fun, HandleObject parent, +extern JSFunction * +CloneFunctionObject(JSContext *cx, HandleFunction fun, HandleObject parent, gc::AllocKind kind = JSFunction::FinalizeKind, NewObjectKind newKindArg = GenericObject, HandleObject proto = NullPtr()); diff --git a/js/src/jsgc.cpp b/js/src/jsgc.cpp index afb25902e0..f6076d5463 100644 --- a/js/src/jsgc.cpp +++ b/js/src/jsgc.cpp @@ -1086,6 +1086,7 @@ GCRuntime::GCRuntime(JSRuntime* rt) : numActiveZoneIters(0), decommitThreshold(32 * 1024 * 1024), cleanUpEverything(false), + grayBufferState(GCRuntime::GrayBufferState::Unused), grayBitsValid(false), majorGCTriggerReason(JS::gcreason::NO_REASON), minorGCTriggerReason(JS::gcreason::NO_REASON), @@ -3983,7 +3984,7 @@ GCRuntime::beginMarkPhase(JS::gcreason::Reason reason) marker.start(); MOZ_ASSERT(!marker.callback); - MOZ_ASSERT(IS_GC_MARKING_TRACER(&marker)); + MOZ_ASSERT(IsMarkingTracer(&marker)); /* For non-incremental GC the following sweep discards the jit code. */ if (isIncremental) { @@ -4037,8 +4038,10 @@ GCRuntime::beginMarkPhase(JS::gcreason::Reason reason) gcstats::AutoPhase ap2(stats, gcstats::PHASE_MARK_ROOTS); - if (isIncremental) + if (isIncremental) { + gcstats::AutoPhase ap3(stats, gcstats::PHASE_BUFFER_GRAY_ROOTS); bufferGrayRoots(); + } /* * This code ensures that if a compartment is "dead", then it will be @@ -4143,9 +4146,9 @@ void GCRuntime::markGrayReferences(gcstats::Phase phase) { gcstats::AutoPhase ap(stats, phase); - if (marker.hasBufferedGrayRoots()) { + if (hasBufferedGrayRoots()) { for (ZoneIterT zone(rt); !zone.done(); zone.next()) - marker.markBufferedGrayRoots(zone); + markBufferedGrayRoots(zone); } else { MOZ_ASSERT(!isIncremental); if (JSTraceDataOp op = grayRootTracer.op) @@ -5591,6 +5594,7 @@ GCRuntime::finishCollection(JS::gcreason::Reason reason) { MOZ_ASSERT(marker.isDrained()); marker.stop(); + clearBufferedGrayRoots(); uint64_t currentTime = PRMJ_Now(); schedulingState.updateHighFrequencyMode(lastGCTime, currentTime, tunables); @@ -5711,6 +5715,7 @@ GCRuntime::resetIncrementalGC(const char* reason) marker.reset(); marker.stop(); + clearBufferedGrayRoots(); for (GCCompartmentsIter c(rt); !c.done(); c.next()) ResetGrayList(c); @@ -5933,7 +5938,7 @@ GCRuntime::incrementalCollectSlice(SliceBudget& budget, JS::gcreason::Reason rea AutoGCRooter::traceAllWrappers(&marker); /* If we needed delayed marking for gray roots, then collect until done. */ - if (!marker.hasBufferedGrayRoots()) { + if (!hasBufferedGrayRoots()) { budget.makeUnlimited(); isIncremental = false; } diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp index e722312639..bc838ce4c6 100644 --- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -2122,7 +2122,7 @@ js::CloneObjectLiteral(JSContext *cx, HandleObject srcObj) value = srcArray->getDenseElement(i); MOZ_ASSERT_IF(value.isMarkable(), value.toGCThing()->isTenured() && - cx->runtime()->isAtomsZone(value.toGCThing()->asTenured().zone())); + cx->runtime()->isAtomsZone(value.toGCThing()->asTenured().zoneFromAnyThread())); id = INT_TO_JSID(i); if (!DefineProperty(cx, res, id, value, nullptr, nullptr, JSPROP_ENUMERATE)) diff --git a/js/src/jsscript.cpp b/js/src/jsscript.cpp index 90ce9e3d8b..7e29c94dd6 100644 --- a/js/src/jsscript.cpp +++ b/js/src/jsscript.cpp @@ -579,7 +579,8 @@ js::XDRScript(XDRState* xdr, HandleObject enclosingScope, HandleScript enc IsCompileAndGo, HasSingleton, TreatAsRunOnce, - HasLazyScript + HasLazyScript, + HasPollutedGlobalScope, }; uint32_t length, lineno, column, nslots, staticLevel; @@ -697,6 +698,8 @@ js::XDRScript(XDRState* xdr, HandleObject enclosingScope, HandleScript enc scriptBits |= (1 << TreatAsRunOnce); if (script->isRelazifiable()) scriptBits |= (1 << HasLazyScript); + if (script->hasPollutedGlobalScope()) + scriptBits |= (1 << HasPollutedGlobalScope); } if (!xdr->codeUint32(&prologLength)) @@ -814,6 +817,8 @@ js::XDRScript(XDRState* xdr, HandleObject enclosingScope, HandleScript enc script->hasSingletons_ = true; if (scriptBits & (1 << TreatAsRunOnce)) script->treatAsRunOnce_ = true; + if (scriptBits & (1 << HasPollutedGlobalScope)) + script->hasPollutedGlobalScope_ = true; if (scriptBits & (1 << IsLegacyGenerator)) { MOZ_ASSERT(!(scriptBits & (1 << IsStarGenerator))); @@ -2395,6 +2400,7 @@ JSScript::Create(ExclusiveContext *cx, HandleObject enclosingScope, bool savedCa script->initCompartment(cx); script->compileAndGo_ = options.compileAndGo; + script->hasPollutedGlobalScope_ = options.hasPollutedGlobalScope; script->selfHosted_ = options.selfHostingMode; script->noScriptRval_ = options.noScriptRval; @@ -2954,8 +2960,9 @@ Rebase(JSScript* dst, JSScript* src, T* srcp) return reinterpret_cast(dst->data + off); } -JSScript* -js::CloneScript(JSContext* cx, HandleObject enclosingScope, HandleFunction fun, HandleScript src, +JSScript * +js::CloneScript(JSContext *cx, HandleObject enclosingScope, HandleFunction fun, HandleScript src, + PollutedGlobalScopeOption polluted /* = HasCleanGlobalScope */, NewObjectKind newKind /* = GenericObject */) { /* NB: Keep this in sync with XDRScript. */ @@ -3079,6 +3086,8 @@ js::CloneScript(JSContext* cx, HandleObject enclosingScope, HandleFunction fun, CompileOptions options(cx); options.setMutedErrors(src->mutedErrors()) .setCompileAndGo(src->compileAndGo()) + .setHasPollutedScope(src->hasPollutedGlobalScope() || + polluted == HasPollutedGlobalScope) .setSelfHostingMode(src->selfHosted()) .setNoScriptRval(src->noScriptRval()) .setVersion(src->getVersion()); @@ -3149,12 +3158,29 @@ js::CloneScript(JSContext* cx, HandleObject enclosingScope, HandleFunction fun, if (nblockscopes != 0) dst->blockScopes()->vector = Rebase(dst, src, src->blockScopes()->vector); + /* + * Function delazification assumes that their script does not have a + * polluted global scope. We ensure that as follows: + * + * 1) Initial parsing only creates lazy functions if + * !hasPollutedGlobalScope. + * 2) Cloning a lazy function into a non-global scope will always require + * that its script be cloned. See comments in + * CloneFunctionObjectUseSameScript. + * 3) Cloning a script never sets a lazyScript on the clone, so the function + * cannot be relazified. + * + * If you decide that lazy functions should be supported with a polluted + * global scope, make sure delazification can deal. + */ + MOZ_ASSERT_IF(dst->hasPollutedGlobalScope(), !dst->maybeLazyScript()); + MOZ_ASSERT_IF(dst->hasPollutedGlobalScope(), !dst->isRelazifiable()); return dst; } bool -js::CloneFunctionScript(JSContext* cx, HandleFunction original, HandleFunction clone, - NewObjectKind newKind /* = GenericObject */) +js::CloneFunctionScript(JSContext *cx, HandleFunction original, HandleFunction clone, + PollutedGlobalScopeOption polluted, NewObjectKind newKind) { MOZ_ASSERT(clone->isInterpreted()); @@ -3176,7 +3202,7 @@ js::CloneFunctionScript(JSContext* cx, HandleFunction original, HandleFunction c clone->mutableScript().init(nullptr); - JSScript* cscript = CloneScript(cx, scope, clone, script, newKind); + JSScript *cscript = CloneScript(cx, scope, clone, script, polluted, newKind); if (!cscript) return false; @@ -3385,8 +3411,8 @@ JSScript::markChildren(JSTracer* trc) // JSScript::Create(), but not yet finished initializing it with // fullyInitFromEmitter() or fullyInitTrivial(). - MOZ_ASSERT_IF(IS_GC_MARKING_TRACER(trc) && - static_cast(trc)->shouldCheckCompartments(), + MOZ_ASSERT_IF(IsMarkingTracer(trc) && + static_cast(trc)->shouldCheckCompartments(), zone()->isCollecting()); for (uint32_t i = 0; i < natoms(); ++i) { @@ -3423,7 +3449,7 @@ JSScript::markChildren(JSTracer* trc) if (maybeLazyScript()) MarkLazyScriptUnbarriered(trc, &lazyScript, "lazyScript"); - if (IS_GC_MARKING_TRACER(trc)) { + if (IsMarkingTracer(trc)) { compartment()->mark(); if (code()) diff --git a/js/src/jsscript.h b/js/src/jsscript.h index 4fef75ca4b..2f2ce3677e 100644 --- a/js/src/jsscript.h +++ b/js/src/jsscript.h @@ -751,17 +751,23 @@ GeneratorKindFromBits(unsigned val) { */ template bool -XDRScript(XDRState* xdr, HandleObject enclosingScope, HandleScript enclosingScript, +XDRScript(XDRState *xdr, HandleObject enclosingScope, HandleScript enclosingScript, HandleFunction fun, MutableHandleScript scriptp); -JSScript* -CloneScript(JSContext* cx, HandleObject enclosingScope, HandleFunction fun, HandleScript script, +enum PollutedGlobalScopeOption { + HasPollutedGlobalScope, + HasCleanGlobalScope +}; + +JSScript * +CloneScript(JSContext *cx, HandleObject enclosingScope, HandleFunction fun, HandleScript script, + PollutedGlobalScopeOption polluted = HasCleanGlobalScope, NewObjectKind newKind = GenericObject); template bool -XDRLazyScript(XDRState* xdr, HandleObject enclosingScope, HandleScript enclosingScript, - HandleFunction fun, MutableHandle lazy); +XDRLazyScript(XDRState *xdr, HandleObject enclosingScope, HandleScript enclosingScript, + HandleFunction fun, MutableHandle lazy); /* * Code any constant value. @@ -780,8 +786,9 @@ class JSScript : public js::gc::TenuredCell js::XDRScript(js::XDRState* xdr, js::HandleObject enclosingScope, js::HandleScript enclosingScript, js::HandleFunction fun, js::MutableHandleScript scriptp); - friend JSScript* - js::CloneScript(JSContext* cx, js::HandleObject enclosingScope, js::HandleFunction fun, js::HandleScript src, + friend JSScript * + js::CloneScript(JSContext *cx, js::HandleObject enclosingScope, js::HandleFunction fun, + js::HandleScript src, js::PollutedGlobalScopeOption polluted, js::NewObjectKind newKind); public: @@ -924,6 +931,11 @@ class JSScript : public js::gc::TenuredCell // See Parser::compileAndGo. bool compileAndGo_:1; + // True if the script has a non-syntactic scope on its dynamic scope chain. + // That is, there are objects about which we know nothing between the + // outermost syntactic scope and the global. + bool hasPollutedGlobalScope_:1; + // see Parser::selfHostingMode. bool selfHosted_:1; @@ -1149,6 +1161,10 @@ class JSScript : public js::gc::TenuredCell return compileAndGo_; } + bool hasPollutedGlobalScope() const { + return hasPollutedGlobalScope_; + } + bool selfHosted() const { return selfHosted_; } bool bindingsAccessedDynamically() const { return bindingsAccessedDynamically_; } bool funHasExtensibleScope() const { @@ -2162,14 +2178,14 @@ enum LineOption { }; extern void -DescribeScriptedCallerForCompilation(JSContext* cx, MutableHandleScript maybeScript, - const char** file, unsigned* linenop, - uint32_t* pcOffset, bool* mutedErrors, +DescribeScriptedCallerForCompilation(JSContext *cx, MutableHandleScript maybeScript, + const char **file, unsigned *linenop, + uint32_t *pcOffset, bool *mutedErrors, LineOption opt = NOT_CALLED_FROM_JSOP_EVAL); bool -CloneFunctionScript(JSContext* cx, HandleFunction original, HandleFunction clone, - NewObjectKind newKind = GenericObject); +CloneFunctionScript(JSContext *cx, HandleFunction original, HandleFunction clone, + PollutedGlobalScopeOption polluted, NewObjectKind newKind); } /* namespace js */ diff --git a/js/src/jsweakmap.cpp b/js/src/jsweakmap.cpp index 02488fc562..8d016a227c 100644 --- a/js/src/jsweakmap.cpp +++ b/js/src/jsweakmap.cpp @@ -41,10 +41,10 @@ WeakMapBase::~WeakMapBase() } void -WeakMapBase::trace(JSTracer* tracer) +WeakMapBase::trace(JSTracer *tracer) { MOZ_ASSERT(isInList()); - if (IS_GC_MARKING_TRACER(tracer)) { + if (IsMarkingTracer(tracer)) { // We don't trace any of the WeakMap entries at this time, just record // record the fact that the WeakMap has been marked. Enties are marked // in the iterative marking phase by markAllIteratively(), which happens diff --git a/js/src/vm/Debugger.cpp b/js/src/vm/Debugger.cpp index 3ddc23406e..5a8661eed5 100644 --- a/js/src/vm/Debugger.cpp +++ b/js/src/vm/Debugger.cpp @@ -6139,6 +6139,7 @@ EvaluateInEnv(JSContext* cx, Handle env, HandleValue thisv, AbstractFrameP return false; CompileOptions options(cx); options.setCompileAndGo(true) + .setHasPollutedScope(true) .setForEval(true) .setNoScriptRval(false) .setFileAndLine(filename, lineno) diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index d2a4d85f57..1206182fe5 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -636,6 +636,11 @@ js::ExecuteKernel(JSContext* cx, HandleScript script, JSObject& scopeChainArg, c AutoSuppressGC nogc(cx); MOZ_ASSERT(GetOuterObject(cx, thisObj) == thisObj); } + RootedObject terminatingScope(cx, &scopeChainArg); + while (!IsValidTerminatingScope(terminatingScope)) + terminatingScope = terminatingScope->enclosingScope(); + MOZ_ASSERT(terminatingScope->is() || + script->hasPollutedGlobalScope()); #endif if (script->isEmpty()) { @@ -665,6 +670,9 @@ js::Execute(JSContext* cx, HandleScript script, JSObject& scopeChainArg, Value* MOZ_RELEASE_ASSERT(scopeChain->is() || !script->compileAndGo(), "Only non-compile-and-go scripts can be executed with " "interesting scopechains"); + MOZ_RELEASE_ASSERT(scopeChain->is() || script->hasPollutedGlobalScope(), + "Only scripts with polluted scopes can be executed with " + "interesting scopechains"); /* Ensure the scope chain is all same-compartment and terminates in a global. */ #ifdef DEBUG diff --git a/js/src/vm/RegExpObject.cpp b/js/src/vm/RegExpObject.cpp index 90c40cef5b..2ffa768c74 100644 --- a/js/src/vm/RegExpObject.cpp +++ b/js/src/vm/RegExpObject.cpp @@ -245,10 +245,10 @@ RegExpObject::trace(JSTracer* trc, JSObject* obj) // conditions, since: // 1. During TraceRuntime, isHeapBusy() is true, but the tracer might not // be a marking tracer. - // 2. When a write barrier executes, IS_GC_MARKING_TRACER is true, but + // 2. When a write barrier executes, IsMarkingTracer is true, but // isHeapBusy() will be false. if (trc->runtime()->isHeapBusy() && - IS_GC_MARKING_TRACER(trc) && + IsMarkingTracer(trc) && !obj->asTenured().zone()->isPreservingCode()) { obj->as().NativeObject::setPrivate(nullptr); @@ -572,9 +572,9 @@ RegExpShared::~RegExpShared() } void -RegExpShared::trace(JSTracer* trc) +RegExpShared::trace(JSTracer *trc) { - if (IS_GC_MARKING_TRACER(trc)) + if (IsMarkingTracer(trc)) marked_ = true; if (source) diff --git a/js/src/vm/Stack.cpp b/js/src/vm/Stack.cpp index 15effaf4ba..80f4873f5c 100644 --- a/js/src/vm/Stack.cpp +++ b/js/src/vm/Stack.cpp @@ -324,7 +324,7 @@ InterpreterFrame::popWith(JSContext* cx) } void -InterpreterFrame::mark(JSTracer* trc) +InterpreterFrame::mark(JSTracer *trc) { /* * Normally we would use MarkRoot here, except that generators also take @@ -342,7 +342,7 @@ InterpreterFrame::mark(JSTracer* trc) } else { gc::MarkScriptUnbarriered(trc, &exec.script, "script"); } - if (IS_GC_MARKING_TRACER(trc)) + if (IsMarkingTracer(trc)) script()->compartment()->zone()->active = true; if (hasReturnValue()) gc::MarkValueUnbarriered(trc, &rval_, "rval"); @@ -1131,7 +1131,8 @@ FrameIter::matchCallee(JSContext* cx, HandleFunction fun) const // Use the same condition as |js::CloneFunctionObject|, to know if we should // expect both functions to have the same JSScript. If so, and if they are // different, then they cannot be equal. - bool useSameScript = CloneFunctionObjectUseSameScript(fun->compartment(), currentCallee); + RootedObject global(cx, &fun->global()); + bool useSameScript = CloneFunctionObjectUseSameScript(fun->compartment(), currentCallee, global); if (useSameScript && (currentCallee->hasScript() != fun->hasScript() || currentCallee->nonLazyScript() != fun->nonLazyScript())) diff --git a/js/src/vm/Xdr.h b/js/src/vm/Xdr.h index 0347134d6a..8a34a66059 100644 --- a/js/src/vm/Xdr.h +++ b/js/src/vm/Xdr.h @@ -29,7 +29,7 @@ namespace js { * * https://developer.mozilla.org/en-US/docs/SpiderMonkey/Internals/Bytecode */ -static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 249; +static const uint32_t XDR_BYTECODE_VERSION_SUBTRAHEND = 250; static const uint32_t XDR_BYTECODE_VERSION = uint32_t(0xb973c0de - XDR_BYTECODE_VERSION_SUBTRAHEND); diff --git a/js/xpconnect/loader/mozJSSubScriptLoader.cpp b/js/xpconnect/loader/mozJSSubScriptLoader.cpp index b126b6cded..ed21d9f051 100644 --- a/js/xpconnect/loader/mozJSSubScriptLoader.cpp +++ b/js/xpconnect/loader/mozJSSubScriptLoader.cpp @@ -177,6 +177,7 @@ mozJSSubScriptLoader::ReadScript(nsIURI* uri, JSContext* cx, JSObject* targetObj } if (!reuseGlobal) { + options.setHasPollutedScope(!JS_IsGlobalObject(target_obj)); JS::Compile(cx, options, srcBuf, script); } else { AutoObjectVector scopeChain(cx); @@ -192,7 +193,8 @@ mozJSSubScriptLoader::ReadScript(nsIURI* uri, JSContext* cx, JSObject* targetObj // We only use lazy source when no special encoding is specified because // the lazy source loader doesn't know the encoding. if (!reuseGlobal) { - options.setSourceIsLazy(true); + options.setSourceIsLazy(true) + .setHasPollutedScope(!JS_IsGlobalObject(target_obj)); JS::Compile(cx, options, buf.get(), len, script); } else { AutoObjectVector scopeChain(cx);