@@ -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+ }
0 commit comments