Skip to content
Permalink
Browse files

[MERGE #6183 @nhat-nguyen] Implement output tracing for generator bai…

…l-in

Merge pull request #6183 from nhat-nguyen:trace

Since bailing in happens in the jit'd code, we have to generate code to output the trace. If tracing is enabled, we will fill the array of bail-in symbols in the generator instance (`bailInSymbolsTraceArray`) with their ids and values and finally output them with a call to a runtime helper.

`-trace:bailin` together with `-trace:bailout -verbose` can help us easily debug jit'd generators by comparing the values when bailing out for `yield` and bailing in:

```
BailOut: function: func68 ( (#1.1), #2) offset: #003f Opcode: Yield Kind: BailOutForGeneratorYield
BailOut:   Register #  0: Not live
BailOut:   Register #  1: Constant table
BailOut:   Register #  2: Register r15     16, value: 0x0000023CE132EEA0 (Yield Return Value)
BailOut:   Register #  3: Register r12     13, value: 0x0001000000000004
BailOut:   Return Value: 0x0000023CE132EEA0

BailIn: function: func68 ( (#1.1), #2) offset: #42
BailIn: Register #   3, value: 0x0001000000000004
```

```
BailOut: function: func68 ( (#1.1), #2) offset: #006b Opcode: Yield Kind: BailOutForGeneratorYield
BailOut:   Register #  0: Not live
BailOut:   Register #  1: Constant table
BailOut:   Register #  3: Register r15     16, value: 0x0000023CE133E060 (Yield Return Value)
BailOut:   Return Value: 0x0000023CE133E060

BailIn: function: func68 ( (#1.1), #2) offset: #006e
BailIn: No symbols reloaded
```
  • Loading branch information...
nhat-nguyen committed Jul 2, 2019
2 parents 94aed86 + 38df3a6 commit 773e5f9d713df13c7f29b08a902b24e57e5f2247
@@ -1909,6 +1909,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
@@ -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
@@ -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<JitArenaAllocator>* 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
@@ -272,6 +272,9 @@ class LinearScan

void InsertRestoreSymbols(BVSparse<JitArenaAllocator>* symbols, BailInInsertionPoint& insertionPoint);

#ifdef ENABLE_DEBUG_CONFIG_OPTIONS
void InsertBailInTrace(BVSparse<JitArenaAllocator>* symbols, IR::Instr* insertBeforeInstr);
#endif
public:
GeneratorBailIn(Func* func, LinearScan* linearScan);
IR::Instr* GenerateBailIn(IR::Instr* resumeLabelInstr, BailOutInfo* bailOutInfo);
@@ -3029,6 +3029,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();
@@ -3138,6 +3146,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:
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:
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
{
@@ -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;
@@ -324,6 +324,7 @@ PHASE(All)
PHASE(FinishPartial)
PHASE(Host)
PHASE(BailOut)
PHASE(BailIn)
PHASE(RegexQc)
PHASE(RegexOptBT)
PHASE(InlineCache)
@@ -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)
MACRO_BACKEND_ONLY(GeneratorBailInLabel, Empty, None)
MACRO_BACKEND_ONLY(GeneratorResumeYieldLabel, Empty, None)
MACRO_BACKEND_ONLY(GeneratorEpilogueFrameNullOut, Empty, None)
@@ -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;
}

@@ -673,6 +673,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<AsyncGeneratorNextProcessor>(RecyclableObject* obj)
{
if (VarIs<JavascriptFunction>(obj))
@@ -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<JavascriptGenerator>(RecyclableObject* obj);

0 comments on commit 773e5f9

Please sign in to comment.
You can’t perform that action at this time.