@@ -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
23592364void
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 ;
0 commit comments