@@ -2215,15 +2215,20 @@ BackwardPass::IsLazyBailOutCurrentlyNeeeded(IR::Instr * instr) const
2215
2215
" liveFixedField is null, MergeSuccBlocksInfo might have not initialized it?"
2216
2216
);
2217
2217
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 ()))
2219
2224
{
2220
2225
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 );
2225
2228
}
2226
2229
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.
2227
2232
return !this ->currentBlock ->liveFixedFields ->IsEmpty ();
2228
2233
}
2229
2234
@@ -2333,7 +2338,7 @@ BackwardPass::DeadStoreTypeCheckBailOut(IR::Instr * instr)
2333
2338
return ;
2334
2339
}
2335
2340
2336
- // If bailOutKind is equivTypeCheck then leave alone the bailout
2341
+ // If bailOutKind is equivTypeCheck then leave the bailout alone.
2337
2342
if (bailOutKind == IR::BailOutFailedEquivalentTypeCheck ||
2338
2343
bailOutKind == IR::BailOutFailedEquivalentFixedFieldTypeCheck)
2339
2344
{
@@ -2357,9 +2362,9 @@ BackwardPass::DeadStoreTypeCheckBailOut(IR::Instr * instr)
2357
2362
}
2358
2363
2359
2364
void
2360
- BackwardPass::DeadStoreLazyBailOut (IR::Instr * instr, bool needsLazyBailOut )
2365
+ BackwardPass::DeadStoreLazyBailOut (IR::Instr * instr)
2361
2366
{
2362
- if (!this ->IsPrePass () && !needsLazyBailOut && instr->HasLazyBailOut ())
2367
+ if (!this ->IsPrePass () && instr->HasLazyBailOut ())
2363
2368
{
2364
2369
instr->ClearLazyBailOut ();
2365
2370
if (!instr->HasBailOutInfo ())
@@ -2441,12 +2446,13 @@ BackwardPass::DeadStoreImplicitCallBailOut(IR::Instr * instr, bool hasLiveFields
2441
2446
// We have an implicit call bailout in the code, and we want to make sure that it's required.
2442
2447
// Do this now, because only in the dead store pass do we have complete forward and backward liveness info.
2443
2448
bool needsBailOutOnImplicitCall = this ->IsImplicitCallBailOutCurrentlyNeeded (instr, mayNeedBailOnImplicitCall, needsLazyBailOut, hasLiveFields);
2449
+
2444
2450
if (!UpdateImplicitCallBailOutKind (instr, needsBailOutOnImplicitCall, needsLazyBailOut))
2445
2451
{
2446
2452
instr->ClearBailOutInfo ();
2447
- if (preOpBailOutInstrToProcess == instr)
2453
+ if (this -> preOpBailOutInstrToProcess == instr)
2448
2454
{
2449
- preOpBailOutInstrToProcess = nullptr ;
2455
+ this -> preOpBailOutInstrToProcess = nullptr ;
2450
2456
}
2451
2457
#if DBG
2452
2458
if (this ->DoMarkTempObjectVerify ())
@@ -2476,36 +2482,41 @@ BackwardPass::UpdateImplicitCallBailOutKind(IR::Instr *const instr, bool needsBa
2476
2482
2477
2483
const bool hasMarkTempObject = bailOutKindWithBits & IR::BailOutMarkTempObject;
2478
2484
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.
2482
2488
if (hasMarkTempObject)
2483
2489
{
2484
2490
instr->SetBailOutKind (bailOutKindWithBits & ~IR::BailOutMarkTempObject);
2485
2491
}
2486
2492
2487
2493
if (needsBailOutOnImplicitCall)
2488
2494
{
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.
2492
2500
if (needsLazyBailOut && !instr->CanChangeFieldValueWithoutImplicitCall ())
2493
2501
{
2494
2502
instr->ClearLazyBailOut ();
2495
2503
}
2496
2504
2497
2505
return true ;
2498
2506
}
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 ())
2500
2513
{
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.
2504
2514
instr->ClearLazyBailOut ();
2505
- if (!instr->HasBailOutInfo ())
2506
- {
2507
- return true ;
2508
- }
2515
+ }
2516
+
2517
+ if (!instr->HasBailOutInfo ())
2518
+ {
2519
+ return true ;
2509
2520
}
2510
2521
2511
2522
const IR::BailOutKind bailOutKindWithoutBits = instr->GetBailOutKindNoBits ();
@@ -2517,8 +2528,8 @@ BackwardPass::UpdateImplicitCallBailOutKind(IR::Instr *const instr, bool needsBa
2517
2528
return true ;
2518
2529
}
2519
2530
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.
2522
2533
IR::BailOutKind newBailOutKind = bailOutKindWithBits - bailOutKindWithoutBits;
2523
2534
2524
2535
if (newBailOutKind == IR::BailOutInvalid)
@@ -3721,7 +3732,10 @@ BackwardPass::ProcessBlock(BasicBlock * block)
3721
3732
);
3722
3733
3723
3734
DeadStoreTypeCheckBailOut (instr);
3724
- DeadStoreLazyBailOut (instr, needsLazyBailOut);
3735
+ if (!needsLazyBailOut)
3736
+ {
3737
+ DeadStoreLazyBailOut (instr);
3738
+ }
3725
3739
DeadStoreImplicitCallBailOut (instr, hasLiveFields, needsLazyBailOut);
3726
3740
3727
3741
AssertMsg (
@@ -5714,8 +5728,13 @@ BackwardPass::TrackAddPropertyTypes(IR::PropertySymOpnd *opnd, BasicBlock *block
5714
5728
typeWithProperty == typeWithoutProperty ||
5715
5729
(opnd->IsTypeChecked () && !opnd->IsInitialTypeChecked ()))
5716
5730
{
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
+ )
5718
5736
{
5737
+
5719
5738
PropertySym *propertySym = opnd->m_sym ->AsPropertySym ();
5720
5739
AddPropertyCacheBucket *pBucket =
5721
5740
block->stackSymToFinalType ->Get (propertySym->m_stackSym ->m_id );
@@ -6009,12 +6028,15 @@ BackwardPass::InsertTypeTransitionsAtPotentialKills()
6009
6028
// Final types can't be pushed up past certain instructions.
6010
6029
IR::Instr *instr = this ->currentInstr ;
6011
6030
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
+ )
6013
6039
{
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.
6018
6040
this ->ForEachAddPropertyCacheBucket ([&](int symId, AddPropertyCacheBucket *data)->bool {
6019
6041
this ->InsertTypeTransitionAfterInstr (instr, symId, data, this ->currentBlock ->upwardExposedUses );
6020
6042
return false ;
0 commit comments