Преглед изворни кода

Enabling Stack Args when there is presence of nested functions.

Premise:
The initial implementation of Stack Args was disabled when there is any
nested function.
We were missing a bunch of opportunities in the real world web pages - where nested function is a common thing.
This PR enables this optimization for nested function.

Summary:
Stack Args will work partially when nested functions are present. We will
attempt to remove only Heap Arguments Object creation instr and retain
scope object creation instr (whenever required).

Details:
ByteCodeGeneration:
  - We track if there are any params being closure captured by any nested functions
  - We give up the optimization if this is case.
  - We track if there are any non-local references inside the nested
    function.
  - We do Stack Args partially here - i.e. we attempt to remove Heap Arguments Object creation but still create scope object - as scope
      object is required for FrameDisplay.
IRBuilder:
  - Scope object tracking has been moved from Forward pass (Globopt) to this phase.
  - This enables us to mark the scope obj sym to be live during the
	  first backward pass.
Backward pass:
  - Scope Object Sym is kept alive in all code paths.
  - This is to facilitate Bailout to record the live Scope object Sym, whenever required.
  - Reason for doing is this because - Scope object has to be implicitly live whenever Heap Arguments object is live.
  - When we restore HeapArguments object in the bail out path, it expects the scope object also to be restored - if one was created.
  - We do not know detailed information about Heap arguments obj syms(aliasing etc.) until we complete Forward Pass.
  - And we want to avoid dead sym clean up (in this case, scope object though not explicitly live, it is live implicitly) during Block merging in the forward pass.
Globopt pass:
  - Extra tracking for instrs having arguments object is done.
Dead Store pass:
    - Scope obj creation instruction is not dead stored - It will be a MOV NULL instr in the Lowerer.
    - All instrs related cached scope obj is deadstored. Reason is to dead
      store the cached scope object creation instruction itself.
    - InitCachedFuncs is deadstored.
    - GetCachedFunc is replaced with NewScFunc instr.
Lowerer pass:
    - If Stack Args optimization is still turned on and the parser has hinted that Scope obj creation is not required, then scope object creation instruction will be turned to a MOV NULL.
    - We retain the instruction and not remove it because we want the value of the scope object to be restored
    - That's when we can always rely on the bail out path to restore some
      valid value (NULL or the scope object itself).
BailOut path:
    - We use the restored scope object value (we track this by using a flag on the bailoutInfo) and copy the param values to it.

Added UT and some more traces
Fixing a bug with the Type of Scope object. Type wasn't being transitioned
correctly in cases where we remove LdHeapArguments opcode.
Took care of review comments.

Review Comments
Satheesh Ravindranath пре 9 година
родитељ
комит
d52a8021c3

+ 105 - 31
lib/Backend/BackwardPass.cpp

@@ -295,6 +295,23 @@ BackwardPass::InsertArgInsForFormals()
     }
 }
 
+void
+BackwardPass::MarkScopeObjSymUseForStackArgOpt()
+{
+    IR::Instr * instr = this->currentInstr;
+    if (tag == Js::DeadStorePhase)
+    {
+        if (instr->DoStackArgsOpt(this->func) && instr->m_func->GetScopeObjSym() != nullptr)
+        {
+            if (this->currentBlock->byteCodeUpwardExposedUsed == nullptr)
+            {
+                this->currentBlock->byteCodeUpwardExposedUsed = JitAnew(this->tempAlloc, BVSparse<JitArenaAllocator>, this->tempAlloc);
+            }
+            this->currentBlock->byteCodeUpwardExposedUsed->Set(instr->m_func->GetScopeObjSym()->m_id);
+        }
+    }
+}
+
 void
 BackwardPass::ProcessBailOnStackArgsOutOfActualsRange()
 {
@@ -1912,17 +1929,6 @@ BackwardPass::ProcessBailOutInfo(IR::Instr * instr)
             BVSparse<JitArenaAllocator> * byteCodeUpwardExposedUsed = byteCodeUsesInstr->byteCodeUpwardExposedUsed;
             if (byteCodeUpwardExposedUsed != nullptr)
             {
-                //Stack Args for Formals optimization.
-                //We will restore the scope object in the bail out path, when we restore Heap arguments object.
-                //So we don't to mark the sym for scope object separately for restoration.
-                //When StackArgs for formal opt is ON , Scope object sym is used by formals access only, which will be replaced by ArgIns and Ld_A 
-                //So it is ok to clear the bit here.
-                //Clearing the bit in byteCodeUpwardExposedUsed here.
-                if (instr->m_func->IsStackArgsEnabled() && instr->m_func->GetScopeObjSym())
-                {
-                    byteCodeUpwardExposedUsed->Clear(instr->m_func->GetScopeObjSym()->m_id);
-                }
-
                 this->currentBlock->byteCodeUpwardExposedUsed->Or(byteCodeUpwardExposedUsed);
 #if DBG
                 FOREACH_BITSET_IN_SPARSEBV(symId, byteCodeUpwardExposedUsed)
@@ -2540,6 +2546,7 @@ BackwardPass::ProcessBlock(BasicBlock * block)
             continue;
         }
 
+        MarkScopeObjSymUseForStackArgOpt();
         ProcessBailOnStackArgsOutOfActualsRange();
         
         if (ProcessNoImplicitCallUses(instr) || this->ProcessBailOutInfo(instr))
@@ -2554,7 +2561,7 @@ BackwardPass::ProcessBlock(BasicBlock * block)
             continue;
         }
 
-        if (CanDeadStoreInstrForScopeObjRemoval() && DeadStoreOrChangeInstrForScopeObjRemoval())
+        if (CanDeadStoreInstrForScopeObjRemoval() && DeadStoreOrChangeInstrForScopeObjRemoval(&instrPrev))
         {
             continue;
         }
@@ -2636,7 +2643,7 @@ BackwardPass::ProcessBlock(BasicBlock * block)
             {
                 case Js::OpCode::LdSlot:
                 {
-                    DeadStoreOrChangeInstrForScopeObjRemoval();
+                    DeadStoreOrChangeInstrForScopeObjRemoval(&instrPrev);
                     break;
                 }
                 case Js::OpCode::InlineArrayPush:
@@ -2889,19 +2896,17 @@ BackwardPass::CanDeadStoreInstrForScopeObjRemoval(Sym *sym) const
     if (tag == Js::DeadStorePhase && this->currentInstr->m_func->IsStackArgsEnabled())
     {
         Func * currFunc = this->currentInstr->m_func;
+        bool doScopeObjCreation = currFunc->GetJITFunctionBody()->GetDoScopeObjectCreation();
         switch (this->currentInstr->m_opcode)
         {
-            case Js::OpCode::LdHeapArguments:
-            case Js::OpCode::LdHeapArgsCached:
-            case Js::OpCode::LdLetHeapArguments:
-            case Js::OpCode::LdLetHeapArgsCached:
+            case Js::OpCode::InitCachedScope:
             {
-                if (this->currentInstr->GetSrc1()->IsScopeObjOpnd(currFunc))
+                if(!doScopeObjCreation && this->currentInstr->GetDst()->IsScopeObjOpnd(currFunc))
                 {
                     /*
-                    *   We don't really dead store these instructions. We just want the source sym of these instructions (which is the scope object)
+                    *   We don't really dead store this instruction. We just want the source sym of this instruction
                     *   to NOT be tracked as USED by this instruction.
-                    *   In case of LdXXHeapArgsXXX opcodes, they will effectively be lowered to dest = MOV NULL, in the lowerer phase.
+                    *   This instr will effectively be lowered to dest = MOV NULLObject, in the lowerer phase.
                     */
                     return true;
                 }
@@ -2916,13 +2921,23 @@ BackwardPass::CanDeadStoreInstrForScopeObjRemoval(Sym *sym) const
                 break;
             }
             case Js::OpCode::CommitScope:
+            case Js::OpCode::GetCachedFunc:
             {
-                return this->currentInstr->GetSrc1()->IsScopeObjOpnd(currFunc);
+                return !doScopeObjCreation && this->currentInstr->GetSrc1()->IsScopeObjOpnd(currFunc);
             }
             case Js::OpCode::BrFncCachedScopeEq:
             case Js::OpCode::BrFncCachedScopeNeq:
             {
-                return this->currentInstr->GetSrc2()->IsScopeObjOpnd(currFunc);
+                return !doScopeObjCreation && this->currentInstr->GetSrc2()->IsScopeObjOpnd(currFunc);
+            }
+            case Js::OpCode::CallHelper:
+            {
+                if (!doScopeObjCreation && this->currentInstr->GetSrc1()->AsHelperCallOpnd()->m_fnHelper == IR::JnHelperMethod::HelperOP_InitCachedFuncs)
+                {
+                    IR::RegOpnd * scopeObjOpnd = this->currentInstr->GetSrc2()->GetStackSym()->GetInstrDef()->GetSrc1()->AsRegOpnd();
+                    return scopeObjOpnd->IsScopeObjOpnd(currFunc);
+                }
+                break;
             }
         }
     }
@@ -2933,7 +2948,7 @@ BackwardPass::CanDeadStoreInstrForScopeObjRemoval(Sym *sym) const
 * This is for Eliminating Scope Object Creation during Heap arguments optimization.
 */
 bool
-BackwardPass::DeadStoreOrChangeInstrForScopeObjRemoval()
+BackwardPass::DeadStoreOrChangeInstrForScopeObjRemoval(IR::Instr ** pInstrPrev)
 {
     IR::Instr * instr = this->currentInstr;
     Func * currFunc = instr->m_func;
@@ -2996,6 +3011,54 @@ BackwardPass::DeadStoreOrChangeInstrForScopeObjRemoval()
                 }
                 break;
             }
+            case Js::OpCode::CallHelper:
+            {
+                //Remove the CALL and all its Argout instrs.
+                if (instr->GetSrc1()->AsHelperCallOpnd()->m_fnHelper == IR::JnHelperMethod::HelperOP_InitCachedFuncs)
+                {
+                    IR::RegOpnd * scopeObjOpnd = instr->GetSrc2()->GetStackSym()->GetInstrDef()->GetSrc1()->AsRegOpnd();
+                    if (scopeObjOpnd->IsScopeObjOpnd(currFunc))
+                    {
+                        IR::Instr * instrDef = instr;
+                        IR::Instr * nextInstr = instr->m_next;
+
+                        while (instrDef != nullptr)
+                        {
+                            IR::Instr * instrToDelete = instrDef;
+                            if (instrDef->GetSrc2() != nullptr)
+                            {
+                                instrDef = instrDef->GetSrc2()->GetStackSym()->GetInstrDef();
+                                Assert(instrDef->m_opcode == Js::OpCode::ArgOut_A);
+                            }
+                            else
+                            {
+                                instrDef = nullptr;
+                            }
+                            instrToDelete->Remove();
+                        }
+                        Assert(nextInstr != nullptr);
+                        *pInstrPrev = nextInstr->m_prev;
+                        return true;
+                    }
+                }
+                break;
+            }
+            case Js::OpCode::GetCachedFunc:
+            {
+                // <dst> = GetCachedFunc <scopeObject>, <functionNum>
+                // is converted to 
+                // <dst> = NewScFunc <functionNum>, <env: FrameDisplay>
+
+                if (instr->GetSrc1()->IsScopeObjOpnd(currFunc))
+                {
+                    instr->m_opcode = Js::OpCode::NewScFunc;
+                    IR::Opnd * intConstOpnd = instr->UnlinkSrc2();
+
+                    instr->ReplaceSrc1(intConstOpnd);
+                    instr->SetSrc2(IR::RegOpnd::New(currFunc->GetLocalFrameDisplaySym(), IRType::TyVar, currFunc));
+                }
+                break;
+            }
         }
     }
     return false;
@@ -3005,7 +3068,7 @@ IR::Instr *
 BackwardPass::TryChangeInstrForStackArgOpt()
 {
     IR::Instr * instr = this->currentInstr;
-    if (instr->DoStackArgsOpt(this->func))
+    if (tag == Js::DeadStorePhase && instr->DoStackArgsOpt(this->func))
     {
         switch (instr->m_opcode)
         {
@@ -3035,6 +3098,22 @@ BackwardPass::TryChangeInstrForStackArgOpt()
             }
         }
     }
+
+    /*
+    *   Scope Object Sym is kept alive in all code paths.
+    *   -This is to facilitate Bailout to record the live Scope object Sym, whenever required.
+    *   -Reason for doing is this because - Scope object has to be implicitly live whenever Heap Arguments object is live.
+    *   -When we restore HeapArguments object in the bail out path, it expects the scope object also to be restored - if one was created.
+    *   -We do not know detailed information about Heap arguments obj syms(aliasing etc.) until we complete Forward Pass. 
+    *   -And we want to avoid dead sym clean up (in this case, scope object though not explicitly live, it is live implicitly) during Block merging in the forward pass. 
+    *   -Hence this is the optimal spot to do this.
+    */
+
+    if (tag == Js::BackwardPhase && instr->m_func->GetScopeObjSym() != nullptr)
+    {
+        this->currentBlock->upwardExposedUses->Set(instr->m_func->GetScopeObjSym()->m_id);
+    }
+
     return nullptr;
 }
 
@@ -6610,13 +6689,8 @@ BackwardPass::DeadStoreInstr(IR::Instr *instr)
 #endif
         PropertySym *unusedPropertySym = nullptr;
         
-        // Do not track the Scope Obj - we will be restoring it in the bailout path while restoring Heap arguments object.
-        // See InterpreterStackFrame::TrySetFrameObjectInHeapArgObj
-        if (!(instr->m_func->IsStackArgsEnabled() && instr->m_opcode == Js::OpCode::LdSlotArr &&
-            instr->GetSrc1() && instr->GetSrc1()->IsScopeObjOpnd(instr->m_func)))
-        {
-            GlobOpt::TrackByteCodeSymUsed(instr, this->currentBlock->byteCodeUpwardExposedUsed, &unusedPropertySym);
-        }
+        GlobOpt::TrackByteCodeSymUsed(instr, this->currentBlock->byteCodeUpwardExposedUsed, &unusedPropertySym);
+        
 #if DBG
         BVSparse<JitArenaAllocator> tempBv2(this->tempAlloc);
         tempBv2.Copy(this->currentBlock->byteCodeUpwardExposedUsed);

+ 2 - 1
lib/Backend/BackwardPass.h

@@ -30,7 +30,8 @@ private:
     IR::Instr * TryChangeInstrForStackArgOpt();
     void InsertArgInsForFormals();
     void ProcessBailOnStackArgsOutOfActualsRange();
-    bool DeadStoreOrChangeInstrForScopeObjRemoval();
+    void MarkScopeObjSymUseForStackArgOpt();
+    bool DeadStoreOrChangeInstrForScopeObjRemoval(IR::Instr ** pInstrPrev);
     void ProcessUse(IR::Opnd * opnd);
     bool ProcessDef(IR::Opnd * opnd);
     void ProcessTransfers(IR::Instr * instr);

+ 12 - 2
lib/Backend/BailOut.cpp

@@ -997,6 +997,12 @@ BailOutRecord::RestoreValue(IR::BailOutKind bailOutKind, Js::JavascriptCallStack
         }
     }
 
+    Js::RegSlot localClosureReg = newInstance->function->GetFunctionBody()->GetLocalClosureRegister();
+    if (regSlot == localClosureReg)
+    {
+        this->globalBailOutRecordTable->isScopeObjRestored = true;
+    }
+
     values[regSlot] = value;
 
     BAILOUT_VERBOSE_TRACE(newInstance->function->GetFunctionBody(), bailOutKind, _u("\n"));
@@ -1651,9 +1657,13 @@ BailOutRecord::BailOutHelper(Js::JavascriptCallStackLayout * layout, Js::ScriptF
     
     if (bailOutRecord->globalBailOutRecordTable->hasStackArgOpt)
     {
-        newInstance->TrySetFrameObjectInHeapArgObj(functionScriptContext, bailOutRecord->globalBailOutRecordTable->hasNonSimpleParams);
+        newInstance->TrySetFrameObjectInHeapArgObj(functionScriptContext, bailOutRecord->globalBailOutRecordTable->hasNonSimpleParams, 
+            bailOutRecord->globalBailOutRecordTable->isScopeObjRestored);
     }
-    
+
+    //Reset the value for tracking the restoration during next bail out.
+    bailOutRecord->globalBailOutRecordTable->isScopeObjRestored = false;
+
     uint32 innerScopeCount = executeFunction->GetInnerScopeCount();
     for (uint32 i = 0; i < innerScopeCount; i++)
     {

+ 1 - 0
lib/Backend/BailOut.h

@@ -471,6 +471,7 @@ struct GlobalBailOutRecordDataTable
     bool isLoopBody;
     bool hasNonSimpleParams;
     bool hasStackArgOpt;
+    bool isScopeObjRestored;
     void Finalize(NativeCodeData::Allocator *allocator, JitArenaAllocator *tempAlloc);
     void AddOrUpdateRow(JitArenaAllocator *allocator, uint32 bailOutRecordId, uint32 regSlot, bool isFloat, bool isInt,
         bool isSimd128F4, bool isSimd128I4, bool isSimd128I8, bool isSimd128I16, bool isSimd128U4, bool isSimd128U8, bool isSimd128U16, bool isSimd128B4, bool isSimd128B8, bool isSimd128B16,

+ 11 - 0
lib/Backend/FlowGraph.cpp

@@ -2590,7 +2590,18 @@ FlowGraph::RemoveInstr(IR::Instr *instr, GlobOpt * globOpt)
             return instr;
         }
 
+        /*
+        *   Scope object has to be implicitly live whenever Heap Arguments object is live.
+        *       - When we restore HeapArguments object in the bail out path, it expects the scope object also to be restored - if one was created.
+        */
         Js::OpCode opcode = instr->m_opcode;
+        if (opcode == Js::OpCode::LdElemI_A && instr->DoStackArgsOpt(this->func) &&
+            globOpt->IsArgumentsOpnd(instr->GetSrc1()) && instr->m_func->GetScopeObjSym())
+        {
+            IR::Instr * byteCodeUsesInstr = IR::ByteCodeUsesInstr::New(instr, instr->m_func->GetScopeObjSym()->m_id);
+            instr->InsertAfter(byteCodeUsesInstr);
+        }
+
         IR::ByteCodeUsesInstr * newByteCodeUseInstr = globOpt->ConvertToByteCodeUses(instr);
         if (newByteCodeUseInstr != nullptr)
         {

+ 15 - 2
lib/Backend/GlobOpt.cpp

@@ -3944,7 +3944,7 @@ GlobOpt::TrackInstrsForScopeObjectRemoval(IR::Instr * instr)
 
     if (instr->m_opcode == Js::OpCode::Ld_A && src1->IsRegOpnd())
     {
-        AssertMsg(!src1->IsScopeObjOpnd(instr->m_func), "There can be no aliasing for scope object.");
+        AssertMsg(!instr->m_func->IsStackArgsEnabled() || !src1->IsScopeObjOpnd(instr->m_func), "There can be no aliasing for scope object.");
     }
 
     // The following is to track formals array for Stack Arguments optimization with Formals
@@ -4063,7 +4063,7 @@ GlobOpt::OptArguments(IR::Instr *instr)
                 StackSym * scopeObjSym = instr->GetSrc1()->GetStackSym();
                 Assert(scopeObjSym);
                 Assert(scopeObjSym->GetInstrDef()->m_opcode == Js::OpCode::InitCachedScope || scopeObjSym->GetInstrDef()->m_opcode == Js::OpCode::NewScopeObject);
-                instr->m_func->SetScopeObjSym(scopeObjSym);
+                Assert(instr->m_func->GetScopeObjSym() == scopeObjSym);
 
                 if (PHASE_VERBOSE_TRACE1(Js::StackArgFormalsOptPhase))
                 {
@@ -4134,6 +4134,7 @@ GlobOpt::OptArguments(IR::Instr *instr)
         {
             instr->usesStackArgumentsObject = true;
         }
+
         break;
     }
     case Js::OpCode::LdLen_A:
@@ -4147,6 +4148,11 @@ GlobOpt::OptArguments(IR::Instr *instr)
     }
     case Js::OpCode::ArgOut_A_InlineBuiltIn:
     {
+        if (IsArgumentsOpnd(src1))
+        {
+            instr->usesStackArgumentsObject = true;
+        }
+
         if (IsArgumentsOpnd(src1) &&
             src1->AsRegOpnd()->m_sym->GetInstrDef()->m_opcode == Js::OpCode::BytecodeArgOutCapture)
         {
@@ -4180,7 +4186,14 @@ GlobOpt::OptArguments(IR::Instr *instr)
     case Js::OpCode::BailOnNotStackArgs:
     case Js::OpCode::ArgOut_A_FromStackArgs:
     case Js::OpCode::BytecodeArgOutUse:
+    {
+        if (src1 && IsArgumentsOpnd(src1))
+        {
+            instr->usesStackArgumentsObject = true;
+        }
+        
         break;
+    }
 
     default:
         {

+ 1 - 1
lib/Backend/GlobOpt.h

@@ -1292,6 +1292,7 @@ public:
 
     IR::ByteCodeUsesInstr * ConvertToByteCodeUses(IR::Instr * isntr);
     bool GetIsAsmJSFunc()const{ return isAsmJSFunc; };
+    BOOLEAN                 IsArgumentsOpnd(IR::Opnd* opnd);
 private:
     bool                    IsLoopPrePass() const { return this->prePassLoop != nullptr; }
     void                    OptBlock(BasicBlock *block);
@@ -1335,7 +1336,6 @@ private:
     IR::Instr *             SetTypeCheckBailOut(IR::Opnd *opnd, IR::Instr *instr, BailOutInfo *bailOutInfo);
     void                    OptArguments(IR::Instr *Instr);
     void                    TrackInstrsForScopeObjectRemoval(IR::Instr * instr);
-    BOOLEAN                 IsArgumentsOpnd(IR::Opnd* opnd);
     bool                    AreFromSameBytecodeFunc(IR::RegOpnd* src1, IR::RegOpnd* dst);
     void                    TrackArgumentsSym(IR::RegOpnd* opnd);
     void                    ClearArgumentsSym(IR::RegOpnd* opnd);

+ 8 - 0
lib/Backend/IRBuilder.cpp

@@ -1589,6 +1589,10 @@ IRBuilder::BuildReg1(Js::OpCode newOpcode, uint32 offset, Js::RegSlot R0)
             Js::RegSlot regFrameObj = m_func->GetJITFunctionBody()->GetLocalClosureReg();
             Assert(regFrameObj != Js::Constants::NoRegister);
             srcOpnd = BuildSrcOpnd(regFrameObj);
+            if (m_func->GetJITFunctionBody()->GetInParamsCount() > 1)
+            {
+                m_func->SetScopeObjSym(srcOpnd->GetStackSym());
+            }
         }
         else
         {
@@ -1617,6 +1621,10 @@ IRBuilder::BuildReg1(Js::OpCode newOpcode, uint32 offset, Js::RegSlot R0)
             Js::Throw::FatalInternalError();
         }
         srcOpnd = BuildSrcOpnd(m_func->GetJITFunctionBody()->GetLocalClosureReg());
+        if (m_func->GetJITFunctionBody()->GetInParamsCount() > 1)
+        {
+            m_func->SetScopeObjSym(srcOpnd->GetStackSym());
+        }
         isNotInt = true;
         break;
 

+ 7 - 0
lib/Backend/JITTimeFunctionBody.cpp

@@ -205,6 +205,7 @@ JITTimeFunctionBody::InitializeJITFunctionData(
     jitBody->isParamAndBodyScopeMerged = functionBody->IsParamAndBodyScopeMerged();
     jitBody->paramClosureReg = functionBody->GetParamClosureRegister();
     jitBody->usesArgumentsObject = functionBody->GetUsesArgumentsObject();
+    jitBody->doScopeObjectCreation = functionBody->GetDoScopeObjectCreation();
     
     //CompileAssert(sizeof(PropertyIdArrayIDL) == sizeof(Js::PropertyIdArray));
     jitBody->formalsPropIdArray = (PropertyIdArrayIDL*)functionBody->GetFormalsPropIdArray(false);
@@ -764,6 +765,12 @@ JITTimeFunctionBody::NeedScopeObjectForArguments(bool hasNonSimpleParams) const
         && !dontNeedScopeObject;
 }
 
+bool
+JITTimeFunctionBody::GetDoScopeObjectCreation() const
+{
+    return !!m_bodyData.doScopeObjectCreation;
+}
+
 const byte *
 JITTimeFunctionBody::GetByteCodeBuffer() const
 {

+ 1 - 0
lib/Backend/JITTimeFunctionBody.h

@@ -97,6 +97,7 @@ public:
     bool IsParamAndBodyScopeMerged() const;
     bool CanInlineRecursively(uint depth, bool tryAggressive = true) const;
     bool NeedScopeObjectForArguments(bool hasNonSimpleParams) const;
+    bool GetDoScopeObjectCreation() const;
 
     const byte * GetByteCodeBuffer() const;
     StatementMapIDL * GetFullStatementMap() const;

+ 1 - 0
lib/Backend/JnHelperMethodList.h

@@ -50,6 +50,7 @@ HELPERCALL(OP_InitCachedScope, Js::JavascriptOperators::OP_InitCachedScope, 0)
 HELPERCALL(OP_InitCachedFuncs, Js::JavascriptOperators::OP_InitCachedFuncs, 0)
 HELPERCALL(OP_InvalidateCachedScope, Js::JavascriptOperators::OP_InvalidateCachedScope, 0)
 HELPERCALL(OP_NewScopeObject, Js::JavascriptOperators::OP_NewScopeObject, 0)
+HELPERCALL(OP_NewScopeObjectWithFormals, Js::JavascriptOperators::OP_NewScopeObjectWithFormals, 0)
 HELPERCALL(OP_NewScopeSlots, Js::JavascriptOperators::OP_NewScopeSlots, 0)
 HELPERCALL(OP_NewScopeSlotsWithoutPropIds, Js::JavascriptOperators::OP_NewScopeSlotsWithoutPropIds, 0)
 HELPERCALL(OP_NewBlockScope, Js::JavascriptOperators::OP_NewBlockScope, 0)

+ 1 - 0
lib/Backend/LinearScan.cpp

@@ -1315,6 +1315,7 @@ LinearScan::EnsureGlobalBailOutRecordTable(Func *func)
         globalBailOutRecordDataTable->isInlinedConstructor = func->IsInlinedConstructor();
         globalBailOutRecordDataTable->isLoopBody = topFunc->IsLoopBody();
         globalBailOutRecordDataTable->returnValueRegSlot = func->returnValueRegSlot;
+        globalBailOutRecordDataTable->isScopeObjRestored = false;
         globalBailOutRecordDataTable->firstActualStackOffset = -1;
         globalBailOutRecordDataTable->registerSaveSpace = (Js::Var*)func->GetThreadContextInfo()->GetBailOutRegisterSaveSpaceAddr();
         globalBailOutRecordDataTable->globalBailOutRecordDataRows = nullptr;

+ 72 - 5
lib/Backend/Lower.cpp

@@ -249,12 +249,79 @@ Lowerer::LowerRange(IR::Instr *instrStart, IR::Instr *instrEnd, bool defaultDoFa
             break;
 
         case Js::OpCode::InitCachedScope:
-            instrPrev = this->LowerInitCachedScope(instr);
+            if (instr->m_func->GetJITFunctionBody()->GetDoScopeObjectCreation() || !instr->m_func->IsStackArgsEnabled())
+            {
+                instrPrev = this->LowerInitCachedScope(instr);
+            }
+            else
+            {
+                instr->ReplaceSrc1(this->LoadLibraryValueOpnd(instr, LibraryValue::ValueNull));
+                instr->m_opcode = Js::OpCode::Ld_A;
+                instrPrev = instr;
+
+                if (PHASE_TRACE1(Js::StackArgFormalsOptPhase))
+                {
+                    Output::Print(_u("StackArgFormals : %s (%d) :Removing Scope object creation in Lowerer and replacing it with MOV NULL. \n"), instr->m_func->GetJITFunctionBody()->GetDisplayName(), instr->m_func->GetFunctionNumber());
+                    Output::Flush();
+                }
+            }
             break;
         case Js::OpCode::NewScopeObject:
-            m_lowererMD.ChangeToHelperCallMem(instr, IR::HelperOP_NewScopeObject);
-            break;
+        {
+            Func * currFunc = instr->m_func;
+            if (currFunc->GetJITFunctionBody()->GetDoScopeObjectCreation() || !currFunc->IsStackArgsEnabled())
+            {
+                //Call Helper that creates scope object and does type transition for the formals
+                if (currFunc->IsStackArgsEnabled() && currFunc->GetJITFunctionBody()->GetInParamsCount() != 1)
+                {
+                    // s3 = formals are let decls
+                    this->m_lowererMD.LoadHelperArgument(instr, IR::IntConstOpnd::New(currFunc->GetHasNonSimpleParams() ? TRUE : FALSE, TyUint8, currFunc));
 
+                    if (currFunc->IsInlinee())
+                    {
+                        // s2 = current function.
+                        this->m_lowererMD.LoadHelperArgument(instr, currFunc->GetInlineeFunctionObjectSlotOpnd());
+                    }
+                    else
+                    {
+                        // s2 = current function
+                        StackSym * paramSym = StackSym::New(TyMachReg, currFunc);
+                        currFunc->SetArgOffset(paramSym, 2 * MachPtr);
+                        IR::Opnd * srcOpnd = IR::SymOpnd::New(paramSym, TyMachReg, currFunc);
+
+                        if (currFunc->GetJITFunctionBody()->IsCoroutine())
+                        {
+                            // the function object for generator calls is a GeneratorVirtualScriptFunction object
+                            // and we need to pass the real JavascriptGeneratorFunction object so grab it instead
+                            IR::RegOpnd *tmpOpnd = IR::RegOpnd::New(TyMachReg, currFunc);
+                            LowererMD::CreateAssign(tmpOpnd, srcOpnd, instr);
+
+                            srcOpnd = IR::IndirOpnd::New(tmpOpnd, Js::GeneratorVirtualScriptFunction::GetRealFunctionOffset(), TyMachPtr, currFunc);
+                        }
+
+                        this->m_lowererMD.LoadHelperArgument(instr, srcOpnd);
+                    }
+                    m_lowererMD.ChangeToHelperCallMem(instr, IR::HelperOP_NewScopeObjectWithFormals);
+                }
+                else
+                {
+                    m_lowererMD.ChangeToHelperCallMem(instr, IR::HelperOP_NewScopeObject);
+                }
+            }
+            else
+            {
+                instr->SetSrc1(this->LoadLibraryValueOpnd(instr, LibraryValue::ValueNull));
+                instr->m_opcode = Js::OpCode::Ld_A;
+                instrPrev = instr;
+
+                if (PHASE_TRACE1(Js::StackArgFormalsOptPhase))
+                {
+                    Output::Print(_u("StackArgFormals : %s (%d) :Removing Scope object creation in Lowerer and replacing it with MOV NULL. \n"), currFunc->GetJITFunctionBody()->GetDisplayName(), currFunc->GetFunctionNumber());
+                    Output::Flush();
+                }
+            }
+            break;
+        }
         case Js::OpCode::NewStackScopeSlots:
             this->LowerNewScopeSlots(instr, m_func->DoStackScopeSlots());
             break;
@@ -3177,7 +3244,7 @@ Lowerer::LoadScriptContextValueOpnd(IR::Instr * instr, ScriptContextValue valueT
 }
 
 IR::Opnd *
-Lowerer::LoadLibraryValueOpnd(IR::Instr * instr, LibraryValue valueType, RegNum regNum)
+Lowerer::LoadLibraryValueOpnd(IR::Instr * instr, LibraryValue valueType)
 {
     ScriptContextInfo *scriptContextInfo = instr->m_func->GetScriptContextInfo();
     switch (valueType)
@@ -19067,7 +19134,7 @@ Lowerer::GenerateFastRealStackArgumentsLdLen(IR::Instr *ldLen)
     else
     {
         IR::Instr *loadInputParamCountInstr = this->m_lowererMD.LoadInputParamCount(ldLen, -1);
-        IR::RegOpnd *actualCountOpnd          = loadInputParamCountInstr->GetDst()->AsRegOpnd();
+        IR::RegOpnd *actualCountOpnd = loadInputParamCountInstr->GetDst()->AsRegOpnd();
         LowererMD::CreateAssign(ldLen->GetDst(), actualCountOpnd, ldLen);
     }
     ldLen->Remove();

+ 1 - 1
lib/Backend/Lower.h

@@ -541,7 +541,7 @@ private:
     IR::Opnd *          LoadFunctionBodyOpnd(IR::Instr *instr);
     IR::Opnd *          LoadScriptContextOpnd(IR::Instr *instr);
     IR::Opnd *          LoadScriptContextValueOpnd(IR::Instr * instr, ScriptContextValue valueType);
-    IR::Opnd *          LoadLibraryValueOpnd(IR::Instr * instr, LibraryValue valueType, RegNum regNum = RegNOREG);
+    IR::Opnd *          LoadLibraryValueOpnd(IR::Instr * instr, LibraryValue valueType);
     IR::Opnd *          LoadVTableValueOpnd(IR::Instr * instr, VTableValue vtableType);
     IR::Opnd *          LoadOptimizationOverridesValueOpnd(IR::Instr *instr, OptimizationOverridesValue valueType);
     IR::Opnd *          LoadNumberAllocatorValueOpnd(IR::Instr *instr, NumberAllocatorValue valueType);

+ 2 - 2
lib/Backend/LowerMDShared.cpp

@@ -641,9 +641,9 @@ LowererMD::LoadArgumentCount(IR::Instr * instr)
 }
 
 IR::Instr *
-LowererMD::LoadHeapArguments(IR::Instr * instrArgs, bool force, IR::Opnd* opndInputParamCount)
+LowererMD::LoadHeapArguments(IR::Instr * instrArgs)
 {
-    return this->lowererMDArch.LoadHeapArguments(instrArgs, force, opndInputParamCount);
+    return this->lowererMDArch.LoadHeapArguments(instrArgs);
 }
 
 IR::Instr *

+ 1 - 1
lib/Backend/LowerMDShared.h

@@ -94,7 +94,7 @@ public:
             IR::Opnd *      CreateStackArgumentsSlotOpnd();
             IR::Instr *     LoadArgumentsFromFrame(IR::Instr * instr);
             IR::Instr *     LoadArgumentCount(IR::Instr * instr);
-            IR::Instr *     LoadHeapArguments(IR::Instr * instr, bool force = false, IR::Opnd* opndInputParamCount = nullptr);
+            IR::Instr *     LoadHeapArguments(IR::Instr * instr);
             IR::Instr *     LoadHeapArgsCached(IR::Instr * instr);
             IR::Instr *     LoadFuncExpression(IR::Instr * instr);
             IR::Instr *     LowerRet(IR::Instr * instr);

+ 5 - 7
lib/Backend/amd64/LowererMDArch.cpp

@@ -291,13 +291,13 @@ LowererMDArch::LoadHeapArgsCached(IR::Instr *instrArgs)
 ///----------------------------------------------------------------------------
 
 IR::Instr *
-LowererMDArch::LoadHeapArguments(IR::Instr *instrArgs, bool force /* = false */, IR::Opnd *opndInputParamCount /* = nullptr */)
+LowererMDArch::LoadHeapArguments(IR::Instr *instrArgs)
 {
     ASSERT_INLINEE_FUNC(instrArgs);
     Func *func = instrArgs->m_func;
 
     IR::Instr *instrPrev = instrArgs->m_prev;
-    if (!force && func->IsStackArgsEnabled())
+    if (func->IsStackArgsEnabled())
     {
         instrArgs->m_opcode = Js::OpCode::MOV;
         instrArgs->ReplaceSrc1(IR::AddrOpnd::NewNull(func));
@@ -374,11 +374,9 @@ LowererMDArch::LoadHeapArguments(IR::Instr *instrArgs, bool force /* = false */,
             this->LoadHelperArgument(instrArgs, instr->GetDst());
 
             // s2 = actual argument count (without counting "this")
-            if (opndInputParamCount == nullptr)
-            {
-                instr = this->lowererMD->LoadInputParamCount(instrArgs, -1);
-                opndInputParamCount = instr->GetDst();
-            }
+            instr = this->lowererMD->LoadInputParamCount(instrArgs, -1);
+            IR::Opnd * opndInputParamCount = instr->GetDst();
+            
             this->LoadHelperArgument(instrArgs, opndInputParamCount);
 
             // s1 = current function

+ 1 - 1
lib/Backend/amd64/LowererMDArch.h

@@ -80,7 +80,7 @@ public:
     IR::Instr *         LoadDoubleHelperArgument(IR::Instr * instr, IR::Opnd * opndArg);
     IR::Instr *         LoadFloatHelperArgument(IR::Instr * instr, IR::Opnd * opndArg);
     IR::Instr *         LoadStackArgPtr(IR::Instr * instr);
-    IR::Instr *         LoadHeapArguments(IR::Instr * instr, bool force = false, IR::Opnd* opndInputParamCount = nullptr);
+    IR::Instr *         LoadHeapArguments(IR::Instr * instr);
     IR::Instr *         LoadHeapArgsCached(IR::Instr * instr);
     IR::Instr *         LoadFuncExpression(IR::Instr * instr);
     IR::Instr *         LowerEntryInstr(IR::EntryInstr * entryInstr);

+ 5 - 7
lib/Backend/arm/LowerMD.cpp

@@ -2170,13 +2170,13 @@ LowererMD::LoadArgumentCount(IR::Instr * instr)
 ///----------------------------------------------------------------------------
 
 IR::Instr *
-LowererMD::LoadHeapArguments(IR::Instr * instrArgs, bool force /* = false */, IR::Opnd *opndInputParamCount /* = nullptr */)
+LowererMD::LoadHeapArguments(IR::Instr * instrArgs)
 {
     ASSERT_INLINEE_FUNC(instrArgs);
     Func *func = instrArgs->m_func;
     IR::Instr * instrPrev = instrArgs->m_prev;
 
-    if (!force && func->IsStackArgsEnabled())
+    if (func->IsStackArgsEnabled())
     {
         // The initial args slot value is zero.
         instrArgs->m_opcode = Js::OpCode::MOV;
@@ -2246,11 +2246,9 @@ LowererMD::LoadHeapArguments(IR::Instr * instrArgs, bool force /* = false */, IR
             this->LoadHelperArgument(instrArgs, instr->GetDst());
 
             // s2 = actual argument count (without counting "this")
-            if (opndInputParamCount == nullptr)
-            {
-                instr = this->LoadInputParamCount(instrArgs, -1);
-                opndInputParamCount = instr->GetDst();
-            }
+            instr = this->LoadInputParamCount(instrArgs, -1);
+            IR::Opnd * opndInputParamCount = instr->GetDst();
+            
             this->LoadHelperArgument(instrArgs, opndInputParamCount);
 
             // s1 = current function

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

@@ -77,7 +77,7 @@ public:
 
             IR::Instr *     LoadArgumentCount(IR::Instr * instr);
             IR::Instr *     LoadStackArgPtr(IR::Instr * instr);
-            IR::Instr *     LoadHeapArguments(IR::Instr * instrArgs, bool force = false, IR::Opnd *opndInputParamCount = nullptr);
+            IR::Instr *     LoadHeapArguments(IR::Instr * instrArgs);
             IR::Instr *     LoadHeapArgsCached(IR::Instr * instr);
             IR::Instr *     LoadInputParamPtr(IR::Instr * instrInsert, IR::RegOpnd * optionalDstOpnd = nullptr);
             IR::Instr *     LoadInputParamCount(IR::Instr * instr, int adjust = 0, bool needFlags = false);

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

@@ -72,7 +72,7 @@ public:
 
             IR::Instr *     LoadArgumentCount(IR::Instr * instr) { __debugbreak(); return 0; }
             IR::Instr *     LoadStackArgPtr(IR::Instr * instr) { __debugbreak(); return 0; }
-              IR::Instr *     LoadHeapArguments(IR::Instr * instrArgs, bool force = false, IR::Opnd *opndInputParamCount = nullptr) { __debugbreak(); return 0; }
+              IR::Instr *     LoadHeapArguments(IR::Instr * instrArgs) { __debugbreak(); return 0; }
               IR::Instr *     LoadHeapArgsCached(IR::Instr * instr) { __debugbreak(); return 0; }
               IR::Instr *     LoadInputParamCount(IR::Instr * instr, int adjust = 0, bool needFlags = false) { __debugbreak(); return 0; }
               IR::Instr *     LoadArgumentsFromFrame(IR::Instr * instr) { __debugbreak(); return 0; }

+ 5 - 7
lib/Backend/i386/LowererMDArch.cpp

@@ -234,13 +234,13 @@ LowererMDArch::LoadStackArgPtr(IR::Instr * instrArgPtr)
 ///----------------------------------------------------------------------------
 
 IR::Instr *
-LowererMDArch::LoadHeapArguments(IR::Instr *instrArgs, bool force, IR::Opnd* opndInputParamCount)
+LowererMDArch::LoadHeapArguments(IR::Instr *instrArgs)
 {
     ASSERT_INLINEE_FUNC(instrArgs);
     Func *func = instrArgs->m_func;
 
     IR::Instr * instrPrev = instrArgs->m_prev;
-    if (!force && func->IsStackArgsEnabled()) //both inlinee & inliner has stack args. We don't support other scenarios.
+    if (func->IsStackArgsEnabled()) //both inlinee & inliner has stack args. We don't support other scenarios.
     {
         // The initial args slot value is zero. (TODO: it should be possible to dead-store the LdHeapArgs in this case.)
         instrArgs->m_opcode = Js::OpCode::MOV;
@@ -325,11 +325,9 @@ LowererMDArch::LoadHeapArguments(IR::Instr *instrArgs, bool force, IR::Opnd* opn
             this->LoadHelperArgument(instrArgs, instr->GetDst());
 
             // s2 = actual argument count (without counting "this")
-            if (opndInputParamCount == nullptr)
-            {
-                instr = this->lowererMD->LoadInputParamCount(instrArgs, -1);
-                opndInputParamCount = instr->GetDst();
-            }
+            instr = this->lowererMD->LoadInputParamCount(instrArgs, -1);
+            IR::Opnd* opndInputParamCount = instr->GetDst();
+            
             this->LoadHelperArgument(instrArgs, opndInputParamCount);
 
             // s1 = current function

+ 1 - 1
lib/Backend/i386/LowererMDArch.h

@@ -66,7 +66,7 @@ public:
             IR::Instr *         LoadDoubleHelperArgument(IR::Instr * instr, IR::Opnd * opndArg);
             IR::Instr *         LoadFloatHelperArgument(IR::Instr * instr, IR::Opnd * opndArg);
             IR::Instr *         LoadStackArgPtr(IR::Instr * instr);
-            IR::Instr *         LoadHeapArguments(IR::Instr * instr, bool force = false, IR::Opnd* opndInputParamCount = nullptr);
+            IR::Instr *         LoadHeapArguments(IR::Instr * instr);
             IR::Instr *         LoadHeapArgsCached(IR::Instr * instr);
             IR::Instr *         LoadFuncExpression(IR::Instr * instr);
             IR::Instr *         LowerEntryInstr(IR::EntryInstr * entryInstr);

+ 7 - 0
lib/JITIDL/JITTypes.h

@@ -466,6 +466,13 @@ typedef struct FunctionBodyDataIDL
     boolean isParamAndBodyScopeMerged;
     boolean hasFinally;
     boolean usesArgumentsObject;
+    boolean doScopeObjectCreation;
+#if defined(_M_IX86) || defined(_M_ARM)
+    IDL_PAD1(0)
+#else
+    IDL_PAD1(0)
+    IDL_PAD2(1)
+#endif
 
     unsigned short envDepth;
     unsigned short inParamCount;

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

@@ -1046,6 +1046,7 @@ namespace Js
         CopyDeferParseField(m_isStrictMode);
         CopyDeferParseField(m_isGlobalFunc);
         CopyDeferParseField(m_doBackendArgumentsOptimization);
+        CopyDeferParseField(m_doScopeObjectCreation);
         CopyDeferParseField(m_usesArgumentsObject);
         CopyDeferParseField(m_isEval);
         CopyDeferParseField(m_isDynamicFunction);
@@ -1141,6 +1142,7 @@ namespace Js
       m_isNameIdentifierRef (true),
       m_isStaticNameFunction(false),
       m_doBackendArgumentsOptimization(true),
+      m_doScopeObjectCreation(true),
       m_usesArgumentsObject(false),
       m_isStrictMode(false),
       m_isAsmjsMode(false),

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

@@ -1650,10 +1650,7 @@ namespace Js
 
         void SetDoBackendArgumentsOptimization(bool set)
         {
-            if (m_doBackendArgumentsOptimization)
-            {
-                m_doBackendArgumentsOptimization = set;
-            }
+            m_doBackendArgumentsOptimization = set;
         }
 
         bool GetDoBackendArgumentsOptimization()
@@ -1661,6 +1658,16 @@ namespace Js
             return m_doBackendArgumentsOptimization;
         }
 
+        void SetDoScopeObjectCreation(bool set)
+        {
+            m_doScopeObjectCreation = set;
+        }
+
+        bool GetDoScopeObjectCreation()
+        {
+            return m_doScopeObjectCreation;
+        }
+
         void SetUsesArgumentsObject(bool set)
         {
             if (!m_usesArgumentsObject)
@@ -1749,6 +1756,7 @@ namespace Js
         bool m_isWasmFunction : 1;
         bool m_isGlobalFunc : 1;
         bool m_doBackendArgumentsOptimization : 1;
+        bool m_doScopeObjectCreation : 1;
         bool m_usesArgumentsObject : 1;
         bool m_isEval : 1;              // Source code is in 'eval'
         bool m_isDynamicFunction : 1;   // Source code is in 'Function'

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

@@ -2355,14 +2355,14 @@ FuncInfo* PreVisitFunction(ParseNode* pnode, ByteCodeGenerator* byteCodeGenerato
                 //Go conservative if it has any nested functions, or any non-local references.
                 //With statements - need scope object to be present.
                 //Nested funtions - need scope object to be present - LdEnv/LdFrameDisplay needs it.
-                if ((doStackArgsOpt && pnode->sxFnc.funcInfo->GetParamScope()->Count() > 1) && (pnode->sxFnc.funcInfo->HasDeferredChild() || pnode->sxFnc.nestedCount > 0 ||
+                if ((doStackArgsOpt && pnode->sxFnc.funcInfo->GetParamScope()->Count() > 1) && (pnode->sxFnc.funcInfo->HasDeferredChild() ||
                     pnode->sxFnc.HasWithStmt() || byteCodeGenerator->IsInDebugMode() || PHASE_OFF1(Js::StackArgFormalsOptPhase) || PHASE_OFF1(Js::StackArgOptPhase)))
                 {
                     doStackArgsOpt = false;
 #ifdef PERF_HINT
                     if (PHASE_TRACE1(Js::PerfHintPhase))
                     {
-                        WritePerfHint(PerfHints::HeapArgumentsDueToNonLocalRef, funcInfo->GetParsedFunctionBody(), 0);
+                        WritePerfHint(PerfHints::HasWithBlock, funcInfo->GetParsedFunctionBody(), 0);
                     }
 #endif
                 }
@@ -2918,6 +2918,32 @@ FuncInfo* PostVisitFunction(ParseNode* pnode, ByteCodeGenerator* byteCodeGenerat
 
     AssignFuncSymRegister(pnode, byteCodeGenerator, top);
 
+    if (pnode->sxFnc.pnodeBody && pnode->sxFnc.HasReferenceableBuiltInArguments() && pnode->sxFnc.UsesArguments() &&
+        pnode->sxFnc.HasHeapArguments())
+    {
+        bool doStackArgsOpt = top->byteCodeFunction->GetDoBackendArgumentsOptimization();
+                
+        bool hasAnyParamInClosure = top->GetHasLocalInClosure() && top->GetParamScope()->GetHasOwnLocalInClosure();
+
+        if ((doStackArgsOpt && top->inArgsCount > 1))
+        {
+            if (doStackArgsOpt && hasAnyParamInClosure)
+            {
+                top->SetHasHeapArguments(true, false /*= Optimize arguments in backend*/);
+#ifdef PERF_HINT
+                if (PHASE_TRACE1(Js::PerfHintPhase))
+                {
+                    WritePerfHint(PerfHints::HeapArgumentsDueToNonLocalRef, top->GetParsedFunctionBody(), 0);
+                }
+#endif
+            }
+            else if (!top->GetHasLocalInClosure())
+            {
+                //Scope object creation instr will be a MOV NULL instruction in the Lowerer - if we still decide to do StackArgs after Globopt phase.
+                top->byteCodeFunction->SetDoScopeObjectCreation(false);
+            }
+        }        
+    }
     return top;
 }
 

+ 4 - 1
lib/Runtime/ByteCode/ByteCodeSerializer.cpp

@@ -212,7 +212,8 @@ enum FunctionFlags
     ffIsAsmJsMode                      = 0x40000,
     ffIsAsmJsFunction                  = 0x80000,
     ffIsAnonymous                      = 0x100000,
-    ffUsesArgumentsObject              = 0x200000
+    ffUsesArgumentsObject              = 0x200000,
+    ffDoScopeObjectCreation            = 0x400000
 };
 
 // Kinds of constant
@@ -1991,6 +1992,7 @@ public:
             | (function->m_isFuncRegistered ? ffIsFuncRegistered : 0)
             | (function->m_isStrictMode ? ffIsStrictMode : 0)
             | (function->m_doBackendArgumentsOptimization ? ffDoBackendArgumentsOptimization : 0)
+            | (function->m_doScopeObjectCreation ? ffDoScopeObjectCreation : 0)
             | (function->m_usesArgumentsObject ? ffUsesArgumentsObject : 0)
             | (function->m_isEval ? ffIsEval : 0)
             | (function->m_isDynamicFunction ? ffIsDynamicFunction : 0)
@@ -3589,6 +3591,7 @@ public:
         (*function)->m_dontInline = (bitflags & ffDontInline) ? true : false;
         (*function)->m_isStrictMode = (bitflags & ffIsStrictMode) ? true : false;
         (*function)->m_doBackendArgumentsOptimization = (bitflags & ffDoBackendArgumentsOptimization) ? true : false;
+        (*function)->m_doScopeObjectCreation = (bitflags & ffDoScopeObjectCreation) ? true : false;
         (*function)->m_usesArgumentsObject = (bitflags & ffUsesArgumentsObject) ? true : false;
         (*function)->m_isEval = (bitflags & ffIsEval) ? true : false;
         (*function)->m_isDynamicFunction = (bitflags & ffIsDynamicFunction) ? true : false;

+ 31 - 15
lib/Runtime/Language/InterpreterStackFrame.cpp

@@ -8752,9 +8752,10 @@ const byte * InterpreterStackFrame::OP_ProfiledLoopBodyStart(const byte * ip)
         return args;
     }
 
-    void InterpreterStackFrame::TrySetFrameObjectInHeapArgObj(ScriptContext * scriptContext, bool hasNonSimpleParams)
+    void InterpreterStackFrame::TrySetFrameObjectInHeapArgObj(ScriptContext * scriptContext, bool hasNonSimpleParams, bool isScopeObjRestored)
     {
-        ActivationObject * frameObject = (ActivationObject*)GetLocalClosure();
+        ActivationObject * frameObject = nullptr;
+        
         uint32 formalsCount = this->m_functionBody->GetInParamsCount() - 1;
         Js::PropertyIdArray * propIds = nullptr;
         Js::HeapArgumentsObject* heapArgObj = nullptr;
@@ -8766,30 +8767,45 @@ const byte * InterpreterStackFrame::OP_ProfiledLoopBodyStart(const byte * ip)
         }
 
         bool isCachedScope = false;
-
+        
         //For Non-simple params, we don't have a scope object created.
         if (this->m_functionBody->NeedScopeObjectForArguments(hasNonSimpleParams))
         {
+                frameObject = (ActivationObject*)GetLocalClosure();
+
                 isCachedScope = m_functionBody->HasCachedScopePropIds();
                 propIds = this->m_functionBody->GetFormalsPropIdArray();
 
-                if (isCachedScope)
+                if(isScopeObjRestored && ActivationObject::Is(frameObject))
                 {
-                    Js::DynamicType *literalType = nullptr;
-                    Assert(!propIds->hasNonSimpleParams && !hasNonSimpleParams);
-                    frameObject = (ActivationObject*)JavascriptOperators::OP_InitCachedScope(this->GetJavascriptFunction(), propIds, &literalType, hasNonSimpleParams, scriptContext);
+                    Assert(this->GetFunctionBody()->GetDoScopeObjectCreation());
+                    isCachedScope = true;
+                    if (PHASE_VERBOSE_TRACE1(Js::StackArgFormalsOptPhase) && m_functionBody->GetInParamsCount() > 1)
+                    {
+                        Output::Print(_u("StackArgFormals : %s (%d) :Using the restored scope object in the bail out path. \n"), m_functionBody->GetDisplayName(), m_functionBody->GetFunctionNumber());
+                        Output::Flush();
+                    }
                 }
                 else
                 {
-                    frameObject = (ActivationObject*)JavascriptOperators::OP_NewScopeObject(GetScriptContext());
-                }
-                Assert(propIds != nullptr);
-                SetLocalClosure(frameObject);
+                    if (isCachedScope)
+                    {
+                        Js::DynamicType *literalType = nullptr;
+                        Assert(!propIds->hasNonSimpleParams && !hasNonSimpleParams);
+                        frameObject = (ActivationObject*)JavascriptOperators::OP_InitCachedScope(this->GetJavascriptFunction(), propIds, &literalType, hasNonSimpleParams, scriptContext);
+                    }
+                    else
+                    {
+                        frameObject = (ActivationObject*)JavascriptOperators::OP_NewScopeObject(GetScriptContext());
+                    }
+                    Assert(propIds != nullptr);
+                    SetLocalClosure(frameObject);
 
-                if (PHASE_VERBOSE_TRACE1(Js::StackArgFormalsOptPhase) && m_functionBody->GetInParamsCount() > 1)
-                {
-                    Output::Print(_u("StackArgFormals : %s (%d) :Creating scope object in the bail out path. \n"), m_functionBody->GetDisplayName(), m_functionBody->GetFunctionNumber());
-                    Output::Flush();
+                    if (PHASE_VERBOSE_TRACE1(Js::StackArgFormalsOptPhase) && m_functionBody->GetInParamsCount() > 1)
+                    {
+                        Output::Print(_u("StackArgFormals : %s (%d) :Creating scope object in the bail out path. \n"), m_functionBody->GetDisplayName(), m_functionBody->GetFunctionNumber());
+                        Output::Flush();
+                    }
                 }
         }
         else

+ 1 - 1
lib/Runtime/Language/InterpreterStackFrame.h

@@ -687,7 +687,7 @@ namespace Js
         template <class T> void OP_EmitTmpRegCount(const unaligned OpLayoutT_Unsigned1<T> * ip);
 
         HeapArgumentsObject * CreateEmptyHeapArgumentsObject(ScriptContext* scriptContext);
-        void TrySetFrameObjectInHeapArgObj(ScriptContext * scriptContext, bool hasNonSimpleParam);
+        void TrySetFrameObjectInHeapArgObj(ScriptContext * scriptContext, bool hasNonSimpleParam, bool isScopeObjRestored);
         Var InnerScopeFromIndex(uint32 index) const;
         void SetInnerScopeFromIndex(uint32 index, Var scope);
         void OP_NewInnerScopeSlots(uint index, uint count, int scopeIndex, ScriptContext *scriptContext, FunctionBody *functionBody);

+ 21 - 3
lib/Runtime/Language/JavascriptOperators.cpp

@@ -6824,8 +6824,7 @@ CommonNumber:
                 // CONSIDER : When we delay type sharing until the second instance is created, pass an argument indicating we want the types
                 // and handlers created here to be marked as shared up-front. This is to ensure we don't get any fixed fields and that the handler
                 // is ready for storing values directly to slots.
-                DynamicType* newType = PathTypeHandlerBase::CreateNewScopeObject(scriptContext, frameObject->GetDynamicType(), propIds, nonSimpleParamList ? PropertyLetDefaults : PropertyNone);
-
+                DynamicType* newType = PathTypeHandlerBase::CreateNewScopeObject(scriptContext, frameObject->GetDynamicType(), propIds, nonSimpleParamList ? PropertyLetDefaults : PropertyNone);    
                 int oldSlotCapacity = frameObject->GetDynamicType()->GetTypeHandler()->GetSlotCapacity();
                 int newSlotCapacity = newType->GetTypeHandler()->GetSlotCapacity();
                 __analysis_assume((uint32)newSlotCapacity >= formalsCount);
@@ -6905,11 +6904,30 @@ CommonNumber:
         return argsObj;
     }
 
-    Var JavascriptOperators::OP_NewScopeObject(ScriptContext*scriptContext)
+    Var JavascriptOperators::OP_NewScopeObject(ScriptContext* scriptContext)
     {
         return scriptContext->GetLibrary()->CreateActivationObject();
     }
 
+    Var JavascriptOperators::OP_NewScopeObjectWithFormals(ScriptContext* scriptContext, JavascriptFunction * funcCallee, bool nonSimpleParamList)
+    {
+        Js::ActivationObject * frameObject = (ActivationObject*)OP_NewScopeObject(scriptContext);
+        // No fixed fields for formal parameters of the arguments object.  Also, mark all fields as initialized up-front, because
+        // we will set them directly using SetSlot below, so the type handler will not have a chance to mark them as initialized later.
+        // CONSIDER : When we delay type sharing until the second instance is created, pass an argument indicating we want the types
+        // and handlers created here to be marked as shared up-front. This is to ensure we don't get any fixed fields and that the handler
+        // is ready for storing values directly to slots.
+        DynamicType* newType = PathTypeHandlerBase::CreateNewScopeObject(scriptContext, frameObject->GetDynamicType(), funcCallee->GetFunctionBody()->GetFormalsPropIdArray(), nonSimpleParamList ? PropertyLetDefaults : PropertyNone);
+
+        int oldSlotCapacity = frameObject->GetDynamicType()->GetTypeHandler()->GetSlotCapacity();
+        int newSlotCapacity = newType->GetTypeHandler()->GetSlotCapacity();
+
+        frameObject->EnsureSlots(oldSlotCapacity, newSlotCapacity, scriptContext, newType->GetTypeHandler());
+        frameObject->ReplaceType(newType);
+        
+        return frameObject;
+    }
+
     Var* JavascriptOperators::OP_NewScopeSlots(unsigned int size, ScriptContext *scriptContext, Var scope)
     {
         Assert(size > ScopeSlots::FirstSlotIndex); // Should never see empty slot array

+ 2 - 1
lib/Runtime/Language/JavascriptOperators.h

@@ -451,7 +451,8 @@ namespace Js
         static Var OP_InitCachedScope(Var varFunc, const PropertyIdArray *propIds, DynamicType ** literalType, bool formalsAreLetDecls, ScriptContext *scriptContext);
         static void OP_InvalidateCachedScope(Var varEnv, int32 envIndex);
         static void OP_InitCachedFuncs(Var varScope, FrameDisplay *pDisplay, const FuncInfoArray *info, ScriptContext *scriptContext);
-        static Var OP_NewScopeObject(ScriptContext*scriptContext);
+        static Var OP_NewScopeObject(ScriptContext* scriptContext);
+        static Var OP_NewScopeObjectWithFormals(ScriptContext* scriptContext, JavascriptFunction * funcCallee, bool nonSimpleParamList);
         static Var* OP_NewScopeSlots(unsigned int count, ScriptContext *scriptContext, Var scope);
         static Var* OP_NewScopeSlotsWithoutPropIds(unsigned int count, int index, ScriptContext *scriptContext, FunctionBody *functionBody);
         static Var* OP_CloneScopeSlots(Var *scopeSlots, ScriptContext *scriptContext);

+ 12 - 4
test/Function/StackArgsWithFormals.baseline

@@ -1,15 +1,23 @@
-StackArgFormals : test1 (3) :Removing Scope object creation in Deadstore pass. 
 StackArgFormals : test1 (3) :Removing Heap Arguments object creation in Lowerer. 
+StackArgFormals : test1 (3) :Removing Scope object creation in Lowerer and replacing it with MOV NULL. 
 StackArgFormals : test1 (3) :Attaching the scope object with the heap arguments object in the bail out path. 
-StackArgFormals : test2 (5) :Removing Scope object creation in Deadstore pass. 
 StackArgFormals : test2 (5) :Removing Heap Arguments object creation in Lowerer. 
+StackArgFormals : test2 (5) :Removing Scope object creation in Lowerer and replacing it with MOV NULL. 
 StackArgFormals : test3 (6) :Removing Heap Arguments object creation in Lowerer. 
 StackArgFormals : inner_test4 (8) :Removing Heap Arguments object creation in Lowerer. 
 StackArgFormals : test5 (9) :Removing Heap Arguments object creation in Lowerer. 
 StackArgFormals : test6 (10) :Removing Heap Arguments object creation in Lowerer. 
 StackArgFormals : test7 (11) :Removing Heap Arguments object creation in Lowerer. 
-StackArgFormals : test12_1 (17) :Removing Scope object creation in Deadstore pass. 
 StackArgFormals : test12_1 (17) :Removing Heap Arguments object creation in Lowerer. 
-StackArgFormals : test13 (19) :Removing Scope object creation in Deadstore pass. 
+StackArgFormals : test12_1 (17) :Removing Scope object creation in Lowerer and replacing it with MOV NULL. 
 StackArgFormals : test13 (19) :Removing Heap Arguments object creation in Lowerer. 
+StackArgFormals : test13 (19) :Removing Scope object creation in Lowerer and replacing it with MOV NULL. 
+StackArgFormals : test14 (20) :Removing Heap Arguments object creation in Lowerer. 
+StackArgFormals : test14 (20) :Removing Scope object creation in Lowerer and replacing it with MOV NULL. 
+StackArgFormals : test15 (21) :Removing Heap Arguments object creation in Lowerer. 
+StackArgFormals : test16 (22) :Removing Heap Arguments object creation in Lowerer. 
+StackArgFormals : test16 (22) :Removing Scope object creation in Lowerer and replacing it with MOV NULL. 
+StackArgFormals : test16 (22) :Attaching the scope object with the heap arguments object in the bail out path. 
+StackArgFormals : test17 (23) :Removing Heap Arguments object creation in Lowerer. 
+StackArgFormals : test17 (23) :Attaching the scope object with the heap arguments object in the bail out path. 
 PASSED

+ 57 - 0
test/Function/StackArgsWithFormals.js

@@ -215,6 +215,63 @@ test13(1,2);
 test13({}, {});
 verify(["number", "object"], "TEST 13");
 
+function test14(a) {
+    actuals.push(arguments[1]);
+    function test14_nested1()
+    {
+    }
+}
+test14(1,2);
+test14(4,5);
+verify([2,5], "TEST 14");
+
+function test15(a){
+	actuals.push(arguments[0])
+	var b = 10;
+	function test15_nested1()
+	{
+		actuals.push(b);
+	};
+	test15_nested1();
+}
+
+test15(1);
+test15(2);
+verify([1, 10, 2, 10], "TEST 15");
+
+function test16(a,b){
+	var c = 10;
+
+	if(shouldBailout)
+	{
+		actuals.push(arguments[1]);
+	}
+	actuals.push(c);
+	function test16_nested1()
+	{
+	}
+}
+
+shouldBailout = false;
+test16(1,2);
+shouldBailout = true;
+test16(3,4);
+verify([10, 4, 10], "TEST 16");
+
+function test17(a){
+	var b = 20;
+	actuals.push(arguments[0.1*10]);
+	
+	function test17_nested1(){
+		actuals.push(b);
+	}
+	test17_nested1();
+}
+
+test17(1);
+test17(2);
+verify([undefined, 20, undefined, 20], "TEST 17");
+
 if(hasAllPassed)
 {
     print("PASSED");