mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-10 03:45:46 +00:00
Bug 1357680 part 1 - Track Ion-inlined scripts explicitly so we can inline functions with unknown properties. r=bhackett
This commit is contained in:
parent
17ca3b4db0
commit
cc0764fefd
@ -197,7 +197,6 @@ namespace JS {
|
||||
_(CantInlineTooManyArgs) \
|
||||
_(CantInlineNeedsArgsObj) \
|
||||
_(CantInlineDebuggee) \
|
||||
_(CantInlineUnknownProps) \
|
||||
_(CantInlineExceededDepth) \
|
||||
_(CantInlineExceededTotalBytecodeLength) \
|
||||
_(CantInlineBigCaller) \
|
||||
|
@ -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;
|
||||
|
@ -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<ConstraintDataFreezeObjectForInlinedCall> T;
|
||||
constraints->add(alloc->new_<T>(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.
|
||||
|
@ -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<uint32_t>(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<RecompileInfo, 1, SystemAllocPolicy> 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<uint32_t>(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<RecompileInfo, 1, SystemAllocPolicy> RecompileInfoVector;
|
||||
|
||||
struct AutoEnterAnalysis;
|
||||
|
||||
class TypeZone
|
||||
|
Loading…
Reference in New Issue
Block a user