diff --git a/lib/Backend/IRBuilder.cpp b/lib/Backend/IRBuilder.cpp index 2cf38943a8c..e31c764c631 100644 --- a/lib/Backend/IRBuilder.cpp +++ b/lib/Backend/IRBuilder.cpp @@ -1891,6 +1891,20 @@ IRBuilder::BuildReg2(Js::OpCode newOpcode, uint32 offset, Js::RegSlot R0, Js::Re this->AddInstr(bailInLabel, offset); this->m_func->AddYieldOffsetResumeLabel(nextOffset, bailInLabel); + +#ifdef ENABLE_DEBUG_CONFIG_OPTIONS + if (PHASE_TRACE(Js::Phase::BailInPhase, this->m_func)) + { + IR::LabelInstr* traceBailInLabel = IR::LabelInstr::New(Js::OpCode::GeneratorOutputBailInTraceLabel, m_func); + traceBailInLabel->m_hasNonBranchRef = true; // set to true so that we don't move this label around + LABELNAMESET(traceBailInLabel, "OutputBailInTrace"); + this->AddInstr(traceBailInLabel, offset); + + IR::Instr* traceBailIn = IR::Instr::New(Js::OpCode::GeneratorOutputBailInTrace, m_func); + this->AddInstr(traceBailIn, offset); + } +#endif + // This label indicates the section where we start loading the ResumeYieldData on the stack // that comes from either .next(), .return(), or .throw() to the right symbol and finally // extract its data through Op_ResumeYield diff --git a/lib/Backend/JnHelperMethodList.h b/lib/Backend/JnHelperMethodList.h index ab92bc44c07..0a577e18305 100644 --- a/lib/Backend/JnHelperMethodList.h +++ b/lib/Backend/JnHelperMethodList.h @@ -543,6 +543,10 @@ HELPERCALL(Await, Js::InterpreterStackFrame::OP_Await, Att HELPERCALL(CreateInterpreterStackFrameForGenerator, Js::InterpreterStackFrame::CreateInterpreterStackFrameForGenerator, AttrCanNotBeReentrant) +#ifdef ENABLE_DEBUG_CONFIG_OPTIONS +HELPERCALL(OutputGeneratorBailInTrace, Js::JavascriptGenerator::OutputBailInTrace, AttrCanNotBeReentrant) +#endif + #if DBG HELPERCALL(IntRangeCheckFailure, Js::JavascriptNativeOperators::IntRangeCheckFailure, AttrCanNotBeReentrant) #endif diff --git a/lib/Backend/LinearScan.cpp b/lib/Backend/LinearScan.cpp index 18c62d843a0..7069b908b05 100644 --- a/lib/Backend/LinearScan.cpp +++ b/lib/Backend/LinearScan.cpp @@ -5039,6 +5039,15 @@ IR::Instr* LinearScan::GeneratorBailIn::GenerateBailIn(IR::Instr* resumeLabelIns this->InsertRestoreSymbols(bailOutInfo->byteCodeUpwardExposedUsed, insertionPoint); Assert(!this->func->IsStackArgsEnabled()); +#ifdef ENABLE_DEBUG_CONFIG_OPTIONS + if (PHASE_TRACE(Js::Phase::BailInPhase, this->func)) + { + IR::Instr* insertBailInTraceBefore = instrAfter; + Assert(insertBailInTraceBefore->m_opcode == Js::OpCode::GeneratorOutputBailInTraceLabel); + this->InsertBailInTrace(bailOutInfo->byteCodeUpwardExposedUsed, insertBailInTraceBefore->m_next); + } +#endif + return instrAfter; } @@ -5195,3 +5204,67 @@ uint32 LinearScan::GeneratorBailIn::GetOffsetFromInterpreterStackFrame(Js::RegSl return regSlot * sizeof(Js::Var) + Js::InterpreterStackFrame::GetOffsetOfLocals(); } } + +#ifdef ENABLE_DEBUG_CONFIG_OPTIONS +void LinearScan::GeneratorBailIn::InsertBailInTrace(BVSparse* symbols, IR::Instr* insertBeforeInstr) +{ + IR::RegOpnd* traceBailInSymbolsArrayRegOpnd = this->interpreterFrameRegOpnd; + + // Load JavascriptGenerator->bailInSymbolsTraceArray + { + LinearScan::InsertMove(traceBailInSymbolsArrayRegOpnd, this->CreateGeneratorObjectOpnd(), insertBeforeInstr); + IR::IndirOpnd* traceBailInSymbolsArrayIndirOpnd = IR::IndirOpnd::New(traceBailInSymbolsArrayRegOpnd, Js::JavascriptGenerator::GetBailInSymbolsTraceArrayOffset(), TyMachPtr, this->func); + LinearScan::InsertMove(traceBailInSymbolsArrayRegOpnd, traceBailInSymbolsArrayIndirOpnd, insertBeforeInstr); + } + + int count = 0; + FOREACH_BITSET_IN_SPARSEBV(symId, symbols) + { + StackSym* stackSym = this->func->m_symTable->FindStackSym(symId); + Lifetime* lifetime = stackSym->scratch.linearScan.lifetime; + + if (!this->NeedsReloadingValueWhenBailIn(stackSym, lifetime)) + { + continue; + } + + int offset = sizeof(Js::JavascriptGenerator::BailInSymbol) * count; + + // Assign JavascriptGenerator->bailInSymbolsTraceArray[count]->id + { + IR::IndirOpnd* idIndirOpnd = IR::IndirOpnd::New(traceBailInSymbolsArrayRegOpnd, offset + Js::JavascriptGenerator::BailInSymbol::GetBailInSymbolIdOffset(), TyMachPtr, this->func); + IR::IntConstOpnd* idConstOpnd = IR::IntConstOpnd::New(stackSym->m_id, TyUint8, this->func); + LinearScan::InsertMove(idIndirOpnd, idConstOpnd, insertBeforeInstr); + } + + // Assign JavascriptGenerator->bailInSymbolsTraceArray[count]->value + { + IR::IndirOpnd* valueIndirOpnd = IR::IndirOpnd::New(traceBailInSymbolsArrayRegOpnd, offset + Js::JavascriptGenerator::BailInSymbol::GetBailInSymbolValueOffset(), TyMachPtr, this->func); + IR::Opnd* srcOpnd; + if (lifetime->isSpilled) + { + IR::SymOpnd* stackSymOpnd = IR::SymOpnd::New(stackSym, stackSym->GetType(), this->func); + LinearScan::InsertMove(this->tempRegOpnd, stackSymOpnd, insertBeforeInstr); + srcOpnd = this->tempRegOpnd; + } + else + { + srcOpnd = IR::RegOpnd::New(stackSym, stackSym->GetType(), this->func); + srcOpnd->AsRegOpnd()->SetReg(lifetime->reg); + } + LinearScan::InsertMove(valueIndirOpnd, srcOpnd, insertBeforeInstr); + } + + count++; + } + NEXT_BITSET_IN_SPARSEBV; + + // Assign JavascriptGenerator->bailInSymbolsTraceArrayCount + { + LinearScan::InsertMove(this->tempRegOpnd, this->CreateGeneratorObjectOpnd(), insertBeforeInstr); + IR::IndirOpnd* traceBailInSymbolsArrayCountIndirOpnd = IR::IndirOpnd::New(this->tempRegOpnd, Js::JavascriptGenerator::GetBailInSymbolsTraceArrayCountOffset(), TyMachPtr, this->func); + IR::IntConstOpnd* countOpnd = IR::IntConstOpnd::New(count, TyInt32, this->func); + LinearScan::InsertMove(traceBailInSymbolsArrayCountIndirOpnd, countOpnd, insertBeforeInstr); + } +} +#endif diff --git a/lib/Backend/LinearScan.h b/lib/Backend/LinearScan.h index d56bb253638..626a2199ca9 100644 --- a/lib/Backend/LinearScan.h +++ b/lib/Backend/LinearScan.h @@ -272,6 +272,9 @@ class LinearScan void InsertRestoreSymbols(BVSparse* symbols, BailInInsertionPoint& insertionPoint); +#ifdef ENABLE_DEBUG_CONFIG_OPTIONS + void InsertBailInTrace(BVSparse* symbols, IR::Instr* insertBeforeInstr); +#endif public: GeneratorBailIn(Func* func, LinearScan* linearScan); IR::Instr* GenerateBailIn(IR::Instr* resumeLabelInstr, BailOutInfo* bailOutInfo); diff --git a/lib/Backend/Lower.cpp b/lib/Backend/Lower.cpp index 9fa84ab6113..bc7779bc289 100644 --- a/lib/Backend/Lower.cpp +++ b/lib/Backend/Lower.cpp @@ -3031,6 +3031,14 @@ Lowerer::LowerRange(IR::Instr *instrStart, IR::Instr *instrEnd, bool defaultDoFa break; } +#ifdef ENABLE_DEBUG_CONFIG_OPTIONS + case Js::OpCode::GeneratorOutputBailInTrace: + { + this->m_lowerGeneratorHelper.LowerGeneratorTraceBailIn(instr); + break; + } +#endif + case Js::OpCode::GeneratorResumeJumpTable: { this->m_lowerGeneratorHelper.InsertBailOutForElidedYield(); @@ -3140,6 +3148,9 @@ Lowerer::LowerRange(IR::Instr *instrStart, IR::Instr *instrEnd, bool defaultDoFa instrPrev = this->LowerStPropIdArrFromVar(instr); break; +#ifdef ENABLE_DEBUG_CONFIG_OPTIONS + case Js::OpCode::GeneratorOutputBailInTraceLabel: +#endif case Js::OpCode::GeneratorBailInLabel: case Js::OpCode::GeneratorResumeYieldLabel: case Js::OpCode::GeneratorEpilogueFrameNullOut: @@ -26550,6 +26561,9 @@ Lowerer::ValidOpcodeAfterLower(IR::Instr* instr, Func * func) Assert(func->HasTry() && func->DoOptimizeTry()); return func && !func->isPostFinalLower; //Lowered in FinalLower phase +#ifdef ENABLE_DEBUG_CONFIG_OPTIONS + case Js::OpCode::GeneratorOutputBailInTraceLabel: +#endif case Js::OpCode::GeneratorBailInLabel: case Js::OpCode::GeneratorResumeYieldLabel: case Js::OpCode::GeneratorEpilogueFrameNullOut: @@ -29286,6 +29300,18 @@ Lowerer::LowerGeneratorHelper::LowerCreateInterpreterStackFrameForGenerator(IR:: this->lowererMD.ChangeToHelperCall(instr, IR::HelperCreateInterpreterStackFrameForGenerator); } +#ifdef ENABLE_DEBUG_CONFIG_OPTIONS +void +Lowerer::LowerGeneratorHelper::LowerGeneratorTraceBailIn(IR::Instr* instr) +{ + StackSym* genParamSym = StackSym::NewParamSlotSym(1, instr->m_func); + instr->m_func->SetArgOffset(genParamSym, LowererMD::GetFormalParamOffset() * MachPtr); + IR::SymOpnd* genParamOpnd = IR::SymOpnd::New(genParamSym, TyMachPtr, instr->m_func); + this->lowererMD.LoadHelperArgument(instr, genParamOpnd); + this->lowererMD.ChangeToHelperCall(instr, IR::HelperOutputGeneratorBailInTrace); +} +#endif + IR::SymOpnd* Lowerer::LowerGeneratorHelper::CreateResumeYieldDataOpnd() const { diff --git a/lib/Backend/Lower.h b/lib/Backend/Lower.h index 3fe919ff51b..f829c293343 100644 --- a/lib/Backend/Lower.h +++ b/lib/Backend/Lower.h @@ -893,6 +893,10 @@ class Lowerer void LowerResumeGenerator(IR::Instr* instr); void LowerYield(IR::Instr* instr); void LowerGeneratorLoadResumeYieldData(IR::Instr* instr); + +#ifdef ENABLE_DEBUG_CONFIG_OPTIONS + void LowerGeneratorTraceBailIn(IR::Instr* instr); +#endif }; LowerGeneratorHelper m_lowerGeneratorHelper; diff --git a/lib/Common/ConfigFlagsList.h b/lib/Common/ConfigFlagsList.h index c47b669963a..4d32ad37025 100644 --- a/lib/Common/ConfigFlagsList.h +++ b/lib/Common/ConfigFlagsList.h @@ -324,6 +324,7 @@ PHASE(All) PHASE(FinishPartial) PHASE(Host) PHASE(BailOut) + PHASE(BailIn) PHASE(RegexQc) PHASE(RegexOptBT) PHASE(InlineCache) diff --git a/lib/Runtime/ByteCode/OpCodes.h b/lib/Runtime/ByteCode/OpCodes.h index 0fca627e86c..95a2694bed6 100755 --- a/lib/Runtime/ByteCode/OpCodes.h +++ b/lib/Runtime/ByteCode/OpCodes.h @@ -853,6 +853,8 @@ MACRO_BACKEND_ONLY(LazyBailOutThunkLabel, Empty, None) MACRO_BACKEND_ONLY(GeneratorResumeJumpTable, Reg1, OpSideEffect) // OpSideEffect because we don't want this to be deadstored MACRO_BACKEND_ONLY(GeneratorCreateInterpreterStackFrame, Reg1, OpSideEffect) // OpSideEffect because we don't want this to be deadstored MACRO_BACKEND_ONLY(GeneratorLoadResumeYieldData, Reg1, OpSideEffect) // OpSideEffect because we don't want this to be deadstored +MACRO_BACKEND_ONLY(GeneratorOutputBailInTrace, Empty, OpSideEffect) // OpSideEffect because we don't want this to be deadstored +MACRO_BACKEND_ONLY(GeneratorOutputBailInTraceLabel, Empty, None) // OpSideEffect because we don't want this to be deadstored MACRO_BACKEND_ONLY(GeneratorBailInLabel, Empty, None) MACRO_BACKEND_ONLY(GeneratorResumeYieldLabel, Empty, None) MACRO_BACKEND_ONLY(GeneratorEpilogueFrameNullOut, Empty, None) diff --git a/lib/Runtime/Language/InterpreterStackFrame.cpp b/lib/Runtime/Language/InterpreterStackFrame.cpp index 4ad8bf08f1b..41beeb1f5a2 100644 --- a/lib/Runtime/Language/InterpreterStackFrame.cpp +++ b/lib/Runtime/Language/InterpreterStackFrame.cpp @@ -1844,6 +1844,20 @@ namespace Js newInstance->m_reader.Create(executeFunction); generator->SetFrame(newInstance, varSizeInBytes); + + // Moving this to when we create the generator instance in the first place would be nice. + // But at that point the function might not have been parsed yet, so we don't have the locals count. + // We are also allocating more space than we actually need because we shouldn't need to + // reload all the symbols when bailing in. +#ifdef ENABLE_DEBUG_CONFIG_OPTIONS + if (PHASE_TRACE(Js::Phase::BailInPhase, function->GetFunctionBody())) + { + generator->bailInSymbolsTraceArray = (Js::JavascriptGenerator::BailInSymbol*) RecyclerNewArrayLeafZ( + functionScriptContext->GetRecycler(), Js::JavascriptGenerator::BailInSymbol, executeFunction->GetFunctionBody()->GetLocalsCount() + ); + } +#endif + return newInstance; } diff --git a/lib/Runtime/Library/JavascriptGenerator.cpp b/lib/Runtime/Library/JavascriptGenerator.cpp index ab73a6f32db..c7e9e902376 100644 --- a/lib/Runtime/Library/JavascriptGenerator.cpp +++ b/lib/Runtime/Library/JavascriptGenerator.cpp @@ -672,6 +672,28 @@ namespace Js return function->GetScriptContext()->GetLibrary()->GetUndefined(); } +#ifdef ENABLE_DEBUG_CONFIG_OPTIONS + void JavascriptGenerator::OutputBailInTrace(JavascriptGenerator* generator) + { + char16 debugStringBuffer[MAX_FUNCTION_BODY_DEBUG_STRING_SIZE]; + FunctionBody *fnBody = generator->scriptFunction->GetFunctionBody(); + Output::Print(_u("BailIn: function: %s (%s) offset: #%04x\n"), fnBody->GetDisplayName(), fnBody->GetDebugNumberSet(debugStringBuffer), generator->frame->m_reader.GetCurrentOffset()); + + if (generator->bailInSymbolsTraceArrayCount == 0) + { + Output::Print(_u("BailIn: No symbols reloaded\n"), fnBody->GetDisplayName(), fnBody->GetDebugNumberSet(debugStringBuffer)); + } + else + { + for (int i = 0; i < generator->bailInSymbolsTraceArrayCount; i++) + { + const JavascriptGenerator::BailInSymbol& symbol = generator->bailInSymbolsTraceArray[i]; + Output::Print(_u("BailIn: Register #%4d, value: 0x%p\n"), symbol.id, symbol.value); + } + } + } +#endif + template <> bool VarIsImpl(RecyclableObject* obj) { if (VarIs(obj)) diff --git a/lib/Runtime/Library/JavascriptGenerator.h b/lib/Runtime/Library/JavascriptGenerator.h index 55284bcc20f..04cc5cd2bcb 100644 --- a/lib/Runtime/Library/JavascriptGenerator.h +++ b/lib/Runtime/Library/JavascriptGenerator.h @@ -163,6 +163,23 @@ namespace Js virtual void ExtractSnapObjectDataInto(TTD::NSSnapObjects::SnapObject* objData, TTD::SlabAllocator& alloc) override; //virtual void ProcessCorePaths() override; #endif + +#ifdef ENABLE_DEBUG_CONFIG_OPTIONS + public: + struct BailInSymbol { + uint32 id; + Var value; + static uint32 GetBailInSymbolIdOffset() { return offsetof(BailInSymbol, id); } + static uint32 GetBailInSymbolValueOffset() { return offsetof(BailInSymbol, value); } + }; + + Field(BailInSymbol*) bailInSymbolsTraceArray = nullptr; + Field(int) bailInSymbolsTraceArrayCount = 0; + + static uint32 GetBailInSymbolsTraceArrayOffset() { return offsetof(JavascriptGenerator, bailInSymbolsTraceArray); } + static uint32 GetBailInSymbolsTraceArrayCountOffset() { return offsetof(JavascriptGenerator, bailInSymbolsTraceArrayCount); } + static void OutputBailInTrace(JavascriptGenerator* generator); +#endif }; template <> bool VarIsImpl(RecyclableObject* obj);