diff --git a/js/public/TrackedOptimizationInfo.h b/js/public/TrackedOptimizationInfo.h index 74282fde5864..64164c3169cc 100644 --- a/js/public/TrackedOptimizationInfo.h +++ b/js/public/TrackedOptimizationInfo.h @@ -197,7 +197,6 @@ namespace JS { _(CantInlineTooManyArgs) \ _(CantInlineNeedsArgsObj) \ _(CantInlineDebuggee) \ - _(CantInlineUnknownProps) \ _(CantInlineExceededDepth) \ _(CantInlineExceededTotalBytecodeLength) \ _(CantInlineBigCaller) \ diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index 7937da88d99f..d4ba09fbcf86 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -502,12 +502,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; } @@ -4000,10 +3994,6 @@ 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; diff --git a/js/src/vm/TypeInference.cpp b/js/src/vm/TypeInference.cpp index 7bbffb134776..e55f41cda5a9 100644 --- a/js/src/vm/TypeInference.cpp +++ b/js/src/vm/TypeInference.cpp @@ -1494,6 +1494,15 @@ 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)) { + ReportOutOfMemory(cx); + return false; + } + } + // If necessary, add constraints to trigger invalidation on the script // after any future changes to the stack type sets. if (entry.script->hasFreezeConstraints()) @@ -1900,37 +1909,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 @@ -2004,16 +1982,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) { @@ -2609,11 +2577,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(); + } } #ifdef JS_CRASH_DIAGNOSTICS @@ -4437,6 +4406,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. @@ -4475,7 +4457,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 6791e5a51080..64d64d2375d8 100644 --- a/js/src/vm/TypeInference.h +++ b/js/src/vm/TypeInference.h @@ -261,7 +261,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); @@ -1085,15 +1084,123 @@ 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_; + } + MOZ_MUST_USE 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. @@ -1236,95 +1343,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; class TypeZone