| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416 |
- //-------------------------------------------------------------------------------------------------------
- // 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"
- void
- GlobOpt::CaptureCopyPropValue(BasicBlock * block, Sym * sym, Value * val, SListBase<CopyPropSyms>::EditingIterator & bailOutCopySymsIter)
- {
- if (!sym->IsStackSym())
- {
- return;
- }
- StackSym * copyPropSym = block->globOptData.GetCopyPropSym(sym, val);
- if (copyPropSym != nullptr)
- {
- bailOutCopySymsIter.InsertNodeBefore(this->func->m_alloc, sym->AsStackSym(), copyPropSym);
- }
- }
- void
- GlobOpt::CaptureValuesFromScratch(BasicBlock * block,
- SListBase<ConstantStackSymValue>::EditingIterator & bailOutConstValuesIter,
- SListBase<CopyPropSyms>::EditingIterator & bailOutCopySymsIter)
- {
- Sym * sym = nullptr;
- Value * value = nullptr;
- ValueInfo * valueInfo = nullptr;
- block->globOptData.changedSyms->ClearAll();
- FOREACH_GLOBHASHTABLE_ENTRY(bucket, block->globOptData.symToValueMap)
- {
- value = bucket.element;
- valueInfo = value->GetValueInfo();
- if (valueInfo->GetSymStore() == nullptr && !valueInfo->HasIntConstantValue())
- {
- continue;
- }
- sym = bucket.value;
- if (sym == nullptr || !sym->IsStackSym() || !(sym->AsStackSym()->HasByteCodeRegSlot()))
- {
- continue;
- }
- block->globOptData.changedSyms->Set(sym->m_id);
- }
- NEXT_GLOBHASHTABLE_ENTRY;
- FOREACH_BITSET_IN_SPARSEBV(symId, block->globOptData.changedSyms)
- {
- HashBucket<Sym*, Value*> * bucket = block->globOptData.symToValueMap->GetBucket(symId);
- StackSym * stackSym = bucket->value->AsStackSym();
- value = bucket->element;
- valueInfo = value->GetValueInfo();
- int intConstantValue;
- if (valueInfo->TryGetIntConstantValue(&intConstantValue))
- {
- BailoutConstantValue constValue;
- constValue.InitIntConstValue(intConstantValue);
- bailOutConstValuesIter.InsertNodeBefore(this->func->m_alloc, stackSym, constValue);
- }
- else if (valueInfo->IsVarConstant())
- {
- BailoutConstantValue constValue;
- constValue.InitVarConstValue(valueInfo->AsVarConstant()->VarValue());
- bailOutConstValuesIter.InsertNodeBefore(this->func->m_alloc, stackSym, constValue);
- }
- else
- {
- CaptureCopyPropValue(block, stackSym, value, bailOutCopySymsIter);
- }
- }
- NEXT_BITSET_IN_SPARSEBV
- }
- void
- GlobOpt::CaptureValuesIncremental(BasicBlock * block,
- SListBase<ConstantStackSymValue>::EditingIterator & bailOutConstValuesIter,
- SListBase<CopyPropSyms>::EditingIterator & bailOutCopySymsIter)
- {
- CapturedValues * currCapturedValues = block->globOptData.capturedValues;
- SListBase<ConstantStackSymValue>::Iterator iterConst(currCapturedValues ? &currCapturedValues->constantValues : nullptr);
- SListBase<CopyPropSyms>::Iterator iterCopyPropSym(currCapturedValues ? &currCapturedValues->copyPropSyms : nullptr);
- bool hasConstValue = currCapturedValues ? iterConst.Next() : false;
- bool hasCopyPropSym = currCapturedValues ? iterCopyPropSym.Next() : false;
- block->globOptData.changedSyms->Set(Js::Constants::InvalidSymID);
- FOREACH_BITSET_IN_SPARSEBV(symId, block->globOptData.changedSyms)
- {
- Sym * sym = hasConstValue ? iterConst.Data().Key() : nullptr;
- Value * val = nullptr;
- HashBucket<Sym *, Value *> * symIdBucket = nullptr;
- // copy unchanged sym to new capturedValues
- while (sym && sym->m_id < symId)
- {
- Assert(sym->IsStackSym());
- if (!sym->AsStackSym()->HasArgSlotNum())
- {
- bailOutConstValuesIter.InsertNodeBefore(this->func->m_alloc, sym->AsStackSym(), iterConst.Data().Value());
- }
- hasConstValue = iterConst.Next();
- sym = hasConstValue ? iterConst.Data().Key() : nullptr;
- }
- if (sym && sym->m_id == symId)
- {
- hasConstValue = iterConst.Next();
- }
- if (symId != Js::Constants::InvalidSymID)
- {
- // recapture changed constant sym
- symIdBucket = block->globOptData.symToValueMap->GetBucket(symId);
- if (symIdBucket == nullptr)
- {
- continue;
- }
- Sym * symIdSym = symIdBucket->value;
- Assert(symIdSym->IsStackSym() && (symIdSym->AsStackSym()->HasByteCodeRegSlot() || symIdSym->AsStackSym()->HasArgSlotNum()));
- val = symIdBucket->element;
- ValueInfo* valueInfo = val->GetValueInfo();
- if (valueInfo->GetSymStore() != nullptr)
- {
- int32 intConstValue;
- BailoutConstantValue constValue;
- if (valueInfo->TryGetIntConstantValue(&intConstValue))
- {
- constValue.InitIntConstValue(intConstValue);
- bailOutConstValuesIter.InsertNodeBefore(this->func->m_alloc, symIdSym->AsStackSym(), constValue);
- continue;
- }
- else if(valueInfo->IsVarConstant())
- {
- constValue.InitVarConstValue(valueInfo->AsVarConstant()->VarValue());
- bailOutConstValuesIter.InsertNodeBefore(this->func->m_alloc, symIdSym->AsStackSym(), constValue);
- continue;
- }
- }
- else if (!valueInfo->HasIntConstantValue())
- {
- continue;
- }
- }
- sym = hasCopyPropSym ? iterCopyPropSym.Data().Key() : nullptr;
- // process unchanged sym, but copy sym might have changed
- while (sym && sym->m_id < symId)
- {
- StackSym * copyPropSym = iterCopyPropSym.Data().Value();
- Assert(sym->IsStackSym());
- if (!block->globOptData.changedSyms->Test(copyPropSym->m_id))
- {
- if (!sym->AsStackSym()->HasArgSlotNum())
- {
- bailOutCopySymsIter.InsertNodeBefore(this->func->m_alloc, sym->AsStackSym(), copyPropSym);
- }
- }
- else
- {
- if (!sym->AsStackSym()->HasArgSlotNum())
- {
- val = this->currentBlock->globOptData.FindValue(sym);
- if (val != nullptr)
- {
- CaptureCopyPropValue(block, sym, val, bailOutCopySymsIter);
- }
- }
- }
- hasCopyPropSym = iterCopyPropSym.Next();
- sym = hasCopyPropSym ? iterCopyPropSym.Data().Key() : nullptr;
- }
- if (sym && sym->m_id == symId)
- {
- hasCopyPropSym = iterCopyPropSym.Next();
- }
- if (symId != Js::Constants::InvalidSymID)
- {
- // recapture changed copy prop sym
- symIdBucket = block->globOptData.symToValueMap->GetBucket(symId);
- if (symIdBucket != nullptr)
- {
- Sym * symIdSym = symIdBucket->value;
- val = this->currentBlock->globOptData.FindValue(symIdSym);
- if (val != nullptr)
- {
- CaptureCopyPropValue(block, symIdSym, val, bailOutCopySymsIter);
- }
- }
- }
- }
- NEXT_BITSET_IN_SPARSEBV
- }
- void
- GlobOpt::CaptureValues(BasicBlock *block, BailOutInfo * bailOutInfo)
- {
- if (!this->func->DoGlobOptsForGeneratorFunc())
- {
- // TODO[generators][ianhall]: Enable constprop and copyprop for generator functions; see GlobOpt::CopyProp()
- // Even though CopyProp is disabled for generator functions we must also not put the copy-prop sym into the
- // bailOutInfo so that the bailOutInfo keeps track of the key sym in its byteCodeUpwardExposed list.
- return;
- }
- CapturedValues capturedValues;
- SListBase<ConstantStackSymValue>::EditingIterator bailOutConstValuesIter(&capturedValues.constantValues);
- SListBase<CopyPropSyms>::EditingIterator bailOutCopySymsIter(&capturedValues.copyPropSyms);
- bailOutConstValuesIter.Next();
- bailOutCopySymsIter.Next();
- if (!block->globOptData.capturedValues)
- {
- CaptureValuesFromScratch(block, bailOutConstValuesIter, bailOutCopySymsIter);
- }
- else
- {
- CaptureValuesIncremental(block, bailOutConstValuesIter, bailOutCopySymsIter);
- }
- // attach capturedValues to bailOutInfo
- bailOutInfo->capturedValues.constantValues.Clear(this->func->m_alloc);
- bailOutConstValuesIter.SetNext(&bailOutInfo->capturedValues.constantValues);
- bailOutInfo->capturedValues.constantValues = capturedValues.constantValues;
- bailOutInfo->capturedValues.copyPropSyms.Clear(this->func->m_alloc);
- bailOutCopySymsIter.SetNext(&bailOutInfo->capturedValues.copyPropSyms);
- bailOutInfo->capturedValues.copyPropSyms = capturedValues.copyPropSyms;
-
- if (!PHASE_OFF(Js::IncrementalBailoutPhase, func))
- {
- // cache the pointer of current bailout as potential baseline for later bailout in this block
- block->globOptData.capturedValuesCandidate = &bailOutInfo->capturedValues;
- // reset changed syms to track symbols change after the above captured values candidate
- this->changedSymsAfterIncBailoutCandidate->ClearAll();
- }
- }
- void
- GlobOpt::CaptureArguments(BasicBlock *block, BailOutInfo * bailOutInfo, JitArenaAllocator *allocator)
- {
- FOREACH_BITSET_IN_SPARSEBV(id, this->currentBlock->globOptData.argObjSyms)
- {
- StackSym * stackSym = this->func->m_symTable->FindStackSym(id);
- Assert(stackSym != nullptr);
- if (!stackSym->HasByteCodeRegSlot())
- {
- continue;
- }
- if (!bailOutInfo->capturedValues.argObjSyms)
- {
- bailOutInfo->capturedValues.argObjSyms = JitAnew(allocator, BVSparse<JitArenaAllocator>, allocator);
- }
- bailOutInfo->capturedValues.argObjSyms->Set(id);
- // Add to BailOutInfo
- }
- NEXT_BITSET_IN_SPARSEBV
- }
- void
- GlobOpt::TrackByteCodeSymUsed(IR::Instr * instr, BVSparse<JitArenaAllocator> * instrByteCodeStackSymUsed, PropertySym **pPropertySym)
- {
- IR::Opnd * src = instr->GetSrc1();
- if (src)
- {
- TrackByteCodeSymUsed(src, instrByteCodeStackSymUsed, pPropertySym);
- src = instr->GetSrc2();
- if (src)
- {
- TrackByteCodeSymUsed(src, instrByteCodeStackSymUsed, pPropertySym);
- }
- }
- #if DBG
- // There should be no more than one property sym used.
- PropertySym *propertySymFromSrc = *pPropertySym;
- #endif
- IR::Opnd * dst = instr->GetDst();
- if (dst)
- {
- StackSym *stackSym = dst->GetStackSym();
- // We want stackSym uses: IndirOpnd and SymOpnds of propertySyms.
- // RegOpnd and SymOPnd of StackSyms are stack sym defs.
- if (stackSym == NULL)
- {
- TrackByteCodeSymUsed(dst, instrByteCodeStackSymUsed, pPropertySym);
- }
- }
- #if DBG
- AssertMsg(propertySymFromSrc == NULL || propertySymFromSrc == *pPropertySym,
- "Lost a property sym use?");
- #endif
- }
- void
- GlobOpt::TrackByteCodeSymUsed(IR::RegOpnd * regOpnd, BVSparse<JitArenaAllocator> * instrByteCodeStackSymUsed)
- {
- // Check JITOptimizedReg to catch case where baseOpnd of indir was optimized.
- if (!regOpnd->GetIsJITOptimizedReg())
- {
- TrackByteCodeSymUsed(regOpnd->m_sym, instrByteCodeStackSymUsed);
- }
- }
- void
- GlobOpt::TrackByteCodeSymUsed(IR::Opnd * opnd, BVSparse<JitArenaAllocator> * instrByteCodeStackSymUsed, PropertySym **pPropertySym)
- {
- if (opnd->GetIsJITOptimizedReg())
- {
- AssertMsg(!opnd->IsIndirOpnd(), "TrackByteCodeSymUsed doesn't expect IndirOpnd with IsJITOptimizedReg turned on");
- return;
- }
- switch(opnd->GetKind())
- {
- case IR::OpndKindReg:
- TrackByteCodeSymUsed(opnd->AsRegOpnd(), instrByteCodeStackSymUsed);
- break;
- case IR::OpndKindSym:
- {
- Sym * sym = opnd->AsSymOpnd()->m_sym;
- if (sym->IsStackSym())
- {
- TrackByteCodeSymUsed(sym->AsStackSym(), instrByteCodeStackSymUsed);
- }
- else
- {
- TrackByteCodeSymUsed(sym->AsPropertySym()->m_stackSym, instrByteCodeStackSymUsed);
- *pPropertySym = sym->AsPropertySym();
- }
- }
- break;
- case IR::OpndKindIndir:
- TrackByteCodeSymUsed(opnd->AsIndirOpnd()->GetBaseOpnd(), instrByteCodeStackSymUsed);
- {
- IR::RegOpnd * indexOpnd = opnd->AsIndirOpnd()->GetIndexOpnd();
- if (indexOpnd)
- {
- TrackByteCodeSymUsed(indexOpnd, instrByteCodeStackSymUsed);
- }
- }
- break;
- }
- }
- void
- GlobOpt::TrackByteCodeSymUsed(StackSym * sym, BVSparse<JitArenaAllocator> * instrByteCodeStackSymUsed)
- {
- // We only care about stack sym that has a corresponding byte code register
- if (sym->HasByteCodeRegSlot())
- {
- if (sym->IsTypeSpec())
- {
- // It has to have a var version for byte code regs
- sym = sym->GetVarEquivSym(nullptr);
- }
- instrByteCodeStackSymUsed->Set(sym->m_id);
- }
- }
- void
- GlobOpt::MarkNonByteCodeUsed(IR::Instr * instr)
- {
- IR::Opnd * dst = instr->GetDst();
- if (dst)
- {
- MarkNonByteCodeUsed(dst);
- }
- IR::Opnd * src1 = instr->GetSrc1();
- if (src1)
- {
- MarkNonByteCodeUsed(src1);
- IR::Opnd * src2 = instr->GetSrc2();
- if (src2)
- {
- MarkNonByteCodeUsed(src2);
- }
- }
- }
- void
- GlobOpt::MarkNonByteCodeUsed(IR::Opnd * opnd)
- {
- switch(opnd->GetKind())
- {
- case IR::OpndKindReg:
- opnd->AsRegOpnd()->SetIsJITOptimizedReg(true);
- break;
- case IR::OpndKindIndir:
- opnd->AsIndirOpnd()->GetBaseOpnd()->SetIsJITOptimizedReg(true);
- {
- IR::RegOpnd * indexOpnd = opnd->AsIndirOpnd()->GetIndexOpnd();
- if (indexOpnd)
- {
- indexOpnd->SetIsJITOptimizedReg(true);
- }
- }
- break;
- }
- }
- void
- GlobOpt::CaptureByteCodeSymUses(IR::Instr * instr)
- {
- if (this->byteCodeUses)
- {
- // We already captured it before.
- return;
- }
- Assert(this->propertySymUse == NULL);
- this->byteCodeUses = JitAnew(this->alloc, BVSparse<JitArenaAllocator>, this->alloc);
- GlobOpt::TrackByteCodeSymUsed(instr, this->byteCodeUses, &this->propertySymUse);
- AssertMsg(this->byteCodeUses->Equal(this->byteCodeUsesBeforeOpt),
- "Instruction edited before capturing the byte code use");
- }
- void
- GlobOpt::TrackCalls(IR::Instr * instr)
- {
- // Keep track of out params for bailout
- switch (instr->m_opcode)
- {
- case Js::OpCode::StartCall:
- Assert(!this->isCallHelper);
- Assert(instr->GetDst()->IsRegOpnd());
- Assert(instr->GetDst()->AsRegOpnd()->m_sym->m_isSingleDef);
- if (this->currentBlock->globOptData.callSequence == nullptr)
- {
- this->currentBlock->globOptData.callSequence = JitAnew(this->alloc, SListBase<IR::Opnd *>);
- this->currentBlock->globOptData.callSequence = this->currentBlock->globOptData.callSequence;
- }
- this->currentBlock->globOptData.callSequence->Prepend(this->alloc, instr->GetDst());
- this->currentBlock->globOptData.totalOutParamCount += instr->GetArgOutCount(/*getInterpreterArgOutCount*/ true);
- this->currentBlock->globOptData.startCallCount++;
- break;
- case Js::OpCode::BytecodeArgOutCapture:
- {
- this->currentBlock->globOptData.callSequence->Prepend(this->alloc, instr->GetDst());
- this->currentBlock->globOptData.argOutCount++;
- break;
- }
- case Js::OpCode::ArgOut_A:
- case Js::OpCode::ArgOut_A_Inline:
- case Js::OpCode::ArgOut_A_FixupForStackArgs:
- case Js::OpCode::ArgOut_A_InlineBuiltIn:
- case Js::OpCode::ArgOut_A_Dynamic:
- case Js::OpCode::ArgOut_A_FromStackArgs:
- case Js::OpCode::ArgOut_A_SpreadArg:
- {
- IR::Opnd * opnd = instr->GetDst();
- if (opnd->IsSymOpnd())
- {
- Assert(!this->isCallHelper);
- Assert(!this->currentBlock->globOptData.callSequence->Empty());
- StackSym* stackSym = opnd->AsSymOpnd()->m_sym->AsStackSym();
- // These scenarios are already tracked using BytecodeArgOutCapture,
- // and we don't want to be tracking ArgOut_A_FixupForStackArgs as these are only visible to the JIT and we should not be restoring them upon bailout.
- if (!stackSym->m_isArgCaptured && instr->m_opcode != Js::OpCode::ArgOut_A_FixupForStackArgs)
- {
- this->currentBlock->globOptData.callSequence->Prepend(this->alloc, instr->GetDst());
- this->currentBlock->globOptData.argOutCount++;
- }
- Assert(stackSym->IsArgSlotSym());
- if (stackSym->m_isInlinedArgSlot)
- {
- this->currentBlock->globOptData.inlinedArgOutCount++;
- // We want to update the offsets only once: don't do in prepass.
- if (!this->IsLoopPrePass() && stackSym->m_offset >= 0)
- {
- Func * currentFunc = instr->m_func;
- stackSym->FixupStackOffset(currentFunc);
- }
- }
- }
- else
- {
- // It is a reg opnd if it is a helper call
- // It should be all ArgOut until the CallHelper instruction
- Assert(opnd->IsRegOpnd());
- this->isCallHelper = true;
- }
- if (instr->m_opcode == Js::OpCode::ArgOut_A_FixupForStackArgs && !this->IsLoopPrePass())
- {
- instr->m_opcode = Js::OpCode::ArgOut_A_Inline;
- }
- break;
- }
- case Js::OpCode::InlineeStart:
- Assert(instr->m_func->GetParentFunc() == this->currentBlock->globOptData.curFunc);
- Assert(instr->m_func->GetParentFunc());
- this->currentBlock->globOptData.curFunc = instr->m_func;
- this->currentBlock->globOptData.curFunc = instr->m_func;
- this->func->UpdateMaxInlineeArgOutCount(this->currentBlock->globOptData.inlinedArgOutCount);
- this->EndTrackCall(instr);
- if (DoInlineArgsOpt(instr->m_func))
- {
- instr->m_func->m_hasInlineArgsOpt = true;
- InlineeFrameInfo* frameInfo = InlineeFrameInfo::New(func->m_alloc);
- instr->m_func->frameInfo = frameInfo;
- frameInfo->floatSyms = currentBlock->globOptData.liveFloat64Syms->CopyNew(this->alloc);
- frameInfo->intSyms = currentBlock->globOptData.liveInt32Syms->MinusNew(currentBlock->globOptData.liveLossyInt32Syms, this->alloc);
- #ifdef ENABLE_SIMDJS
- // SIMD_JS
- frameInfo->simd128F4Syms = currentBlock->globOptData.liveSimd128F4Syms->CopyNew(this->alloc);
- frameInfo->simd128I4Syms = currentBlock->globOptData.liveSimd128I4Syms->CopyNew(this->alloc);
- #endif
- }
- break;
- case Js::OpCode::EndCallForPolymorphicInlinee:
- // Have this opcode mimic the functions of both InlineeStart and InlineeEnd in the bailout block of a polymorphic call inlined using fixed methods.
- this->EndTrackCall(instr);
- break;
- case Js::OpCode::CallHelper:
- case Js::OpCode::IsInst:
- Assert(this->isCallHelper);
- this->isCallHelper = false;
- break;
- case Js::OpCode::InlineeEnd:
- if (instr->m_func->m_hasInlineArgsOpt)
- {
- RecordInlineeFrameInfo(instr);
- }
- EndTrackingOfArgObjSymsForInlinee();
- Assert(this->currentBlock->globOptData.inlinedArgOutCount >= instr->GetArgOutCount(/*getInterpreterArgOutCount*/ false));
- this->currentBlock->globOptData.inlinedArgOutCount -= instr->GetArgOutCount(/*getInterpreterArgOutCount*/ false);
- break;
- case Js::OpCode::InlineeMetaArg:
- {
- Assert(instr->GetDst()->IsSymOpnd());
- StackSym * stackSym = instr->GetDst()->AsSymOpnd()->m_sym->AsStackSym();
- Assert(stackSym->IsArgSlotSym());
- // InlineeMetaArg has the m_func set as the "inlinee" and not the "inliner"
- // TODO: Review this and fix the m_func of InlineeMetaArg to be "inliner" (as for the rest of the ArgOut's)
- // We want to update the offsets only once: don't do in prepass.
- if (!this->IsLoopPrePass())
- {
- Func * currentFunc = instr->m_func->GetParentFunc();
- stackSym->FixupStackOffset(currentFunc);
- }
- this->currentBlock->globOptData.inlinedArgOutCount++;
- break;
- }
- case Js::OpCode::InlineBuiltInStart:
- this->inInlinedBuiltIn = true;
- break;
- case Js::OpCode::InlineNonTrackingBuiltInEnd:
- case Js::OpCode::InlineBuiltInEnd:
- {
- // If extra bailouts were added for the InlineMathXXX call itself,
- // move InlineeBuiltInStart just above the InlineMathXXX.
- // This is needed so that the function argument has lifetime after all bailouts for InlineMathXXX,
- // otherwise when we bailout we would get wrong function.
- IR::Instr* inlineBuiltInStartInstr = instr->m_prev;
- while (inlineBuiltInStartInstr->m_opcode != Js::OpCode::InlineBuiltInStart)
- {
- inlineBuiltInStartInstr = inlineBuiltInStartInstr->m_prev;
- }
- IR::Instr *byteCodeUsesInstr = inlineBuiltInStartInstr->m_prev;
- IR::Instr * insertBeforeInstr = instr->m_prev;
- IR::Instr * tmpInstr = insertBeforeInstr;
- while(tmpInstr->m_opcode != Js::OpCode::InlineBuiltInStart )
- {
- if(tmpInstr->m_opcode == Js::OpCode::ByteCodeUses)
- {
- insertBeforeInstr = tmpInstr;
- }
- tmpInstr = tmpInstr->m_prev;
- }
- inlineBuiltInStartInstr->Unlink();
- if(insertBeforeInstr == instr->m_prev)
- {
- insertBeforeInstr->InsertBefore(inlineBuiltInStartInstr);
- }
- else
- {
- insertBeforeInstr->m_prev->InsertBefore(inlineBuiltInStartInstr);
- }
- // Need to move the byte code uses instructions associated with inline built-in start instruction as well. For instance,
- // copy-prop may have replaced the function sym and inserted a byte code uses for the original sym holding the function.
- // That byte code uses instruction needs to appear after bailouts inserted for the InlinMathXXX instruction since the
- // byte code register holding the function object needs to be restored on bailout.
- IR::Instr *const insertByteCodeUsesAfterInstr = inlineBuiltInStartInstr->m_prev;
- if(byteCodeUsesInstr != insertByteCodeUsesAfterInstr)
- {
- // The InlineBuiltInStart instruction was moved, look for its ByteCodeUses instructions that also need to be moved
- while(
- byteCodeUsesInstr->IsByteCodeUsesInstr() &&
- byteCodeUsesInstr->AsByteCodeUsesInstr()->GetByteCodeOffset() == inlineBuiltInStartInstr->GetByteCodeOffset())
- {
- IR::Instr *const instrToMove = byteCodeUsesInstr;
- byteCodeUsesInstr = byteCodeUsesInstr->m_prev;
- instrToMove->Unlink();
- insertByteCodeUsesAfterInstr->InsertAfter(instrToMove);
- }
- }
- // The following code makes more sense to be processed when we hit InlineeBuiltInStart,
- // but when extra bailouts are added for the InlineMathXXX and InlineArrayPop instructions itself, those bailouts
- // need to know about current bailout record, but since they are added after TrackCalls is called
- // for InlineeBuiltInStart, we can't clear current record when got InlineeBuiltInStart
- // Do not track calls for InlineNonTrackingBuiltInEnd, as it is already tracked for InlineArrayPop
- if(instr->m_opcode == Js::OpCode::InlineBuiltInEnd)
- {
- this->EndTrackCall(instr);
- }
- Assert(this->currentBlock->globOptData.inlinedArgOutCount >= instr->GetArgOutCount(/*getInterpreterArgOutCount*/ false));
- this->currentBlock->globOptData.inlinedArgOutCount -= instr->GetArgOutCount(/*getInterpreterArgOutCount*/ false);
- this->inInlinedBuiltIn = false;
- break;
- }
- case Js::OpCode::InlineArrayPop:
- {
- // EndTrackCall should be called here as the Post-op BailOutOnImplicitCalls will bail out to the instruction after the Pop function call instr.
- // This bailout shouldn't be tracking the call sequence as it will then erroneously reserve stack space for arguments when the call would have already happened
- // Can't wait till InlineBuiltinEnd like we do for other InlineMathXXX because by then we would have filled bailout info for the BailOutOnImplicitCalls for InlineArrayPop.
- this->EndTrackCall(instr);
- break;
- }
- default:
- if (OpCodeAttr::CallInstr(instr->m_opcode))
- {
- this->EndTrackCall(instr);
- if (this->inInlinedBuiltIn && instr->m_opcode == Js::OpCode::CallDirect)
- {
- // We can end up in this situation when a built-in apply target is inlined to a CallDirect. We have the following IR:
- //
- // StartCall
- // ArgOut_InlineBuiltIn
- // ArgOut_InlineBuiltIn
- // ArgOut_InlineBuiltIn
- // InlineBuiltInStart
- // ArgOut_A_InlineSpecialized
- // ArgOut_A
- // ArgOut_A
- // CallDirect
- // InlineNonTrackingBuiltInEnd
- //
- // We need to call EndTrackCall twice for CallDirect in this case. The CallDirect may get a BailOutOnImplicitCalls later,
- // but it should not be tracking the call sequence for the apply call as it is a post op bailout and the call would have
- // happened when we bail out.
- // Can't wait till InlineBuiltinEnd like we do for other InlineMathXXX because by then we would have filled bailout info for the BailOutOnImplicitCalls for CallDirect.
- this->EndTrackCall(instr);
- }
- }
- break;
- }
- }
- void GlobOpt::RecordInlineeFrameInfo(IR::Instr* inlineeEnd)
- {
- if (this->IsLoopPrePass())
- {
- return;
- }
- InlineeFrameInfo* frameInfo = inlineeEnd->m_func->frameInfo;
- if (frameInfo->isRecorded)
- {
- Assert(frameInfo->function.type != InlineeFrameInfoValueType_None);
- // Due to Cmp peeps in flow graph - InlineeEnd can be cloned.
- return;
- }
- inlineeEnd->IterateArgInstrs([=] (IR::Instr* argInstr)
- {
- if (argInstr->m_opcode == Js::OpCode::InlineeStart)
- {
- Assert(frameInfo->function.type == InlineeFrameInfoValueType_None);
- IR::RegOpnd* functionObject = argInstr->GetSrc1()->AsRegOpnd();
- if (functionObject->m_sym->IsConst())
- {
- frameInfo->function = InlineFrameInfoValue(functionObject->m_sym->GetConstValueForBailout());
- }
- else
- {
- frameInfo->function = InlineFrameInfoValue(functionObject->m_sym);
- }
- }
- else
- {
- Js::ArgSlot argSlot = argInstr->GetDst()->AsSymOpnd()->m_sym->AsStackSym()->GetArgSlotNum();
- IR::Opnd* argOpnd = argInstr->GetSrc1();
- InlineFrameInfoValue frameInfoValue;
- StackSym* argSym = argOpnd->GetStackSym();
- if (!argSym)
- {
- frameInfoValue = InlineFrameInfoValue(argOpnd->GetConstValue());
- }
- else if (argSym->IsConst())
- {
- frameInfoValue = InlineFrameInfoValue(argSym->GetConstValueForBailout());
- }
- else
- {
- if (PHASE_ON(Js::CopyPropPhase, func))
- {
- Value* value = this->currentBlock->globOptData.FindValue(argSym);
- StackSym * copyPropSym = this->currentBlock->globOptData.GetCopyPropSym(argSym, value);
- if (copyPropSym)
- {
- argSym = copyPropSym;
- }
- }
- GlobOptBlockData& globOptData = this->currentBlock->globOptData;
- if (frameInfo->intSyms->TestEmpty() && frameInfo->intSyms->Test(argSym->m_id))
- {
- // Var version of the sym is not live, use the int32 version
- argSym = argSym->GetInt32EquivSym(nullptr);
- Assert(argSym);
- }
- else if (frameInfo->floatSyms->TestEmpty() && frameInfo->floatSyms->Test(argSym->m_id))
- {
- // Var/int32 version of the sym is not live, use the float64 version
- argSym = argSym->GetFloat64EquivSym(nullptr);
- Assert(argSym);
- }
- #ifdef ENABLE_SIMDJS
- // SIMD_JS
- else if (frameInfo->simd128F4Syms->TestEmpty() && frameInfo->simd128F4Syms->Test(argSym->m_id))
- {
- argSym = argSym->GetSimd128F4EquivSym(nullptr);
- }
- else if (frameInfo->simd128I4Syms->TestEmpty() && frameInfo->simd128I4Syms->Test(argSym->m_id))
- {
- argSym = argSym->GetSimd128I4EquivSym(nullptr);
- }
- #endif
- else
- {
- Assert(globOptData.liveVarSyms->Test(argSym->m_id));
- }
- if (argSym->IsConst())
- {
- frameInfoValue = InlineFrameInfoValue(argSym->GetConstValueForBailout());
- }
- else
- {
- frameInfoValue = InlineFrameInfoValue(argSym);
- }
- }
- Assert(argSlot >= 1);
- frameInfo->arguments->SetItem(argSlot - 1, frameInfoValue);
- }
- return false;
- });
- JitAdelete(this->alloc, frameInfo->intSyms);
- frameInfo->intSyms = nullptr;
- JitAdelete(this->alloc, frameInfo->floatSyms);
- frameInfo->floatSyms = nullptr;
- #ifdef ENABLE_SIMDJS
- // SIMD_JS
- JitAdelete(this->alloc, frameInfo->simd128F4Syms);
- frameInfo->simd128F4Syms = nullptr;
- JitAdelete(this->alloc, frameInfo->simd128I4Syms);
- frameInfo->simd128I4Syms = nullptr;
- #endif
- frameInfo->isRecorded = true;
- }
- void GlobOpt::EndTrackingOfArgObjSymsForInlinee()
- {
- Assert(this->currentBlock->globOptData.curFunc->GetParentFunc());
- if (this->currentBlock->globOptData.curFunc->argObjSyms && TrackArgumentsObject())
- {
- BVSparse<JitArenaAllocator> * tempBv = JitAnew(this->tempAlloc, BVSparse<JitArenaAllocator>, this->tempAlloc);
- tempBv->Minus(this->currentBlock->globOptData.curFunc->argObjSyms, this->currentBlock->globOptData.argObjSyms);
- if(!tempBv->IsEmpty())
- {
- // This means there are arguments object symbols in the current function which are not in the current block.
- // This could happen when one of the blocks has a throw and arguments object aliased in it and other blocks don't see it.
- // Rare case, abort stack arguments optimization in this case.
- CannotAllocateArgumentsObjectOnStack();
- }
- else
- {
- Assert(this->currentBlock->globOptData.argObjSyms->OrNew(this->currentBlock->globOptData.curFunc->argObjSyms)->Equal(this->currentBlock->globOptData.argObjSyms));
- this->currentBlock->globOptData.argObjSyms->Minus(this->currentBlock->globOptData.curFunc->argObjSyms);
- }
- JitAdelete(this->tempAlloc, tempBv);
- }
- this->currentBlock->globOptData.curFunc = this->currentBlock->globOptData.curFunc->GetParentFunc();
- this->currentBlock->globOptData.curFunc = this->currentBlock->globOptData.curFunc;
- }
- void GlobOpt::EndTrackCall(IR::Instr* instr)
- {
- Assert(instr);
- Assert(OpCodeAttr::CallInstr(instr->m_opcode) || instr->m_opcode == Js::OpCode::InlineeStart || instr->m_opcode == Js::OpCode::InlineBuiltInEnd
- || instr->m_opcode == Js::OpCode::InlineArrayPop || instr->m_opcode == Js::OpCode::EndCallForPolymorphicInlinee);
- Assert(!this->isCallHelper);
- Assert(!this->currentBlock->globOptData.callSequence->Empty());
- #if DBG
- uint origArgOutCount = this->currentBlock->globOptData.argOutCount;
- #endif
- while (this->currentBlock->globOptData.callSequence->Head()->GetStackSym()->HasArgSlotNum())
- {
- this->currentBlock->globOptData.argOutCount--;
- this->currentBlock->globOptData.callSequence->RemoveHead(this->alloc);
- }
- StackSym * sym = this->currentBlock->globOptData.callSequence->Head()->AsRegOpnd()->m_sym->AsStackSym();
- this->currentBlock->globOptData.callSequence->RemoveHead(this->alloc);
- #if DBG
- Assert(sym->m_isSingleDef);
- Assert(sym->m_instrDef->m_opcode == Js::OpCode::StartCall);
- // Number of argument set should be the same as indicated at StartCall
- // except NewScObject has an implicit arg1
- Assert((uint)sym->m_instrDef->GetArgOutCount(/*getInterpreterArgOutCount*/ true) ==
- origArgOutCount - this->currentBlock->globOptData.argOutCount +
- (instr->m_opcode == Js::OpCode::NewScObject || instr->m_opcode == Js::OpCode::NewScObjArray
- || instr->m_opcode == Js::OpCode::NewScObjectSpread || instr->m_opcode == Js::OpCode::NewScObjArraySpread));
- #endif
- this->currentBlock->globOptData.totalOutParamCount -= sym->m_instrDef->GetArgOutCount(/*getInterpreterArgOutCount*/ true);
- this->currentBlock->globOptData.startCallCount--;
- }
- void
- GlobOpt::FillBailOutInfo(BasicBlock *block, BailOutInfo * bailOutInfo)
- {
- AssertMsg(!this->isCallHelper, "Bail out can't be inserted the middle of CallHelper sequence");
- bailOutInfo->liveVarSyms = block->globOptData.liveVarSyms->CopyNew(this->func->m_alloc);
- bailOutInfo->liveFloat64Syms = block->globOptData.liveFloat64Syms->CopyNew(this->func->m_alloc);
- #ifdef ENABLE_SIMDJS
- // SIMD_JS
- bailOutInfo->liveSimd128F4Syms = block->globOptData.liveSimd128F4Syms->CopyNew(this->func->m_alloc);
- bailOutInfo->liveSimd128I4Syms = block->globOptData.liveSimd128I4Syms->CopyNew(this->func->m_alloc);
- #endif
- // The live int32 syms in the bailout info are only the syms resulting from lossless conversion to int. If the int32 value
- // was created from a lossy conversion to int, the original var value cannot be re-materialized from the int32 value. So, the
- // int32 version is considered to be not live for the purposes of bailout, which forces the var or float versions to be used
- // directly for restoring the value during bailout. Otherwise, bailout may try to re-materialize the var value by converting
- // the lossily-converted int value back into a var, restoring the wrong value.
- bailOutInfo->liveLosslessInt32Syms =
- block->globOptData.liveInt32Syms->MinusNew(block->globOptData.liveLossyInt32Syms, this->func->m_alloc);
- // Save the stack literal init field count so we can null out the uninitialized fields
- StackLiteralInitFldDataMap * stackLiteralInitFldDataMap = block->globOptData.stackLiteralInitFldDataMap;
- if (stackLiteralInitFldDataMap != nullptr)
- {
- uint stackLiteralInitFldDataCount = stackLiteralInitFldDataMap->Count();
- if (stackLiteralInitFldDataCount != 0)
- {
- auto stackLiteralBailOutInfo = AnewArray(this->func->m_alloc,
- BailOutInfo::StackLiteralBailOutInfo, stackLiteralInitFldDataCount);
- uint i = 0;
- stackLiteralInitFldDataMap->Map(
- [stackLiteralBailOutInfo, stackLiteralInitFldDataCount, &i](StackSym * stackSym, StackLiteralInitFldData const& data)
- {
- Assert(i < stackLiteralInitFldDataCount);
- stackLiteralBailOutInfo[i].stackSym = stackSym;
- stackLiteralBailOutInfo[i].initFldCount = data.currentInitFldCount;
- i++;
- });
- Assert(i == stackLiteralInitFldDataCount);
- bailOutInfo->stackLiteralBailOutInfoCount = stackLiteralInitFldDataCount;
- bailOutInfo->stackLiteralBailOutInfo = stackLiteralBailOutInfo;
- }
- }
- if (TrackArgumentsObject())
- {
- this->CaptureArguments(block, bailOutInfo, this->func->m_alloc);
- }
- if (block->globOptData.callSequence && !block->globOptData.callSequence->Empty())
- {
- uint currentArgOutCount = 0;
- uint startCallNumber = block->globOptData.startCallCount;
- bailOutInfo->startCallInfo = JitAnewArray(this->func->m_alloc, BailOutInfo::StartCallInfo, startCallNumber);
- bailOutInfo->startCallCount = startCallNumber;
- // Save the start call's func to identify the function (inlined) that the call sequence is for
- // We might not have any arg out yet to get the function from
- bailOutInfo->startCallFunc = JitAnewArray(this->func->m_alloc, Func *, startCallNumber);
- #ifdef _M_IX86
- bailOutInfo->inlinedStartCall = BVFixed::New(startCallNumber, this->func->m_alloc, false);
- #endif
- uint totalOutParamCount = block->globOptData.totalOutParamCount;
- bailOutInfo->totalOutParamCount = totalOutParamCount;
- bailOutInfo->argOutSyms = JitAnewArrayZ(this->func->m_alloc, StackSym *, totalOutParamCount);
- uint argRestoreAdjustCount = 0;
- FOREACH_SLISTBASE_ENTRY(IR::Opnd *, opnd, block->globOptData.callSequence)
- {
- if(opnd->GetStackSym()->HasArgSlotNum())
- {
- StackSym * sym;
- if(opnd->IsSymOpnd())
- {
- sym = opnd->AsSymOpnd()->m_sym->AsStackSym();
- Assert(sym->IsArgSlotSym());
- Assert(sym->m_isSingleDef);
- Assert(sym->m_instrDef->m_opcode == Js::OpCode::ArgOut_A
- || sym->m_instrDef->m_opcode == Js::OpCode::ArgOut_A_Inline
- || sym->m_instrDef->m_opcode == Js::OpCode::ArgOut_A_InlineBuiltIn
- || sym->m_instrDef->m_opcode == Js::OpCode::ArgOut_A_SpreadArg
- || sym->m_instrDef->m_opcode == Js::OpCode::ArgOut_A_Dynamic);
- }
- else
- {
- sym = opnd->GetStackSym();
- Assert(this->currentBlock->globOptData.FindValue(sym));
- // StackSym args need to be re-captured
- this->currentBlock->globOptData.SetChangedSym(sym->m_id);
- }
- Assert(totalOutParamCount != 0);
- Assert(totalOutParamCount > currentArgOutCount);
- currentArgOutCount++;
- #pragma prefast(suppress:26000, "currentArgOutCount is never 0");
- bailOutInfo->argOutSyms[totalOutParamCount - currentArgOutCount] = sym;
- // Note that there could be ArgOuts below current bailout instr that belong to current call (currentArgOutCount < argOutCount),
- // in which case we will have nulls in argOutSyms[] in start of section for current call, because we fill from tail.
- // Example: StartCall 3, ArgOut1,.. ArgOut2, Bailout,.. Argout3 -> [NULL, ArgOut1, ArgOut2].
- }
- else
- {
- Assert(opnd->IsRegOpnd());
- StackSym * sym = opnd->AsRegOpnd()->m_sym;
- Assert(!sym->IsArgSlotSym());
- Assert(sym->m_isSingleDef);
- Assert(sym->m_instrDef->m_opcode == Js::OpCode::StartCall);
- Assert(startCallNumber != 0);
- startCallNumber--;
- bailOutInfo->startCallFunc[startCallNumber] = sym->m_instrDef->m_func;
- #ifdef _M_IX86
- if (this->currentRegion && (this->currentRegion->GetType() == RegionTypeTry || this->currentRegion->GetType() == RegionTypeFinally))
- {
- // For a bailout in argument evaluation from an EH region, the esp is offset by the TryCatch helper�s frame. So, the argouts are not actually pushed at the
- // offsets stored in the bailout record, which are relative to ebp. Need to restore the argouts from the actual value of esp before calling the Bailout helper.
- // For nested calls, argouts for the outer call need to be restored from an offset of stack-adjustment-done-by-the-inner-call from esp.
- if (startCallNumber + 1 == bailOutInfo->startCallCount)
- {
- argRestoreAdjustCount = 0;
- }
- else
- {
- argRestoreAdjustCount = bailOutInfo->startCallInfo[startCallNumber + 1].argRestoreAdjustCount + bailOutInfo->startCallInfo[startCallNumber + 1].argCount;
- if ((Math::Align<int32>(bailOutInfo->startCallInfo[startCallNumber + 1].argCount * MachPtr, MachStackAlignment) - (bailOutInfo->startCallInfo[startCallNumber + 1].argCount * MachPtr)) != 0)
- {
- argRestoreAdjustCount++;
- }
- }
- }
- if (sym->m_isInlinedArgSlot)
- {
- bailOutInfo->inlinedStartCall->Set(startCallNumber);
- }
- #endif
- uint argOutCount = sym->m_instrDef->GetArgOutCount(/*getInterpreterArgOutCount*/ true);
- Assert(totalOutParamCount >= argOutCount);
- Assert(argOutCount >= currentArgOutCount);
- bailOutInfo->RecordStartCallInfo(startCallNumber, argRestoreAdjustCount, sym->m_instrDef);
- totalOutParamCount -= argOutCount;
- currentArgOutCount = 0;
- }
- }
- NEXT_SLISTBASE_ENTRY;
- Assert(totalOutParamCount == 0);
- Assert(startCallNumber == 0);
- Assert(currentArgOutCount == 0);
- }
- // Save the constant values that we know so we can restore them directly.
- // This allows us to dead store the constant value assign.
- this->CaptureValues(block, bailOutInfo);
- }
- IR::ByteCodeUsesInstr *
- GlobOpt::InsertByteCodeUses(IR::Instr * instr, bool includeDef)
- {
- IR::ByteCodeUsesInstr * byteCodeUsesInstr = nullptr;
- Assert(this->byteCodeUses);
- IR::RegOpnd * dstOpnd = nullptr;
- if (includeDef)
- {
- IR::Opnd * opnd = instr->GetDst();
- if (opnd && opnd->IsRegOpnd())
- {
- dstOpnd = opnd->AsRegOpnd();
- if (dstOpnd->GetIsJITOptimizedReg() || !dstOpnd->m_sym->HasByteCodeRegSlot())
- {
- dstOpnd = nullptr;
- }
- }
- }
- if (!this->byteCodeUses->IsEmpty() || this->propertySymUse || dstOpnd != nullptr)
- {
- byteCodeUsesInstr = IR::ByteCodeUsesInstr::New(instr);
- if (!this->byteCodeUses->IsEmpty())
- {
- byteCodeUsesInstr->SetBV(byteCodeUses->CopyNew(instr->m_func->m_alloc));
- }
- if (dstOpnd != nullptr)
- {
- byteCodeUsesInstr->SetFakeDst(dstOpnd);
- }
- if (this->propertySymUse)
- {
- byteCodeUsesInstr->propertySymUse = this->propertySymUse;
- }
- instr->InsertBefore(byteCodeUsesInstr);
- }
- JitAdelete(this->alloc, this->byteCodeUses);
- this->byteCodeUses = nullptr;
- this->propertySymUse = nullptr;
- return byteCodeUsesInstr;
- }
- IR::ByteCodeUsesInstr *
- GlobOpt::ConvertToByteCodeUses(IR::Instr * instr)
- {
- #if DBG
- PropertySym *propertySymUseBefore = NULL;
- Assert(this->byteCodeUses == nullptr);
- this->byteCodeUsesBeforeOpt->ClearAll();
- GlobOpt::TrackByteCodeSymUsed(instr, this->byteCodeUsesBeforeOpt, &propertySymUseBefore);
- #endif
- this->CaptureByteCodeSymUses(instr);
- IR::ByteCodeUsesInstr * byteCodeUsesInstr = this->InsertByteCodeUses(instr, true);
- instr->Remove();
- if (byteCodeUsesInstr)
- {
- byteCodeUsesInstr->Aggregate();
- }
- return byteCodeUsesInstr;
- }
- bool
- GlobOpt::MayNeedBailOut(Loop * loop) const
- {
- Assert(this->IsLoopPrePass());
- return loop->CanHoistInvariants() ||
- this->DoFieldCopyProp(loop) || (this->DoFieldHoisting(loop) && !loop->fieldHoistCandidates->IsEmpty());
- }
- bool
- GlobOpt::MaySrcNeedBailOnImplicitCall(IR::Opnd const * opnd, Value const * val)
- {
- switch (opnd->GetKind())
- {
- case IR::OpndKindAddr:
- case IR::OpndKindFloatConst:
- case IR::OpndKindIntConst:
- return false;
- case IR::OpndKindReg:
- // Only need implicit call if the operation will call ToPrimitive and we haven't prove
- // that it is already a primitive
- return
- !(val && val->GetValueInfo()->IsPrimitive()) &&
- !opnd->AsRegOpnd()->GetValueType().IsPrimitive() &&
- !opnd->AsRegOpnd()->m_sym->IsInt32() &&
- !opnd->AsRegOpnd()->m_sym->IsFloat64() &&
- !opnd->AsRegOpnd()->m_sym->IsFloatConst() &&
- !opnd->AsRegOpnd()->m_sym->IsIntConst();
- case IR::OpndKindSym:
- if (opnd->AsSymOpnd()->IsPropertySymOpnd())
- {
- IR::PropertySymOpnd const * propertySymOpnd = opnd->AsSymOpnd()->AsPropertySymOpnd();
- if (!propertySymOpnd->MayHaveImplicitCall())
- {
- return false;
- }
- }
- return true;
- default:
- return true;
- };
- }
- bool
- GlobOpt::IsImplicitCallBailOutCurrentlyNeeded(IR::Instr * instr, Value const * src1Val, Value const * src2Val) const
- {
- Assert(!this->IsLoopPrePass());
- return this->IsImplicitCallBailOutCurrentlyNeeded(instr, src1Val, src2Val, this->currentBlock,
- (!this->currentBlock->globOptData.liveFields->IsEmpty()), !this->currentBlock->IsLandingPad(), true);
- }
- bool
- GlobOpt::IsImplicitCallBailOutCurrentlyNeeded(IR::Instr * instr, Value const * src1Val, Value const * src2Val, BasicBlock const * block, bool hasLiveFields, bool mayNeedImplicitCallBailOut, bool isForwardPass) const
- {
- if (mayNeedImplicitCallBailOut &&
- !instr->CallsAccessor() &&
- (
- NeedBailOnImplicitCallForLiveValues(block, isForwardPass) ||
- NeedBailOnImplicitCallForCSE(block, isForwardPass) ||
- NeedBailOnImplicitCallWithFieldOpts(block->loop, hasLiveFields) ||
- NeedBailOnImplicitCallForArrayCheckHoist(block, isForwardPass)
- ) &&
- (!instr->HasTypeCheckBailOut() && MayNeedBailOnImplicitCall(instr, src1Val, src2Val)))
- {
- return true;
- }
- #if DBG
- if (Js::Configuration::Global.flags.IsEnabled(Js::BailOutAtEveryImplicitCallFlag) &&
- !instr->HasBailOutInfo() && MayNeedBailOnImplicitCall(instr, nullptr, nullptr))
- {
- // always add implicit call bailout even if we don't need it, but only on opcode that supports it
- return true;
- }
- #endif
- return false;
- }
- bool
- GlobOpt::IsTypeCheckProtected(const IR::Instr * instr)
- {
- #if DBG
- IR::Opnd* dst = instr->GetDst();
- IR::Opnd* src1 = instr->GetSrc1();
- IR::Opnd* src2 = instr->GetSrc2();
- AssertMsg(!dst || !dst->IsSymOpnd() || !dst->AsSymOpnd()->IsPropertySymOpnd() ||
- !src1 || !src1->IsSymOpnd() || !src1->AsSymOpnd()->IsPropertySymOpnd(), "No instruction should have a src1 and dst be a PropertySymOpnd.");
- AssertMsg(!src2 || !src2->IsSymOpnd() || !src2->AsSymOpnd()->IsPropertySymOpnd(), "No instruction should have a src2 be a PropertySymOpnd.");
- #endif
- IR::Opnd * opnd = instr->GetDst();
- if (opnd && opnd->IsSymOpnd() && opnd->AsSymOpnd()->IsPropertySymOpnd())
- {
- return opnd->AsPropertySymOpnd()->IsTypeCheckProtected();
- }
- opnd = instr->GetSrc1();
- if (opnd && opnd->IsSymOpnd() && opnd->AsSymOpnd()->IsPropertySymOpnd())
- {
- return opnd->AsPropertySymOpnd()->IsTypeCheckProtected();
- }
- return false;
- }
- bool
- GlobOpt::NeedsTypeCheckBailOut(const IR::Instr *instr, IR::PropertySymOpnd *propertySymOpnd, bool isStore, bool* pIsTypeCheckProtected, IR::BailOutKind *pBailOutKind)
- {
- if (instr->m_opcode == Js::OpCode::CheckPropertyGuardAndLoadType || instr->m_opcode == Js::OpCode::LdMethodFldPolyInlineMiss)
- {
- return false;
- }
- // CheckFixedFld always requires a type check and bailout either at the instruction or upstream.
- Assert(instr->m_opcode != Js::OpCode::CheckFixedFld || (propertySymOpnd->UsesFixedValue() && propertySymOpnd->MayNeedTypeCheckProtection()));
- if (propertySymOpnd->MayNeedTypeCheckProtection())
- {
- bool isCheckFixedFld = instr->m_opcode == Js::OpCode::CheckFixedFld;
- AssertMsg(!isCheckFixedFld || !PHASE_OFF(Js::FixedMethodsPhase, instr->m_func) ||
- !PHASE_OFF(Js::UseFixedDataPropsPhase, instr->m_func), "CheckFixedFld with fixed method/data phase disabled?");
- Assert(!isStore || !isCheckFixedFld);
- // We don't share caches between field loads and stores. We should never have a field store involving a proto cache.
- Assert(!isStore || !propertySymOpnd->IsLoadedFromProto());
- if (propertySymOpnd->NeedsTypeCheckAndBailOut())
- {
- *pBailOutKind = propertySymOpnd->HasEquivalentTypeSet() && !propertySymOpnd->MustDoMonoCheck() ?
- (isCheckFixedFld ? IR::BailOutFailedEquivalentFixedFieldTypeCheck : IR::BailOutFailedEquivalentTypeCheck) :
- (isCheckFixedFld ? IR::BailOutFailedFixedFieldTypeCheck : IR::BailOutFailedTypeCheck);
- return true;
- }
- else
- {
- *pIsTypeCheckProtected = propertySymOpnd->IsTypeCheckProtected();
- *pBailOutKind = IR::BailOutInvalid;
- return false;
- }
- }
- else
- {
- Assert(instr->m_opcode != Js::OpCode::CheckFixedFld);
- *pBailOutKind = IR::BailOutInvalid;
- return false;
- }
- }
- bool
- GlobOpt::MayNeedBailOnImplicitCall(IR::Instr const * instr, Value const * src1Val, Value const * src2Val)
- {
- if (!instr->HasAnyImplicitCalls())
- {
- return false;
- }
- bool isLdElem = false;
- switch (instr->m_opcode)
- {
- case Js::OpCode::LdLen_A:
- {
- const ValueType baseValueType(instr->GetSrc1()->GetValueType());
- return
- !(
- baseValueType.IsString() ||
- (baseValueType.IsAnyArray() && baseValueType.GetObjectType() != ObjectType::ObjectWithArray) ||
- (instr->HasBailOutInfo() && instr->GetBailOutKindNoBits() == IR::BailOutOnIrregularLength) // guarantees no implicit calls
- );
- }
- case Js::OpCode::LdElemI_A:
- case Js::OpCode::LdMethodElem:
- case Js::OpCode::InlineArrayPop:
- isLdElem = true;
- // fall-through
- case Js::OpCode::StElemI_A:
- case Js::OpCode::StElemI_A_Strict:
- case Js::OpCode::InlineArrayPush:
- {
- if(!instr->HasBailOutInfo())
- {
- return true;
- }
- // The following bailout kinds already prevent implicit calls from happening. Any conditions that could trigger an
- // implicit call result in a pre-op bailout.
- const IR::BailOutKind bailOutKind = instr->GetBailOutKind();
- return
- !(
- (bailOutKind & ~IR::BailOutKindBits) == IR::BailOutConventionalTypedArrayAccessOnly ||
- bailOutKind & IR::BailOutOnArrayAccessHelperCall ||
- (isLdElem && bailOutKind & IR::BailOutConventionalNativeArrayAccessOnly)
- );
- }
- default:
- break;
- }
- if (OpCodeAttr::HasImplicitCall(instr->m_opcode))
- {
- // Operation has an implicit call regardless of operand attributes.
- return true;
- }
- IR::Opnd const * opnd = instr->GetDst();
- if (opnd)
- {
- switch (opnd->GetKind())
- {
- case IR::OpndKindReg:
- break;
- case IR::OpndKindSym:
- // No implicit call if we are just storing to a stack sym. Note that stores to non-configurable root
- // object fields may still need implicit call bailout. That's because a non-configurable field may still
- // become read-only and thus the store field will not take place (or throw in strict mode). Hence, we
- // can't optimize (e.g. copy prop) across such field stores.
- if (opnd->AsSymOpnd()->m_sym->IsStackSym())
- {
- return false;
- }
- if (opnd->AsSymOpnd()->IsPropertySymOpnd())
- {
- IR::PropertySymOpnd const * propertySymOpnd = opnd->AsSymOpnd()->AsPropertySymOpnd();
- if (!propertySymOpnd->MayHaveImplicitCall())
- {
- return false;
- }
- }
- return true;
- case IR::OpndKindIndir:
- return true;
- default:
- Assume(UNREACHED);
- }
- }
- opnd = instr->GetSrc1();
- if (opnd != nullptr && MaySrcNeedBailOnImplicitCall(opnd, src1Val))
- {
- return true;
- }
- opnd = instr->GetSrc2();
- if (opnd != nullptr && MaySrcNeedBailOnImplicitCall(opnd, src2Val))
- {
- return true;
- }
- return false;
- }
- void
- GlobOpt::GenerateBailAfterOperation(IR::Instr * *const pInstr, IR::BailOutKind kind)
- {
- Assert(pInstr);
- IR::Instr* instr = *pInstr;
- Assert(instr);
- IR::Instr * nextInstr = instr->GetNextRealInstrOrLabel();
- uint32 currentOffset = instr->GetByteCodeOffset();
- while (nextInstr->GetByteCodeOffset() == Js::Constants::NoByteCodeOffset ||
- nextInstr->GetByteCodeOffset() == currentOffset)
- {
- nextInstr = nextInstr->GetNextRealInstrOrLabel();
- }
- // This can happen due to break block removal
- while (nextInstr->GetByteCodeOffset() == Js::Constants::NoByteCodeOffset ||
- nextInstr->GetByteCodeOffset() < currentOffset)
- {
- nextInstr = nextInstr->GetNextRealInstrOrLabel();
- }
- IR::Instr * bailOutInstr = instr->ConvertToBailOutInstr(nextInstr, kind);
- if (this->currentBlock->GetLastInstr() == instr)
- {
- this->currentBlock->SetLastInstr(bailOutInstr);
- }
- FillBailOutInfo(this->currentBlock, bailOutInstr->GetBailOutInfo());
- *pInstr = bailOutInstr;
- }
- void
- GlobOpt::GenerateBailAtOperation(IR::Instr * *const pInstr, const IR::BailOutKind bailOutKind)
- {
- Assert(pInstr);
- IR::Instr * instr = *pInstr;
- Assert(instr);
- Assert(instr->GetByteCodeOffset() != Js::Constants::NoByteCodeOffset);
- Assert(bailOutKind != IR::BailOutInvalid);
- IR::Instr * bailOutInstr = instr->ConvertToBailOutInstr(instr, bailOutKind);
- if (this->currentBlock->GetLastInstr() == instr)
- {
- this->currentBlock->SetLastInstr(bailOutInstr);
- }
- FillBailOutInfo(currentBlock, bailOutInstr->GetBailOutInfo());
- *pInstr = bailOutInstr;
- }
- IR::Instr *
- GlobOpt::EnsureBailTarget(Loop * loop)
- {
- BailOutInfo * bailOutInfo = loop->bailOutInfo;
- IR::Instr * bailOutInstr = bailOutInfo->bailOutInstr;
- if (bailOutInstr == nullptr)
- {
- bailOutInstr = IR::BailOutInstr::New(Js::OpCode::BailTarget, IR::BailOutShared, bailOutInfo, bailOutInfo->bailOutFunc);
- loop->landingPad->InsertAfter(bailOutInstr);
- }
- return bailOutInstr;
- }
|