mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-18 15:55:36 +00:00
Bug 1635375 part 7 - Invalidate Warp code when transpiled IC stubs change. r=iain
Set a flag on stubs used by the transpiler and then treat those similar to a constraint in the old system: invalidate when attaching a new IC stub (in front of it), when updating the stub, or when unlinking the stub. This is necessary to avoid repeated bailouts followed by a deoptimization in CheckFrequentBailouts. Differential Revision: https://phabricator.services.mozilla.com/D81823
This commit is contained in:
parent
93a769fb25
commit
ff9dfe44e3
@ -1706,6 +1706,11 @@ static void HandleBoundsCheckFailure(JSContext* cx, HandleScript outerScript,
|
||||
|
||||
static void HandleShapeGuardFailure(JSContext* cx, HandleScript outerScript,
|
||||
HandleScript innerScript) {
|
||||
if (JitOptions.warpBuilder) {
|
||||
// Warp handles this by invalidating when the IC stub changes.
|
||||
return;
|
||||
}
|
||||
|
||||
JitSpew(JitSpew_IonBailouts,
|
||||
"Shape guard failure %s:%u:%u, inlined into %s:%u:%u",
|
||||
innerScript->filename(), innerScript->lineno(), innerScript->column(),
|
||||
@ -1722,6 +1727,11 @@ static void HandleShapeGuardFailure(JSContext* cx, HandleScript outerScript,
|
||||
|
||||
static void HandleBaselineInfoBailout(JSContext* cx, HandleScript outerScript,
|
||||
HandleScript innerScript) {
|
||||
if (JitOptions.warpBuilder) {
|
||||
// Warp handles this by invalidating when the IC stub changes.
|
||||
return;
|
||||
}
|
||||
|
||||
JitSpew(JitSpew_IonBailouts,
|
||||
"Baseline info failure %s:%u:%u, inlined into %s:%u:%u",
|
||||
innerScript->filename(), innerScript->lineno(), innerScript->column(),
|
||||
|
@ -2215,6 +2215,7 @@ ICStub* js::jit::AttachBaselineCacheIRStub(
|
||||
// attach. Just return nullptr, the caller should do nothing in this
|
||||
// case.
|
||||
if (updated) {
|
||||
stub->maybeInvalidateWarp(cx, outerScript);
|
||||
*attached = true;
|
||||
} else {
|
||||
JitSpew(JitSpew_BaselineICFallback,
|
||||
@ -2240,6 +2241,8 @@ ICStub* js::jit::AttachBaselineCacheIRStub(
|
||||
// about the chain much easier.
|
||||
ResetEnteredCounts(stub);
|
||||
|
||||
stub->maybeInvalidateWarp(cx, outerScript);
|
||||
|
||||
switch (stubKind) {
|
||||
case BaselineCacheIRStubKind::Regular: {
|
||||
auto newStub = new (newStubMem) ICCacheIR_Regular(code, stubInfo);
|
||||
|
@ -546,12 +546,14 @@ ICStubIterator& ICStubIterator::operator++() {
|
||||
return *this;
|
||||
}
|
||||
|
||||
void ICStubIterator::unlink(JSContext* cx) {
|
||||
void ICStubIterator::unlink(JSContext* cx, JSScript* script) {
|
||||
MOZ_ASSERT(currentStub_->next() != nullptr);
|
||||
MOZ_ASSERT(currentStub_ != fallbackStub_);
|
||||
MOZ_ASSERT(!unlinked_);
|
||||
|
||||
fallbackStub_->unlinkStub(cx->zone(), previousStub_, currentStub_);
|
||||
fallbackStub_->maybeInvalidateWarp(cx, script);
|
||||
fallbackStub_->unlinkStubDontInvalidateWarp(cx->zone(), previousStub_,
|
||||
currentStub_);
|
||||
|
||||
// Mark the current iterator position as unlinked, so operator++ works
|
||||
// properly.
|
||||
@ -603,6 +605,19 @@ uint32_t ICStub::getEnteredCount() const {
|
||||
}
|
||||
}
|
||||
|
||||
void ICFallbackStub::maybeInvalidateWarp(JSContext* cx, JSScript* script) {
|
||||
if (!state_.usedByTranspiler()) {
|
||||
return;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(JitOptions.warpBuilder);
|
||||
clearUsedByTranspiler();
|
||||
|
||||
if (script->hasIonScript()) {
|
||||
Invalidate(cx, script);
|
||||
}
|
||||
}
|
||||
|
||||
void ICStub::updateCode(JitCode* code) {
|
||||
// Write barrier on the old code.
|
||||
JitCode::writeBarrierPre(jitCode());
|
||||
@ -772,7 +787,7 @@ static void TryAttachStub(const char* name, JSContext* cx, BaselineFrame* frame,
|
||||
ICFallbackStub* stub, BaselineCacheIRStubKind kind,
|
||||
Args&&... args) {
|
||||
if (stub->state().maybeTransition()) {
|
||||
stub->discardStubs(cx);
|
||||
stub->discardStubs(cx, frame->script());
|
||||
}
|
||||
|
||||
if (stub->state().canAttachStub()) {
|
||||
@ -805,7 +820,8 @@ static void TryAttachStub(const char* name, JSContext* cx, BaselineFrame* frame,
|
||||
}
|
||||
}
|
||||
|
||||
void ICFallbackStub::unlinkStub(Zone* zone, ICStub* prev, ICStub* stub) {
|
||||
void ICFallbackStub::unlinkStubDontInvalidateWarp(Zone* zone, ICStub* prev,
|
||||
ICStub* stub) {
|
||||
MOZ_ASSERT(stub->next());
|
||||
|
||||
if (prev) {
|
||||
@ -849,9 +865,9 @@ void ICFallbackStub::unlinkStub(Zone* zone, ICStub* prev, ICStub* stub) {
|
||||
#endif
|
||||
}
|
||||
|
||||
void ICFallbackStub::discardStubs(JSContext* cx) {
|
||||
void ICFallbackStub::discardStubs(JSContext* cx, JSScript* script) {
|
||||
for (ICStubIterator iter = beginChain(); !iter.atEnd(); iter++) {
|
||||
iter.unlink(cx);
|
||||
iter.unlink(cx, script);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1817,7 +1833,8 @@ bool FallbackICCodeCompiler::emit_ToBool() {
|
||||
return tailCallVM<Fn, DoToBoolFallback>(masm);
|
||||
}
|
||||
|
||||
static void StripPreliminaryObjectStubs(JSContext* cx, ICFallbackStub* stub) {
|
||||
static void StripPreliminaryObjectStubs(JSContext* cx, ICFallbackStub* stub,
|
||||
JSScript* script) {
|
||||
// Before the new script properties analysis has been performed on a type,
|
||||
// all instances of that type have the maximum number of fixed slots.
|
||||
// Afterwards, the objects (even the preliminary ones) might be changed
|
||||
@ -1830,13 +1847,13 @@ static void StripPreliminaryObjectStubs(JSContext* cx, ICFallbackStub* stub) {
|
||||
for (ICStubIterator iter = stub->beginChain(); !iter.atEnd(); iter++) {
|
||||
if (iter->isCacheIR_Regular() &&
|
||||
iter->toCacheIR_Regular()->hasPreliminaryObject()) {
|
||||
iter.unlink(cx);
|
||||
iter.unlink(cx, script);
|
||||
} else if (iter->isCacheIR_Monitored() &&
|
||||
iter->toCacheIR_Monitored()->hasPreliminaryObject()) {
|
||||
iter.unlink(cx);
|
||||
iter.unlink(cx, script);
|
||||
} else if (iter->isCacheIR_Updated() &&
|
||||
iter->toCacheIR_Updated()->hasPreliminaryObject()) {
|
||||
iter.unlink(cx);
|
||||
iter.unlink(cx, script);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1848,7 +1865,7 @@ static bool TryAttachGetPropStub(const char* name, JSContext* cx,
|
||||
bool attached = false;
|
||||
|
||||
if (stub->state().maybeTransition()) {
|
||||
stub->discardStubs(cx);
|
||||
stub->discardStubs(cx, frame->script());
|
||||
}
|
||||
|
||||
if (stub->state().canAttachStub()) {
|
||||
@ -1869,7 +1886,7 @@ static bool TryAttachGetPropStub(const char* name, JSContext* cx,
|
||||
if (gen.shouldNotePreliminaryObjectStub()) {
|
||||
newStub->toCacheIR_Monitored()->notePreliminaryObject();
|
||||
} else if (gen.shouldUnlinkPreliminaryObjectStubs()) {
|
||||
StripPreliminaryObjectStubs(cx, stub);
|
||||
StripPreliminaryObjectStubs(cx, stub, script);
|
||||
}
|
||||
}
|
||||
} break;
|
||||
@ -2130,7 +2147,7 @@ bool DoSetElemFallback(JSContext* cx, BaselineFrame* frame,
|
||||
bool attached = false;
|
||||
|
||||
if (stub->state().maybeTransition()) {
|
||||
stub->discardStubs(cx);
|
||||
stub->discardStubs(cx, script);
|
||||
}
|
||||
|
||||
if (stub->state().canAttachStub()) {
|
||||
@ -2151,7 +2168,7 @@ bool DoSetElemFallback(JSContext* cx, BaselineFrame* frame,
|
||||
if (gen.shouldNotePreliminaryObjectStub()) {
|
||||
newStub->toCacheIR_Updated()->notePreliminaryObject();
|
||||
} else if (gen.shouldUnlinkPreliminaryObjectStubs()) {
|
||||
StripPreliminaryObjectStubs(cx, stub);
|
||||
StripPreliminaryObjectStubs(cx, stub, script);
|
||||
}
|
||||
|
||||
if (gen.attachedTypedArrayOOBStub()) {
|
||||
@ -2212,7 +2229,7 @@ bool DoSetElemFallback(JSContext* cx, BaselineFrame* frame,
|
||||
// The SetObjectElement call might have entered this IC recursively, so try
|
||||
// to transition.
|
||||
if (stub->state().maybeTransition()) {
|
||||
stub->discardStubs(cx);
|
||||
stub->discardStubs(cx, script);
|
||||
}
|
||||
|
||||
bool canAttachStub = stub->state().canAttachStub();
|
||||
@ -2239,7 +2256,7 @@ bool DoSetElemFallback(JSContext* cx, BaselineFrame* frame,
|
||||
if (gen.shouldNotePreliminaryObjectStub()) {
|
||||
newStub->toCacheIR_Updated()->notePreliminaryObject();
|
||||
} else if (gen.shouldUnlinkPreliminaryObjectStubs()) {
|
||||
StripPreliminaryObjectStubs(cx, stub);
|
||||
StripPreliminaryObjectStubs(cx, stub, script);
|
||||
}
|
||||
}
|
||||
} break;
|
||||
@ -2722,7 +2739,7 @@ bool DoSetPropFallback(JSContext* cx, BaselineFrame* frame,
|
||||
DeferType deferType = DeferType::None;
|
||||
bool attached = false;
|
||||
if (stub->state().maybeTransition()) {
|
||||
stub->discardStubs(cx);
|
||||
stub->discardStubs(cx, script);
|
||||
}
|
||||
|
||||
if (stub->state().canAttachStub()) {
|
||||
@ -2744,7 +2761,7 @@ bool DoSetPropFallback(JSContext* cx, BaselineFrame* frame,
|
||||
if (gen.shouldNotePreliminaryObjectStub()) {
|
||||
newStub->toCacheIR_Updated()->notePreliminaryObject();
|
||||
} else if (gen.shouldUnlinkPreliminaryObjectStubs()) {
|
||||
StripPreliminaryObjectStubs(cx, stub);
|
||||
StripPreliminaryObjectStubs(cx, stub, script);
|
||||
}
|
||||
}
|
||||
} break;
|
||||
@ -2801,7 +2818,7 @@ bool DoSetPropFallback(JSContext* cx, BaselineFrame* frame,
|
||||
// The SetProperty call might have entered this IC recursively, so try
|
||||
// to transition.
|
||||
if (stub->state().maybeTransition()) {
|
||||
stub->discardStubs(cx);
|
||||
stub->discardStubs(cx, script);
|
||||
}
|
||||
|
||||
bool canAttachStub = stub->state().canAttachStub();
|
||||
@ -2829,7 +2846,7 @@ bool DoSetPropFallback(JSContext* cx, BaselineFrame* frame,
|
||||
if (gen.shouldNotePreliminaryObjectStub()) {
|
||||
newStub->toCacheIR_Updated()->notePreliminaryObject();
|
||||
} else if (gen.shouldUnlinkPreliminaryObjectStubs()) {
|
||||
StripPreliminaryObjectStubs(cx, stub);
|
||||
StripPreliminaryObjectStubs(cx, stub, script);
|
||||
}
|
||||
}
|
||||
} break;
|
||||
@ -2926,7 +2943,7 @@ bool DoCallFallback(JSContext* cx, BaselineFrame* frame, ICCall_Fallback* stub,
|
||||
|
||||
// Transition stub state to megamorphic or generic if warranted.
|
||||
if (stub->state().maybeTransition()) {
|
||||
stub->discardStubs(cx);
|
||||
stub->discardStubs(cx, script);
|
||||
}
|
||||
|
||||
bool canAttachStub = stub->state().canAttachStub();
|
||||
@ -2999,7 +3016,7 @@ bool DoCallFallback(JSContext* cx, BaselineFrame* frame, ICCall_Fallback* stub,
|
||||
|
||||
// Try to transition again in case we called this IC recursively.
|
||||
if (stub->state().maybeTransition()) {
|
||||
stub->discardStubs(cx);
|
||||
stub->discardStubs(cx, script);
|
||||
}
|
||||
canAttachStub = stub->state().canAttachStub();
|
||||
|
||||
@ -3059,7 +3076,7 @@ bool DoSpreadCallFallback(JSContext* cx, BaselineFrame* frame,
|
||||
|
||||
// Transition stub state to megamorphic or generic if warranted.
|
||||
if (stub->state().maybeTransition()) {
|
||||
stub->discardStubs(cx);
|
||||
stub->discardStubs(cx, script);
|
||||
}
|
||||
|
||||
// Try attaching a call stub.
|
||||
|
@ -373,7 +373,7 @@ class ICStubIterator {
|
||||
|
||||
bool atEnd() const { return currentStub_ == (ICStub*)fallbackStub_; }
|
||||
|
||||
void unlink(JSContext* cx);
|
||||
void unlink(JSContext* cx, JSScript* script);
|
||||
};
|
||||
|
||||
//
|
||||
@ -705,9 +705,16 @@ class ICFallbackStub : public ICStub {
|
||||
|
||||
ICStubIterator beginChain() { return ICStubIterator(this); }
|
||||
|
||||
void discardStubs(JSContext* cx);
|
||||
void discardStubs(JSContext* cx, JSScript* script);
|
||||
|
||||
void unlinkStub(Zone* zone, ICStub* prev, ICStub* stub);
|
||||
void clearUsedByTranspiler() { state_.clearUsedByTranspiler(); }
|
||||
void setUsedByTranspiler() { state_.setUsedByTranspiler(); }
|
||||
|
||||
// If the transpiler optimized based on this IC, invalidate the script's Warp
|
||||
// code.
|
||||
void maybeInvalidateWarp(JSContext* cx, JSScript* script);
|
||||
|
||||
void unlinkStubDontInvalidateWarp(Zone* zone, ICStub* prev, ICStub* stub);
|
||||
|
||||
// Return the number of times this stub has successfully provided a value to
|
||||
// the caller.
|
||||
|
@ -26,7 +26,11 @@ class ICState {
|
||||
enum class Mode : uint8_t { Specialized = 0, Megamorphic, Generic };
|
||||
|
||||
private:
|
||||
Mode mode_;
|
||||
uint32_t mode_ : 2;
|
||||
|
||||
// Whether WarpOracle created a snapshot based on stubs attached to this
|
||||
// Baseline IC.
|
||||
bool usedByTranspiler_ : 1;
|
||||
|
||||
// Number of optimized stubs currently attached to this IC.
|
||||
uint8_t numOptimizedStubs_;
|
||||
@ -36,9 +40,14 @@ class ICState {
|
||||
|
||||
static const size_t MaxOptimizedStubs = 6;
|
||||
|
||||
void setMode(Mode mode) {
|
||||
mode_ = uint32_t(mode);
|
||||
MOZ_ASSERT(Mode(mode_) == mode, "mode must fit in bitfield");
|
||||
}
|
||||
|
||||
void transition(Mode mode) {
|
||||
MOZ_ASSERT(mode > mode_);
|
||||
mode_ = mode;
|
||||
MOZ_ASSERT(mode > this->mode());
|
||||
setMode(mode);
|
||||
numFailures_ = 0;
|
||||
}
|
||||
|
||||
@ -54,7 +63,7 @@ class ICState {
|
||||
public:
|
||||
ICState() { reset(); }
|
||||
|
||||
Mode mode() const { return mode_; }
|
||||
Mode mode() const { return Mode(mode_); }
|
||||
size_t numOptimizedStubs() const { return numOptimizedStubs_; }
|
||||
bool hasFailures() const { return (numFailures_ != 0); }
|
||||
|
||||
@ -62,7 +71,7 @@ class ICState {
|
||||
// Note: we cannot assert that numOptimizedStubs_ <= MaxOptimizedStubs
|
||||
// because old-style baseline ICs may attach more stubs than
|
||||
// MaxOptimizedStubs allows.
|
||||
if (mode_ == Mode::Generic || JitOptions.disableCacheIR) {
|
||||
if (mode() == Mode::Generic || JitOptions.disableCacheIR) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@ -74,23 +83,24 @@ class ICState {
|
||||
// Note: we cannot assert that numOptimizedStubs_ <= MaxOptimizedStubs
|
||||
// because old-style baseline ICs may attach more stubs than
|
||||
// MaxOptimizedStubs allows.
|
||||
if (mode_ == Mode::Generic) {
|
||||
if (mode() == Mode::Generic) {
|
||||
return false;
|
||||
}
|
||||
if (numOptimizedStubs_ < MaxOptimizedStubs &&
|
||||
numFailures_ < maxFailures()) {
|
||||
return false;
|
||||
}
|
||||
if (numFailures_ == maxFailures() || mode_ == Mode::Megamorphic) {
|
||||
if (numFailures_ == maxFailures() || mode() == Mode::Megamorphic) {
|
||||
transition(Mode::Generic);
|
||||
return true;
|
||||
}
|
||||
MOZ_ASSERT(mode_ == Mode::Specialized);
|
||||
MOZ_ASSERT(mode() == Mode::Specialized);
|
||||
transition(Mode::Megamorphic);
|
||||
return true;
|
||||
}
|
||||
void reset() {
|
||||
mode_ = Mode::Specialized;
|
||||
setMode(Mode::Specialized);
|
||||
usedByTranspiler_ = false;
|
||||
numOptimizedStubs_ = 0;
|
||||
numFailures_ = 0;
|
||||
}
|
||||
@ -118,6 +128,10 @@ class ICState {
|
||||
numOptimizedStubs_--;
|
||||
}
|
||||
void trackUnlinkedAllStubs() { numOptimizedStubs_ = 0; }
|
||||
|
||||
void clearUsedByTranspiler() { usedByTranspiler_ = false; }
|
||||
void setUsedByTranspiler() { usedByTranspiler_ = true; }
|
||||
bool usedByTranspiler() const { return usedByTranspiler_; }
|
||||
};
|
||||
|
||||
} // namespace jit
|
||||
|
@ -494,7 +494,11 @@ void ICScript::purgeOptimizedStubs(Zone* zone) {
|
||||
|
||||
while (stub->next()) {
|
||||
if (!stub->allocatedInFallbackSpace()) {
|
||||
lastStub->toFallbackStub()->unlinkStub(zone, prev, stub);
|
||||
// Note: this is called when discarding JIT code, after invalidating
|
||||
// all Warp code, so we don't need to check for that here.
|
||||
lastStub->toFallbackStub()->clearUsedByTranspiler();
|
||||
lastStub->toFallbackStub()->unlinkStubDontInvalidateWarp(zone, prev,
|
||||
stub);
|
||||
stub = stub->next();
|
||||
continue;
|
||||
}
|
||||
|
@ -727,10 +727,16 @@ AbortReasonOr<Ok> WarpScriptOracle::maybeInlineIC(WarpOpSnapshotList& snapshots,
|
||||
|
||||
const ICEntry& entry = getICEntry(loc);
|
||||
ICStub* stub = entry.firstStub();
|
||||
ICFallbackStub* fallbackStub = entry.fallbackStub();
|
||||
|
||||
uint32_t offset = loc.bytecodeToOffset(script_);
|
||||
|
||||
if (stub->isFallback()) {
|
||||
// Clear the used-by-transpiler flag on the IC. It can still be set from a
|
||||
// previous compilation because we don't clear the flag on every IC when
|
||||
// invalidating.
|
||||
fallbackStub->clearUsedByTranspiler();
|
||||
|
||||
if (stub == fallbackStub) {
|
||||
[[maybe_unused]] unsigned line, column;
|
||||
LineNumberAndColumn(script_, loc, &line, &column);
|
||||
|
||||
@ -738,11 +744,11 @@ AbortReasonOr<Ok> WarpScriptOracle::maybeInlineIC(WarpOpSnapshotList& snapshots,
|
||||
JitSpew(JitSpew_WarpTranspiler,
|
||||
"fallback stub (entered-count: %" PRIu32
|
||||
") for JSOp::%s @ %s:%u:%u",
|
||||
stub->toFallbackStub()->enteredCount(), CodeName(loc.getOp()),
|
||||
fallbackStub->enteredCount(), CodeName(loc.getOp()),
|
||||
script_->filename(), line, column);
|
||||
|
||||
// If the fallback stub was used but there's no optimized stub, use an IC.
|
||||
if (stub->toFallbackStub()->enteredCount() != 0) {
|
||||
if (fallbackStub->enteredCount() != 0) {
|
||||
return Ok();
|
||||
}
|
||||
|
||||
@ -854,5 +860,7 @@ AbortReasonOr<Ok> WarpScriptOracle::maybeInlineIC(WarpOpSnapshotList& snapshots,
|
||||
return abort(AbortReason::Alloc);
|
||||
}
|
||||
|
||||
fallbackStub->setUsedByTranspiler();
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user