Skip to content

Commit 7c8040b

Browse files
committed
Move bail-in implementation to common LinearScan
1 parent 607f220 commit 7c8040b

File tree

5 files changed

+300
-301
lines changed

5 files changed

+300
-301
lines changed

lib/Backend/LinearScan.cpp

Lines changed: 245 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,7 @@ LinearScan::RegAlloc()
306306

307307
if (insertBailInAfter == instr)
308308
{
309-
instrNext = linearScanMD.GenerateBailInForGeneratorYield(instr, bailOutInfoForBailIn);
309+
instrNext = this->bailIn.GenerateBailIn(instr, bailOutInfoForBailIn);
310310
insertBailInAfter = nullptr;
311311
bailOutInfoForBailIn = nullptr;
312312
}
@@ -3361,18 +3361,7 @@ LinearScan::KillImplicitRegs(IR::Instr *instr)
33613361

33623362
if (instr->m_opcode == Js::OpCode::Yield)
33633363
{
3364-
#if defined(_M_X64)
3365-
RegNum regs[] = { RegRAX, RegRCX };
3366-
#else
3367-
RegNum regs[] = { RegEAX, RegECX };
3368-
#endif
3369-
for (int i = 0; i < 2; i++)
3370-
{
3371-
this->SpillReg(regs[i]);
3372-
this->tempRegs.Clear(regs[i]);
3373-
this->RecordLoopUse(nullptr, regs[i]);
3374-
}
3375-
3364+
this->bailIn.SpillRegsForBailIn();
33763365
return;
33773366
}
33783367
#endif
@@ -4953,3 +4942,246 @@ LinearScan::ProcessLazyBailOut(IR::Instr *instr)
49534942
this->FillBailOutRecord(instr);
49544943
}
49554944
}
4945+
4946+
LinearScan::GeneratorBailIn::GeneratorBailIn(Func* func, LinearScan* linearScan) :
4947+
func { func },
4948+
linearScan { linearScan },
4949+
jitFnBody { func->GetJITFunctionBody() },
4950+
initializedRegs { func->m_alloc },
4951+
regs {
4952+
#if defined(_M_X64)
4953+
RegRAX, RegRCX
4954+
#elif defined(_M_IX86)
4955+
RegEAX, RegECX
4956+
#endif
4957+
},
4958+
interpreterFrameRegOpnd { IR::RegOpnd::New(nullptr, regs[0], TyMachPtr, func) },
4959+
tempRegOpnd { IR::RegOpnd::New(nullptr, regs[1], TyVar, func) }
4960+
{
4961+
// The yield register holds the evaluated value of the expression passed as
4962+
// the parameter to .next(), this can be obtained from the generator object itself,
4963+
// so no need to restore.
4964+
this->initializedRegs.Set(this->jitFnBody->GetYieldReg());
4965+
4966+
// The environment is loaded before the resume jump table, no need to restore either.
4967+
this->initializedRegs.Set(this->jitFnBody->GetEnvReg());
4968+
}
4969+
4970+
void LinearScan::GeneratorBailIn::SpillRegsForBailIn()
4971+
{
4972+
for (int i = 0; i < GeneratorBailIn::regNum; i++)
4973+
{
4974+
this->linearScan->SpillReg(this->regs[i]);
4975+
this->linearScan->tempRegs.Clear(this->regs[i]);
4976+
this->linearScan->RecordLoopUse(nullptr, this->regs[i]);
4977+
}
4978+
}
4979+
4980+
// Restores the live stack locations followed by the live registers from
4981+
// the interpreter's register slots.
4982+
// RecordDefs each live register that is restored.
4983+
//
4984+
// Generates the following code:
4985+
//
4986+
// PUSH rax ; if needed
4987+
// PUSH rcx ; if needed
4988+
//
4989+
// MOV rax, param0
4990+
// MOV rax, [rax + JavascriptGenerator::GetFrameOffset()]
4991+
//
4992+
// for each live stack location, sym
4993+
//
4994+
// MOV rcx, [rax + regslot offset]
4995+
// MOV sym(stack location), rcx
4996+
//
4997+
// for each live register, sym (rax is restore last if it is live)
4998+
//
4999+
// MOV sym(register), [rax + regslot offset]
5000+
//
5001+
// POP rax; if needed
5002+
// POP rcx; if needed
5003+
IR::Instr* LinearScan::GeneratorBailIn::GenerateBailIn(IR::Instr* resumeLabelInstr, BailOutInfo* bailOutInfo)
5004+
{
5005+
Assert(!bailOutInfo->capturedValues || bailOutInfo->capturedValues->constantValues.Empty());
5006+
Assert(!bailOutInfo->capturedValues || bailOutInfo->capturedValues->copyPropSyms.Empty());
5007+
Assert(!bailOutInfo->liveLosslessInt32Syms || bailOutInfo->liveLosslessInt32Syms->IsEmpty());
5008+
Assert(!bailOutInfo->liveFloat64Syms || bailOutInfo->liveFloat64Syms->IsEmpty());
5009+
5010+
IR::Instr* instrAfter = resumeLabelInstr->m_next;
5011+
5012+
// 1) Load the generator object that was passed as one of the arguments to the jitted frame
5013+
LinearScan::InsertMove(this->interpreterFrameRegOpnd, this->CreateGeneratorObjectOpnd(), instrAfter);
5014+
5015+
// 2) Gets the InterpreterStackFrame pointer into rax
5016+
IR::IndirOpnd* generatorFrameOpnd = IR::IndirOpnd::New(this->interpreterFrameRegOpnd, Js::JavascriptGenerator::GetFrameOffset(), TyMachPtr, this->func);
5017+
LinearScan::InsertMove(this->interpreterFrameRegOpnd, generatorFrameOpnd, instrAfter);
5018+
5019+
// 3) Put the Javascript's `arguments` object, which is stored in the interpreter frame, to the jit's stack slot if needed
5020+
// See BailOutRecord::RestoreValues
5021+
if (this->func->HasArgumentSlot())
5022+
{
5023+
IR::IndirOpnd* generatorArgumentsOpnd = IR::IndirOpnd::New(this->interpreterFrameRegOpnd, Js::InterpreterStackFrame::GetOffsetOfArguments(), TyMachPtr, this->func);
5024+
LinearScan::InsertMove(this->tempRegOpnd, generatorArgumentsOpnd, instrAfter);
5025+
LinearScan::InsertMove(LowererMD::CreateStackArgumentsSlotOpnd(this->func), this->tempRegOpnd, instrAfter);
5026+
}
5027+
5028+
BailInInsertionPoint insertionPoint
5029+
{
5030+
nullptr, /* raxRestoreInstr */
5031+
instrAfter, /* instrInsertStackSym */
5032+
instrAfter /* instrInsertRegSym */
5033+
};
5034+
5035+
// 4) Restore symbols
5036+
// - We don't need to restore argObjSyms because StackArgs is currently not enabled
5037+
// Commented out here in case we do want to enable it in the future:
5038+
// this->InsertRestoreSymbols(bailOutInfo->capturedValues->argObjSyms, insertionPoint, saveInitializedReg);
5039+
//
5040+
// - We move all argout symbols right before the call so we don't need to restore argouts either
5041+
this->InsertRestoreSymbols(bailOutInfo->byteCodeUpwardExposedUsed, insertionPoint);
5042+
Assert(!this->func->IsStackArgsEnabled());
5043+
5044+
return instrAfter;
5045+
}
5046+
5047+
void LinearScan::GeneratorBailIn::InsertRestoreSymbols(BVSparse<JitArenaAllocator>* symbols, BailInInsertionPoint& insertionPoint)
5048+
{
5049+
if (symbols == nullptr)
5050+
{
5051+
return;
5052+
}
5053+
5054+
FOREACH_BITSET_IN_SPARSEBV(symId, symbols)
5055+
{
5056+
StackSym* stackSym = this->func->m_symTable->FindStackSym(symId);
5057+
Lifetime* lifetime = stackSym->scratch.linearScan.lifetime;
5058+
5059+
if (!this->NeedsReloadingValueWhenBailIn(stackSym, lifetime))
5060+
{
5061+
continue;
5062+
}
5063+
5064+
Js::RegSlot regSlot = stackSym->GetByteCodeRegSlot();
5065+
IR::Opnd* srcOpnd = IR::IndirOpnd::New(
5066+
this->interpreterFrameRegOpnd,
5067+
this->GetOffsetFromInterpreterStackFrame(regSlot),
5068+
stackSym->GetType(),
5069+
this->func
5070+
);
5071+
5072+
if (lifetime->isSpilled)
5073+
{
5074+
Assert(!stackSym->IsConst());
5075+
// Stack restores require an extra register since we can't move an indir directly to an indir on amd64
5076+
IR::SymOpnd* dstOpnd = IR::SymOpnd::New(stackSym, stackSym->GetType(), this->func);
5077+
LinearScan::InsertMove(this->tempRegOpnd, srcOpnd, insertionPoint.instrInsertStackSym);
5078+
LinearScan::InsertMove(dstOpnd, this->tempRegOpnd, insertionPoint.instrInsertStackSym);
5079+
}
5080+
else
5081+
{
5082+
// Register restores must come after stack restores so that we have RAX and RCX free to
5083+
// use for stack restores and further RAX must be restored last since it holds the
5084+
// pointer to the InterpreterStackFrame from which we are restoring values.
5085+
// We must also track these restores using RecordDef in case the symbols are spilled.
5086+
5087+
IR::Instr* instr;
5088+
5089+
if (stackSym->IsConst())
5090+
{
5091+
instr = this->linearScan->InsertLoad(insertionPoint.instrInsertRegSym, stackSym, lifetime->reg);
5092+
}
5093+
else
5094+
{
5095+
IR::RegOpnd* dstRegOpnd = IR::RegOpnd::New(stackSym, stackSym->GetType(), this->func);
5096+
dstRegOpnd->SetReg(lifetime->reg);
5097+
instr = LinearScan::InsertMove(dstRegOpnd, srcOpnd, insertionPoint.instrInsertRegSym);
5098+
}
5099+
5100+
if (insertionPoint.instrInsertRegSym == insertionPoint.instrInsertStackSym)
5101+
{
5102+
// This is the first register sym, make sure we don't insert stack stores
5103+
// after this instruction so we can ensure rax and rcx remain free to use
5104+
// for restoring spilled stack syms.
5105+
insertionPoint.instrInsertStackSym = instr;
5106+
}
5107+
5108+
if (lifetime->reg == interpreterFrameRegOpnd->GetReg())
5109+
{
5110+
// Ensure rax is restored last
5111+
Assert(insertionPoint.instrInsertRegSym != insertionPoint.instrInsertStackSym);
5112+
5113+
insertionPoint.instrInsertRegSym = instr;
5114+
5115+
if (insertionPoint.raxRestoreInstr != nullptr)
5116+
{
5117+
AssertMsg(false, "this is unexpected until copy prop is enabled");
5118+
// rax was mapped to multiple bytecode registers. Obviously only the first
5119+
// restore we do will work so change all following stores to `mov rax, rax`.
5120+
// We still need to keep them around for RecordDef in case the corresponding
5121+
// dst sym is spilled later on.
5122+
insertionPoint.raxRestoreInstr->FreeSrc1();
5123+
insertionPoint.raxRestoreInstr->SetSrc1(this->interpreterFrameRegOpnd);
5124+
}
5125+
5126+
insertionPoint.raxRestoreInstr = instr;
5127+
}
5128+
5129+
this->linearScan->RecordDef(lifetime, instr, 0);
5130+
}
5131+
}
5132+
NEXT_BITSET_IN_SPARSEBV;
5133+
}
5134+
5135+
bool LinearScan::GeneratorBailIn::NeedsReloadingValueWhenBailIn(StackSym* sym, Lifetime* lifetime) const
5136+
{
5137+
if (sym->IsConst())
5138+
{
5139+
if (this->func->GetJITFunctionBody()->RegIsConstant(sym->GetByteCodeRegSlot()))
5140+
{
5141+
return false;
5142+
}
5143+
else
5144+
{
5145+
return !lifetime->isSpilled;
5146+
}
5147+
}
5148+
5149+
// If we have for-in in the generator, don't need to reload the symbol again as it is done
5150+
// during the resume jump table
5151+
if (this->func->GetForInEnumeratorSymForGeneratorSym() && this->func->GetForInEnumeratorSymForGeneratorSym()->m_id == sym->m_id)
5152+
{
5153+
return false;
5154+
}
5155+
5156+
// Check for other special registers that are already initialized
5157+
return !this->initializedRegs.Test(sym->GetByteCodeRegSlot());
5158+
}
5159+
5160+
IR::SymOpnd* LinearScan::GeneratorBailIn::CreateGeneratorObjectOpnd() const
5161+
{
5162+
StackSym* sym = StackSym::NewParamSlotSym(1, this->func);
5163+
this->func->SetArgOffset(sym, LowererMD::GetFormalParamOffset() * MachPtr);
5164+
return IR::SymOpnd::New(sym, TyMachPtr, this->func);
5165+
}
5166+
5167+
uint32 LinearScan::GeneratorBailIn::GetOffsetFromInterpreterStackFrame(Js::RegSlot regSlot) const
5168+
{
5169+
// Some objects aren't stored in the local space in interpreter frame, but instead
5170+
// in their own fields. Use their offsets in such cases.
5171+
if (regSlot == this->jitFnBody->GetLocalFrameDisplayReg())
5172+
{
5173+
return Js::InterpreterStackFrame::GetOffsetOfLocalFrameDisplay();
5174+
}
5175+
else if (regSlot == this->jitFnBody->GetLocalClosureReg())
5176+
{
5177+
return Js::InterpreterStackFrame::GetOffsetOfLocalClosure();
5178+
}
5179+
else if (regSlot == this->jitFnBody->GetParamClosureReg())
5180+
{
5181+
return Js::InterpreterStackFrame::GetOffsetOfParamClosure();
5182+
}
5183+
else
5184+
{
5185+
return regSlot * sizeof(Js::Var) + Js::InterpreterStackFrame::GetOffsetOfLocals();
5186+
}
5187+
}

lib/Backend/LinearScan.h

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ class LinearScan
103103
linearScanMD(func), opHelperSpilledLiveranges(NULL), currentOpHelperBlock(NULL),
104104
lastLabel(NULL), numInt32Regs(0), numFloatRegs(0), stackPackInUseLiveRanges(NULL), stackSlotsFreeList(NULL),
105105
totalOpHelperFullVisitedLength(0), curLoop(NULL), currentBlock(nullptr), currentRegion(nullptr), m_bailOutRecordCount(0),
106-
globalBailOutRecordTables(nullptr), lastUpdatedRowIndices(nullptr)
106+
globalBailOutRecordTables(nullptr), lastUpdatedRowIndices(nullptr), bailIn(GeneratorBailIn(func, this))
107107
{
108108
}
109109

@@ -227,4 +227,56 @@ class LinearScan
227227
static IR::Instr * InsertMove(IR::Opnd *dst, IR::Opnd *src, IR::Instr *const insertBeforeInstr);
228228
static IR::Instr * InsertLea(IR::RegOpnd *dst, IR::Opnd *src, IR::Instr *const insertBeforeInstr);
229229

230+
class GeneratorBailIn {
231+
// We need to rely on 2 registers `rax` and `rcx` to generate the bail-in code.
232+
// At this point, since `rax` already has the address of the generator's interpreter frame,
233+
// we can easily get the symbols' values through something like: mov dst [rax + appropriate offset]
234+
//
235+
// There are 4 types of symbols that we have to deal with:
236+
// - symbols that are currently on the stack at this point. We need 2 instructions:
237+
// - Load the value to `rcx`: mov rcx [rax + offset]
238+
// - Finally load the value to its stack slot: mov [rbp + stack offset] rcx
239+
// - symbol that is in rax
240+
// - symbol that is in rcx
241+
// - symbols that are in the other registers. We only need 1 instruction:
242+
// - mov reg [rax + offset]
243+
//
244+
// Since restoring symbols on the stack might mess up values that will be in rax/rcx,
245+
// and we want to maintain the invariant that rax has to hold the value of the interpreter
246+
// frame, we need to restore the symbols in the following order:
247+
// - symbols in stack
248+
// - symbols in registers
249+
// - symbol in rax
250+
//
251+
// The following 3 instructions indicate the insertion points for the above cases:
252+
struct BailInInsertionPoint
253+
{
254+
IR::Instr* raxRestoreInstr;
255+
IR::Instr* instrInsertStackSym;
256+
IR::Instr* instrInsertRegSym;
257+
};
258+
259+
Func* const func;
260+
LinearScan* const linearScan;
261+
const JITTimeFunctionBody* const jitFnBody;
262+
BVSparse<JitArenaAllocator> initializedRegs;
263+
264+
static constexpr int regNum = 2;
265+
const RegNum regs[regNum];
266+
IR::RegOpnd* const interpreterFrameRegOpnd;
267+
IR::RegOpnd* const tempRegOpnd;
268+
269+
bool NeedsReloadingValueWhenBailIn(StackSym* sym, Lifetime* lifetime) const;
270+
uint32 GetOffsetFromInterpreterStackFrame(Js::RegSlot regSlot) const;
271+
IR::SymOpnd* CreateGeneratorObjectOpnd() const;
272+
273+
void InsertRestoreSymbols(BVSparse<JitArenaAllocator>* symbols, BailInInsertionPoint& insertionPoint);
274+
275+
public:
276+
GeneratorBailIn(Func* func, LinearScan* linearScan);
277+
IR::Instr* GenerateBailIn(IR::Instr* resumeLabelInstr, BailOutInfo* bailOutInfo);
278+
void SpillRegsForBailIn();
279+
};
280+
281+
GeneratorBailIn bailIn;
230282
};

0 commit comments

Comments
 (0)