| //------------------------------------------------------------------------------------------------------- |
| // Copyright (C) Microsoft. All rights reserved. |
| // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. |
| //------------------------------------------------------------------------------------------------------- |
| #include "Backend.h" |
| |
| /* =================================================================================== |
| * TempTracker runs the mark temp algorithm. The template parameter provides information |
| * what are valid temp use, temp transfer, or temp producing operations and what bit to |
| * set once a symbol def can be marked temp. |
| * |
| * NumberTemp mark temp JavascriptNumber creation for math operations, run during deadstore |
| * |
| * ObjectTemp mark temp object allocations, run during backward pass so that it can provide |
| * information to the globopt to install pre op bailout on implicit call while during stack |
| * allocation objects. |
| * |
| * ObjectTempVerify runs a similar mark temp during deadstore in debug mode to assert |
| * that globopt have install the pre op necessary and a marked temp def is still valid |
| * as a mark temp |
| * |
| * The basic of the mark temp algorithm is very simple: we keep track if we have seen |
| * any use of a symbol that is not a valid mark temp (the nonTempSyms bitvector) |
| * and on definition of the symbol, if the all the use allow temp object (not in nonTempSyms |
| * bitvector) then it is mark them able. |
| * |
| * However, the complication comes when the stack object is transferred to another symbol |
| * and we are in a loop. We need to make sure that the stack object isn't still referred |
| * by another symbol when we allocate the number/object in the next iteration |
| * |
| * For example: |
| * Loop top: |
| * s1 = NewScObject |
| * = s6 |
| * s6 = s1 |
| * Goto Loop top |
| * |
| * We cannot mark them this case because when s1 is created, the object might still be |
| * referred to by s6 from previous iteration, and thus if we mark them we would have |
| * change the content of s6 as well. |
| * |
| * To detect this dependency, we conservatively collect "all" transfers in the pre pass |
| * of the loop. We have to be conservative to detect reverse dependencies without |
| * iterating more than 2 times for the loop. |
| * =================================================================================== */ |
| |
| JitArenaAllocator * |
| TempTrackerBase::GetAllocator() const |
| { |
| return nonTempSyms.GetAllocator(); |
| } |
| |
| TempTrackerBase::TempTrackerBase(JitArenaAllocator * alloc, bool inLoop) |
| : nonTempSyms(alloc), tempTransferredSyms(alloc) |
| { |
| if (inLoop) |
| { |
| tempTransferDependencies = HashTable<BVSparse<JitArenaAllocator> *>::New(alloc, 16); |
| } |
| else |
| { |
| tempTransferDependencies = nullptr; |
| } |
| } |
| |
| TempTrackerBase::~TempTrackerBase() |
| { |
| if (this->tempTransferDependencies != nullptr) |
| { |
| JitArenaAllocator * alloc = this->GetAllocator(); |
| FOREACH_HASHTABLE_ENTRY(BVSparse<JitArenaAllocator> *, bucket, this->tempTransferDependencies) |
| { |
| JitAdelete(alloc, bucket.element); |
| } |
| NEXT_HASHTABLE_ENTRY; |
| this->tempTransferDependencies->Delete(); |
| } |
| } |
| void |
| TempTrackerBase::MergeData(TempTrackerBase * fromData, bool deleteData) |
| { |
| nonTempSyms.Or(&fromData->nonTempSyms); |
| tempTransferredSyms.Or(&fromData->tempTransferredSyms); |
| MergeDependencies(tempTransferDependencies, fromData->tempTransferDependencies, deleteData); |
| } |
| |
| void |
| TempTrackerBase::AddTransferDependencies(int sourceId, SymID dstSymID, HashTable<BVSparse<JitArenaAllocator> *> * dependencies) |
| { |
| // Add to the transfer dependencies set |
| BVSparse<JitArenaAllocator> ** pBVSparse = dependencies->FindOrInsertNew(sourceId); |
| if (*pBVSparse == nullptr) |
| { |
| *pBVSparse = JitAnew(this->GetAllocator(), BVSparse<JitArenaAllocator>, this->GetAllocator()); |
| } |
| AddTransferDependencies(*pBVSparse, dstSymID); |
| } |
| |
| void |
| TempTrackerBase::AddTransferDependencies(BVSparse<JitArenaAllocator> * bv, SymID dstSymID) |
| { |
| bv->Set(dstSymID); |
| |
| // Add the indirect transfers (always from tempTransferDependencies) |
| BVSparse<JitArenaAllocator> *dstBVSparse = this->tempTransferDependencies->GetAndClear(dstSymID); |
| if (dstBVSparse != nullptr) |
| { |
| bv->Or(dstBVSparse); |
| JitAdelete(this->GetAllocator(), dstBVSparse); |
| } |
| } |
| |
| template <typename T> |
| TempTracker<T>::TempTracker(JitArenaAllocator * alloc, bool inLoop): |
| T(alloc, inLoop) |
| { |
| } |
| |
| template <typename T> |
| void |
| TempTracker<T>::MergeData(TempTracker<T> * fromData, bool deleteData) |
| { |
| TempTrackerBase::MergeData(fromData, deleteData); |
| T::MergeData(fromData, deleteData); |
| |
| if (deleteData) |
| { |
| JitAdelete(this->GetAllocator(), fromData); |
| } |
| } |
| |
| void |
| TempTrackerBase::OrHashTableOfBitVector(HashTable<BVSparse<JitArenaAllocator> *> * toData, HashTable<BVSparse<JitArenaAllocator> *> *& fromData, bool deleteData) |
| { |
| Assert(toData != nullptr); |
| Assert(fromData != nullptr); |
| toData->Or(fromData, |
| [=](BVSparse<JitArenaAllocator> * bv1, BVSparse<JitArenaAllocator> * bv2) -> BVSparse<JitArenaAllocator> * |
| { |
| if (bv1 == nullptr) |
| { |
| if (deleteData) |
| { |
| return bv2; |
| } |
| return bv2->CopyNew(this->GetAllocator()); |
| } |
| bv1->Or(bv2); |
| if (deleteData) |
| { |
| JitAdelete(this->GetAllocator(), bv2); |
| } |
| return bv1; |
| }); |
| |
| if (deleteData) |
| { |
| fromData->Delete(); |
| fromData = nullptr; |
| } |
| } |
| |
| void |
| TempTrackerBase::MergeDependencies(HashTable<BVSparse<JitArenaAllocator> *> * toData, HashTable<BVSparse<JitArenaAllocator> *> *& fromData, bool deleteData) |
| { |
| if (fromData != nullptr) |
| { |
| if (toData != nullptr) |
| { |
| OrHashTableOfBitVector(toData, fromData, deleteData); |
| } |
| else if (deleteData) |
| { |
| FOREACH_HASHTABLE_ENTRY(BVSparse<JitArenaAllocator> *, bucket, fromData) |
| { |
| JitAdelete(this->GetAllocator(), bucket.element); |
| } |
| NEXT_HASHTABLE_ENTRY; |
| fromData->Delete(); |
| fromData = nullptr; |
| } |
| } |
| } |
| |
| #if DBG_DUMP |
| void |
| TempTrackerBase::Dump(char16 const * traceName) |
| { |
| Output::Print(_u("%s: Non temp syms:"), traceName); |
| this->nonTempSyms.Dump(); |
| Output::Print(_u("%s: Temp transferred syms:"), traceName); |
| this->tempTransferredSyms.Dump(); |
| if (this->tempTransferDependencies != nullptr) |
| { |
| Output::Print(_u("%s: Temp transfer dependencies:\n"), traceName); |
| this->tempTransferDependencies->Dump(); |
| } |
| } |
| #endif |
| |
| template <typename T> |
| void |
| TempTracker<T>::ProcessUse(StackSym * sym, BackwardPass * backwardPass) |
| { |
| // Don't care about type specialized syms |
| if (!sym->IsVar()) |
| { |
| return; |
| } |
| IR::Instr * instr = backwardPass->currentInstr; |
| SymID usedSymID = sym->m_id; |
| bool isTempPropertyTransferStore = T::IsTempPropertyTransferStore(instr, backwardPass); |
| bool isTempUse = isTempPropertyTransferStore || T::IsTempUse(instr, sym, backwardPass); |
| if (!isTempUse) |
| { |
| this->nonTempSyms.Set(usedSymID); |
| } |
| #if DBG |
| if (T::DoTrace(backwardPass)) |
| { |
| Output::Print(_u("%s: %8s%4sTemp Use (s%-3d): "), T::GetTraceName(), |
| backwardPass->IsPrePass() ? _u("Prepass ") : _u(""), isTempUse ? _u("") : _u("Non "), usedSymID); |
| instr->DumpSimple(); |
| Output::Flush(); |
| } |
| #endif |
| if (T::IsTempTransfer(instr)) |
| { |
| this->tempTransferredSyms.Set(usedSymID); |
| |
| // Track dependencies if we are in loop only |
| if (this->tempTransferDependencies != nullptr) |
| { |
| IR::Opnd * dstOpnd = instr->GetDst(); |
| if (dstOpnd->IsRegOpnd()) |
| { |
| SymID dstSymID = dstOpnd->AsRegOpnd()->m_sym->m_id; |
| |
| if (dstSymID != usedSymID) |
| { |
| // Record that the usedSymID may propagate to dstSymID and all the symbols |
| // that it may propagate to as well |
| this->AddTransferDependencies(usedSymID, dstSymID, this->tempTransferDependencies); |
| #if DBG_DUMP |
| if (T::DoTrace(backwardPass)) |
| { |
| Output::Print(_u("%s: %8s s%d -> s%d: "), T::GetTraceName(), |
| backwardPass->IsPrePass() ? _u("Prepass ") : _u(""), dstSymID, usedSymID); |
| (*this->tempTransferDependencies->Get(usedSymID))->Dump(); |
| } |
| #endif |
| } |
| } |
| } |
| } |
| |
| if (isTempPropertyTransferStore) |
| { |
| this->tempTransferredSyms.Set(usedSymID); |
| PropertySym * propertySym = instr->GetDst()->AsSymOpnd()->m_sym->AsPropertySym(); |
| this->PropagateTempPropertyTransferStoreDependencies(usedSymID, propertySym, backwardPass); |
| |
| #if DBG_DUMP |
| if (T::DoTrace(backwardPass) && this->tempTransferDependencies) |
| { |
| Output::Print(_u("%s: %8s (PropId:%d)+[] -> s%d: "), T::GetTraceName(), |
| backwardPass->IsPrePass() ? _u("Prepass ") : _u(""), propertySym->m_propertyId, usedSymID); |
| BVSparse<JitArenaAllocator> ** transferDependencies = this->tempTransferDependencies->Get(usedSymID); |
| if (transferDependencies) |
| { |
| (*transferDependencies)->Dump(); |
| } |
| else |
| { |
| Output::Print(_u("[]\n")); |
| } |
| } |
| #endif |
| } |
| }; |
| |
| template <typename T> |
| void |
| TempTracker<T>::MarkTemp(StackSym * sym, BackwardPass * backwardPass) |
| { |
| // Don't care about type specialized syms |
| Assert(sym->IsVar()); |
| |
| IR::Instr * instr = backwardPass->currentInstr; |
| BOOLEAN nonTemp = this->nonTempSyms.TestAndClear(sym->m_id); |
| BOOLEAN isTempTransferred; |
| BVSparse<JitArenaAllocator> * bvTempTransferDependencies = nullptr; |
| |
| bool const isTransferOperation = |
| T::IsTempTransfer(instr) |
| || T::IsTempPropertyTransferLoad(instr, backwardPass) |
| || T::IsTempIndirTransferLoad(instr, backwardPass); |
| |
| if (this->tempTransferDependencies != nullptr) |
| { |
| // Since we don't iterate "while (!changed)" in loops, we don't have complete accurate dataflow |
| // for loop carried dependencies. So don't clear the dependency transfer info. WOOB:1121525 |
| |
| // Check if this dst is transferred (assigned) to another symbol |
| if (isTransferOperation) |
| { |
| isTempTransferred = this->tempTransferredSyms.Test(sym->m_id); |
| } |
| else |
| { |
| isTempTransferred = this->tempTransferredSyms.TestAndClear(sym->m_id); |
| } |
| |
| // We only need to look at the dependencies if we are in a loop because of the back edge |
| // Also we don't need to if we are in pre pass |
| if (isTempTransferred) |
| { |
| if (!backwardPass->IsPrePass()) |
| { |
| if (isTransferOperation) |
| { |
| // Transfer operation, load but not clear the information |
| BVSparse<JitArenaAllocator> **pBv = this->tempTransferDependencies->Get(sym->m_id); |
| if (pBv) |
| { |
| bvTempTransferDependencies = *pBv; |
| } |
| } |
| else |
| { |
| // Non transfer operation, load and clear the information and the dst value is replaced |
| bvTempTransferDependencies = this->tempTransferDependencies->GetAndClear(sym->m_id); |
| } |
| } |
| else if (!isTransferOperation) |
| { |
| // In pre pass, and not a transfer operation (just an assign). We can clear the dependency info |
| // and not look at it. |
| this->tempTransferDependencies->Clear(sym->m_id); |
| } |
| } |
| } |
| else |
| { |
| isTempTransferred = this->tempTransferredSyms.TestAndClear(sym->m_id); |
| } |
| |
| // Reset the dst is temp bit (we set it optimistically on the loop pre pass) |
| bool dstIsTemp = false; |
| bool dstIsTempTransferred = false; |
| |
| if (nonTemp) |
| { |
| #if DBG_DUMP |
| if (T::DoTrace(backwardPass) && !backwardPass->IsPrePass() && T::CanMarkTemp(instr, backwardPass)) |
| { |
| Output::Print(_u("%s: Not temp (s%-03d):"), T::GetTraceName(), sym->m_id); |
| instr->DumpSimple(); |
| } |
| #endif |
| } |
| else if (backwardPass->IsPrePass()) |
| { |
| // On pre pass, we don't have complete information about whether it is tempable or |
| // not from the back edge. If we already discovered that it is not a temp (above), then |
| // we don't mark it, other wise, assume that it is okay to be tempable and have the |
| // second pass set the bit correctly. The only works on dependency chain that is in order |
| // e.g. |
| // s1 = Add |
| // s2 = s1 |
| // s3 = s2 |
| // The dependencies tracking to catch the case whether the dependency chain is out of order |
| // e.g |
| // s1 = Add |
| // s3 = s2 |
| // s2 = s3 |
| |
| Assert(isTransferOperation == T::IsTempTransfer(instr) |
| || T::IsTempPropertyTransferLoad(instr, backwardPass) |
| || T::IsTempIndirTransferLoad(instr, backwardPass)); |
| if (isTransferOperation) |
| { |
| dstIsTemp = true; |
| } |
| } |
| else if (T::CanMarkTemp(instr, backwardPass)) |
| { |
| dstIsTemp = true; |
| |
| if (isTempTransferred) |
| { |
| // Track whether the dst is transferred or not, and allocate separate stack slot for them |
| // so that another dst will not overrides the value |
| dstIsTempTransferred = true; |
| |
| // The temp is aliased, need to trace if there is another use of the set of aliased |
| // sym that is still live so that we won't mark them this symbol and destroy the value |
| |
| if (bvTempTransferDependencies != nullptr) |
| { |
| // Inside a loop we need to track if any of the reg that we transferred to is still live |
| // s1 = Add |
| // = s2 |
| // s2 = s1 |
| // Since s2 is still live on the next iteration when we reassign s1, making s1 a temp |
| // will cause the value of s2 to change before it's use. |
| |
| // The upwardExposedUses are the live regs, check if it intersect with the set |
| // of dependency or not. |
| |
| #if DBG_DUMP |
| if (T::DoTrace(backwardPass) && Js::Configuration::Global.flags.Verbose) |
| { |
| Output::Print(_u("%s: Loop mark temp check instr:\n"), T::GetTraceName()); |
| instr->DumpSimple(); |
| Output::Print(_u("Transfer dependencies: ")); |
| bvTempTransferDependencies->Dump(); |
| Output::Print(_u("Upward exposed Uses : ")); |
| backwardPass->currentBlock->upwardExposedUses->Dump(); |
| Output::Print(_u("\n")); |
| } |
| #endif |
| |
| BVSparse<JitArenaAllocator> * upwardExposedUses = backwardPass->currentBlock->upwardExposedUses; |
| bool hasExposedDependencies = bvTempTransferDependencies->Test(upwardExposedUses) |
| || T::HasExposedFieldDependencies(bvTempTransferDependencies, backwardPass); |
| if (hasExposedDependencies) |
| { |
| #if DBG_DUMP |
| if (T::DoTrace(backwardPass)) |
| { |
| Output::Print(_u("%s: Not temp (s%-03d): "), T::GetTraceName(), sym->m_id); |
| instr->DumpSimple(); |
| Output::Print(_u(" Transferred exposed uses: ")); |
| JitArenaAllocator tempAllocator(_u("temp"), this->GetAllocator()->GetPageAllocator(), Js::Throw::OutOfMemory); |
| bvTempTransferDependencies->AndNew(upwardExposedUses, &tempAllocator)->Dump(); |
| } |
| #endif |
| |
| dstIsTemp = false; |
| dstIsTempTransferred = false; |
| #if DBG |
| if (IsObjectTempVerify<T>()) |
| { |
| dstIsTemp = ObjectTempVerify::DependencyCheck(instr, bvTempTransferDependencies, backwardPass); |
| } |
| #endif |
| // Only ObjectTmepVerify would do the do anything here. All other returns false |
| } |
| } |
| } |
| } |
| |
| T::SetDstIsTemp(dstIsTemp, dstIsTempTransferred, instr, backwardPass); |
| } |
| |
| NumberTemp::NumberTemp(JitArenaAllocator * alloc, bool inLoop) |
| : TempTrackerBase(alloc, inLoop), elemLoadDependencies(alloc), nonTempElemLoad(false), |
| upwardExposedMarkTempObjectLiveFields(alloc), upwardExposedMarkTempObjectSymsProperties(nullptr) |
| { |
| propertyIdsTempTransferDependencies = inLoop ? HashTable<BVSparse<JitArenaAllocator> *>::New(alloc, 16) : nullptr; |
| } |
| |
| void |
| NumberTemp::MergeData(NumberTemp * fromData, bool deleteData) |
| { |
| nonTempElemLoad = nonTempElemLoad || fromData->nonTempElemLoad; |
| if (!nonTempElemLoad) // Don't bother merging other data if we already have a nonTempElemLoad |
| { |
| if (IsInLoop()) |
| { |
| // in loop |
| elemLoadDependencies.Or(&fromData->elemLoadDependencies); |
| } |
| MergeDependencies(propertyIdsTempTransferDependencies, fromData->propertyIdsTempTransferDependencies, deleteData); |
| |
| if (fromData->upwardExposedMarkTempObjectSymsProperties) |
| { |
| if (upwardExposedMarkTempObjectSymsProperties) |
| { |
| OrHashTableOfBitVector(upwardExposedMarkTempObjectSymsProperties, fromData->upwardExposedMarkTempObjectSymsProperties, deleteData); |
| } |
| else if (deleteData) |
| { |
| upwardExposedMarkTempObjectSymsProperties = fromData->upwardExposedMarkTempObjectSymsProperties; |
| fromData->upwardExposedMarkTempObjectSymsProperties = nullptr; |
| } |
| else |
| { |
| upwardExposedMarkTempObjectSymsProperties = HashTable<BVSparse<JitArenaAllocator> *>::New(this->GetAllocator(), 16); |
| OrHashTableOfBitVector(upwardExposedMarkTempObjectSymsProperties, fromData->upwardExposedMarkTempObjectSymsProperties, deleteData); |
| } |
| } |
| |
| upwardExposedMarkTempObjectLiveFields.Or(&fromData->upwardExposedMarkTempObjectLiveFields); |
| } |
| } |
| |
| bool |
| NumberTemp::IsTempUse(IR::Instr * instr, Sym * sym, BackwardPass * backwardPass) |
| { |
| Js::OpCode opcode = instr->m_opcode; |
| if (OpCodeAttr::NonTempNumberSources(opcode) |
| || (OpCodeAttr::TempNumberTransfer(opcode) && !instr->dstIsTempNumber)) |
| { |
| // For TypedArray stores, we don't store the Var object, so MarkTemp is valid |
| if (opcode != Js::OpCode::StElemI_A |
| || !instr->GetDst()->AsIndirOpnd()->GetBaseOpnd()->GetValueType().IsLikelyOptimizedTypedArray()) |
| { |
| // Mark the symbol as non-tempable if the instruction doesn't allow temp sources, |
| // or it is transferred to a non-temp dst |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool |
| NumberTemp::IsTempTransfer(IR::Instr * instr) |
| { |
| return OpCodeAttr::TempNumberTransfer(instr->m_opcode); |
| } |
| |
| bool |
| NumberTemp::IsTempProducing(IR::Instr * instr) |
| { |
| Js::OpCode opcode = instr->m_opcode; |
| if (OpCodeAttr::TempNumberProducing(opcode)) |
| { |
| return true; |
| } |
| |
| // Loads from float typedArrays usually require a conversion to Var, which we can MarkTemp. |
| if (opcode == Js::OpCode::LdElemI_A) |
| { |
| const ValueType baseValueType(instr->GetSrc1()->AsIndirOpnd()->GetBaseOpnd()->GetValueType()); |
| if (baseValueType.IsLikelyObject() |
| && (baseValueType.GetObjectType() == ObjectType::Float32Array |
| || baseValueType.GetObjectType() == ObjectType::Float64Array)) |
| { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| bool |
| NumberTemp::CanMarkTemp(IR::Instr * instr, BackwardPass * backwardPass) |
| { |
| if (IsTempTransfer(instr) || IsTempProducing(instr)) |
| { |
| return true; |
| } |
| |
| // REVIEW: this is added a long time ago, and I am not sure what this is for any more. |
| if (OpCodeAttr::InlineCallInstr(instr->m_opcode)) |
| { |
| return true; |
| } |
| |
| if (NumberTemp::IsTempIndirTransferLoad(instr, backwardPass) |
| || NumberTemp::IsTempPropertyTransferLoad(instr, backwardPass)) |
| { |
| return true; |
| } |
| |
| // the opcode is not temp producing or a transfer, this is not a tmp |
| // Also mark calls which may get inlined. |
| return false; |
| } |
| |
| void |
| NumberTemp::ProcessInstr(IR::Instr * instr, BackwardPass * backwardPass) |
| { |
| #if DBG |
| if (instr->m_opcode == Js::OpCode::BailOnNoProfile) |
| { |
| // If we see BailOnNoProfile, we shouldn't have any successor to have any non temp syms |
| Assert(!this->nonTempElemLoad); |
| Assert(this->nonTempSyms.IsEmpty()); |
| Assert(this->tempTransferredSyms.IsEmpty()); |
| Assert(this->elemLoadDependencies.IsEmpty()); |
| Assert(this->upwardExposedMarkTempObjectLiveFields.IsEmpty()); |
| } |
| #endif |
| |
| // We don't get to process all dst in MarkTemp. Do it here for the upwardExposedMarkTempObjectLiveFields |
| if (!this->DoMarkTempNumbersOnTempObjects(backwardPass)) |
| { |
| return; |
| } |
| |
| IR::Opnd * dst = instr->GetDst(); |
| if (dst == nullptr || !dst->IsRegOpnd()) |
| { |
| return; |
| } |
| StackSym * dstSym = dst->AsRegOpnd()->m_sym; |
| if (!dstSym->IsVar()) |
| { |
| dstSym = dstSym->GetVarEquivSym(nullptr); |
| if (dstSym == nullptr) |
| { |
| return; |
| } |
| } |
| |
| SymID dstSymId = dstSym->m_id; |
| if (this->upwardExposedMarkTempObjectSymsProperties) |
| { |
| // We are assigning to dstSym, it no longer has upward exposed use, get the information and clear it from the hash table |
| BVSparse<JitArenaAllocator> * dstBv = this->upwardExposedMarkTempObjectSymsProperties->GetAndClear(dstSymId); |
| if (dstBv) |
| { |
| // Clear the upward exposed live fields of all the property sym id associated to dstSym |
| this->upwardExposedMarkTempObjectLiveFields.Minus(dstBv); |
| if (ObjectTemp::IsTempTransfer(instr) && instr->GetSrc1()->IsRegOpnd()) |
| { |
| // If it is transfer, copy the dst info to the src |
| SymID srcStackSymId = instr->GetSrc1()->AsRegOpnd()->m_sym->AsStackSym()->m_id; |
| |
| SymTable * symTable = backwardPass->func->m_symTable; |
| FOREACH_BITSET_IN_SPARSEBV(index, dstBv) |
| { |
| PropertySym * propertySym = symTable->FindPropertySym(srcStackSymId, index); |
| if (propertySym) |
| { |
| this->upwardExposedMarkTempObjectLiveFields.Set(propertySym->m_id); |
| } |
| } |
| NEXT_BITSET_IN_SPARSEBV; |
| |
| BVSparse<JitArenaAllocator> ** srcBv = this->upwardExposedMarkTempObjectSymsProperties->FindOrInsert(dstBv, srcStackSymId); |
| if (srcBv) |
| { |
| (*srcBv)->Or(dstBv); |
| JitAdelete(this->GetAllocator(), dstBv); |
| } |
| } |
| else |
| { |
| JitAdelete(this->GetAllocator(), dstBv); |
| } |
| } |
| } |
| } |
| |
| void |
| NumberTemp::SetDstIsTemp(bool dstIsTemp, bool dstIsTempTransferred, IR::Instr * instr, BackwardPass * backwardPass) |
| { |
| Assert(dstIsTemp || !dstIsTempTransferred); |
| Assert(!instr->dstIsTempNumberTransferred); |
| instr->dstIsTempNumber = dstIsTemp; |
| instr->dstIsTempNumberTransferred = dstIsTempTransferred; |
| #if DBG_DUMP |
| if (!backwardPass->IsPrePass() && IsTempProducing(instr)) |
| { |
| backwardPass->numMarkTempNumber += dstIsTemp; |
| backwardPass->numMarkTempNumberTransferred += dstIsTempTransferred; |
| } |
| #endif |
| } |
| |
| bool |
| NumberTemp::IsTempPropertyTransferLoad(IR::Instr * instr, BackwardPass * backwardPass) |
| { |
| if (DoMarkTempNumbersOnTempObjects(backwardPass)) |
| { |
| switch (instr->m_opcode) |
| { |
| case Js::OpCode::LdFld: |
| case Js::OpCode::LdFldForTypeOf: |
| case Js::OpCode::LdMethodFld: |
| case Js::OpCode::LdFldForCallApplyTarget: |
| case Js::OpCode::LdMethodFromFlags: |
| { |
| // Only care about load from possible stack allocated object. |
| return instr->GetSrc1()->CanStoreTemp(); |
| } |
| }; |
| |
| // All other opcode shouldn't have sym opnd that can store temp, See ObjectTemp::IsTempUseOpCodeSym. |
| Assert(instr->GetSrc1() == nullptr |
| || instr->GetDst() == nullptr // this isn't a value loading instruction |
| || instr->GetSrc1()->IsIndirOpnd() // this is detected in IsTempIndirTransferLoad |
| || !instr->GetSrc1()->CanStoreTemp()); |
| } |
| return false; |
| } |
| |
| bool |
| NumberTemp::IsTempPropertyTransferStore(IR::Instr * instr, BackwardPass * backwardPass) |
| { |
| if (DoMarkTempNumbersOnTempObjects(backwardPass)) |
| { |
| switch (instr->m_opcode) |
| { |
| case Js::OpCode::InitFld: |
| case Js::OpCode::StFld: |
| case Js::OpCode::StFldStrict: |
| { |
| IR::Opnd * dst = instr->GetDst(); |
| Assert(dst->IsSymOpnd()); |
| if (!dst->CanStoreTemp()) |
| { |
| return false; |
| } |
| // We don't mark temp store of numeric properties (e.g. object literal { 86: foo }); |
| // This should only happen for InitFld, as StFld should have changed to StElem |
| PropertySym *propertySym = dst->AsSymOpnd()->m_sym->AsPropertySym(); |
| SymID propertySymId = this->GetRepresentativePropertySymId(propertySym, backwardPass); |
| return !this->nonTempSyms.Test(propertySymId) && |
| !instr->m_func->GetThreadContextInfo()->IsNumericProperty(propertySym->m_propertyId); |
| } |
| }; |
| |
| // All other opcode shouldn't have sym opnd that can store temp, see ObjectTemp::IsTempUseOpCodeSym. |
| // We also never mark the dst indir as can store temp for StElemI_A because we don't know what property |
| // it is storing in (or it could be an array index). |
| Assert(instr->GetDst() == nullptr || !instr->GetDst()->CanStoreTemp()); |
| } |
| return false; |
| } |
| |
| bool |
| NumberTemp::IsTempIndirTransferLoad(IR::Instr * instr, BackwardPass * backwardPass) |
| { |
| if (DoMarkTempNumbersOnTempObjects(backwardPass)) |
| { |
| if (instr->m_opcode == Js::OpCode::LdElemI_A) |
| { |
| // If the index is an int, then we don't care about the non-temp use |
| IR::Opnd * src1Opnd = instr->GetSrc1(); |
| IR::RegOpnd * indexOpnd = src1Opnd->AsIndirOpnd()->GetIndexOpnd(); |
| if (indexOpnd && (indexOpnd->m_sym->m_isNotInt || !indexOpnd->GetValueType().IsInt())) |
| { |
| return src1Opnd->CanStoreTemp(); |
| } |
| } |
| else |
| { |
| // All other opcode shouldn't have sym opnd that can store temp, See ObjectTemp::IsTempUseOpCodeSym. |
| Assert(instr->GetSrc1() == nullptr || instr->GetSrc1()->IsSymOpnd() |
| || !instr->GetSrc1()->CanStoreTemp()); |
| } |
| } |
| return false; |
| } |
| void |
| NumberTemp::PropagateTempPropertyTransferStoreDependencies(SymID usedSymID, PropertySym * propertySym, BackwardPass * backwardPass) |
| { |
| Assert(!this->nonTempElemLoad); |
| upwardExposedMarkTempObjectLiveFields.Clear(propertySym->m_id); |
| |
| if (!this->IsInLoop()) |
| { |
| // Don't need to track dependencies outside of loop, as we already marked the |
| // use as temp transfer already and we won't have a case where the "dst" is reused again (outside of loop) |
| return; |
| } |
| |
| Assert(this->tempTransferDependencies != nullptr); |
| SymID dstSymID = this->GetRepresentativePropertySymId(propertySym, backwardPass); |
| AddTransferDependencies(usedSymID, dstSymID, this->tempTransferDependencies); |
| |
| Js::PropertyId storedPropertyId = propertySym->m_propertyId; |
| // The symbol this properties are transferred to |
| BVSparse<JitArenaAllocator> ** pPropertyTransferDependencies = this->propertyIdsTempTransferDependencies->Get(storedPropertyId); |
| BVSparse<JitArenaAllocator> * transferDependencies = nullptr; |
| if (pPropertyTransferDependencies == nullptr) |
| { |
| if (elemLoadDependencies.IsEmpty()) |
| { |
| // No dependencies to transfer |
| return; |
| } |
| |
| transferDependencies = &elemLoadDependencies; |
| } |
| else |
| { |
| transferDependencies = *pPropertyTransferDependencies; |
| } |
| |
| BVSparse<JitArenaAllocator> ** pBVSparse = this->tempTransferDependencies->FindOrInsertNew(usedSymID); |
| if (*pBVSparse == nullptr) |
| { |
| *pBVSparse = transferDependencies->CopyNew(this->GetAllocator()); |
| } |
| else |
| { |
| (*pBVSparse)->Or(transferDependencies); |
| } |
| |
| if (transferDependencies != &elemLoadDependencies) |
| { |
| // Always include the element load dependencies as well |
| (*pBVSparse)->Or(&elemLoadDependencies); |
| } |
| |
| // Track the propertySym as well for the case where the dependence is not carried by the use |
| // Loop1 |
| // o.x = e |
| // Loop2 |
| // f = o.x |
| // = f |
| // e = e + blah |
| // Here, although we can detect that e and f has dependent relationship, f's life time doesn't cross with e's. |
| // But o.x will keep the value of e alive, so e can't be mark temp because o.x is still in use (not f) |
| // We will add the property sym int he dependency set and check with the upward exposed mark temp object live fields |
| // that we keep track of in NumberTemp |
| (*pBVSparse)->Set(propertySym->m_id); |
| } |
| |
| SymID |
| NumberTemp::GetRepresentativePropertySymId(PropertySym * propertySym, BackwardPass * backwardPass) |
| { |
| // Since we don't track alias with objects, all property accesses are all grouped together. |
| // Use a single property sym id to represent a propertyId to track dependencies. |
| SymID symId; |
| Js::PropertyId propertyId = propertySym->m_propertyId; |
| if (!backwardPass->numberTempRepresentativePropertySym->TryGetValue(propertyId, &symId)) |
| { |
| symId = propertySym->m_id; |
| backwardPass->numberTempRepresentativePropertySym->Add(propertyId, symId); |
| } |
| return symId; |
| } |
| |
| void |
| NumberTemp::ProcessIndirUse(IR::IndirOpnd * indirOpnd, IR::Instr * instr, BackwardPass * backwardPass) |
| { |
| Assert(backwardPass->DoMarkTempNumbersOnTempObjects()); |
| if (!NumberTemp::IsTempIndirTransferLoad(instr, backwardPass)) |
| { |
| return; |
| } |
| |
| bool isTempUse = instr->dstIsTempNumber; |
| if (!isTempUse) |
| { |
| nonTempElemLoad = true; |
| } |
| else if (this->IsInLoop()) |
| { |
| // We didn't already detect non temp use of this property id. so we should track the dependencies in loops |
| IR::Opnd * dstOpnd = instr->GetDst(); |
| Assert(dstOpnd->IsRegOpnd()); |
| SymID dstSymID = dstOpnd->AsRegOpnd()->m_sym->m_id; |
| |
| // Use the no property id as a place holder for elem dependencies |
| AddTransferDependencies(&elemLoadDependencies, dstSymID); |
| #if DBG_DUMP |
| if (NumberTemp::DoTrace(backwardPass)) |
| { |
| Output::Print(_u("%s: %8s s%d -> []: "), NumberTemp::GetTraceName(), |
| backwardPass->IsPrePass() ? _u("Prepass ") : _u(""), dstSymID); |
| elemLoadDependencies.Dump(); |
| } |
| #endif |
| } |
| |
| #if DBG_DUMP |
| if (NumberTemp::DoTrace(backwardPass)) |
| { |
| Output::Print(_u("%s: %8s%4sTemp Use ([] )"), NumberTemp::GetTraceName(), |
| backwardPass->IsPrePass() ? _u("Prepass ") : _u(""), isTempUse ? _u("") : _u("Non ")); |
| instr->DumpSimple(); |
| } |
| #endif |
| } |
| |
| void |
| NumberTemp::ProcessPropertySymUse(IR::SymOpnd * symOpnd, IR::Instr * instr, BackwardPass * backwardPass) |
| { |
| Assert(backwardPass->DoMarkTempNumbersOnTempObjects()); |
| |
| // We only care about instruction that may transfer the property value |
| if (!NumberTemp::IsTempPropertyTransferLoad(instr, backwardPass)) |
| { |
| return; |
| } |
| |
| PropertySym * propertySym = symOpnd->m_sym->AsPropertySym(); |
| upwardExposedMarkTempObjectLiveFields.Set(propertySym->m_id); |
| if (upwardExposedMarkTempObjectSymsProperties == nullptr) |
| { |
| upwardExposedMarkTempObjectSymsProperties = HashTable<BVSparse<JitArenaAllocator> *>::New(this->GetAllocator(), 16); |
| } |
| BVSparse<JitArenaAllocator> ** bv = upwardExposedMarkTempObjectSymsProperties->FindOrInsertNew(propertySym->m_stackSym->m_id); |
| if (*bv == nullptr) |
| { |
| *bv = JitAnew(this->GetAllocator(), BVSparse<JitArenaAllocator>, this->GetAllocator()); |
| } |
| (*bv)->Set(propertySym->m_propertyId); |
| |
| SymID propertySymId = this->GetRepresentativePropertySymId(propertySym, backwardPass); |
| bool isTempUse = instr->dstIsTempNumber; |
| if (!isTempUse) |
| { |
| // Use of the value is non temp, track the property ID's property representative sym so we don't mark temp |
| // assignment to this property on stack objects. |
| this->nonTempSyms.Set(propertySymId); |
| } |
| else if (this->IsInLoop() && !this->nonTempSyms.Test(propertySymId)) |
| { |
| // We didn't already detect non temp use of this property id. so we should track the dependencies in loops |
| IR::Opnd * dstOpnd = instr->GetDst(); |
| Assert(dstOpnd->IsRegOpnd()); |
| SymID dstSymID = dstOpnd->AsRegOpnd()->m_sym->m_id; |
| AddTransferDependencies(propertySym->m_propertyId, dstSymID, this->propertyIdsTempTransferDependencies); |
| #if DBG_DUMP |
| if (NumberTemp::DoTrace(backwardPass)) |
| { |
| Output::Print(_u("%s: %8s s%d -> PropId:%d: "), NumberTemp::GetTraceName(), |
| backwardPass->IsPrePass() ? _u("Prepass ") : _u(""), dstSymID, propertySym->m_propertyId); |
| (*this->propertyIdsTempTransferDependencies->Get(propertySym->m_propertyId))->Dump(); |
| } |
| #endif |
| } |
| |
| #if DBG_DUMP |
| if (NumberTemp::DoTrace(backwardPass)) |
| { |
| Output::Print(_u("%s: %8s%4sTemp Use (PropId:%d)"), NumberTemp::GetTraceName(), |
| backwardPass->IsPrePass() ? _u("Prepass ") : _u(""), isTempUse ? _u("") : _u("Non "), propertySym->m_propertyId); |
| instr->DumpSimple(); |
| } |
| #endif |
| } |
| |
| |
| bool |
| NumberTemp::HasExposedFieldDependencies(BVSparse<JitArenaAllocator> * bvTempTransferDependencies, BackwardPass * backwardPass) |
| { |
| if (!DoMarkTempNumbersOnTempObjects(backwardPass)) |
| { |
| return false; |
| } |
| return bvTempTransferDependencies->Test(&upwardExposedMarkTempObjectLiveFields); |
| } |
| |
| bool |
| NumberTemp::DoMarkTempNumbersOnTempObjects(BackwardPass * backwardPass) const |
| { |
| return backwardPass->DoMarkTempNumbersOnTempObjects() && !this->nonTempElemLoad; |
| } |
| |
| #if DBG |
| void |
| NumberTemp::Dump(char16 const * traceName) |
| { |
| if (nonTempElemLoad) |
| { |
| Output::Print(_u("%s: Has Non Temp Elem Load\n"), traceName); |
| } |
| else |
| { |
| Output::Print(_u("%s: Non Temp Syms"), traceName); |
| this->nonTempSyms.Dump(); |
| if (this->propertyIdsTempTransferDependencies != nullptr) |
| { |
| Output::Print(_u("%s: Temp transfer propertyId dependencies:\n"), traceName); |
| this->propertyIdsTempTransferDependencies->Dump(); |
| } |
| } |
| } |
| #endif |
| //================================================================================================= |
| // ObjectTemp |
| //================================================================================================= |
| bool |
| ObjectTemp::IsTempUse(IR::Instr * instr, Sym * sym, BackwardPass * backwardPass) |
| { |
| Js::OpCode opcode = instr->m_opcode; |
| |
| // If the opcode has implicit call and the profile say we have implicit call, then it is not a temp use |
| // TODO: More precise implicit call tracking |
| if (instr->HasAnyImplicitCalls() |
| && |
| ((backwardPass->currentBlock->loop != nullptr ? |
| !GlobOpt::ImplicitCallFlagsAllowOpts(backwardPass->currentBlock->loop) : |
| !GlobOpt::ImplicitCallFlagsAllowOpts(backwardPass->func)) |
| || instr->CallsAccessor()) |
| ) |
| { |
| return false; |
| } |
| |
| return IsTempUseOpCodeSym(instr, opcode, sym); |
| } |
| |
| bool |
| ObjectTemp::IsTempUseOpCodeSym(IR::Instr * instr, Js::OpCode opcode, Sym * sym) |
| { |
| // Special case ArgOut_A which communicate information about CallDirect |
| switch (opcode) |
| { |
| case Js::OpCode::LdLen_A: |
| return instr->GetSrc1()->AsRegOpnd()->GetStackSym() == sym; |
| case Js::OpCode::ArgOut_A: |
| return instr->dstIsTempObject; |
| case Js::OpCode::LdFld: |
| case Js::OpCode::LdFldForTypeOf: |
| case Js::OpCode::LdMethodFld: |
| case Js::OpCode::LdFldForCallApplyTarget: |
| case Js::OpCode::LdMethodFromFlags: |
| return instr->GetSrc1()->AsPropertySymOpnd()->GetObjectSym() == sym; |
| case Js::OpCode::InitFld: |
| if (Js::PropertyRecord::DefaultAttributesForPropertyId( |
| instr->GetDst()->AsPropertySymOpnd()->GetPropertySym()->m_propertyId, true) & PropertyDeleted) |
| { |
| // If the property record is marked PropertyDeleted, the InitFld will cause a type handler conversion, |
| // which may result in creation of a weak reference to the object itself. |
| return false; |
| } |
| // Fall through |
| case Js::OpCode::StFld: |
| case Js::OpCode::StFldStrict: |
| return |
| !(instr->GetSrc1() && instr->GetSrc1()->GetStackSym() == sym) && |
| !(instr->GetSrc2() && instr->GetSrc2()->GetStackSym() == sym) && |
| instr->GetDst()->AsPropertySymOpnd()->GetObjectSym() == sym; |
| case Js::OpCode::LdElemI_A: |
| return instr->GetSrc1()->AsIndirOpnd()->GetBaseOpnd()->m_sym == sym; |
| case Js::OpCode::StElemI_A: |
| case Js::OpCode::StElemI_A_Strict: |
| return instr->GetDst()->AsIndirOpnd()->GetBaseOpnd()->m_sym == sym; |
| case Js::OpCode::Memset: |
| return instr->GetDst()->AsIndirOpnd()->GetBaseOpnd()->m_sym == sym || (instr->GetSrc1()->IsRegOpnd() && instr->GetSrc1()->AsRegOpnd()->m_sym == sym); |
| case Js::OpCode::Memcopy: |
| return instr->GetDst()->AsIndirOpnd()->GetBaseOpnd()->m_sym == sym || instr->GetSrc1()->AsIndirOpnd()->GetBaseOpnd()->m_sym == sym; |
| |
| // Special case FromVar for now until we can allow CallsValueOf opcode to be accept temp use |
| case Js::OpCode::FromVar: |
| return true; |
| } |
| |
| // TODO: Currently, when we disable implicit call, we still don't allow valueOf/toString that has no side effects |
| // So we shouldn't mark them if we have use of the sym on opcode that does OpndHasImplicitCall yet. |
| if (OpCodeAttr::OpndHasImplicitCall(opcode)) |
| { |
| return false; |
| } |
| |
| // Mark the symbol as non-tempable if the instruction doesn't allow temp sources, |
| // or it is transferred to a non-temp dst |
| return (OpCodeAttr::TempObjectSources(opcode) |
| && (!OpCodeAttr::TempObjectTransfer(opcode) || instr->dstIsTempObject)); |
| } |
| |
| bool |
| ObjectTemp::IsTempTransfer(IR::Instr * instr) |
| { |
| return OpCodeAttr::TempObjectTransfer(instr->m_opcode); |
| } |
| |
| bool |
| ObjectTemp::IsTempProducing(IR::Instr * instr) |
| { |
| Js::OpCode opcode = instr->m_opcode; |
| if (OpCodeAttr::TempObjectProducing(opcode)) |
| { |
| return true; |
| } |
| |
| // TODO: Process NewScObject and CallI with isCtorCall when the ctor is fixed |
| return false; |
| } |
| |
| bool |
| ObjectTemp::CanStoreTemp(IR::Instr * instr) |
| { |
| // In order to allow storing temp number on temp objects, |
| // We have to make sure that if the instr is marked as dstIsTempObject |
| // we will always generate the code to allocate the object on the stack (so no helper call). |
| // Currently, we only do this for NewRegEx, NewScObjectSimple, NewScObjectLiteral and |
| // NewScObjectNoCtor (where the ctor is inlined). |
| |
| // CONSIDER: review lowering of other TempObjectProducing opcode and see if we can always allocate on the stack |
| // (for example, NewScArray should be able to, but plain NewScObject can't because the size depends on the |
| // number inline slots) |
| Js::OpCode opcode = instr->m_opcode; |
| if (OpCodeAttr::TempObjectCanStoreTemp(opcode)) |
| { |
| // Special cases where stack allocation doesn't happen |
| #if ENABLE_REGEX_CONFIG_OPTIONS |
| if (opcode == Js::OpCode::NewRegEx && REGEX_CONFIG_FLAG(RegexTracing)) |
| { |
| return false; |
| } |
| #endif |
| if (opcode == Js::OpCode::NewScObjectNoCtor) |
| { |
| if (PHASE_OFF(Js::FixedNewObjPhase, instr->m_func) && PHASE_OFF(Js::ObjTypeSpecNewObjPhase, instr->m_func->GetTopFunc())) |
| { |
| return false; |
| } |
| |
| // Only if we have BailOutFailedCtorGuardCheck would we generate a stack object. |
| // Otherwise we will call the helper, which will not generate stack object. |
| return instr->HasBailOutInfo(); |
| } |
| |
| return true; |
| } |
| return false; |
| } |
| |
| bool |
| ObjectTemp::CanMarkTemp(IR::Instr * instr, BackwardPass * backwardPass) |
| { |
| // We mark the ArgOut with the call in ProcessInstr, no need to do it here |
| return IsTempProducing(instr) || IsTempTransfer(instr); |
| } |
| |
| void |
| ObjectTemp::ProcessBailOnNoProfile(IR::Instr * instr) |
| { |
| Assert(instr->m_opcode == Js::OpCode::BailOnNoProfile); |
| |
| // ObjectTemp is done during Backward pass, which hasn't change all succ to BailOnNoProfile |
| // to dead yet, so we need to manually clear all the information |
| this->nonTempSyms.ClearAll(); |
| this->tempTransferredSyms.ClearAll(); |
| if (this->tempTransferDependencies) |
| { |
| this->tempTransferDependencies->ClearAll(); |
| } |
| } |
| |
| void |
| ObjectTemp::ProcessInstr(IR::Instr * instr) |
| { |
| if (instr->m_opcode != Js::OpCode::CallDirect) |
| { |
| return; |
| } |
| |
| IR::HelperCallOpnd * helper = instr->GetSrc1()->AsHelperCallOpnd(); |
| switch (helper->m_fnHelper) |
| { |
| case IR::JnHelperMethod::HelperString_Match: |
| case IR::JnHelperMethod::HelperString_Replace: |
| { |
| // First (non-this) parameter is either a regexp or search string. |
| // It doesn't escape. |
| IR::Instr * instrArgDef; |
| instr->FindCallArgumentOpnd(2, &instrArgDef); |
| instrArgDef->dstIsTempObject = true; |
| break; |
| } |
| |
| case IR::JnHelperMethod::HelperRegExp_Exec: |
| { |
| IR::Instr * instrArgDef; |
| instr->FindCallArgumentOpnd(1, &instrArgDef); |
| instrArgDef->dstIsTempObject = true; |
| break; |
| } |
| }; |
| } |
| |
| |
| void |
| ObjectTemp::SetDstIsTemp(bool dstIsTemp, bool dstIsTempTransferred, IR::Instr * instr, BackwardPass * backwardPass) |
| { |
| Assert(dstIsTemp || !dstIsTempTransferred); |
| |
| // ArgOut_A are marked by CallDirect and don't need to be set |
| if (instr->m_opcode == Js::OpCode::ArgOut_A) |
| { |
| return; |
| } |
| instr->dstIsTempObject = dstIsTemp; |
| |
| if (!backwardPass->IsPrePass()) |
| { |
| if (OpCodeAttr::TempObjectProducing(instr->m_opcode)) |
| { |
| backwardPass->func->SetHasMarkTempObjects(); |
| #if DBG_DUMP |
| backwardPass->numMarkTempObject += dstIsTemp; |
| #endif |
| } |
| } |
| } |
| |
| StackSym * |
| ObjectTemp::GetStackSym(IR::Opnd * opnd, IR::PropertySymOpnd ** pPropertySymOpnd) |
| { |
| StackSym * stackSym = nullptr; |
| switch (opnd->GetKind()) |
| { |
| case IR::OpndKindReg: |
| stackSym = opnd->AsRegOpnd()->m_sym; |
| break; |
| case IR::OpndKindSym: |
| { |
| IR::SymOpnd * symOpnd = opnd->AsSymOpnd(); |
| if (symOpnd->IsPropertySymOpnd()) |
| { |
| IR::PropertySymOpnd * propertySymOpnd = symOpnd->AsPropertySymOpnd(); |
| *pPropertySymOpnd = propertySymOpnd; |
| stackSym = propertySymOpnd->GetObjectSym(); |
| } |
| else if (symOpnd->m_sym->IsPropertySym()) |
| { |
| stackSym = symOpnd->m_sym->AsPropertySym()->m_stackSym; |
| } |
| break; |
| } |
| case IR::OpndKindIndir: |
| stackSym = opnd->AsIndirOpnd()->GetBaseOpnd()->m_sym; |
| break; |
| }; |
| return stackSym; |
| } |
| |
| #if DBG |
| //================================================================================================= |
| // ObjectTempVerify |
| //================================================================================================= |
| ObjectTempVerify::ObjectTempVerify(JitArenaAllocator * alloc, bool inLoop) |
| : TempTrackerBase(alloc, inLoop), removedUpwardExposedUse(alloc) |
| { |
| } |
| |
| |
| bool |
| ObjectTempVerify::IsTempUse(IR::Instr * instr, Sym * sym, BackwardPass * backwardPass) |
| { |
| Js::OpCode opcode = instr->m_opcode; |
| |
| // If the opcode has implicit call and the profile say we have implicit call, then it is not a temp use. |
| // TODO: More precise implicit call tracking |
| bool isLandingPad = backwardPass->currentBlock->IsLandingPad(); |
| if (OpCodeAttr::HasImplicitCall(opcode) && !isLandingPad |
| && |
| ((backwardPass->currentBlock->loop != nullptr ? |
| !GlobOpt::ImplicitCallFlagsAllowOpts(backwardPass->currentBlock->loop) : |
| !GlobOpt::ImplicitCallFlagsAllowOpts(backwardPass->func)) |
| || instr->CallsAccessor()) |
| ) |
| { |
| return false; |
| } |
| |
| if (!ObjectTemp::IsTempUseOpCodeSym(instr, opcode, sym)) |
| { |
| // the opcode and sym is not a temp use, just return |
| return false; |
| } |
| |
| // In the backward pass, this would have been a temp use already. Continue to verify |
| // if we have install sufficient bailout on implicit call |
| |
| if (isLandingPad || !GlobOpt::MayNeedBailOnImplicitCall(instr, nullptr, nullptr)) |
| { |
| // Implicit call would not happen, or we are in the landing pad where implicit call is disabled. |
| return true; |
| } |
| |
| if (instr->HasBailOutInfo()) |
| { |
| // make sure we have mark the bailout for mark temp object, |
| // so that we won't optimize it away in DeadStoreImplicitCalls |
| return ((instr->GetBailOutKind() & IR::BailOutMarkTempObject) != 0); |
| } |
| |
| // Review (ObjTypeSpec): This is a bit conservative now that we don't revert from obj type specialized operations to live cache |
| // access even if the operation is isolated. Once we decide a given instruction is an object type spec candidate, we know it |
| // will never need an implicit call, so we could basically do opnd->IsObjTypeSpecOptimized() here, instead. |
| if (GlobOpt::IsTypeCheckProtected(instr)) |
| { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| bool |
| ObjectTempVerify::IsTempTransfer(IR::Instr * instr) |
| { |
| if (ObjectTemp::IsTempTransfer(instr) |
| // Add the Ld_I4, and LdC_A_I4 as the forward pass might have changed Ld_A to these |
| || instr->m_opcode == Js::OpCode::Ld_I4 |
| || instr->m_opcode == Js::OpCode::LdC_A_I4) |
| { |
| if (!instr->dstIsTempObject && instr->GetDst() && instr->GetDst()->IsRegOpnd() |
| && instr->GetDst()->AsRegOpnd()->GetValueType().IsNotObject()) |
| { |
| // Globopt has proved that dst is not an object, so this is not really an object transfer. |
| // This prevents the case where glob opt turned a Conv_Num to Ld_A and expose additional |
| // transfer. |
| return false; |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| bool |
| ObjectTempVerify::CanMarkTemp(IR::Instr * instr, BackwardPass * backwardPass) |
| { |
| // We mark the ArgOut with the call in ProcessInstr, no need to do it here |
| return ObjectTemp::IsTempProducing(instr) |
| || IsTempTransfer(instr); |
| } |
| |
| void |
| ObjectTempVerify::ProcessInstr(IR::Instr * instr, BackwardPass * backwardPass) |
| { |
| if (instr->m_opcode == Js::OpCode::InlineThrow) |
| { |
| // We cannot accurately track mark temp for any upward exposed symbol here |
| this->removedUpwardExposedUse.Or(backwardPass->currentBlock->byteCodeUpwardExposedUsed); |
| return; |
| } |
| |
| if (instr->m_opcode != Js::OpCode::CallDirect) |
| { |
| return; |
| } |
| |
| IR::HelperCallOpnd * helper = instr->GetSrc1()->AsHelperCallOpnd(); |
| switch (helper->m_fnHelper) |
| { |
| case IR::JnHelperMethod::HelperString_Match: |
| case IR::JnHelperMethod::HelperString_Replace: |
| { |
| // First (non-this) parameter is either a regexp or search string |
| // It doesn't escape |
| IR::Instr * instrArgDef; |
| instr->FindCallArgumentOpnd(2, &instrArgDef); |
| Assert(instrArgDef->dstIsTempObject); |
| break; |
| } |
| |
| case IR::JnHelperMethod::HelperRegExp_Exec: |
| { |
| IR::Instr * instrArgDef; |
| instr->FindCallArgumentOpnd(1, &instrArgDef); |
| Assert(instrArgDef->dstIsTempObject); |
| break; |
| } |
| }; |
| } |
| |
| |
| void |
| ObjectTempVerify::SetDstIsTemp(bool dstIsTemp, bool dstIsTempTransferred, IR::Instr * instr, BackwardPass * backwardPass) |
| { |
| Assert(dstIsTemp || !dstIsTempTransferred); |
| |
| // ArgOut_A are marked by CallDirect and don't need to be set |
| if (instr->m_opcode == Js::OpCode::ArgOut_A) |
| { |
| return; |
| } |
| |
| if (OpCodeAttr::TempObjectProducing(instr->m_opcode)) |
| { |
| if (!backwardPass->IsPrePass()) |
| { |
| if (dstIsTemp) |
| { |
| // Don't assert if we have detected a removed upward exposed use that could |
| // expose a new mark temp object. Don't assert if it is set in removedUpwardExposedUse |
| bool isBailOnNoProfileUpwardExposedUse = |
| !!this->removedUpwardExposedUse.Test(instr->GetDst()->AsRegOpnd()->m_sym->m_id); |
| #if DBG |
| if (DoTrace(backwardPass) && !instr->dstIsTempObject && !isBailOnNoProfileUpwardExposedUse) |
| { |
| Output::Print(_u("%s: Missed Mark Temp Object: "), GetTraceName()); |
| instr->DumpSimple(); |
| Output::Flush(); |
| } |
| #endif |
| // TODO: Unfortunately we still hit this a lot as we are not accounting for some of the globopt changes |
| // to the IR. It is just reporting that we have missed mark temp object opportunity, so it doesn't |
| // indicate a functional failure. Disable for now. |
| // Assert(instr->dstIsTempObject || isBailOnNoProfileUpwardExposedUse); |
| } |
| else |
| { |
| // If we have marked the dst is temp in the backward pass, the globopt |
| // should have maintained it, and it will be wrong to have detect that it is not |
| // temp now in the deadstore pass (whether there is BailOnNoProfile or not) |
| #if DBG |
| if (DoTrace(backwardPass) && instr->dstIsTempObject) |
| { |
| Output::Print(_u("%s: Invalid Mark Temp Object: "), GetTraceName()); |
| instr->DumpSimple(); |
| Output::Flush(); |
| } |
| #endif |
| Assert(!instr->dstIsTempObject); |
| } |
| } |
| } |
| else if (IsTempTransfer(instr)) |
| { |
| // Only set the transfer |
| instr->dstIsTempObject = dstIsTemp; |
| } |
| else |
| { |
| Assert(!dstIsTemp); |
| Assert(!instr->dstIsTempObject); |
| } |
| |
| // clear or transfer the bailOnNoProfile upward exposed use |
| if (this->removedUpwardExposedUse.TestAndClear(instr->GetDst()->AsRegOpnd()->m_sym->m_id) |
| && IsTempTransfer(instr) && instr->GetSrc1()->IsRegOpnd()) |
| { |
| this->removedUpwardExposedUse.Set(instr->GetSrc1()->AsRegOpnd()->m_sym->m_id); |
| } |
| } |
| |
| void |
| ObjectTempVerify::MergeData(ObjectTempVerify * fromData, bool deleteData) |
| { |
| this->removedUpwardExposedUse.Or(&fromData->removedUpwardExposedUse); |
| } |
| |
| void |
| ObjectTempVerify::MergeDeadData(BasicBlock * block) |
| { |
| MergeData(block->tempObjectVerifyTracker, false); |
| if (!block->isDead) |
| { |
| // If there was dead flow to a block that is not dead, it might expose |
| // new mark temp object, so all its current used (upwardExposedUsed) and optimized |
| // use (byteCodeupwardExposedUsed) might not be trace for "missed" mark temp object |
| this->removedUpwardExposedUse.Or(block->upwardExposedUses); |
| if (block->byteCodeUpwardExposedUsed) |
| { |
| this->removedUpwardExposedUse.Or(block->byteCodeUpwardExposedUsed); |
| } |
| } |
| } |
| |
| void |
| ObjectTempVerify::NotifyBailOutRemoval(IR:: Instr * instr, BackwardPass * backwardPass) |
| { |
| Js::OpCode opcode = instr->m_opcode; |
| switch (opcode) |
| { |
| case Js::OpCode::LdFld: |
| case Js::OpCode::LdFldForTypeOf: |
| case Js::OpCode::LdMethodFld: |
| ((TempTracker<ObjectTempVerify> *)this)->ProcessUse(instr->GetSrc1()->AsPropertySymOpnd()->GetObjectSym(), backwardPass); |
| break; |
| case Js::OpCode::InitFld: |
| case Js::OpCode::StFld: |
| case Js::OpCode::StFldStrict: |
| ((TempTracker<ObjectTempVerify> *)this)->ProcessUse(instr->GetDst()->AsPropertySymOpnd()->GetObjectSym(), backwardPass); |
| break; |
| case Js::OpCode::LdElemI_A: |
| ((TempTracker<ObjectTempVerify> *)this)->ProcessUse(instr->GetSrc1()->AsIndirOpnd()->GetBaseOpnd()->m_sym, backwardPass); |
| break; |
| case Js::OpCode::StElemI_A: |
| ((TempTracker<ObjectTempVerify> *)this)->ProcessUse(instr->GetDst()->AsIndirOpnd()->GetBaseOpnd()->m_sym, backwardPass); |
| break; |
| } |
| } |
| |
| void |
| ObjectTempVerify::NotifyReverseCopyProp(IR::Instr * instr) |
| { |
| Assert(instr->GetDst()); |
| SymID symId = instr->GetDst()->AsRegOpnd()->m_sym->m_id; |
| this->removedUpwardExposedUse.Clear(symId); |
| this->nonTempSyms.Clear(symId); |
| } |
| |
| void |
| ObjectTempVerify::NotifyDeadStore(IR::Instr * instr, BackwardPass * backwardPass) |
| { |
| // Even if we dead store, simulate the uses |
| IR::Opnd * src1 = instr->GetSrc1(); |
| if (src1) |
| { |
| IR::PropertySymOpnd * propertySymOpnd; |
| StackSym * stackSym = ObjectTemp::GetStackSym(src1, &propertySymOpnd); |
| if (stackSym) |
| { |
| ((TempTracker<ObjectTempVerify> *)this)->ProcessUse(stackSym, backwardPass); |
| } |
| |
| IR::Opnd * src2 = instr->GetSrc2(); |
| if (src2) |
| { |
| stackSym = ObjectTemp::GetStackSym(src2, &propertySymOpnd); |
| if (stackSym) |
| { |
| ((TempTracker<ObjectTempVerify> *)this)->ProcessUse(stackSym, backwardPass); |
| } |
| } |
| } |
| } |
| |
| void |
| ObjectTempVerify::NotifyDeadByteCodeUses(IR::Instr * instr) |
| { |
| if (instr->GetDst()) |
| { |
| SymID symId = instr->GetDst()->AsRegOpnd()->m_sym->m_id; |
| this->removedUpwardExposedUse.Clear(symId); |
| this->nonTempSyms.Clear(symId); |
| } |
| |
| IR::ByteCodeUsesInstr *byteCodeUsesInstr = instr->AsByteCodeUsesInstr(); |
| const BVSparse<JitArenaAllocator> * byteCodeUpwardExposedUsed = byteCodeUsesInstr->GetByteCodeUpwardExposedUsed(); |
| if (byteCodeUpwardExposedUsed != nullptr) |
| { |
| this->removedUpwardExposedUse.Or(byteCodeUpwardExposedUsed); |
| } |
| } |
| |
| bool |
| ObjectTempVerify::DependencyCheck(IR::Instr * instr, BVSparse<JitArenaAllocator> * bvTempTransferDependencies, BackwardPass * backwardPass) |
| { |
| if (!instr->dstIsTempObject) |
| { |
| // The instruction is not marked as temp object anyway, no need to do extra check |
| return false; |
| } |
| |
| // Since our algorithm is conservative, there are cases where even though two defs are unrelated, the use will still |
| // seem like overlapping and not mark-temp-able |
| // For example: |
| // = s6.blah |
| // s1 = LdRootFld |
| // s6 = s1 |
| // s1 = NewScObject // s1 is dependent of s6, and s6 is upward exposed. |
| // = s6.blah |
| // s6 = s1 |
| // Here, although s1 is mark temp able because the s6.blah use is not related, we only know that s1 is dependent of s6 |
| // so it looks like s1 may overlap through the iterations. The backward pass will be able to catch that and not mark temp them |
| |
| // However, the globopt may create situation like the above while it wasn't there in the backward phase |
| // For example: |
| // = s6.blah |
| // s1 = LdRootFld g |
| // s6 = s1 |
| // s1 = NewScObject |
| // s7 = LdRootFld g |
| // = s7.blah // Globopt copy prop s7 -> s6, creating the example above. |
| // s6 = s1 |
| // This make it impossible to verify whether we did the right thing using the conservative algorithm. |
| |
| // Luckily, this case is very rare (ExprGen didn't hit it with > 100K test cases) |
| // So we can use this rather expensive algorithm to find out if any of upward exposed used that we think overlaps |
| // really get their value from the marked temp sym or not. |
| |
| // See unittest\Object\stackobject_dependency.js (with -maxinterpretcount:1 -off:inline) |
| |
| BasicBlock * currentBlock = backwardPass->currentBlock; |
| BVSparse<JitArenaAllocator> * upwardExposedUses = currentBlock->upwardExposedUses; |
| JitArenaAllocator tempAllocator(_u("temp"), instr->m_func->m_alloc->GetPageAllocator(), Js::Throw::OutOfMemory); |
| BVSparse<JitArenaAllocator> * dependentSyms = bvTempTransferDependencies->AndNew(upwardExposedUses, &tempAllocator); |
| BVSparse<JitArenaAllocator> * initialDependentSyms = dependentSyms->CopyNew(); |
| Assert(!dependentSyms->IsEmpty()); |
| struct BlockRecord |
| { |
| BasicBlock * block; |
| BVSparse<JitArenaAllocator> * dependentSyms; |
| }; |
| SList<BlockRecord> blockStack(&tempAllocator); |
| JsUtil::BaseDictionary<BasicBlock *, BVSparse<JitArenaAllocator> *, JitArenaAllocator> processedSyms(&tempAllocator); |
| |
| IR::Instr * currentInstr = instr; |
| Assert(instr->GetDst()->AsRegOpnd()->m_sym->IsVar()); |
| SymID markTempSymId = instr->GetDst()->AsRegOpnd()->m_sym->m_id; |
| bool initial = true; |
| while (true) |
| { |
| while (currentInstr != currentBlock->GetFirstInstr()) |
| { |
| if (initial) |
| { |
| initial = false; |
| } |
| else if (currentInstr == instr) |
| { |
| if (dependentSyms->Test(markTempSymId)) |
| { |
| // One of the dependent sym from the original set get it's value from the current marked temp dst. |
| // The dst definitely cannot be temp because it's lifetime overlaps across iterations. |
| return false; |
| } |
| |
| // If we have already check the same dependent sym, no need to do it again. |
| // It will produce the same result anyway. |
| dependentSyms->Minus(initialDependentSyms); |
| if (dependentSyms->IsEmpty()) |
| { |
| break; |
| } |
| // Add in newly discovered dependentSym so we won't do it again when it come back here. |
| initialDependentSyms->Or(dependentSyms); |
| } |
| |
| if (currentInstr->GetDst() && currentInstr->GetDst()->IsRegOpnd()) |
| { |
| // Clear the def and mark the src if it is transferred. |
| |
| // If the dst sym is a type specialized sym, clear the var sym instead. |
| StackSym * dstSym = currentInstr->GetDst()->AsRegOpnd()->m_sym; |
| if (!dstSym->IsVar()) |
| { |
| dstSym = dstSym->GetVarEquivSym(nullptr); |
| } |
| if (dstSym && dependentSyms->TestAndClear(dstSym->m_id) && |
| IsTempTransfer(currentInstr) && currentInstr->GetSrc1()->IsRegOpnd()) |
| { |
| // We only really care about var syms uses for object temp. |
| StackSym * srcSym = currentInstr->GetSrc1()->AsRegOpnd()->m_sym; |
| if (srcSym->IsVar()) |
| { |
| dependentSyms->Set(srcSym->m_id); |
| } |
| } |
| |
| if (dependentSyms->IsEmpty()) |
| { |
| // No more dependent sym, we found the def of all of them we can move on to the next block. |
| break; |
| } |
| } |
| currentInstr = currentInstr->m_prev; |
| } |
| |
| if (currentBlock->isLoopHeader && !dependentSyms->IsEmpty()) |
| { |
| Assert(currentInstr == currentBlock->GetFirstInstr()); |
| // If we have try to propagate the symbol through the loop before, we don't need to propagate it again. |
| BVSparse<JitArenaAllocator> * currentLoopProcessedSyms = processedSyms.Lookup(currentBlock, nullptr); |
| if (currentLoopProcessedSyms == nullptr) |
| { |
| processedSyms.Add(currentBlock, dependentSyms->CopyNew()); |
| } |
| else |
| { |
| dependentSyms->Minus(currentLoopProcessedSyms); |
| currentLoopProcessedSyms->Or(dependentSyms); |
| } |
| } |
| |
| if (!dependentSyms->IsEmpty()) |
| { |
| Assert(currentInstr == currentBlock->GetFirstInstr()); |
| |
| FOREACH_PREDECESSOR_BLOCK(predBlock, currentBlock) |
| { |
| if (predBlock->loop == nullptr) |
| { |
| // No need to track outside of loops. |
| continue; |
| } |
| BlockRecord record; |
| record.block = predBlock; |
| record.dependentSyms = dependentSyms->CopyNew(); |
| blockStack.Prepend(record); |
| } |
| NEXT_PREDECESSOR_BLOCK; |
| } |
| JitAdelete(&tempAllocator, dependentSyms); |
| |
| if (blockStack.Empty()) |
| { |
| // No more blocks. We are done. |
| break; |
| } |
| |
| currentBlock = blockStack.Head().block; |
| dependentSyms = blockStack.Head().dependentSyms; |
| blockStack.RemoveHead(); |
| currentInstr = currentBlock->GetLastInstr(); |
| } |
| |
| // All the dependent sym doesn't get their value from the marked temp def, so it can really be marked temp. |
| #if DBG |
| if (DoTrace(backwardPass)) |
| { |
| Output::Print(_u("%s: Unrelated overlap mark temp (s%-3d): "), GetTraceName(), markTempSymId); |
| instr->DumpSimple(); |
| Output::Flush(); |
| } |
| #endif |
| return true; |
| } |
| #endif |
| |
| |
| #if DBG |
| bool |
| NumberTemp::DoTrace(BackwardPass * backwardPass) |
| { |
| return PHASE_TRACE(Js::MarkTempNumberPhase, backwardPass->func); |
| } |
| |
| bool |
| ObjectTemp::DoTrace(BackwardPass * backwardPass) |
| { |
| return PHASE_TRACE(Js::MarkTempObjectPhase, backwardPass->func); |
| } |
| |
| bool |
| ObjectTempVerify::DoTrace(BackwardPass * backwardPass) |
| { |
| return PHASE_TRACE(Js::MarkTempObjectPhase, backwardPass->func); |
| } |
| #endif |
| |
| // explicit instantiation |
| template class TempTracker<NumberTemp>; |
| template class TempTracker<ObjectTemp>; |
| |
| #if DBG |
| template class TempTracker<ObjectTempVerify>; |
| #endif |