diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index 90681c765a..89e0f3967d 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -315,7 +315,7 @@ IonBuilder::getSingleCallTarget(TemporaryTypeSet* calleeTypes) bool IonBuilder::getPolyCallTargets(TemporaryTypeSet* calleeTypes, bool constructing, - ObjectVector& targets, uint32_t maxTargets) + InliningTargets& targets, uint32_t maxTargets) { MOZ_ASSERT(targets.empty()); @@ -334,10 +334,11 @@ IonBuilder::getPolyCallTargets(TemporaryTypeSet* calleeTypes, bool constructing, return false; for (unsigned i = 0; i < objCount; i++) { JSObject* obj = calleeTypes->getSingleton(i); + ObjectGroup* group = nullptr; if (obj) { MOZ_ASSERT(obj->isSingleton()); } else { - ObjectGroup* group = calleeTypes->getGroup(i); + group = calleeTypes->getGroup(i); if (!group) continue; @@ -358,7 +359,7 @@ IonBuilder::getPolyCallTargets(TemporaryTypeSet* calleeTypes, bool constructing, return true; } - targets.infallibleAppend(obj); + targets.infallibleAppend(InliningTarget(obj, group)); } return true; @@ -544,12 +545,6 @@ IonBuilder::canInlineTarget(JSFunction* target, CallInfo& callInfo) return DontInline(inlineScript, "Script is debuggee"); } - TypeSet::ObjectKey* targetKey = TypeSet::ObjectKey::get(target); - if (targetKey->unknownProperties()) { - trackOptimizationOutcome(TrackedOutcome::CantInlineUnknownProps); - return DontInline(inlineScript, "Target type has unknown properties"); - } - return InliningDecision_Inline; } @@ -5815,18 +5810,14 @@ IonBuilder::makeInliningDecision(JSObject* targetArg, CallInfo& callInfo) // End of heuristics, we will inline this function. - // TI calls ObjectStateChange to trigger invalidation of the caller. - TypeSet::ObjectKey* targetKey = TypeSet::ObjectKey::get(target); - targetKey->watchStateChangeForInlinedCall(constraints()); - outerBuilder->inlinedBytecodeLength_ += targetScript->length(); return InliningDecision_Inline; } -bool -IonBuilder::selectInliningTargets(const ObjectVector& targets, CallInfo& callInfo, BoolVector& choiceSet, - uint32_t* numInlineable) +AbortReasonOr +IonBuilder::selectInliningTargets(const InliningTargets& targets, CallInfo& callInfo, + BoolVector& choiceSet, uint32_t* numInlineable) { *numInlineable = 0; uint32_t totalSize = 0; @@ -5841,7 +5832,7 @@ IonBuilder::selectInliningTargets(const ObjectVector& targets, CallInfo& callInf return true; for (size_t i = 0; i < targets.length(); i++) { - JSObject* target = targets[i]; + JSObject* target = targets[i].target; trackOptimizationAttempt(TrackedStrategy::Call_Inline); trackTypeInfo(TrackedTypeSite::Call_Target, target); @@ -5875,6 +5866,16 @@ IonBuilder::selectInliningTargets(const ObjectVector& targets, CallInfo& callInf inlineable = false; } + // Only use a group guard and inline the target if we will recompile when + // the target function gets a new group. + if (inlineable && targets[i].group) { + ObjectGroup* group = targets[i].group; + TypeSet::ObjectKey* key = TypeSet::ObjectKey::get(group); + if (!key->hasStableClassAndProto(constraints())) { + inlineable = false; + } + } + choiceSet.infallibleAppend(inlineable); if (inlineable) *numInlineable += 1; @@ -5885,7 +5886,7 @@ IonBuilder::selectInliningTargets(const ObjectVector& targets, CallInfo& callInf // depend on the types of the arguments and the return value. if (isOptimizationTrackingEnabled()) { for (size_t i = 0; i < targets.length(); i++) { - if (choiceSet[i] && targets[i]->as().isNative()) { + if (choiceSet[i] && targets[i].target->as().isNative()) { trackTypeInfo(callInfo); break; } @@ -6033,8 +6034,8 @@ IonBuilder::inlineSingleCall(CallInfo& callInfo, JSObject* targetArg) return inlineScriptedCall(callInfo, target); } -IonBuilder::InliningStatus -IonBuilder::inlineCallsite(const ObjectVector& targets, CallInfo& callInfo) +IonBuilder::InliningResult +IonBuilder::inlineCallsite(const InliningTargets& targets, CallInfo& callInfo) { if (targets.empty()) { trackOptimizationAttempt(TrackedStrategy::Call_Inline); @@ -6051,7 +6052,7 @@ IonBuilder::inlineCallsite(const ObjectVector& targets, CallInfo& callInfo) // Inline single targets -- unless they derive from a cache, in which case // avoiding the cache and guarding is still faster. if (!propCache.get() && targets.length() == 1) { - JSObject* target = targets[0]; + JSObject* target = targets[0].target; trackOptimizationAttempt(TrackedStrategy::Call_Inline); trackTypeInfo(TrackedTypeSite::Call_Target, target); @@ -6224,8 +6225,8 @@ IonBuilder::inlineObjectGroupFallback(CallInfo& callInfo, MBasicBlock* dispatchB return true; } -bool -IonBuilder::inlineCalls(CallInfo& callInfo, const ObjectVector& targets, BoolVector& choiceSet, +AbortReasonOr +IonBuilder::inlineCalls(CallInfo& callInfo, const InliningTargets& targets, BoolVector& choiceSet, MGetPropertyCache* maybeCache) { // Only handle polymorphic inlining. @@ -6297,7 +6298,7 @@ IonBuilder::inlineCalls(CallInfo& callInfo, const ObjectVector& targets, BoolVec amendOptimizationAttempt(i); // Target must be reachable by the MDispatchInstruction. - JSFunction* target = &targets[i]->as(); + JSFunction* target = &targets[i].target->as(); if (maybeCache && !maybeCache->propTable()->hasFunction(target)) { choiceSet[i] = false; trackOptimizationOutcome(TrackedOutcome::CantInlineNotInDispatch); @@ -6365,9 +6366,8 @@ IonBuilder::inlineCalls(CallInfo& callInfo, const ObjectVector& targets, BoolVec setCurrent(dispatchBlock); // Connect the inline path to the returnBlock. - ObjectGroup* funcGroup = target->isSingleton() ? nullptr : target->group(); - if (!dispatch->addCase(target, funcGroup, inlineBlock)) - return false; + if (!dispatch->addCase(target, targets[i].group, inlineBlock)) + return abort(AbortReason::Alloc); MDefinition* retVal = inlineReturnBlock->peek(-1); retPhi->addInput(retVal); @@ -6454,8 +6454,9 @@ IonBuilder::inlineCalls(CallInfo& callInfo, const ObjectVector& targets, BoolVec continue; MOZ_ASSERT(!remaining); - if (targets[i]->is() && targets[i]->as().isSingleton()) - remaining = &targets[i]->as(); + JSObject* target = targets[i].target; + if (target->is() && target->isSingleton()) + remaining = &target->as(); break; } } @@ -7047,7 +7048,7 @@ IonBuilder::jsop_call(uint32_t argc, bool constructing, bool ignoresReturnValue) int calleeDepth = -((int)argc + 2 + constructing); // Acquire known call target if existent. - ObjectVector targets(alloc()); + InliningTargets targets(alloc()); TemporaryTypeSet* calleeTypes = current->peek(calleeDepth)->resultTypeSet(); if (calleeTypes && !getPolyCallTargets(calleeTypes, constructing, targets, 4)) return false; @@ -7068,8 +7069,8 @@ IonBuilder::jsop_call(uint32_t argc, bool constructing, bool ignoresReturnValue) // No inline, just make the call. JSFunction* target = nullptr; - if (targets.length() == 1 && targets[0]->is()) - target = &targets[0]->as(); + if (targets.length() == 1 && targets[0].target->is()) + target = &targets[0].target->as(); if (target && status == InliningStatus_WarmUpCountTooLow) { MRecompileCheck* check = diff --git a/js/src/jit/IonBuilder.h b/js/src/jit/IonBuilder.h index 8f616b74eb..427a52549e 100644 --- a/js/src/jit/IonBuilder.h +++ b/js/src/jit/IonBuilder.h @@ -233,8 +233,8 @@ class IonBuilder void spew(const char* message); JSFunction* getSingleCallTarget(TemporaryTypeSet* calleeTypes); - MOZ_MUST_USE bool getPolyCallTargets(TemporaryTypeSet* calleeTypes, bool constructing, - ObjectVector& targets, uint32_t maxTargets); + AbortReasonOr getPolyCallTargets(TemporaryTypeSet* calleeTypes, bool constructing, + InliningTargets& targets, uint32_t maxTargets); void popCfgStack(); DeferredEdge* filterDeadDeferredEdges(DeferredEdge* edge); @@ -830,7 +830,7 @@ class IonBuilder // Oracles. InliningDecision canInlineTarget(JSFunction* target, CallInfo& callInfo); InliningDecision makeInliningDecision(JSObject* target, CallInfo& callInfo); - MOZ_MUST_USE bool selectInliningTargets(const ObjectVector& targets, CallInfo& callInfo, + AbortReasonOr selectInliningTargets(const InliningTargets& targets, CallInfo& callInfo, BoolVector& choiceSet, uint32_t* numInlineable); // Native inlining helpers. @@ -961,8 +961,8 @@ class IonBuilder InliningStatus inlineSingleCall(CallInfo& callInfo, JSObject* target); // Call functions - InliningStatus inlineCallsite(const ObjectVector& targets, CallInfo& callInfo); - MOZ_MUST_USE bool inlineCalls(CallInfo& callInfo, const ObjectVector& targets, + InliningResult inlineCallsite(const InliningTargets& targets, CallInfo& callInfo); + AbortReasonOr inlineCalls(CallInfo& callInfo, const InliningTargets& targets, BoolVector& choiceSet, MGetPropertyCache* maybeCache); // Inlining helpers. diff --git a/js/src/jit/MCallOptimize.cpp b/js/src/jit/MCallOptimize.cpp index 0f78ef3f2f..0f2759610d 100644 --- a/js/src/jit/MCallOptimize.cpp +++ b/js/src/jit/MCallOptimize.cpp @@ -610,10 +610,6 @@ IonBuilder::inlineArrayPopShift(CallInfo& callInfo, MArrayPopShift::Mode mode) const Class* clasp = thisTypes->getKnownClass(constraints()); if (clasp != &ArrayObject::class_ && clasp != &UnboxedArrayObject::class_) return InliningStatus_NotInlined; - if (thisTypes->hasObjectFlags(constraints(), unhandledFlags)) { - trackOptimizationOutcome(TrackedOutcome::ArrayBadFlags); - return InliningStatus_NotInlined; - } // Watch out for extra indexed properties on the object or its prototype. if (ElementAccessHasExtraIndexedProperty(this, obj)) { diff --git a/js/src/jit/MIR.cpp b/js/src/jit/MIR.cpp index 197737c1ef..a183b25198 100644 --- a/js/src/jit/MIR.cpp +++ b/js/src/jit/MIR.cpp @@ -4909,14 +4909,14 @@ MGuardReceiverPolymorphic::congruentTo(const MDefinition* ins) const } void -InlinePropertyTable::trimTo(const ObjectVector& targets, const BoolVector& choiceSet) +InlinePropertyTable::trimTo(const InliningTargets& targets, const BoolVector& choiceSet) { for (size_t i = 0; i < targets.length(); i++) { // If the target was inlined, don't erase the entry. if (choiceSet[i]) continue; - JSFunction* target = &targets[i]->as(); + JSFunction* target = &targets[i].target->as(); // Eliminate all entries containing the vetoed function from the map. size_t j = 0; @@ -4930,7 +4930,7 @@ InlinePropertyTable::trimTo(const ObjectVector& targets, const BoolVector& choic } void -InlinePropertyTable::trimToTargets(const ObjectVector& targets) +InlinePropertyTable::trimToTargets(const InliningTargets& targets) { JitSpew(JitSpew_Inlining, "Got inlineable property cache with %d cases", (int)numEntries()); @@ -4939,7 +4939,7 @@ InlinePropertyTable::trimToTargets(const ObjectVector& targets) while (i < numEntries()) { bool foundFunc = false; for (size_t j = 0; j < targets.length(); j++) { - if (entries_[i]->func == targets[j]) { + if (entries_[i]->func == targets[j].target) { foundFunc = true; break; } @@ -6102,10 +6102,14 @@ jit::PropertyWriteNeedsTypeBarrier(TempAllocator& alloc, CompilerConstraintList* bool success = true; for (size_t i = 0; i < types->getObjectCount(); i++) { TypeSet::ObjectKey* key = types->getObject(i); - if (!key) - continue; - if (!key->hasStableClassAndProto(constraints)) - return true; + + if (!key) { + continue; + } + + if (!key->hasStableClassAndProto(constraints)) { + return true; + } // TI doesn't track TypedArray indexes and should never insert a type // barrier for them. @@ -6157,10 +6161,14 @@ jit::PropertyWriteNeedsTypeBarrier(TempAllocator& alloc, CompilerConstraintList* TypeSet::ObjectKey* excluded = nullptr; for (size_t i = 0; i < types->getObjectCount(); i++) { TypeSet::ObjectKey* key = types->getObject(i); - if (!key) - continue; - if (!key->hasStableClassAndProto(constraints)) - return true; + + if (!key) { + continue; + } + + if (!key->hasStableClassAndProto(constraints)) { + return true; + } if (!name && IsTypedArrayClass(key->clasp())) continue; diff --git a/js/src/jit/MIR.h b/js/src/jit/MIR.h index a6b0b169af..7514d4d2c7 100644 --- a/js/src/jit/MIR.h +++ b/js/src/jit/MIR.h @@ -9539,8 +9539,25 @@ class MStoreFixedSlot ALLOW_CLONE(MStoreFixedSlot) }; -typedef Vector ObjectVector; -typedef Vector BoolVector; +struct InliningTarget +{ + JSObject* target; + + // If target is a singleton, group is nullptr. If target is not a singleton, + // this is the group we need to guard on when doing a polymorphic inlining + // dispatch. Note that this can be different from target->group() due to + // proto mutation. + ObjectGroup* group; + + InliningTarget(JSObject* target, ObjectGroup* group) + : target(target), group(group) + { + MOZ_ASSERT(target->isSingleton() == !group); + } +}; + +using InliningTargets = Vector; +using BoolVector = Vector; class InlinePropertyTable : public TempObject { @@ -9604,10 +9621,10 @@ class InlinePropertyTable : public TempObject TemporaryTypeSet* buildTypeSetForFunction(JSFunction* func) const; // Remove targets that vetoed inlining from the InlinePropertyTable. - void trimTo(const ObjectVector& targets, const BoolVector& choiceSet); + void trimTo(const InliningTargets& targets, const BoolVector& choiceSet); // Ensure that the InlinePropertyTable's domain is a subset of |targets|. - void trimToTargets(const ObjectVector& targets); + void trimToTargets(const InliningTargets& targets); bool appendRoots(MRootList& roots) const; }; diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp index b9c4f30340..2045364297 100644 --- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -1892,27 +1892,38 @@ js::SetClassAndProto(JSContext* cx, HandleObject obj, return true; } - if (proto.isObject()) { - RootedObject protoObj(cx, proto.toObject()); - if (!JSObject::setNewGroupUnknown(cx, clasp, protoObj)) + RootedObjectGroup oldGroup(cx, obj->group()); + + ObjectGroup* newGroup; + if (oldGroup->maybeInterpretedFunction()) { + // We're changing the group/proto of a scripted function. Create a new + // group so we can keep track of the interpreted function for Ion + // inlining. + MOZ_ASSERT(obj->is()); + newGroup = ObjectGroupCompartment::makeGroup(cx, &JSFunction::class_, proto); + if (!newGroup) + return false; + newGroup->setInterpretedFunction(oldGroup->maybeInterpretedFunction()); + } else { + newGroup = ObjectGroup::defaultNewGroup(cx, clasp, proto); + if (!newGroup) return false; } - ObjectGroup* group = ObjectGroup::defaultNewGroup(cx, clasp, proto); - if (!group) - return false; + obj->setGroup(newGroup); - /* - * Setting __proto__ on an object that has escaped and may be referenced by - * other heap objects can only be done if the properties of both objects - * are unknown. Type sets containing this object will contain the original - * type but not the new type of the object, so we need to treat all such - * type sets as unknown. - */ - MarkObjectGroupUnknownProperties(cx, obj->group()); - MarkObjectGroupUnknownProperties(cx, group); + // Add the object's property types to the new group. + if (!newGroup->unknownProperties()) { + if (obj->isNative()) + AddPropertyTypesAfterProtoChange(cx, &obj->as(), oldGroup); + else + MarkObjectGroupUnknownProperties(cx, newGroup); + } - obj->setGroup(group); + // Type sets containing this object will contain the old group but not the + // new group of the object, so we need to treat all such type sets as + // unknown. + MarkObjectGroupUnknownProperties(cx, oldGroup); return true; } diff --git a/js/src/vm/NativeObject.cpp b/js/src/vm/NativeObject.cpp index cde86fb829..47c17ccfb9 100644 --- a/js/src/vm/NativeObject.cpp +++ b/js/src/vm/NativeObject.cpp @@ -1130,6 +1130,53 @@ UpdateShapeTypeAndValue(ExclusiveContext* cx, HandleNativeObject obj, HandleShap return true; } +void +js::AddPropertyTypesAfterProtoChange(JSContext* cx, NativeObject* obj, ObjectGroup* oldGroup) +{ + MOZ_ASSERT(obj->group() != oldGroup); + MOZ_ASSERT(!obj->group()->unknownProperties()); + + if (oldGroup->unknownProperties()) { + MarkObjectGroupUnknownProperties(cx, obj->group()); + return; + } + + // First copy the dynamic flags. + MarkObjectGroupFlags(cx, obj, oldGroup->flags() & OBJECT_FLAG_DYNAMIC_MASK); + + // Now update all property types. If the object has many properties, this + // function may be slow so we mark all properties as unknown. + static const size_t MaxPropertyCount = 40; + + size_t nprops = obj->getDenseInitializedLength(); + if (nprops > MaxPropertyCount) { + MarkObjectGroupUnknownProperties(cx, obj->group()); + return; + } + + // Add dense element types. + for (size_t i = 0; i < obj->getDenseInitializedLength(); i++) { + Value val = obj->getDenseElement(i); + if (!val.isMagic(JS_ELEMENTS_HOLE)) + AddTypePropertyId(cx, obj, JSID_VOID, val); + } + + // Add property types. + for (Shape::Range r(obj->lastProperty()); !r.empty(); r.popFront()) { + Shape* shape = &r.front(); + jsid id = shape->propid(); + if (JSID_IS_EMPTY(id)) + continue; + + if (nprops++ > MaxPropertyCount) { + MarkObjectGroupUnknownProperties(cx, obj->group()); + return; + } + + Value val = shape->hasSlot() ? obj->getSlot(shape->slot()) : UndefinedValue(); + UpdateShapeTypeAndValue(cx, obj, shape, id, val); + } +} static bool PurgeProtoChain(ExclusiveContext* cx, JSObject* objArg, HandleId id) { diff --git a/js/src/vm/NativeObject.h b/js/src/vm/NativeObject.h index 86977d109f..8a24dffdf7 100644 --- a/js/src/vm/NativeObject.h +++ b/js/src/vm/NativeObject.h @@ -1517,6 +1517,9 @@ MaybeNativeObject(JSObject* obj) // Defined in NativeObject-inl.h. bool IsPackedArray(JSObject* obj); +extern void +AddPropertyTypesAfterProtoChange(JSContext* cx, NativeObject* obj, ObjectGroup* oldGroup); + } // namespace js diff --git a/js/src/vm/TypeInference.cpp b/js/src/vm/TypeInference.cpp index a36926eb94..31fd9af6e3 100644 --- a/js/src/vm/TypeInference.cpp +++ b/js/src/vm/TypeInference.cpp @@ -1491,6 +1491,13 @@ js::FinishCompilation(JSContext* cx, HandleScript script, CompilerConstraintList succeeded = false; } + // Add this compilation to the inlinedCompilations list of each inlined + // script, so we can invalidate it on changes to stack type sets. + if (entry.script != script) { + if (!entry.script->types()->addInlinedCompilation(*precompileInfo)) + succeeded = false; + } + // If necessary, add constraints to trigger invalidation on the script // after any future changes to the stack type sets. if (entry.script->hasFreezeConstraints()) @@ -1880,37 +1887,6 @@ ObjectGroup::initialHeap(CompilerConstraintList* constraints) namespace { -// Constraint which triggers recompilation on any type change in an inlined -// script. The freeze constraints added to stack type sets will only directly -// invalidate the script containing those stack type sets. To invalidate code -// for scripts into which the base script was inlined, ObjectStateChange is used. -class ConstraintDataFreezeObjectForInlinedCall -{ - public: - ConstraintDataFreezeObjectForInlinedCall() - {} - - const char* kind() { return "freezeObjectForInlinedCall"; } - - bool invalidateOnNewType(TypeSet::Type type) { return false; } - bool invalidateOnNewPropertyState(TypeSet* property) { return false; } - bool invalidateOnNewObjectState(ObjectGroup* group) { - // We don't keep track of the exact dependencies the caller has on its - // inlined scripts' type sets, so always invalidate the caller. - return true; - } - - bool constraintHolds(JSContext* cx, - const HeapTypeSetKey& property, TemporaryTypeSet* expected) - { - return true; - } - - bool shouldSweep() { return false; } - - JSCompartment* maybeCompartment() { return nullptr; } -}; - // Constraint which triggers recompilation when a typed array's data becomes // invalid. class ConstraintDataFreezeObjectForTypedArrayData @@ -1984,16 +1960,6 @@ class ConstraintDataFreezeObjectForUnboxedConvertedToNative } /* anonymous namespace */ -void -TypeSet::ObjectKey::watchStateChangeForInlinedCall(CompilerConstraintList* constraints) -{ - HeapTypeSetKey objectProperty = property(JSID_EMPTY); - LifoAlloc* alloc = constraints->alloc(); - - typedef CompilerConstraintInstance T; - constraints->add(alloc->new_(alloc, objectProperty, ConstraintDataFreezeObjectForInlinedCall())); -} - void TypeSet::ObjectKey::watchStateChangeForTypedArrayData(CompilerConstraintList* constraints) { @@ -2588,11 +2554,12 @@ TypeZone::addPendingRecompile(JSContext* cx, JSScript* script) if (script->hasIonScript()) addPendingRecompile(cx, script->ionScript()->recompileInfo()); - // When one script is inlined into another the caller listens to state - // changes on the callee's script, so trigger these to force recompilation - // of any such callers. - if (script->functionNonDelazifying() && !script->functionNonDelazifying()->hasLazyGroup()) - ObjectStateChange(cx, script->functionNonDelazifying()->group(), false); + // Trigger recompilation of any callers inlining this script. + if (TypeScript* types = script->types()) { + for (RecompileInfo info : types->inlinedCompilations()) + addPendingRecompile(cx, info); + types->inlinedCompilations().clearAndFree(); + } } void @@ -2884,6 +2851,9 @@ ObjectGroup::markStateChange(ExclusiveContext* cxArg) void ObjectGroup::setFlags(ExclusiveContext* cx, ObjectGroupFlags flags) { + MOZ_ASSERT(!(flags & OBJECT_FLAG_UNKNOWN_PROPERTIES), + "Should use markUnknown to set unknownProperties"); + if (hasAllFlags(flags)) return; @@ -4449,6 +4419,19 @@ JSScript::maybeSweepTypes(AutoClearTypeInferenceStateOnOOM* oom) TypeZone& types = zone()->types; + // Sweep the inlinedCompilations Vector. + { + RecompileInfoVector& inlinedCompilations = types_->inlinedCompilations(); + size_t dest = 0; + for (size_t i = 0; i < inlinedCompilations.length(); i++) { + if (inlinedCompilations[i].shouldSweep(types)) + continue; + inlinedCompilations[dest] = inlinedCompilations[i]; + dest++; + } + inlinedCompilations.shrinkTo(dest); + } + // Destroy all type information attached to the script if desired. We can // only do this if nothing has been compiled for the script, which will be // the case unless the script has been compiled since we started sweeping. @@ -4487,7 +4470,7 @@ JSScript::maybeSweepTypes(AutoClearTypeInferenceStateOnOOM* oom) void TypeScript::destroy() { - js_free(this); + js_delete(this); } void diff --git a/js/src/vm/TypeInference.h b/js/src/vm/TypeInference.h index 58c606912c..ae593a4e73 100644 --- a/js/src/vm/TypeInference.h +++ b/js/src/vm/TypeInference.h @@ -264,7 +264,6 @@ class TypeSet bool unknownProperties(); bool hasFlags(CompilerConstraintList* constraints, ObjectGroupFlags flags); bool hasStableClassAndProto(CompilerConstraintList* constraints); - void watchStateChangeForInlinedCall(CompilerConstraintList* constraints); void watchStateChangeForTypedArrayData(CompilerConstraintList* constraints); void watchStateChangeForUnboxedConvertedToNative(CompilerConstraintList* constraints); HeapTypeSetKey property(jsid id); @@ -1066,15 +1065,122 @@ inline bool isInlinableCall(jsbytecode* pc); bool ClassCanHaveExtraProperties(const Class* clasp); + +/* + * Information about the result of the compilation of a script. This structure + * stored in the TypeCompartment is indexed by the RecompileInfo. This + * indirection enables the invalidation of all constraints related to the same + * compilation. + */ +class CompilerOutput +{ + // If this compilation has not been invalidated, the associated script and + // kind of compilation being performed. + JSScript* script_; + + // Whether this compilation is about to be invalidated. + bool pendingInvalidation_ : 1; + + // During sweeping, the list of compiler outputs is compacted and invalidated + // outputs are removed. This gives the new index for a valid compiler output. + uint32_t sweepIndex_ : 31; + + public: + static const uint32_t INVALID_SWEEP_INDEX = static_cast(1 << 31) - 1; + + CompilerOutput() + : script_(nullptr), + pendingInvalidation_(false), sweepIndex_(INVALID_SWEEP_INDEX) + {} + + explicit CompilerOutput(JSScript* script) + : script_(script), + pendingInvalidation_(false), sweepIndex_(INVALID_SWEEP_INDEX) + {} + + JSScript* script() const { return script_; } + + inline jit::IonScript* ion() const; + + bool isValid() const { + return script_ != nullptr; + } + void invalidate() { + script_ = nullptr; + } + + void setPendingInvalidation() { + pendingInvalidation_ = true; + } + bool pendingInvalidation() { + return pendingInvalidation_; + } + + void setSweepIndex(uint32_t index) { + if (index >= INVALID_SWEEP_INDEX) + MOZ_CRASH(); + sweepIndex_ = index; + } + uint32_t sweepIndex() { + MOZ_ASSERT(sweepIndex_ != INVALID_SWEEP_INDEX); + return sweepIndex_; + } +}; + +class RecompileInfo +{ + // Index in the TypeZone's compilerOutputs or sweepCompilerOutputs arrays, + // depending on the generation value. + uint32_t outputIndex : 31; + + // If out of sync with the TypeZone's generation, this index is for the + // zone's sweepCompilerOutputs rather than compilerOutputs. + uint32_t generation : 1; + + public: + RecompileInfo(uint32_t outputIndex, uint32_t generation) + : outputIndex(outputIndex), generation(generation) + {} + + RecompileInfo() + : outputIndex(JS_BITMASK(31)), generation(0) + {} + + bool operator==(const RecompileInfo& other) const { + return outputIndex == other.outputIndex && generation == other.generation; + } + + CompilerOutput* compilerOutput(TypeZone& types) const; + CompilerOutput* compilerOutput(JSContext* cx) const; + bool shouldSweep(TypeZone& types); +}; + +// The RecompileInfoVector has a MinInlineCapacity of one so that invalidating a +// single IonScript doesn't require an allocation. +typedef Vector RecompileInfoVector; /* Persistent type information for a script, retained across GCs. */ class TypeScript { friend class ::JSScript; + // The freeze constraints added to stack type sets will only directly + // invalidate the script containing those stack type sets. This Vector + // contains compilations that inlined this script, so we can invalidate + // them as well. + RecompileInfoVector inlinedCompilations_; // Variable-size array StackTypeSet typeArray_[1]; public: + RecompileInfoVector& inlinedCompilations() { + return inlinedCompilations_; + } + [[nodiscard]] bool addInlinedCompilation(RecompileInfo info) { + if (!inlinedCompilations_.empty() && inlinedCompilations_.back() == info) + return true; + return inlinedCompilations_.append(info); + } + /* Array of type sets for variables and JOF_TYPESET ops. */ StackTypeSet* typeArray() const { // Ensure typeArray_ is the last data member of TypeScript. @@ -1208,94 +1314,6 @@ class HeapTypeSetKey bool couldBeConstant(CompilerConstraintList* constraints); }; -/* - * Information about the result of the compilation of a script. This structure - * stored in the TypeCompartment is indexed by the RecompileInfo. This - * indirection enables the invalidation of all constraints related to the same - * compilation. - */ -class CompilerOutput -{ - // If this compilation has not been invalidated, the associated script and - // kind of compilation being performed. - JSScript* script_; - - // Whether this compilation is about to be invalidated. - bool pendingInvalidation_ : 1; - - // During sweeping, the list of compiler outputs is compacted and invalidated - // outputs are removed. This gives the new index for a valid compiler output. - uint32_t sweepIndex_ : 31; - - public: - static const uint32_t INVALID_SWEEP_INDEX = static_cast(1 << 31) - 1; - - CompilerOutput() - : script_(nullptr), - pendingInvalidation_(false), sweepIndex_(INVALID_SWEEP_INDEX) - {} - - explicit CompilerOutput(JSScript* script) - : script_(script), - pendingInvalidation_(false), sweepIndex_(INVALID_SWEEP_INDEX) - {} - - JSScript* script() const { return script_; } - - inline jit::IonScript* ion() const; - - bool isValid() const { - return script_ != nullptr; - } - void invalidate() { - script_ = nullptr; - } - - void setPendingInvalidation() { - pendingInvalidation_ = true; - } - bool pendingInvalidation() { - return pendingInvalidation_; - } - - void setSweepIndex(uint32_t index) { - if (index >= INVALID_SWEEP_INDEX) - MOZ_CRASH(); - sweepIndex_ = index; - } - uint32_t sweepIndex() { - MOZ_ASSERT(sweepIndex_ != INVALID_SWEEP_INDEX); - return sweepIndex_; - } -}; - -class RecompileInfo -{ - // Index in the TypeZone's compilerOutputs or sweepCompilerOutputs arrays, - // depending on the generation value. - uint32_t outputIndex : 31; - - // If out of sync with the TypeZone's generation, this index is for the - // zone's sweepCompilerOutputs rather than compilerOutputs. - uint32_t generation : 1; - - public: - RecompileInfo(uint32_t outputIndex, uint32_t generation) - : outputIndex(outputIndex), generation(generation) - {} - - RecompileInfo() - : outputIndex(JS_BITMASK(31)), generation(0) - {} - - CompilerOutput* compilerOutput(TypeZone& types) const; - CompilerOutput* compilerOutput(JSContext* cx) const; - bool shouldSweep(TypeZone& types); -}; - -// The RecompileInfoVector has a MinInlineCapacity of one so that invalidating a -// single IonScript doesn't require an allocation. -typedef Vector RecompileInfoVector; struct AutoEnterAnalysis;