Skip to content

Commit

Permalink
Implement output tracing for generator bail-in
Browse files Browse the repository at this point in the history
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 ( (chakra-core#1.1), chakra-core#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 ( (chakra-core#1.1), chakra-core#2) offset: chakra-core#42
BailIn: Register #   3, value: 0x0001000000000004
```

```
BailOut: function: func68 ( (chakra-core#1.1), chakra-core#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 ( (chakra-core#1.1), chakra-core#2) offset: #006e
BailIn: No symbols reloaded
```
  • Loading branch information
nhat-nguyen committed Jun 27, 2019
1 parent 9ea3919 commit 451bba5
Show file tree
Hide file tree
Showing 11 changed files with 180 additions and 0 deletions.
14 changes: 14 additions & 0 deletions lib/Backend/IRBuilder.cpp
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions lib/Backend/JnHelperMethodList.h
Expand Up @@ -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
Expand Down
73 changes: 73 additions & 0 deletions lib/Backend/LinearScan.cpp
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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
3 changes: 3 additions & 0 deletions lib/Backend/LinearScan.h
Expand Up @@ -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);
Expand Down
26 changes: 26 additions & 0 deletions lib/Backend/Lower.cpp
Expand Up @@ -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();
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
{
Expand Down
4 changes: 4 additions & 0 deletions lib/Backend/Lower.h
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions lib/Common/ConfigFlagsList.h
Expand Up @@ -324,6 +324,7 @@ PHASE(All)
PHASE(FinishPartial)
PHASE(Host)
PHASE(BailOut)
PHASE(BailIn)
PHASE(RegexQc)
PHASE(RegexOptBT)
PHASE(InlineCache)
Expand Down
2 changes: 2 additions & 0 deletions lib/Runtime/ByteCode/OpCodes.h
Expand Up @@ -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)
Expand Down
14 changes: 14 additions & 0 deletions lib/Runtime/Language/InterpreterStackFrame.cpp
Expand Up @@ -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;
}

Expand Down
22 changes: 22 additions & 0 deletions lib/Runtime/Library/JavascriptGenerator.cpp
Expand Up @@ -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<AsyncGeneratorNextProcessor>(RecyclableObject* obj)
{
if (VarIs<JavascriptFunction>(obj))
Expand Down
17 changes: 17 additions & 0 deletions lib/Runtime/Library/JavascriptGenerator.h
Expand Up @@ -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);
Expand Down

0 comments on commit 451bba5

Please sign in to comment.