Skip to content

Commit 8597210

Browse files
committed
Implemented OOPJIT support for LazyBailout
1 parent 1c5ab56 commit 8597210

40 files changed

+806
-269
lines changed

lib/Backend/BackwardPass.cpp

+55-33
Original file line numberDiff line numberDiff line change
@@ -2215,15 +2215,20 @@ BackwardPass::IsLazyBailOutCurrentlyNeeeded(IR::Instr * instr) const
22152215
"liveFixedField is null, MergeSuccBlocksInfo might have not initialized it?"
22162216
);
22172217

2218-
if (instr->IsStFldVariant())
2218+
// StFld LazyBailOut tag removal optimization. Given that this instr is a StFld variant and
2219+
// that there already is an BailOutOnImplicitCall tag on this instr, we can remove the
2220+
// LazyBailOut tag on this instr if the StFld is writing to a live fixed field. We cannot
2221+
// perform this optimization if a BailOutOnImplicitCall tag is abscent because writing to
2222+
// a property can result in an implicit call that then can result in a lazy bailout.
2223+
if (instr->IsStFldVariant() && BailOutInfo::IsBailOutOnImplicitCalls(instr->GetBailOutKind()))
22192224
{
22202225
Assert(instr->GetDst());
2221-
Js::PropertyId id = instr->GetDst()->GetSym()->AsPropertySym()->m_propertyId;
2222-
2223-
// We only need to protect against SetFld if it is setting to one of the live fixed fields
2224-
return this->currentBlock->liveFixedFields->Test(id);
2226+
// We only need to protect against StFld if it is setting to one of the live fixed fields.
2227+
return currentBlock->liveFixedFields->Test(instr->GetDst()->GetSym()->AsPropertySym()->m_propertyId);
22252228
}
22262229

2230+
// If no more fixed fields exist at this point in the block it is safe to assume that any field marked as
2231+
// a fixed field has been verified to have not been modified and thus a LazyBailOut tag is not necessary.
22272232
return !this->currentBlock->liveFixedFields->IsEmpty();
22282233
}
22292234

@@ -2333,7 +2338,7 @@ BackwardPass::DeadStoreTypeCheckBailOut(IR::Instr * instr)
23332338
return;
23342339
}
23352340

2336-
// If bailOutKind is equivTypeCheck then leave alone the bailout
2341+
// If bailOutKind is equivTypeCheck then leave the bailout alone.
23372342
if (bailOutKind == IR::BailOutFailedEquivalentTypeCheck ||
23382343
bailOutKind == IR::BailOutFailedEquivalentFixedFieldTypeCheck)
23392344
{
@@ -2357,9 +2362,9 @@ BackwardPass::DeadStoreTypeCheckBailOut(IR::Instr * instr)
23572362
}
23582363

23592364
void
2360-
BackwardPass::DeadStoreLazyBailOut(IR::Instr * instr, bool needsLazyBailOut)
2365+
BackwardPass::DeadStoreLazyBailOut(IR::Instr * instr)
23612366
{
2362-
if (!this->IsPrePass() && !needsLazyBailOut && instr->HasLazyBailOut())
2367+
if (!this->IsPrePass() && instr->HasLazyBailOut())
23632368
{
23642369
instr->ClearLazyBailOut();
23652370
if (!instr->HasBailOutInfo())
@@ -2441,12 +2446,13 @@ BackwardPass::DeadStoreImplicitCallBailOut(IR::Instr * instr, bool hasLiveFields
24412446
// We have an implicit call bailout in the code, and we want to make sure that it's required.
24422447
// Do this now, because only in the dead store pass do we have complete forward and backward liveness info.
24432448
bool needsBailOutOnImplicitCall = this->IsImplicitCallBailOutCurrentlyNeeded(instr, mayNeedBailOnImplicitCall, needsLazyBailOut, hasLiveFields);
2449+
24442450
if(!UpdateImplicitCallBailOutKind(instr, needsBailOutOnImplicitCall, needsLazyBailOut))
24452451
{
24462452
instr->ClearBailOutInfo();
2447-
if (preOpBailOutInstrToProcess == instr)
2453+
if (this->preOpBailOutInstrToProcess == instr)
24482454
{
2449-
preOpBailOutInstrToProcess = nullptr;
2455+
this->preOpBailOutInstrToProcess = nullptr;
24502456
}
24512457
#if DBG
24522458
if (this->DoMarkTempObjectVerify())
@@ -2476,36 +2482,41 @@ BackwardPass::UpdateImplicitCallBailOutKind(IR::Instr *const instr, bool needsBa
24762482

24772483
const bool hasMarkTempObject = bailOutKindWithBits & IR::BailOutMarkTempObject;
24782484

2479-
// Firstly, we remove the mark temp object bit, as it is not needed after the dead store pass.
2480-
// We will later skip removing BailOutOnImplicitCalls when there is a mark temp object bit regardless
2481-
// of `needsBailOutOnImplicitCall`.
2485+
// First we remove the mark temp object bit as it is not needed after the dead
2486+
// store pass. We will later skip removing BailOutOnImplicitCalls when there
2487+
// is a mark temp object bit regardless of needsBailOutOnImplicitCall.
24822488
if (hasMarkTempObject)
24832489
{
24842490
instr->SetBailOutKind(bailOutKindWithBits & ~IR::BailOutMarkTempObject);
24852491
}
24862492

24872493
if (needsBailOutOnImplicitCall)
24882494
{
2489-
// We decided that BailOutOnImplicitCall is needed. So lazy bailout is unnecessary
2490-
// because we are already protected from potential side effects unless the operation
2491-
// itself can change fields' values (StFld/StElem).
2495+
// We decided that BailOutOnImplicitCall is needed; LazyBailOut is unnecessary because
2496+
// the modification of a property would trigger an implicit call bailout before a LazyBailOut
2497+
// would trigger. An edge case is when the act of checking the type of the object with the
2498+
// property, which occurs before the implicit call check, results in a property guard invalidation.
2499+
// In this case a LazyBailOut is necessary.
24922500
if (needsLazyBailOut && !instr->CanChangeFieldValueWithoutImplicitCall())
24932501
{
24942502
instr->ClearLazyBailOut();
24952503
}
24962504

24972505
return true;
24982506
}
2499-
else
2507+
2508+
// needsBailOutOnImplicitCall also captures our intention to keep BailOutOnImplicitCalls
2509+
// because we want to do fixed field lazy bailout optimization. So if we don't need them,
2510+
// just remove our lazy bailout unless this instr can cause a PropertyGuard invalidation
2511+
// during the type check.
2512+
if (!instr->CanChangeFieldValueWithoutImplicitCall())
25002513
{
2501-
// `needsBailOutOnImplicitCall` also captures our intention to keep BailOutOnImplicitCalls
2502-
// because we want to do fixed field lazy bailout optimization. So if we don't need them,
2503-
// just remove our lazy bailout.
25042514
instr->ClearLazyBailOut();
2505-
if (!instr->HasBailOutInfo())
2506-
{
2507-
return true;
2508-
}
2515+
}
2516+
2517+
if (!instr->HasBailOutInfo())
2518+
{
2519+
return true;
25092520
}
25102521

25112522
const IR::BailOutKind bailOutKindWithoutBits = instr->GetBailOutKindNoBits();
@@ -2517,8 +2528,8 @@ BackwardPass::UpdateImplicitCallBailOutKind(IR::Instr *const instr, bool needsBa
25172528
return true;
25182529
}
25192530

2520-
// At this point, we don't need the bail on implicit calls.
2521-
// Simply use the bailout kind bits as our new bailout kind.
2531+
// At this point we don't need the bail on implicit calls,
2532+
// use the bailout kind bits as our new bailout kind.
25222533
IR::BailOutKind newBailOutKind = bailOutKindWithBits - bailOutKindWithoutBits;
25232534

25242535
if (newBailOutKind == IR::BailOutInvalid)
@@ -3721,7 +3732,10 @@ BackwardPass::ProcessBlock(BasicBlock * block)
37213732
);
37223733

37233734
DeadStoreTypeCheckBailOut(instr);
3724-
DeadStoreLazyBailOut(instr, needsLazyBailOut);
3735+
if (!needsLazyBailOut)
3736+
{
3737+
DeadStoreLazyBailOut(instr);
3738+
}
37253739
DeadStoreImplicitCallBailOut(instr, hasLiveFields, needsLazyBailOut);
37263740

37273741
AssertMsg(
@@ -5714,8 +5728,13 @@ BackwardPass::TrackAddPropertyTypes(IR::PropertySymOpnd *opnd, BasicBlock *block
57145728
typeWithProperty == typeWithoutProperty ||
57155729
(opnd->IsTypeChecked() && !opnd->IsInitialTypeChecked()))
57165730
{
5717-
if (!this->IsPrePass() && block->stackSymToFinalType != nullptr && !this->currentInstr->HasBailOutInfo())
5731+
if (
5732+
!this->IsPrePass() &&
5733+
block->stackSymToFinalType != nullptr &&
5734+
(!this->currentInstr->HasBailOutInfo() || currentInstr->OnlyHasLazyBailOut())
5735+
)
57185736
{
5737+
57195738
PropertySym *propertySym = opnd->m_sym->AsPropertySym();
57205739
AddPropertyCacheBucket *pBucket =
57215740
block->stackSymToFinalType->Get(propertySym->m_stackSym->m_id);
@@ -6009,12 +6028,15 @@ BackwardPass::InsertTypeTransitionsAtPotentialKills()
60096028
// Final types can't be pushed up past certain instructions.
60106029
IR::Instr *instr = this->currentInstr;
60116030

6012-
if (instr->HasBailOutInfo() || instr->m_opcode == Js::OpCode::UpdateNewScObjectCache)
6031+
// Final types can't be pushed up past a BailOut point. Insert any transitions called
6032+
// for by the current state of add-property buckets. Also do this for ctor cache updates
6033+
// to avoid putting a type in the ctor cache that extends past the end of the ctor that
6034+
// the cache covers.
6035+
// TODO: explain why LBO gets exempted from this rule.
6036+
if (instr->m_opcode == Js::OpCode::UpdateNewScObjectCache ||
6037+
(instr->HasBailOutInfo() && !instr->OnlyHasLazyBailOut())
6038+
)
60136039
{
6014-
// Final types can't be pushed up past a bailout point.
6015-
// Insert any transitions called for by the current state of add-property buckets.
6016-
// Also do this for ctor cache updates, to avoid putting a type in the ctor cache that extends past
6017-
// the end of the ctor that the cache covers.
60186040
this->ForEachAddPropertyCacheBucket([&](int symId, AddPropertyCacheBucket *data)->bool {
60196041
this->InsertTypeTransitionAfterInstr(instr, symId, data, this->currentBlock->upwardExposedUses);
60206042
return false;

lib/Backend/BackwardPass.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ class BackwardPass
7474
void DumpMarkTemp();
7575
#endif
7676

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

7979
bool ProcessNoImplicitCallUses(IR::Instr *const instr);
8080
void ProcessNoImplicitCallDef(IR::Instr *const instr);
@@ -106,7 +106,7 @@ class BackwardPass
106106
bool IsLazyBailOutCurrentlyNeeeded(IR::Instr * instr) const;
107107
void DeadStoreImplicitCallBailOut(IR::Instr * instr, bool hasLiveFields, bool needsLazyBailOut);
108108
void DeadStoreTypeCheckBailOut(IR::Instr * instr);
109-
void DeadStoreLazyBailOut(IR::Instr * instr, bool needsLazyBailOut);
109+
void DeadStoreLazyBailOut(IR::Instr * instr);
110110
bool IsImplicitCallBailOutCurrentlyNeeded(IR::Instr * instr, bool mayNeedImplicitCallBailOut, bool needLazyBailOut, bool hasLiveFields);
111111
bool NeedBailOutOnImplicitCallsForTypedArrayStore(IR::Instr* instr);
112112
bool TrackNoImplicitCallInlinees(IR::Instr *instr);

lib/Backend/BailOut.cpp

-8
Original file line numberDiff line numberDiff line change
@@ -2974,14 +2974,6 @@ SharedBailOutRecord::SharedBailOutRecord(uint32 bailOutOffset, uint bailOutCache
29742974
this->type = BailoutRecordType::Shared;
29752975
}
29762976

2977-
#if DBG
2978-
void LazyBailOutRecord::Dump(Js::FunctionBody* functionBody) const
2979-
{
2980-
OUTPUT_PRINT(functionBody);
2981-
Output::Print(_u("Bytecode Offset: #%04x opcode: %s"), this->bailOutRecord->GetBailOutOffset(), Js::OpCodeUtil::GetOpCodeName(this->bailOutRecord->GetBailOutOpCode()));
2982-
}
2983-
#endif
2984-
29852977
void GlobalBailOutRecordDataTable::Finalize(NativeCodeData::Allocator *allocator, JitArenaAllocator *tempAlloc)
29862978
{
29872979
GlobalBailOutRecordDataRow *newRows = NativeCodeDataNewArrayZNoFixup(allocator, GlobalBailOutRecordDataRow, length);

lib/Backend/BailOut.h

+4-7
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,10 @@ class BailOutInfo
5050
void PartialDeepCopyTo(BailOutInfo *const bailOutInfo) const;
5151
void Clear(JitArenaAllocator * allocator);
5252

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.
53+
// Related to Lazy bailout. Workaround for dealing with use of destination register of `call` instructions
54+
// with postop lazy bailout. As an example, in globopt, we have s1 = Call and s1 is in byteCodeUpwardExposedUse,
55+
// but after lowering, the instructions are: s3 = Call, s1 = s3. If we add a postop lazy bailout to s3 = call,
56+
// we will create a use of s1 right at that instructions. However, s1 at that point is not initialized yet.
6057
// As a workaround, we will clear the use of s1 and restore it if we determine that lazy bailout is not needed.
6158
void ClearUseOfDst(SymID id);
6259
void RestoreUseOfDst();

lib/Backend/DbCheckPostLower.cpp

+4-1
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,7 @@ DbCheckPostLower::IsAssign(IR::Instr *instr)
319319
return LowererMD::IsAssign(instr)
320320
#ifdef _M_X64
321321
|| instr->m_opcode == Js::OpCode::MOVQ
322+
|| instr->m_opcode == Js::OpCode::MOV_TRUNC
322323
#endif
323324
;
324325
}
@@ -364,7 +365,9 @@ DbCheckPostLower::EnsureOnlyMovesToRegisterOpnd(IR::Instr *instr)
364365
if (this->IsCallToHelper(instr, IR::HelperOp_Equal) ||
365366
this->IsCallToHelper(instr, IR::HelperOp_StrictEqual) ||
366367
this->IsCallToHelper(instr, IR::HelperOP_CmEq_A) ||
367-
this->IsCallToHelper(instr, IR::HelperOP_CmNeq_A)
368+
this->IsCallToHelper(instr, IR::HelperOP_CmNeq_A) ||
369+
this->IsCallToHelper(instr, IR::HelperOP_CmSrNeq_A) ||
370+
this->IsCallToHelper(instr, IR::HelperOP_CmSrEq_A)
368371
)
369372
{
370373
// Pattern matched

0 commit comments

Comments
 (0)