|
|
@@ -3,102 +3,6 @@
|
|
|
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
|
|
|
//-------------------------------------------------------------------------------------------------------
|
|
|
#include "Backend.h"
|
|
|
-/*
|
|
|
-Field Hoisting
|
|
|
---------------
|
|
|
-The backward pass calculates field load values that are reachable from the loop top.
|
|
|
-It optimistically assumes that a[] doesn't kill any fields in the hopes that glob opt
|
|
|
-will have more information to not kill the field.
|
|
|
-
|
|
|
-During the forward pass the root prepass will assume that the field hoist candidate
|
|
|
-is live (set the livefields bitvector). (GlobOpt::PreparePrepassFieldHoisting)
|
|
|
-The "hoistable field" bitvector is used to keep track of whether the current instruction
|
|
|
-may have the initial field value at the loop top. If so, they are hoistable.
|
|
|
-Even when the value is only visible at the loop top from one path, there is benefit in
|
|
|
-hoisting the field.
|
|
|
-
|
|
|
-e.g 1. We can hoist the field in this case and get benefit:
|
|
|
- while {
|
|
|
- if {
|
|
|
- o.x = <== kills field value
|
|
|
- }
|
|
|
- = o.x <== only has the loop top field value on the "!if" path
|
|
|
- }
|
|
|
-After hoisting the field:
|
|
|
- s1 = o.x <== hoisted field load
|
|
|
- while {
|
|
|
- if {
|
|
|
- o.x =
|
|
|
- s1 = <== maintain the hoisted field value
|
|
|
- }
|
|
|
- = s1 <== avoided a field load
|
|
|
- }
|
|
|
-
|
|
|
-When we identify a field load as hoistable, we will add the instruction to a list on the loop.
|
|
|
-
|
|
|
-After the prepass, we will determine the fields that we are going to hoist from the
|
|
|
-field candidates. (GlobOpt::PrepareFieldHoisting)
|
|
|
-
|
|
|
-If it is not live - even if we detect a hoistable load - it is not beneficial to hoist
|
|
|
-the load as we will have to insert a field load to compensate for the loop back edges.
|
|
|
-We will just rely on field copy prop to optimize in that case.
|
|
|
-
|
|
|
-e.g 2. Hoisting this require us to add a field load back at the end of the loop with no benefit.
|
|
|
- while {
|
|
|
- = o.x <== hoistable field but isn't live on back edge
|
|
|
- b.x = <== kills o.x as o and b may be aliased
|
|
|
- }
|
|
|
-
|
|
|
-If it is live on back edge then it is possible to hoist the field load.
|
|
|
-
|
|
|
-e.g 3. Although the field is killed, if the value is live on back edge we can still hoist it.
|
|
|
- while {
|
|
|
- = o.x
|
|
|
- = o.x
|
|
|
- b.x =
|
|
|
- = o.x
|
|
|
- }
|
|
|
-After hoisting the field, s1 is live for the whole loop
|
|
|
- s1 = o.x
|
|
|
- while {
|
|
|
- = s1 <== eliminated one field load
|
|
|
- = s1 <== copy prop
|
|
|
- b.x =
|
|
|
- s1 = o.x
|
|
|
- }
|
|
|
-
|
|
|
-However, since our register allocator doesn't handle long lifetimes, copy prop may do a better job.
|
|
|
-We would only replace one field load - instead of two.
|
|
|
-(Currently we hoist in this case)
|
|
|
-
|
|
|
-e.g. 4. Live time of s1 is much shorter, which works better with our current register allocator.
|
|
|
- while {
|
|
|
- s1 = o.x
|
|
|
- = s1 <== copy prop
|
|
|
- b.x =
|
|
|
- s1 = o.x
|
|
|
- }
|
|
|
-
|
|
|
-May want to add heuristics to determine whether to hoist by looking at the number of field
|
|
|
-loads we can replace. See unittest\fieldopts\fieldhoist5.js for timing with various -off/-force
|
|
|
-of fieldhoist/fieldcopyprop. Currently, field hoist is better or the same as field copy prop
|
|
|
-except for kill_singleuse in the test where we can only eliminate one field load compared to copy prop
|
|
|
-If we ever improve the register allocator to do better, we might lift this restriction.
|
|
|
-
|
|
|
-In GlobOpt::PrepareFieldHoisting, we go through all the hoistable field loads that are live on the back edge.
|
|
|
-We create a preassigned symbol for the hoisted field and add it to the fieldHoistSymMap of the loop.
|
|
|
-If the value is live coming into the loop (via field copy prop, we will create the instruction to
|
|
|
-assign the value to the preassigned sym. (GlobOpt::HoistFieldLoadValue)
|
|
|
-If we don't know the value yet, we will create the load field instead. (GlobOpt::HoistFieldLoad)
|
|
|
-
|
|
|
-As we are processing instructions in the non-prepass, when we see a field load, if it is live already then we have
|
|
|
-a value in the preassigned sym and we can just replace the load. (GlobOpt::CopyPropHoistedFields)
|
|
|
-If it is not live, then we don't have the value of the field, so keep the field load, but also
|
|
|
-assign the loaded value to the preassigned sym. (GlobOpt::ReloadFieldHoistStackSym)
|
|
|
-
|
|
|
-If the instruction is a store of a hoisted field, then create an assignment of the value to the preassigned
|
|
|
-symbol to maintain a live field value. (GlobOpt::CopyStoreFieldHoistStackSym)
|
|
|
-*/
|
|
|
|
|
|
bool
|
|
|
GlobOpt::DoFieldCopyProp() const
|
|
|
@@ -140,12 +44,6 @@ GlobOpt::DoFieldCopyProp(Loop * loop) const
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
- if (this->DoFieldHoisting(loop))
|
|
|
- {
|
|
|
- // Have to do field copy prop when we are doing field hoisting
|
|
|
- return true;
|
|
|
- }
|
|
|
-
|
|
|
if (PHASE_OFF(Js::FieldCopyPropPhase, this->func))
|
|
|
{
|
|
|
return false;
|
|
|
@@ -154,46 +52,6 @@ GlobOpt::DoFieldCopyProp(Loop * loop) const
|
|
|
return this->DoFieldOpts(loop);
|
|
|
}
|
|
|
|
|
|
-bool
|
|
|
-GlobOpt::DoFieldHoisting(Loop *loop)
|
|
|
-{
|
|
|
- if (loop == nullptr)
|
|
|
- {
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
- Func * func = loop->GetHeadBlock()->GetFirstInstr()->m_func->GetTopFunc();
|
|
|
- if (PHASE_OFF(Js::CopyPropPhase, func))
|
|
|
- {
|
|
|
- // Can't do field hoisting without copy prop
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
- if (PHASE_OFF(Js::FieldHoistPhase, func))
|
|
|
- {
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
- if (!PHASE_OFF(Js::FieldPREPhase, func))
|
|
|
- {
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
- if (PHASE_FORCE(Js::FieldHoistPhase, func))
|
|
|
- {
|
|
|
- // Force always turns on field hoisting
|
|
|
- return true;
|
|
|
- }
|
|
|
-
|
|
|
- return loop->CanDoFieldHoist();
|
|
|
-}
|
|
|
-
|
|
|
-bool
|
|
|
-GlobOpt::DoFieldHoisting() const
|
|
|
-{
|
|
|
- return this->DoFieldHoisting(this->currentBlock->loop);
|
|
|
-}
|
|
|
-
|
|
|
bool
|
|
|
GlobOpt::DoObjTypeSpec() const
|
|
|
{
|
|
|
@@ -278,12 +136,6 @@ bool GlobOpt::HasMemOp(Loop *loop)
|
|
|
);
|
|
|
}
|
|
|
|
|
|
-bool
|
|
|
-GlobOpt::TrackHoistableFields() const
|
|
|
-{
|
|
|
- return this->IsLoopPrePass() && this->currentBlock->loop == this->prePassLoop;
|
|
|
-}
|
|
|
-
|
|
|
void
|
|
|
GlobOpt::KillLiveFields(StackSym * stackSym, BVSparse<JitArenaAllocator> * bv)
|
|
|
{
|
|
|
@@ -597,1428 +449,106 @@ GlobOpt::ProcessFieldKills(IR::Instr * instr)
|
|
|
}
|
|
|
|
|
|
ProcessFieldKills(instr, this->currentBlock->globOptData.liveFields, true);
|
|
|
- if (this->currentBlock->globOptData.hoistableFields)
|
|
|
- {
|
|
|
- Assert(this->TrackHoistableFields());
|
|
|
-
|
|
|
- // Fields that are killed are no longer hoistable.
|
|
|
- this->currentBlock->globOptData.hoistableFields->And(this->currentBlock->globOptData.liveFields);
|
|
|
- }
|
|
|
}
|
|
|
|
|
|
-void
|
|
|
-GlobOpt::PreparePrepassFieldHoisting(Loop * loop)
|
|
|
+Value *
|
|
|
+GlobOpt::CreateFieldSrcValue(PropertySym * sym, PropertySym * originalSym, IR::Opnd ** ppOpnd, IR::Instr * instr)
|
|
|
{
|
|
|
- BVSparse<JitArenaAllocator> * fieldHoistCandidates = loop->fieldHoistCandidates;
|
|
|
-
|
|
|
-#if DBG_DUMP
|
|
|
- if (Js::Configuration::Global.flags.Trace.IsEnabled(Js::FieldHoistPhase, this->func->GetSourceContextId(), this->func->GetLocalFunctionId()))
|
|
|
- {
|
|
|
- Output::Print(_u("\nFieldHoist: Start Loop: "));
|
|
|
- loop->GetHeadBlock()->DumpHeader();
|
|
|
- Output::Print(_u("FieldHoist: Backward candidates : "));
|
|
|
- fieldHoistCandidates->Dump();
|
|
|
- }
|
|
|
-#endif
|
|
|
-#if ENABLE_DEBUG_CONFIG_OPTIONS
|
|
|
- if (Js::Configuration::Global.flags.TestTrace.IsEnabled(Js::FieldHoistPhase, this->func->GetSourceContextId(), this->func->GetLocalFunctionId()))
|
|
|
- {
|
|
|
- char16 debugStringBuffer[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
|
|
|
- Output::Print(_u("FieldHoist: START LOOP function %s (%s)\n"), this->func->GetJITFunctionBody()->GetDisplayName(), this->func->GetDebugNumberSet(debugStringBuffer));
|
|
|
- }
|
|
|
-#endif
|
|
|
-
|
|
|
- loop->fieldHoistCandidateTypes = JitAnew(this->alloc, BVSparse<JitArenaAllocator>, this->alloc);
|
|
|
-
|
|
|
- if (fieldHoistCandidates->IsEmpty())
|
|
|
- {
|
|
|
- return;
|
|
|
- }
|
|
|
+#if DBG
|
|
|
+ // If the opcode going to kill all field values immediate anyway, we shouldn't be giving it a value
|
|
|
+ Assert(!instr->UsesAllFields());
|
|
|
|
|
|
- BasicBlock * landingPad = loop->landingPad;
|
|
|
+ AssertCanCopyPropOrCSEFieldLoad(instr);
|
|
|
|
|
|
- // If it is live, the field doesn't need to be hoisted
|
|
|
- Assert(loop->liveInFieldHoistCandidates == nullptr);
|
|
|
- BVSparse<JitArenaAllocator> * liveInFieldHoistCandidates = fieldHoistCandidates->AndNew(landingPad->globOptData.liveFields);
|
|
|
- loop->liveInFieldHoistCandidates = liveInFieldHoistCandidates;
|
|
|
+ Assert(instr->GetSrc1() == *ppOpnd);
|
|
|
+#endif
|
|
|
|
|
|
- if (!liveInFieldHoistCandidates->IsEmpty())
|
|
|
+ // Only give a value to fields if we are doing field copy prop.
|
|
|
+ // Consider: We should always copy prop local slots, but the only use right now is LdSlot from jit loop body.
|
|
|
+ // This should have one onus load, and thus no need for copy prop of field itself. We may want to support
|
|
|
+ // copy prop LdSlot if there are other uses of local slots
|
|
|
+ if (!this->DoFieldCopyProp())
|
|
|
{
|
|
|
- // Assume the live fields don't need to hoist for now
|
|
|
- fieldHoistCandidates->Minus(liveInFieldHoistCandidates);
|
|
|
-
|
|
|
- // If it was hoisted in an outer loop, and the value is live coming in, we don't need to hoist it again
|
|
|
- Loop * currentLoop = loop->parent;
|
|
|
- while (currentLoop != nullptr && this->DoFieldHoisting(currentLoop))
|
|
|
- {
|
|
|
- if (currentLoop->hoistedFields)
|
|
|
- {
|
|
|
- liveInFieldHoistCandidates->Minus(currentLoop->hoistedFields);
|
|
|
- }
|
|
|
- currentLoop = currentLoop->parent;
|
|
|
- }
|
|
|
-
|
|
|
- FOREACH_BITSET_IN_SPARSEBV(index, liveInFieldHoistCandidates)
|
|
|
- {
|
|
|
- if (landingPad->globOptData.FindValueFromMapDirect((SymID)index) == nullptr)
|
|
|
- {
|
|
|
- // Create initial values if we don't have one already for live fields
|
|
|
- Value * newValue = this->NewGenericValue(ValueType::Uninitialized);
|
|
|
- Value * oldValue = CopyValue(newValue, newValue->GetValueNumber());
|
|
|
- Sym *sym = this->func->m_symTable->Find(index);
|
|
|
- landingPad->globOptData.SetValue(oldValue, sym);
|
|
|
- this->currentBlock->globOptData.SetValue(newValue, sym);
|
|
|
- }
|
|
|
- }
|
|
|
- NEXT_BITSET_IN_SPARSEBV;
|
|
|
+ return nullptr;
|
|
|
}
|
|
|
|
|
|
- // Assume that the candidates are hoisted on prepass
|
|
|
- landingPad->globOptData.liveFields->Or(fieldHoistCandidates);
|
|
|
- this->currentBlock->globOptData.liveFields->Or(fieldHoistCandidates);
|
|
|
+ BOOL wasLive = this->currentBlock->globOptData.liveFields->TestAndSet(sym->m_id);
|
|
|
|
|
|
- Loop * parentLoop = loop->parent;
|
|
|
- FOREACH_BITSET_IN_SPARSEBV(index, fieldHoistCandidates)
|
|
|
+ if (sym != originalSym)
|
|
|
{
|
|
|
- // Create initial values
|
|
|
- Value * newValue = this->NewGenericValue(ValueType::Uninitialized);
|
|
|
- Value * oldValue = CopyValue(newValue, newValue->GetValueNumber());
|
|
|
- Sym *sym = this->func->m_symTable->Find(index);
|
|
|
- landingPad->globOptData.SetValue(oldValue, sym);
|
|
|
- this->currentBlock->globOptData.SetValue(newValue, sym);
|
|
|
-
|
|
|
- StackSym* objectSym = sym->AsPropertySym()->m_stackSym;
|
|
|
- if (objectSym->HasObjectTypeSym())
|
|
|
- {
|
|
|
- StackSym* typeSym = objectSym->GetObjectTypeSym();
|
|
|
-
|
|
|
- // If the type isn't live into the loop, let's keep track of it, so we can add it to
|
|
|
- // live fields on pre-pass, verify if it is invariant through the loop, and if so produce it
|
|
|
- // into the loop on the real pass.
|
|
|
- if (!loop->landingPad->globOptData.liveFields->Test(typeSym->m_id))
|
|
|
- {
|
|
|
- Assert(!this->currentBlock->globOptData.liveFields->Test(typeSym->m_id));
|
|
|
- loop->fieldHoistCandidateTypes->Set(typeSym->m_id);
|
|
|
-
|
|
|
- // Set object type live on prepass so we can track if it got killed in the loop. (see FinishOptHoistedPropOps)
|
|
|
- JsTypeValueInfo* typeValueInfo = JsTypeValueInfo::New(this->alloc, nullptr, nullptr);
|
|
|
- typeValueInfo->SetIsShared();
|
|
|
- this->SetSymStoreDirect(typeValueInfo, typeSym);
|
|
|
-
|
|
|
- ValueNumber typeValueNumber = this->NewValueNumber();
|
|
|
- Value* landingPadTypeValue = NewValue(typeValueNumber, typeValueInfo);
|
|
|
- Value* headerTypeValue = NewValue(typeValueNumber, typeValueInfo);
|
|
|
-
|
|
|
- SetObjectTypeFromTypeSym(typeSym, landingPadTypeValue, landingPad);
|
|
|
- SetObjectTypeFromTypeSym(typeSym, headerTypeValue, this->currentBlock);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // If the sym holding the hoisted value is used as an instance pointer in the outer loop,
|
|
|
- // its type may appear to be live in the inner loop. But the instance itself is being killed
|
|
|
- // here, so make sure the type is killed as well.
|
|
|
- if (parentLoop != nullptr)
|
|
|
- {
|
|
|
- StackSym * copySym;
|
|
|
- Loop * hoistedLoop = FindFieldHoistStackSym(parentLoop, index, ©Sym, nullptr);
|
|
|
- if (hoistedLoop != nullptr)
|
|
|
- {
|
|
|
- this->KillObjectType(copySym);
|
|
|
- }
|
|
|
- }
|
|
|
+ this->currentBlock->globOptData.liveFields->TestAndSet(originalSym->m_id);
|
|
|
}
|
|
|
- NEXT_BITSET_IN_SPARSEBV;
|
|
|
-
|
|
|
- Assert(this->TrackHoistableFields());
|
|
|
|
|
|
- // Initialize the bit vector to keep track of whether the hoisted value will reach a field load
|
|
|
- // to determine whether it should be hoisted.
|
|
|
- if (this->currentBlock->globOptData.hoistableFields)
|
|
|
- {
|
|
|
- this->currentBlock->globOptData.hoistableFields->Copy(fieldHoistCandidates);
|
|
|
- }
|
|
|
- else
|
|
|
+ if (!wasLive)
|
|
|
{
|
|
|
- this->currentBlock->globOptData.hoistableFields = fieldHoistCandidates->CopyNew(this->alloc);
|
|
|
- this->currentBlock->globOptData.hoistableFields = this->currentBlock->globOptData.hoistableFields;
|
|
|
+ // We don't clear the value when we kill the field.
|
|
|
+ // Clear it to make sure we don't use the old value.
|
|
|
+ this->currentBlock->globOptData.ClearSymValue(sym);
|
|
|
+ this->currentBlock->globOptData.ClearSymValue(originalSym);
|
|
|
}
|
|
|
- this->currentBlock->globOptData.hoistableFields->Or(liveInFieldHoistCandidates);
|
|
|
|
|
|
-#if DBG_DUMP
|
|
|
- if (Js::Configuration::Global.flags.Trace.IsEnabled(Js::FieldHoistPhase, this->func->GetSourceContextId(), this->func->GetLocalFunctionId()))
|
|
|
- {
|
|
|
- Output::Print(_u("FieldHoist: Prepass candidates (not live): "));
|
|
|
- fieldHoistCandidates->Dump();
|
|
|
- Output::Print(_u("FieldHoist: Prepass candidates (live) : "));
|
|
|
- liveInFieldHoistCandidates->Dump();
|
|
|
- }
|
|
|
-#endif
|
|
|
+ Assert((*ppOpnd)->AsSymOpnd()->m_sym == sym || this->IsLoopPrePass());
|
|
|
+
|
|
|
+ // We don't use the sym store to do copy prop on hoisted fields, but create a value
|
|
|
+ // in case it can be copy prop out of the loop.
|
|
|
+ return this->NewGenericValue(ValueType::Uninitialized, *ppOpnd);
|
|
|
}
|
|
|
|
|
|
-void
|
|
|
-GlobOpt::PrepareFieldHoisting(Loop * loop)
|
|
|
+bool
|
|
|
+GlobOpt::NeedBailOnImplicitCallWithFieldOpts(Loop *loop, bool hasLiveFields) const
|
|
|
{
|
|
|
- Assert(!this->IsLoopPrePass());
|
|
|
-
|
|
|
- if (loop->parent != nullptr)
|
|
|
- {
|
|
|
- loop->hasHoistedFields = loop->parent->hasHoistedFields;
|
|
|
- }
|
|
|
-
|
|
|
- BVSparse<JitArenaAllocator> * fieldHoistCandidates = loop->fieldHoistCandidates;
|
|
|
- BVSparse<JitArenaAllocator> * liveInFieldHoistCandidates = loop->liveInFieldHoistCandidates;
|
|
|
- if (fieldHoistCandidates->IsEmpty() && (!liveInFieldHoistCandidates || liveInFieldHoistCandidates->IsEmpty()))
|
|
|
+ if (!(((this->DoFieldRefOpts(loop) ||
|
|
|
+ this->DoFieldCopyProp(loop)) &&
|
|
|
+ hasLiveFields)))
|
|
|
{
|
|
|
- if (loop->hasHoistedFields)
|
|
|
- {
|
|
|
- loop->hoistedFieldCopySyms = JitAnew(this->alloc, BVSparse<JitArenaAllocator>, this->alloc);
|
|
|
-
|
|
|
- AnalysisAssert(loop->parent && loop->parent->hasHoistedFields);
|
|
|
- loop->hoistedFieldCopySyms->Copy(loop->parent->hoistedFieldCopySyms);
|
|
|
- loop->regAlloc.liveOnBackEdgeSyms->Or(loop->hoistedFieldCopySyms);
|
|
|
- }
|
|
|
-
|
|
|
- return;
|
|
|
+ return false;
|
|
|
}
|
|
|
|
|
|
- BasicBlock * landingPad = loop->landingPad;
|
|
|
- Assert(landingPad->globOptData.hoistableFields == nullptr);
|
|
|
- Assert(this->currentBlock->globOptData.hoistableFields == nullptr);
|
|
|
-
|
|
|
- BVSparse<JitArenaAllocator>* fieldHoistCandidateTypes = loop->fieldHoistCandidateTypes;
|
|
|
-
|
|
|
- // Remove the live fields that are added during prepass
|
|
|
- landingPad->globOptData.liveFields->Minus(fieldHoistCandidates);
|
|
|
- landingPad->globOptData.liveFields->Minus(fieldHoistCandidateTypes);
|
|
|
-
|
|
|
- // After prepass, if the field is not loaded on the back edge then we shouldn't hoist it
|
|
|
- fieldHoistCandidates->And(this->currentBlock->globOptData.liveFields);
|
|
|
- liveInFieldHoistCandidates->And(this->currentBlock->globOptData.liveFields);
|
|
|
- fieldHoistCandidateTypes->And(this->currentBlock->globOptData.liveFields);
|
|
|
-
|
|
|
- // Remove the live fields that were added during prepass
|
|
|
- this->currentBlock->globOptData.liveFields->Minus(fieldHoistCandidates);
|
|
|
- this->currentBlock->globOptData.liveFields->Minus(fieldHoistCandidateTypes);
|
|
|
-
|
|
|
- loop->hoistedFields = JitAnew(this->alloc, BVSparse<JitArenaAllocator>, this->alloc);
|
|
|
- loop->hoistedFieldCopySyms = JitAnew(this->alloc, BVSparse<JitArenaAllocator>, this->alloc);
|
|
|
+ return true;
|
|
|
+}
|
|
|
|
|
|
- if (loop->parent && loop->parent->hasHoistedFields)
|
|
|
+IR::Instr *
|
|
|
+GlobOpt::EnsureDisableImplicitCallRegion(Loop * loop)
|
|
|
+{
|
|
|
+ Assert(loop->bailOutInfo != nullptr);
|
|
|
+ IR::Instr * endDisableImplicitCall = loop->endDisableImplicitCall;
|
|
|
+ if (endDisableImplicitCall)
|
|
|
{
|
|
|
- loop->hoistedFieldCopySyms->Copy(loop->parent->hoistedFieldCopySyms);
|
|
|
+ return endDisableImplicitCall;
|
|
|
}
|
|
|
|
|
|
- Func * loopTopFunc = loop->GetFunc();
|
|
|
-
|
|
|
- // We built the list in reverse order, i.e., by prepending to it. Reverse it now so
|
|
|
- // the hoisted instr's can be inserted in the correct order.
|
|
|
- loop->prepassFieldHoistInstrCandidates.Reverse();
|
|
|
-
|
|
|
- // Hoist the field load
|
|
|
- FOREACH_SLISTBASE_ENTRY(IR::Instr *, instr, &loop->prepassFieldHoistInstrCandidates)
|
|
|
- {
|
|
|
- // We should have removed all fields that are hoisted in outer loops already.
|
|
|
-#if DBG
|
|
|
- AssertCanCopyPropOrCSEFieldLoad(instr);
|
|
|
-#endif
|
|
|
- PropertySym * propertySym = instr->GetSrc1()->AsSymOpnd()->m_sym->AsPropertySym();
|
|
|
- SymID symId = propertySym->m_id;
|
|
|
-
|
|
|
- if (loop->fieldHoistSymMap.ContainsKey(symId))
|
|
|
- {
|
|
|
- // The field is already hoisted
|
|
|
-#if DBG
|
|
|
- StackSym * hoistedCopySym;
|
|
|
- Assert(loop == FindFieldHoistStackSym(loop, symId, &hoistedCopySym, instr));
|
|
|
-#endif
|
|
|
- continue;
|
|
|
- }
|
|
|
-
|
|
|
- Assert(landingPad->globOptData.IsLive(propertySym->m_stackSym));
|
|
|
-
|
|
|
- if (fieldHoistCandidates->Test(symId))
|
|
|
- {
|
|
|
- // Hoist non-live field in
|
|
|
- Value * oldValue = landingPad->globOptData.FindValueFromMapDirect(symId);
|
|
|
- Value * newValue = this->currentBlock->globOptData.FindValueFromMapDirect(symId);
|
|
|
- HoistFieldLoad(propertySym, loop, instr, oldValue, newValue);
|
|
|
- continue;
|
|
|
- }
|
|
|
-
|
|
|
- if (!liveInFieldHoistCandidates->Test(symId))
|
|
|
- {
|
|
|
- // Not live in back edge; don't hoist field
|
|
|
- Assert(!this->currentBlock->globOptData.liveFields->Test(symId));
|
|
|
- continue;
|
|
|
- }
|
|
|
-
|
|
|
- Assert(landingPad->globOptData.liveFields->Test(symId));
|
|
|
- Assert(this->currentBlock->globOptData.liveFields->Test(symId));
|
|
|
-
|
|
|
- // If the value is live in, we shouldn't have a hoisted symbol already
|
|
|
- Assert(!this->IsHoistedPropertySym(symId, loop->parent));
|
|
|
- Value * oldValue = landingPad->globOptData.FindPropertyValue(symId);
|
|
|
- AssertMsg(oldValue != nullptr, "We should have created an initial value for the field");
|
|
|
- ValueInfo *oldValueInfo = oldValue->GetValueInfo();
|
|
|
-
|
|
|
- Value * newValue = this->currentBlock->globOptData.FindPropertyValue(symId);
|
|
|
-
|
|
|
- // The value of the loop isn't invariant, we need to create a value to hold the field through the loop
|
|
|
-
|
|
|
- int32 oldIntConstantValue;
|
|
|
- if (oldValueInfo->TryGetIntConstantValue(&oldIntConstantValue))
|
|
|
- {
|
|
|
- // Generate the constant load
|
|
|
- IR::IntConstOpnd * intConstOpnd = IR::IntConstOpnd::New(oldIntConstantValue, TyInt32, loopTopFunc);
|
|
|
- this->HoistFieldLoadValue(loop, newValue, symId, Js::OpCode::LdC_A_I4, intConstOpnd);
|
|
|
- }
|
|
|
- else if (oldValueInfo->IsFloatConstant())
|
|
|
- {
|
|
|
- // Generate the constant load
|
|
|
- this->HoistFieldLoadValue(loop, newValue, symId,
|
|
|
- Js::OpCode::LdC_A_R8, IR::FloatConstOpnd::New(oldValueInfo->AsFloatConstant()->FloatValue(), TyFloat64, loopTopFunc));
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- // This should be looking at the landingPad's value
|
|
|
- Sym * copySym = landingPad->globOptData.GetCopyPropSym(nullptr, oldValue);
|
|
|
-
|
|
|
- if (copySym != nullptr)
|
|
|
- {
|
|
|
- if (newValue && oldValue->GetValueNumber() == newValue->GetValueNumber())
|
|
|
- {
|
|
|
- // The value of the field is invariant through the loop.
|
|
|
- // Copy prop can deal with this so we don't need to do anything.
|
|
|
- continue;
|
|
|
- }
|
|
|
-
|
|
|
- StackSym * copyStackSym = copySym->AsStackSym();
|
|
|
+ IR::Instr * bailOutTarget = EnsureBailTarget(loop);
|
|
|
|
|
|
- // Transfer from an old copy prop value
|
|
|
- IR::RegOpnd * srcOpnd = IR::RegOpnd::New(copyStackSym, TyVar, loopTopFunc);
|
|
|
- srcOpnd->SetIsJITOptimizedReg(true);
|
|
|
- this->HoistFieldLoadValue(loop, newValue, symId, Js::OpCode::Ld_A, srcOpnd);
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- // We don't have a copy sym, even though the field value is live, we can't copy prop.
|
|
|
- // Generate the field load instead.
|
|
|
-#if DBG
|
|
|
- landingPad->globOptData.liveFields->Clear(symId);
|
|
|
- this->currentBlock->globOptData.liveFields->Clear(symId);
|
|
|
- liveInFieldHoistCandidates->Clear(symId);
|
|
|
- fieldHoistCandidates->Set(symId);
|
|
|
-#endif
|
|
|
- HoistNewFieldLoad(propertySym, loop, instr, oldValue, newValue);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- NEXT_SLISTBASE_ENTRY;
|
|
|
+ Func * bailOutFunc = loop->GetFunc();
|
|
|
+ Assert(loop->bailOutInfo->bailOutFunc == bailOutFunc);
|
|
|
|
|
|
- this->FinishOptHoistedPropOps(loop);
|
|
|
+ IR::MemRefOpnd * disableImplicitCallAddress = IR::MemRefOpnd::New(this->func->GetThreadContextInfo()->GetDisableImplicitFlagsAddr(), TyInt8, bailOutFunc);
|
|
|
+ IR::IntConstOpnd * disableImplicitCallAndExceptionValue = IR::IntConstOpnd::New(DisableImplicitCallAndExceptionFlag, TyInt8, bailOutFunc, true);
|
|
|
+ IR::IntConstOpnd * enableImplicitCallAndExceptionValue = IR::IntConstOpnd::New(DisableImplicitNoFlag, TyInt8, bailOutFunc, true);
|
|
|
|
|
|
- JitAdelete(this->alloc, loop->fieldHoistCandidateTypes);
|
|
|
- fieldHoistCandidateTypes = nullptr;
|
|
|
- loop->fieldHoistCandidateTypes = nullptr;
|
|
|
+ IR::Opnd * implicitCallFlags = Lowerer::GetImplicitCallFlagsOpnd(bailOutFunc);
|
|
|
+ IR::IntConstOpnd * noImplicitCall = IR::IntConstOpnd::New(Js::ImplicitCall_None, TyInt8, bailOutFunc, true);
|
|
|
|
|
|
- loop->regAlloc.liveOnBackEdgeSyms->Or(loop->hoistedFieldCopySyms);
|
|
|
+ // Consider: if we are already doing implicit call in the outer loop, we don't need to clear the implicit call bit again
|
|
|
+ IR::Instr * clearImplicitCall = IR::Instr::New(Js::OpCode::Ld_A, implicitCallFlags, noImplicitCall, bailOutFunc);
|
|
|
+ bailOutTarget->InsertBefore(clearImplicitCall);
|
|
|
|
|
|
-#if DBG || DBG_DUMP
|
|
|
- if (loop->hoistedFields->IsEmpty())
|
|
|
- {
|
|
|
- Assert(loop->fieldHoistSymMap.Count() == 0);
|
|
|
- liveInFieldHoistCandidates->ClearAll();
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- // Update liveInFieldHoistCandidates for assert in FindFieldHoistStackSym
|
|
|
- liveInFieldHoistCandidates->And(loop->hoistedFields);
|
|
|
+ IR::Instr * disableImplicitCall = IR::Instr::New(Js::OpCode::Ld_A, disableImplicitCallAddress, disableImplicitCallAndExceptionValue, bailOutFunc);
|
|
|
+ bailOutTarget->InsertBefore(disableImplicitCall);
|
|
|
|
|
|
- if (Js::Configuration::Global.flags.Trace.IsEnabled(Js::FieldHoistPhase, this->func->GetSourceContextId(), this->func->GetLocalFunctionId()))
|
|
|
- {
|
|
|
- Output::Print(_u("FieldHoist: All candidates: "));
|
|
|
- loop->hoistedFields->Dump();
|
|
|
- Output::Print(_u("FieldHoist: Live in candidates: "));
|
|
|
- liveInFieldHoistCandidates->Dump();
|
|
|
- }
|
|
|
- }
|
|
|
-#else
|
|
|
- JitAdelete(this->alloc, liveInFieldHoistCandidates);
|
|
|
- loop->liveInFieldHoistCandidates = nullptr;
|
|
|
-#endif
|
|
|
+ endDisableImplicitCall = IR::Instr::New(Js::OpCode::Ld_A, disableImplicitCallAddress, enableImplicitCallAndExceptionValue, bailOutFunc);
|
|
|
+ bailOutTarget->InsertBefore(endDisableImplicitCall);
|
|
|
|
|
|
- JitAdelete(this->alloc, fieldHoistCandidates);
|
|
|
- loop->fieldHoistCandidates = nullptr;
|
|
|
-}
|
|
|
+ IR::BailOutInstr * bailOutInstr = IR::BailOutInstr::New(Js::OpCode::BailOnNotEqual, IR::BailOutOnImplicitCalls, loop->bailOutInfo, loop->bailOutInfo->bailOutFunc);
|
|
|
+ bailOutInstr->SetSrc1(implicitCallFlags);
|
|
|
+ bailOutInstr->SetSrc2(noImplicitCall);
|
|
|
+ bailOutTarget->InsertBefore(bailOutInstr);
|
|
|
|
|
|
-void
|
|
|
-GlobOpt::CheckFieldHoistCandidate(IR::Instr * instr, PropertySym * sym)
|
|
|
-{
|
|
|
- // See if this field load is hoistable.
|
|
|
- // This load probably may have a store or kill before it.
|
|
|
- // We will hoist it in another path. Just copy prop the value from the field store.
|
|
|
- //
|
|
|
- // For example:
|
|
|
- // loop
|
|
|
- // {
|
|
|
- // if ()
|
|
|
- // {
|
|
|
- // o.i =
|
|
|
- // = o.i <= not hoistable (but can copy prop)
|
|
|
- // }
|
|
|
- // else
|
|
|
- // {
|
|
|
- // = o.i <= hoistable
|
|
|
- // }
|
|
|
- // }
|
|
|
- if (this->currentBlock->globOptData.hoistableFields->TestAndClear(sym->m_id))
|
|
|
- {
|
|
|
- Assert(this->currentBlock->globOptData.liveFields->Test(sym->m_id));
|
|
|
- // We're adding this instruction as a candidate for hoisting. If it gets hoisted, its jit-time inline
|
|
|
- // cache will be used to generate the type check and bailout at the top of the loop. After we bail out,
|
|
|
- // however, we may not go down the code path on which this instruction resides, and so the inline cache
|
|
|
- // will not turn polymorphic. If we then re-jit, we would hoist the same instruction again, and get
|
|
|
- // stuck in infinite bailout cycle. That's why we use BailOutRecord::polymorphicCacheIndex for hoisted
|
|
|
- // field loads to force the profile info for the right inline cache into polymorphic state.
|
|
|
- this->rootLoopPrePass->prepassFieldHoistInstrCandidates.Prepend(this->alloc, instr);
|
|
|
-#if DBG_DUMP
|
|
|
- if (Js::Configuration::Global.flags.Trace.IsEnabled(Js::FieldHoistPhase, this->func->GetSourceContextId(), this->func->GetLocalFunctionId()))
|
|
|
- {
|
|
|
- Output::Print(_u("FieldHoist: Prepass marked hoist load"));
|
|
|
- Output::SkipToColumn(30);
|
|
|
- Output::Print(_u(" : "));
|
|
|
- instr->Dump();
|
|
|
- }
|
|
|
-#endif
|
|
|
- }
|
|
|
+ loop->endDisableImplicitCall = endDisableImplicitCall;
|
|
|
+ return endDisableImplicitCall;
|
|
|
}
|
|
|
|
|
|
-void
|
|
|
-GlobOpt::FinishOptHoistedPropOps(Loop * loop)
|
|
|
-{
|
|
|
- // Set up hoisted fields for object type specialization.
|
|
|
- Assert(loop);
|
|
|
-
|
|
|
- // This extra check for parent loop was added as a fix for Windows 8 Bug 480217. The issue there might have affected
|
|
|
- // the original redundant type elimination, but does not cause problems for object type spec. With this check some
|
|
|
- // operations which were candidates for object type spec in the backward pass (where we only checked the current loop),
|
|
|
- // could unexpectedly not be candidates, anymore. This led to problems in the lowerer.
|
|
|
- // (Do this only if we're doing the optimization in the loop's parent, which is where we're inserting
|
|
|
- // the hoisted instruction.)
|
|
|
- //if (loop->parent && !DoFieldRefOpts(loop->parent))
|
|
|
- //{
|
|
|
- // return;
|
|
|
- //}
|
|
|
-
|
|
|
- bool doFieldRefOpts = DoFieldRefOpts(loop);
|
|
|
- bool forceFieldHoisting = PHASE_FORCE(Js::FieldHoistPhase, this->func);
|
|
|
- bool doForcedTypeChecksOnly = !doFieldRefOpts && forceFieldHoisting;
|
|
|
-
|
|
|
- if (!doFieldRefOpts && !forceFieldHoisting)
|
|
|
- {
|
|
|
- IR::Instr * instrEnd = loop->endDisableImplicitCall;
|
|
|
- if (instrEnd == nullptr)
|
|
|
- {
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- FOREACH_INSTR_EDITING_IN_RANGE(instr, instrNext, loop->landingPad->GetFirstInstr(), instrEnd)
|
|
|
- {
|
|
|
- // LdMethodFromFlags must always have a type check and bailout. If we hoisted it as a result of
|
|
|
- // -force:fieldHoist, we will have to set the bailout here again, even if there are implicit calls
|
|
|
- // in the loop (and DoFieldRefOpts returns false). See Windows Blue Bugs 608503 and 610237.
|
|
|
- if (instr->m_opcode == Js::OpCode::LdMethodFromFlags)
|
|
|
- {
|
|
|
- instr = SetTypeCheckBailOut(instr->GetSrc1(), instr, loop->bailOutInfo);
|
|
|
- }
|
|
|
- }
|
|
|
- NEXT_INSTR_EDITING_IN_RANGE;
|
|
|
-
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- // Walk the implicit-call-disabled region in the loop header, creating PropertySymOpnd's and
|
|
|
- // tracking liveness of the type/slot-array syms.
|
|
|
- IR::Instr * instrEnd = loop->endDisableImplicitCall;
|
|
|
- if (instrEnd == nullptr)
|
|
|
- {
|
|
|
- return;
|
|
|
- }
|
|
|
- Assert(loop->bailOutInfo->bailOutInstr != nullptr);
|
|
|
-
|
|
|
- // Consider (ObjTypeSpec): Do we really need all this extra tracking of live fields on back edges, so as to
|
|
|
- // remove them from the live fields on the loop header? We already do this in MergeBlockData called from
|
|
|
- // MergePredBlocksValueMaps, which takes place just before we get here.
|
|
|
-
|
|
|
- // Build the set of fields that are live on all back edges.
|
|
|
- // Use this to limit the type symbols we make live into the loop. We made the types of the hoisted fields
|
|
|
- // live in the prepass, so if they're not live on a back edge, that means some path through the loop
|
|
|
- // kills them.
|
|
|
- BVSparse<JitArenaAllocator> *bvBackEdge = nullptr;
|
|
|
- FOREACH_PREDECESSOR_BLOCK(predBlock, loop->GetHeadBlock())
|
|
|
- {
|
|
|
- if (!loop->IsDescendentOrSelf(predBlock->loop))
|
|
|
- {
|
|
|
- // This is the edge that enters the loop - not interesting here.
|
|
|
- continue;
|
|
|
- }
|
|
|
- if (!bvBackEdge)
|
|
|
- {
|
|
|
- bvBackEdge = predBlock->globOptData.liveFields;
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- bvBackEdge = bvBackEdge->AndNew(predBlock->globOptData.liveFields, this->alloc);
|
|
|
- }
|
|
|
- }
|
|
|
- NEXT_PREDECESSOR_BLOCK;
|
|
|
-
|
|
|
- if (!doForcedTypeChecksOnly)
|
|
|
- {
|
|
|
- FOREACH_INSTR_EDITING_IN_RANGE(instr, instrNext, loop->landingPad->GetFirstInstr(), instrEnd)
|
|
|
- {
|
|
|
- IR::Opnd *opnd = instr->GetSrc1();
|
|
|
- if (opnd && opnd->IsSymOpnd() && opnd->AsSymOpnd()->IsPropertySymOpnd())
|
|
|
- {
|
|
|
- bool isHoistedTypeValue = false;
|
|
|
- bool isTypeInvariant = false;
|
|
|
- if (opnd->AsPropertySymOpnd()->HasObjectTypeSym())
|
|
|
- {
|
|
|
- StackSym* typeSym = opnd->AsPropertySymOpnd()->GetObjectTypeSym();
|
|
|
-
|
|
|
- // We've cleared the live bits for types that are purely hoisted (not live into the loop),
|
|
|
- // so we can't use FindObjectTypeValue here.
|
|
|
- Value* landingPadValue = loop->landingPad->globOptData.FindValueFromMapDirect(typeSym->m_id);
|
|
|
- Value* headerValue = loop->GetHeadBlock()->globOptData.FindValueFromMapDirect(typeSym->m_id);
|
|
|
- isHoistedTypeValue = landingPadValue != nullptr && loop->fieldHoistCandidateTypes->Test(typeSym->m_id);
|
|
|
- isTypeInvariant = landingPadValue != nullptr && headerValue != nullptr && landingPadValue->GetValueNumber() == headerValue->GetValueNumber();
|
|
|
- }
|
|
|
-
|
|
|
- // Prepare the operand for object type specialization by creating a type sym for it, if not yet present
|
|
|
- // and marking it as candidate for specialization.
|
|
|
- PreparePropertySymOpndForTypeCheckSeq(opnd->AsPropertySymOpnd(), instr, loop);
|
|
|
-
|
|
|
- // Let's update the existing type value, if possible, to retain the value number created in pre-pass.
|
|
|
- bool changesTypeValue = false;
|
|
|
- FinishOptPropOp(instr, opnd->AsPropertySymOpnd(), loop->landingPad, /* updateExistingValue = */ isHoistedTypeValue, nullptr, &changesTypeValue);
|
|
|
- instr = SetTypeCheckBailOut(opnd, instr, loop->bailOutInfo);
|
|
|
-
|
|
|
- // If we changed the type's value in the landing pad we want to reflect this change in the header block as well,
|
|
|
- // but only if the type is invariant throughout the loop. Note that if the type was live into the loop and
|
|
|
- // live on all back edges, but not invariant, it will already be live in the header, but its value will be blank,
|
|
|
- // because we merge type values conservatively on loop back edges. (see MergeJsTypeValueInfo)
|
|
|
-
|
|
|
- // Consider (ObjTypeSpec): There are corner cases where we copy prop an object pointer into the newly hoisted instruction,
|
|
|
- // and that object doesn't have a type yet. We then create a type on the fly (see GenerateHoistFieldLoad and
|
|
|
- // CopyPropPropertySymObj), and don't have a value for it in the landing pad. Thus we can't prove that the type is invariant
|
|
|
- // throughout the loop, and so we won't produce a value for it into the loop. This could be addressed by creating
|
|
|
- // a mapping of type syms from before to after object pointer copy prop.
|
|
|
- if (changesTypeValue && isTypeInvariant)
|
|
|
- {
|
|
|
- Assert(opnd->AsPropertySymOpnd()->HasObjectTypeSym());
|
|
|
- StackSym* typeSym = opnd->AsPropertySymOpnd()->GetObjectTypeSym();
|
|
|
-
|
|
|
- // If we changed the type value in the landing pad, we must have set it live there.
|
|
|
- Value* landingPadValue = loop->landingPad->globOptData.FindObjectTypeValue(typeSym);
|
|
|
- Assert(landingPadValue != nullptr && landingPadValue->GetValueInfo()->IsJsType());
|
|
|
-
|
|
|
- // But in the loop header we may have only a value with the live bit still cleared,
|
|
|
- // so we can't use FindObjectTypeValue here.
|
|
|
- Value* headerValue = loop->GetHeadBlock()->globOptData.FindValueFromMapDirect(typeSym->m_id);
|
|
|
- Assert(headerValue != nullptr && headerValue->GetValueInfo()->IsJsType());
|
|
|
-
|
|
|
- Assert(!isHoistedTypeValue || landingPadValue->GetValueNumber() == headerValue->GetValueNumber());
|
|
|
- JsTypeValueInfo* valueInfo = landingPadValue->GetValueInfo()->AsJsType();
|
|
|
- valueInfo->SetIsShared();
|
|
|
- headerValue->SetValueInfo(valueInfo);
|
|
|
-
|
|
|
- loop->GetHeadBlock()->globOptData.liveFields->Set(typeSym->m_id);
|
|
|
- }
|
|
|
-
|
|
|
-#if DBG
|
|
|
- if (opnd->AsPropertySymOpnd()->HasObjectTypeSym())
|
|
|
- {
|
|
|
- StackSym* typeSym = opnd->AsPropertySymOpnd()->GetObjectTypeSym();
|
|
|
- Assert(!isHoistedTypeValue || isTypeInvariant || !loop->GetHeadBlock()->globOptData.liveFields->Test(typeSym->m_id));
|
|
|
- }
|
|
|
-#endif
|
|
|
- }
|
|
|
- }
|
|
|
- NEXT_INSTR_EDITING_IN_RANGE;
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- FOREACH_INSTR_EDITING_IN_RANGE(instr, instrNext, loop->landingPad->GetFirstInstr(), instrEnd)
|
|
|
- {
|
|
|
- // LdMethodFromFlags must always have a type check and bailout. If we hoisted it as a result of
|
|
|
- // -force:fieldHoist, we will have to set the bailout here again, even if there are implicit calls
|
|
|
- // in the loop.
|
|
|
- if (instr->m_opcode == Js::OpCode::LdMethodFromFlags)
|
|
|
- {
|
|
|
- instr = SetTypeCheckBailOut(instr->GetSrc1(), instr, loop->bailOutInfo);
|
|
|
- }
|
|
|
- }
|
|
|
- NEXT_INSTR_EDITING_IN_RANGE;
|
|
|
- }
|
|
|
-
|
|
|
- if (bvBackEdge)
|
|
|
- {
|
|
|
- // Take the fields not live on some back edge out of the set that's live into the loop.
|
|
|
- this->currentBlock->globOptData.liveFields->And(bvBackEdge);
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-void
|
|
|
-GlobOpt::HoistFieldLoadValue(Loop * loop, Value * newValue, SymID symId, Js::OpCode opcode, IR::Opnd * srcOpnd)
|
|
|
-{
|
|
|
- IR::Instr * insertInstr = this->EnsureDisableImplicitCallRegion(loop);
|
|
|
-
|
|
|
- Assert(!this->IsLoopPrePass());
|
|
|
- Assert(IsPropertySymId(symId));
|
|
|
- Assert(!loop->fieldHoistCandidates->Test(symId));
|
|
|
- Assert(loop->landingPad->globOptData.liveFields->Test(symId));
|
|
|
- Assert(this->currentBlock->globOptData.liveFields->Test(symId));
|
|
|
-
|
|
|
- Func * loopTopFunc = loop->GetFunc();
|
|
|
-
|
|
|
- // Just transfer the copy prop sym to a new stack sym for the property.
|
|
|
- // Consider: What happens if the outer loop already has a field hoist stack sym for this propertysym?
|
|
|
- StackSym * newStackSym = StackSym::New(TyVar, loopTopFunc);
|
|
|
-
|
|
|
- // This new stack sym may or may not be single def.
|
|
|
- // Just make it not a single def so that we don't lose the value when it become non-single def.
|
|
|
- newStackSym->m_isSingleDef = false;
|
|
|
- IR::RegOpnd * newOpnd = IR::RegOpnd::New(newStackSym, TyVar, loopTopFunc);
|
|
|
- IR::Instr * newInstr = IR::Instr::New(opcode, newOpnd, srcOpnd, loopTopFunc);
|
|
|
-
|
|
|
- insertInstr->InsertBefore(newInstr);
|
|
|
- loop->landingPad->globOptData.liveVarSyms->Set(newStackSym->m_id);
|
|
|
- loop->varSymsOnEntry->Set(newStackSym->m_id);
|
|
|
-
|
|
|
- // Update value in the current block
|
|
|
- if (newValue == nullptr)
|
|
|
- {
|
|
|
- // Even though we don't use the symStore to copy prop the hoisted stack sym in the loop
|
|
|
- // we might be able to propagate it out of the loop. Create a value just in case.
|
|
|
- newValue = this->NewGenericValue(ValueType::Uninitialized, newStackSym);
|
|
|
-
|
|
|
- // This should pass the sym directly.
|
|
|
- Sym *sym = this->func->m_symTable->Find(symId);
|
|
|
-
|
|
|
- this->currentBlock->globOptData.SetValue(newValue, sym);
|
|
|
- Assert(newValue->GetValueInfo()->GetSymStore() == newStackSym);
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- this->currentBlock->globOptData.SetValue(newValue, newStackSym);
|
|
|
- this->SetSymStoreDirect(newValue->GetValueInfo(), newStackSym);
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- this->currentBlock->globOptData.liveVarSyms->Set(newStackSym->m_id);
|
|
|
- loop->fieldHoistSymMap.Add(symId, newStackSym);
|
|
|
- loop->hoistedFieldCopySyms->Set(newStackSym->m_id);
|
|
|
-
|
|
|
- loop->hasHoistedFields = true;
|
|
|
- loop->hoistedFields->Set(symId);
|
|
|
-
|
|
|
- if(newInstr->GetSrc1()->IsRegOpnd())
|
|
|
- {
|
|
|
- // Make sure the source sym is available as a var
|
|
|
- const auto srcRegOpnd = newInstr->GetSrc1()->AsRegOpnd();
|
|
|
- if(!loop->landingPad->globOptData.liveVarSyms->Test(srcRegOpnd->m_sym->m_id))
|
|
|
- {
|
|
|
- this->ToVar(newInstr, srcRegOpnd, loop->landingPad, nullptr, false);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
-#if DBG_DUMP
|
|
|
- if (Js::Configuration::Global.flags.Trace.IsEnabled(Js::FieldHoistPhase, this->func->GetSourceContextId(), this->func->GetLocalFunctionId()))
|
|
|
- {
|
|
|
- Output::Print(_u("FieldHoist: Live value load "));
|
|
|
- this->func->m_symTable->Find(symId)->Dump();
|
|
|
- Output::SkipToColumn(30);
|
|
|
- Output::Print(_u(" : "));
|
|
|
- newInstr->Dump();
|
|
|
- }
|
|
|
-#endif
|
|
|
-}
|
|
|
-
|
|
|
-bool
|
|
|
-GlobOpt::IsHoistablePropertySym(SymID symId) const
|
|
|
-{
|
|
|
- return this->currentBlock->globOptData.hoistableFields && this->currentBlock->globOptData.hoistableFields->Test(symId);
|
|
|
-}
|
|
|
-
|
|
|
-bool
|
|
|
-GlobOpt::HasHoistableFields(BasicBlock const * basicBlock)
|
|
|
-{
|
|
|
- return HasHoistableFields(&basicBlock->globOptData);
|
|
|
-}
|
|
|
-
|
|
|
-bool
|
|
|
-GlobOpt::HasHoistableFields(GlobOptBlockData const * globOptData)
|
|
|
-{
|
|
|
- return globOptData->hoistableFields && !globOptData->hoistableFields->IsEmpty();
|
|
|
-}
|
|
|
-
|
|
|
-Loop *
|
|
|
-GlobOpt::FindFieldHoistStackSym(Loop * startLoop, SymID propertySymId, StackSym ** copySym, IR::Instr * instrToHoist) const
|
|
|
-{
|
|
|
- Assert(IsPropertySymId(propertySymId));
|
|
|
-
|
|
|
- if (instrToHoist && instrToHoist->m_opcode == Js::OpCode::LdMethodFromFlags)
|
|
|
- {
|
|
|
- return nullptr;
|
|
|
- }
|
|
|
-
|
|
|
- Loop * loop = startLoop;
|
|
|
-
|
|
|
- while (loop && this->DoFieldHoisting(loop))
|
|
|
- {
|
|
|
- if (loop->fieldHoistSymMap.TryGetValue(propertySymId, copySym))
|
|
|
- {
|
|
|
- Assert(loop->hasHoistedFields);
|
|
|
- Assert(loop->hoistedFields->Test(propertySymId));
|
|
|
-
|
|
|
- if (this->IsLoopPrePass())
|
|
|
- {
|
|
|
- return loop;
|
|
|
- }
|
|
|
-
|
|
|
- BasicBlock * landingPad = loop->landingPad;
|
|
|
-#if DBG
|
|
|
- BOOL liveInSym = FALSE;
|
|
|
- liveInSym = loop->liveInFieldHoistCandidates->Test(propertySymId);
|
|
|
-
|
|
|
- Assert(landingPad->globOptData.liveFields->Test(propertySymId));
|
|
|
- Assert(landingPad->globOptData.liveVarSyms->Test((*copySym)->m_id));
|
|
|
-#endif
|
|
|
-
|
|
|
- // This has been hoisted already.
|
|
|
- // Verify the hoisted instruction.
|
|
|
- bool found = false;
|
|
|
- FOREACH_INSTR_BACKWARD_IN_BLOCK(instr, landingPad)
|
|
|
- {
|
|
|
- IR::Opnd * dstOpnd = instr->GetDst();
|
|
|
- if (dstOpnd && dstOpnd->IsRegOpnd() && dstOpnd->AsRegOpnd()->m_sym == *copySym)
|
|
|
- {
|
|
|
- found = true;
|
|
|
-#if DBG
|
|
|
- // We used to try to assert that the property sym on the instruction in the landing pad
|
|
|
- // matched the one on the instruction we're changing now. But we may have done object ptr
|
|
|
- // copy prop in the landing pad, so the assertion no longer holds.
|
|
|
- if (liveInSym)
|
|
|
- {
|
|
|
- Assert((instr->m_opcode == Js::OpCode::Ld_A && instr->GetSrc1()->IsRegOpnd())
|
|
|
- || (instr->m_opcode == Js::OpCode::LdC_A_I4 && instr->GetSrc1()->IsIntConstOpnd())
|
|
|
- || instr->m_opcode == Js::OpCode::LdC_A_R8 && instr->GetSrc1()->IsFloatConstOpnd());
|
|
|
- }
|
|
|
- else if (instrToHoist)
|
|
|
- {
|
|
|
- bool instrIsLdFldEquivalent = (instr->m_opcode == Js::OpCode::LdFld || instr->m_opcode == Js::OpCode::LdFldForCallApplyTarget);
|
|
|
- bool instrToHoistIsLdFldEquivalent = (instrToHoist->m_opcode == Js::OpCode::LdFld || instrToHoist->m_opcode == Js::OpCode::LdFldForCallApplyTarget);
|
|
|
- Assert(instr->m_opcode == instrToHoist->m_opcode ||
|
|
|
- instrIsLdFldEquivalent && instrToHoistIsLdFldEquivalent ||
|
|
|
- instr->m_opcode == Js::OpCode::LdMethodFld ||
|
|
|
- instr->m_opcode == Js::OpCode::LdRootMethodFld ||
|
|
|
- instr->m_opcode == Js::OpCode::ScopedLdMethodFld ||
|
|
|
- instrToHoist->m_opcode == Js::OpCode::LdMethodFld ||
|
|
|
- instrToHoist->m_opcode == Js::OpCode::LdRootMethodFld ||
|
|
|
- instrToHoist->m_opcode == Js::OpCode::ScopedLdMethodFld ||
|
|
|
- (instrIsLdFldEquivalent && instrToHoist->m_opcode == Js::OpCode::LdRootFld) ||
|
|
|
- (instr->m_opcode == Js::OpCode::LdMethodFld && instrToHoist->m_opcode == Js::OpCode::LdRootMethodFld) ||
|
|
|
- (instrToHoistIsLdFldEquivalent && instr->m_opcode == Js::OpCode::LdRootFld) ||
|
|
|
- (instrToHoist->m_opcode == Js::OpCode::LdMethodFld && instr->m_opcode == Js::OpCode::LdRootMethodFld));
|
|
|
- }
|
|
|
-#endif
|
|
|
- if (instrToHoist
|
|
|
- && (instrToHoist->m_opcode == Js::OpCode::LdMethodFld ||
|
|
|
- instrToHoist->m_opcode == Js::OpCode::LdRootMethodFld ||
|
|
|
- instrToHoist->m_opcode == Js::OpCode::ScopedLdMethodFld)
|
|
|
- && instr->m_opcode != Js::OpCode::Ld_A
|
|
|
- && instr->m_opcode != Js::OpCode::LdC_A_I4
|
|
|
- && instr->m_opcode != Js::OpCode::LdC_A_R8)
|
|
|
- {
|
|
|
- // We may have property sym referred to by both Ld[Root]Fld and Ld[Root]MethodFld
|
|
|
- // in the loop. If this happens, make sure the hoisted instruction is Ld[Root]MethodFld
|
|
|
- // so we get the prototype inline cache fast path we want.
|
|
|
- // Other differences such as error messages and HostDispatch behavior shouldn't
|
|
|
- // matter, because we'll bail out in those cases.
|
|
|
- Assert(instr->GetSrc1()->IsSymOpnd() && instr->GetSrc1()->AsSymOpnd()->m_sym->IsPropertySym());
|
|
|
- instr->m_opcode = instrToHoist->m_opcode;
|
|
|
- }
|
|
|
- else if (instrToHoist &&
|
|
|
- ((instr->m_opcode == Js::OpCode::LdFld && instrToHoist->m_opcode == Js::OpCode::LdRootFld)
|
|
|
- || (instr->m_opcode == Js::OpCode::LdMethodFld && instrToHoist->m_opcode == Js::OpCode::LdRootMethodFld)))
|
|
|
- {
|
|
|
- instr->m_opcode = instrToHoist->m_opcode;
|
|
|
- }
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
- NEXT_INSTR_BACKWARD_IN_BLOCK;
|
|
|
- Assert(found);
|
|
|
-
|
|
|
- return loop;
|
|
|
- }
|
|
|
- Assert(!loop->hoistedFields || !loop->hoistedFields->Test(propertySymId));
|
|
|
- loop = loop->parent;
|
|
|
- }
|
|
|
- return nullptr;
|
|
|
-}
|
|
|
-
|
|
|
-void
|
|
|
-GlobOpt::HoistFieldLoad(PropertySym * sym, Loop * loop, IR::Instr * instr, Value * oldValue, Value * newValue)
|
|
|
-{
|
|
|
- Loop * parentLoop = loop->parent;
|
|
|
- if (parentLoop != nullptr)
|
|
|
- {
|
|
|
- StackSym * copySym;
|
|
|
- Loop * hoistedLoop = FindFieldHoistStackSym(parentLoop, sym->m_id, ©Sym, instr);
|
|
|
- if (hoistedLoop != nullptr)
|
|
|
- {
|
|
|
- // Use an outer loop pre-assigned stack sym if it is already hoisted there
|
|
|
- Assert(hoistedLoop != loop);
|
|
|
- GenerateHoistFieldLoad(sym, loop, instr, copySym, oldValue, newValue);
|
|
|
- return;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- HoistNewFieldLoad(sym, loop, instr, oldValue, newValue);
|
|
|
-}
|
|
|
-
|
|
|
-void
|
|
|
-GlobOpt::HoistNewFieldLoad(PropertySym * sym, Loop * loop, IR::Instr * instr, Value * oldValue, Value * newValue)
|
|
|
-{
|
|
|
- Assert(!this->IsHoistedPropertySym(sym->m_id, loop));
|
|
|
-
|
|
|
- StackSym * newStackSym = StackSym::New(TyVar, this->func);
|
|
|
-
|
|
|
- // This new stack sym may or may not be single def.
|
|
|
- // Just make it not a single def so that we don't lose the value when it become non-single def.
|
|
|
- newStackSym->m_isSingleDef = false;
|
|
|
-
|
|
|
- GenerateHoistFieldLoad(sym, loop, instr, newStackSym, oldValue, newValue);
|
|
|
-}
|
|
|
-
|
|
|
-void
|
|
|
-GlobOpt::GenerateHoistFieldLoad(PropertySym * sym, Loop * loop, IR::Instr * instr, StackSym * newStackSym, Value * oldValue, Value * newValue)
|
|
|
-{
|
|
|
- Assert(loop != nullptr);
|
|
|
-
|
|
|
- SymID symId = sym->m_id;
|
|
|
- BasicBlock * landingPad = loop->landingPad;
|
|
|
-
|
|
|
-#if DBG
|
|
|
- Assert(!this->IsLoopPrePass());
|
|
|
- AssertCanCopyPropOrCSEFieldLoad(instr);
|
|
|
- Assert(instr->GetSrc1()->AsSymOpnd()->m_sym == sym);
|
|
|
-
|
|
|
- Assert(loop->fieldHoistCandidates->Test(symId));
|
|
|
- Assert(!landingPad->globOptData.liveFields->Test(sym->m_id));
|
|
|
- Assert(!this->currentBlock->globOptData.liveFields->Test(sym->m_id));
|
|
|
- Assert(!loop->fieldHoistSymMap.ContainsKey(symId));
|
|
|
-#endif
|
|
|
-
|
|
|
- loop->fieldHoistSymMap.Add(symId, newStackSym);
|
|
|
- loop->hoistedFieldCopySyms->Set(newStackSym->m_id);
|
|
|
-
|
|
|
- Func * loopTopFunc = loop->GetFunc();
|
|
|
-
|
|
|
- // Generate the hoisted field load
|
|
|
- IR::RegOpnd * newDst = IR::RegOpnd::New(newStackSym, TyVar, loopTopFunc);
|
|
|
- IR::SymOpnd * newSrc;
|
|
|
-
|
|
|
- if (instr->GetSrc1() && instr->GetSrc1()->IsSymOpnd() && instr->GetSrc1()->AsSymOpnd()->IsPropertySymOpnd())
|
|
|
- {
|
|
|
- IR::PropertySymOpnd * srcPropertySymOpnd = instr->GetSrc1()->AsPropertySymOpnd();
|
|
|
- AssertMsg(!srcPropertySymOpnd->IsTypeAvailable() && !srcPropertySymOpnd->IsTypeChecked() && !srcPropertySymOpnd->IsWriteGuardChecked(),
|
|
|
- "Why are the object type spec bits set before we specialized this instruction?");
|
|
|
-
|
|
|
- // We only set guarded properties in the dead store pass, so they shouldn't be set here yet. If they were
|
|
|
- // we would need to move them from this operand to the operand which is being copy propagated.
|
|
|
- Assert(srcPropertySymOpnd->GetGuardedPropOps() == nullptr);
|
|
|
-
|
|
|
- // We're hoisting an instruction from the loop, so we're placing it in a different position in the flow. Make sure only the flow
|
|
|
- // insensitive info is copied.
|
|
|
- IR::PropertySymOpnd * newPropertySymOpnd = srcPropertySymOpnd->CopyWithoutFlowSensitiveInfo(loopTopFunc);
|
|
|
- Assert(newPropertySymOpnd->GetObjTypeSpecFlags() == 0);
|
|
|
- Value *const propertyOwnerValueInLandingPad = loop->landingPad->globOptData.FindValue(srcPropertySymOpnd->GetObjectSym());
|
|
|
- if(propertyOwnerValueInLandingPad)
|
|
|
- {
|
|
|
- newPropertySymOpnd->SetPropertyOwnerValueType(propertyOwnerValueInLandingPad->GetValueInfo()->Type());
|
|
|
- }
|
|
|
- newSrc = newPropertySymOpnd;
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- newSrc = IR::SymOpnd::New(sym, TyVar, func);
|
|
|
- }
|
|
|
-
|
|
|
- IR::Instr * newInstr = nullptr;
|
|
|
- ValueType profiledFieldType;
|
|
|
-
|
|
|
- if (instr->IsProfiledInstr())
|
|
|
- {
|
|
|
- profiledFieldType = instr->AsProfiledInstr()->u.FldInfo().valueType;
|
|
|
- }
|
|
|
-
|
|
|
- newInstr = IR::Instr::New(instr->m_opcode, newDst, newSrc, loopTopFunc);
|
|
|
-
|
|
|
- // Win8 910551: Kill the live field for this hoisted field load
|
|
|
- KillLiveFields(newStackSym, this->currentBlock->globOptData.liveFields);
|
|
|
-
|
|
|
- IR::Instr * insertInstr = this->EnsureDisableImplicitCallRegion(loop);
|
|
|
- insertInstr->InsertBefore(newInstr);
|
|
|
-
|
|
|
- // Track use/def of arguments object
|
|
|
- this->OptArguments(newInstr);
|
|
|
-
|
|
|
- landingPad->globOptData.liveFields->Set(symId);
|
|
|
- this->currentBlock->globOptData.liveFields->Set(symId);
|
|
|
-
|
|
|
- // If we are reusing an already hoisted stack sym, while the var version is made live, we need to make sure that specialized
|
|
|
- // versions of it are not live since this is effectively a field reload.
|
|
|
- this->ToVarStackSym(newStackSym, landingPad);
|
|
|
- this->ToVarStackSym(newStackSym, this->currentBlock);
|
|
|
- loop->varSymsOnEntry->Set(newStackSym->m_id);
|
|
|
- loop->int32SymsOnEntry->Clear(newStackSym->m_id);
|
|
|
- loop->lossyInt32SymsOnEntry->Clear(newStackSym->m_id);
|
|
|
- loop->float64SymsOnEntry->Clear(newStackSym->m_id);
|
|
|
-
|
|
|
- Assert(oldValue != nullptr);
|
|
|
-
|
|
|
- // Create a value in case we can copy prop out of the loop
|
|
|
- if (newValue == nullptr || newValue->GetValueInfo()->IsUninitialized())
|
|
|
- {
|
|
|
- const bool hoistValue = newValue && oldValue->GetValueNumber() == newValue->GetValueNumber();
|
|
|
-
|
|
|
- if(newValue)
|
|
|
- {
|
|
|
- // Assuming the profile data gives more precise value types based on the path it took at runtime, we can improve the
|
|
|
- // original value type.
|
|
|
- newValue->GetValueInfo()->Type() = profiledFieldType;
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- newValue = NewGenericValue(profiledFieldType, newDst);
|
|
|
- }
|
|
|
-
|
|
|
- this->currentBlock->globOptData.SetValue(newValue, sym);
|
|
|
- if(hoistValue)
|
|
|
- {
|
|
|
- // The field value is invariant through the loop. Since we're updating its value to a more precise value, hoist the
|
|
|
- // new value up to the loop landing pad where the field is being hoisted.
|
|
|
- Assert(loop == currentBlock->loop);
|
|
|
- Assert(landingPad == loop->landingPad);
|
|
|
- oldValue = CopyValue(newValue, newValue->GetValueNumber());
|
|
|
- landingPad->globOptData.SetValue(oldValue, sym);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- newInstr->GetDst()->SetValueType(oldValue->GetValueInfo()->Type());
|
|
|
- newInstr->GetSrc1()->SetValueType(oldValue->GetValueInfo()->Type());
|
|
|
- loop->landingPad->globOptData.SetValue(oldValue, newStackSym);
|
|
|
-
|
|
|
- this->currentBlock->globOptData.SetValue(newValue, newStackSym);
|
|
|
- instr->GetSrc1()->SetValueType(newValue->GetValueInfo()->Type());
|
|
|
-
|
|
|
- loop->hasHoistedFields = true;
|
|
|
- loop->hoistedFields->Set(sym->m_id);
|
|
|
-
|
|
|
- // Try to do object pointer copy prop. Do it now because, for instance, we want the ToVar we insert below
|
|
|
- // to define the right sym (Win8 906875).
|
|
|
- // Consider: Restructure field hoisting to call OptBlock on the completed loop landing pad instead of
|
|
|
- // doing these optimizations and bitvector updates piecemeal.
|
|
|
-#ifdef DBG
|
|
|
- PropertySym *propertySymUseBefore = nullptr;
|
|
|
- Assert(this->byteCodeUses == nullptr);
|
|
|
- this->byteCodeUsesBeforeOpt->ClearAll();
|
|
|
- GlobOpt::TrackByteCodeSymUsed(instr, this->byteCodeUsesBeforeOpt, &propertySymUseBefore);
|
|
|
-#endif
|
|
|
- this->CaptureByteCodeSymUses(newInstr);
|
|
|
-
|
|
|
- // Consider (ObjTypeSpec): If we copy prop an object sym into the hoisted instruction we lose track of the original
|
|
|
- // object sym's type being invariant through the loop and so we won't produce the new type's value into the loop,
|
|
|
- // and end up with unnecessary type checks in the loop. If the new type isn't live in the landing pad (that is
|
|
|
- // we weren't tracking its liveness and invariance through the loop), but the old type was invariant, let's add
|
|
|
- // the new type to fieldHoistCandidateTypes and produce a value for it in the landing pad and loop header. If the
|
|
|
- // old type was live then its liveness and invariance are already correctly reflected and there is nothing to do.
|
|
|
- this->CopyPropPropertySymObj(newSrc, newInstr);
|
|
|
- if (this->byteCodeUses != nullptr)
|
|
|
- {
|
|
|
- sym = newSrc->m_sym->AsPropertySym();
|
|
|
- this->InsertByteCodeUses(newInstr);
|
|
|
- }
|
|
|
-
|
|
|
- StackSym * propertyBase = sym->m_stackSym;
|
|
|
- if (!landingPad->globOptData.liveVarSyms->Test(propertyBase->m_id))
|
|
|
- {
|
|
|
- IR::RegOpnd *newOpnd = IR::RegOpnd::New(propertyBase, TyVar, instr->m_func);
|
|
|
- this->ToVar(newInstr, newOpnd, landingPad, this->currentBlock->globOptData.FindValue(propertyBase), false);
|
|
|
- }
|
|
|
-
|
|
|
- if (landingPad->globOptData.canStoreTempObjectSyms && landingPad->globOptData.canStoreTempObjectSyms->Test(propertyBase->m_id))
|
|
|
- {
|
|
|
- newSrc->SetCanStoreTemp();
|
|
|
- }
|
|
|
-
|
|
|
-#if DBG_DUMP
|
|
|
- if (Js::Configuration::Global.flags.Trace.IsEnabled(Js::FieldHoistPhase, this->func->GetSourceContextId(), this->func->GetLocalFunctionId()))
|
|
|
- {
|
|
|
- Output::Print(_u("FieldHoist: Hoisted Load "));
|
|
|
- Output::SkipToColumn(30);
|
|
|
- Output::Print(_u(" : "));
|
|
|
- newInstr->Dump();
|
|
|
- }
|
|
|
-#endif
|
|
|
-#if ENABLE_DEBUG_CONFIG_OPTIONS
|
|
|
- if (Js::Configuration::Global.flags.TestTrace.IsEnabled(Js::FieldHoistPhase, this->func->GetSourceContextId(), this->func->GetLocalFunctionId()))
|
|
|
- {
|
|
|
- char16 debugStringBuffer[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
|
|
|
- Output::Print(_u(" FieldHoist: function %s (%s) "), this->func->GetJITFunctionBody()->GetDisplayName(), this->func->GetDebugNumberSet(debugStringBuffer));
|
|
|
- newInstr->DumpTestTrace();
|
|
|
- }
|
|
|
-#endif
|
|
|
-}
|
|
|
-
|
|
|
-Value *
|
|
|
-GlobOpt::CreateFieldSrcValue(PropertySym * sym, PropertySym * originalSym, IR::Opnd ** ppOpnd, IR::Instr * instr)
|
|
|
-{
|
|
|
-#if DBG
|
|
|
- // If the opcode going to kill all field values immediate anyway, we shouldn't be giving it a value
|
|
|
- Assert(!instr->UsesAllFields());
|
|
|
-
|
|
|
- AssertCanCopyPropOrCSEFieldLoad(instr);
|
|
|
-
|
|
|
- Assert(instr->GetSrc1() == *ppOpnd);
|
|
|
-#endif
|
|
|
-
|
|
|
- // Only give a value to fields if we are doing field copy prop.
|
|
|
- // Consider: We should always copy prop local slots, but the only use right now is LdSlot from jit loop body.
|
|
|
- // This should have one onus load, and thus no need for copy prop of field itself. We may want to support
|
|
|
- // copy prop LdSlot if there are other uses of local slots
|
|
|
- if (!this->DoFieldCopyProp())
|
|
|
- {
|
|
|
- return nullptr;
|
|
|
- }
|
|
|
-
|
|
|
- BOOL wasLive = this->currentBlock->globOptData.liveFields->TestAndSet(sym->m_id);
|
|
|
-
|
|
|
- if (this->DoFieldHoisting())
|
|
|
- {
|
|
|
- // We don't track copy prop sym for fields on loop prepass, no point in creating an empty unknown value.
|
|
|
- // If we can copy prop through the back edge, we would have hoisted the field load, in which case we will
|
|
|
- // just pick the live in copy prop sym for the field or create a new sym for the stack sym of the hoist field.
|
|
|
- if (this->IsLoopPrePass())
|
|
|
- {
|
|
|
- // We don't clear the value when we kill the field.
|
|
|
- // Clear it to make sure we don't use the old value.
|
|
|
- this->currentBlock->globOptData.ClearSymValue(sym);
|
|
|
- return nullptr;
|
|
|
- }
|
|
|
- }
|
|
|
- else if (sym != originalSym)
|
|
|
- {
|
|
|
- this->currentBlock->globOptData.liveFields->TestAndSet(originalSym->m_id);
|
|
|
- }
|
|
|
-
|
|
|
- if (!wasLive)
|
|
|
- {
|
|
|
- // We don't clear the value when we kill the field.
|
|
|
- // Clear it to make sure we don't use the old value.
|
|
|
- this->currentBlock->globOptData.ClearSymValue(sym);
|
|
|
- this->currentBlock->globOptData.ClearSymValue(originalSym);
|
|
|
- }
|
|
|
-
|
|
|
- Assert((*ppOpnd)->AsSymOpnd()->m_sym == sym || this->IsLoopPrePass());
|
|
|
- if (wasLive)
|
|
|
- {
|
|
|
- // We should have dealt with field hoist already
|
|
|
- Assert(!IsHoistedPropertySym(sym) || instr->m_opcode == Js::OpCode::CheckFixedFld);
|
|
|
-
|
|
|
- // We don't use the sym store to do copy prop on hoisted fields, but create a value
|
|
|
- // in case it can be copy prop out of the loop.
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- // If it wasn't live, it should not be hoistable
|
|
|
- Assert(!this->IsHoistablePropertySym(sym->m_id));
|
|
|
- }
|
|
|
-
|
|
|
- return this->NewGenericValue(ValueType::Uninitialized, *ppOpnd);
|
|
|
-}
|
|
|
-
|
|
|
-bool
|
|
|
-GlobOpt::FieldHoistOptSrc(IR::Opnd *opnd, IR::Instr *instr, PropertySym * propertySym)
|
|
|
-{
|
|
|
- if (!DoFieldHoisting())
|
|
|
- {
|
|
|
- return false;
|
|
|
- }
|
|
|
- if (!instr->TransfersSrcValue() || instr->m_opcode == Js::OpCode::LdMethodFromFlags)
|
|
|
- {
|
|
|
- // Instructions like typeof don't transfer value of the field, we can't hoist those right now.
|
|
|
- return false;
|
|
|
- }
|
|
|
- if (TrackHoistableFields() && HasHoistableFields(&this->currentBlock->globOptData))
|
|
|
- {
|
|
|
- Assert(this->DoFieldHoisting());
|
|
|
- CheckFieldHoistCandidate(instr, propertySym);
|
|
|
-
|
|
|
- // This may have been a hoistable field with respect to the current loop. If so, that means:
|
|
|
- // - It is assumed that it will be live on the back-edge and hence currently live for the purposes of determining
|
|
|
- // whether to hoist the field.
|
|
|
- // - It is not already hoisted outside a parent loop or not live coming into this loop.
|
|
|
- // - It is not already marked for hoisting in this loop.
|
|
|
- //
|
|
|
- // If this is a hoistable field, and if the field is ultimately chosen to be hoisted outside this loop, the field will
|
|
|
- // be reloaded in this loop's landing pad. However, since the field may already have been hoisted outside a parent
|
|
|
- // loop with a specialized stack sym still live and a value still available (since these are killed lazily), neither of
|
|
|
- // which are valid anymore due to the reload, we still need to kill the specialized stack syms and the field value. On
|
|
|
- // the other hand, if this was not a hoistable field, we need to treat it as a field load anyway. So, since this is the
|
|
|
- // first use of the field in this loop, fall through to reload the field.
|
|
|
- }
|
|
|
- else if (!this->IsLoopPrePass())
|
|
|
- {
|
|
|
- if (CopyPropHoistedFields(propertySym, &opnd, instr))
|
|
|
- {
|
|
|
- return true;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- this->ReloadFieldHoistStackSym(instr, propertySym);
|
|
|
- return false;
|
|
|
-}
|
|
|
-
|
|
|
-void
|
|
|
-GlobOpt::FieldHoistOptDst(IR::Instr * instr, PropertySym * propertySym, Value * src1Val)
|
|
|
-{
|
|
|
- if(DoFieldHoisting())
|
|
|
- {
|
|
|
- switch (instr->m_opcode)
|
|
|
- {
|
|
|
- case Js::OpCode::StSlot:
|
|
|
- case Js::OpCode::StSlotChkUndecl:
|
|
|
- case Js::OpCode::StFld:
|
|
|
- case Js::OpCode::StRootFld:
|
|
|
- case Js::OpCode::StFldStrict:
|
|
|
- case Js::OpCode::StRootFldStrict:
|
|
|
- CopyStoreFieldHoistStackSym(instr, propertySym, src1Val);
|
|
|
- break;
|
|
|
- }
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-bool
|
|
|
-GlobOpt::CopyPropHoistedFields(PropertySym * sym, IR::Opnd ** ppOpnd, IR::Instr * instr)
|
|
|
-{
|
|
|
- Assert(instr->TransfersSrcValue());
|
|
|
- if (!this->currentBlock->globOptData.liveFields->Test(sym->m_id))
|
|
|
- {
|
|
|
- // Not live
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
- StackSym * hoistedCopySym;
|
|
|
- Loop * loop = FindFieldHoistStackSym(this->currentBlock->loop, sym->m_id, &hoistedCopySym, instr);
|
|
|
- Assert(loop != nullptr || !this->IsHoistablePropertySym(sym->m_id));
|
|
|
-
|
|
|
- if (loop)
|
|
|
- {
|
|
|
- // The field was live before, so we have the hoisted stack sym live value, just copy prop it
|
|
|
- *ppOpnd = CopyPropReplaceOpnd(instr, *ppOpnd, hoistedCopySym);
|
|
|
-
|
|
|
#if DBG
|
|
|
- if (Js::Configuration::Global.flags.Trace.IsEnabled(Js::FieldHoistPhase, this->func->GetSourceContextId(), this->func->GetLocalFunctionId()))
|
|
|
- {
|
|
|
- Output::Print(_u("FieldHoist: Copy prop "));
|
|
|
- sym->Dump();
|
|
|
- Output::SkipToColumn(30);
|
|
|
- Output::Print(_u(" : "));
|
|
|
- instr->Dump();
|
|
|
- }
|
|
|
-#endif
|
|
|
- return true;
|
|
|
- }
|
|
|
- return false;
|
|
|
-}
|
|
|
-
|
|
|
-void
|
|
|
-GlobOpt::ReloadFieldHoistStackSym(IR::Instr * instr, PropertySym * propertySym)
|
|
|
-{
|
|
|
- Assert(instr->TransfersSrcValue());
|
|
|
- StackSym * fieldHoistSym = nullptr;
|
|
|
- Loop * loop = this->FindFieldHoistStackSym(this->currentBlock->loop, propertySym->m_id, &fieldHoistSym, instr);
|
|
|
-
|
|
|
- if (loop == nullptr)
|
|
|
- {
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- // When a field is killed, ideally the specialized versions of the corresponding hoisted stack syms should also be killed,
|
|
|
- // since the field needs to be reloaded the next time it's used (which may be earlier in the loop). However, killing the
|
|
|
- // specialized stack syms when the field is killed requires discovering and walking all fields that are killed and their
|
|
|
- // hoisted stack syms, which requires more computation (since many fields can be killed at once).
|
|
|
- //
|
|
|
- // Alternatively, we can kill the specialized stack syms for a field when the field is reloaded, which is what's happening
|
|
|
- // here. Since this happens per field and lazily, it requires less work. It works because killing the specialized stack
|
|
|
- // syms only matters when the field is reloaded.
|
|
|
- //
|
|
|
- // Furthermore, to handle the case where a field is not live on entry into the loop (field is killed in the loop and not
|
|
|
- // reloaded in the same loop afterwards), the specialized stack syms for that field must also be killed on entry into the
|
|
|
- // loop. Instead of checking all hoisted field stack syms on entry into a loop after the prepass merge, and killing them if
|
|
|
- // their corresponding field is not live, this is also done in a lazy fashion as above, only when a field is reloaded. If a
|
|
|
- // field is reloaded in a loop before it's killed, and not reloaded again after the kill, the field won't be live on entry,
|
|
|
- // and hence the specialized stack syms should also not be live on entry. This is true for all parent loops up to the
|
|
|
- // nearest parent loop out of which the field is hoisted.
|
|
|
-
|
|
|
- ToVarStackSym(fieldHoistSym, currentBlock);
|
|
|
- if(!this->IsLoopPrePass())
|
|
|
- {
|
|
|
- for(Loop *currentLoop = currentBlock->loop;
|
|
|
- currentLoop != loop->parent && !currentLoop->liveFieldsOnEntry->Test(propertySym->m_id);
|
|
|
- currentLoop = currentLoop->parent)
|
|
|
- {
|
|
|
- currentLoop->int32SymsOnEntry->Clear(fieldHoistSym->m_id);
|
|
|
- currentLoop->lossyInt32SymsOnEntry->Clear(fieldHoistSym->m_id);
|
|
|
- currentLoop->float64SymsOnEntry->Clear(fieldHoistSym->m_id);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // Win8 943662: Kill the live field for this hoisted field load
|
|
|
- this->KillLiveFields(fieldHoistSym, this->currentBlock->globOptData.liveFields);
|
|
|
-
|
|
|
- if (this->IsLoopPrePass())
|
|
|
- {
|
|
|
- // In the prepass we are conservative and always assume that the fields are going to be reloaded
|
|
|
- // because we don't loop until value is unchanged and we are unable to detect dependencies.
|
|
|
-
|
|
|
- // Clear the value of the field to kill the value of the field even if it still live now.
|
|
|
- this->currentBlock->globOptData.liveFields->Clear(propertySym->m_id);
|
|
|
-
|
|
|
- // If we have to reload, we don't know the value, kill the old value for the fieldHoistSym.
|
|
|
- this->currentBlock->globOptData.ClearSymValue(fieldHoistSym);
|
|
|
-
|
|
|
- // No IR transformations in the prepass.
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- // If we are reloading, the field should be dead. CreateFieldSrc will create a value for the field.
|
|
|
- Assert(!this->currentBlock->globOptData.liveFields->Test(propertySym->m_id));
|
|
|
-
|
|
|
- // Copy the dst to the field hoist sym.
|
|
|
- IR::Instr * copyInstr = IR::Instr::New(Js::OpCode::Ld_A, IR::RegOpnd::New(fieldHoistSym, TyVar, instr->m_func), instr->GetDst(), instr->m_func);
|
|
|
- instr->InsertAfter(copyInstr);
|
|
|
-
|
|
|
-#if DBG_DUMP
|
|
|
- if (Js::Configuration::Global.flags.Trace.IsEnabled(Js::FieldHoistPhase, this->func->GetSourceContextId(), this->func->GetLocalFunctionId()))
|
|
|
- {
|
|
|
- Output::Print(_u("FieldHoist: Reload field sym "));
|
|
|
- Output::SkipToColumn(30);
|
|
|
- Output::Print(_u(" : "));
|
|
|
- instr->Dump();
|
|
|
- }
|
|
|
-#endif
|
|
|
-}
|
|
|
-
|
|
|
-void
|
|
|
-GlobOpt::CopyStoreFieldHoistStackSym(IR::Instr * storeFldInstr, PropertySym * sym, Value * src1Val)
|
|
|
-{
|
|
|
- // In the real (not prepass) pass, do the actual IR rewrites.
|
|
|
- // In the prepass, only track the impact that the rewrites will have. (See Win8 521029)
|
|
|
-
|
|
|
- Assert(storeFldInstr->m_opcode == Js::OpCode::StSlot
|
|
|
- || storeFldInstr->m_opcode == Js::OpCode::StSlotChkUndecl
|
|
|
- || storeFldInstr->m_opcode == Js::OpCode::StFld
|
|
|
- || storeFldInstr->m_opcode == Js::OpCode::StRootFld
|
|
|
- || storeFldInstr->m_opcode == Js::OpCode::StFldStrict
|
|
|
- || storeFldInstr->m_opcode == Js::OpCode::StRootFldStrict);
|
|
|
- Assert(storeFldInstr->GetDst()->GetType() == TyVar);
|
|
|
-
|
|
|
- // We may use StSlot for all sort of things other then assigning TyVars
|
|
|
- Assert(storeFldInstr->GetSrc1()->GetType() == TyVar || storeFldInstr->m_opcode == Js::OpCode::StSlot || storeFldInstr->m_opcode == Js::OpCode::StSlotChkUndecl);
|
|
|
- Assert(storeFldInstr->GetSrc2() == nullptr);
|
|
|
-
|
|
|
- StackSym * copySym;
|
|
|
- Loop * loop = this->FindFieldHoistStackSym(this->currentBlock->loop, sym->m_id, ©Sym);
|
|
|
- if (loop == nullptr)
|
|
|
- {
|
|
|
- return;
|
|
|
- }
|
|
|
- IR::Opnd * srcOpnd = storeFldInstr->GetSrc1();
|
|
|
- Func * storeFldFunc = storeFldInstr->m_func;
|
|
|
- IR::Instr * newInstr;
|
|
|
- if (!this->IsLoopPrePass())
|
|
|
- {
|
|
|
- this->CaptureByteCodeSymUses(storeFldInstr);
|
|
|
-
|
|
|
- IR::RegOpnd * dstOpnd = IR::RegOpnd::New(copySym, TyVar, storeFldFunc);
|
|
|
- dstOpnd->SetIsJITOptimizedReg(true);
|
|
|
- storeFldInstr->UnlinkSrc1();
|
|
|
- newInstr = IR::Instr::New(Js::OpCode::Ld_A, dstOpnd, srcOpnd, storeFldFunc);
|
|
|
- storeFldInstr->SetSrc1(dstOpnd);
|
|
|
- storeFldInstr->InsertBefore(newInstr);
|
|
|
- }
|
|
|
- this->ToVarStackSym(copySym, this->currentBlock); // The field-hoisted stack sym is now unspecialized
|
|
|
-
|
|
|
- Value * dstVal = this->CopyValue(src1Val);
|
|
|
- TrackCopiedValueForKills(dstVal);
|
|
|
- this->SetSymStoreDirect(dstVal->GetValueInfo(), copySym);
|
|
|
- this->currentBlock->globOptData.SetValue(dstVal, copySym);
|
|
|
-
|
|
|
- // Copy the type specialized sym as well, in case we have a use for them
|
|
|
- bool neededCopySymDef = false;
|
|
|
- if(srcOpnd->IsRegOpnd())
|
|
|
- {
|
|
|
- StackSym *const srcSym = srcOpnd->AsRegOpnd()->m_sym;
|
|
|
- if (this->currentBlock->globOptData.liveInt32Syms->Test(srcSym->m_id))
|
|
|
- {
|
|
|
- this->currentBlock->globOptData.liveInt32Syms->Set(copySym->m_id);
|
|
|
- if(this->currentBlock->globOptData.liveLossyInt32Syms->Test(srcSym->m_id))
|
|
|
- {
|
|
|
- this->currentBlock->globOptData.liveLossyInt32Syms->Set(copySym->m_id);
|
|
|
- }
|
|
|
- if (!this->IsLoopPrePass())
|
|
|
- {
|
|
|
- StackSym * int32CopySym = copySym->GetInt32EquivSym(storeFldFunc);
|
|
|
- IR::RegOpnd * int32CopyOpnd = IR::RegOpnd::New(int32CopySym, TyInt32, storeFldFunc);
|
|
|
- IR::RegOpnd * int32SrcOpnd = IR::RegOpnd::New(srcSym->GetInt32EquivSym(nullptr),
|
|
|
- TyInt32, storeFldFunc);
|
|
|
- newInstr = IR::Instr::New(Js::OpCode::Ld_I4, int32CopyOpnd, int32SrcOpnd, storeFldFunc);
|
|
|
- int32SrcOpnd->SetIsJITOptimizedReg(true);
|
|
|
- storeFldInstr->InsertBefore(newInstr);
|
|
|
- }
|
|
|
- neededCopySymDef = true;
|
|
|
- }
|
|
|
- if (this->currentBlock->globOptData.liveFloat64Syms->Test(srcSym->m_id))
|
|
|
- {
|
|
|
- this->currentBlock->globOptData.liveFloat64Syms->Set(copySym->m_id);
|
|
|
- if (!this->IsLoopPrePass())
|
|
|
- {
|
|
|
- StackSym * float64CopySym = copySym->GetFloat64EquivSym(storeFldFunc);
|
|
|
- IR::RegOpnd * float64CopyOpnd = IR::RegOpnd::New(float64CopySym, TyFloat64, storeFldFunc);
|
|
|
- IR::RegOpnd * float64SrcOpnd = IR::RegOpnd::New(srcSym->GetFloat64EquivSym(nullptr),
|
|
|
- TyFloat64, storeFldFunc);
|
|
|
- newInstr = IR::Instr::New(Js::OpCode::Ld_A, float64CopyOpnd, float64SrcOpnd, storeFldFunc);
|
|
|
- float64SrcOpnd->SetIsJITOptimizedReg(true);
|
|
|
- storeFldInstr->InsertBefore(newInstr);
|
|
|
- }
|
|
|
- neededCopySymDef = true;
|
|
|
- }
|
|
|
- }
|
|
|
- else if(srcOpnd->IsAddrOpnd())
|
|
|
- {
|
|
|
- const auto srcAddrOpnd = srcOpnd->AsAddrOpnd();
|
|
|
- if(srcAddrOpnd->IsVar() && Js::TaggedInt::Is(srcAddrOpnd->m_address))
|
|
|
- {
|
|
|
- this->currentBlock->globOptData.liveInt32Syms->Set(copySym->m_id);
|
|
|
- if (!this->IsLoopPrePass())
|
|
|
- {
|
|
|
- StackSym * int32CopySym = copySym->GetInt32EquivSym(storeFldFunc);
|
|
|
- IR::RegOpnd * int32CopyOpnd = IR::RegOpnd::New(int32CopySym, TyInt32, storeFldFunc);
|
|
|
- IR::IntConstOpnd * int32SrcOpnd =
|
|
|
- IR::IntConstOpnd::New(Js::TaggedInt::ToInt32(srcAddrOpnd->m_address), TyInt32, storeFldFunc);
|
|
|
- newInstr = IR::Instr::New(Js::OpCode::Ld_I4, int32CopyOpnd, int32SrcOpnd, storeFldFunc);
|
|
|
- int32SrcOpnd->SetIsJITOptimizedReg(true);
|
|
|
- storeFldInstr->InsertBefore(newInstr);
|
|
|
- }
|
|
|
- neededCopySymDef = true;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- if(IsLoopPrePass() && neededCopySymDef)
|
|
|
- {
|
|
|
- // Record the def that would have been added
|
|
|
- rootLoopPrePass->symsDefInLoop->Set(copySym->m_id);
|
|
|
- }
|
|
|
-
|
|
|
- this->KillLiveFields(copySym, this->currentBlock->globOptData.liveFields);
|
|
|
-
|
|
|
-#if DBG_DUMP
|
|
|
- if (!this->IsLoopPrePass())
|
|
|
- {
|
|
|
- if (Js::Configuration::Global.flags.Trace.IsEnabled(Js::FieldHoistPhase, this->func->GetSourceContextId(), this->func->GetLocalFunctionId()))
|
|
|
- {
|
|
|
- Output::Print(_u("FieldHoist: Copy field store "));
|
|
|
- Output::SkipToColumn(30);
|
|
|
- Output::Print(_u(" : "));
|
|
|
- storeFldInstr->Dump();
|
|
|
- }
|
|
|
- }
|
|
|
-#endif
|
|
|
-}
|
|
|
-
|
|
|
-bool
|
|
|
-GlobOpt::NeedBailOnImplicitCallWithFieldOpts(Loop *loop, bool hasLiveFields) const
|
|
|
-{
|
|
|
- if (!((this->DoFieldHoisting(loop) && loop->hasHoistedFields) ||
|
|
|
- ((this->DoFieldRefOpts(loop) ||
|
|
|
- this->DoFieldCopyProp(loop)) &&
|
|
|
- hasLiveFields)))
|
|
|
- {
|
|
|
- return false;
|
|
|
- }
|
|
|
-
|
|
|
- return true;
|
|
|
-}
|
|
|
-
|
|
|
-IR::Instr *
|
|
|
-GlobOpt::EnsureDisableImplicitCallRegion(Loop * loop)
|
|
|
-{
|
|
|
- Assert(loop->bailOutInfo != nullptr);
|
|
|
- IR::Instr * endDisableImplicitCall = loop->endDisableImplicitCall;
|
|
|
- if (endDisableImplicitCall)
|
|
|
- {
|
|
|
- return endDisableImplicitCall;
|
|
|
- }
|
|
|
-
|
|
|
- IR::Instr * bailOutTarget = EnsureBailTarget(loop);
|
|
|
-
|
|
|
- Func * bailOutFunc = loop->GetFunc();
|
|
|
- Assert(loop->bailOutInfo->bailOutFunc == bailOutFunc);
|
|
|
-
|
|
|
- IR::MemRefOpnd * disableImplicitCallAddress = IR::MemRefOpnd::New(this->func->GetThreadContextInfo()->GetDisableImplicitFlagsAddr(), TyInt8, bailOutFunc);
|
|
|
- IR::IntConstOpnd * disableImplicitCallAndExceptionValue = IR::IntConstOpnd::New(DisableImplicitCallAndExceptionFlag, TyInt8, bailOutFunc, true);
|
|
|
- IR::IntConstOpnd * enableImplicitCallAndExceptionValue = IR::IntConstOpnd::New(DisableImplicitNoFlag, TyInt8, bailOutFunc, true);
|
|
|
-
|
|
|
- IR::Opnd * implicitCallFlags = Lowerer::GetImplicitCallFlagsOpnd(bailOutFunc);
|
|
|
- IR::IntConstOpnd * noImplicitCall = IR::IntConstOpnd::New(Js::ImplicitCall_None, TyInt8, bailOutFunc, true);
|
|
|
-
|
|
|
- // Consider: if we are already doing implicit call in the outer loop, we don't need to clear the implicit call bit again
|
|
|
- IR::Instr * clearImplicitCall = IR::Instr::New(Js::OpCode::Ld_A, implicitCallFlags, noImplicitCall, bailOutFunc);
|
|
|
- bailOutTarget->InsertBefore(clearImplicitCall);
|
|
|
-
|
|
|
- IR::Instr * disableImplicitCall = IR::Instr::New(Js::OpCode::Ld_A, disableImplicitCallAddress, disableImplicitCallAndExceptionValue, bailOutFunc);
|
|
|
- bailOutTarget->InsertBefore(disableImplicitCall);
|
|
|
-
|
|
|
- endDisableImplicitCall = IR::Instr::New(Js::OpCode::Ld_A, disableImplicitCallAddress, enableImplicitCallAndExceptionValue, bailOutFunc);
|
|
|
- bailOutTarget->InsertBefore(endDisableImplicitCall);
|
|
|
-
|
|
|
- IR::BailOutInstr * bailOutInstr = IR::BailOutInstr::New(Js::OpCode::BailOnNotEqual, IR::BailOutOnImplicitCalls, loop->bailOutInfo, loop->bailOutInfo->bailOutFunc);
|
|
|
- bailOutInstr->SetSrc1(implicitCallFlags);
|
|
|
- bailOutInstr->SetSrc2(noImplicitCall);
|
|
|
- bailOutTarget->InsertBefore(bailOutInstr);
|
|
|
-
|
|
|
- loop->endDisableImplicitCall = endDisableImplicitCall;
|
|
|
- return endDisableImplicitCall;
|
|
|
-}
|
|
|
-
|
|
|
-#if DBG
|
|
|
-bool
|
|
|
-GlobOpt::IsHoistedPropertySym(PropertySym * sym) const
|
|
|
-{
|
|
|
- return IsHoistedPropertySym(sym->m_id, this->currentBlock->loop);
|
|
|
-}
|
|
|
-
|
|
|
-bool
|
|
|
-GlobOpt::IsHoistedPropertySym(SymID symId, Loop * loop) const
|
|
|
-{
|
|
|
- StackSym * copySym;
|
|
|
- return this->FindFieldHoistStackSym(loop, symId, ©Sym) != nullptr;
|
|
|
-}
|
|
|
-
|
|
|
bool
|
|
|
GlobOpt::IsPropertySymId(SymID symId) const
|
|
|
{
|