Skip to content

Commit 8527dc2

Browse files
committed
[MERGE #5861 @nhat-nguyen] Lazy bailout
Merge pull request #5861 from nhat-nguyen:lazybailout ## Overview + Eager fixed field checks are no longer generated thanks to lazy bailout + Lazy bailout should work for both user JavaScript calls and runtime helper calls + Operations that have implicit calls (like getter/setter) are also guarded by lazy bailout + Lazy bailout points are inserted in the forward pass and removed if not needed during backward pass + For helper calls, we copy the `BailOutInfo` from the bailout path to the call to helper so that the register allocator can fill it correctly + Jit'd frames on the stack that depend on fixed fields are invalidated by changing their return addresses to a common lazy bailout thunk per function. The thunk is responsible for cleaning up the stack, loading the right `BailOutRecord` for each bailout point, and finally calls to the bailout code. Other jit'd functions are invalidated normally ## TODO + Support x86/ARM/ARM64 as lazy bailout only works on x64 for now + `NewScObjArray` helper path needs rework + Instead of having `BailOnNotNativeArray` directly on `NewScObjArray`, insert it on a bailout instruction before `NewScObjArray` + Support Out-of-proc jit + Lower `BailOnImplicitCallPreOp` when we need to attach lazy bailout to instructions that already have preop bailouts + Investigate instructions that have helper path but don't go through `ChangeToHelperCall` + Stack clean up for x86 in lazy bailout thunk + Support `SaveAllRegisterAndBranchBailOut` variant in the thunk + `pdf.js` bug
2 parents b7eaa8f + 39f1ccd commit 8527dc2

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+1891
-366
lines changed

lib/Backend/BackwardPass.cpp

Lines changed: 226 additions & 72 deletions
Large diffs are not rendered by default.

lib/Backend/BackwardPass.h

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ class BackwardPass
4343
bool ProcessBailOutInfo(IR::Instr * instr);
4444
void ProcessBailOutInfo(IR::Instr * instr, BailOutInfo * bailOutInfo);
4545
IR::Instr* ProcessPendingPreOpBailOutInfo(IR::Instr *const currentInstr);
46+
void ClearDstUseForPostOpLazyBailOut(IR::Instr *instr);
4647
void ProcessBailOutArgObj(BailOutInfo * bailOutInfo, BVSparse<JitArenaAllocator> * byteCodeUpwardExposedUsed);
4748
void ProcessBailOutConstants(BailOutInfo * bailOutInfo, BVSparse<JitArenaAllocator> * byteCodeUpwardExposedUsed, BVSparse<JitArenaAllocator>* argSymsBv);
4849
void ProcessBailOutCopyProps(BailOutInfo * bailOutInfo, BVSparse<JitArenaAllocator> * byteCodeUpwardExposedUsed, BVSparse<JitArenaAllocator>* argSymsBv);
@@ -73,7 +74,7 @@ class BackwardPass
7374
void DumpMarkTemp();
7475
#endif
7576

76-
static bool UpdateImplicitCallBailOutKind(IR::Instr *const instr, bool needsBailOutOnImplicitCall);
77+
static bool UpdateImplicitCallBailOutKind(IR::Instr *const instr, bool needsBailOutOnImplicitCall, bool needsLazyBailOut);
7778

7879
bool ProcessNoImplicitCallUses(IR::Instr *const instr);
7980
void ProcessNoImplicitCallDef(IR::Instr *const instr);
@@ -102,9 +103,11 @@ class BackwardPass
102103

103104
void TrackFloatSymEquivalence(IR::Instr *const instr);
104105

105-
void DeadStoreImplicitCallBailOut(IR::Instr * instr, bool hasLiveFields);
106+
bool IsLazyBailOutCurrentlyNeeeded(IR::Instr * instr) const;
107+
void DeadStoreImplicitCallBailOut(IR::Instr * instr, bool hasLiveFields, bool needsLazyBailOut);
106108
void DeadStoreTypeCheckBailOut(IR::Instr * instr);
107-
bool IsImplicitCallBailOutCurrentlyNeeded(IR::Instr * instr, bool mayNeedImplicitCallBailOut, bool hasLiveFields);
109+
void DeadStoreLazyBailOut(IR::Instr * instr, bool needsLazyBailOut);
110+
bool IsImplicitCallBailOutCurrentlyNeeded(IR::Instr * instr, bool mayNeedImplicitCallBailOut, bool needLazyBailOut, bool hasLiveFields);
108111
bool NeedBailOutOnImplicitCallsForTypedArrayStore(IR::Instr* instr);
109112
bool TrackNoImplicitCallInlinees(IR::Instr *instr);
110113
bool ProcessBailOnNoProfile(IR::Instr *instr, BasicBlock *block);

lib/Backend/BailOut.cpp

Lines changed: 109 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,85 @@
1313

1414
extern const IRType RegTypes[RegNumCount];
1515

16+
// In `FillBailOutRecord`, some of the fields of BailOutInfo are modified directly,
17+
// so simply doing a shallow copy of pointers when duplicating the BailOutInfo to
18+
// the helper calls for lazy bailouts will mess things up.Make a deep copies of such fields.
19+
void BailOutInfo::PartialDeepCopyTo(BailOutInfo * const other) const
20+
{
21+
// Primitive types
22+
other->wasCloned = this->wasCloned;
23+
other->isInvertedBranch = this->isInvertedBranch;
24+
other->sharedBailOutKind = this->sharedBailOutKind;
25+
other->isLoopTopBailOutInfo = this->isLoopTopBailOutInfo;
26+
other->bailOutOffset = this->bailOutOffset;
27+
other->polymorphicCacheIndex = this->polymorphicCacheIndex;
28+
other->startCallCount = this->startCallCount;
29+
other->totalOutParamCount = this->totalOutParamCount;
30+
other->stackLiteralBailOutInfoCount = this->stackLiteralBailOutInfoCount;
31+
#if DBG
32+
other->wasCopied = this->wasCopied;
33+
#endif
34+
35+
other->bailOutRecord = this->bailOutRecord;
36+
37+
this->capturedValues->CopyTo(this->bailOutFunc->m_alloc, other->capturedValues);
38+
this->usedCapturedValues->CopyTo(this->bailOutFunc->m_alloc, other->usedCapturedValues);
39+
40+
if (this->byteCodeUpwardExposedUsed != nullptr)
41+
{
42+
other->byteCodeUpwardExposedUsed = this->byteCodeUpwardExposedUsed->CopyNew(this->bailOutFunc->m_alloc);
43+
}
44+
45+
if (this->liveVarSyms != nullptr)
46+
{
47+
other->liveVarSyms = this->liveVarSyms->CopyNew(this->bailOutFunc->m_alloc);
48+
}
49+
50+
if (this->liveLosslessInt32Syms != nullptr)
51+
{
52+
other->liveLosslessInt32Syms = this->liveLosslessInt32Syms->CopyNew(this->bailOutFunc->m_alloc);
53+
}
54+
55+
if (this->liveFloat64Syms != nullptr)
56+
{
57+
other->liveFloat64Syms = this->liveFloat64Syms->CopyNew(this->bailOutFunc->m_alloc);
58+
}
59+
60+
if (this->outParamInlinedArgSlot != nullptr)
61+
{
62+
other->outParamInlinedArgSlot = this->outParamInlinedArgSlot->CopyNew(this->bailOutFunc->m_alloc);
63+
}
64+
65+
other->startCallFunc = this->startCallFunc;
66+
other->argOutSyms = this->argOutSyms;
67+
other->startCallInfo = this->startCallInfo;
68+
other->stackLiteralBailOutInfo = this->stackLiteralBailOutInfo;
69+
other->outParamOffsets = this->outParamOffsets;
70+
71+
72+
#ifdef _M_IX86
73+
other->outParamFrameAdjustArgSlot = this->outParamFrameAdjustArgSlot;
74+
other->inlinedStartCall = this->inlinedStartCall;
75+
#endif
76+
77+
other->bailOutInstr = this->bailOutInstr;
78+
79+
#if ENABLE_DEBUG_CONFIG_OPTIONS
80+
other->bailOutOpcode = this->bailOutOpcode;
81+
#endif
82+
83+
other->bailOutFunc = this->bailOutFunc;
84+
other->branchConditionOpnd = this->branchConditionOpnd;
85+
}
86+
1687
void
1788
BailOutInfo::Clear(JitArenaAllocator * allocator)
1889
{
19-
// Currently, we don't have a case where we delete bailout info after we allocated the bailout record
20-
Assert(!bailOutRecord);
90+
// Previously, we don't have a case where we delete bailout info after we allocated the bailout record.
91+
// However, since lazy bailouts can now be attached on helper call instructions, and those instructions
92+
// might sometimes be removed in Peeps, we will hit those cases. Make sure that in such cases, lazy bailout
93+
// is the only bailout reason we have.
94+
Assert(bailOutRecord == nullptr || BailOutInfo::OnlyHasLazyBailOut(bailOutRecord->bailOutKind));
2195
if (this->capturedValues && this->capturedValues->DecrementRefCount() == 0)
2296
{
2397
this->capturedValues->constantValues.Clear(allocator);
@@ -69,6 +143,37 @@ BailOutInfo::Clear(JitArenaAllocator * allocator)
69143
#endif
70144
}
71145

146+
// Refer to comments in the header file
147+
void BailOutInfo::ClearUseOfDst(SymID id)
148+
{
149+
Assert(id != SymID_Invalid);
150+
if (this->byteCodeUpwardExposedUsed != nullptr &&
151+
this->byteCodeUpwardExposedUsed->Test(id))
152+
{
153+
this->clearedDstByteCodeUpwardExposedUseId = id;
154+
this->byteCodeUpwardExposedUsed->Clear(id);
155+
}
156+
}
157+
158+
void BailOutInfo::RestoreUseOfDst()
159+
{
160+
if (this->byteCodeUpwardExposedUsed != nullptr &&
161+
this->NeedsToRestoreUseOfDst())
162+
{
163+
this->byteCodeUpwardExposedUsed->Set(this->clearedDstByteCodeUpwardExposedUseId);
164+
}
165+
}
166+
167+
bool BailOutInfo::NeedsToRestoreUseOfDst() const
168+
{
169+
return this->clearedDstByteCodeUpwardExposedUseId != SymID_Invalid;
170+
}
171+
172+
SymID BailOutInfo::GetClearedUseOfDstId() const
173+
{
174+
return this->clearedDstByteCodeUpwardExposedUseId;
175+
}
176+
72177
#ifdef _M_IX86
73178

74179
uint
@@ -2852,16 +2957,11 @@ SharedBailOutRecord::SharedBailOutRecord(uint32 bailOutOffset, uint bailOutCache
28522957
this->type = BailoutRecordType::Shared;
28532958
}
28542959

2855-
void LazyBailOutRecord::SetBailOutKind()
2856-
{
2857-
this->bailoutRecord->SetBailOutKind(IR::BailOutKind::LazyBailOut);
2858-
}
2859-
28602960
#if DBG
2861-
void LazyBailOutRecord::Dump(Js::FunctionBody* functionBody)
2961+
void LazyBailOutRecord::Dump(Js::FunctionBody* functionBody) const
28622962
{
28632963
OUTPUT_PRINT(functionBody);
2864-
Output::Print(_u("Bytecode Offset: #%04x opcode: %s"), this->bailoutRecord->GetBailOutOffset(), Js::OpCodeUtil::GetOpCodeName(this->bailoutRecord->GetBailOutOpCode()));
2964+
Output::Print(_u("Bytecode Offset: #%04x opcode: %s"), this->bailOutRecord->GetBailOutOffset(), Js::OpCodeUtil::GetOpCodeName(this->bailOutRecord->GetBailOutOpCode()));
28652965
}
28662966
#endif
28672967

lib/Backend/BailOut.h

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ class BailOutInfo
3030
totalOutParamCount(0), argOutSyms(nullptr), bailOutRecord(nullptr), wasCloned(false), isInvertedBranch(false), sharedBailOutKind(true), isLoopTopBailOutInfo(false),
3131
outParamInlinedArgSlot(nullptr), liveVarSyms(nullptr), liveLosslessInt32Syms(nullptr), liveFloat64Syms(nullptr),
3232
branchConditionOpnd(nullptr),
33-
stackLiteralBailOutInfoCount(0), stackLiteralBailOutInfo(nullptr)
33+
stackLiteralBailOutInfoCount(0), stackLiteralBailOutInfo(nullptr),
34+
clearedDstByteCodeUpwardExposedUseId(SymID_Invalid)
3435
{
3536
Assert(bailOutOffset != Js::Constants::NoByteCodeOffset);
3637
#ifdef _M_IX86
@@ -45,8 +46,23 @@ class BailOutInfo
4546
this->usedCapturedValues = JitAnew(bailOutFunc->m_alloc, CapturedValues);
4647
this->usedCapturedValues->argObjSyms = nullptr;
4748
}
49+
50+
void PartialDeepCopyTo(BailOutInfo *const bailOutInfo) const;
4851
void Clear(JitArenaAllocator * allocator);
4952

53+
// Lazy bailout
54+
//
55+
// Workaround for dealing with use of destination register of `call` instructions with postop lazy bailout.
56+
// As an example, in globopt, we have s1 = Call and s1 is in byteCodeUpwardExposedUse,
57+
// but after lowering, the instructions are: s3 = Call, s1 = s3.
58+
// If we add a postop lazy bailout to s3 = call, we will create a use of s1 right at that instructions.
59+
// However, s1 at that point is not initialized yet.
60+
// As a workaround, we will clear the use of s1 and restore it if we determine that lazy bailout is not needed.
61+
void ClearUseOfDst(SymID id);
62+
void RestoreUseOfDst();
63+
bool NeedsToRestoreUseOfDst() const;
64+
SymID GetClearedUseOfDstId() const;
65+
5066
void FinalizeBailOutRecord(Func * func);
5167
#ifdef MD_GROW_LOCALS_AREA_UP
5268
void FinalizeOffsets(__in_ecount(count) int * offsets, uint count, Func *func, BVSparse<JitArenaAllocator> *bvInlinedArgSlot);
@@ -66,6 +82,26 @@ class BailOutInfo
6682
kindMinusBits == IR::BailOutOnImplicitCallsPreOp;
6783
}
6884

85+
static bool OnlyHasLazyBailOut(IR::BailOutKind kind)
86+
{
87+
return kind == IR::LazyBailOut;
88+
}
89+
90+
static bool HasLazyBailOut(IR::BailOutKind kind)
91+
{
92+
return (kind & IR::LazyBailOut) != 0;
93+
}
94+
95+
static IR::BailOutKind WithoutLazyBailOut(IR::BailOutKind kind)
96+
{
97+
return kind & ~IR::LazyBailOut;
98+
}
99+
100+
static IR::BailOutKind WithLazyBailOut(IR::BailOutKind kind)
101+
{
102+
return kind | IR::LazyBailOut;
103+
}
104+
69105
#if DBG
70106
static bool IsBailOutHelper(IR::JnHelperMethod helper);
71107
#endif
@@ -77,6 +113,7 @@ class BailOutInfo
77113
#if DBG
78114
bool wasCopied;
79115
#endif
116+
SymID clearedDstByteCodeUpwardExposedUseId;
80117
uint32 bailOutOffset;
81118
BailOutRecord * bailOutRecord;
82119
CapturedValues * capturedValues; // Values we know about after forward pass

lib/Backend/BailOutKind.h

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,42 +7,41 @@
77
#error BAIL_OUT_KIND, BAIL_OUT_KIND_VALUE, and BAIL_OUT_KIND_VALUE_LAST must be defined before including this file.
88
#endif
99
/* kind */ /* allowed bits */
10-
BAIL_OUT_KIND(BailOutInvalid, IR::BailOutOnResultConditions | IR::BailOutForArrayBits | IR::BailOutForDebuggerBits | IR::BailOutMarkTempObject)
10+
BAIL_OUT_KIND(BailOutInvalid, IR::BailOutOnResultConditions | IR::BailOutForArrayBits | IR::BailOutForDebuggerBits | IR::BailOutMarkTempObject | IR::LazyBailOut)
1111
BAIL_OUT_KIND(BailOutIntOnly, IR::BailOutMarkTempObject)
1212
BAIL_OUT_KIND(BailOutNumberOnly, IR::BailOutMarkTempObject)
1313
BAIL_OUT_KIND(BailOutPrimitiveButString, IR::BailOutMarkTempObject)
14-
BAIL_OUT_KIND(BailOutOnImplicitCalls, IR::BailOutForArrayBits)
15-
BAIL_OUT_KIND(BailOutOnImplicitCallsPreOp, (IR::BailOutOnResultConditions | IR::BailOutForArrayBits | IR::BailOutMarkTempObject) & ~IR::BailOutOnArrayAccessHelperCall )
14+
BAIL_OUT_KIND(BailOutOnImplicitCalls, IR::BailOutForArrayBits | IR::LazyBailOut)
15+
BAIL_OUT_KIND(BailOutOnImplicitCallsPreOp, (IR::BailOutOnResultConditions | IR::BailOutForArrayBits | IR::BailOutMarkTempObject | IR::LazyBailOut) & ~IR::BailOutOnArrayAccessHelperCall )
1616
BAIL_OUT_KIND(BailOutOnNotPrimitive, IR::BailOutMarkTempObject)
1717
BAIL_OUT_KIND(BailOutOnMemOpError, IR::BailOutForArrayBits)
1818
BAIL_OUT_KIND(BailOutOnInlineFunction, 0)
1919
BAIL_OUT_KIND(BailOutOnNoProfile, 0)
2020
BAIL_OUT_KIND(BailOutOnPolymorphicInlineFunction, 0)
2121
BAIL_OUT_KIND(BailOutOnFailedPolymorphicInlineTypeCheck, 0)
2222
BAIL_OUT_KIND(BailOutShared, 0)
23-
BAIL_OUT_KIND(BailOutOnNotArray, IR::BailOutOnMissingValue)
24-
BAIL_OUT_KIND(BailOutOnNotNativeArray, IR::BailOutOnMissingValue)
25-
BAIL_OUT_KIND(BailOutConventionalTypedArrayAccessOnly, IR::BailOutMarkTempObject)
26-
BAIL_OUT_KIND(BailOutOnIrregularLength, IR::BailOutMarkTempObject)
23+
BAIL_OUT_KIND(BailOutOnNotArray, IR::BailOutOnMissingValue | IR::LazyBailOut)
24+
BAIL_OUT_KIND(BailOutOnNotNativeArray, IR::BailOutOnMissingValue | IR::LazyBailOut)
25+
BAIL_OUT_KIND(BailOutConventionalTypedArrayAccessOnly, IR::BailOutMarkTempObject | IR::LazyBailOut)
26+
BAIL_OUT_KIND(BailOutOnIrregularLength, IR::BailOutMarkTempObject | IR::LazyBailOut)
2727
BAIL_OUT_KIND(BailOutCheckThis, 0)
2828
BAIL_OUT_KIND(BailOutOnTaggedValue, 0)
29-
BAIL_OUT_KIND(BailOutFailedTypeCheck, IR::BailOutMarkTempObject)
30-
BAIL_OUT_KIND(BailOutFailedEquivalentTypeCheck, IR::BailOutMarkTempObject)
29+
BAIL_OUT_KIND(BailOutFailedTypeCheck, IR::BailOutMarkTempObject | IR::LazyBailOut)
30+
BAIL_OUT_KIND(BailOutFailedEquivalentTypeCheck, IR::BailOutMarkTempObject | IR::LazyBailOut)
3131
BAIL_OUT_KIND(BailOutInjected, 0)
3232
BAIL_OUT_KIND(BailOutExpectingInteger, 0)
3333
BAIL_OUT_KIND(BailOutExpectingString, 0)
3434
BAIL_OUT_KIND(BailOutFailedInlineTypeCheck, IR::BailOutMarkTempObject)
35-
BAIL_OUT_KIND(BailOutFailedFixedFieldTypeCheck, IR::BailOutMarkTempObject)
36-
BAIL_OUT_KIND(BailOutFailedFixedFieldCheck, 0)
37-
BAIL_OUT_KIND(BailOutFailedEquivalentFixedFieldTypeCheck, IR::BailOutMarkTempObject)
35+
BAIL_OUT_KIND(BailOutFailedFixedFieldTypeCheck, IR::BailOutMarkTempObject | IR::LazyBailOut)
36+
BAIL_OUT_KIND(BailOutFailedFixedFieldCheck, IR::LazyBailOut)
37+
BAIL_OUT_KIND(BailOutFailedEquivalentFixedFieldTypeCheck, IR::BailOutMarkTempObject | IR::LazyBailOut)
3838
BAIL_OUT_KIND(BailOutOnFloor, 0)
3939
BAIL_OUT_KIND(BailOnModByPowerOf2, 0)
4040
BAIL_OUT_KIND(BailOnIntMin, 0)
4141
BAIL_OUT_KIND(BailOnDivResultNotInt, IR::BailOutOnDivByZero | IR::BailOutOnDivOfMinInt | IR::BailOutOnNegativeZero)
4242
BAIL_OUT_KIND(BailOnSimpleJitToFullJitLoopBody, 0)
4343
BAIL_OUT_KIND(BailOutFailedCtorGuardCheck, 0)
4444
BAIL_OUT_KIND(BailOutOnFailedHoistedBoundCheck, 0)
45-
BAIL_OUT_KIND(LazyBailOut, 0)
4645
BAIL_OUT_KIND(BailOutOnFailedHoistedLoopCountBasedBoundCheck, 0)
4746
BAIL_OUT_KIND(BailOutForGeneratorYield, 0)
4847
BAIL_OUT_KIND(BailOutOnException, 0)
@@ -110,9 +109,11 @@ BAIL_OUT_KIND_VALUE(BailOutOnDivSrcConditions, BailOutOnDivByZero | BailOutOnDiv
110109

111110
#define BAIL_OUT_KIND_MISC_BIT_START BAIL_OUT_KIND_DIV_SRC_CONDITIONS_BIT_START + 2
112111
BAIL_OUT_KIND_VALUE(BailOutMarkTempObject, 1 << (BAIL_OUT_KIND_MISC_BIT_START + 0))
112+
// this is the most significant bit, must cast it to unsigned int so that the compiler knows we are not using a negative number
113+
BAIL_OUT_KIND_VALUE(LazyBailOut, (uint) 1 << (BAIL_OUT_KIND_MISC_BIT_START + 1))
114+
BAIL_OUT_KIND_VALUE(BailOutMisc, BailOutMarkTempObject | LazyBailOut)
113115

114-
115-
BAIL_OUT_KIND_VALUE_LAST(BailOutKindBits, BailOutMarkTempObject | BailOutOnDivSrcConditions | BailOutOnResultConditions | BailOutForArrayBits | BailOutForDebuggerBits)
116+
BAIL_OUT_KIND_VALUE_LAST(BailOutKindBits, BailOutMisc | BailOutOnDivSrcConditions | BailOutOnResultConditions | BailOutForArrayBits | BailOutForDebuggerBits)
116117

117118
// Help caller undefine the macros
118119
#undef BAIL_OUT_KIND

0 commit comments

Comments
 (0)