blob: 2d2fbc7a3a0caaebfd0d39edacde016786523366 [file] [log] [blame]
//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft Corporation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------
#include "Backend.h"
void
GlobOptBlockData::NullOutBlockData(GlobOpt* globOpt, Func* func)
{
this->globOpt = globOpt;
this->symToValueMap = nullptr;
this->exprToValueMap = nullptr;
this->liveFields = nullptr;
this->maybeWrittenTypeSyms = nullptr;
this->isTempSrc = nullptr;
this->liveVarSyms = nullptr;
this->liveInt32Syms = nullptr;
this->liveLossyInt32Syms = nullptr;
this->liveFloat64Syms = nullptr;
this->argObjSyms = nullptr;
this->maybeTempObjectSyms = nullptr;
this->canStoreTempObjectSyms = nullptr;
this->valuesToKillOnCalls = nullptr;
this->inductionVariables = nullptr;
this->availableIntBoundChecks = nullptr;
this->callSequence = nullptr;
this->startCallCount = 0;
this->argOutCount = 0;
this->totalOutParamCount = 0;
this->inlinedArgOutSize = 0;
this->hasCSECandidates = false;
this->curFunc = func;
this->stackLiteralInitFldDataMap = nullptr;
if (this->capturedValues)
{
this->capturedValues->DecrementRefCount();
}
this->capturedValues = nullptr;
this->changedSyms = nullptr;
this->OnDataUnreferenced();
}
void
GlobOptBlockData::InitBlockData(GlobOpt* globOpt, Func* func)
{
this->globOpt = globOpt;
JitArenaAllocator *const alloc = this->globOpt->alloc;
this->symToValueMap = GlobHashTable::New(alloc, 64);
this->exprToValueMap = ExprHashTable::New(alloc, 64);
this->liveFields = JitAnew(alloc, BVSparse<JitArenaAllocator>, alloc);
this->liveArrayValues = JitAnew(alloc, BVSparse<JitArenaAllocator>, alloc);
this->isTempSrc = JitAnew(alloc, BVSparse<JitArenaAllocator>, alloc);
this->liveVarSyms = JitAnew(alloc, BVSparse<JitArenaAllocator>, alloc);
this->liveInt32Syms = JitAnew(alloc, BVSparse<JitArenaAllocator>, alloc);
this->liveLossyInt32Syms = JitAnew(alloc, BVSparse<JitArenaAllocator>, alloc);
this->liveFloat64Syms = JitAnew(alloc, BVSparse<JitArenaAllocator>, alloc);
this->argObjSyms = JitAnew(alloc, BVSparse<JitArenaAllocator>, alloc);
this->maybeTempObjectSyms = nullptr;
this->canStoreTempObjectSyms = nullptr;
this->valuesToKillOnCalls = JitAnew(alloc, ValueSet, alloc);
if(this->globOpt->DoBoundCheckHoist())
{
this->inductionVariables = this->globOpt->IsLoopPrePass() ? JitAnew(alloc, InductionVariableSet, alloc) : nullptr;
this->availableIntBoundChecks = JitAnew(alloc, IntBoundCheckSet, alloc);
}
this->maybeWrittenTypeSyms = nullptr;
this->callSequence = nullptr;
this->startCallCount = 0;
this->argOutCount = 0;
this->totalOutParamCount = 0;
this->inlinedArgOutSize = 0;
this->hasCSECandidates = false;
this->curFunc = func;
this->stackLiteralInitFldDataMap = nullptr;
this->changedSyms = JitAnew(alloc, BVSparse<JitArenaAllocator>, alloc);
this->OnDataInitialized(alloc);
}
void
GlobOptBlockData::ReuseBlockData(GlobOptBlockData *fromData)
{
this->globOpt = fromData->globOpt;
// Reuse dead map
this->symToValueMap = fromData->symToValueMap;
this->exprToValueMap = fromData->exprToValueMap;
this->liveFields = fromData->liveFields;
this->liveArrayValues = fromData->liveArrayValues;
this->maybeWrittenTypeSyms = fromData->maybeWrittenTypeSyms;
this->isTempSrc = fromData->isTempSrc;
this->liveVarSyms = fromData->liveVarSyms;
this->liveInt32Syms = fromData->liveInt32Syms;
this->liveLossyInt32Syms = fromData->liveLossyInt32Syms;
this->liveFloat64Syms = fromData->liveFloat64Syms;
if (this->globOpt->TrackArgumentsObject())
{
this->argObjSyms = fromData->argObjSyms;
}
this->maybeTempObjectSyms = fromData->maybeTempObjectSyms;
this->canStoreTempObjectSyms = fromData->canStoreTempObjectSyms;
this->curFunc = fromData->curFunc;
this->valuesToKillOnCalls = fromData->valuesToKillOnCalls;
this->inductionVariables = fromData->inductionVariables;
this->availableIntBoundChecks = fromData->availableIntBoundChecks;
this->callSequence = fromData->callSequence;
this->startCallCount = fromData->startCallCount;
this->argOutCount = fromData->argOutCount;
this->totalOutParamCount = fromData->totalOutParamCount;
this->inlinedArgOutSize = fromData->inlinedArgOutSize;
this->hasCSECandidates = fromData->hasCSECandidates;
this->stackLiteralInitFldDataMap = fromData->stackLiteralInitFldDataMap;
this->changedSyms = fromData->changedSyms;
this->capturedValues = fromData->capturedValues;
if (this->capturedValues)
{
this->capturedValues->IncrementRefCount();
}
this->OnDataReused(fromData);
}
void
GlobOptBlockData::CopyBlockData(GlobOptBlockData *fromData)
{
this->globOpt = fromData->globOpt;
this->symToValueMap = fromData->symToValueMap;
this->exprToValueMap = fromData->exprToValueMap;
this->liveFields = fromData->liveFields;
this->liveArrayValues = fromData->liveArrayValues;
this->maybeWrittenTypeSyms = fromData->maybeWrittenTypeSyms;
this->isTempSrc = fromData->isTempSrc;
this->liveVarSyms = fromData->liveVarSyms;
this->liveInt32Syms = fromData->liveInt32Syms;
this->liveLossyInt32Syms = fromData->liveLossyInt32Syms;
this->liveFloat64Syms = fromData->liveFloat64Syms;
this->argObjSyms = fromData->argObjSyms;
this->maybeTempObjectSyms = fromData->maybeTempObjectSyms;
this->canStoreTempObjectSyms = fromData->canStoreTempObjectSyms;
this->curFunc = fromData->curFunc;
this->valuesToKillOnCalls = fromData->valuesToKillOnCalls;
this->inductionVariables = fromData->inductionVariables;
this->availableIntBoundChecks = fromData->availableIntBoundChecks;
this->callSequence = fromData->callSequence;
this->startCallCount = fromData->startCallCount;
this->argOutCount = fromData->argOutCount;
this->totalOutParamCount = fromData->totalOutParamCount;
this->inlinedArgOutSize = fromData->inlinedArgOutSize;
this->hasCSECandidates = fromData->hasCSECandidates;
this->changedSyms = fromData->changedSyms;
this->capturedValues = fromData->capturedValues;
this->stackLiteralInitFldDataMap = fromData->stackLiteralInitFldDataMap;
this->OnDataReused(fromData);
}
void
GlobOptBlockData::DeleteBlockData()
{
JitArenaAllocator *const alloc = this->globOpt->alloc;
this->symToValueMap->Delete();
this->exprToValueMap->Delete();
JitAdelete(alloc, this->liveFields);
JitAdelete(alloc, this->liveArrayValues);
if (this->maybeWrittenTypeSyms)
{
JitAdelete(alloc, this->maybeWrittenTypeSyms);
}
JitAdelete(alloc, this->isTempSrc);
JitAdelete(alloc, this->liveVarSyms);
JitAdelete(alloc, this->liveInt32Syms);
JitAdelete(alloc, this->liveLossyInt32Syms);
JitAdelete(alloc, this->liveFloat64Syms);
if (this->argObjSyms)
{
JitAdelete(alloc, this->argObjSyms);
}
if (this->maybeTempObjectSyms)
{
JitAdelete(alloc, this->maybeTempObjectSyms);
if (this->canStoreTempObjectSyms)
{
JitAdelete(alloc, this->canStoreTempObjectSyms);
}
}
else
{
Assert(!this->canStoreTempObjectSyms);
}
JitAdelete(alloc, this->valuesToKillOnCalls);
if(this->inductionVariables)
{
JitAdelete(alloc, this->inductionVariables);
}
if(this->availableIntBoundChecks)
{
JitAdelete(alloc, this->availableIntBoundChecks);
}
if (this->stackLiteralInitFldDataMap)
{
JitAdelete(alloc, this->stackLiteralInitFldDataMap);
}
JitAdelete(alloc, this->changedSyms);
this->changedSyms = nullptr;
if (this->capturedValues && this->capturedValues->DecrementRefCount() == 0)
{
JitAdelete(this->curFunc->m_alloc, this->capturedValues);
this->capturedValues = nullptr;
}
this->OnDataDeleted();
}
void GlobOptBlockData::CloneBlockData(BasicBlock *const toBlockContext, BasicBlock *const fromBlock)
{
GlobOptBlockData *const fromData = &fromBlock->globOptData;
this->globOpt = fromData->globOpt;
JitArenaAllocator *const alloc = this->globOpt->alloc;
this->symToValueMap = fromData->symToValueMap->Copy();
this->exprToValueMap = fromData->exprToValueMap->Copy();
// Clone the values as well to allow for flow-sensitive ValueInfo
this->globOpt->CloneValues(toBlockContext, this, fromData);
if(this->globOpt->DoBoundCheckHoist())
{
this->globOpt->CloneBoundCheckHoistBlockData(toBlockContext, this, fromBlock, fromData);
}
this->liveFields = JitAnew(alloc, BVSparse<JitArenaAllocator>, alloc);
this->liveFields->Copy(fromData->liveFields);
this->liveArrayValues = JitAnew(alloc, BVSparse<JitArenaAllocator>, alloc);
this->liveArrayValues->Copy(fromData->liveArrayValues);
if (fromData->maybeWrittenTypeSyms)
{
this->maybeWrittenTypeSyms = JitAnew(alloc, BVSparse<JitArenaAllocator>, alloc);
this->maybeWrittenTypeSyms->Copy(fromData->maybeWrittenTypeSyms);
}
this->isTempSrc = JitAnew(alloc, BVSparse<JitArenaAllocator>, alloc);
this->isTempSrc->Copy(fromData->isTempSrc);
this->liveVarSyms = JitAnew(alloc, BVSparse<JitArenaAllocator>, alloc);
this->liveVarSyms->Copy(fromData->liveVarSyms);
this->liveInt32Syms = JitAnew(alloc, BVSparse<JitArenaAllocator>, alloc);
this->liveInt32Syms->Copy(fromData->liveInt32Syms);
this->liveLossyInt32Syms = JitAnew(alloc, BVSparse<JitArenaAllocator>, alloc);
this->liveLossyInt32Syms->Copy(fromData->liveLossyInt32Syms);
this->liveFloat64Syms = JitAnew(alloc, BVSparse<JitArenaAllocator>, alloc);
this->liveFloat64Syms->Copy(fromData->liveFloat64Syms);
if (this->globOpt->TrackArgumentsObject() && fromData->argObjSyms)
{
this->argObjSyms = fromData->argObjSyms->CopyNew(alloc);
}
if (fromData->maybeTempObjectSyms && !fromData->maybeTempObjectSyms->IsEmpty())
{
this->maybeTempObjectSyms = fromData->maybeTempObjectSyms->CopyNew(alloc);
if (fromData->canStoreTempObjectSyms && !fromData->canStoreTempObjectSyms->IsEmpty())
{
this->canStoreTempObjectSyms = fromData->canStoreTempObjectSyms->CopyNew(alloc);
}
}
else
{
Assert(fromData->canStoreTempObjectSyms == nullptr || fromData->canStoreTempObjectSyms->IsEmpty());
}
this->curFunc = fromData->curFunc;
if (fromData->callSequence != nullptr)
{
this->callSequence = JitAnew(alloc, SListBase<IR::Opnd *>);
fromData->callSequence->CopyTo(alloc, *(this->callSequence));
}
else
{
this->callSequence = nullptr;
}
this->startCallCount = fromData->startCallCount;
this->argOutCount = fromData->argOutCount;
this->totalOutParamCount = fromData->totalOutParamCount;
this->inlinedArgOutSize = fromData->inlinedArgOutSize;
this->hasCSECandidates = fromData->hasCSECandidates;
// Although we don't need the data on loop pre pass, we need to do it for the loop header
// because we capture the loop header bailout on loop prepass
if (fromData->stackLiteralInitFldDataMap != nullptr &&
(!this->globOpt->IsLoopPrePass() || (toBlockContext->isLoopHeader && toBlockContext->loop == this->globOpt->rootLoopPrePass)))
{
this->stackLiteralInitFldDataMap = fromData->stackLiteralInitFldDataMap->Clone();
}
else
{
this->stackLiteralInitFldDataMap = nullptr;
}
this->changedSyms = JitAnew(alloc, BVSparse<JitArenaAllocator>, alloc);
this->changedSyms->Copy(fromData->changedSyms);
this->capturedValues = fromData->capturedValues;
if (this->capturedValues)
{
this->capturedValues->IncrementRefCount();
}
Assert(fromData->HasData());
this->OnDataInitialized(alloc);
}
void GlobOptBlockData::RemoveUnavailableCandidates(PRECandidates * candidates)
{
// In case of multiple back-edges to the loop, make sure the candidates are still valid.
FOREACH_SLIST_ENTRY_EDITING(GlobHashBucket*, candidate, candidates->candidatesList, iter)
{
Value *candidateValue = candidate->element;
PropertySym *candidatePropertySym = candidate->value->AsPropertySym();
ValueNumber valueNumber = candidateValue->GetValueNumber();
Sym *symStore = candidateValue->GetValueInfo()->GetSymStore();
Value *blockValue = this->FindValue(candidatePropertySym);
if (blockValue && blockValue->GetValueNumber() == valueNumber
&& blockValue->GetValueInfo()->GetSymStore() == symStore)
{
Value *symStoreValue = this->FindValue(symStore);
if (symStoreValue && symStoreValue->GetValueNumber() == valueNumber)
{
continue;
}
}
iter.RemoveCurrent();
Assert(candidates->candidatesToProcess->Test(candidatePropertySym->m_id));
candidates->candidatesToProcess->Clear(candidatePropertySym->m_id);
} NEXT_SLIST_ENTRY_EDITING;
}
template <typename CapturedList, typename CapturedItemsAreEqual>
void
GlobOptBlockData::MergeCapturedValues(
SListBase<CapturedList> * toList,
SListBase<CapturedList> * fromList,
CapturedItemsAreEqual itemsAreEqual)
{
typename SListBase<CapturedList>::Iterator iterTo(toList);
typename SListBase<CapturedList>::Iterator iterFrom(fromList);
bool hasTo = iterTo.Next();
bool hasFrom = fromList == nullptr ? false : iterFrom.Next();
// to be conservative, only copy the captured value for common sym Ids
// in from and to CapturedList, mark all non-common sym Ids for re-capture
while (hasFrom && hasTo)
{
Sym * symFrom = iterFrom.Data().Key();
Sym * symTo = iterTo.Data().Key();
if (symFrom->m_id < symTo->m_id)
{
this->changedSyms->Set(symFrom->m_id);
hasFrom = iterFrom.Next();
}
else if(symFrom->m_id > symTo->m_id)
{
this->changedSyms->Set(symTo->m_id);
hasTo = iterTo.Next();
}
else
{
if (!itemsAreEqual(&iterFrom.Data(), &iterTo.Data()))
{
this->changedSyms->Set(symTo->m_id);
}
hasFrom = iterFrom.Next();
hasTo = iterTo.Next();
}
}
bool hasRemain = hasFrom || hasTo;
if (hasRemain)
{
typename SListBase<CapturedList>::Iterator iterRemain(hasFrom ? iterFrom : iterTo);
do
{
Sym * symRemain = iterRemain.Data().Key();
this->changedSyms->Set(symRemain->m_id);
hasRemain = iterRemain.Next();
} while (hasRemain);
}
}
void
GlobOptBlockData::MergeBlockData(
BasicBlock *toBlock,
BasicBlock *fromBlock,
BVSparse<JitArenaAllocator> *const symsRequiringCompensation,
BVSparse<JitArenaAllocator> *const symsCreatedForMerge,
bool forceTypeSpecOnLoopHeader)
{
GlobOptBlockData *fromData = &(fromBlock->globOptData);
if(this->globOpt->DoBoundCheckHoist())
{
// Do this before merging values so that it can see whether a sym's value was changed on one side or the other
this->globOpt->MergeBoundCheckHoistBlockData(toBlock, this, fromBlock, fromData);
}
bool isLoopBackEdge = toBlock->isLoopHeader;
this->MergeValueMaps(toBlock, fromBlock, symsRequiringCompensation, symsCreatedForMerge);
this->globOpt->InsertCloneStrs(toBlock, this, fromData);
this->liveFields->And(fromData->liveFields);
this->liveArrayValues->And(fromData->liveArrayValues);
this->isTempSrc->And(fromData->isTempSrc);
this->hasCSECandidates &= fromData->hasCSECandidates;
this->changedSyms->Or(fromData->changedSyms);
if (this->capturedValues == nullptr)
{
this->capturedValues = fromData->capturedValues;
if (this->capturedValues)
{
this->capturedValues->IncrementRefCount();
}
}
else
{
this->MergeCapturedValues(
&this->capturedValues->constantValues,
fromData->capturedValues == nullptr ? nullptr : &fromData->capturedValues->constantValues,
[&](ConstantStackSymValue * symValueFrom, ConstantStackSymValue * symValueTo)
{
Assert(symValueFrom->Key()->m_id == symValueTo->Key()->m_id);
return symValueFrom->Value().IsEqual(symValueTo->Value());
});
this->MergeCapturedValues(
&this->capturedValues->copyPropSyms,
fromData->capturedValues == nullptr ? nullptr : &fromData->capturedValues->copyPropSyms,
[&](CopyPropSyms * copyPropSymFrom, CopyPropSyms * copyPropSymTo)
{
Assert(copyPropSymFrom->Key()->m_id == copyPropSymTo->Key()->m_id);
if (copyPropSymFrom->Value()->m_id == copyPropSymTo->Value()->m_id)
{
Value * fromVal = fromData->FindValue(copyPropSymFrom->Key());
Value * toVal = this->FindValue(copyPropSymFrom->Key());
return fromVal && toVal && fromVal->IsEqualTo(toVal);
}
return false;
});
}
if (fromData->maybeWrittenTypeSyms)
{
if (this->maybeWrittenTypeSyms == nullptr)
{
this->maybeWrittenTypeSyms = JitAnew(this->globOpt->alloc, BVSparse<JitArenaAllocator>, this->globOpt->alloc);
this->maybeWrittenTypeSyms->Copy(fromData->maybeWrittenTypeSyms);
}
else
{
this->maybeWrittenTypeSyms->Or(fromData->maybeWrittenTypeSyms);
}
}
{
// - Keep the var sym live if any of the following is true:
// - The var sym is live on both sides
// - The var sym is the only live sym that contains the lossless value of the sym on a side (that is, the lossless
// int32 sym is not live, and the float64 sym is not live on that side), and the sym of any type is live on the
// other side
// - On a side, the var and float64 syms are live, the lossless int32 sym is not live, the sym's merged value is
// likely int, and the sym of any type is live on the other side. Since the value is likely int, it may be
// int-specialized (with lossless conversion) later. Keeping only the float64 sym live requires doing a lossless
// conversion from float64 to int32, with bailout if the value of the float is not a true 32-bit integer. Checking
// that is costly, and if the float64 sym is converted back to var, it does not become a tagged int, causing a
// guaranteed bailout if a lossless conversion to int happens later. Keep the var sym live to preserve its
// tagged-ness so that it can be int-specialized while avoiding unnecessary bailouts.
// - Keep the int32 sym live if it's live on both sides
// - Mark the sym as lossy if it's lossy on any side
// - Keep the float64 sym live if it's live on a side and the sym of a specialized lossless type is live on the other
// side
//
// fromData.temp =
// (fromData.var - (fromData.int32 - fromData.lossyInt32)) &
// (this.var | this.int32 | this.float64)
// this.temp =
// (this.var - (this.int32 - this.lossyInt32)) &
// (fromData.var | fromData.int32 | fromData.float64)
// this.var =
// (fromData.var & this.var) |
// (fromData.temp - fromData.float64) |
// (this.temp - this.float64) |
// (fromData.temp & fromData.float64 | this.temp & this.float64) & (value ~ int)
//
// this.float64 =
// fromData.float64 & ((this.int32 - this.lossyInt32) | this.float64) |
// this.float64 & ((fromData.int32 - fromData.lossyInt32) | fromData.float64)
// this.int32 &= fromData.int32
// this.lossyInt32 = (fromData.lossyInt32 | this.lossyInt32) & this.int32
BVSparse<JitArenaAllocator> tempBv1(this->globOpt->tempAlloc);
BVSparse<JitArenaAllocator> tempBv2(this->globOpt->tempAlloc);
if (isLoopBackEdge && forceTypeSpecOnLoopHeader)
{
Loop *const loop = toBlock->loop;
// Force to lossless int32:
// forceLosslessInt32 =
// ((fromData.int32 - fromData.lossyInt32) - (this.int32 - this.lossyInt32)) &
// loop.likelyIntSymsUsedBeforeDefined &
// this.var
tempBv1.Minus(fromData->liveInt32Syms, fromData->liveLossyInt32Syms);
tempBv2.Minus(this->liveInt32Syms, this->liveLossyInt32Syms);
tempBv1.Minus(&tempBv2);
tempBv1.And(loop->likelyIntSymsUsedBeforeDefined);
tempBv1.And(this->liveVarSyms);
this->liveInt32Syms->Or(&tempBv1);
this->liveLossyInt32Syms->Minus(&tempBv1);
if(this->globOpt->DoLossyIntTypeSpec())
{
// Force to lossy int32:
// forceLossyInt32 = (fromData.int32 - this.int32) & loop.symsUsedBeforeDefined & this.var
tempBv1.Minus(fromData->liveInt32Syms, this->liveInt32Syms);
tempBv1.And(loop->symsUsedBeforeDefined);
tempBv1.And(this->liveVarSyms);
this->liveInt32Syms->Or(&tempBv1);
this->liveLossyInt32Syms->Or(&tempBv1);
}
// Force to float64:
// forceFloat64 =
// fromData.float64 & loop.forceFloat64 |
// (fromData.float64 - this.float64) & loop.likelyNumberSymsUsedBeforeDefined
tempBv1.And(fromData->liveFloat64Syms, loop->forceFloat64SymsOnEntry);
this->liveFloat64Syms->Or(&tempBv1);
tempBv1.Minus(fromData->liveFloat64Syms, this->liveFloat64Syms);
tempBv1.And(loop->likelyNumberSymsUsedBeforeDefined);
this->liveFloat64Syms->Or(&tempBv1);
}
{
BVSparse<JitArenaAllocator> tempBv3(this->globOpt->tempAlloc);
// fromData.temp =
// (fromData.var - (fromData.int32 - fromData.lossyInt32)) &
// (this.var | this.int32 | this.float64)
tempBv2.Minus(fromData->liveInt32Syms, fromData->liveLossyInt32Syms);
tempBv1.Minus(fromData->liveVarSyms, &tempBv2);
tempBv2.Or(this->liveVarSyms, this->liveInt32Syms);
tempBv2.Or(this->liveFloat64Syms);
tempBv1.And(&tempBv2);
// this.temp =
// (this.var - (this.int32 - this.lossyInt32)) &
// (fromData.var | fromData.int32 | fromData.float64)
tempBv3.Minus(this->liveInt32Syms, this->liveLossyInt32Syms);
tempBv2.Minus(this->liveVarSyms, &tempBv3);
tempBv3.Or(fromData->liveVarSyms, fromData->liveInt32Syms);
tempBv3.Or(fromData->liveFloat64Syms);
tempBv2.And(&tempBv3);
{
BVSparse<JitArenaAllocator> tempBv4(this->globOpt->tempAlloc);
// fromData.temp & fromData.float64 | this.temp & this.float64
tempBv3.And(&tempBv1, fromData->liveFloat64Syms);
tempBv4.And(&tempBv2, this->liveFloat64Syms);
tempBv3.Or(&tempBv4);
}
// (fromData.temp - fromData.float64) |
// (this.temp - this.float64)
tempBv1.Minus(fromData->liveFloat64Syms);
tempBv2.Minus(this->liveFloat64Syms);
tempBv1.Or(&tempBv2);
// this.var =
// (fromData.var & this.var) |
// (fromData.temp - fromData.float64) |
// (this.temp - this.float64)
this->liveVarSyms->And(fromData->liveVarSyms);
this->liveVarSyms->Or(&tempBv1);
// this.var |=
// (fromData.temp & fromData.float64 | this.temp & this.float64) & (value ~ int)
FOREACH_BITSET_IN_SPARSEBV(id, &tempBv3)
{
StackSym *const stackSym = this->globOpt->func->m_symTable->FindStackSym(id);
Assert(stackSym);
Value *const value = this->FindValue(stackSym);
if(value)
{
ValueInfo *const valueInfo = value->GetValueInfo();
if(valueInfo->IsInt() || (valueInfo->IsLikelyInt() && this->globOpt->DoAggressiveIntTypeSpec()))
{
this->liveVarSyms->Set(id);
}
}
} NEXT_BITSET_IN_SPARSEBV;
}
// fromData.float64 & ((this.int32 - this.lossyInt32) | this.float64)
tempBv1.Minus(this->liveInt32Syms, this->liveLossyInt32Syms);
tempBv1.Or(this->liveFloat64Syms);
tempBv1.And(fromData->liveFloat64Syms);
// this.float64 & ((fromData.int32 - fromData.lossyInt32) | fromData.float64)
tempBv2.Minus(fromData->liveInt32Syms, fromData->liveLossyInt32Syms);
tempBv2.Or(fromData->liveFloat64Syms);
tempBv2.And(this->liveFloat64Syms);
// this.float64 =
// fromData.float64 & ((this.int32 - this.lossyInt32) | this.float64) |
// this.float64 & ((fromData.int32 - fromData.lossyInt32) | fromData.float64)
this->liveFloat64Syms->Or(&tempBv1, &tempBv2);
// this.int32 &= fromData.int32
// this.lossyInt32 = (fromData.lossyInt32 | this.lossyInt32) & this.int32
this->liveInt32Syms->And(fromData->liveInt32Syms);
this->liveLossyInt32Syms->Or(fromData->liveLossyInt32Syms);
this->liveLossyInt32Syms->And(this->liveInt32Syms);
}
if (this->globOpt->TrackArgumentsObject())
{
if (!this->argObjSyms->Equal(fromData->argObjSyms))
{
this->globOpt->CannotAllocateArgumentsObjectOnStack(nullptr);
}
}
if (fromData->maybeTempObjectSyms && !fromData->maybeTempObjectSyms->IsEmpty())
{
if (this->maybeTempObjectSyms)
{
this->maybeTempObjectSyms->Or(fromData->maybeTempObjectSyms);
}
else
{
this->maybeTempObjectSyms = fromData->maybeTempObjectSyms->CopyNew(this->globOpt->alloc);
}
if (fromData->canStoreTempObjectSyms && !fromData->canStoreTempObjectSyms->IsEmpty())
{
if (this->canStoreTempObjectSyms)
{
// Both need to be temp object
this->canStoreTempObjectSyms->And(fromData->canStoreTempObjectSyms);
}
}
else if (this->canStoreTempObjectSyms)
{
this->canStoreTempObjectSyms->ClearAll();
}
}
else
{
Assert(!fromData->canStoreTempObjectSyms || fromData->canStoreTempObjectSyms->IsEmpty());
if (this->canStoreTempObjectSyms)
{
this->canStoreTempObjectSyms->ClearAll();
}
}
Assert(this->curFunc == fromData->curFunc);
Assert((this->callSequence == nullptr && fromData->callSequence == nullptr) || this->callSequence->Equals(*(fromData->callSequence)));
Assert(this->startCallCount == fromData->startCallCount);
Assert(this->argOutCount == fromData->argOutCount);
Assert(this->totalOutParamCount == fromData->totalOutParamCount);
Assert(this->inlinedArgOutSize == fromData->inlinedArgOutSize);
// stackLiteralInitFldDataMap is a union of the stack literal from two path.
// Although we don't need the data on loop prepass, we need to do it for the loop header
// because we capture the loop header bailout on loop prepass.
if (fromData->stackLiteralInitFldDataMap != nullptr &&
(!this->globOpt->IsLoopPrePass() || (toBlock->isLoopHeader && toBlock->loop == this->globOpt->rootLoopPrePass)))
{
if (this->stackLiteralInitFldDataMap == nullptr)
{
this->stackLiteralInitFldDataMap = fromData->stackLiteralInitFldDataMap->Clone();
}
else
{
StackLiteralInitFldDataMap * toMap = this->stackLiteralInitFldDataMap;
fromData->stackLiteralInitFldDataMap->Map([toMap](StackSym * stackSym, StackLiteralInitFldData const& data)
{
if (toMap->AddNew(stackSym, data) == -1)
{
// If there is an existing data for the stackSym, both path should match
DebugOnly(StackLiteralInitFldData const * currentData);
Assert(toMap->TryGetReference(stackSym, &currentData));
Assert(currentData->currentInitFldCount == data.currentInitFldCount);
Assert(currentData->propIds == data.propIds);
}
});
}
}
}
void
GlobOptBlockData::MergeValueMaps(
BasicBlock *toBlock,
BasicBlock *fromBlock,
BVSparse<JitArenaAllocator> *const symsRequiringCompensation,
BVSparse<JitArenaAllocator> *const symsCreatedForMerge)
{
GlobOptBlockData *fromData = &(fromBlock->globOptData);
bool isLoopBackEdge = toBlock->isLoopHeader;
Loop *loop = toBlock->loop;
bool isLoopPrepass = (loop && this->globOpt->prePassLoop == loop);
Assert(this->globOpt->valuesCreatedForMerge->Count() == 0);
DebugOnly(ValueSetByValueNumber mergedValues(this->globOpt->tempAlloc, 64));
BVSparse<JitArenaAllocator> *const mergedValueTypesTrackedForKills = this->globOpt->tempBv;
Assert(mergedValueTypesTrackedForKills->IsEmpty());
this->valuesToKillOnCalls->Clear(); // the tracking will be reevaluated based on merged value types
GlobHashTable *thisTable = this->symToValueMap;
GlobHashTable *otherTable = fromData->symToValueMap;
for (uint i = 0; i < thisTable->tableSize; i++)
{
SListBase<GlobHashBucket>::Iterator iter2(&otherTable->table[i]);
iter2.Next();
FOREACH_SLISTBASE_ENTRY_EDITING(GlobHashBucket, bucket, &thisTable->table[i], iter)
{
while (iter2.IsValid() && bucket.value->m_id < iter2.Data().value->m_id)
{
iter2.Next();
}
Value *newValue = nullptr;
if (iter2.IsValid() && bucket.value->m_id == iter2.Data().value->m_id)
{
newValue =
this->MergeValues(
bucket.element,
iter2.Data().element,
iter2.Data().value,
isLoopBackEdge,
symsRequiringCompensation,
symsCreatedForMerge);
}
if (newValue == nullptr)
{
iter.RemoveCurrent(thisTable->alloc);
continue;
}
else
{
#if DBG
// Ensure that only one value per value number is produced by merge. Byte-code constant values are reused in
// multiple blocks without cloning, so exclude those value numbers.
{
Value *const previouslyMergedValue = mergedValues.Lookup(newValue->GetValueNumber());
if (previouslyMergedValue)
{
if (!this->globOpt->byteCodeConstantValueNumbersBv->Test(newValue->GetValueNumber()))
{
Assert(newValue == previouslyMergedValue);
}
}
else
{
mergedValues.Add(newValue);
}
}
#endif
this->globOpt->TrackMergedValueForKills(newValue, this, mergedValueTypesTrackedForKills);
bucket.element = newValue;
}
iter2.Next();
} NEXT_SLISTBASE_ENTRY_EDITING;
if (isLoopPrepass && !this->globOpt->rootLoopPrePass->allFieldsKilled)
{
while (iter2.IsValid())
{
iter2.Next();
}
}
}
this->globOpt->valuesCreatedForMerge->Clear();
DebugOnly(mergedValues.Reset());
mergedValueTypesTrackedForKills->ClearAll();
this->exprToValueMap->And(fromData->exprToValueMap);
this->globOpt->ProcessValueKills(toBlock, this);
bool isLastLoopBackEdge = false;
if (isLoopBackEdge)
{
this->globOpt->ProcessValueKillsForLoopHeaderAfterBackEdgeMerge(toBlock, this);
BasicBlock *lastBlock = nullptr;
FOREACH_PREDECESSOR_BLOCK(pred, toBlock)
{
Assert(!lastBlock || pred->GetBlockNum() > lastBlock->GetBlockNum());
lastBlock = pred;
}NEXT_PREDECESSOR_BLOCK;
isLastLoopBackEdge = (lastBlock == fromBlock);
}
}
Value *
GlobOptBlockData::MergeValues(
Value *toDataValue,
Value *fromDataValue,
Sym *fromDataSym,
bool isLoopBackEdge,
BVSparse<JitArenaAllocator> *const symsRequiringCompensation,
BVSparse<JitArenaAllocator> *const symsCreatedForMerge)
{
// Same map
if (toDataValue == fromDataValue)
{
return toDataValue;
}
const ValueNumberPair sourceValueNumberPair(toDataValue->GetValueNumber(), fromDataValue->GetValueNumber());
const bool sameValueNumber = sourceValueNumberPair.First() == sourceValueNumberPair.Second();
ValueInfo *newValueInfo =
this->MergeValueInfo(
toDataValue,
fromDataValue,
fromDataSym,
isLoopBackEdge,
sameValueNumber,
symsRequiringCompensation,
symsCreatedForMerge);
if (newValueInfo == nullptr)
{
return nullptr;
}
if (sameValueNumber && newValueInfo == toDataValue->GetValueInfo())
{
return toDataValue;
}
// There may be other syms in toData that haven't been merged yet, referring to the current toData value for this sym. If
// the merge produced a new value info, don't corrupt the value info for the other sym by changing the same value. Instead,
// create one value per source value number pair per merge and reuse that for new value infos.
Value *newValue = this->globOpt->valuesCreatedForMerge->Lookup(sourceValueNumberPair, nullptr);
if(newValue)
{
Assert(sameValueNumber == (newValue->GetValueNumber() == toDataValue->GetValueNumber()));
// This is an exception where Value::SetValueInfo is called directly instead of GlobOpt::ChangeValueInfo, because we're
// actually generating new value info through merges.
newValue->SetValueInfo(newValueInfo);
}
else
{
newValue = this->globOpt->NewValue(sameValueNumber ? sourceValueNumberPair.First() : this->globOpt->NewValueNumber(), newValueInfo);
this->globOpt->valuesCreatedForMerge->Add(sourceValueNumberPair, newValue);
}
// Set symStore if same on both paths.
if (toDataValue->GetValueInfo()->GetSymStore() == fromDataValue->GetValueInfo()->GetSymStore())
{
this->globOpt->SetSymStoreDirect(newValueInfo, toDataValue->GetValueInfo()->GetSymStore());
}
return newValue;
}
ValueInfo *
GlobOptBlockData::MergeValueInfo(
Value *toDataVal,
Value *fromDataVal,
Sym *fromDataSym,
bool isLoopBackEdge,
bool sameValueNumber,
BVSparse<JitArenaAllocator> *const symsRequiringCompensation,
BVSparse<JitArenaAllocator> *const symsCreatedForMerge)
{
ValueInfo *const toDataValueInfo = toDataVal->GetValueInfo();
ValueInfo *const fromDataValueInfo = fromDataVal->GetValueInfo();
if (toDataValueInfo->IsJsType() || fromDataValueInfo->IsJsType())
{
Assert(toDataValueInfo->IsJsType() && fromDataValueInfo->IsJsType());
return this->MergeJsTypeValueInfo(toDataValueInfo->AsJsType(), fromDataValueInfo->AsJsType(), isLoopBackEdge, sameValueNumber);
}
// Same value
if (toDataValueInfo == fromDataValueInfo)
{
return toDataValueInfo;
}
ValueType newValueType(toDataValueInfo->Type().Merge(fromDataValueInfo->Type()));
if (newValueType.IsLikelyInt())
{
return ValueInfo::MergeLikelyIntValueInfo(this->globOpt->alloc, toDataVal, fromDataVal, newValueType);
}
if(newValueType.IsLikelyAnyOptimizedArray())
{
if(newValueType.IsLikelyArrayOrObjectWithArray() &&
toDataValueInfo->IsLikelyArrayOrObjectWithArray() &&
fromDataValueInfo->IsLikelyArrayOrObjectWithArray())
{
// Value type merge for missing values is aggressive by default (for profile data) - if either side likely has no
// missing values, then the merged value type also likely has no missing values. This is because arrays often start
// off having missing values but are eventually filled up. In GlobOpt however, we need to be conservative because
// the existence of a value type that likely has missing values indicates that it is more likely for it to have
// missing values than not. Also, StElems that are likely to create missing values are tracked in profile data and
// will update value types to say they are now likely to have missing values, and that needs to be propagated
// conservatively.
newValueType =
newValueType.SetHasNoMissingValues(
toDataValueInfo->HasNoMissingValues() && fromDataValueInfo->HasNoMissingValues());
if(toDataValueInfo->HasIntElements() != fromDataValueInfo->HasIntElements() ||
toDataValueInfo->HasFloatElements() != fromDataValueInfo->HasFloatElements())
{
// When merging arrays with different native storage types, make the merged value type a likely version to force
// array checks to be done again and cause a conversion and/or bailout as necessary
newValueType = newValueType.ToLikely();
}
}
if(!(newValueType.IsObject() && toDataValueInfo->IsArrayValueInfo() && fromDataValueInfo->IsArrayValueInfo()))
{
return ValueInfo::New(this->globOpt->alloc, newValueType);
}
return
this->MergeArrayValueInfo(
newValueType,
toDataValueInfo->AsArrayValueInfo(),
fromDataValueInfo->AsArrayValueInfo(),
fromDataSym,
symsRequiringCompensation,
symsCreatedForMerge);
}
// Consider: If both values are VarConstantValueInfo with the same value, we could
// merge them preserving the value.
return ValueInfo::New(this->globOpt->alloc, newValueType);
}
JsTypeValueInfo*
GlobOptBlockData::MergeJsTypeValueInfo(JsTypeValueInfo * toValueInfo, JsTypeValueInfo * fromValueInfo, bool isLoopBackEdge, bool sameValueNumber)
{
// On loop back edges we must be conservative and only consider type values which are invariant throughout the loop.
// That's because in dead store pass we can't correctly track object pointer assignments (o = p), and we may not
// be able to register correct type checks for the right properties upstream. If we ever figure out how to enhance
// the dead store pass to track this info we could go more aggressively, as below.
if (isLoopBackEdge && !sameValueNumber)
{
return nullptr;
}
if (toValueInfo == fromValueInfo)
{
return toValueInfo;
}
const JITTypeHolder toType = toValueInfo->GetJsType();
const JITTypeHolder fromType = fromValueInfo->GetJsType();
const JITTypeHolder mergedType = toType == fromType ? toType : JITTypeHolder(nullptr);
Js::EquivalentTypeSet* toTypeSet = toValueInfo->GetJsTypeSet();
Js::EquivalentTypeSet* fromTypeSet = fromValueInfo->GetJsTypeSet();
Js::EquivalentTypeSet* mergedTypeSet = (toTypeSet != nullptr && fromTypeSet != nullptr && Js::EquivalentTypeSet::AreIdentical(toTypeSet, fromTypeSet)) ? toTypeSet : nullptr;
#if DBG_DUMP
if (PHASE_TRACE(Js::ObjTypeSpecPhase, this->globOpt->func) || PHASE_TRACE(Js::EquivObjTypeSpecPhase, this->globOpt->func))
{
Output::Print(_u("ObjTypeSpec: Merging type value info:\n"));
Output::Print(_u(" from (shared %d): "), fromValueInfo->GetIsShared());
fromValueInfo->Dump();
Output::Print(_u("\n to (shared %d): "), toValueInfo->GetIsShared());
toValueInfo->Dump();
}
#endif
if (mergedType == toType && mergedTypeSet == toTypeSet)
{
#if DBG_DUMP
if (PHASE_TRACE(Js::ObjTypeSpecPhase, this->globOpt->func) || PHASE_TRACE(Js::EquivObjTypeSpecPhase, this->globOpt->func))
{
Output::Print(_u("\n result (shared %d): "), toValueInfo->GetIsShared());
toValueInfo->Dump();
Output::Print(_u("\n"));
}
#endif
return toValueInfo;
}
if (mergedType == nullptr && mergedTypeSet == nullptr)
{
// No info, so don't bother making a value.
return nullptr;
}
if (toValueInfo->GetIsShared())
{
JsTypeValueInfo* mergedValueInfo = JsTypeValueInfo::New(this->globOpt->alloc, mergedType, mergedTypeSet);
#if DBG_DUMP
if (PHASE_TRACE(Js::ObjTypeSpecPhase, this->globOpt->func) || PHASE_TRACE(Js::EquivObjTypeSpecPhase, this->globOpt->func))
{
Output::Print(_u("\n result (shared %d): "), mergedValueInfo->GetIsShared());
mergedValueInfo->Dump();
Output::Print(_u("\n"));
}
#endif
return mergedValueInfo;
}
else
{
toValueInfo->SetJsType(mergedType);
toValueInfo->SetJsTypeSet(mergedTypeSet);
#if DBG_DUMP
if (PHASE_TRACE(Js::ObjTypeSpecPhase, this->globOpt->func) || PHASE_TRACE(Js::EquivObjTypeSpecPhase, this->globOpt->func))
{
Output::Print(_u("\n result (shared %d): "), toValueInfo->GetIsShared());
toValueInfo->Dump();
Output::Print(_u("\n"));
}
#endif
return toValueInfo;
}
}
ValueInfo *GlobOptBlockData::MergeArrayValueInfo(
const ValueType mergedValueType,
const ArrayValueInfo *const toDataValueInfo,
const ArrayValueInfo *const fromDataValueInfo,
Sym *const arraySym,
BVSparse<JitArenaAllocator> *const symsRequiringCompensation,
BVSparse<JitArenaAllocator> *const symsCreatedForMerge)
{
Assert(mergedValueType.IsAnyOptimizedArray());
Assert(toDataValueInfo);
Assert(fromDataValueInfo);
Assert(toDataValueInfo != fromDataValueInfo);
Assert(arraySym);
Assert(!symsRequiringCompensation == this->globOpt->IsLoopPrePass());
Assert(!symsCreatedForMerge == this->globOpt->IsLoopPrePass());
// Merge the segment and segment length syms. If we have the segment and/or the segment length syms available on both sides
// but in different syms, create a new sym and record that the array sym requires compensation. Compensation will be
// inserted later to initialize this new sym from all predecessors of the merged block.
StackSym *newHeadSegmentSym = nullptr;
if(toDataValueInfo->HeadSegmentSym() && fromDataValueInfo->HeadSegmentSym())
{
if(toDataValueInfo->HeadSegmentSym() == fromDataValueInfo->HeadSegmentSym())
{
newHeadSegmentSym = toDataValueInfo->HeadSegmentSym();
}
else
{
if (!this->globOpt->IsLoopPrePass())
{
// Adding compensation code in the prepass won't help, as the symstores would again be different in the main pass.
Assert(symsRequiringCompensation);
symsRequiringCompensation->Set(arraySym->m_id);
Assert(symsCreatedForMerge);
if (symsCreatedForMerge->Test(toDataValueInfo->HeadSegmentSym()->m_id))
{
newHeadSegmentSym = toDataValueInfo->HeadSegmentSym();
}
else
{
newHeadSegmentSym = StackSym::New(TyMachPtr, this->globOpt->func);
symsCreatedForMerge->Set(newHeadSegmentSym->m_id);
}
}
}
}
StackSym *newHeadSegmentLengthSym = nullptr;
if(toDataValueInfo->HeadSegmentLengthSym() && fromDataValueInfo->HeadSegmentLengthSym())
{
if(toDataValueInfo->HeadSegmentLengthSym() == fromDataValueInfo->HeadSegmentLengthSym())
{
newHeadSegmentLengthSym = toDataValueInfo->HeadSegmentLengthSym();
}
else
{
if (!this->globOpt->IsLoopPrePass())
{
Assert(symsRequiringCompensation);
symsRequiringCompensation->Set(arraySym->m_id);
Assert(symsCreatedForMerge);
if (symsCreatedForMerge->Test(toDataValueInfo->HeadSegmentLengthSym()->m_id))
{
newHeadSegmentLengthSym = toDataValueInfo->HeadSegmentLengthSym();
}
else
{
newHeadSegmentLengthSym = StackSym::New(TyUint32, this->globOpt->func);
symsCreatedForMerge->Set(newHeadSegmentLengthSym->m_id);
}
}
}
}
StackSym *newLengthSym = nullptr;
if(toDataValueInfo->LengthSym() && fromDataValueInfo->LengthSym())
{
if(toDataValueInfo->LengthSym() == fromDataValueInfo->LengthSym())
{
newLengthSym = toDataValueInfo->LengthSym();
}
else
{
if (!this->globOpt->IsLoopPrePass())
{
Assert(symsRequiringCompensation);
symsRequiringCompensation->Set(arraySym->m_id);
Assert(symsCreatedForMerge);
if (symsCreatedForMerge->Test(toDataValueInfo->LengthSym()->m_id))
{
newLengthSym = toDataValueInfo->LengthSym();
}
else
{
newLengthSym = StackSym::New(TyUint32, this->globOpt->func);
symsCreatedForMerge->Set(newLengthSym->m_id);
}
}
}
}
if(newHeadSegmentSym || newHeadSegmentLengthSym || newLengthSym)
{
return ArrayValueInfo::New(this->globOpt->alloc, mergedValueType, newHeadSegmentSym, newHeadSegmentLengthSym, newLengthSym);
}
if(symsRequiringCompensation)
{
symsRequiringCompensation->Clear(arraySym->m_id);
}
return ValueInfo::New(this->globOpt->alloc, mergedValueType);
}
void
GlobOptBlockData::TrackArgumentsSym(IR::RegOpnd const* opnd)
{
if(!this->curFunc->argObjSyms)
{
this->curFunc->argObjSyms = JitAnew(this->globOpt->alloc, BVSparse<JitArenaAllocator>, this->globOpt->alloc);
}
this->curFunc->argObjSyms->Set(opnd->m_sym->m_id);
this->argObjSyms->Set(opnd->m_sym->m_id);
#ifdef ENABLE_DEBUG_CONFIG_OPTIONS
if (PHASE_TESTTRACE(Js::StackArgOptPhase, this->globOpt->func))
{
char16 debugStringBuffer[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
char16 debugStringBuffer2[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE];
Output::Print(_u("Created a new alias s%d for arguments object in function %s(%s) topFunc %s(%s)\n"),
opnd->m_sym->m_id,
this->curFunc->GetJITFunctionBody()->GetDisplayName(),
this->curFunc->GetDebugNumberSet(debugStringBuffer),
this->globOpt->func->GetJITFunctionBody()->GetDisplayName(),
this->globOpt->func->GetDebugNumberSet(debugStringBuffer2)
);
Output::Flush();
}
#endif
}
void
GlobOptBlockData::ClearArgumentsSym(IR::RegOpnd const* opnd)
{
// We blindly clear so need to check func has argObjSyms
if (this->curFunc->argObjSyms)
{
this->curFunc->argObjSyms->Clear(opnd->m_sym->m_id);
}
this->argObjSyms->Clear(opnd->m_sym->m_id);
}
BOOL
GlobOptBlockData::TestAnyArgumentsSym() const
{
return this->argObjSyms->TestEmpty();
}
BOOL
GlobOptBlockData::IsArgumentsSymID(SymID id) const
{
return this->argObjSyms->Test(id);
}
BOOL
GlobOptBlockData::IsArgumentsOpnd(IR::Opnd const* opnd) const
{
SymID id = 0;
if (opnd->IsRegOpnd())
{
id = opnd->AsRegOpnd()->m_sym->m_id;
return this->IsArgumentsSymID(id);
}
else if (opnd->IsSymOpnd())
{
Sym const *sym = opnd->AsSymOpnd()->m_sym;
if (sym && sym->IsPropertySym())
{
PropertySym const *propertySym = sym->AsPropertySym();
id = propertySym->m_stackSym->m_id;
return this->IsArgumentsSymID(id);
}
return false;
}
else if (opnd->IsIndirOpnd())
{
IR::RegOpnd const *indexOpnd = opnd->AsIndirOpnd()->GetIndexOpnd();
IR::RegOpnd const *baseOpnd = opnd->AsIndirOpnd()->GetBaseOpnd();
return this->IsArgumentsSymID(baseOpnd->m_sym->m_id) || (indexOpnd && this->IsArgumentsSymID(indexOpnd->m_sym->m_id));
}
AssertMsg(false, "Unknown type");
return false;
}
Value*
GlobOptBlockData::FindValue(Sym *sym)
{
Assert(this->symToValueMap);
if (sym->IsStackSym() && sym->AsStackSym()->IsTypeSpec())
{
sym = sym->AsStackSym()->GetVarEquivSym(this->globOpt->func);
}
else if (sym->IsPropertySym())
{
return this->FindPropertyValue(sym->m_id);
}
if (sym->IsStackSym() && sym->AsStackSym()->IsFromByteCodeConstantTable())
{
return this->globOpt->byteCodeConstantValueArray->Get(sym->m_id);
}
else
{
return FindValueFromMapDirect(sym->m_id);
}
}
Value *
GlobOptBlockData::FindPropertyValue(SymID symId)
{
Assert(this->globOpt->func->m_symTable->Find(symId)->IsPropertySym());
if (!this->liveFields->Test(symId))
{
return nullptr;
}
return FindValueFromMapDirect(symId);
}
Value *
GlobOptBlockData::FindObjectTypeValue(SymID typeSymId)
{
Assert(this->globOpt->func->m_symTable->Find(typeSymId)->IsStackSym());
if (!this->liveFields->Test(typeSymId))
{
return nullptr;
}
return FindObjectTypeValueNoLivenessCheck(typeSymId);
}
Value *
GlobOptBlockData::FindObjectTypeValueNoLivenessCheck(StackSym* typeSym)
{
return FindObjectTypeValueNoLivenessCheck(typeSym->m_id);
}
Value *
GlobOptBlockData::FindObjectTypeValueNoLivenessCheck(SymID typeSymId)
{
Value* value = this->FindValueFromMapDirect(typeSymId);
Assert(value == nullptr || value->GetValueInfo()->IsJsType());
return value;
}
Value *
GlobOptBlockData::FindObjectTypeValue(StackSym* typeSym)
{
return FindObjectTypeValue(typeSym->m_id);
}
Value *
GlobOptBlockData::FindFuturePropertyValue(PropertySym *const propertySym)
{
Assert(propertySym);
// Try a direct lookup based on this sym
Value *const value = this->FindValue(propertySym);
if(value)
{
return value;
}
if(PHASE_OFF(Js::CopyPropPhase, this->globOpt->func))
{
// Need to use copy-prop info to backtrack
return nullptr;
}
// Try to get the property object's value
StackSym *const objectSym = propertySym->m_stackSym;
Value *objectValue = this->FindValue(objectSym);
if(!objectValue)
{
if(!objectSym->IsSingleDef())
{
return nullptr;
}
switch(objectSym->m_instrDef->m_opcode)
{
case Js::OpCode::Ld_A:
case Js::OpCode::LdSlotArr:
case Js::OpCode::LdSlot:
// Allow only these op-codes for tracking the object sym's value transfer backwards. Other transfer op-codes
// could be included here if this function is used in scenarios that need them.
break;
default:
return nullptr;
}
// Try to get the property object's value from the src of the definition
IR::Opnd *const objectTransferSrc = objectSym->m_instrDef->GetSrc1();
if(!objectTransferSrc)
{
return nullptr;
}
if(objectTransferSrc->IsRegOpnd())
{
objectValue = this->FindValue(objectTransferSrc->AsRegOpnd()->m_sym);
}
else if(objectTransferSrc->IsSymOpnd())
{
Sym *const objectTransferSrcSym = objectTransferSrc->AsSymOpnd()->m_sym;
if(objectTransferSrcSym->IsStackSym())
{
objectValue = this->FindValue(objectTransferSrcSym);
}
else
{
// About to make a recursive call, so when jitting in the foreground, probe the stack
if(!this->globOpt->func->IsBackgroundJIT())
{
PROBE_STACK_NO_DISPOSE(this->globOpt->func->GetScriptContext(), Js::Constants::MinStackDefault);
}
objectValue = FindFuturePropertyValue(objectTransferSrcSym->AsPropertySym());
}
}
else
{
return nullptr;
}
if(!objectValue)
{
return nullptr;
}
}
// Try to use the property object's copy-prop sym and the property ID to find a mapped property sym, and get its value
StackSym *const objectCopyPropSym = this->GetCopyPropSym(nullptr, objectValue);
if(!objectCopyPropSym)
{
return nullptr;
}
PropertySym *const propertyCopyPropSym = PropertySym::Find(objectCopyPropSym->m_id, propertySym->m_propertyId, this->globOpt->func);
if(!propertyCopyPropSym)
{
return nullptr;
}
return this->FindValue(propertyCopyPropSym);
}
Value *
GlobOptBlockData::FindValueFromMapDirect(SymID symId)
{
Value ** valuePtr = this->symToValueMap->Get(symId);
if (valuePtr == nullptr)
{
return 0;
}
return (*valuePtr);
}
StackSym *
GlobOptBlockData::GetCopyPropSym(Sym * sym, Value * value)
{
ValueInfo *valueInfo = value->GetValueInfo();
Sym * copySym = valueInfo->GetSymStore();
if (!copySym)
{
return nullptr;
}
// Only copy prop stackSym, as a propertySym wouldn't improve anything.
// SingleDef info isn't flow sensitive, so make sure the symbol is actually live.
if (copySym->IsStackSym() && copySym != sym)
{
Assert(!copySym->AsStackSym()->IsTypeSpec());
Value *copySymVal = this->FindValue(valueInfo->GetSymStore());
if (copySymVal && copySymVal->GetValueNumber() == value->GetValueNumber())
{
if (valueInfo->IsVarConstant() && !this->IsLive(copySym))
{
// Because the addrConstantToValueMap isn't flow-based, the symStore of
// varConstants may not be live.
return nullptr;
}
return copySym->AsStackSym();
}
}
return nullptr;
}
void
GlobOptBlockData::ClearSymValue(Sym* sym)
{
this->symToValueMap->Clear(sym->m_id);
}
void
GlobOptBlockData::MarkTempLastUse(IR::Instr *instr, IR::RegOpnd *regOpnd)
{
if (OpCodeAttr::NonTempNumberSources(instr->m_opcode))
{
// Turn off bit if opcode could cause the src to be aliased.
this->isTempSrc->Clear(regOpnd->m_sym->m_id);
}
else if (this->isTempSrc->Test(regOpnd->m_sym->m_id))
{
// We just mark things that are temp in the globopt phase.
// The backwards phase will turn this off if it is not the last use.
// The isTempSrc is freed at the end of each block, which is why the backwards phase can't
// just use it.
if (!PHASE_OFF(Js::BackwardPhase, this->globOpt->func) && !this->globOpt->IsLoopPrePass())
{
regOpnd->m_isTempLastUse = true;
}
}
}
Value *
GlobOptBlockData::InsertNewValue(Value *val, IR::Opnd *opnd)
{
return this->SetValue(val, opnd);
}
void
GlobOptBlockData::SetChangedSym(SymID symId)
{
// this->currentBlock might not be the one which contain the changing symId,
// like hoisting invariant, but more changed symId is overly conservative and safe.
// symId in the hoisted to block is marked as JITOptimizedReg so it does't affect bailout.
if (this->changedSyms)
{
this->changedSyms->Set(symId);
if (this->capturedValuesCandidate != nullptr)
{
this->globOpt->changedSymsAfterIncBailoutCandidate->Set(symId);
}
}
// else could be hit only in MergeValues and it is handled by MergeCapturedValues
}
void
GlobOptBlockData::SetChangedSym(Sym* sym)
{
if (sym && sym->IsStackSym() && sym->AsStackSym()->HasByteCodeRegSlot())
{
SetChangedSym(sym->m_id);
}
}
void
GlobOptBlockData::SetValue(Value *val, Sym * sym)
{
ValueInfo *valueInfo = val->GetValueInfo();
sym = this->globOpt->SetSymStore(valueInfo, sym);
bool isStackSym = sym->IsStackSym();
if (isStackSym && sym->AsStackSym()->IsFromByteCodeConstantTable())
{
// Put the constants in a global array. This will minimize the per-block info.
this->globOpt->byteCodeConstantValueArray->Set(sym->m_id, val);
this->globOpt->byteCodeConstantValueNumbersBv->Set(val->GetValueNumber());
}
else
{
this->SetValueToHashTable(this->symToValueMap, val, sym);
this->SetChangedSym(sym);
}
}
Value *
GlobOptBlockData::SetValue(Value *val, IR::Opnd *opnd)
{
if (opnd)
{
Sym *sym;
switch (opnd->GetKind())
{
case IR::OpndKindSym:
sym = opnd->AsSymOpnd()->m_sym;
break;
case IR::OpndKindReg:
sym = opnd->AsRegOpnd()->m_sym;
break;
default:
sym = nullptr;
}
if (sym)
{
this->SetValue(val, sym);
}
}
return val;
}
void
GlobOptBlockData::SetValueToHashTable(GlobHashTable *valueNumberMap, Value *val, Sym *sym)
{
Value **pValue = valueNumberMap->FindOrInsertNew(sym);
*pValue = val;
}
void
GlobOptBlockData::MakeLive(StackSym *sym, const bool lossy)
{
Assert(sym);
Assert(this);
if(sym->IsTypeSpec())
{
const SymID varSymId = sym->GetVarEquivSym(this->globOpt->func)->m_id;
if(sym->IsInt32())
{
this->liveInt32Syms->Set(varSymId);
if(lossy)
{
this->liveLossyInt32Syms->Set(varSymId);
}
else
{
this->liveLossyInt32Syms->Clear(varSymId);
}
return;
}
if (sym->IsFloat64())
{
this->liveFloat64Syms->Set(varSymId);
return;
}
}
this->liveVarSyms->Set(sym->m_id);
}
bool
GlobOptBlockData::IsLive(Sym const * sym) const
{
sym = StackSym::GetVarEquivStackSym_NoCreate(sym);
return
sym &&
(
this->liveVarSyms->Test(sym->m_id) ||
this->liveInt32Syms->Test(sym->m_id) ||
this->liveFloat64Syms->Test(sym->m_id)
);
}
bool
GlobOptBlockData::IsTypeSpecialized(Sym const * sym) const
{
return this->IsInt32TypeSpecialized(sym) || this->IsFloat64TypeSpecialized(sym);
}
bool
GlobOptBlockData::IsSwitchInt32TypeSpecialized(IR::Instr const * instr) const
{
return GlobOpt::IsSwitchOptEnabledForIntTypeSpec(instr->m_func->GetTopFunc())
&& instr->GetSrc1()->IsRegOpnd()
&& this->IsInt32TypeSpecialized(instr->GetSrc1()->AsRegOpnd()->m_sym);
}
bool
GlobOptBlockData::IsInt32TypeSpecialized(Sym const * sym) const
{
sym = StackSym::GetVarEquivStackSym_NoCreate(sym);
return sym && this->liveInt32Syms->Test(sym->m_id) && !this->liveLossyInt32Syms->Test(sym->m_id);
}
bool
GlobOptBlockData::IsFloat64TypeSpecialized(Sym const * sym) const
{
sym = StackSym::GetVarEquivStackSym_NoCreate(sym);
return sym && this->liveFloat64Syms->Test(sym->m_id);
}
void
GlobOptBlockData::KillStateForGeneratorYield()
{
/*
TODO[generators][ianhall]: Do a ToVar on any typespec'd syms before the bailout so that we can enable typespec in generators without bailin having to restore typespec'd values
FOREACH_BITSET_IN_SPARSEBV(symId, this->liveInt32Syms)
{
this->ToVar(instr, , this->globOpt->currentBlock, , );
}
NEXT_BITSET_IN_SPARSEBV;
FOREACH_BITSET_IN_SPARSEBV(symId, this->liveInt32Syms)
{
this->ToVar(instr, , this->globOpt->currentBlock, , );
}
NEXT_BITSET_IN_SPARSEBV;
*/
FOREACH_GLOBHASHTABLE_ENTRY(bucket, this->symToValueMap)
{
ValueType type = bucket.element->GetValueInfo()->Type().ToLikely();
bucket.element = this->globOpt->NewGenericValue(type);
}
NEXT_GLOBHASHTABLE_ENTRY;
this->exprToValueMap->ClearAll();
this->liveFields->ClearAll();
this->liveArrayValues->ClearAll();
if (this->maybeWrittenTypeSyms)
{
this->maybeWrittenTypeSyms->ClearAll();
}
this->isTempSrc->ClearAll();
this->liveInt32Syms->ClearAll();
this->liveLossyInt32Syms->ClearAll();
this->liveFloat64Syms->ClearAll();
// Keep this->liveVarSyms as is
// Keep this->argObjSyms as is
// MarkTemp should be disabled for generator functions for now
Assert(this->maybeTempObjectSyms == nullptr || this->maybeTempObjectSyms->IsEmpty());
Assert(this->canStoreTempObjectSyms == nullptr || this->canStoreTempObjectSyms->IsEmpty());
this->valuesToKillOnCalls->Clear();
if (this->inductionVariables)
{
this->inductionVariables->Clear();
}
if (this->availableIntBoundChecks)
{
this->availableIntBoundChecks->Clear();
}
// Keep bailout data as is
this->hasCSECandidates = false;
}
#if DBG_DUMP
void
GlobOptBlockData::DumpSymToValueMap() const
{
if (this->symToValueMap != nullptr)
{
this->symToValueMap->Dump(GlobOptBlockData::DumpSym);
}
}
void
GlobOptBlockData::DumpSym(Sym *sym)
{
((Sym const*)sym)->Dump();
}
#endif