Procházet zdrojové kódy

Enable basic jitting without global optimizer for generators on x64

Nhat Nguyen před 6 roky
rodič
revize
6b660229db
44 změnil soubory, kde provedl 1298 přidání a 525 odebrání
  1. 1 1
      lib/Backend/BackendApi.cpp
  2. 40 14
      lib/Backend/BackwardPass.cpp
  3. 2 0
      lib/Backend/BackwardPass.h
  4. 37 49
      lib/Backend/BailOut.cpp
  5. 1 1
      lib/Backend/FlowGraph.cpp
  6. 1 0
      lib/Backend/Func.cpp
  7. 14 1
      lib/Backend/Func.h
  8. 8 0
      lib/Backend/GlobOpt.cpp
  9. 7 0
      lib/Backend/IR.cpp
  10. 3 0
      lib/Backend/IR.h
  11. 8 0
      lib/Backend/IR.inl
  12. 248 78
      lib/Backend/IRBuilder.cpp
  13. 33 2
      lib/Backend/IRBuilder.h
  14. 13 0
      lib/Backend/JITTimeFunctionBody.cpp
  15. 2 0
      lib/Backend/JITTimeFunctionBody.h
  16. 3 1
      lib/Backend/JnHelperMethodList.h
  17. 25 4
      lib/Backend/LinearScan.cpp
  18. 321 183
      lib/Backend/Lower.cpp
  19. 64 6
      lib/Backend/Lower.h
  20. 4 4
      lib/Backend/LowerMDShared.cpp
  21. 1 1
      lib/Backend/LowerMDShared.h
  22. 13 2
      lib/Backend/Peeps.cpp
  23. 1 1
      lib/Backend/SccLiveness.cpp
  24. 267 137
      lib/Backend/amd64/LinearScanMD.cpp
  25. 80 1
      lib/Backend/amd64/LinearScanMD.h
  26. 3 3
      lib/Backend/amd64/LowererMDArch.cpp
  27. 3 3
      lib/Backend/arm/LowerMD.cpp
  28. 1 1
      lib/Backend/arm/LowerMD.h
  29. 5 5
      lib/Backend/arm64/LowerMD.cpp
  30. 1 1
      lib/Backend/arm64/LowerMD.h
  31. 2 2
      lib/Backend/i386/LowererMDArch.cpp
  32. 6 0
      lib/Common/BackendApi.h
  33. 2 1
      lib/JITIDL/JITTypes.h
  34. 6 0
      lib/Runtime/Base/FunctionBody.cpp
  35. 1 4
      lib/Runtime/Base/FunctionBody.h
  36. 7 4
      lib/Runtime/ByteCode/ByteCodeEmitter.cpp
  37. 2 0
      lib/Runtime/ByteCode/ByteCodeGenerator.cpp
  38. 11 3
      lib/Runtime/ByteCode/OpCodes.h
  39. 2 1
      lib/Runtime/Debug/DiagHelperMethodWrapper.cpp
  40. 23 8
      lib/Runtime/Language/InterpreterStackFrame.cpp
  41. 12 2
      lib/Runtime/Language/InterpreterStackFrame.h
  42. 2 1
      lib/Runtime/Library/JavascriptGenerator.h
  43. 5 0
      lib/Runtime/Library/JavascriptGeneratorFunction.h
  44. 7 0
      lib/Runtime/Library/JavascriptLibrary.cpp

+ 1 - 1
lib/Backend/BackendApi.cpp

@@ -126,7 +126,7 @@ Js::JavascriptMethod GetCheckAsmJsCodeGenThunk()
 
 uint GetBailOutRegisterSaveSlotCount()
 {
-    // REVIEW: not all registers are used, we are allocating more space then necessary.
+    // REVIEW: not all registers are used, we are allocating more space than necessary.
     return LinearScanMD::GetRegisterSaveSlotCount();
 }
 

+ 40 - 14
lib/Backend/BackwardPass.cpp

@@ -88,13 +88,12 @@ BackwardPass::DoMarkTempNumbers() const
 }
 
 bool
-BackwardPass::DoMarkTempObjects() const
-{
-    // only mark temp object on the backward store phase
-    return (tag == Js::BackwardPhase) && !PHASE_OFF(Js::MarkTempPhase, this->func) &&
-        !PHASE_OFF(Js::MarkTempObjectPhase, this->func) && func->DoGlobOpt() && func->GetHasTempObjectProducingInstr() &&
-        !func->IsJitInDebugMode() &&
-        func->DoGlobOptsForGeneratorFunc();
+BackwardPass::SatisfyMarkTempObjectsConditions() const {
+    return !PHASE_OFF(Js::MarkTempPhase, this->func) &&
+           !PHASE_OFF(Js::MarkTempObjectPhase, this->func) &&
+           func->DoGlobOpt() && func->GetHasTempObjectProducingInstr() &&
+           !func->IsJitInDebugMode() &&
+           func->DoGlobOptsForGeneratorFunc();
 
     // Why MarkTempObject is disabled under debugger:
     //   We add 'identified so far dead non-temp locals' to byteCodeUpwardExposedUsed in ProcessBailOutInfo,
@@ -102,6 +101,13 @@ BackwardPass::DoMarkTempObjects() const
     //   from a temp to non-temp. That's in general not a supported conversion (while non-temp -> temp is fine).
 }
 
+bool
+BackwardPass::DoMarkTempObjects() const
+{
+    // only mark temp object on the backward store phase
+    return (tag == Js::BackwardPhase) && SatisfyMarkTempObjectsConditions();
+}
+
 bool
 BackwardPass::DoMarkTempNumbersOnTempObjects() const
 {
@@ -113,8 +119,7 @@ bool
 BackwardPass::DoMarkTempObjectVerify() const
 {
     // only mark temp object on the backward store phase
-    return (tag == Js::DeadStorePhase) && !PHASE_OFF(Js::MarkTempPhase, this->func) &&
-        !PHASE_OFF(Js::MarkTempObjectPhase, this->func) && func->DoGlobOpt() && func->GetHasTempObjectProducingInstr();
+    return (tag == Js::DeadStorePhase) && SatisfyMarkTempObjectsConditions();
 }
 #endif
 
@@ -7709,7 +7714,7 @@ BackwardPass::ProcessDef(IR::Opnd * opnd)
         PropertySym *propertySym = sym->AsPropertySym();
         ProcessStackSymUse(propertySym->m_stackSym, isJITOptimizedReg);
 
-        if(IsCollectionPass())
+        if (IsCollectionPass())
         {
             return false;
         }
@@ -7796,7 +7801,7 @@ BackwardPass::ProcessDef(IR::Opnd * opnd)
             }
         }
 
-        if(IsCollectionPass())
+        if (IsCollectionPass())
         {
             return false;
         }
@@ -8247,9 +8252,20 @@ BackwardPass::ProcessBailOnNoProfile(IR::Instr *instr, BasicBlock *block)
         return false;
     }
 
+    // For generator functions, we don't want to move the BailOutOnNoProfile above
+    // certain instructions such as ResumeYield/ResumeYieldStar/CreateInterpreterStackFrameForGenerator
+    // This indicates the insertion point for the BailOutOnNoProfile in such cases.
+    IR::Instr *insertionPointForGenerator = nullptr;
+
     // Don't hoist if we see calls with profile data (recursive calls)
     while(!curInstr->StartsBasicBlock())
     {
+        if (curInstr->DontHoistBailOnNoProfileAboveInGeneratorFunction())
+        {
+            Assert(insertionPointForGenerator == nullptr);
+            insertionPointForGenerator = curInstr;
+        }
+
         // If a function was inlined, it must have had profile info.
         if (curInstr->m_opcode == Js::OpCode::InlineeEnd || curInstr->m_opcode == Js::OpCode::InlineBuiltInEnd || curInstr->m_opcode == Js::OpCode::InlineNonTrackingBuiltInEnd
             || curInstr->m_opcode == Js::OpCode::InlineeStart || curInstr->m_opcode == Js::OpCode::EndCallForPolymorphicInlinee)
@@ -8320,7 +8336,8 @@ BackwardPass::ProcessBailOnNoProfile(IR::Instr *instr, BasicBlock *block)
     // Now try to move this up the flowgraph to the predecessor blocks
     FOREACH_PREDECESSOR_BLOCK(pred, block)
     {
-        bool hoistBailToPred = true;
+        // Don't hoist BailOnNoProfile up past blocks containing ResumeYield/ResumeYieldStar
+        bool hoistBailToPred = (insertionPointForGenerator == nullptr);
 
         if (block->isLoopHeader && pred->loop == block->loop)
         {
@@ -8396,10 +8413,19 @@ BackwardPass::ProcessBailOnNoProfile(IR::Instr *instr, BasicBlock *block)
 #if DBG
         blockHeadInstr->m_noHelperAssert = true;
 #endif
-        block->beginsBailOnNoProfile = true;
 
         instr->m_func = curInstr->m_func;
-        curInstr->InsertAfter(instr);
+
+        if (insertionPointForGenerator != nullptr)
+        {
+            insertionPointForGenerator->InsertAfter(instr);
+            block->beginsBailOnNoProfile = false;
+        }
+        else
+        {
+            curInstr->InsertAfter(instr);
+            block->beginsBailOnNoProfile = true;
+        }
 
         bool setLastInstr = (curInstr == block->GetLastInstr());
         if (setLastInstr)

+ 2 - 0
lib/Backend/BackwardPass.h

@@ -118,6 +118,8 @@ private:
     bool DoByteCodeUpwardExposedUsed() const;
     bool DoCaptureByteCodeUpwardExposedUsed() const;
     void DoSetDead(IR::Opnd * opnd, bool isDead) const;
+
+    bool SatisfyMarkTempObjectsConditions() const;
     bool DoMarkTempObjects() const;
     bool DoMarkTempNumbers() const;
     bool DoMarkTempNumbersOnTempObjects() const;

+ 37 - 49
lib/Backend/BailOut.cpp

@@ -989,6 +989,25 @@ BailOutRecord::RestoreValue(IR::BailOutKind bailOutKind, Js::JavascriptCallStack
         value = Js::JavascriptNumber::ToVar(int32Value, scriptContext);
         BAILOUT_VERBOSE_TRACE(newInstance->function->GetFunctionBody(), bailOutKind, _u(", value: %10d (ToVar: 0x%p)"), int32Value, value);
     }
+    else if (regSlot == newInstance->function->GetFunctionBody()->GetYieldRegister() && newInstance->function->GetFunctionBody()->IsCoroutine())
+    {
+        // This value can only either be:
+        // 1) the ResumeYieldData. Even though this value is on the stack, it is only used to extract the data as part of Op_ResumeYield.
+        //    So there is no need to box the value.
+        // 2) the object used as the return value for yield statement. This object is created on the heap, so no need to box either.
+        Assert(value);
+
+#if ENABLE_DEBUG_CONFIG_OPTIONS
+        if (ThreadContext::IsOnStack(value))
+        {
+            BAILOUT_VERBOSE_TRACE(newInstance->function->GetFunctionBody(), bailOutKind, _u(", value: 0x%p (ResumeYieldData)"), value);
+        }
+        else
+        {
+            BAILOUT_VERBOSE_TRACE(newInstance->function->GetFunctionBody(), bailOutKind, _u(", value: 0x%p (Yield Return Value)"), value);
+        }
+#endif
+    }
     else
     {
         BAILOUT_VERBOSE_TRACE(newInstance->function->GetFunctionBody(), bailOutKind, _u(", value: 0x%p"), value);
@@ -1507,63 +1526,32 @@ BailOutRecord::BailOutHelper(Js::JavascriptCallStackLayout * layout, Js::ScriptF
     if (executeFunction->IsCoroutine())
     {
         // If the FunctionBody is a generator then this call is being made by one of the three
-        // generator resuming methods: next(), throw(), or return().  They all pass the generator
-        // object as the first of two arguments.  The real user arguments are obtained from the
-        // generator object.  The second argument is the ResumeYieldData which is only needed
-        // when resuming a generator and not needed when yielding from a generator, as is occurring
-        // here.
+        // generator resuming methods: next(), throw(), or return(). They all pass the generator
+        // object as the first of two arguments. The real user arguments are obtained from the
+        // generator object. The second argument is the ResumeYieldData which is only needed when
+        // resuming a generator and not needed when yielding from a generator, as is occurring here.
         AssertMsg(args.Info.Count == 2, "Generator ScriptFunctions should only be invoked by generator APIs with the pair of arguments they pass in -- the generator object and a ResumeYieldData pointer");
         Js::JavascriptGenerator* generator = Js::VarTo<Js::JavascriptGenerator>(args[0]);
         newInstance = generator->GetFrame();
 
-        if (newInstance != nullptr)
-        {
-            // BailOut will recompute OutArg pointers based on BailOutRecord.  Reset them back
-            // to initial position before that happens so that OP_StartCall calls don't accumulate
-            // incorrectly over multiple yield bailouts.
-            newInstance->ResetOut();
-
-            // The debugger relies on comparing stack addresses of frames to decide when a step_out is complete so
-            // give the InterpreterStackFrame a legit enough stack address to make this comparison work.
-            newInstance->m_stackAddress = reinterpret_cast<DWORD_PTR>(&generator);
-        }
-        else
-        {
-            //
-            // Allocate a new InterpreterStackFrame instance on the recycler heap.
-            // It will live with the JavascriptGenerator object.
-            //
-            Js::Arguments generatorArgs = generator->GetArguments();
-            Js::InterpreterStackFrame::Setup setup(function, generatorArgs, true, isInlinee);
-            Assert(setup.GetStackAllocationVarCount() == 0);
-            size_t varAllocCount = setup.GetAllocationVarCount();
-            size_t varSizeInBytes = varAllocCount * sizeof(Js::Var);
-            DWORD_PTR stackAddr = reinterpret_cast<DWORD_PTR>(&generator); // as mentioned above, use any stack address from this frame to ensure correct debugging functionality
-            Js::LoopHeader* loopHeaderArray = executeFunction->GetHasAllocatedLoopHeaders() ? executeFunction->GetLoopHeaderArrayPtr() : nullptr;
-
-            allocation = RecyclerNewPlus(functionScriptContext->GetRecycler(), varSizeInBytes, Js::Var);
-
-            // Initialize the interpreter stack frame (constants) but not the param, the bailout record will restore the value
-#if DBG
-            // Allocate invalidVar on GC instead of stack since this InterpreterStackFrame will out live the current real frame
-            Js::Var invalidVar = (Js::RecyclableObject*)RecyclerNewPlusLeaf(functionScriptContext->GetRecycler(), sizeof(Js::RecyclableObject), Js::Var);
-            memset(invalidVar, 0xFE, sizeof(Js::RecyclableObject));
-#endif
+        // The jit relies on the interpreter stack frame to store various information such as
+        // for-in enumerators. Therefore, we always create an interpreter stack frame for generator
+        // as part of the resume jump table, at the beginning of the jit'd function, if it doesn't
+        // already exist.
+        Assert(newInstance != nullptr);
 
-            newInstance = setup.InitializeAllocation(allocation, nullptr, false, false, loopHeaderArray, stackAddr
-#if DBG
-                , invalidVar
-#endif
-                );
+        // BailOut will recompute OutArg pointers based on BailOutRecord. Reset them back
+        // to initial position before that happens so that OP_StartCall calls don't accumulate
+        // incorrectly over multiple yield bailouts.
+        newInstance->ResetOut();
 
-            newInstance->m_reader.Create(executeFunction);
-
-            generator->SetFrame(newInstance, varSizeInBytes);
-        }
+        // The debugger relies on comparing stack addresses of frames to decide when a step_out is complete so
+        // give the InterpreterStackFrame a legit enough stack address to make this comparison work.
+        newInstance->m_stackAddress = reinterpret_cast<DWORD_PTR>(&generator);
     }
     else
     {
-        Js::InterpreterStackFrame::Setup setup(function, args, true, isInlinee);
+        Js::InterpreterStackFrame::Setup setup(function, args, true /* bailedOut */, isInlinee);
         size_t varAllocCount = setup.GetAllocationVarCount();
         size_t stackVarAllocCount = setup.GetStackAllocationVarCount();
         size_t varSizeInBytes;
@@ -2826,7 +2814,7 @@ void BailOutRecord::CheckPreemptiveRejit(Js::FunctionBody* executeFunction, IR::
 
 Js::Var BailOutRecord::BailOutForElidedYield(void * framePointer)
 {
-    JIT_HELPER_REENTRANT_HEADER(NoSaveRegistersBailOutForElidedYield);
+    JIT_HELPER_NOT_REENTRANT_NOLOCK_HEADER(NoSaveRegistersBailOutForElidedYield);
     Js::JavascriptCallStackLayout * const layout = Js::JavascriptCallStackLayout::FromFramePointer(framePointer);
     Js::ScriptFunction ** functionRef = (Js::ScriptFunction **)&layout->functionObject;
     Js::ScriptFunction * function = *functionRef;

+ 1 - 1
lib/Backend/FlowGraph.cpp

@@ -3318,7 +3318,7 @@ FlowGraph::RemoveInstr(IR::Instr *instr, GlobOpt * globOpt)
             if (opcode == Js::OpCode::Yield)
             {
                 IR::Instr *instrLabel = newByteCodeUseInstr->m_next;
-                while (instrLabel->m_opcode != Js::OpCode::Label)
+                while (instrLabel->m_opcode != Js::OpCode::GeneratorBailInLabel)
                 {
                     instrLabel = instrLabel->m_next;
                 }

+ 1 - 0
lib/Backend/Func.cpp

@@ -148,6 +148,7 @@ Func::Func(JitArenaAllocator *alloc, JITTimeWorkItem * workItem,
     , m_forInEnumeratorArrayOffset(-1)
     , argInsCount(0)
     , m_globalObjTypeSpecFldInfoArray(nullptr)
+    , m_forInEnumeratorForGeneratorSym(nullptr)
 #if LOWER_SPLIT_INT64
     , m_int64SymPairMap(nullptr)
 #endif

+ 14 - 1
lib/Backend/Func.h

@@ -205,7 +205,8 @@ public:
         return
             !PHASE_OFF(Js::GlobOptPhase, this) && !IsSimpleJit() &&
             (!GetTopFunc()->HasTry() || GetTopFunc()->CanOptimizeTryCatch()) &&
-            (!GetTopFunc()->HasFinally() || GetTopFunc()->CanOptimizeTryFinally());
+            (!GetTopFunc()->HasFinally() || GetTopFunc()->CanOptimizeTryFinally()) &&
+            !GetTopFunc()->GetJITFunctionBody()->IsCoroutine();
     }
 
     bool DoInline() const
@@ -1061,11 +1062,23 @@ private:
     StackSym* m_loopParamSym;
     StackSym* m_bailoutReturnValueSym;
     StackSym* m_hasBailedOutSym;
+    StackSym* m_forInEnumeratorForGeneratorSym;
 
 public:
     StackSym* tempSymDouble;
     StackSym* tempSymBool;
 
+    void SetForInEnumeratorSymForGeneratorSym(StackSym* sym)
+    {
+        Assert(this->m_forInEnumeratorForGeneratorSym == nullptr);
+        this->m_forInEnumeratorForGeneratorSym = sym;
+    }
+
+    StackSym* GetForInEnumeratorSymForGeneratorSym() const
+    {
+        return this->m_forInEnumeratorForGeneratorSym;
+    }
+
     // StackSyms' corresponding getters/setters
     void SetInlineeFrameStartSym(StackSym* sym)
     {

+ 8 - 0
lib/Backend/GlobOpt.cpp

@@ -4704,6 +4704,14 @@ GlobOpt::ValueNumberDst(IR::Instr **pInstr, Value *src1Val, Value *src2Val)
     case Js::OpCode::Coerce_Str:
         AssertMsg(instr->GetDst()->GetValueType().IsString(),
             "Creator of this instruction should have set the type");
+
+        // Due to fall through and the fact that Ld_A only takes one source,
+        // free the other source here.
+        if (instr->GetSrc2())
+        {
+            instr->FreeSrc2();
+        }
+
         // fall-through
     case Js::OpCode::Coerce_StrOrRegex:
         // We don't set the ValueType of src1 for Coerce_StrOrRegex, hence skip the ASSERT

+ 7 - 0
lib/Backend/IR.cpp

@@ -1047,6 +1047,13 @@ bool IR::Instr::IsStElemVariant() const
         this->m_opcode == Js::OpCode::StElemC;
 }
 
+bool IR::Instr::DontHoistBailOnNoProfileAboveInGeneratorFunction() const
+{
+    return this->m_opcode == Js::OpCode::ResumeYield ||
+        this->m_opcode == Js::OpCode::ResumeYieldStar ||
+        this->m_opcode == Js::OpCode::GeneratorCreateInterpreterStackFrame;
+}
+
 bool IR::Instr::CanChangeFieldValueWithoutImplicitCall() const
 {
     // TODO: Why is InitFld necessary?

+ 3 - 0
lib/Backend/IR.h

@@ -336,6 +336,8 @@ public:
     static Instr*   FindSingleDefInstr(Js::OpCode opCode, Opnd* src);
     bool            CanAggregateByteCodeUsesAcrossInstr(IR::Instr * instr);
 
+    bool            DontHoistBailOnNoProfileAboveInGeneratorFunction() const;
+
     // LazyBailOut
     bool            AreAllOpndsTypeSpecialized() const;
     bool            IsStFldVariant() const;
@@ -798,6 +800,7 @@ public:
     inline void             SetRegion(Region *);
     inline Region *         GetRegion(void) const;
     inline BOOL             IsUnreferenced(void) const;
+    inline BOOL             IsGeneratorEpilogueLabel(void) const;
 
     LabelInstr *            CloneLabel(BOOL fCreate);
 

+ 8 - 0
lib/Backend/IR.inl

@@ -256,6 +256,7 @@ Instr::EndsBasicBlock() const
     return
         this->IsBranchInstr() ||
         this->IsExitInstr() ||
+        this->m_opcode == Js::OpCode::Yield ||
         this->m_opcode == Js::OpCode::Ret ||
         this->m_opcode == Js::OpCode::Throw ||
         this->m_opcode == Js::OpCode::RuntimeTypeError ||
@@ -728,6 +729,13 @@ LabelInstr::IsUnreferenced(void) const
     return labelRefs.Empty() && !m_hasNonBranchRef;
 }
 
+inline BOOL
+LabelInstr::IsGeneratorEpilogueLabel(void) const
+{
+    return this->m_opcode == Js::OpCode::GeneratorEpilogueNoFrameNullOut ||
+            this->m_opcode == Js::OpCode::GeneratorEpilogueFrameNullOut;
+}
+
 inline void
 LabelInstr::SetRegion(Region * region)
 {

+ 248 - 78
lib/Backend/IRBuilder.cpp

@@ -422,7 +422,6 @@ IRBuilder::Build()
     this->LoadNativeCodeData();
 
     this->BuildConstantLoads();
-    this->BuildGeneratorPreamble();
 
     if (!this->IsLoopBody() && m_func->GetJITFunctionBody()->HasImplicitArgIns())
     {
@@ -434,9 +433,12 @@ IRBuilder::Build()
         this->BuildArgInRest();
     }
 
-    if (m_func->IsJitInDebugMode())
+    // This is first bailout in the function, the locals at stack have not initialized to undefined, so do not restore them.
+    // Note that for generators, we insert the bailout after the jump table to allow
+    // the generator's execution to proceed before bailing out. Otherwise, we would always
+    // bail to the beginning of the function in the interpreter, creating an infinite loop.
+    if (m_func->IsJitInDebugMode() && !this->m_func->GetJITFunctionBody()->IsCoroutine())
     {
-        // This is first bailout in the function, the locals at stack have not initialized to undefined, so do not restore them.
         this->InsertBailOutForDebugger(m_functionStartOffset, IR::BailOutForceByFlag | IR::BailOutBreakPointInFunction | IR::BailOutStep, nullptr);
     }
 
@@ -482,6 +484,34 @@ IRBuilder::Build()
             this->AddInstr(instr, offset);
         }
 
+        // The point at which we insert the generator resume jump table is important.
+        // We want to insert it right *after* the environment and constants have
+        // been loaded and *before* we create any other important objects
+        // (e.g: FrameDisplay, LocalClosure) which will be passed on to the interpreter
+        // frame when we bail out. Those values, if used when we resume, will be restored
+        // by the bail-in code, therefore we don't want to unnecessarily create those new
+        // objects every time we "resume" a generator
+        //
+        // Note: We need to make sure that all the values below are allocated on the heap.
+        // so that they don't go away once this jit'd frame is popped off.
+
+#ifdef BAILOUT_INJECTION
+        lastInstr = this->m_generatorJumpTable.BuildJumpTable();
+#else
+        this->m_generatorJumpTable.BuildJumpTable();
+#endif
+
+        // When debugging generators, insert bail-out after the jump table so that we can
+        // get to the right point before going back to the interpreter.
+        // This bailout is equivalent to the one inserted above for non-generator functions.
+        // Additionally, we also need to insert bailouts on each resume point and right
+        // after the bail-in code since this bailout is only for the very first time
+        // we are in the generator.
+        if (m_func->IsJitInDebugMode() && this->m_func->GetJITFunctionBody()->IsCoroutine())
+        {
+            this->InsertBailOutForDebugger(m_functionStartOffset, IR::BailOutForceByFlag | IR::BailOutBreakPointInFunction | IR::BailOutStep, nullptr);
+        }
+
         Js::RegSlot funcExprScopeReg = m_func->GetJITFunctionBody()->GetFuncExprScopeReg();
         IR::RegOpnd *frameDisplayOpnd = nullptr;
         if (funcExprScopeReg != Js::Constants::NoRegister)
@@ -1231,14 +1261,30 @@ IR::Opnd *
 IRBuilder::BuildForInEnumeratorOpnd(uint forInLoopLevel)
 {
     Assert(forInLoopLevel < this->m_func->GetJITFunctionBody()->GetForInLoopDepth());
-    if (!this->IsLoopBody())
+    if (this->IsLoopBody())
+    {
+        return IR::IndirOpnd::New(
+            this->EnsureLoopBodyForInEnumeratorArrayOpnd(),
+            forInLoopLevel * sizeof(Js::ForInObjectEnumerator),
+            TyMachPtr,
+            this->m_func
+        );
+    }
+    else if (this->m_func->GetJITFunctionBody()->IsCoroutine())
     {
-        StackSym *stackSym = StackSym::New(TyMisc, this->m_func);
+        return IR::IndirOpnd::New(
+            this->m_generatorJumpTable.EnsureForInEnumeratorArrayOpnd(),
+            forInLoopLevel * sizeof(Js::ForInObjectEnumerator),
+            TyMachPtr,
+            this->m_func
+        );
+    }
+    else
+    {
+        StackSym* stackSym = StackSym::New(TyMisc, this->m_func);
         stackSym->m_offset = forInLoopLevel;
         return IR::SymOpnd::New(stackSym, TyMachPtr, this->m_func);
     }
-    return IR::IndirOpnd::New(
-        EnsureLoopBodyForInEnumeratorArrayOpnd(), forInLoopLevel * sizeof(Js::ForInObjectEnumerator), TyMachPtr, this->m_func);
 }
 
 ///----------------------------------------------------------------------------
@@ -1349,71 +1395,6 @@ IRBuilder::BuildImplicitArgIns()
     }
 }
 
-#if DBG_DUMP || defined(ENABLE_IR_VIEWER)
-#define POINTER_OFFSET(opnd, c, field) \
-    BuildIndirOpnd((opnd), c::Get##field##Offset(), _u(#c) _u(".") _u(#field))
-#else
-#define POINTER_OFFSET(opnd, c, field) \
-    BuildIndirOpnd((opnd), c::Get##field##Offset())
-#endif
-
-void
-IRBuilder::BuildGeneratorPreamble()
-{
-    if (!this->m_func->GetJITFunctionBody()->IsCoroutine())
-    {
-        return;
-    }
-
-    // Build code to check if the generator already has state and if it does then jump to the corresponding resume point.
-    // Otherwise jump to the start of the function.  The generator object is the first argument by convention established
-    // in JavascriptGenerator::EntryNext/EntryReturn/EntryThrow.
-    //
-    // s1 = Ld_A prm1
-    // s2 = Ld_A s1[offset of JavascriptGenerator::frame]
-    //      BrAddr_A s2 nullptr $startOfFunc
-    // s3 = Ld_A s2[offset of InterpreterStackFrame::m_reader.m_currentLocation]
-    // s4 = Ld_A s2[offset of InterpreterStackFrame::m_reader.m_startLocation]
-    // s5 = Sub_I4 s3 s4
-    //      GeneratorResumeJumpTable s5
-    // $startOfFunc:
-    //
-
-    StackSym *genParamSym = StackSym::NewParamSlotSym(1, this->m_func);
-    this->m_func->SetArgOffset(genParamSym, LowererMD::GetFormalParamOffset() * MachPtr);
-
-    IR::SymOpnd *genParamOpnd = IR::SymOpnd::New(genParamSym, TyMachPtr, this->m_func);
-    IR::RegOpnd *genRegOpnd = IR::RegOpnd::New(TyMachPtr, this->m_func);
-    IR::Instr *instr = IR::Instr::New(Js::OpCode::Ld_A, genRegOpnd, genParamOpnd, this->m_func);
-    this->AddInstr(instr, Js::Constants::NoByteCodeOffset);
-
-    IR::RegOpnd *genFrameOpnd = IR::RegOpnd::New(TyMachPtr, this->m_func);
-    instr = IR::Instr::New(Js::OpCode::Ld_A, genFrameOpnd, POINTER_OFFSET(genRegOpnd, Js::JavascriptGenerator, Frame), this->m_func);
-    this->AddInstr(instr, Js::Constants::NoByteCodeOffset);
-
-    IR::LabelInstr *labelInstr = IR::LabelInstr::New(Js::OpCode::Label, this->m_func);
-    IR::BranchInstr *branchInstr = IR::BranchInstr::New(Js::OpCode::BrAddr_A, labelInstr, genFrameOpnd, IR::AddrOpnd::NewNull(this->m_func), this->m_func);
-    this->AddInstr(branchInstr, Js::Constants::NoByteCodeOffset);
-
-    IR::RegOpnd *curLocOpnd = IR::RegOpnd::New(TyMachPtr, this->m_func);
-    instr = IR::Instr::New(Js::OpCode::Ld_A, curLocOpnd, POINTER_OFFSET(genFrameOpnd, Js::InterpreterStackFrame, CurrentLocation), this->m_func);
-    this->AddInstr(instr, Js::Constants::NoByteCodeOffset);
-
-    IR::RegOpnd *startLocOpnd = IR::RegOpnd::New(TyMachPtr, this->m_func);
-    instr = IR::Instr::New(Js::OpCode::Ld_A, startLocOpnd, POINTER_OFFSET(genFrameOpnd, Js::InterpreterStackFrame, StartLocation), this->m_func);
-    this->AddInstr(instr, Js::Constants::NoByteCodeOffset);
-
-    IR::RegOpnd *curOffsetOpnd = IR::RegOpnd::New(TyUint32, this->m_func);
-    instr = IR::Instr::New(Js::OpCode::Sub_I4, curOffsetOpnd, curLocOpnd, startLocOpnd, this->m_func);
-    this->AddInstr(instr, Js::Constants::NoByteCodeOffset);
-
-    instr = IR::Instr::New(Js::OpCode::GeneratorResumeJumpTable, this->m_func);
-    instr->SetSrc1(curOffsetOpnd);
-    this->AddInstr(instr, Js::Constants::NoByteCodeOffset);
-
-    this->AddInstr(labelInstr, Js::Constants::NoByteCodeOffset);
-}
-
 void
 IRBuilder::LoadNativeCodeData()
 {
@@ -1883,16 +1864,40 @@ IRBuilder::BuildReg2(Js::OpCode newOpcode, uint32 offset, Js::RegSlot R0, Js::Re
         dstOpnd->SetValueType(ValueType::String);
         break;
 
+    case Js::OpCode::ResumeYield:
+    {
+        IR::Instr* loadResumeYieldData = IR::Instr::New(Js::OpCode::GeneratorLoadResumeYieldData, src1Opnd /* dst */, m_func);
+        this->AddInstr(loadResumeYieldData, offset);
+
+        // Insert bailout for debugger, since we are bailing out to the ResumeYield instruction (OP_ResumeYield) in the interpreter,
+        // we have to load the ResumeYieldData first
+        if (this->m_func->IsJitInDebugMode())
+        {
+            this->InsertBailOutForDebugger(offset, IR::BailOutForceByFlag | IR::BailOutBreakPointInFunction | IR::BailOutStep);
+        }
+
+        break;
+    }
+
     case Js::OpCode::Yield:
         instr = IR::Instr::New(newOpcode, dstOpnd, src1Opnd, m_func);
         this->AddInstr(instr, offset);
         this->m_lastInstr = instr->ConvertToBailOutInstr(instr, IR::BailOutForGeneratorYield);
 
-        IR::LabelInstr* label = IR::LabelInstr::New(Js::OpCode::Label, m_func);
-        label->m_hasNonBranchRef = true;
-        this->AddInstr(label, Js::Constants::NoByteCodeOffset);
-
-        this->m_func->AddYieldOffsetResumeLabel(nextOffset, label);
+        // This label indicates the bail-in section that we will jump to from the generator jump table
+        IR::LabelInstr* bailInLabel = IR::LabelInstr::New(Js::OpCode::GeneratorBailInLabel, m_func);
+        bailInLabel->m_hasNonBranchRef = true;              // set to true so that we don't move this label around
+        LABELNAMESET(bailInLabel, "GeneratorBailInLabel");
+        this->AddInstr(bailInLabel, offset);
+        this->m_func->AddYieldOffsetResumeLabel(nextOffset, bailInLabel);
+
+        // 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
+        IR::LabelInstr* resumptionLabel = IR::LabelInstr::New(Js::OpCode::GeneratorResumeYieldLabel, m_func);
+        resumptionLabel->m_hasNonBranchRef = true;          // set to true so that we don't move this label around
+        LABELNAMESET(resumptionLabel, "ResumeYieldHelperLabel");
+        this->AddInstr(resumptionLabel, offset);
 
         return;
     }
@@ -2116,6 +2121,19 @@ IRBuilder::BuildReg3(Js::OpCode newOpcode, uint32 offset, Js::RegSlot dstRegSlot
         instr = IR::Instr::New(newOpcode, dstOpnd, src1Opnd, src2Opnd, m_func);
     }
 
+    if (newOpcode == Js::OpCode::ResumeYieldStar)
+    {
+        IR::Instr* loadResumeYieldData = IR::Instr::New(Js::OpCode::GeneratorLoadResumeYieldData, src1Opnd /* dst */, m_func);
+        this->AddInstr(loadResumeYieldData, offset);
+
+        // Insert bailout for debugger, since we are bailing out to the ResumeYieldStar instruction (OP_ResumeYield) in the interpreter,
+        // we have to load the ResumeYieldData first
+        if (this->m_func->IsJitInDebugMode())
+        {
+            this->InsertBailOutForDebugger(offset, IR::BailOutForceByFlag | IR::BailOutBreakPointInFunction | IR::BailOutStep);
+        }
+    }
+
     this->AddInstr(instr, offset);
 
     if (wasNotProfiled && DoBailOnNoProfile())
@@ -6904,7 +6922,7 @@ IRBuilder::BuildEmpty(Js::OpCode newOpcode, uint32 offset)
 
     case Js::OpCode::BeginBodyScope:
     {
-        // This marks the end of a param socpe which is not merged with body scope.
+        // This marks the end of a param scope which is not merged with body scope.
         // So we have to first cache the closure so that we can use it to copy the initial values for
         // body syms from corresponding param syms (LdParamSlot). Body should get its own scope slot.
         Assert(!this->IsParamScopeDone());
@@ -7655,3 +7673,155 @@ IRBuilder::AllowNativeArrayProfileInfo()
     return !((!(m_func->GetTopFunc()->HasTry() && !m_func->GetTopFunc()->DoOptimizeTry()) && m_func->GetWeakFuncRef() && !m_func->HasArrayInfo()) ||
         m_func->IsJitInDebugMode());
 }
+
+#if DBG_DUMP || defined(ENABLE_IR_VIEWER)
+#define POINTER_OFFSET(opnd, c, field) \
+    m_irBuilder->BuildIndirOpnd((opnd), c, _u(#c) _u(".") _u(#field))
+#else
+#define POINTER_OFFSET(opnd, c, field) \
+    m_irBuilder->BuildIndirOpnd((opnd), c)
+#endif
+
+IRBuilder::GeneratorJumpTable::GeneratorJumpTable(Func* func, IRBuilder* irBuilder) : m_func(func), m_irBuilder(irBuilder) {}
+
+IR::Instr*
+IRBuilder::GeneratorJumpTable::BuildJumpTable()
+{
+    if (!this->m_func->GetJITFunctionBody()->IsCoroutine())
+    {
+        return this->m_irBuilder->m_lastInstr;
+    }
+
+    // Build code to check if the generator already has state and if it does then jump to the corresponding resume point.
+    // Otherwise jump to the start of the function. The generator object is the first argument by convention established
+    // in JavascriptGenerator::EntryNext/EntryReturn/EntryThrow.
+    // We also create the interpreter stack frame for generator if it doesn't already exist.
+    //
+    // s1 = Ld_A prm1
+    // s2 = Ld_A s1[offset of JavascriptGenerator::frame]
+    //      BrNotAddr_A s2 !nullptr $initializationCode
+    //
+    // $createInterpreterStackFrame:
+    // call helper
+    //
+    // $initializationCode:
+    // load for-in enumerator address from interpreter stack frame
+    //
+    // 
+    // $jumpTable:
+    //
+    // s3 = Ld_A s2[offset of InterpreterStackFrame::m_reader.m_currentLocation]
+    // s4 = Ld_A s2[offset of InterpreterStackFrame::m_reader.m_startLocation]
+    // s5 = Sub_I4 s3 s4
+    //      GeneratorResumeJumpTable s5
+    //
+    // $startOfFunc:
+    //
+
+    // s1 = Ld_A prm1
+    StackSym* genParamSym = StackSym::NewParamSlotSym(1, this->m_func);
+    this->m_func->SetArgOffset(genParamSym, LowererMD::GetFormalParamOffset() * MachPtr);
+
+    IR::SymOpnd* genParamOpnd = IR::SymOpnd::New(genParamSym, TyMachPtr, this->m_func);
+    IR::RegOpnd* genRegOpnd = IR::RegOpnd::New(TyMachPtr, this->m_func);
+    IR::Instr* instr = IR::Instr::New(Js::OpCode::Ld_A, genRegOpnd, genParamOpnd, this->m_func);
+    this->m_irBuilder->AddInstr(instr, this->m_irBuilder->m_functionStartOffset);
+
+    // s2 = Ld_A s1[offset of JavascriptGenerator::frame]
+    IR::RegOpnd* genFrameOpnd = IR::RegOpnd::New(TyMachPtr, this->m_func);
+    instr = IR::Instr::New(
+        Js::OpCode::Ld_A,
+        genFrameOpnd,
+        POINTER_OFFSET(genRegOpnd, Js::JavascriptGenerator::GetFrameOffset(), GeneratorFrame),
+        this->m_func
+    );
+    this->m_irBuilder->AddInstr(instr, this->m_irBuilder->m_functionStartOffset);
+
+    IR::LabelInstr* initCode = IR::LabelInstr::New(Js::OpCode::Label, this->m_func);
+    LABELNAMESET(initCode, "GeneratorInitializationAndJumpTable");
+
+    // BrNotAddr_A s2 nullptr $initializationCode
+    IR::BranchInstr* branchInstr = IR::BranchInstr::New(Js::OpCode::BrNotAddr_A, initCode, genFrameOpnd, IR::AddrOpnd::NewNull(this->m_func), this->m_func);
+    this->m_irBuilder->AddInstr(branchInstr, this->m_irBuilder->m_functionStartOffset);
+
+    // Create interpreter stack frame
+    IR::Instr* createInterpreterFrame = IR::Instr::New(Js::OpCode::GeneratorCreateInterpreterStackFrame, genFrameOpnd /* dst */, genRegOpnd /* src */, this->m_func);
+    this->m_irBuilder->AddInstr(createInterpreterFrame, this->m_irBuilder->m_functionStartOffset);
+
+    // Label to insert any initialization code
+    // $initializationCode:
+    this->m_irBuilder->AddInstr(initCode, this->m_irBuilder->m_functionStartOffset);
+
+    // s3 = Ld_A s2[offset of InterpreterStackFrame::m_reader.m_currentLocation]
+    IR::RegOpnd* curLocOpnd = IR::RegOpnd::New(TyMachPtr, this->m_func);
+    instr = IR::Instr::New(
+        Js::OpCode::Ld_A,
+        curLocOpnd,
+        POINTER_OFFSET(genFrameOpnd, Js::InterpreterStackFrame::GetCurrentLocationOffset(), InterpreterCurrentLocation),
+        this->m_func
+    );
+    this->m_irBuilder->AddInstr(instr, this->m_irBuilder->m_functionStartOffset);
+
+    // s4 = Ld_A s2[offset of InterpreterStackFrame::m_reader.m_startLocation]
+    IR::RegOpnd* startLocOpnd = IR::RegOpnd::New(TyMachPtr, this->m_func);
+    instr = IR::Instr::New(
+        Js::OpCode::Ld_A,
+        startLocOpnd,
+        POINTER_OFFSET(genFrameOpnd, Js::InterpreterStackFrame::GetStartLocationOffset(), InterpreterStartLocation),
+        this->m_func
+    );
+    this->m_irBuilder->AddInstr(instr, this->m_irBuilder->m_functionStartOffset);
+
+    // s5 = Sub_I4 s3 s4
+    IR::RegOpnd* curOffsetOpnd = IR::RegOpnd::New(TyUint32, this->m_func);
+    instr = IR::Instr::New(Js::OpCode::Sub_I4, curOffsetOpnd, curLocOpnd, startLocOpnd, this->m_func);
+    this->m_irBuilder->AddInstr(instr, this->m_irBuilder->m_functionStartOffset);
+
+    // GeneratorResumeJumpTable s5
+    instr = IR::Instr::New(Js::OpCode::GeneratorResumeJumpTable, this->m_func);
+    instr->SetSrc1(curOffsetOpnd);
+    this->m_irBuilder->AddInstr(instr, this->m_irBuilder->m_functionStartOffset);
+
+    // Save these values for later use
+    this->m_initLabel = initCode;
+    this->m_generatorFrameOpnd = genFrameOpnd;
+
+    return this->m_irBuilder->m_lastInstr;
+}
+
+IR::LabelInstr*
+IRBuilder::GeneratorJumpTable::GetInitLabel() const
+{
+    Assert(this->m_initLabel != nullptr);
+    return this->m_initLabel;
+}
+
+IR::RegOpnd*
+IRBuilder::GeneratorJumpTable::CreateForInEnumeratorArrayOpnd()
+{
+    Assert(this->m_initLabel != nullptr);
+    Assert(this->m_generatorFrameOpnd != nullptr);
+
+    IR::RegOpnd* forInEnumeratorOpnd = IR::RegOpnd::New(TyMachPtr, this->m_func);
+    IR::Instr* instr = IR::Instr::New(
+        Js::OpCode::Ld_A,
+        forInEnumeratorOpnd,
+        POINTER_OFFSET(this->m_generatorFrameOpnd, Js::InterpreterStackFrame::GetOffsetOfForInEnumerators(), ForInEnumerators),
+        this->m_func
+    );
+    this->m_initLabel->InsertAfter(instr);
+
+    return forInEnumeratorOpnd;
+}
+
+IR::RegOpnd*
+IRBuilder::GeneratorJumpTable::EnsureForInEnumeratorArrayOpnd()
+{
+    if (this->m_forInEnumeratorArrayOpnd == nullptr)
+    {
+        this->m_forInEnumeratorArrayOpnd = this->CreateForInEnumeratorArrayOpnd();
+        this->m_func->SetForInEnumeratorSymForGeneratorSym(m_forInEnumeratorArrayOpnd->GetStackSym());
+    }
+
+    return this->m_forInEnumeratorArrayOpnd;
+}

+ 33 - 2
lib/Backend/IRBuilder.h

@@ -84,6 +84,7 @@ public:
 #ifdef BYTECODE_BRANCH_ISLAND
         , longBranchMap(nullptr)
 #endif
+        , m_generatorJumpTable(GeneratorJumpTable(func, this))
     {
         auto loopCount = func->GetJITFunctionBody()->GetLoopCount();
         if (loopCount > 0) {
@@ -121,7 +122,6 @@ private:
     uint                ResolveVirtualLongBranch(IR::BranchInstr * branchInstr, uint offset);
 #endif
     BranchReloc *       CreateRelocRecord(IR::BranchInstr * branchInstr, uint32 offset, uint32 targetOffset);
-    void                BuildGeneratorPreamble();
     void                LoadNativeCodeData();
     void                BuildConstantLoads();
     void                BuildImplicitArgIns();    
@@ -281,7 +281,7 @@ private:
 
     BOOL                RegIsConstant(Js::RegSlot reg)
     {
-        return reg > 0 && reg < m_func->GetJITFunctionBody()->GetConstCount();
+        return this->m_func->GetJITFunctionBody()->RegIsConstant(reg);
     }
 
     bool                IsParamScopeDone() const { return m_paramScopeDone; }
@@ -380,4 +380,35 @@ private:
     LongBranchMap * longBranchMap;
     static IR::Instr * const VirtualLongBranchInstr;
 #endif
+
+    class GeneratorJumpTable {
+        Func* const m_func;
+        IRBuilder* const m_irBuilder;
+
+        // for-in enumerators are allocated on the heap for jit'd loop body
+        // and on the stack for all other cases (the interpreter frame will
+        // reuses them when bailing out). But because we have the concept of
+        // "bailing in" for generator, reusing enumerators allocated on the stack
+        // would not work. So we have to allocate them on the generator's interpreter
+        // frame instead. This operand is loaded as part of the jump table, before we
+        // jump to any of the resume point.
+        IR::RegOpnd* m_forInEnumeratorArrayOpnd = nullptr;
+
+        IR::RegOpnd* m_generatorFrameOpnd = nullptr;
+
+        // This label is used to insert any initialization code that might be needed
+        // when bailing in and before jumping to any of the resume points.
+        // As of now, we only need to load the operand for for-in enumerator.
+        IR::LabelInstr* m_initLabel = nullptr;
+
+        IR::RegOpnd* CreateForInEnumeratorArrayOpnd();
+
+    public:
+        GeneratorJumpTable(Func* func, IRBuilder* irBuilder);
+        IR::Instr* BuildJumpTable();
+        IR::LabelInstr* GetInitLabel() const;
+        IR::RegOpnd* EnsureForInEnumeratorArrayOpnd();
+    };
+
+    GeneratorJumpTable m_generatorJumpTable;
 };

+ 13 - 0
lib/Backend/JITTimeFunctionBody.cpp

@@ -162,6 +162,7 @@ JITTimeFunctionBody::InitializeJITFunctionData(
         }
     }
 
+    jitBody->yieldReg = functionBody->GetYieldRegister();
     jitBody->localFrameDisplayReg = functionBody->GetLocalFrameDisplayRegister();
     jitBody->localClosureReg = functionBody->GetLocalClosureRegister();
     jitBody->envReg = functionBody->GetEnvRegister();
@@ -401,6 +402,12 @@ JITTimeFunctionBody::GetLocalFrameDisplayReg() const
     return static_cast<Js::RegSlot>(m_bodyData.localFrameDisplayReg);
 }
 
+Js::RegSlot
+JITTimeFunctionBody::GetYieldReg() const
+{
+    return static_cast<Js::RegSlot>(m_bodyData.yieldReg);
+}
+
 Js::RegSlot
 JITTimeFunctionBody::GetLocalClosureReg() const
 {
@@ -816,6 +823,12 @@ JITTimeFunctionBody::NeedScopeObjectForArguments(bool hasNonSimpleParams) const
         && !dontNeedScopeObject;
 }
 
+bool
+JITTimeFunctionBody::RegIsConstant(Js::RegSlot reg) const
+{
+    return reg > 0 && reg < this->GetConstCount();
+}
+
 bool
 JITTimeFunctionBody::GetDoScopeObjectCreation() const
 {

+ 2 - 0
lib/Backend/JITTimeFunctionBody.h

@@ -37,6 +37,7 @@ public:
     uint GetInlineCacheCount() const;
     uint GetRecursiveCallSiteCount() const;
     uint GetForInLoopDepth() const;
+    Js::RegSlot GetYieldReg() const;
     Js::RegSlot GetLocalFrameDisplayReg() const;
     Js::RegSlot GetLocalClosureReg() const;
     Js::RegSlot GetEnvReg() const;
@@ -102,6 +103,7 @@ public:
     void EnsureConsistentConstCount() const;
     bool HasComputedName() const;
     bool HasHomeObj() const;
+    bool RegIsConstant(Js::RegSlot reg) const;
 
     const byte * GetByteCodeBuffer() const;
     StatementMapIDL * GetFullStatementMap() const;

+ 3 - 1
lib/Backend/JnHelperMethodList.h

@@ -439,7 +439,7 @@ HELPERCALLCHK(SimpleRecordLoopImplicitCallFlags, Js::SimpleJitHelpers::RecordLoo
 
 HELPERCALLCHK(ScriptAbort, Js::JavascriptOperators::ScriptAbort, AttrCanThrow | AttrCanNotBeReentrant)
 
-HELPERCALLCHK(NoSaveRegistersBailOutForElidedYield, BailOutRecord::BailOutForElidedYield, 0)
+HELPERCALLCHK(NoSaveRegistersBailOutForElidedYield, BailOutRecord::BailOutForElidedYield, AttrCanNotBeReentrant)
 
 // We don't want these functions to be valid iCall targets because they can be used to disclose stack addresses
 //   which CFG cannot defend against. Instead, return these addresses in GetNonTableMethodAddress
@@ -541,6 +541,8 @@ HELPERCALL(AsyncYieldStar,              Js::InterpreterStackFrame::OP_AsyncYield
 HELPERCALL(AsyncYield,                  Js::InterpreterStackFrame::OP_AsyncYield, AttrCanNotBeReentrant)
 HELPERCALL(Await,                       Js::InterpreterStackFrame::OP_Await, AttrCanNotBeReentrant)
 
+HELPERCALL(CreateInterpreterStackFrameForGenerator, Js::InterpreterStackFrame::CreateInterpreterStackFrameForGenerator, AttrCanNotBeReentrant)
+
 #if DBG
 HELPERCALL(IntRangeCheckFailure, Js::JavascriptNativeOperators::IntRangeCheckFailure, AttrCanNotBeReentrant)
 #endif

+ 25 - 4
lib/Backend/LinearScan.cpp

@@ -251,9 +251,18 @@ LinearScan::RegAlloc()
             this->FillBailOutRecord(instr);
             if (instr->GetBailOutKind() == IR::BailOutForGeneratorYield)
             {
-                Assert(instr->m_next->IsLabelInstr());
-                insertBailInAfter = instr->m_next;
+                Assert(insertBailInAfter == nullptr);
                 bailOutInfoForBailIn = instr->GetBailOutInfo();
+                insertBailInAfter = instr->m_next;
+
+                // Insert right after the GeneratorBailInLabel
+                // The register allocator might insert some compensation code between
+                // the BailOutForGeneratorYield and the GeneratorBailInLabel, so our
+                // bail-in insertion point is not necessarily always the next instruction.
+                while (insertBailInAfter != nullptr && insertBailInAfter->m_opcode != Js::OpCode::GeneratorBailInLabel)
+                {
+                    insertBailInAfter = insertBailInAfter->m_next;
+                }
             }
         }
 
@@ -301,7 +310,7 @@ LinearScan::RegAlloc()
             insertBailInAfter = nullptr;
             bailOutInfoForBailIn = nullptr;
         }
-    }NEXT_INSTR_EDITING;
+    } NEXT_INSTR_EDITING;
 
     if (func->hasBailout)
     {
@@ -1350,7 +1359,14 @@ LinearScan::EnsureGlobalBailOutRecordTable(Func *func)
         globalBailOutRecordDataTable->firstActualStackOffset = -1;
         globalBailOutRecordDataTable->registerSaveSpace = (Js::Var*)func->GetThreadContextInfo()->GetBailOutRegisterSaveSpaceAddr();
         globalBailOutRecordDataTable->globalBailOutRecordDataRows = nullptr;
-        if (func->GetJITFunctionBody()->GetForInLoopDepth() != 0)
+
+        if (func->GetJITFunctionBody()->IsCoroutine())
+        {
+            // Don't restore for-in enumerators for generators because they are
+            // already on the generator's interpreter frame
+            globalBailOutRecordDataTable->forInEnumeratorArrayRestoreOffset = -1;
+        }
+        else if (func->GetJITFunctionBody()->GetForInLoopDepth() != 0)
         {
 #ifdef MD_GROW_LOCALS_AREA_UP
             Assert(func->GetForInEnumeratorArrayOffset() >= 0);
@@ -4033,6 +4049,11 @@ LinearScan::InsertSecondChanceCompensation(Lifetime ** branchRegContent, Lifetim
                 continue;
             }
 
+            if (!branchLifetime && lifetime && lifetime->start > branchInstr->GetNumber() && labelInstr->m_opcode == Js::OpCode::GeneratorBailInLabel)
+            {
+                continue;
+            }
+
             if (branchLifetime && branchLifetime->isSpilled && !branchLifetime->sym->IsConst() && branchLifetime->end > labelInstr->GetNumber())
             {
                 // The lifetime was in a reg at the branch and is now spilled.  We need a store on this path.

+ 321 - 183
lib/Backend/Lower.cpp

@@ -919,14 +919,16 @@ Lowerer::LowerRange(IR::Instr *instrStart, IR::Instr *instrEnd, bool defaultDoFa
                 // If this RET isn't at the end of the function, insert a branch to
                 // the epilog.
 
-                IR::Instr *exitPrev = m_func->m_exitInstr->m_prev;
-                if (!exitPrev->IsLabelInstr())
+                IR::LabelInstr* epilogue;
+                if (this->m_func->GetJITFunctionBody()->IsCoroutine())
                 {
-                    exitPrev = IR::LabelInstr::New(Js::OpCode::Label, m_func);
-                    m_func->m_exitInstr->InsertBefore(exitPrev);
+                    epilogue = this->m_lowerGeneratorHelper.GetEpilogueForReturnStatements();
                 }
-                IR::BranchInstr *exitBr = IR::BranchInstr::New(LowererMD::MDUncondBranchOpcode,
-                    exitPrev->AsLabelInstr(), m_func);
+                else
+                {
+                    epilogue = this->EnsureEpilogueLabel();
+                }
+                IR::BranchInstr *exitBr = IR::BranchInstr::New(LowererMD::MDUncondBranchOpcode, epilogue, m_func);
                 instr->InsertAfter(exitBr);
             }
 
@@ -3006,94 +3008,33 @@ Lowerer::LowerRange(IR::Instr *instrStart, IR::Instr *instrEnd, bool defaultDoFa
 
         case Js::OpCode::Yield:
         {
-            instr->FreeSrc1(); // Source is not actually used by the backend other than to calculate lifetime
-            IR::Opnd* dstOpnd = instr->UnlinkDst();
-
-            // prm2 is the ResumeYieldData pointer per calling convention established in JavascriptGenerator::CallGenerator
-            // This is the value the bytecode expects to be in the dst register of the Yield opcode after resumption.
-            // Load it here after the bail-in.
-
-            StackSym *resumeYieldDataSym = StackSym::NewImplicitParamSym(4, m_func);
-            m_func->SetArgOffset(resumeYieldDataSym, (LowererMD::GetFormalParamOffset() + 1) * MachPtr);
-            IR::SymOpnd * resumeYieldDataOpnd = IR::SymOpnd::New(resumeYieldDataSym, TyMachPtr, m_func);
-
-            AssertMsg(instr->m_next->IsLabelInstr(), "Expect the resume label to immediately follow Yield instruction");
-            InsertMove(dstOpnd, resumeYieldDataOpnd, instr->m_next->m_next);
-
-            GenerateBailOut(instr);
+            this->m_lowerGeneratorHelper.LowerYield(instr);
+            break;
+        }
 
+        case Js::OpCode::GeneratorLoadResumeYieldData:
+        {
+            this->m_lowerGeneratorHelper.LowerGeneratorLoadResumeYieldData(instr);
             break;
         }
 
         case Js::OpCode::ResumeYield:
         case Js::OpCode::ResumeYieldStar:
         {
-            IR::Opnd *srcOpnd1 = instr->UnlinkSrc1();
-            IR::Opnd *srcOpnd2 = instr->m_opcode == Js::OpCode::ResumeYieldStar ? instr->UnlinkSrc2() : IR::AddrOpnd::NewNull(m_func);
-            m_lowererMD.LoadHelperArgument(instr, srcOpnd2);
-            m_lowererMD.LoadHelperArgument(instr, srcOpnd1);
-            m_lowererMD.ChangeToHelperCall(instr, IR::HelperResumeYield);
+            this->m_lowerGeneratorHelper.LowerResumeGenerator(instr);
             break;
         }
 
-        case Js::OpCode::GeneratorResumeJumpTable:
+        case Js::OpCode::GeneratorCreateInterpreterStackFrame:
         {
-            // Lowered in LowerPrologEpilog so that the jumps introduced are not considered to be part of the flow for the RegAlloc phase.
-
-            // Introduce a BailOutNoSave label if there were yield points that were elided due to optimizations.  They could still be hit
-            // if an active generator object had been paused at such a yield point when the function body was JITed.  So safe guard such a
-            // case by having the native code simply jump back to the interpreter for such yield points.
-
-            IR::LabelInstr *bailOutNoSaveLabel = nullptr;
-
-            m_func->MapUntilYieldOffsetResumeLabels([this, &bailOutNoSaveLabel](int, const YieldOffsetResumeLabel& yorl)
-            {
-                if (yorl.Second() == nullptr)
-                {
-                    if (bailOutNoSaveLabel == nullptr)
-                    {
-                        bailOutNoSaveLabel = IR::LabelInstr::New(Js::OpCode::Label, m_func);
-                    }
-
-                    return true;
-                }
-
-                return false;
-            });
-
-            // Insert the bailoutnosave label somewhere along with a call to BailOutNoSave helper
-            if (bailOutNoSaveLabel != nullptr)
-            {
-                IR::Instr * exitPrevInstr = this->m_func->m_exitInstr->m_prev;
-                IR::LabelInstr * exitTargetInstr;
-                if (exitPrevInstr->IsLabelInstr())
-                {
-                    exitTargetInstr = exitPrevInstr->AsLabelInstr();
-                    exitPrevInstr = exitPrevInstr->m_prev;
-                }
-                else
-                {
-                    exitTargetInstr = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, false);
-                    exitPrevInstr->InsertAfter(exitTargetInstr);
-                }
-
-                bailOutNoSaveLabel->m_hasNonBranchRef = true;
-                bailOutNoSaveLabel->isOpHelper = true;
-
-                IR::Instr* bailOutCall = IR::Instr::New(Js::OpCode::Call, m_func);
-
-                exitPrevInstr->InsertAfter(bailOutCall);
-                exitPrevInstr->InsertAfter(bailOutNoSaveLabel);
-                exitPrevInstr->InsertAfter(IR::BranchInstr::New(LowererMD::MDUncondBranchOpcode, exitTargetInstr, m_func));
-
-                IR::RegOpnd * frameRegOpnd = IR::RegOpnd::New(nullptr, LowererMD::GetRegFramePointer(), TyMachPtr, m_func);
-
-                m_lowererMD.LoadHelperArgument(bailOutCall, frameRegOpnd);
-                m_lowererMD.ChangeToHelperCall(bailOutCall, IR::HelperNoSaveRegistersBailOutForElidedYield);
-
-                m_func->m_bailOutNoSaveLabel = bailOutNoSaveLabel;
-            }
+            this->m_lowerGeneratorHelper.LowerCreateInterpreterStackFrameForGenerator(instr);
+            break;
+        }
 
+        case Js::OpCode::GeneratorResumeJumpTable:
+        {
+            this->m_lowerGeneratorHelper.InsertBailOutForElidedYield();
+            this->m_lowerGeneratorHelper.LowerGeneratorResumeJumpTable(instr);
             break;
         }
 
@@ -3199,6 +3140,13 @@ Lowerer::LowerRange(IR::Instr *instrStart, IR::Instr *instrEnd, bool defaultDoFa
             instrPrev = this->LowerStPropIdArrFromVar(instr);
             break;
 
+        case Js::OpCode::GeneratorBailInLabel:
+        case Js::OpCode::GeneratorResumeYieldLabel:
+        case Js::OpCode::GeneratorEpilogueFrameNullOut:
+        case Js::OpCode::GeneratorEpilogueNoFrameNullOut:
+            Assert(this->m_func->GetJITFunctionBody()->IsCoroutine());
+            break;
+
         default:
 #ifdef ENABLE_WASM_SIMD
             if (IsSimd128Opcode(instr->m_opcode))
@@ -5550,11 +5498,6 @@ Lowerer::LowerNewScObjArrayNoArg(IR::Instr *newObjInstr)
 void
 Lowerer::LowerPrologEpilog()
 {
-    if (m_func->GetJITFunctionBody()->IsCoroutine())
-    {
-        LowerGeneratorResumeJumpTable();
-    }
-
     IR::Instr * instr;
 
     instr = m_func->m_headInstr;
@@ -5565,6 +5508,12 @@ Lowerer::LowerPrologEpilog()
     instr = m_func->m_exitInstr;
     AssertMsg(instr->IsExitInstr(), "Last instr isn't an ExitInstr...");
 
+    if (m_func->GetJITFunctionBody()->IsCoroutine())
+    {
+        IR::LabelInstr* epilogueLabel = this->m_lowerGeneratorHelper.GetEpilogueForReturnStatements();
+        this->m_lowerGeneratorHelper.InsertNullOutGeneratorFrameInEpilogue(epilogueLabel);
+    }
+
     m_lowererMD.LowerExitInstr(instr->AsExitInstr());
 }
 
@@ -5584,45 +5533,6 @@ Lowerer::LowerPrologEpilogAsmJs()
     m_lowererMD.LowerExitInstrAsmJs(instr->AsExitInstr());
 }
 
-void
-Lowerer::LowerGeneratorResumeJumpTable()
-{
-    Assert(m_func->GetJITFunctionBody()->IsCoroutine());
-
-    IR::Instr * jumpTableInstr = m_func->m_headInstr;
-    AssertMsg(jumpTableInstr->IsEntryInstr(), "First instr isn't an EntryInstr...");
-
-    // Hope to do away with this linked list scan by moving this lowering to a post-prolog-epilog/pre-encoder phase that is common to all architectures (currently such phase is only available on amd64/arm)
-    while (jumpTableInstr->m_opcode != Js::OpCode::GeneratorResumeJumpTable)
-    {
-        jumpTableInstr = jumpTableInstr->m_next;
-    }
-
-    IR::Opnd * srcOpnd = jumpTableInstr->UnlinkSrc1();
-
-    m_func->MapYieldOffsetResumeLabels([&](int i, const YieldOffsetResumeLabel& yorl)
-    {
-        uint32 offset = yorl.First();
-        IR::LabelInstr * label = yorl.Second();
-
-        if (label != nullptr && label->m_hasNonBranchRef)
-        {
-            // Also fix up the bailout at the label with the jump to epilog that was not emitted in GenerateBailOut()
-            Assert(label->m_prev->HasBailOutInfo());
-            GenerateJumpToEpilogForBailOut(label->m_prev->GetBailOutInfo(), label->m_prev);
-        }
-        else if (label == nullptr)
-        {
-            label = m_func->m_bailOutNoSaveLabel;
-        }
-
-        // For each offset label pair, insert a compare of the offset and branch if equal to the label
-        InsertCompareBranch(srcOpnd, IR::IntConstOpnd::New(offset, TyUint32, m_func), Js::OpCode::BrSrEq_A, label, jumpTableInstr);
-    });
-
-    jumpTableInstr->Remove();
-}
-
 void
 Lowerer::DoInterruptProbes()
 {
@@ -14417,36 +14327,49 @@ Lowerer::GenerateBailOut(IR::Instr * instr, IR::BranchInstr * branchInstr, IR::L
     instr->SetSrc1(IR::HelperCallOpnd::New(helperMethod, this->m_func));
     m_lowererMD.LowerCall(instr, 0);
 
-    if (bailOutInstr->GetBailOutKind() != IR::BailOutForGeneratorYield)
+    if (this->m_func->GetJITFunctionBody()->IsCoroutine())
+    {
+        if (bailOutInstr->GetBailOutKind() != IR::BailOutForGeneratorYield)
+        {
+            // Defer introducing the JMP to epilog until LowerPrologEpilog phase for Yield bailouts so
+            // that Yield does not appear to have flow out of its containing block for the RegAlloc phase.
+            // Yield is an unconditional bailout but we want to simulate the flow as if the Yield were
+            // just like a call.
+            GenerateJumpToEpilogForBailOut(bailOutInfo, instr, this->m_lowerGeneratorHelper.GetEpilogueForBailOut());
+        }
+    }
+    else
     {
-        // Defer introducing the JMP to epilog until LowerPrologEpilog phase for Yield bailouts so
-        // that Yield does not appear to have flow out of its containing block for the RegAlloc phase.
-        // Yield is an unconditional bailout but we want to simulate the flow as if the Yield were
-        // just like a call.
-        GenerateJumpToEpilogForBailOut(bailOutInfo, instr);
+        GenerateJumpToEpilogForBailOut(bailOutInfo, instr, this->EnsureEpilogueLabel());
     }
 
+
+
     return collectRuntimeStatsLabel ? collectRuntimeStatsLabel : bailOutLabel;
 }
 
-void
-Lowerer::GenerateJumpToEpilogForBailOut(BailOutInfo * bailOutInfo, IR::Instr *instr)
+IR::LabelInstr *
+Lowerer::EnsureEpilogueLabel() const
 {
-    IR::Instr * exitPrevInstr = this->m_func->m_exitInstr->m_prev;
-    // JMP to the epilog
-    IR::LabelInstr * exitTargetInstr;
+    Assert(!this->m_func->GetJITFunctionBody()->IsCoroutine());
+    IR::Instr* exitPrevInstr = this->m_func->m_exitInstr->m_prev;
     if (exitPrevInstr->IsLabelInstr())
     {
-        exitTargetInstr = exitPrevInstr->AsLabelInstr();
+        return exitPrevInstr->AsLabelInstr();
     }
     else
     {
-        exitTargetInstr = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, false);
-        exitPrevInstr->InsertAfter(exitTargetInstr);
+        IR::LabelInstr* epilogueLabel = IR::LabelInstr::New(Js::OpCode::Label, this->m_func, false);
+        LABELNAMESET(epilogueLabel, "Epilogue");
+        exitPrevInstr->InsertAfter(epilogueLabel);
+        return epilogueLabel;
     }
+}
 
+void
+Lowerer::GenerateJumpToEpilogForBailOut(BailOutInfo * bailOutInfo, IR::Instr *instr, IR::LabelInstr *exitTargetInstr)
+{
     exitTargetInstr = m_lowererMD.GetBailOutStackRestoreLabel(bailOutInfo, exitTargetInstr);
-
     IR::Instr * instrAfter = instr->m_next;
     IR::BranchInstr * exitInstr = IR::BranchInstr::New(LowererMD::MDUncondBranchOpcode, exitTargetInstr, this->m_func);
     instrAfter->InsertBefore(exitInstr);
@@ -25672,9 +25595,7 @@ Lowerer::GenerateLdHomeObj(IR::Instr* instr)
     Func *func = instr->m_func;
 
     IR::LabelInstr *labelDone = IR::LabelInstr::New(Js::OpCode::Label, func, false);
-    IR::LabelInstr *labelInlineFunc = IR::LabelInstr::New(Js::OpCode::Label, func, false);
     IR::LabelInstr *testLabel = IR::LabelInstr::New(Js::OpCode::Label, func, false);
-    IR::LabelInstr *scriptFuncLabel = IR::LabelInstr::New(Js::OpCode::Label, func, false);
     IR::Opnd *opndUndefAddress = this->LoadLibraryValueOpnd(instr, LibraryValue::ValueUndefined);
 
     IR::RegOpnd *instanceRegOpnd = IR::RegOpnd::New(TyMachPtr, func);
@@ -25695,27 +25616,71 @@ Lowerer::GenerateLdHomeObj(IR::Instr* instr)
 
     if (func->GetJITFunctionBody()->HasHomeObj())
     {
-        // Is this an function with inline cache and home obj??
-        IR::Opnd * vtableAddressInlineFuncHomObjOpnd = this->LoadVTableValueOpnd(instr, VTableValue::VtableScriptFunctionWithInlineCacheAndHomeObj);
-        IR::BranchInstr* inlineFuncHomObjOpndBr = InsertCompareBranch(IR::IndirOpnd::New(instanceRegOpnd, 0, TyMachPtr, func), vtableAddressInlineFuncHomObjOpnd, Js::OpCode::BrNeq_A, labelInlineFunc, instr);
-        InsertObjectPoison(instanceRegOpnd, inlineFuncHomObjOpndBr, instr, false);
-        IR::IndirOpnd *indirInlineFuncHomeObjOpnd = IR::IndirOpnd::New(instanceRegOpnd, Js::FunctionWithHomeObj<Js::ScriptFunctionWithInlineCache>::GetOffsetOfHomeObj(), TyMachPtr, func);
-        Lowerer::InsertMove(instanceRegOpnd, indirInlineFuncHomeObjOpnd, instr);
-        InsertBranch(Js::OpCode::Br, testLabel, instr);
+        // Is this a generator function with home obj?
+        {
+            IR::LabelInstr* nextCaseLabel = IR::LabelInstr::New(Js::OpCode::Label, func, false);
+            LABELNAMESET(nextCaseLabel, "GeneratorFunctionWithHomeObjAndComputedName");
+
+            IR::Opnd* vtableAddressInlineFuncHomObjOpnd = this->LoadVTableValueOpnd(instr, VTableValue::VtableVirtualJavascriptGeneratorFunctionWithHomeObj);
+            IR::BranchInstr* inlineFuncHomObjOpndBr = InsertCompareBranch(IR::IndirOpnd::New(instanceRegOpnd, 0, TyMachPtr, func), vtableAddressInlineFuncHomObjOpnd, Js::OpCode::BrNeq_A, nextCaseLabel, instr);
+            InsertObjectPoison(instanceRegOpnd, inlineFuncHomObjOpndBr, instr, false);
+            IR::IndirOpnd* indirInlineFuncHomeObjOpnd = IR::IndirOpnd::New(instanceRegOpnd, Js::FunctionWithHomeObj<Js::GeneratorVirtualScriptFunction>::GetOffsetOfHomeObj(), TyMachPtr, func);
+            Lowerer::InsertMove(instanceRegOpnd, indirInlineFuncHomeObjOpnd, instr);
+            InsertBranch(Js::OpCode::Br, testLabel, instr);
+
+            instr->InsertBefore(nextCaseLabel);
+        }
+
+        // Is this a generator function with home obj and computed name?
+        {
+            IR::LabelInstr* nextCaseLabel = IR::LabelInstr::New(Js::OpCode::Label, func, false);
+            LABELNAMESET(nextCaseLabel, "FunctionWithInlineCacheAndHomeObj");
+
+            IR::Opnd* vtableAddressInlineFuncHomObjOpnd = this->LoadVTableValueOpnd(instr, VTableValue::VtableVirtualJavascriptGeneratorFunctionWithHomeObjAndComputedName);
+            IR::BranchInstr* inlineFuncHomObjOpndBr = InsertCompareBranch(IR::IndirOpnd::New(instanceRegOpnd, 0, TyMachPtr, func), vtableAddressInlineFuncHomObjOpnd, Js::OpCode::BrNeq_A, nextCaseLabel, instr);
+            InsertObjectPoison(instanceRegOpnd, inlineFuncHomObjOpndBr, instr, false);
+            IR::IndirOpnd* indirInlineFuncHomeObjOpnd = IR::IndirOpnd::New(instanceRegOpnd, Js::FunctionWithComputedName<Js::FunctionWithHomeObj<Js::GeneratorVirtualScriptFunction>>::GetOffsetOfHomeObj(), TyMachPtr, func);
+            Lowerer::InsertMove(instanceRegOpnd, indirInlineFuncHomeObjOpnd, instr);
+            InsertBranch(Js::OpCode::Br, testLabel, instr);
+
+            instr->InsertBefore(nextCaseLabel);
+        }
+
+        // Is this an function with inline cache and home obj?
+        {
+            IR::LabelInstr* labelInlineFunc = IR::LabelInstr::New(Js::OpCode::Label, func, false);
+            LABELNAMESET(labelInlineFunc, "FunctionWithInlineCacheHomeObjAndComputedName");
+
+            IR::Opnd* vtableAddressInlineFuncHomObjOpnd = this->LoadVTableValueOpnd(instr, VTableValue::VtableScriptFunctionWithInlineCacheAndHomeObj);
+            IR::BranchInstr* inlineFuncHomObjOpndBr = InsertCompareBranch(IR::IndirOpnd::New(instanceRegOpnd, 0, TyMachPtr, func), vtableAddressInlineFuncHomObjOpnd, Js::OpCode::BrNeq_A, labelInlineFunc, instr);
+            InsertObjectPoison(instanceRegOpnd, inlineFuncHomObjOpndBr, instr, false);
+            IR::IndirOpnd* indirInlineFuncHomeObjOpnd = IR::IndirOpnd::New(instanceRegOpnd, Js::FunctionWithHomeObj<Js::ScriptFunctionWithInlineCache>::GetOffsetOfHomeObj(), TyMachPtr, func);
+            Lowerer::InsertMove(instanceRegOpnd, indirInlineFuncHomeObjOpnd, instr);
+            InsertBranch(Js::OpCode::Br, testLabel, instr);
+
+            instr->InsertBefore(labelInlineFunc);
+        }
+
+        // Is this a function with inline cache, home obj and computed name?
+        {
+            IR::LabelInstr* scriptFuncLabel = IR::LabelInstr::New(Js::OpCode::Label, func, false);
+            LABELNAMESET(scriptFuncLabel, "ScriptFunctionWithHomeObj");
 
-        instr->InsertBefore(labelInlineFunc);
+            IR::Opnd* vtableAddressInlineFuncHomObjCompNameOpnd = this->LoadVTableValueOpnd(instr, VTableValue::VtableScriptFunctionWithInlineCacheHomeObjAndComputedName);
+            IR::BranchInstr* inlineFuncHomObjCompNameBr = InsertCompareBranch(IR::IndirOpnd::New(instanceRegOpnd, 0, TyMachPtr, func), vtableAddressInlineFuncHomObjCompNameOpnd, Js::OpCode::BrNeq_A, scriptFuncLabel, instr);
+            InsertObjectPoison(instanceRegOpnd, inlineFuncHomObjCompNameBr, instr, false);
+            IR::IndirOpnd* indirInlineFuncHomeObjCompNameOpnd = IR::IndirOpnd::New(instanceRegOpnd, Js::FunctionWithComputedName<Js::FunctionWithHomeObj<Js::ScriptFunctionWithInlineCache>>::GetOffsetOfHomeObj(), TyMachPtr, func);
+            Lowerer::InsertMove(instanceRegOpnd, indirInlineFuncHomeObjCompNameOpnd, instr);
+            InsertBranch(Js::OpCode::Br, testLabel, instr);
 
-        // Is this a function with inline cache, home obj and computed name??
-        IR::Opnd * vtableAddressInlineFuncHomObjCompNameOpnd = this->LoadVTableValueOpnd(instr, VTableValue::VtableScriptFunctionWithInlineCacheHomeObjAndComputedName);
-        IR::BranchInstr* inlineFuncHomObjCompNameBr = InsertCompareBranch(IR::IndirOpnd::New(instanceRegOpnd, 0, TyMachPtr, func), vtableAddressInlineFuncHomObjCompNameOpnd, Js::OpCode::BrNeq_A, scriptFuncLabel, instr);
-        InsertObjectPoison(instanceRegOpnd, inlineFuncHomObjCompNameBr, instr, false);
-        IR::IndirOpnd *indirInlineFuncHomeObjCompNameOpnd = IR::IndirOpnd::New(instanceRegOpnd, Js::FunctionWithComputedName<Js::FunctionWithHomeObj<Js::ScriptFunctionWithInlineCache>>::GetOffsetOfHomeObj(), TyMachPtr, func);
-        Lowerer::InsertMove(instanceRegOpnd, indirInlineFuncHomeObjCompNameOpnd, instr);
-        InsertBranch(Js::OpCode::Br, testLabel, instr);
+            instr->InsertBefore(scriptFuncLabel);
+        }
 
-        instr->InsertBefore(scriptFuncLabel);
-        IR::IndirOpnd *indirOpnd = IR::IndirOpnd::New(instanceRegOpnd, Js::ScriptFunctionWithHomeObj::GetOffsetOfHomeObj(), TyMachPtr, func);
-        Lowerer::InsertMove(instanceRegOpnd, indirOpnd, instr);
+        // All other cases
+        {
+            IR::IndirOpnd* indirOpnd = IR::IndirOpnd::New(instanceRegOpnd, Js::ScriptFunctionWithHomeObj::GetOffsetOfHomeObj(), TyMachPtr, func);
+            Lowerer::InsertMove(instanceRegOpnd, indirOpnd, instr);
+        }
     }
     else
     {
@@ -26549,7 +26514,7 @@ Lowerer::ValidOpcodeAfterLower(IR::Instr* instr, Func * func)
     case Js::OpCode::TryCatch:
     case Js::OpCode::TryFinally:
     case Js::OpCode::Catch:
-    case Js::OpCode::GeneratorResumeJumpTable:
+    // case Js::OpCode::GeneratorResumeJumpTable:
 
     case Js::OpCode::Break:
 
@@ -26587,6 +26552,12 @@ Lowerer::ValidOpcodeAfterLower(IR::Instr* instr, Func * func)
         Assert(func->HasTry() && func->DoOptimizeTry());
         return func && !func->isPostFinalLower; //Lowered in FinalLower phase
 
+    case Js::OpCode::GeneratorBailInLabel:
+    case Js::OpCode::GeneratorResumeYieldLabel:
+    case Js::OpCode::GeneratorEpilogueFrameNullOut:
+    case Js::OpCode::GeneratorEpilogueNoFrameNullOut:
+        return func->GetJITFunctionBody()->IsCoroutine();
+
     case Js::OpCode::LazyBailOutThunkLabel:
         return func && func->HasLazyBailOut() && func->isPostFinalLower; //Lowered in FinalLower phase
     };
@@ -26617,29 +26588,8 @@ void Lowerer::LowerProfiledBinaryOp(IR::JitProfilingInstr* instr, IR::JnHelperMe
     m_lowererMD.LowerCall(instr, 0);
 }
 
-void Lowerer::GenerateNullOutGeneratorFrame(IR::Instr* insertInstr)
-{
-    // null out frame pointer on generator object to signal completion to JavascriptGenerator::CallGenerator
-    // s = MOV prm1
-    // s[offset of JavascriptGenerator::frame] = MOV nullptr
-    StackSym *symSrc = StackSym::NewImplicitParamSym(3, m_func);
-    m_func->SetArgOffset(symSrc, LowererMD::GetFormalParamOffset() * MachPtr);
-    IR::SymOpnd *srcOpnd = IR::SymOpnd::New(symSrc, TyMachPtr, m_func);
-    IR::RegOpnd *dstOpnd = IR::RegOpnd::New(TyMachReg, m_func);
-    InsertMove(dstOpnd, srcOpnd, insertInstr);
-
-    IR::IndirOpnd *indirOpnd = IR::IndirOpnd::New(dstOpnd, Js::JavascriptGenerator::GetFrameOffset(), TyMachPtr, m_func);
-    IR::AddrOpnd *addrOpnd = IR::AddrOpnd::NewNull(m_func);
-    InsertMove(indirOpnd, addrOpnd, insertInstr);
-}
-
 void Lowerer::LowerFunctionExit(IR::Instr* funcExit)
 {
-    if (m_func->GetJITFunctionBody()->IsCoroutine())
-    {
-        GenerateNullOutGeneratorFrame(funcExit->m_prev);
-    }
-
     if (!m_func->DoSimpleJitDynamicProfile())
     {
         return;
@@ -29179,3 +29129,191 @@ Lowerer::LowerCheckUpperIntBound(IR::Instr * instr)
     return instrPrev;
 }
 #endif
+
+Lowerer::LowerGeneratorHelper::LowerGeneratorHelper(Func* func, Lowerer* lowerer, LowererMD& lowererMD):
+    func(func), lowerer(lowerer), lowererMD(lowererMD) {}
+
+void Lowerer::LowerGeneratorHelper::InsertNullOutGeneratorFrameInEpilogue(IR::LabelInstr* epilogueLabel)
+{
+    IR::Instr* insertionPoint = epilogueLabel->m_next;
+
+    // null out frame pointer on generator object to signal completion to JavascriptGenerator::CallGenerator
+    // s = MOV prm1
+    // s[offset of JavascriptGenerator::frame] = MOV nullptr
+    StackSym* symSrc = StackSym::NewImplicitParamSym(3, this->func);
+    this->func->SetArgOffset(symSrc, LowererMD::GetFormalParamOffset() * MachPtr);
+    IR::SymOpnd* srcOpnd = IR::SymOpnd::New(symSrc, TyMachPtr, this->func);
+    IR::RegOpnd* dstOpnd = IR::RegOpnd::New(TyMachReg, this->func);
+    dstOpnd->SetReg(RegRSI); // callee-save register
+    InsertMove(dstOpnd, srcOpnd, insertionPoint);
+
+    IR::IndirOpnd* indirOpnd = IR::IndirOpnd::New(dstOpnd, Js::JavascriptGenerator::GetFrameOffset(), TyMachPtr, this->func);
+    IR::AddrOpnd* addrOpnd = IR::AddrOpnd::NewNull(this->func);
+    InsertMove(indirOpnd, addrOpnd, insertionPoint);
+}
+
+void
+Lowerer::LowerGeneratorHelper::InsertBailOutForElidedYield()
+{
+    IR::LabelInstr* bailOutNoSaveLabel = nullptr;
+
+    this->func->MapUntilYieldOffsetResumeLabels([this, &bailOutNoSaveLabel](int, const YieldOffsetResumeLabel& yorl)
+    {
+        if (yorl.Second() == nullptr)
+        {
+            if (bailOutNoSaveLabel == nullptr)
+            {
+                bailOutNoSaveLabel = IR::LabelInstr::New(Js::OpCode::Label, this->func);
+            }
+
+            return true;
+        }
+
+        return false;
+    });
+
+    // Insert the bailoutnosave label somewhere along with a call to BailOutNoSave helper
+    if (bailOutNoSaveLabel != nullptr)
+    {
+        IR::Instr* exitPrevInstr = this->GetEpilogueForReturnStatements()->m_prev;
+        IR::LabelInstr* exitTargetInstr = this->GetEpilogueForBailOut();
+
+        bailOutNoSaveLabel->m_hasNonBranchRef = true;
+        bailOutNoSaveLabel->isOpHelper = true;
+
+        IR::Instr* bailOutCall = IR::Instr::New(Js::OpCode::Call, this->func);
+
+        exitPrevInstr->InsertAfter(bailOutCall);
+        exitPrevInstr->InsertAfter(bailOutNoSaveLabel);
+
+        IR::RegOpnd* frameRegOpnd = IR::RegOpnd::New(nullptr, LowererMD::GetRegFramePointer(), TyMachPtr, this->func);
+
+        this->lowererMD.LoadHelperArgument(bailOutCall, frameRegOpnd);
+        IR::Instr* call = this->lowererMD.ChangeToHelperCall(bailOutCall, IR::HelperNoSaveRegistersBailOutForElidedYield);
+        call->InsertAfter(IR::BranchInstr::New(LowererMD::MDUncondBranchOpcode, exitTargetInstr, this->func));
+
+        this->func->m_bailOutNoSaveLabel = bailOutNoSaveLabel;
+        LABELNAMESET(bailOutNoSaveLabel, "GeneratorBailOutForElidedYield");
+    }
+}
+
+void
+Lowerer::LowerGeneratorHelper::EnsureEpilogueLabels()
+{
+    if (epilogueForBailOut != nullptr && epilogueForReturnStatements != nullptr)
+    {
+        return;
+    }
+
+    IR::LabelInstr* withSignalGeneratorDone = IR::LabelInstr::New(Js::OpCode::GeneratorEpilogueFrameNullOut, this->func, false);
+    LABELNAMESET(withSignalGeneratorDone, "Epilogue_WithSignalGeneratorDone");
+    withSignalGeneratorDone->m_hasNonBranchRef = true;
+    this->epilogueForReturnStatements = withSignalGeneratorDone;
+
+    IR::LabelInstr* withoutSignalGeneratorDone = IR::LabelInstr::New(Js::OpCode::GeneratorEpilogueNoFrameNullOut, this->func, false);
+    LABELNAMESET(withoutSignalGeneratorDone, "Epilogue_NoSignalGeneratorDone");
+    withoutSignalGeneratorDone->m_hasNonBranchRef = true;
+    this->epilogueForBailOut = withoutSignalGeneratorDone;
+
+    this->func->m_exitInstr->InsertBefore(withSignalGeneratorDone);
+    this->func->m_exitInstr->InsertBefore(withoutSignalGeneratorDone);
+}
+
+
+IR::LabelInstr*
+Lowerer::LowerGeneratorHelper::GetEpilogueForReturnStatements()
+{
+    this->EnsureEpilogueLabels();
+    return this->epilogueForReturnStatements;
+}
+
+IR::LabelInstr*
+Lowerer::LowerGeneratorHelper::GetEpilogueForBailOut()
+{
+    this->EnsureEpilogueLabels();
+    return this->epilogueForBailOut;
+}
+
+void
+Lowerer::LowerGeneratorHelper::LowerGeneratorResumeJumpTable(IR::Instr* jumpTableInstr)
+{
+    Assert(this->func->GetJITFunctionBody()->IsCoroutine());
+    Assert(jumpTableInstr->m_opcode == Js::OpCode::GeneratorResumeJumpTable);
+
+    IR::Opnd* srcOpnd = jumpTableInstr->UnlinkSrc1();
+
+    this->func->MapYieldOffsetResumeLabels([this, &srcOpnd, &jumpTableInstr](int i, const YieldOffsetResumeLabel& yorl)
+    {
+        uint32 offset = yorl.First();
+        IR::LabelInstr* label = yorl.Second();
+
+        if (label != nullptr && label->m_hasNonBranchRef)
+        {
+            // Also fix up the bailout at the label with the jump to epilog that was not emitted in GenerateBailOut()
+            this->lowerer->GenerateJumpToEpilogForBailOut(label->m_prev->GetBailOutInfo(), label->m_prev, this->GetEpilogueForBailOut());
+        }
+        else if (label == nullptr)
+        {
+            label = this->func->m_bailOutNoSaveLabel;
+        }
+
+        // For each offset label pair, insert a compare of the offset and branch if equal to the label
+        this->lowerer->InsertCompareBranch(srcOpnd, IR::IntConstOpnd::New(offset, TyUint32, this->func), Js::OpCode::BrSrEq_A, label, jumpTableInstr);
+    });
+
+    jumpTableInstr->Remove();
+}
+
+void
+Lowerer::LowerGeneratorHelper::LowerCreateInterpreterStackFrameForGenerator(IR::Instr* instr)
+{
+    IR::Opnd* scriptFunctionOpnd = nullptr;
+    IR::Opnd* functionBodyOpnd = this->lowerer->CreateFunctionBodyOpnd(instr->m_func);
+    IR::Opnd* generatorOpnd = instr->UnlinkSrc1();
+    IR::IntConstOpnd* doProfileOpnd = IR::IntConstOpnd::New(0, TyInt8, instr->m_func);
+
+    this->lowererMD.LoadFunctionObjectOpnd(instr, scriptFunctionOpnd);
+
+    this->lowererMD.LoadHelperArgument(instr, doProfileOpnd);
+    this->lowererMD.LoadHelperArgument(instr, generatorOpnd);
+    this->lowererMD.LoadHelperArgument(instr, functionBodyOpnd);
+    this->lowererMD.LoadHelperArgument(instr, scriptFunctionOpnd);
+
+    this->lowererMD.ChangeToHelperCall(instr, IR::HelperCreateInterpreterStackFrameForGenerator);
+}
+
+IR::SymOpnd*
+Lowerer::LowerGeneratorHelper::CreateResumeYieldDataOpnd() const
+{
+    StackSym* resumeYieldDataSym = StackSym::NewImplicitParamSym(4, this->func);
+    this->func->SetArgOffset(resumeYieldDataSym, (LowererMD::GetFormalParamOffset() + 1) * MachPtr);
+    return IR::SymOpnd::New(resumeYieldDataSym, TyMachPtr, this->func);
+}
+
+void
+Lowerer::LowerGeneratorHelper::LowerGeneratorLoadResumeYieldData(IR::Instr* instr)
+{
+    // prm2 is the ResumeYieldData pointer per calling convention established in JavascriptGenerator::CallGenerator
+    // This is the value the bytecode expects to be in the dst register of the Yield opcode after resumption.
+    // Load it here after the bail-in.
+    this->lowerer->InsertMove(instr->UnlinkDst(), this->CreateResumeYieldDataOpnd(), instr);
+    instr->Unlink();
+}
+
+void
+Lowerer::LowerGeneratorHelper::LowerResumeGenerator(IR::Instr* instr)
+{
+    IR::Opnd* srcOpnd1 = instr->UnlinkSrc1();
+    IR::Opnd* srcOpnd2 = instr->m_opcode == Js::OpCode::ResumeYieldStar ? instr->UnlinkSrc2() : IR::AddrOpnd::NewNull(this->func);
+    this->lowererMD.LoadHelperArgument(instr, srcOpnd2);
+    this->lowererMD.LoadHelperArgument(instr, srcOpnd1);
+    this->lowererMD.ChangeToHelperCall(instr, IR::HelperResumeYield);
+}
+
+void
+Lowerer::LowerGeneratorHelper::LowerYield(IR::Instr* instr)
+{
+    instr->FreeSrc1(); // Source is not actually used by the backend other than to calculate lifetime
+    instr->FreeDst();
+    this->lowerer->GenerateBailOut(instr);
+}

+ 64 - 6
lib/Backend/Lower.h

@@ -53,7 +53,8 @@ class Lowerer
 
 public:
     Lowerer(Func * func) : m_func(func), m_lowererMD(func), nextStackFunctionOpnd(nullptr), outerMostLoopLabel(nullptr),
-        initializedTempSym(nullptr), addToLiveOnBackEdgeSyms(nullptr), currentRegion(nullptr)
+        initializedTempSym(nullptr), addToLiveOnBackEdgeSyms(nullptr), currentRegion(nullptr),
+        m_lowerGeneratorHelper(LowerGeneratorHelper(func, this, this->m_lowererMD))
     {
 #ifdef RECYCLER_WRITE_BARRIER_JIT
         m_func->m_lowerer = this;
@@ -75,7 +76,6 @@ public:
     void LowerRange(IR::Instr *instrStart, IR::Instr *instrEnd, bool defaultDoFastPath, bool defaultDoLoopFastPath);
     void LowerPrologEpilog();
     void LowerPrologEpilogAsmJs();
-    void LowerGeneratorResumeJumpTable();
 
     void DoInterruptProbes();
 
@@ -126,7 +126,6 @@ private:
     void            LowerProfiledBeginSwitch(IR::JitProfilingInstr *instr);
     void            LowerFunctionExit(IR::Instr* funcExit);
     void            LowerFunctionEntry(IR::Instr* funcEntry);
-    void            GenerateNullOutGeneratorFrame(IR::Instr* instrInsert);
     void            LowerFunctionBodyCallCountChange(IR::Instr *const insertBeforeInstr);
     IR::Instr*      LowerProfiledNewArray(IR::JitProfilingInstr* instr, bool hasArgs);
     IR::Instr *     LowerProfiledLdSlot(IR::JitProfilingInstr *instr);
@@ -193,6 +192,8 @@ private:
         IR::LabelInstr * labelFallThru,
         bool isInlineSlot);
 
+    IR::LabelInstr* EnsureEpilogueLabel() const;
+
     void            GenerateFlagProtoCheck(IR::Instr * insertBeforeInstr, IR::RegOpnd * opndInlineCache, IR::LabelInstr * labelFail);
     void            GenerateFlagInlineCacheCheck(IR::Instr * instrLdSt, IR::RegOpnd * opndType, IR::RegOpnd * opndInlineCache, IR::LabelInstr * labelNext);
     bool            GenerateFastLdMethodFromFlags(IR::Instr * instrLdFld);
@@ -602,7 +603,7 @@ private:
     void            LowerInstrWithBailOnResultCondition(IR::Instr *const instr, const IR::BailOutKind bailOutKind, IR::LabelInstr *const bailOutLabel, IR::LabelInstr *const skipBailOutLabel) const;
     void            GenerateObjectTestAndTypeLoad(IR::Instr *instrLdSt, IR::RegOpnd *opndBase, IR::RegOpnd *opndType, IR::LabelInstr *labelHelper);
     IR::LabelInstr *GenerateBailOut(IR::Instr * instr, IR::BranchInstr * branchInstr = nullptr, IR::LabelInstr * labelBailOut = nullptr, IR::LabelInstr * collectRuntimeStatsLabel = nullptr);
-    void            GenerateJumpToEpilogForBailOut(BailOutInfo * bailOutInfo, IR::Instr *instrAfter);
+    void            GenerateJumpToEpilogForBailOut(BailOutInfo * bailOutInfo, IR::Instr *instrAfter, IR::LabelInstr *exitTargetInstr);
     void            GenerateThrow(IR::Opnd* errorCode, IR::Instr * instr);
     void            LowerDivI4(IR::Instr * const instr);
     void            LowerRemI4(IR::Instr * const instr);
@@ -764,8 +765,6 @@ private:
     IR::Instr *     LowerLdNativeCodeData(IR::Instr *instr);
     IR::Instr *     LowerFrameDisplayCheck(IR::Instr * instr);
     IR::Instr *     LowerSlotArrayCheck(IR::Instr * instr);
-    void            InsertSlotArrayCheck(IR::Instr * instr, StackSym * dstSym, uint32 slotId);
-    void            InsertFrameDisplayCheck(IR::Instr * instr, StackSym * dstSym, FrameDisplayCheckRecord * record);
     static void     InsertObjectPoison(IR::Opnd* poisonedOpnd, IR::BranchInstr* branchInstr, IR::Instr* insertInstr, bool isForStore);
 
     IR::RegOpnd *   LoadIndexFromLikelyFloat(IR::RegOpnd *indexOpnd, const bool skipNegativeCheck, IR::LabelInstr *const notTaggedIntLabel, IR::LabelInstr *const negativeLabel, IR::Instr *const insertBeforeInstr);
@@ -838,4 +837,63 @@ private:
     HelperCallCheckState oldHelperCallCheckState;
     Js::OpCode m_currentInstrOpCode;
 #endif
+
+    //
+    // Generator
+    //
+    class LowerGeneratorHelper {
+        Func* const func;
+        LowererMD &lowererMD;
+        Lowerer* const lowerer;
+
+        IR::LabelInstr* epilogueForReturnStatements = nullptr;
+        IR::LabelInstr* epilogueForBailOut = nullptr;
+
+        void EnsureEpilogueLabels();
+        IR::SymOpnd* CreateResumeYieldDataOpnd() const;
+
+    public:
+        LowerGeneratorHelper(Func* func, Lowerer* lowerer, LowererMD &lowererMD);
+
+        // Insert code to set generator->interpreterFrame to nullptr so that we know the
+        // generator has finished executing and has no more yield points.
+        // This will be inserted at the epilogue of the jitted function.
+        void InsertNullOutGeneratorFrameInEpilogue(IR::LabelInstr* epilogueLabel);
+
+        // Normally, after every bail out, we would do a jump to the epilogue and pop off the current frame.
+        // However, in the case of generator, we also want to null out the interpreter frame to signal that
+        // the generator is completed in the epilogue (i.e: there are no more yield points). This makes
+        // jumping to the epilogue after the bailout call returns not possible because we wouldn't know if
+        // the jump was because we actually want to return or because we have just bailed out.
+        //
+        // To deal with this, generators will have two kinds of epilogue label:
+        //  - one that nulls out the generator's interpreter frame
+        //  - one that doesn't
+        //
+        // Both of them share the register restore code, only the jump point differs:
+        //
+        // $Label_GeneratorEpilogueFrameNullOut: (intended for return statement)
+        //    null out generator's interpreter frame
+        // $Label_GeneratorEpilogueNoFrameNullOut: (intended for jumping after bailout call returns)
+        //    pop ...
+        //    pop ...
+        //    ret
+        //
+        IR::LabelInstr* GetEpilogueForReturnStatements();
+        IR::LabelInstr* GetEpilogueForBailOut();
+
+        // Introduce a BailOutNoSave label if there were yield points that were elided due to optimizations.
+        // They could still be hit if an active generator object had been paused at such a yield point when
+        // the function body was JITed. So safe guard such a case by having the native code simply jump back
+        // to the interpreter for such yield points.
+        void InsertBailOutForElidedYield();
+
+        void LowerGeneratorResumeJumpTable(IR::Instr* jumpTableInstr);
+        void LowerCreateInterpreterStackFrameForGenerator(IR::Instr* instr);
+        void LowerResumeGenerator(IR::Instr* instr);
+        void LowerYield(IR::Instr* instr);
+        void LowerGeneratorLoadResumeYieldData(IR::Instr* instr);
+    };
+
+    LowerGeneratorHelper m_lowerGeneratorHelper;
 };

+ 4 - 4
lib/Backend/LowerMDShared.cpp

@@ -604,7 +604,7 @@ LowererMD::LoadArgumentsFromFrame(IR::Instr * instr)
     }
     else
     {
-        instr->SetSrc1(this->CreateStackArgumentsSlotOpnd());
+        instr->SetSrc1(LowererMD::CreateStackArgumentsSlotOpnd(this->m_func));
     }
 
     instr->m_opcode = Js::OpCode::MOV;
@@ -4456,13 +4456,13 @@ LowererMD::GenerateFastScopedStFld(IR::Instr * instrStScopedFld)
 }
 
 IR::Opnd *
-LowererMD::CreateStackArgumentsSlotOpnd()
+LowererMD::CreateStackArgumentsSlotOpnd(Func *func)
 {
-    StackSym *sym = StackSym::New(TyMachReg, this->m_func);
+    StackSym *sym = StackSym::New(TyMachReg, func);
     sym->m_offset = -MachArgsSlotOffset;
     sym->m_allocated = true;
 
-    return IR::SymOpnd::New(sym, TyMachReg, this->m_func);
+    return IR::SymOpnd::New(sym, TyMachReg, func);
 }
 
 IR::RegOpnd *

+ 1 - 1
lib/Backend/LowerMDShared.h

@@ -103,10 +103,10 @@ public:
     static  IR::Instr *     ChangeToAssignNoBarrierCheck(IR::Instr * instr);
     static  IR::Instr *     ChangeToAssign(IR::Instr * instr, IRType type);
     static  void            ImmedSrcToReg(IR::Instr * instr, IR::Opnd * newOpnd, int srcNum);
+    static IR::Opnd *       CreateStackArgumentsSlotOpnd(Func *func);
 
             IR::Instr *     LoadInputParamCount(IR::Instr * instr, int adjust = 0, bool needFlags = false);
             IR::Instr *     LoadStackArgPtr(IR::Instr * instr);
-            IR::Opnd *      CreateStackArgumentsSlotOpnd();
             IR::Instr *     LoadArgumentsFromFrame(IR::Instr * instr);
             IR::Instr *     LoadArgumentCount(IR::Instr * instr);
             IR::Instr *     LoadHeapArguments(IR::Instr * instr);

+ 13 - 2
lib/Backend/Peeps.cpp

@@ -104,11 +104,22 @@ Peeps::PeepFunc()
         }
 
         case IR::InstrKindBranch:
+        {
             if (!peepsEnabled || instr->m_opcode == Js::OpCode::Leave)
             {
                 break;
             }
-            instrNext = Peeps::PeepBranch(instr->AsBranchInstr());
+
+            IR::BranchInstr *branchInstr = instr->AsBranchInstr();
+            IR::LabelInstr* target = branchInstr->GetTarget();
+
+            // Don't remove any branches to the generator's epilogue
+            if (target != nullptr && target->IsGeneratorEpilogueLabel())
+            {
+                break;
+            }
+
+            instrNext = Peeps::PeepBranch(branchInstr);
 #if defined(_M_IX86) || defined(_M_X64)
             Assert(instrNext && instrNext->m_prev);
             if (instrNext->m_prev->IsBranchInstr())
@@ -118,7 +129,7 @@ Peeps::PeepFunc()
 
 #endif
             break;
-
+        }
         case IR::InstrKindPragma:
             if (instr->m_opcode == Js::OpCode::Nop)
             {

+ 1 - 1
lib/Backend/SccLiveness.cpp

@@ -298,7 +298,7 @@ SCCLiveness::Build()
             this->EndOpHelper(instr);
         }
 
-    }NEXT_INSTR_IN_FUNC_EDITING;
+    } NEXT_INSTR_IN_FUNC_EDITING;
 
     if (this->func->HasTry())
     {

+ 267 - 137
lib/Backend/amd64/LinearScanMD.cpp

@@ -10,7 +10,8 @@ extern const IRType RegTypes[RegNumCount];
 LinearScanMD::LinearScanMD(Func *func)
     : helperSpillSlots(nullptr),
       maxOpHelperSpilledLiveranges(0),
-      func(func)
+      func(func),
+      bailIn(GeneratorBailIn(func, this))
 {
     this->byteableRegsBv.ClearAll();
 
@@ -318,9 +319,6 @@ LinearScanMD::GenerateBailOut(IR::Instr * instr, __in_ecount(registerSaveSymsCou
         this->linearScan->RecordUse(stackSym->scratch.linearScan.lifetime, firstInstr, nullptr, true);
     }
 
-
-
-
     // Load the bailout target into rax
     //     mov  rax, BailOut
     //     call rax
@@ -336,139 +334,6 @@ LinearScanMD::GenerateBailOut(IR::Instr * instr, __in_ecount(registerSaveSymsCou
     }
 }
 
-// Gets the InterpreterStackFrame pointer into RAX.
-// Restores the live stack locations followed by the live registers from
-// the interpreter's register slots.
-// RecordDefs each live register that is restored.
-//
-// Generates the following code:
-//
-// MOV rax, param0
-// MOV rax, [rax + JavascriptGenerator::GetFrameOffset()]
-//
-// for each live stack location, sym
-//
-//   MOV rcx, [rax + regslot offset]
-//   MOV sym(stack location), rcx
-//
-// for each live register, sym (rax is restore last if it is live)
-//
-//   MOV sym(register), [rax + regslot offset]
-//
-IR::Instr *
-LinearScanMD::GenerateBailInForGeneratorYield(IR::Instr * resumeLabelInstr, BailOutInfo * bailOutInfo)
-{
-    IR::Instr * instrAfter = resumeLabelInstr->m_next;
-
-    IR::RegOpnd * raxRegOpnd = IR::RegOpnd::New(nullptr, RegRAX, TyMachPtr, this->func);
-    IR::RegOpnd * rcxRegOpnd = IR::RegOpnd::New(nullptr, RegRCX, TyVar, this->func);
-
-    StackSym * sym = StackSym::NewParamSlotSym(1, this->func);
-    this->func->SetArgOffset(sym, LowererMD::GetFormalParamOffset() * MachPtr);
-    IR::SymOpnd * symOpnd = IR::SymOpnd::New(sym, TyMachPtr, this->func);
-    LinearScan::InsertMove(raxRegOpnd, symOpnd, instrAfter);
-
-    IR::IndirOpnd * indirOpnd = IR::IndirOpnd::New(raxRegOpnd, Js::JavascriptGenerator::GetFrameOffset(), TyMachPtr, this->func);
-    LinearScan::InsertMove(raxRegOpnd, indirOpnd, instrAfter);
-
-
-    // rax points to the frame, restore stack syms and registers except rax, restore rax last
-
-    IR::Instr * raxRestoreInstr = nullptr;
-    IR::Instr * instrInsertStackSym = instrAfter;
-    IR::Instr * instrInsertRegSym = instrAfter;
-
-    Assert(bailOutInfo->capturedValues->constantValues.Empty());
-    Assert(bailOutInfo->capturedValues->copyPropSyms.Empty());
-    Assert(bailOutInfo->liveLosslessInt32Syms->IsEmpty());
-    Assert(bailOutInfo->liveFloat64Syms->IsEmpty());
-
-    auto restoreSymFn = [this, &raxRegOpnd, &rcxRegOpnd, &raxRestoreInstr, &instrInsertStackSym, &instrInsertRegSym](Js::RegSlot regSlot, StackSym* stackSym)
-    {
-        Assert(stackSym->IsVar());
-
-        int32 offset = regSlot * sizeof(Js::Var) + Js::InterpreterStackFrame::GetOffsetOfLocals();
-
-        IR::Opnd * srcOpnd = IR::IndirOpnd::New(raxRegOpnd, offset, stackSym->GetType(), this->func);
-        Lifetime * lifetime = stackSym->scratch.linearScan.lifetime;
-
-        if (lifetime->isSpilled)
-        {
-            // stack restores require an extra register since we can't move an indir directly to an indir on amd64
-            IR::SymOpnd * dstOpnd = IR::SymOpnd::New(stackSym, stackSym->GetType(), this->func);
-            LinearScan::InsertMove(rcxRegOpnd, srcOpnd, instrInsertStackSym);
-            LinearScan::InsertMove(dstOpnd, rcxRegOpnd, instrInsertStackSym);
-        }
-        else
-        {
-            // register restores must come after stack restores so that we have RAX and RCX free to
-            // use for stack restores and further RAX must be restored last since it holds the
-            // pointer to the InterpreterStackFrame from which we are restoring values.
-            // We must also track these restores using RecordDef in case the symbols are spilled.
-
-            IR::RegOpnd * dstRegOpnd = IR::RegOpnd::New(stackSym, stackSym->GetType(), this->func);
-            dstRegOpnd->SetReg(lifetime->reg);
-
-            IR::Instr * instr = LinearScan::InsertMove(dstRegOpnd, srcOpnd, instrInsertRegSym);
-
-            if (instrInsertRegSym == instrInsertStackSym)
-            {
-                // this is the first register sym, make sure we don't insert stack stores
-                // after this instruction so we can ensure rax and rcx remain free to use
-                // for restoring spilled stack syms.
-                instrInsertStackSym = instr;
-            }
-
-            if (lifetime->reg == RegRAX)
-            {
-                // ensure rax is restored last
-                Assert(instrInsertRegSym != instrInsertStackSym);
-
-                instrInsertRegSym = instr;
-
-                if (raxRestoreInstr != nullptr)
-                {
-                    AssertMsg(false, "this is unexpected until copy prop is enabled");
-                    // rax was mapped to multiple bytecode registers.  Obviously only the first
-                    // restore we do will work so change all following stores to `mov rax, rax`.
-                    // We still need to keep them around for RecordDef in case the corresponding
-                    // dst sym is spilled later on.
-                    raxRestoreInstr->FreeSrc1();
-                    raxRestoreInstr->SetSrc1(raxRegOpnd);
-                }
-
-                raxRestoreInstr = instr;
-            }
-
-            this->linearScan->RecordDef(lifetime, instr, 0);
-        }
-    };
-
-    FOREACH_BITSET_IN_SPARSEBV(symId, bailOutInfo->byteCodeUpwardExposedUsed)
-    {
-        StackSym* stackSym = this->func->m_symTable->FindStackSym(symId);
-        restoreSymFn(stackSym->GetByteCodeRegSlot(), stackSym);
-    }
-    NEXT_BITSET_IN_SPARSEBV;
-
-    if (bailOutInfo->capturedValues->argObjSyms)
-    {
-        FOREACH_BITSET_IN_SPARSEBV(symId, bailOutInfo->capturedValues->argObjSyms)
-        {
-            StackSym* stackSym = this->func->m_symTable->FindStackSym(symId);
-            restoreSymFn(stackSym->GetByteCodeRegSlot(), stackSym);
-        }
-        NEXT_BITSET_IN_SPARSEBV;
-    }
-
-    Js::RegSlot localsCount = this->func->GetJITFunctionBody()->GetLocalsCount();
-    bailOutInfo->IterateArgOutSyms([localsCount, &restoreSymFn](uint, uint argOutSlotOffset, StackSym* sym) {
-        restoreSymFn(localsCount + argOutSlotOffset, sym);
-    });
-
-    return instrAfter;
-}
-
 uint LinearScanMD::GetRegisterSaveIndex(RegNum reg)
 {
     if (RegTypes[reg] == TyFloat64)
@@ -574,3 +439,268 @@ RegNum LinearScanMD::GetParamReg(IR::SymOpnd *symOpnd, Func *func)
 
     return reg;
 }
+
+
+IR::Instr* LinearScanMD::GenerateBailInForGeneratorYield(IR::Instr* resumeLabelInstr, BailOutInfo* bailOutInfo)
+{
+    Assert(!bailOutInfo->capturedValues || bailOutInfo->capturedValues->constantValues.Empty());
+    Assert(!bailOutInfo->capturedValues || bailOutInfo->capturedValues->copyPropSyms.Empty());
+    Assert(!bailOutInfo->liveLosslessInt32Syms || bailOutInfo->liveLosslessInt32Syms->IsEmpty());
+    Assert(!bailOutInfo->liveFloat64Syms || bailOutInfo->liveFloat64Syms->IsEmpty());
+    return this->bailIn.GenerateBailIn(resumeLabelInstr, bailOutInfo);
+}
+
+LinearScanMD::GeneratorBailIn::GeneratorBailIn(Func* func, LinearScanMD* linearScanMD):
+    func(func),
+    linearScanMD(linearScanMD),
+    jitFnBody(func->GetJITFunctionBody()),
+    initializedRegs(func->m_alloc),
+    raxRegOpnd(IR::RegOpnd::New(nullptr, RegRAX, TyMachPtr, func)),
+    rcxRegOpnd(IR::RegOpnd::New(nullptr, RegRCX, TyVar, func))
+{
+    // The yield register holds the evaluated value of the expression passed as
+    // the parameter to .next(), this can be obtained from the generator object itself,
+    // so no need to restore.
+    this->initializedRegs.Set(this->jitFnBody->GetYieldReg());
+
+    // The environment is loaded before the resume jump table, no need to restore either.
+    this->initializedRegs.Set(this->jitFnBody->GetEnvReg());
+}
+
+// Restores the live stack locations followed by the live registers from
+// the interpreter's register slots.
+// RecordDefs each live register that is restored.
+//
+// Generates the following code:
+// 
+// PUSH rax ; if needed
+// PUSH rcx ; if needed
+//
+// MOV rax, param0
+// MOV rax, [rax + JavascriptGenerator::GetFrameOffset()]
+//
+// for each live stack location, sym
+//
+//   MOV rcx, [rax + regslot offset]
+//   MOV sym(stack location), rcx
+//
+// for each live register, sym (rax is restore last if it is live)
+//
+//   MOV sym(register), [rax + regslot offset]
+//
+// POP rax; if needed
+// POP rcx; if needed
+IR::Instr* LinearScanMD::GeneratorBailIn::GenerateBailIn(IR::Instr* resumeLabelInstr, BailOutInfo* bailOutInfo)
+{
+    IR::Instr* instrAfter = resumeLabelInstr->m_next;
+
+    // 1) Load the generator object that was passed as one of the arguments to the jitted frame
+    LinearScan::InsertMove(this->raxRegOpnd, this->CreateGeneratorObjectOpnd(), instrAfter);
+
+    // 2) Gets the InterpreterStackFrame pointer into rax
+    IR::IndirOpnd* generatorFrameOpnd = IR::IndirOpnd::New(this->raxRegOpnd, Js::JavascriptGenerator::GetFrameOffset(), TyMachPtr, this->func);
+    LinearScan::InsertMove(this->raxRegOpnd, generatorFrameOpnd, instrAfter);
+
+    // 3) Put the Javascript's `arguments` object, which is stored in the interpreter frame, to the jit's stack slot if needed
+    //    See BailOutRecord::RestoreValues
+    if (this->func->HasArgumentSlot())
+    {
+        IR::IndirOpnd* generatorArgumentsOpnd = IR::IndirOpnd::New(this->raxRegOpnd, Js::InterpreterStackFrame::GetOffsetOfArguments(), TyMachPtr, this->func);
+        LinearScan::InsertMove(this->rcxRegOpnd, generatorArgumentsOpnd, instrAfter);
+        LinearScan::InsertMove(LowererMD::CreateStackArgumentsSlotOpnd(this->func), this->rcxRegOpnd, instrAfter);
+    }
+
+    BailInInsertionPoint insertionPoint
+    {
+        nullptr,    /* raxRestoreInstr */
+        instrAfter, /* instrInsertStackSym */
+        instrAfter  /* instrInsertRegSym */
+    };
+
+    SaveInitializedRegister saveInitializedReg { false /* rax */, false /* rcx */ };
+
+    // 4) Restore symbols
+    // - We don't need to restore argObjSyms because StackArgs is currently not enabled
+    //   Commented out here in case we do want to enable it in the future:
+    // this->InsertRestoreSymbols(bailOutInfo->capturedValues->argObjSyms, insertionPoint, saveInitializedReg);
+    // 
+    // - We move all argout symbols right before the call so we don't need to restore argouts either
+    this->InsertRestoreSymbols(bailOutInfo->byteCodeUpwardExposedUsed, insertionPoint, saveInitializedReg);
+    Assert(!this->func->IsStackArgsEnabled());
+
+    // 5) Save/restore rax/rcx if needed
+    if (saveInitializedReg.rax)
+    {
+        this->InsertSaveAndRestore(resumeLabelInstr, instrAfter, raxRegOpnd);
+    }
+
+    if (saveInitializedReg.rcx)
+    {
+        this->InsertSaveAndRestore(resumeLabelInstr, instrAfter, rcxRegOpnd);
+    }
+
+    return instrAfter;
+}
+
+void LinearScanMD::GeneratorBailIn::InsertRestoreSymbols(
+    BVSparse<JitArenaAllocator>* symbols,
+    BailInInsertionPoint& insertionPoint,
+    SaveInitializedRegister& saveInitializedReg
+)
+{
+    if (symbols == nullptr)
+    {
+        return;
+    }
+
+    FOREACH_BITSET_IN_SPARSEBV(symId, symbols)
+    {
+        StackSym* stackSym = this->func->m_symTable->FindStackSym(symId);
+        Lifetime* lifetime = stackSym->scratch.linearScan.lifetime;
+
+        if (this->NeedsReloadingValueWhenBailIn(stackSym))
+        {
+            Js::RegSlot regSlot = stackSym->GetByteCodeRegSlot();
+            IR::Opnd* srcOpnd = IR::IndirOpnd::New(
+                this->raxRegOpnd,
+                this->GetOffsetFromInterpreterStackFrame(regSlot),
+                stackSym->GetType(),
+                this->func
+            );
+
+            if (lifetime->isSpilled)
+            {
+                this->InsertRestoreStackSymbol(stackSym, srcOpnd, insertionPoint);
+            }
+            else
+            {
+                this->InsertRestoreRegSymbol(stackSym, srcOpnd, insertionPoint);
+            }
+        }
+        else
+        {
+            if (lifetime->reg == RegRAX)
+            {
+                Assert(!saveInitializedReg.rax);
+                saveInitializedReg.rax = true;
+            }
+
+            if (lifetime->reg == RegRCX)
+            {
+                Assert(!saveInitializedReg.rcx);
+                saveInitializedReg.rcx = true;
+            }
+        }
+    }
+    NEXT_BITSET_IN_SPARSEBV;
+}
+
+bool LinearScanMD::GeneratorBailIn::NeedsReloadingValueWhenBailIn(StackSym* sym) const
+{
+    // We load constant values before the generator resume jump table, no need to reload
+    if (this->func->GetJITFunctionBody()->RegIsConstant(sym->GetByteCodeRegSlot()))
+    {
+        return false;
+    }
+
+    // If we have for-in in the generator, don't need to reload the symbol again as it is done
+    // during the resume jump table
+    if (this->func->GetForInEnumeratorSymForGeneratorSym() && this->func->GetForInEnumeratorSymForGeneratorSym()->m_id == sym->m_id)
+    {
+        return false;
+    }
+
+    // Check for other special registers that are already initialized
+    return !this->initializedRegs.Test(sym->GetByteCodeRegSlot());
+}
+
+void LinearScanMD::GeneratorBailIn::InsertRestoreRegSymbol(StackSym* stackSym, IR::Opnd* srcOpnd, BailInInsertionPoint& insertionPoint)
+{
+    Lifetime* lifetime = stackSym->scratch.linearScan.lifetime;
+
+    // Register restores must come after stack restores so that we have RAX and RCX free to
+    // use for stack restores and further RAX must be restored last since it holds the
+    // pointer to the InterpreterStackFrame from which we are restoring values.
+    // We must also track these restores using RecordDef in case the symbols are spilled.
+
+    IR::RegOpnd* dstRegOpnd = IR::RegOpnd::New(stackSym, stackSym->GetType(), this->func);
+    dstRegOpnd->SetReg(lifetime->reg);
+
+    IR::Instr* instr = LinearScan::InsertMove(dstRegOpnd, srcOpnd, insertionPoint.instrInsertRegSym);
+
+    if (insertionPoint.instrInsertRegSym == insertionPoint.instrInsertStackSym)
+    {
+        // This is the first register sym, make sure we don't insert stack stores
+        // after this instruction so we can ensure rax and rcx remain free to use
+        // for restoring spilled stack syms.
+        insertionPoint.instrInsertStackSym = instr;
+    }
+
+    if (lifetime->reg == RegRAX)
+    {
+        // Ensure rax is restored last
+        Assert(insertionPoint.instrInsertRegSym != insertionPoint.instrInsertStackSym);
+
+        insertionPoint.instrInsertRegSym = instr;
+
+        if (insertionPoint.raxRestoreInstr != nullptr)
+        {
+            AssertMsg(false, "this is unexpected until copy prop is enabled");
+            // rax was mapped to multiple bytecode registers.  Obviously only the first
+            // restore we do will work so change all following stores to `mov rax, rax`.
+            // We still need to keep them around for RecordDef in case the corresponding
+            // dst sym is spilled later on.
+            insertionPoint.raxRestoreInstr->FreeSrc1();
+            insertionPoint.raxRestoreInstr->SetSrc1(this->raxRegOpnd);
+        }
+
+        insertionPoint.raxRestoreInstr = instr;
+    }
+
+    this->linearScanMD->linearScan->RecordDef(lifetime, instr, 0);
+}
+
+void LinearScanMD::GeneratorBailIn::InsertRestoreStackSymbol(StackSym* stackSym, IR::Opnd* srcOpnd, BailInInsertionPoint& insertionPoint)
+{
+    // Stack restores require an extra register since we can't move an indir directly to an indir on amd64
+    IR::SymOpnd* dstOpnd = IR::SymOpnd::New(stackSym, stackSym->GetType(), this->func);
+    LinearScan::InsertMove(this->rcxRegOpnd, srcOpnd, insertionPoint.instrInsertStackSym);
+    LinearScan::InsertMove(dstOpnd, this->rcxRegOpnd, insertionPoint.instrInsertStackSym);
+}
+
+IR::SymOpnd* LinearScanMD::GeneratorBailIn::CreateGeneratorObjectOpnd() const
+{
+    StackSym* sym = StackSym::NewParamSlotSym(1, this->func);
+    this->func->SetArgOffset(sym, LowererMD::GetFormalParamOffset() * MachPtr);
+    return IR::SymOpnd::New(sym, TyMachPtr, this->func);
+}
+
+uint32 LinearScanMD::GeneratorBailIn::GetOffsetFromInterpreterStackFrame(Js::RegSlot regSlot) const
+{
+    // Some objects aren't stored in the local space in interpreter frame, but instead
+    // in their own fields. Use their offsets in such cases.
+    if (regSlot == this->jitFnBody->GetLocalFrameDisplayReg())
+    {
+        return Js::InterpreterStackFrame::GetOffsetOfLocalFrameDisplay();
+    }
+    else if (regSlot == this->jitFnBody->GetLocalClosureReg())
+    {
+        return Js::InterpreterStackFrame::GetOffsetOfLocalClosure();
+    }
+    else if (regSlot == this->jitFnBody->GetParamClosureReg())
+    {
+        return Js::InterpreterStackFrame::GetOffsetOfParamClosure();
+    }
+    else
+    {
+        return regSlot * sizeof(Js::Var) + Js::InterpreterStackFrame::GetOffsetOfLocals();
+    }
+}
+
+void LinearScanMD::GeneratorBailIn::InsertSaveAndRestore(IR::Instr* start, IR::Instr* end, IR::RegOpnd* reg)
+{
+    IR::Instr* push = IR::Instr::New(Js::OpCode::PUSH, nullptr /* dst */, reg /* src1 */, this->func);
+    IR::Instr* pop = IR::Instr::New(Js::OpCode::POP, reg /* dst */, this->func);
+    start->InsertAfter(push);
+    end->InsertBefore(pop);
+}

+ 80 - 1
lib/Backend/amd64/LinearScanMD.h

@@ -37,7 +37,7 @@ public:
     void        GenerateBailOut(IR::Instr * instr,
                                 __in_ecount(registerSaveSymsCount) StackSym ** registerSaveSyms,
                                 uint registerSaveSymsCount);
-    IR::Instr  *GenerateBailInForGeneratorYield(IR::Instr * resumeLabelInstr, BailOutInfo * bailOutInfo);
+    IR::Instr* GenerateBailInForGeneratorYield(IR::Instr* resumeLabelInstr, BailOutInfo* bailOutInfo);
 
 private:
     static void SaveAllRegisters(BailOutRecord *const bailOutRecord);
@@ -57,4 +57,83 @@ public:
     static const uint RegisterSaveSlotCount = RegNumCount + XMM_REGCOUNT;
 private:
     void        InsertOpHelperSpillsAndRestores(const OpHelperBlock& opHelperBlock);
+
+    class GeneratorBailIn {
+
+        // We need to rely on 2 registers `rax` and `rcx` to generate the bail-in code.
+        // At this point, since `rax` already has the address of the generator's interpreter frame,
+        // we can easily get the symbols' values through something like: mov dst [rax + appropriate offset]
+        //
+        // There are 4 types of symbols that we have to deal with:
+        //  - symbols that are currently on the stack at this point. We need 2 instructions:
+        //     - Load the value to `rcx`: mov rcx [rax + offset]
+        //     - Finally load the value to its stack slot: mov [rbp + stack offset] rcx
+        //  - symbol that is in rax
+        //  - symbol that is in rcx
+        //  - symbols that are in the other registers. We only need 1 instruction:
+        //     - mov reg [rax + offset]
+        //
+        // Since restoring symbols on the stack might mess up values that will be in rax/rcx,
+        // and we want to maintain the invariant that rax has to hold the value of the interpreter
+        // frame, we need to restore the symbols in the following order:
+        //  - symbols in stack
+        //  - symbols in registers
+        //  - symbol in rax
+        //
+        // The following 3 instructions indicate the insertion points for the above cases:
+        struct BailInInsertionPoint
+        {
+            IR::Instr* raxRestoreInstr = nullptr;
+            IR::Instr* instrInsertStackSym = nullptr;
+            IR::Instr* instrInsertRegSym = nullptr;
+        };
+
+        // There are symbols that we don't need to restore such as constant values,
+        // ScriptFunction's Environment (through LdEnv) and the address pointing to for-in enumerator
+        // on the interpreter frame because their values are already loaded before (or as part) of the
+        // generator resume jump table. In such cases, they could already be in either rax or rcx.
+        // So we would need to save their values (and restore afterwards) before generating the bail-in code.
+        struct SaveInitializedRegister
+        {
+            bool rax = false;
+            bool rcx = false;
+        };
+
+        Func* const func;
+        LinearScanMD* const linearScanMD;
+        const JITTimeFunctionBody* const jitFnBody;
+        BVSparse<JitArenaAllocator> initializedRegs;
+        IR::RegOpnd* const raxRegOpnd;
+        IR::RegOpnd* const rcxRegOpnd;
+
+        bool NeedsReloadingValueWhenBailIn(StackSym* sym) const;
+        uint32 GetOffsetFromInterpreterStackFrame(Js::RegSlot regSlot) const;
+        IR::SymOpnd* CreateGeneratorObjectOpnd() const;
+
+        void InsertSaveAndRestore(IR::Instr* start, IR::Instr* end, IR::RegOpnd* reg);
+
+        void InsertRestoreRegSymbol(
+            StackSym* stackSym,
+            IR::Opnd* srcOpnd,
+            BailInInsertionPoint& insertionPoint
+        );
+
+        void InsertRestoreStackSymbol(
+            StackSym* stackSym,
+            IR::Opnd* srcOpnd,
+            BailInInsertionPoint& insertionPoint
+        );
+
+        void InsertRestoreSymbols(
+            BVSparse<JitArenaAllocator>* symbols,
+            BailInInsertionPoint& insertionPoint,
+            SaveInitializedRegister& saveInitializedReg
+        );
+
+    public:
+        GeneratorBailIn(Func* func, LinearScanMD* linearScanMD);
+        IR::Instr* GenerateBailIn(IR::Instr* resumeLabelInstr, BailOutInfo* bailOutInfo);
+    };
+
+    GeneratorBailIn bailIn;
 };

+ 3 - 3
lib/Backend/amd64/LowererMDArch.cpp

@@ -269,7 +269,7 @@ LowererMDArch::LoadHeapArgsCached(IR::Instr *instrArgs)
             this->LoadHelperArgument(instrArgs, srcOpnd);
 
             // Save the newly-created args object to its dedicated stack slot.
-            IR::Opnd *opnd = this->lowererMD->CreateStackArgumentsSlotOpnd();
+            IR::Opnd *opnd = LowererMD::CreateStackArgumentsSlotOpnd(func);
             instr = IR::Instr::New(Js::OpCode::MOV, opnd, instrArgs->GetDst(), func);
             instrArgs->InsertAfter(instr);
         }
@@ -394,7 +394,7 @@ LowererMDArch::LoadHeapArguments(IR::Instr *instrArgs)
             this->LoadHelperArgument(instrArgs, srcOpnd);
 
             // Save the newly-created args object to its dedicated stack slot.
-            IR::Opnd *opnd = this->lowererMD->CreateStackArgumentsSlotOpnd();
+            IR::Opnd *opnd = LowererMD::CreateStackArgumentsSlotOpnd(func);
             instr = IR::Instr::New(Js::OpCode::MOV, opnd, instrArgs->GetDst(), func);
             instrArgs->InsertAfter(instr);
         }
@@ -1745,7 +1745,7 @@ LowererMDArch::LowerEntryInstr(IR::EntryInstr * entryInstr)
         movRax0 = IR::Instr::New(Js::OpCode::XOR, raxOpnd, raxOpnd, raxOpnd, this->m_func);
         secondInstr->m_prev->InsertAfter(movRax0);
 
-        IR::Opnd *opnd = this->lowererMD->CreateStackArgumentsSlotOpnd();
+        IR::Opnd *opnd = LowererMD::CreateStackArgumentsSlotOpnd(this->m_func);
         IR::Instr *movNullInstr = IR::Instr::New(Js::OpCode::MOV, opnd, raxOpnd->UseWithNewType(TyMachReg, this->m_func), this->m_func);
         secondInstr->m_prev->InsertAfter(movNullInstr);
     }

+ 3 - 3
lib/Backend/arm/LowerMD.cpp

@@ -4634,11 +4634,11 @@ LowererMD::GenerateStFldFromLocalInlineCache(
 }
 
 IR::Opnd *
-LowererMD::CreateStackArgumentsSlotOpnd()
+LowererMD::CreateStackArgumentsSlotOpnd(Func *func)
 {
     // Save the newly-created args object to its dedicated stack slot.
-    IR::IndirOpnd *indirOpnd = IR::IndirOpnd::New(IR::RegOpnd::New(nullptr, FRAME_REG , TyMachReg, m_func),
-            -MachArgsSlotOffset, TyMachPtr, m_func);
+    IR::IndirOpnd *indirOpnd = IR::IndirOpnd::New(IR::RegOpnd::New(nullptr, FRAME_REG , TyMachReg, func),
+            -MachArgsSlotOffset, TyMachPtr, func);
 
     return indirOpnd;
 }

+ 1 - 1
lib/Backend/arm/LowerMD.h

@@ -119,7 +119,6 @@ public:
             void            GenerateFastBrS(IR::BranchInstr *brInstr);
             void            GenerateFastInlineBuiltInCall(IR::Instr* instr, IR::JnHelperMethod helperMethod);
             void            HelperCallForAsmMathBuiltin(IR::Instr* instr, IR::JnHelperMethod helperMethodFloat, IR::JnHelperMethod helperMethodDouble) { Assert(UNREACHED); } // only for asm.js
-            IR::Opnd *      CreateStackArgumentsSlotOpnd();
             void            GenerateSmIntTest(IR::Opnd *opndSrc, IR::Instr *insertInstr, IR::LabelInstr *labelHelper, IR::Instr **instrFirst = nullptr, bool fContinueLabel = false);
             IR::RegOpnd *   LoadNonnegativeIndex(IR::RegOpnd *indexOpnd, const bool skipNegativeCheck, IR::LabelInstr *const notTaggedIntLabel, IR::LabelInstr *const negativeLabel, IR::Instr *const insertBeforeInstr);
             IR::RegOpnd *   GenerateUntagVar(IR::RegOpnd * opnd, IR::LabelInstr * labelFail, IR::Instr * insertBeforeInstr, bool generateTagCheck = true);
@@ -131,6 +130,7 @@ public:
             bool            TryGenerateFastMulAdd(IR::Instr * instrAdd, IR::Instr ** pInstrPrev);
             void            GenerateFloatTest(IR::RegOpnd * opndSrc, IR::Instr * insertInstr, IR::LabelInstr* labelHelper, const bool checkForNullInLoopBody = false);
 
+     static IR::Opnd *      CreateStackArgumentsSlotOpnd(Func *func);
      static void            EmitInt4Instr(IR::Instr *instr);
             void            EmitLoadVar(IR::Instr *instr, bool isFromUint32 = false, bool isHelper = false);
             bool            EmitLoadInt32(IR::Instr *instr, bool conversionFromObjectAllowed, bool bailOutOnHelper = false, IR::LabelInstr * labelBailOut = nullptr);

+ 5 - 5
lib/Backend/arm64/LowerMD.cpp

@@ -1863,7 +1863,7 @@ LowererMD::LoadHeapArguments(IR::Instr * instrArgs)
             this->LoadHelperArgument(instrArgs, srcOpnd);
 
             // Save the newly-created args object to its dedicated stack slot.
-            Lowerer::InsertMove(CreateStackArgumentsSlotOpnd(), instrArgs->GetDst(), instrArgs->m_next);
+            Lowerer::InsertMove(LowererMD::CreateStackArgumentsSlotOpnd(func), instrArgs->GetDst(), instrArgs->m_next);
         }
         this->ChangeToHelperCall(instrArgs, IR::HelperOp_LoadHeapArguments);
     }
@@ -1964,7 +1964,7 @@ LowererMD::LoadHeapArgsCached(IR::Instr * instrArgs)
 
 
             // Save the newly-created args object to its dedicated stack slot.
-            Lowerer::InsertMove(CreateStackArgumentsSlotOpnd(), instrArgs->GetDst(), instrArgs->m_next);
+            Lowerer::InsertMove(LowererMD::CreateStackArgumentsSlotOpnd(func), instrArgs->GetDst(), instrArgs->m_next);
 
         }
 
@@ -4335,11 +4335,11 @@ LowererMD::GenerateStFldFromLocalInlineCache(
 }
 
 IR::Opnd *
-LowererMD::CreateStackArgumentsSlotOpnd()
+LowererMD::CreateStackArgumentsSlotOpnd(Func *func)
 {
     // Save the newly-created args object to its dedicated stack slot.
-    IR::IndirOpnd *indirOpnd = IR::IndirOpnd::New(IR::RegOpnd::New(nullptr, FRAME_REG , TyMachReg, m_func),
-            -MachArgsSlotOffset, TyMachPtr, m_func);
+    IR::IndirOpnd *indirOpnd = IR::IndirOpnd::New(IR::RegOpnd::New(nullptr, FRAME_REG , TyMachReg, func),
+            -MachArgsSlotOffset, TyMachPtr, func);
 
     return indirOpnd;
 }

+ 1 - 1
lib/Backend/arm64/LowerMD.h

@@ -119,7 +119,6 @@ public:
             void            GenerateFastBrS(IR::BranchInstr *brInstr);
             void            GenerateFastInlineBuiltInCall(IR::Instr* instr, IR::JnHelperMethod helperMethod);
             void            HelperCallForAsmMathBuiltin(IR::Instr* instr, IR::JnHelperMethod helperMethodFloat, IR::JnHelperMethod helperMethodDouble) { Assert(UNREACHED); } // only for asm.js
-            IR::Opnd *      CreateStackArgumentsSlotOpnd();
             void            GenerateSmIntTest(IR::Opnd *opndSrc, IR::Instr *insertInstr, IR::LabelInstr *labelHelper, IR::Instr **instrFirst = nullptr, bool fContinueLabel = false);
             IR::RegOpnd *   LoadNonnegativeIndex(IR::RegOpnd *indexOpnd, const bool skipNegativeCheck, IR::LabelInstr *const notTaggedIntLabel, IR::LabelInstr *const negativeLabel, IR::Instr *const insertBeforeInstr);
             IR::RegOpnd *   GenerateUntagVar(IR::RegOpnd * opnd, IR::LabelInstr * labelFail, IR::Instr * insertBeforeInstr, bool generateTagCheck = true);
@@ -132,6 +131,7 @@ public:
             void            GenerateFloatTest(IR::RegOpnd * opndSrc, IR::Instr * insertInstr, IR::LabelInstr* labelHelper, const bool checkForNullInLoopBody = false);
             IR::RegOpnd*    CheckFloatAndUntag(IR::RegOpnd * opndSrc, IR::Instr * insertInstr, IR::LabelInstr* labelHelper);
 
+     static IR::Opnd *      CreateStackArgumentsSlotOpnd(Func* func);
      static void            EmitInt4Instr(IR::Instr *instr);
             void            EmitLoadVar(IR::Instr *instr, bool isFromUint32 = false, bool isHelper = false);
             bool            EmitLoadInt32(IR::Instr *instr, bool conversionFromObjectAllowed, bool bailOutOnHelper = false, IR::LabelInstr * labelBailOut = nullptr);

+ 2 - 2
lib/Backend/i386/LowererMDArch.cpp

@@ -347,7 +347,7 @@ LowererMDArch::LoadHeapArguments(IR::Instr *instrArgs)
 
 
             // Save the newly-created args object to its dedicated stack slot.
-            IR::Opnd *opnd = this->lowererMD->CreateStackArgumentsSlotOpnd();
+            IR::Opnd *opnd = LowererMD::CreateStackArgumentsSlotOpnd(func);
             instr = IR::Instr::New(Js::OpCode::MOV, opnd, instrArgs->GetDst(), func);
             instrArgs->InsertAfter(instr);
         }
@@ -460,7 +460,7 @@ LowererMDArch::LoadHeapArgsCached(IR::Instr *instrArgs)
             this->LoadHelperArgument(instrArgs, srcOpnd);
 
             // Save the newly-created args object to its dedicated stack slot.
-            IR::Opnd *opnd = this->lowererMD->CreateStackArgumentsSlotOpnd();
+            IR::Opnd *opnd = LowererMD::CreateStackArgumentsSlotOpnd(func);
             instr = IR::Instr::New(Js::OpCode::MOV, opnd, instrArgs->GetDst(), func);
             instrArgs->InsertAfter(instr);
         }

+ 6 - 0
lib/Common/BackendApi.h

@@ -265,8 +265,14 @@ enum VTableValue {
     VtableJavascriptNativeIntArray,
     VtableJavascriptRegExp,
     VtableScriptFunction,
+
+    // Generator/Async functions
     VtableJavascriptGeneratorFunction,
+    VtableVirtualJavascriptGeneratorFunctionWithHomeObj,
+    VtableVirtualJavascriptGeneratorFunctionWithComputedName,
+    VtableVirtualJavascriptGeneratorFunctionWithHomeObjAndComputedName,
     VtableJavascriptAsyncFunction,
+
     VtableStackScriptFunction,
     VtableScriptFunctionWithInlineCacheAndHomeObj,
     VtableScriptFunctionWithInlineCacheHomeObjAndComputedName,

+ 2 - 1
lib/JITIDL/JITTypes.h

@@ -78,7 +78,7 @@ typedef unsigned char boolean;
 #define __JITTypes_h__
 
 // TODO: OOP JIT, how do we make this better?
-const int VTABLE_COUNT = 51;
+const int VTABLE_COUNT = 54;
 const int EQUIVALENT_TYPE_CACHE_SIZE = 8;
 
 typedef IDL_DEF([context_handle]) void * PTHREADCONTEXT_HANDLE;
@@ -582,6 +582,7 @@ typedef struct FunctionBodyDataIDL
     unsigned int localFrameDisplayReg;
     unsigned int paramClosureReg;
     unsigned int localClosureReg;
+    unsigned int yieldReg;
     unsigned int envReg;
     unsigned int firstTmpReg;
     unsigned int firstInnerScopeReg;

+ 6 - 0
lib/Runtime/Base/FunctionBody.cpp

@@ -333,6 +333,12 @@ namespace Js
         return FALSE;
     }
 
+    bool
+    FunctionBody::IsGeneratorAndJitIsDisabled() const
+    {
+        return this->IsCoroutine() && !(CONFIG_ISENABLED(Js::JitES6GeneratorsFlag) && !this->GetHasTry() && !this->IsInDebugMode());
+    }
+
     ScriptContext* EntryPointInfo::GetScriptContext()
     {
         Assert(!IsCleanedUp());

+ 1 - 4
lib/Runtime/Base/FunctionBody.h

@@ -3408,10 +3408,7 @@ namespace Js
             return IsJitLoopBodyPhaseForced() && !this->GetHasTry();
         }
 
-        bool IsGeneratorAndJitIsDisabled()
-        {
-            return this->IsCoroutine() && !(CONFIG_ISENABLED(Js::JitES6GeneratorsFlag) && !this->GetHasTry());
-        }
+        bool IsGeneratorAndJitIsDisabled() const;
 
         FunctionBodyFlags * GetAddressOfFlags() { return &this->flags; }
         Js::RegSlot GetRestParamRegSlot();

+ 7 - 4
lib/Runtime/ByteCode/ByteCodeEmitter.cpp

@@ -8223,8 +8223,10 @@ void EmitNew(ParseNode* pnode, ByteCodeGenerator* byteCodeGenerator, FuncInfo* f
 
             Js::AuxArray<uint32> *spreadIndices = nullptr;
 
+            // Emit argouts at end for generators so that we don't need to restore them when bailing in
+            bool emitArgOutsAtEnd = pnode->AsParseNodeCall()->hasDestructuring || (funcInfo->byteCodeFunction->IsCoroutine() && pnode->AsParseNodeCall()->pnodeArgs != nullptr);
             actualArgCount = EmitArgList(pnode->AsParseNodeCall()->pnodeArgs, Js::Constants::NoRegister, Js::Constants::NoRegister,
-                false, true, byteCodeGenerator, funcInfo, callSiteId, argCount, pnode->AsParseNodeCall()->hasDestructuring, emitProfiledArgouts, pnode->AsParseNodeCall()->spreadArgCount, &spreadIndices);
+                false, true, byteCodeGenerator, funcInfo, callSiteId, argCount, emitArgOutsAtEnd, emitProfiledArgouts, pnode->AsParseNodeCall()->spreadArgCount, &spreadIndices);
 
             funcInfo->ReleaseLoc(pnode->AsParseNodeCall()->pnodeTarget);
 
@@ -8361,8 +8363,11 @@ void EmitCall(
 
     // Only emit profiled argouts if we're going to allocate callSiteInfo (on the DynamicProfileInfo) for this call.
     bool emitProfiledArgouts = callSiteId != byteCodeGenerator->GetCurrentCallSiteId();
+
+    // Emit argouts at end for generators so that we don't need to restore them when bailing in
+    bool emitArgOutsAtEnd = pnodeCall->hasDestructuring || (funcInfo->byteCodeFunction->IsCoroutine() && pnodeCall->pnodeArgs != nullptr);
     Js::AuxArray<uint32> *spreadIndices;
-    EmitArgList(pnodeArgs, thisLocation, newTargetLocation, fIsEval, fEvaluateComponents, byteCodeGenerator, funcInfo, callSiteId, (Js::ArgSlot)argCount, pnodeCall->hasDestructuring, emitProfiledArgouts, spreadArgCount, &spreadIndices);
+    EmitArgList(pnodeArgs, thisLocation, newTargetLocation, fIsEval, fEvaluateComponents, byteCodeGenerator, funcInfo, callSiteId, (Js::ArgSlot)argCount, emitArgOutsAtEnd, emitProfiledArgouts, spreadArgCount, &spreadIndices);
 
     if (!fEvaluateComponents)
     {
@@ -10284,8 +10289,6 @@ void EmitYield(Js::RegSlot inputLocation, Js::RegSlot resultLocation, ByteCodeGe
 
 void EmitYieldStar(ParseNodeUni* yieldStarNode, ByteCodeGenerator* byteCodeGenerator, FuncInfo* funcInfo)
 {
-    funcInfo->AcquireLoc(yieldStarNode);
-
     Js::ByteCodeLabel loopEntrance = byteCodeGenerator->Writer()->DefineLabel();
     Js::ByteCodeLabel continuePastLoop = byteCodeGenerator->Writer()->DefineLabel();
 

+ 2 - 0
lib/Runtime/ByteCode/ByteCodeGenerator.cpp

@@ -5177,6 +5177,8 @@ void AssignRegisters(ParseNode *pnode, ByteCodeGenerator *byteCodeGenerator)
         CheckMaybeEscapedUse(pnode->AsParseNodeUni()->pnode1, byteCodeGenerator);
         break;
     case knopYieldStar:
+        // Reserve a local for our YieldStar loop so that the backend doesn't complain
+        pnode->location = byteCodeGenerator->NextVarRegister();
         byteCodeGenerator->AssignNullConstRegister();
         byteCodeGenerator->AssignUndefinedConstRegister();
         CheckMaybeEscapedUse(pnode->AsParseNodeUni()->pnode1, byteCodeGenerator);

+ 11 - 3
lib/Runtime/ByteCode/OpCodes.h

@@ -233,9 +233,9 @@ MACRO_EXTEND_WMS(       Await,              Reg2,           OpSideEffect)
 MACRO_EXTEND_WMS(       AsyncYield,         Reg2,           OpSideEffect)                                       // Yield from async generator function
 MACRO_EXTEND_WMS(       AsyncYieldStar,     Reg2,           OpSideEffect)                                       // Yield* from async generator function
 MACRO_WMS(              Yield,              Reg2,           OpSideEffect|OpUseAllFields)                        // Yield from generator function
-MACRO_WMS(              ResumeYield,        Reg2,           OpSideEffect)
-MACRO_WMS(              ResumeYieldStar,    Reg3,           OpSideEffect)
 MACRO_EXTEND_WMS(       AsyncYieldIsReturn, Reg2,           OpSideEffect)                                       // Check for .return() during async yield*
+MACRO_WMS(              ResumeYield,        Reg2,           OpSideEffect|OpHasImplicitCall)
+MACRO_WMS(              ResumeYieldStar,    Reg3,           OpSideEffect|OpHasImplicitCall)
 
 // Unary operations
 MACRO_WMS(              Incr_A,             Reg2,           OpTempNumberProducing|OpOpndHasImplicitCall|OpDoNotTransfer|OpTempNumberSources|OpTempObjectSources|OpCanCSE|OpPostOpDbgBailOut|OpProducesNumber)     // Increment
@@ -823,7 +823,6 @@ MACRO_EXTEND_WMS(       ImportCall,         Reg2,           OpSideEffect|OpHasIm
 MACRO_BACKEND_ONLY(     BrFncCachedScopeEq, Reg2,           None)
 MACRO_BACKEND_ONLY(     BrFncCachedScopeNeq,Reg2,           None)
 
-MACRO_BACKEND_ONLY(     GeneratorResumeJumpTable, Reg1,     OpSideEffect)
 MACRO_BACKEND_ONLY(     RestoreOutParam,    Empty,          None)
 
 MACRO_BACKEND_ONLY(     SlotArrayCheck,     Empty,          OpCanCSE)
@@ -850,6 +849,15 @@ MACRO_EXTEND_WMS(Incr_Num_A, Reg2, OpTempNumberProducing | OpOpndHasImplicitCall
 MACRO_EXTEND_WMS(Decr_Num_A, Reg2, OpTempNumberProducing | OpOpndHasImplicitCall | OpDoNotTransfer | OpTempNumberSources | OpTempObjectSources | OpCanCSE | OpPostOpDbgBailOut | OpProducesNumber)     // Increment Numeric
 MACRO_BACKEND_ONLY(LazyBailOutThunkLabel, Empty, None)
 
+// Jitting Generator
+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(GeneratorBailInLabel,                    Empty,  None)
+MACRO_BACKEND_ONLY(GeneratorResumeYieldLabel,               Empty,  None)
+MACRO_BACKEND_ONLY(GeneratorEpilogueFrameNullOut,           Empty,  None)
+MACRO_BACKEND_ONLY(GeneratorEpilogueNoFrameNullOut,         Empty,  None)
+
 // All SIMD ops are backend only for non-asmjs.
 #define MACRO_SIMD(opcode, asmjsLayout, opCodeAttrAsmJs, OpCodeAttr, ...) MACRO_BACKEND_ONLY(opcode, Empty, OpCodeAttr)
 #define MACRO_SIMD_WMS(opcode, asmjsLayout, opCodeAttrAsmJs, OpCodeAttr, ...) MACRO_BACKEND_ONLY(opcode, Empty, OpCodeAttr)

+ 2 - 1
lib/Runtime/Debug/DiagHelperMethodWrapper.cpp

@@ -212,7 +212,8 @@ namespace Js
         Assert(exceptionObject);
 
         // Note: there also could be plain OutOfMemoryException and StackOverflowException, no special handling for these.
-        if (!exceptionObject->IsDebuggerSkip() ||
+        if (exceptionObject->IsGeneratorReturnException() ||
+            !exceptionObject->IsDebuggerSkip() ||
             exceptionObject == scriptContext->GetThreadContext()->GetPendingOOMErrorObject() ||
             exceptionObject == scriptContext->GetThreadContext()->GetPendingSOErrorObject())
         {

+ 23 - 8
lib/Runtime/Language/InterpreterStackFrame.cpp

@@ -1021,14 +1021,28 @@ namespace Js
     }
 
     const int k_stackFrameVarCount = (sizeof(InterpreterStackFrame) + sizeof(Var) - 1) / sizeof(Var);
-    InterpreterStackFrame::Setup::Setup(Js::ScriptFunction * function, Js::Arguments& args, bool bailedOut, bool inlinee)
-        : function(function), inParams(args.Values), inSlotsCount(args.Info.Count), executeFunction(function->GetFunctionBody()), callFlags(args.Info.Flags), bailedOutOfInlinee(inlinee), bailedOut(bailedOut)
+    InterpreterStackFrame::Setup::Setup(Js::ScriptFunction * function, Js::Arguments& args, bool bailedOut, bool inlinee, bool isGeneratorFrame)
+        : function(function),
+          inParams(args.Values),
+          inSlotsCount(args.Info.Count),
+          executeFunction(function->GetFunctionBody()),
+          callFlags(args.Info.Flags),
+          bailedOutOfInlinee(inlinee),
+          bailedOut(bailedOut),
+          isGeneratorFrame(isGeneratorFrame)
     {
         SetupInternal();
     }
 
     InterpreterStackFrame::Setup::Setup(Js::ScriptFunction * function, Var * inParams, int inSlotsCount)
-        : function(function), inParams(inParams), inSlotsCount(inSlotsCount), executeFunction(function->GetFunctionBody()), callFlags(CallFlags_None), bailedOutOfInlinee(false), bailedOut(false)
+        : function(function),
+          inParams(inParams),
+          inSlotsCount(inSlotsCount),
+          executeFunction(function->GetFunctionBody()),
+          callFlags(CallFlags_None),
+          bailedOutOfInlinee(false),
+          bailedOut(false),
+          isGeneratorFrame(false)
     {
         SetupInternal();
     }
@@ -1059,8 +1073,9 @@ namespace Js
             extraVarCount += (sizeof(ImplicitCallFlags) * this->executeFunction->GetLoopCount() + sizeof(Var) - 1) / sizeof(Var);
         }
 #endif
-        // If we bailed out, we will use the JIT frame's for..in enumerators
-        uint forInVarCount = bailedOut ? 0 : (this->executeFunction->GetForInLoopDepth() * (sizeof(Js::ForInObjectEnumerator) / sizeof(Var)));
+        // If we bailed out, we will use the JIT frame's for..in enumerators.
+        // But for generators, we will allocate space for them instead.
+        uint forInVarCount = (bailedOut && !isGeneratorFrame) ? 0 : (this->executeFunction->GetForInLoopDepth() * (sizeof(Js::ForInObjectEnumerator) / sizeof(Var)));
         this->varAllocCount = k_stackFrameVarCount + localCount + this->executeFunction->GetOutParamMaxDepth() + forInVarCount +
             extraVarCount + this->executeFunction->GetInnerScopeCount();
         this->stackVarAllocCount = 0;
@@ -1208,7 +1223,7 @@ namespace Js
         char * nextAllocBytes = (char *)(outparamsEnd);
 
         // If we bailed out, we will use the JIT frame's for..in enumerators
-        if (bailedOut || this->executeFunction->GetForInLoopDepth() == 0)
+        if (this->executeFunction->GetForInLoopDepth() == 0 || (!isGeneratorFrame && bailedOut))
         {
             newInstance->forInObjectEnumerators = nullptr;
         }
@@ -1803,7 +1818,7 @@ skipThunk:
         //
         ScriptContext* functionScriptContext = function->GetScriptContext();
         Arguments generatorArgs = generator->GetArguments();
-        InterpreterStackFrame::Setup setup(function, generatorArgs);
+        InterpreterStackFrame::Setup setup(function, generatorArgs, false /* bailedOut */, false /* inlinee */, true /* isGeneratorFrame */);
         Assert(setup.GetStackAllocationVarCount() == 0);
         size_t varAllocCount = setup.GetAllocationVarCount();
         size_t varSizeInBytes = varAllocCount * sizeof(Var);
@@ -1942,7 +1957,7 @@ skipThunk:
             // generator resuming methods: next(), throw(), or return().  They all pass the generator
             // object as the first of two arguments.  The real user arguments are obtained from the
             // generator object.  The second argument is the ResumeYieldData which is only needed
-            // when resuming a generator and so it only used here if a frame already exists on the
+            // when resuming a generator and so it is only used here if a frame already exists on the
             // generator object.
             AssertOrFailFastMsg(args.Info.Count == 2 && ((args.Info.Flags & CallFlags_ExtraArg) == CallFlags_None), "Generator ScriptFunctions should only be invoked by generator APIs with the pair of arguments they pass in -- the generator object and a ResumeYieldData pointer");
 

+ 12 - 2
lib/Runtime/Language/InterpreterStackFrame.h

@@ -47,7 +47,7 @@ namespace Js
         class Setup
         {
         public:
-            Setup(ScriptFunction * function, Arguments& args, bool bailout = false, bool inlinee = false);
+            Setup(ScriptFunction * function, Arguments& args, bool bailout = false, bool inlinee = false, bool isGeneratorFrame = false);
             Setup(ScriptFunction * function, Var * inParams, int inSlotsCount);
             size_t GetAllocationVarCount() const { return varAllocCount; }
             size_t GetStackAllocationVarCount() const { return stackVarAllocCount; }
@@ -83,6 +83,11 @@ namespace Js
             Js::CallFlags callFlags;
             bool bailedOut;
             bool bailedOutOfInlinee;
+
+            // Indicate whether this InterpreterStackFrame belongs to a generator function
+            // We use this flag to determine whether we need to allocate more space for
+            // objects such as for-in enumerators in a generator.
+            bool isGeneratorFrame;
         };
 
         struct AsmJsReturnStruct
@@ -315,12 +320,17 @@ namespace Js
         void * GetReturnAddress() { return returnAddress; }
 
         static uint32 GetOffsetOfLocals() { return offsetof(InterpreterStackFrame, m_localSlots); }
-        static uint32 GetOffsetOfArguments() { return offsetof(InterpreterStackFrame, m_arguments); }
+
         static uint32 GetOffsetOfInParams() { return offsetof(InterpreterStackFrame, m_inParams); }
         static uint32 GetOffsetOfInSlotsCount() { return offsetof(InterpreterStackFrame, m_inSlotsCount); }
         static uint32 GetOffsetOfStackNestedFunctions() { return offsetof(InterpreterStackFrame, stackNestedFunctions); }
         static uint32 GetOffsetOfForInEnumerators() { return offsetof(InterpreterStackFrame, forInObjectEnumerators); }
 
+        static uint32 GetOffsetOfArguments() { return offsetof(InterpreterStackFrame, m_arguments); }
+        static uint32 GetOffsetOfLocalFrameDisplay() { return offsetof(InterpreterStackFrame, localFrameDisplay); }
+        static uint32 GetOffsetOfLocalClosure() { return offsetof(InterpreterStackFrame, localClosure); }
+        static uint32 GetOffsetOfParamClosure() { return offsetof(InterpreterStackFrame, paramClosure); }
+
         static uint32 GetStartLocationOffset() { return offsetof(InterpreterStackFrame, m_reader) + ByteCodeReader::GetStartLocationOffset(); }
         static uint32 GetCurrentLocationOffset() { return offsetof(InterpreterStackFrame, m_reader) + ByteCodeReader::GetCurrentLocationOffset(); }
 

+ 2 - 1
lib/Runtime/Library/JavascriptGenerator.h

@@ -8,6 +8,7 @@ namespace Js
 {
     // Helper struct used to communicate to a yield point whether it was resumed via next(), return(), or throw()
     // and provide the data necessary for the corresponding action taken (see OP_ResumeYield)
+    // `data` stores the value that was passed in as parameter to .next()
     struct ResumeYieldData
     {
         Var data;
@@ -48,7 +49,7 @@ namespace Js
 
         void SetState(GeneratorState state) {
             this->state = state;
-            if(state == GeneratorState::Completed)
+            if (state == GeneratorState::Completed)
             {
                 frame = nullptr;
                 args.Values = nullptr;

+ 5 - 0
lib/Runtime/Library/JavascriptGeneratorFunction.h

@@ -200,6 +200,11 @@ namespace Js
         virtual TTD::NSSnapObjects::SnapObjectType GetSnapTag_TTD() const override;
         virtual void ExtractSnapObjectDataInto(TTD::NSSnapObjects::SnapObject* objData, TTD::SlabAllocator& alloc) override;
 #endif
+
+        virtual VTableValue DummyVirtualFunctionToHinderLinkerICF()
+        {
+            return VTableValue::VtableJavascriptGeneratorFunction;
+        }
     };
 
     typedef FunctionWithComputedName<GeneratorVirtualScriptFunction> GeneratorVirtualScriptFunctionWithComputedName;

+ 7 - 0
lib/Runtime/Library/JavascriptLibrary.cpp

@@ -3497,8 +3497,15 @@ namespace Js
         VirtualTableRecorder<Js::ScriptFunction>::RecordVirtualTableAddress(vtableAddresses, VTableValue::VtableScriptFunction);
         VirtualTableRecorder<Js::JavascriptGeneratorFunction>::RecordVirtualTableAddress(vtableAddresses, VTableValue::VtableJavascriptGeneratorFunction);
         VirtualTableRecorder<Js::JavascriptAsyncFunction>::RecordVirtualTableAddress(vtableAddresses, VTableValue::VtableJavascriptAsyncFunction);
+
+        // Generators
+        vtableAddresses[VTableValue::VtableVirtualJavascriptGeneratorFunctionWithHomeObj] = VirtualTableInfo<Js::FunctionWithHomeObj<Js::GeneratorVirtualScriptFunction>>::Address;
+        vtableAddresses[VTableValue::VtableVirtualJavascriptGeneratorFunctionWithComputedName] = VirtualTableInfo<Js::FunctionWithComputedName<Js::GeneratorVirtualScriptFunction>>::Address;
+        vtableAddresses[VTableValue::VtableVirtualJavascriptGeneratorFunctionWithHomeObjAndComputedName] = VirtualTableInfo<Js::FunctionWithComputedName<Js::FunctionWithHomeObj<Js::GeneratorVirtualScriptFunction>>>::Address;
+
         vtableAddresses[VTableValue::VtableScriptFunctionWithInlineCacheAndHomeObj] = VirtualTableInfo<Js::FunctionWithHomeObj<Js::ScriptFunctionWithInlineCache>>::Address;
         vtableAddresses[VTableValue::VtableScriptFunctionWithInlineCacheHomeObjAndComputedName] = VirtualTableInfo<Js::FunctionWithComputedName<Js::FunctionWithHomeObj<Js::ScriptFunctionWithInlineCache>>>::Address;
+
         VirtualTableRecorder<Js::ConcatStringMulti>::RecordVirtualTableAddress(vtableAddresses, VTableValue::VtableConcatStringMulti);
         VirtualTableRecorder<Js::CompoundString>::RecordVirtualTableAddress(vtableAddresses, VTableValue::VtableCompoundString);