diff --git a/lib/Backend/BackwardPass.cpp b/lib/Backend/BackwardPass.cpp index cf9134dc156..910e12832d1 100644 --- a/lib/Backend/BackwardPass.cpp +++ b/lib/Backend/BackwardPass.cpp @@ -7832,10 +7832,12 @@ BackwardPass::ProcessInlineeEnd(IR::Instr* instr) } if (this->tag == Js::BackwardPhase) { - if (!GlobOpt::DoInlineArgsOpt(instr->m_func)) + // Commenting out to allow for argument length and argument[constant] optimization + // Will revisit in phase two + /*if (!GlobOpt::DoInlineArgsOpt(instr->m_func)) { return; - } + }*/ // This adds a use for function sym as part of InlineeStart & all the syms referenced by the args. // It ensure they do not get cleared from the copy prop sym map. diff --git a/lib/Backend/Func.cpp b/lib/Backend/Func.cpp index a772a606285..8db27b34d94 100644 --- a/lib/Backend/Func.cpp +++ b/lib/Backend/Func.cpp @@ -58,6 +58,7 @@ Func::Func(JitArenaAllocator *alloc, JITTimeWorkItem * workItem, m_bailoutReturnValueSym(nullptr), m_hasBailedOutSym(nullptr), m_inlineeFrameStartSym(nullptr), + inlineeStart(nullptr), m_regsUsed(0), m_fg(nullptr), m_labelCount(0), @@ -92,6 +93,7 @@ Func::Func(JitArenaAllocator *alloc, JITTimeWorkItem * workItem, hasInlinee(false), thisOrParentInlinerHasArguments(false), hasStackArgs(false), + hasArgLenAndConstOpt(false), hasImplicitParamLoad(false), hasThrow(false), hasNonSimpleParams(false), @@ -301,8 +303,10 @@ Func::Codegen(JitArenaAllocator *alloc, JITTimeWorkItem * workItem, Js::ScriptContextProfiler *const codeGenProfiler, const bool isBackgroundJIT) { bool rejit; + int rejitCounter = 0; do { + Assert(rejitCounter < 25); Func func(alloc, workItem, threadContextInfo, scriptContextInfo, outputData, epInfo, runtimeInfo, polymorphicInlineCacheInfo, codeGenAllocators, @@ -334,6 +338,8 @@ Func::Codegen(JitArenaAllocator *alloc, JITTimeWorkItem * workItem, case RejitReason::DisableStackArgOpt: outputData->disableStackArgOpt = TRUE; break; + case RejitReason::DisableStackArgLenAndConstOpt: + break; case RejitReason::DisableSwitchOptExpectingInteger: case RejitReason::DisableSwitchOptExpectingString: outputData->disableSwitchOpt = TRUE; @@ -360,6 +366,7 @@ Func::Codegen(JitArenaAllocator *alloc, JITTimeWorkItem * workItem, } rejit = true; + rejitCounter++; } // Either the entry point has a reference to the number now, or we failed to code gen and we // don't need to numbers, we can flush the completed page now. diff --git a/lib/Backend/Func.h b/lib/Backend/Func.h index c1f373a2a4c..0ae468d67b6 100644 --- a/lib/Backend/Func.h +++ b/lib/Backend/Func.h @@ -517,6 +517,17 @@ static const unsigned __int64 c_debugFillPattern8 = 0xcececececececece; m_inlineeFrameStartSym = sym; } + void SetInlineeStart(IR::Instr *inlineeStartInstr) + { + Assert(inlineeStart == nullptr); + inlineeStart = inlineeStartInstr; + } + + IR::Instr* GetInlineeStart() + { + return inlineeStart; + } + IR::SymOpnd *GetInlineeArgCountSlotOpnd() { return GetInlineeOpndAtOffset(Js::Constants::InlineeMetaArgIndex_Argc * MachPtr); @@ -721,6 +732,7 @@ static const unsigned __int64 c_debugFillPattern8 = 0xcececececececece; bool hasBailout: 1; bool hasBailoutInEHRegion : 1; bool hasStackArgs: 1; + bool hasArgLenAndConstOpt : 1; bool hasImplicitParamLoad : 1; // True if there is a load of CallInfo, FunctionObject bool hasThrow : 1; bool hasUnoptimizedArgumentsAccess : 1; // True if there are any arguments access beyond the simple case of this.apply pattern @@ -1030,6 +1042,7 @@ static const unsigned __int64 c_debugFillPattern8 = 0xcececececececece; Func * const topFunc; Func * const parentFunc; StackSym * m_inlineeFrameStartSym; + IR::Instr * inlineeStart; uint maxInlineeArgOutSize; const bool m_isBackgroundJIT; bool hasInstrNumber; diff --git a/lib/Backend/GlobOpt.cpp b/lib/Backend/GlobOpt.cpp index 88cafeac763..8a45907e45f 100644 --- a/lib/Backend/GlobOpt.cpp +++ b/lib/Backend/GlobOpt.cpp @@ -174,7 +174,7 @@ GlobOpt::Optimize() // Still need to run the dead store phase to calculate the live reg on back edge this->BackwardPass(Js::DeadStorePhase); - CannotAllocateArgumentsObjectOnStack(); + CannotAllocateArgumentsObjectOnStack(nullptr); return; } @@ -883,7 +883,7 @@ GlobOpt::ToTypeSpec(BVSparse *bv, BasicBlock *block, IRType t // instruction itself should disable arguments object optimization. if(block->globOptData.argObjSyms && block->globOptData.IsArgumentsSymID(id)) { - CannotAllocateArgumentsObjectOnStack(); + CannotAllocateArgumentsObjectOnStack(nullptr); } if (block->globOptData.liveVarSyms->Test(id)) @@ -1508,7 +1508,7 @@ GlobOpt::OptArguments(IR::Instr *instr) if (instr->m_func->GetJITFunctionBody()->GetInParamsCount() != 1 && !instr->m_func->IsStackArgsEnabled()) { - CannotAllocateArgumentsObjectOnStack(); + CannotAllocateArgumentsObjectOnStack(instr->m_func); } else { @@ -1523,7 +1523,7 @@ GlobOpt::OptArguments(IR::Instr *instr) // In the debug mode, we don't want to optimize away the aliases. Since we may have to show them on the inspection. if (((!AreFromSameBytecodeFunc(src1->AsRegOpnd(), dst->AsRegOpnd()) || this->currentBlock->loop) && instr->m_opcode != Js::OpCode::BytecodeArgOutCapture) || this->func->IsJitInDebugMode()) { - CannotAllocateArgumentsObjectOnStack(); + CannotAllocateArgumentsObjectOnStack(instr->m_func); return; } if(!dst->AsRegOpnd()->GetStackSym()->m_nonEscapingArgObjAlias) @@ -1546,7 +1546,7 @@ GlobOpt::OptArguments(IR::Instr *instr) } SymID id = 0; - + switch(instr->m_opcode) { case Js::OpCode::LdElemI_A: @@ -1557,7 +1557,7 @@ GlobOpt::OptArguments(IR::Instr *instr) if (indexOpnd && CurrentBlockData()->IsArgumentsSymID(indexOpnd->m_sym->m_id)) { // Pathological test cases such as a[arguments] - CannotAllocateArgumentsObjectOnStack(); + CannotAllocateArgumentsObjectOnStack(instr->m_func); return; } @@ -1646,7 +1646,7 @@ GlobOpt::OptArguments(IR::Instr *instr) WritePerfHint(PerfHints::HeapArgumentsCreated, instr->m_func, instr->GetByteCodeOffset()); } #endif - CannotAllocateArgumentsObjectOnStack(); + CannotAllocateArgumentsObjectOnStack(instr->m_func); return; } } @@ -1664,7 +1664,7 @@ GlobOpt::OptArguments(IR::Instr *instr) WritePerfHint(PerfHints::HeapArgumentsCreated, instr->m_func, instr->GetByteCodeOffset()); } #endif - CannotAllocateArgumentsObjectOnStack(); + CannotAllocateArgumentsObjectOnStack(instr->m_func); return; } } @@ -1683,7 +1683,7 @@ GlobOpt::OptArguments(IR::Instr *instr) WritePerfHint(PerfHints::HeapArgumentsModification, instr->m_func, instr->GetByteCodeOffset()); } #endif - CannotAllocateArgumentsObjectOnStack(); + CannotAllocateArgumentsObjectOnStack(instr->m_func); return; } } @@ -1697,7 +1697,7 @@ GlobOpt::OptArguments(IR::Instr *instr) WritePerfHint(PerfHints::HeapArgumentsModification, instr->m_func, instr->GetByteCodeOffset()); } #endif - CannotAllocateArgumentsObjectOnStack(); + CannotAllocateArgumentsObjectOnStack(instr->m_func); return; } CurrentBlockData()->ClearArgumentsSym(dst->AsRegOpnd()); @@ -2442,6 +2442,7 @@ GlobOpt::OptInstr(IR::Instr *&instr, bool* isInstrRemoved) OptimizeChecks(instr); OptArraySrc(&instr, &src1Val, &src2Val); OptNewScObject(&instr, src1Val); + OptArgLenAndConst(instr, &src1Val); instr = this->OptPeep(instr, src1Val, src2Val); @@ -13083,6 +13084,69 @@ GlobOpt::OptArraySrc(IR::Instr ** const instrRef, Value ** src1Val, Value ** src arraySrcOpt.Optimize(); } +void +GlobOpt::OptArgLenAndConst(IR::Instr* instr, Value** src1Val) +{ + if (instr->usesStackArgumentsObject && instr->IsInlined()) + { + IR::Opnd* src1 = instr->GetSrc1(); + auto replaceInstr = [&](IR::Opnd* newopnd) + { + this->CaptureByteCodeSymUses(instr); + instr->m_opcode = Js::OpCode::Ld_A; + instr->ReplaceSrc1(newopnd); + if (instr->HasBailOutInfo()) + { + instr->ClearBailOutInfo(); + } + *src1Val = this->OptSrc(instr->GetSrc1(), &instr); + instr->m_func->hasArgLenAndConstOpt = true; + }; + Assert(CurrentBlockData()->IsArgumentsOpnd(src1)); + switch(instr->m_opcode) + { + case Js::OpCode::LdLen_A: + { + IR::AddrOpnd* newopnd = IR::AddrOpnd::New(Js::TaggedInt::ToVarUnchecked(instr->m_func->actualCount - 1), IR::AddrOpndKindConstantVar, instr->m_func); + replaceInstr(newopnd); + break; + } + + case Js::OpCode::LdElemI_A: + { + IR::IndirOpnd* indirOpndSrc1 = src1->AsIndirOpnd(); + if (!indirOpndSrc1->GetIndexOpnd()) + { + int argIndex = indirOpndSrc1->GetOffset() + 1; + IR::Instr* defInstr = nullptr; + IR::Instr* inlineeStart = instr->m_func->GetInlineeStart(); + inlineeStart->IterateArgInstrs([&](IR::Instr* argInstr) { + StackSym *argSym = argInstr->GetDst()->AsSymOpnd()->m_sym->AsStackSym(); + if (argSym->GetArgSlotNum() - 1 == argIndex) + { + defInstr = argInstr; + return true; + } + return false; + }); + // If we cannot find the right instruction. I.E. When calling arguments[2] and no arguments were passed to the func + if (defInstr == nullptr) + { + IR::Opnd * undefined = IR::AddrOpnd::New(instr->m_func->GetScriptContextInfo()->GetUndefinedAddr(), IR::AddrOpndKindDynamicVar, instr->m_func, true); + undefined->SetValueType(ValueType::Undefined); + replaceInstr(undefined); + } + else + { + replaceInstr(defInstr->GetSrc1()); + } + } + break; + } + } + } +} + void GlobOpt::CaptureNoImplicitCallUses( IR::Opnd *opnd, @@ -15609,7 +15673,7 @@ GlobOpt::TrackArgumentsObject() { if (PHASE_OFF(Js::StackArgOptPhase, this->func)) { - this->CannotAllocateArgumentsObjectOnStack(); + this->CannotAllocateArgumentsObjectOnStack(nullptr); return false; } @@ -15617,8 +15681,15 @@ GlobOpt::TrackArgumentsObject() } void -GlobOpt::CannotAllocateArgumentsObjectOnStack() +GlobOpt::CannotAllocateArgumentsObjectOnStack(Func * curFunc) { + if (curFunc != nullptr && curFunc->hasArgLenAndConstOpt) + { + Assert(!curFunc->GetJITOutput()->GetOutputData()->disableStackArgOpt); + curFunc->GetJITOutput()->GetOutputData()->disableStackArgOpt = true; + throw Js::RejitException(RejitReason::DisableStackArgLenAndConstOpt); + } + func->SetHasStackArgs(false); #ifdef ENABLE_DEBUG_CONFIG_OPTIONS diff --git a/lib/Backend/GlobOpt.h b/lib/Backend/GlobOpt.h index a6a1fb293af..309e57d35b0 100644 --- a/lib/Backend/GlobOpt.h +++ b/lib/Backend/GlobOpt.h @@ -688,6 +688,7 @@ class GlobOpt IR::Instr* CreateBoundsCheckInstr(IR::Opnd* lowerBound, IR::Opnd* upperBound, int offset, IR::BailOutKind bailoutkind, BailOutInfo* bailoutInfo, Func* func); IR::Instr* AttachBoundsCheckData(IR::Instr* instr, IR::Opnd* lowerBound, IR::Opnd* upperBound, int offset); void OptArraySrc(IR::Instr **const instrRef, Value ** src1Val, Value ** src2Val); + void OptArgLenAndConst(IR::Instr* instr, Value** src1Val); private: void TrackIntSpecializedAddSubConstant(IR::Instr *const instr, const AddSubConstantInfo *const addSubConstantInfo, Value *const dstValue, const bool updateSourceBounds); @@ -911,7 +912,7 @@ class GlobOpt void UpdateObjPtrValueType(IR::Opnd * opnd, IR::Instr * instr); bool TrackArgumentsObject(); - void CannotAllocateArgumentsObjectOnStack(); + void CannotAllocateArgumentsObjectOnStack(Func * curFunc); #if DBG bool IsPropertySymId(SymID symId) const; diff --git a/lib/Backend/GlobOptBailOut.cpp b/lib/Backend/GlobOptBailOut.cpp index 71ae7d1d699..0ef805ae649 100644 --- a/lib/Backend/GlobOptBailOut.cpp +++ b/lib/Backend/GlobOptBailOut.cpp @@ -849,7 +849,7 @@ void GlobOpt::EndTrackingOfArgObjSymsForInlinee() // 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(); + CannotAllocateArgumentsObjectOnStack(this->currentBlock->globOptData.curFunc); } else { diff --git a/lib/Backend/GlobOptBlockData.cpp b/lib/Backend/GlobOptBlockData.cpp index 31ad9566b1a..2d2fbc7a3a0 100644 --- a/lib/Backend/GlobOptBlockData.cpp +++ b/lib/Backend/GlobOptBlockData.cpp @@ -660,7 +660,7 @@ GlobOptBlockData::MergeBlockData( { if (!this->argObjSyms->Equal(fromData->argObjSyms)) { - this->globOpt->CannotAllocateArgumentsObjectOnStack(); + this->globOpt->CannotAllocateArgumentsObjectOnStack(nullptr); } } diff --git a/lib/Backend/Inline.cpp b/lib/Backend/Inline.cpp index 20d3cd00a07..8bcd497647f 100644 --- a/lib/Backend/Inline.cpp +++ b/lib/Backend/Inline.cpp @@ -1360,12 +1360,13 @@ void Inline::InsertOneInlinee(IR::Instr* callInstr, IR::RegOpnd* returnValueOpnd Js::ArgSlot actualCount = MapActuals(currentCallInstr, argOuts, Js::InlineeCallInfo::MaxInlineeArgoutCount, inlinee, (Js::ProfileId)callInstr->AsProfiledInstr()->u.profileId, &stackArgsArgOutExpanded); Assert(actualCount > 0); MapFormals(inlinee, argOuts, funcBody->GetInParamsCount(), actualCount, returnValueOpnd, currentCallInstr->GetSrc1(), symCallerThis, stackArgsArgOutExpanded, fixedFunctionSafeThis, argOuts); + inlinee->SetInlineeStart(currentCallInstr); currentCallInstr->m_func = inlinee; // Put the meta arguments that the stack walker expects to find on the stack. // As all the argouts are shared among the inlinees, do this only once. SetupInlineeFrame(inlinee, currentCallInstr, actualCount, currentCallInstr->GetSrc1()); - + IR::Instr* inlineeEndInstr = IR::Instr::New(Js::OpCode::InlineeEnd, inlinee); inlineeEndInstr->SetByteCodeOffset(inlinee->m_tailInstr->GetPrevRealInstr()); inlineeEndInstr->SetSrc1(IR::IntConstOpnd::New(actualCount + Js::Constants::InlineeMetaArgCount, TyInt32, inlinee)); @@ -3967,6 +3968,7 @@ Inline::InlineFunctionCommon(IR::Instr *callInstr, bool originalCallTargetOpndIs callInstr->m_opcode = Js::OpCode::InlineeStart; // Set it to belong to the inlinee, so that we can use the actual count when lowering InlineeStart + inlinee->SetInlineeStart(callInstr); callInstr->m_func = inlinee; callInstr->SetDst(IR::RegOpnd::New(returnValueOpnd ? returnValueOpnd->GetType() : TyVar, inlinee)); // Put the meta arguments that the stack walker expects to find on the stack. diff --git a/lib/Common/Common/RejitReasons.h b/lib/Common/Common/RejitReasons.h index 0c35143eeb7..285c0c5221d 100644 --- a/lib/Common/Common/RejitReasons.h +++ b/lib/Common/Common/RejitReasons.h @@ -49,4 +49,5 @@ REJIT_REASON(ModByPowerOf2) REJIT_REASON(NoProfile) REJIT_REASON(PowIntIntTypeSpecDisabled) REJIT_REASON(DisableStackArgOpt) +REJIT_REASON(DisableStackArgLenAndConstOpt) REJIT_REASON(OptimizeTryFinallyDisabled)