瀏覽代碼

Let redeferral work around block scope limitation of deferred parsing. Detect cases where only block-scoping prevents upfront deferral. Also detect cases where it is safe to treat a lexically-scoped function as a var-scoped one. For functions that meet both criteria, use the var binding for it in place of the lexical binding. If at byte code gen time there are no lexical/with/catch scopes in the current chain, mark the function as a redeferral candidate.

Paul Leathers 9 年之前
父節點
當前提交
1e22d8df49

+ 1 - 0
lib/Common/ConfigFlagsList.h

@@ -29,6 +29,7 @@ PHASE(All)
         PHASE(ByteCodeSerialization)
             PHASE(VariableIntEncoding)
         PHASE(NativeCodeSerialization)
+        PHASE(OptimizeBlockScope)
     PHASE(Delay)
         PHASE(Speculation)
         PHASE(GatherCodeGenData)

+ 81 - 52
lib/Parser/Parse.cpp

@@ -55,6 +55,12 @@ struct StmtNest
         };
     };
     StmtNest *pstmtOuter;           // Enclosing statement.
+
+    OpCode GetNop() const 
+    { 
+        AnalysisAssert(isDeferred || pnodeStmt != nullptr);
+        return isDeferred ? op : pnodeStmt->nop; 
+    }
 };
 
 struct BlockInfoStack
@@ -110,6 +116,7 @@ Parser::Parser(Js::ScriptContext* scriptContext, BOOL strictMode, PageAllocator
     m_errorCallback = nullptr;
     m_uncertainStructure = FALSE;
     m_reparsingLambdaParams = false;
+    m_inFIB = false;
     currBackgroundParseItem = nullptr;
     backgroundParseItems = nullptr;
     fastScannedRegExpNodes = nullptr;
@@ -722,7 +729,7 @@ ParseNodePtr Parser::CreateNodeT(charcount_t ichMin,charcount_t ichLim)
     return pnode;
 }
 
-ParseNodePtr Parser::CreateDeclNode(OpCode nop, IdentPtr pid, SymbolType symbolType, bool errorOnRedecl)
+ParseNodePtr Parser::CreateDeclNode(OpCode nop, IdentPtr pid, SymbolType symbolType, bool errorOnRedecl, bool *isRedecl)
 {
     ParseNodePtr pnode = CreateNode(nop);
 
@@ -730,51 +737,38 @@ ParseNodePtr Parser::CreateDeclNode(OpCode nop, IdentPtr pid, SymbolType symbolT
 
     if (symbolType != STUnknown)
     {
-        pnode->sxVar.sym = AddDeclForPid(pnode, pid, symbolType, errorOnRedecl);
+        pnode->sxVar.sym = AddDeclForPid(pnode, pid, symbolType, errorOnRedecl, isRedecl);
     }
 
     return pnode;
 }
 
-Symbol* Parser::AddDeclForPid(ParseNodePtr pnode, IdentPtr pid, SymbolType symbolType, bool errorOnRedecl)
+Symbol* Parser::AddDeclForPid(ParseNodePtr pnode, IdentPtr pid, SymbolType symbolType, bool errorOnRedecl, bool *isRedecl)
 {
     Assert(pnode->IsVarLetOrConst());
 
     PidRefStack *refForUse = nullptr, *refForDecl = nullptr;
 
+    if (isRedecl)
+    {
+        *isRedecl = false;
+    }
+
     BlockInfoStack *blockInfo;
     bool fBlockScope = false;
     if (pnode->nop != knopVarDecl || symbolType == STFunction)
     {
         Assert(m_pstmtCur);
-        if (m_pstmtCur->isDeferred)
+        if (m_pstmtCur->GetNop() != knopBlock)
         {
-            // Deferred parsing: there's no pnodeStmt node, only an opcode on the Stmt struct.
-            if (m_pstmtCur->op != knopBlock)
-            {
-                // Let/const declared in a bare statement context.
-                Error(ERRDeclOutOfStmt);
-            }
-
-            if (m_pstmtCur->pstmtOuter && m_pstmtCur->pstmtOuter->op == knopSwitch)
-            {
-                // Let/const declared inside a switch block (requiring conservative use-before-decl check).
-                pnode->sxVar.isSwitchStmtDecl = true;
-            }
+            // Let/const declared in a bare statement context.
+            Error(ERRDeclOutOfStmt);
         }
-        else
-        {
-            if (m_pstmtCur->pnodeStmt->nop != knopBlock)
-            {
-                // Let/const declared in a bare statement context.
-                Error(ERRDeclOutOfStmt);
-            }
 
-            if (m_pstmtCur->pstmtOuter && m_pstmtCur->pstmtOuter->pnodeStmt->nop == knopSwitch)
-            {
-                // Let/const declared inside a switch block (requiring conservative use-before-decl check).
-                pnode->sxVar.isSwitchStmtDecl = true;
-            }
+        if (m_pstmtCur->pstmtOuter && m_pstmtCur->pstmtOuter->GetNop() == knopSwitch)
+        {
+            // Let/const declared inside a switch block (requiring conservative use-before-decl check).
+            pnode->sxVar.isSwitchStmtDecl = true;
         }
 
         fBlockScope = pnode->nop != knopVarDecl ||
@@ -833,6 +827,10 @@ Symbol* Parser::AddDeclForPid(ParseNodePtr pnode, IdentPtr pid, SymbolType symbo
     Symbol *sym = refForDecl->GetSym();
     if (sym != nullptr)
     {
+        if (isRedecl)
+        {
+            *isRedecl = true;
+        }
         // Multiple declarations in the same scope. 3 possibilities: error, existing one wins, new one wins.
         switch (pnode->nop)
         {
@@ -901,6 +899,10 @@ Symbol* Parser::AddDeclForPid(ParseNodePtr pnode, IdentPtr pid, SymbolType symbo
         {
             Assert(blockInfo->pnodeBlock->sxBlock.blockType == PnodeBlockType::Regular);
             scope = Anew(&m_nodeAllocator, Scope, &m_nodeAllocator, ScopeType_Block);
+            if (this->IsCurBlockInLoop())
+            {
+                scope->SetIsBlockInLoop();
+            }
             blockInfo->pnodeBlock->sxBlock.scope = scope;
             PushScope(scope);
         }
@@ -961,6 +963,23 @@ Symbol* Parser::AddDeclForPid(ParseNodePtr pnode, IdentPtr pid, SymbolType symbo
     return sym;
 }
 
+bool Parser::IsCurBlockInLoop() const
+{
+    for (StmtNest *stmt = this->m_pstmtCur; stmt != nullptr; stmt = stmt->pstmtOuter)
+    {
+        OpCode nop = stmt->GetNop();
+        if (ParseNode::Grfnop(nop) & fnopContinue)
+        {
+            return true;
+        }
+        if (nop == knopFncDecl)
+        {
+            return false;
+        }
+    }
+    return false;
+}
+
 void Parser::RestorePidRefForSym(Symbol *sym)
 {
     IdentPtr pid = m_pscan->m_phtbl->PidHashNameLen(sym->GetName().GetBuffer(), sym->GetName().GetLength());
@@ -1369,9 +1388,9 @@ ParseNodePtr Parser::CreateModuleImportDeclNode(IdentPtr localName)
     return declNode;
 }
 
-ParseNodePtr Parser::CreateVarDeclNode(IdentPtr pid, SymbolType symbolType, bool autoArgumentsObject, ParseNodePtr pnodeFnc, bool errorOnRedecl)
+ParseNodePtr Parser::CreateVarDeclNode(IdentPtr pid, SymbolType symbolType, bool autoArgumentsObject, ParseNodePtr pnodeFnc, bool errorOnRedecl, bool *isRedecl)
 {
-    ParseNodePtr pnode = CreateDeclNode(knopVarDecl, pid, symbolType, errorOnRedecl);
+    ParseNodePtr pnode = CreateDeclNode(knopVarDecl, pid, symbolType, errorOnRedecl, isRedecl);
 
     // Append the variable to the end of the current variable list.
     AssertMem(m_ppnodeVar);
@@ -4540,10 +4559,7 @@ ParseNodePtr Parser::ParseFncDecl(ushort flags, LPCOLESTR pNameHint, const bool
 
     if (fDeclaration)
     {
-        AnalysisAssert(m_pstmtCur->isDeferred || m_pstmtCur->pnodeStmt != nullptr);
-        noStmtContext =
-            (m_pstmtCur->isDeferred && m_pstmtCur->op != knopBlock) ||
-            (!m_pstmtCur->isDeferred && m_pstmtCur->pnodeStmt->nop != knopBlock);
+        noStmtContext = m_pstmtCur->GetNop() != knopBlock;
 
         if (noStmtContext)
         {
@@ -4712,8 +4728,13 @@ ParseNodePtr Parser::ParseFncDecl(ushort flags, LPCOLESTR pNameHint, const bool
             // level and we accomplish this by having each block scoped function
             // declaration assign to both the block scoped "let" binding, as well
             // as the function scoped "var" binding.
-            ParseNodePtr vardecl = CreateVarDeclNode(pnodeFnc->sxFnc.pnodeName->sxVar.pid, STVariable, false, nullptr, false);
+            bool isRedecl = false;
+            ParseNodePtr vardecl = CreateVarDeclNode(pnodeFnc->sxFnc.pnodeName->sxVar.pid, STVariable, false, nullptr, false, &isRedecl);
             vardecl->sxVar.isBlockScopeFncDeclVar = true;
+            if (isRedecl)
+            {
+                vardecl->sxVar.sym->SetHasBlockFncVarRedecl();
+            }
         }
     }
 
@@ -4906,7 +4927,6 @@ bool Parser::ParseFncDeclHelper(ParseNodePtr pnodeFnc, LPCOLESTR pNameHint, usho
 
     uint uDeferSave = m_grfscr & fscrDeferFncParse;
     if ((!fDeclaration && m_ppnodeExprScope) ||
-        fFunctionInBlock ||
         isEnclosedInParamScope ||
         (flags & (fFncNoName | fFncLambda)))
     {
@@ -4920,6 +4940,8 @@ bool Parser::ParseFncDeclHelper(ParseNodePtr pnodeFnc, LPCOLESTR pNameHint, usho
         m_grfscr &= ~fscrDeferFncParse;
     }
 
+    bool saveInFIB = this->m_inFIB;
+    this->m_inFIB = fFunctionInBlock || this->m_inFIB;
 
     bool isTopLevelDeferredFunc = false;
 
@@ -4938,14 +4960,12 @@ bool Parser::ParseFncDeclHelper(ParseNodePtr pnodeFnc, LPCOLESTR pNameHint, usho
 
         BOOL isDeferredFnc = IsDeferredFnc();
         AnalysisAssert(isDeferredFnc || pnodeFnc);
+        // These are the conditions that prohibit upfront deferral *and* redeferral.
         isTopLevelDeferredFunc =
             (!fLambda
              && pnodeFnc
              && DeferredParse(pnodeFnc->sxFnc.functionId)
              && (!pnodeFnc->sxFnc.IsNested() || CONFIG_FLAG(DeferNested))
-            // Don't defer if this is a function expression not contained in a statement or other expression.
-            // Assume it will be called as part of this expression.
-             && (!isLikelyIIFE || !topLevelStmt || PHASE_FORCE_RAW(Js::DeferParsePhase, m_sourceContextInfo->sourceContextId, pnodeFnc->sxFnc.functionId))
              && !m_InAsmMode
             // Don't defer a module function wrapper because we need to do export resolution at parse time
              && !fModule
@@ -4954,9 +4974,25 @@ bool Parser::ParseFncDeclHelper(ParseNodePtr pnodeFnc, LPCOLESTR pNameHint, usho
         if (pnodeFnc)
         {
             pnodeFnc->sxFnc.SetCanBeDeferred(isTopLevelDeferredFunc && PnFnc::CanBeRedeferred(pnodeFnc->sxFnc.fncFlags));
+            pnodeFnc->sxFnc.SetFIBPreventsDeferral(false);
         }
-        isTopLevelDeferredFunc = isTopLevelDeferredFunc && !isDeferredFnc;
 
+        if (this->m_inFIB)
+        {
+            if (isTopLevelDeferredFunc)
+            {
+                // Block-scoping is the only non-heuristic reason for not deferring this function up front.
+                // So on creating the full FunctionBody at byte code gen time, verify that there is no
+                // block-scoped content visible to this function so it can remain a redeferral candidate.
+                pnodeFnc->sxFnc.SetFIBPreventsDeferral(true);
+            }
+            isTopLevelDeferredFunc = false;
+        }        
+
+        // These are heuristic conditions that prohibit upfront deferral but not redeferral.
+        isTopLevelDeferredFunc = isTopLevelDeferredFunc && !isDeferredFnc && 
+            (!isLikelyIIFE || !topLevelStmt || PHASE_FORCE_RAW(Js::DeferParsePhase, m_sourceContextInfo->sourceContextId, pnodeFnc->sxFnc.functionId));
+;
         if (!fLambda &&
             !isDeferredFnc &&
             !isLikelyIIFE &&
@@ -5392,7 +5428,7 @@ bool Parser::ParseFncDeclHelper(ParseNodePtr pnodeFnc, LPCOLESTR pNameHint, usho
     {
         m_grfscr |= uDeferSave;
     }
-
+    m_inFIB = saveInFIB;
 
     m_pscan->SetYieldIsKeyword(fPreviousYieldIsKeyword);
     m_pscan->SetAwaitIsKeyword(fPreviousAwaitIsKeyword);
@@ -10012,21 +10048,14 @@ LGetJumpStatement:
                     }
                     else
                     {
-                        if (pstmt->isDeferred)
+                        if (ParseNode::Grfnop(pstmt->GetNop()) & fnop)
                         {
-                            if (ParseNode::Grfnop(pstmt->op) & fnop)
-                            {
-                                goto LNeedTerminator;
-                            }
-                        }
-                        else
-                        {
-                            AnalysisAssert(pstmt->pnodeStmt);
-                            if (pstmt->pnodeStmt->Grfnop() & fnop)
+                            if (!pstmt->isDeferred)
                             {
+                                AnalysisAssert(pstmt->pnodeStmt);
                                 pstmt->pnodeStmt->sxStmt.grfnop |= fnop;
-                                goto LNeedTerminator;
                             }
+                            goto LNeedTerminator;
                         }
                     }
                 }

+ 5 - 3
lib/Parser/Parse.h

@@ -225,6 +225,7 @@ private:
     bool CheckStrictModeStrPid(IdentPtr pid);
     bool CheckAsmjsModeStrPid(IdentPtr pid);
 
+    bool IsCurBlockInLoop() const;
 
     void InitPids();
 
@@ -263,8 +264,8 @@ public:
     ParseNodePtr CreateTempRef(ParseNode* tempNode);
 
     ParseNodePtr CreateNode(OpCode nop) { return CreateNode(nop, m_pscan? m_pscan->IchMinTok() : 0); }
-    ParseNodePtr CreateDeclNode(OpCode nop, IdentPtr pid, SymbolType symbolType, bool errorOnRedecl = true);
-    Symbol*      AddDeclForPid(ParseNodePtr pnode, IdentPtr pid, SymbolType symbolType, bool errorOnRedecl);
+    ParseNodePtr CreateDeclNode(OpCode nop, IdentPtr pid, SymbolType symbolType, bool errorOnRedecl = true, bool *isRedecl = nullptr);
+    Symbol*      AddDeclForPid(ParseNodePtr pnode, IdentPtr pid, SymbolType symbolType, bool errorOnRedecl, bool *isRedecl = nullptr);
     ParseNodePtr CreateNameNode(IdentPtr pid)
     {
         ParseNodePtr pnode = CreateNode(knopName);
@@ -314,7 +315,7 @@ public:
     void CheckPidIsValid(IdentPtr pid, bool autoArgumentsObject = false);
     void AddVarDeclToBlock(ParseNode *pnode);
     // Add a var declaration. Only use while parsing. Assumes m_ppnodeVar is pointing to the right place already
-    ParseNodePtr CreateVarDeclNode(IdentPtr pid, SymbolType symbolType, bool autoArgumentsObject = false, ParseNodePtr pnodeFnc = NULL, bool checkReDecl = true);
+    ParseNodePtr CreateVarDeclNode(IdentPtr pid, SymbolType symbolType, bool autoArgumentsObject = false, ParseNodePtr pnodeFnc = NULL, bool checkReDecl = true, bool *isRedecl = nullptr);
     // Add a var declaration, during parse tree rewriting. Will setup m_ppnodeVar for the given pnodeFnc
     ParseNodePtr AddVarDeclNode(IdentPtr pid, ParseNodePtr pnodeFnc);
     // Add a 'const' or 'let' declaration.
@@ -368,6 +369,7 @@ private:
     bool m_inDeferredNestedFunc; // true if parsing a function in deferred mode, nested within the current node
     bool m_isInBackground;
     bool m_reparsingLambdaParams;
+    bool m_inFIB;
 
     // This bool is used for deferring the shorthand initializer error ( {x = 1}) - as it is allowed in the destructuring grammar.
     bool m_hasDeferredShorthandInitError;

+ 5 - 0
lib/Parser/ptree.h

@@ -247,6 +247,7 @@ struct PnFnc
     RestorePoint *pRestorePoint;
     DeferredFunctionStub *deferredStub;
     bool canBeDeferred;
+    bool fibPreventsDeferral;
 
     static const int32 MaxStackClosureAST = 800000;
 
@@ -284,6 +285,8 @@ public:
     void ClearFlags()
     {
         fncFlags = kFunctionNone;
+        canBeDeferred = false;
+        fibPreventsDeferral = false;
     }
 
     void SetAsmjsMode(bool set = true) { SetFlags(kFunctionAsmjsMode, set); }
@@ -320,6 +323,7 @@ public:
     void SetIsDefaultModuleExport(bool set = true) { SetFlags(kFunctionIsDefaultModuleExport, set); }
     void SetNestedFuncEscapes(bool set = true) { nestedFuncEscapes = set; }
     void SetCanBeDeferred(bool set = true) { canBeDeferred = set; }
+    void SetFIBPreventsDeferral(bool set = true) { fibPreventsDeferral = set; }
 
     bool CallsEval() const { return HasFlags(kFunctionCallsEval); }
     bool ChildCallsEval() const { return HasFlags(kFunctionChildCallsEval); }
@@ -358,6 +362,7 @@ public:
     bool IsDefaultModuleExport() const { return HasFlags(kFunctionIsDefaultModuleExport); }
     bool NestedFuncEscapes() const { return nestedFuncEscapes; }
     bool CanBeDeferred() const { return canBeDeferred; }
+    bool FIBPreventsDeferral() const { return fibPreventsDeferral; }
 
     size_t LengthInBytes()
     {

+ 16 - 1
lib/Runtime/ByteCode/ByteCodeEmitter.cpp

@@ -1169,7 +1169,7 @@ void EmitAssignmentToFuncName(ParseNode *pnodeFnc, ByteCodeGenerator *byteCodeGe
             {
                 byteCodeGenerator->EmitPropStore(pnodeFnc->location, sym, nullptr, funcInfoParent);
             }
-            else
+            else if (!sym->GetIsBlockVar() || sym->HasRealBlockVarRef() || sym->GetScope()->GetIsObject())
             {
                 byteCodeGenerator->EmitLocalPropInit(pnodeFnc->location, sym, funcInfoParent);
             }
@@ -3839,6 +3839,21 @@ void ByteCodeGenerator::StartEmitFunction(ParseNode *pnodeFnc)
     {
         // Only set the environment depth if it's truly known (i.e., not in eval or event handler).
         funcInfo->GetParsedFunctionBody()->SetEnvDepth(this->envDepth);
+
+        if (pnodeFnc->sxFnc.FIBPreventsDeferral())
+        {
+            for (Scope *scope = this->currentScope; scope; scope = scope->GetEnclosingScope())
+            {
+                if (scope->GetScopeType() != ScopeType_FunctionBody && 
+                    scope->GetScopeType() != ScopeType_Global &&
+                    scope->GetScopeType() != ScopeType_GlobalEvalBlock &&
+                    scope->GetMustInstantiate())
+                {
+                    funcInfo->byteCodeFunction->SetAttributes((Js::FunctionInfo::Attributes)(funcInfo->byteCodeFunction->GetAttributes() & ~Js::FunctionInfo::Attributes::CanDefer));
+                    break;
+                }
+            }
+        }
     }
 
     if (funcInfo->GetCallsEval())

+ 70 - 3
lib/Runtime/ByteCode/ByteCodeGenerator.cpp

@@ -1615,10 +1615,11 @@ Symbol * ByteCodeGenerator::FindSymbol(Symbol **symRef, IdentPtr pid, bool forRe
         FuncInfo *top = funcInfoStack->Top();
 
         bool nonLocalRef = symScope->GetFunc() != top;
+        Scope *scope = nullptr;
         if (forReference)
         {
             Js::PropertyId i;
-            Scope *scope = FindScopeForSym(symScope, nullptr, &i, top);
+            scope = FindScopeForSym(symScope, nullptr, &i, top);
             // If we have a reference to a local within a with, we want to generate a closure represented by an object.
             if (scope != symScope && scope->GetIsDynamic())
             {
@@ -1628,6 +1629,63 @@ Symbol * ByteCodeGenerator::FindSymbol(Symbol **symRef, IdentPtr pid, bool forRe
             }
         }
 
+        bool didTransferToFncVarSym = false;
+
+        if (!PHASE_OFF(Js::OptimizeBlockScopePhase, top->byteCodeFunction) &&
+            sym->GetIsBlockVar() && 
+            !sym->GetScope()->IsBlockInLoop() &&
+            sym->GetSymbolType() == STFunction)
+        {
+            // Try to use the var-scoped function binding in place of the lexically scoped one.
+            // This can be done if neither binding is explicitly assigned to, if there's no ambiguity in the binding
+            // (with/eval), and if the function is not declared in a loop. (Loops are problematic, because as the loop
+            // iterates different instances can be captured. If we always capture the var-scoped binding, then we
+            // always get the latest instance, when we should get the instance belonging to the iteration that captured it.)
+            if (sym->GetHasNonLocalReference() && !this->scriptContext->IsScriptContextInSourceRundownOrDebugMode())
+            {
+                if (!scope)
+                {
+                    Js::PropertyId i;
+                    scope = FindScopeForSym(symScope, nullptr, &i, top);
+                }
+                if (scope == symScope && !scope->GetIsObject())
+                {
+                    Symbol *fncVarSym = sym->GetFuncScopeVarSym();
+                    if (fncVarSym &&
+                        !fncVarSym->HasBlockFncVarRedecl() &&
+                        sym->GetAssignmentState() == NotAssigned &&
+                        fncVarSym->GetAssignmentState() == NotAssigned)
+                    {
+                        // Make sure no dynamic scope intrudes between the two bindings.
+                        bool foundDynamicScope = false;
+                        for (Scope *tmpScope = symScope->GetEnclosingScope(); tmpScope != fncVarSym->GetScope(); tmpScope = symScope->GetEnclosingScope())
+                        {
+                            Assert(tmpScope);
+                            if (tmpScope->GetIsDynamic())
+                            {
+                                foundDynamicScope = true;
+                                break;
+                            }
+                        }
+                        if (!foundDynamicScope)
+                        {
+                            didTransferToFncVarSym = true;
+                            sym = fncVarSym;
+                            symScope = sym->GetScope();
+                            if (nonLocalRef)
+                            {
+                                sym->SetHasNonLocalReference();
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        if (!didTransferToFncVarSym)
+        {
+            sym->SetHasRealBlockVarRef();
+        }
+
         if (nonLocalRef)
         {
             // Symbol referenced through a closure. Mark it as such and give it a property ID.
@@ -1694,7 +1752,6 @@ Symbol * ByteCodeGenerator::AddSymbolToScope(Scope *scope, const char16 *key, in
         // on such compiles, so we essentially have to migrate the symbol to the new scope.
         // We check fscrEvalCode, not fscrEval, because the same thing can happen in indirect eval,
         // when fscrEval is not set.
-        Assert(((this->flags & fscrEvalCode) && sym->GetIsGlobal() && sym->GetSymbolType() == STFunction) || this->IsConsoleScopeEval());
         Assert(scope->GetScopeType() == ScopeType_Global);
         scope->AddNewSymbol(sym);
     }
@@ -2854,6 +2911,7 @@ FuncInfo* PostVisitFunction(ParseNode* pnode, ByteCodeGenerator* byteCodeGenerat
     Js::FunctionBody * parentFunctionBody = parentFunc->byteCodeFunction->GetFunctionBody();
     Assert(parentFunctionBody != nullptr);
     bool const hasAnyDeferredChild = top->HasDeferredChild() || top->IsDeferred();
+    bool const hasAnyRedeferrableChild = top->HasRedeferrableChild() || top->IsRedeferrable();
     bool setHasNonLocalReference = parentFunctionBody->HasAllNonLocalReferenced();
 
     // If we have any deferred child, we need to instantiate the fake global block scope if it is not empty
@@ -2870,6 +2928,10 @@ FuncInfo* PostVisitFunction(ParseNode* pnode, ByteCodeGenerator* byteCodeGenerat
             {
                 parentFunc->SetHasDeferredChild();
             }
+            if (hasAnyRedeferrableChild)
+            {
+                parentFunc->SetHasRedeferrableChild();
+            }
         }
     }
     else
@@ -2891,6 +2953,10 @@ FuncInfo* PostVisitFunction(ParseNode* pnode, ByteCodeGenerator* byteCodeGenerat
             Assert(CONFIG_FLAG(DeferNested));
             parentFunc->SetHasDeferredChild();
         }
+        if (hasAnyRedeferrableChild)
+        {
+            parentFunc->SetHasRedeferrableChild();
+        }
 
         if (top->ChildHasWith() || pnode->sxFnc.HasWithStmt())
         {
@@ -4350,7 +4416,8 @@ void Bind(ParseNode *pnode, ByteCodeGenerator *byteCodeGenerator)
                 // This is a named load, not just a reference, so if it's a nested function note that all
                 // the nested scopes escape.
                 Assert(!sym->GetDecl() || (pnode->sxPid.symRef && *pnode->sxPid.symRef));
-                Assert(!sym->GetDecl() || ((*pnode->sxPid.symRef)->GetDecl() == sym->GetDecl()));
+                Assert(!sym->GetDecl() || ((*pnode->sxPid.symRef)->GetDecl() == sym->GetDecl()) ||
+                       ((*pnode->sxPid.symRef)->GetFuncScopeVarSym() == sym));
 
                 pnode->sxPid.sym = sym;
                 if (sym->GetSymbolType() == STFunction &&

+ 6 - 0
lib/Runtime/ByteCode/FuncInfo.cpp

@@ -62,6 +62,7 @@ FuncInfo::FuncInfo(
     applyEnclosesArgs(false),
     escapes(false),
     hasDeferredChild(false),
+    hasRedeferrableChild(false),
     childHasWith(false),
     hasLoop(false),
     hasEscapedUseNestedFunc(false),
@@ -125,6 +126,11 @@ bool FuncInfo::IsDeferred() const
     return root && root->sxFnc.pnodeBody == nullptr;
 }
 
+bool FuncInfo::IsRedeferrable() const
+{
+    return byteCodeFunction && byteCodeFunction->CanBeDeferred();
+}
+
 BOOL FuncInfo::HasSuperReference() const
 {
     return root->sxFnc.HasSuperReference();

+ 11 - 0
lib/Runtime/ByteCode/FuncInfo.h

@@ -134,6 +134,7 @@ public:
     uint applyEnclosesArgs : 1;
     uint escapes : 1;
     uint hasDeferredChild : 1; // switch for DeferNested to persist outer scopes
+    uint hasRedeferrableChild : 1;
     uint childHasWith : 1; // deferNested needs to know if child has with
     uint hasLoop : 1;
     uint hasEscapedUseNestedFunc : 1;
@@ -431,6 +432,16 @@ public:
         hasDeferredChild = true;
     }
 
+    bool HasRedeferrableChild() const {
+        return hasRedeferrableChild;
+    }
+
+    void SetHasRedeferrableChild() {
+        hasRedeferrableChild = true;
+    }
+
+    bool IsRedeferrable() const;
+
     Js::FunctionBody* GetParsedFunctionBody() const
     {
         AssertMsg(this->byteCodeFunction->IsFunctionParsed(), "Function must be parsed in order to call this method");

+ 5 - 0
lib/Runtime/ByteCode/Scope.h

@@ -39,6 +39,7 @@ private:
     BYTE hasDuplicateFormals : 1;
     BYTE canMergeWithBodyScope : 1;
     BYTE hasLocalInClosure : 1;
+    BYTE isBlockInLoop : 1;
 public:
 #if DBG
     BYTE isRestored : 1;
@@ -56,6 +57,7 @@ public:
         hasDuplicateFormals(false),
         canMergeWithBodyScope(true),
         hasLocalInClosure(false),
+        isBlockInLoop(false),
         location(Js::Constants::NoRegister),
         m_symList(nullptr),
         m_count(0),
@@ -248,6 +250,9 @@ public:
     void SetHasOwnLocalInClosure(bool has) { hasLocalInClosure = has; }
     bool GetHasOwnLocalInClosure() const { return hasLocalInClosure; }
 
+    void SetIsBlockInLoop(bool is = true) { isBlockInLoop = is; }
+    bool IsBlockInLoop() const { return isBlockInLoop; }
+
     bool HasInnerScopeIndex() const { return innerScopeIndex != (uint)-1; }
     uint GetInnerScopeIndex() const { return innerScopeIndex; }
     void SetInnerScopeIndex(uint index) { innerScopeIndex = index; }

+ 17 - 14
lib/Runtime/ByteCode/ScopeInfo.cpp

@@ -122,7 +122,7 @@ namespace Js
     //
     void ScopeInfo::SaveParentScopeInfo(FuncInfo* parentFunc, FuncInfo* func)
     {
-        Assert(func->IsDeferred());
+        Assert(func->IsDeferred() || func->byteCodeFunction->CanBeDeferred());
 
         // Parent must be parsed
         FunctionBody* parent = parentFunc->byteCodeFunction->GetFunctionBody();
@@ -144,7 +144,7 @@ namespace Js
         ParseableFunctionInfo* funcBody = func->byteCodeFunction;
 
         Assert((!func->IsGlobalFunction() || byteCodeGenerator->GetFlags() & fscrEvalCode) &&
-            (func->HasDeferredChild() || (funcBody->IsReparsed())));
+            (func->HasDeferredChild() || func->HasRedeferrableChild() || funcBody->IsReparsed()));
 
         // If we are reparsing a deferred function, we already have correct "parent" info in
         // funcBody->scopeInfo. parentFunc is the knopProg shell and should not be used in this
@@ -194,11 +194,22 @@ namespace Js
 
         Scope* currentScope = byteCodeGenerator->GetCurrentScope();
         Assert(currentScope == funcInfo->GetBodyScope());
-        if (funcInfo->IsDeferred())
+        if (funcInfo->HasDeferredChild() ||
+            funcInfo->HasRedeferrableChild() ||
+            (!funcInfo->IsGlobalFunction() &&
+                funcInfo->byteCodeFunction &&
+                funcInfo->byteCodeFunction->IsReparsed() &&
+                funcInfo->byteCodeFunction->GetFunctionBody()->HasAllNonLocalReferenced()))
+        {
+            // When we reparse due to attach, we would need to capture all of them, since they were captured before going to debug mode.
+
+            Js::ScopeInfo::SaveScopeInfo(byteCodeGenerator, parentFunc, funcInfo);
+        }
+        else if (funcInfo->IsDeferred() || funcInfo->IsRedeferrable())
         {
             // Don't need to remember the parent function if we have a global function
             if (!parentFunc->IsGlobalFunction() ||
-                ((byteCodeGenerator->GetFlags() & fscrEvalCode) && parentFunc->HasDeferredChild()))
+                ((byteCodeGenerator->GetFlags() & fscrEvalCode) && (parentFunc->HasDeferredChild() || parentFunc->HasRedeferrableChild())))
             {
                 // TODO: currently we only support defer nested function that is in function scope (no block scope, no with scope, etc.)
 #if DBG
@@ -227,26 +238,18 @@ namespace Js
                     {
                         Assert(!funcInfo->GetParamScope()->GetCanMergeWithBodyScope());
                     }
+#if 0
                     else
                     { 
                         Assert(currentScope->GetEnclosingScope() ==
                             (parentFunc->IsGlobalFunction() && parentFunc->GetGlobalEvalBlockScope() && parentFunc->GetGlobalEvalBlockScope()->GetMustInstantiate() ? parentFunc->GetGlobalEvalBlockScope() : parentFunc->GetBodyScope()));
                     }
+#endif
                 }
 #endif
                 Js::ScopeInfo::SaveParentScopeInfo(parentFunc, funcInfo);
             }
         }
-        else if (funcInfo->HasDeferredChild() ||
-            (!funcInfo->IsGlobalFunction() &&
-                funcInfo->byteCodeFunction &&
-                funcInfo->byteCodeFunction->IsReparsed() &&
-                funcInfo->byteCodeFunction->GetFunctionBody()->HasAllNonLocalReferenced()))
-        {
-            // When we reparse due to attach, we would need to capture all of them, since they were captured before going to debug mode.
-
-            Js::ScopeInfo::SaveScopeInfo(byteCodeGenerator, parentFunc, funcInfo);
-        }
     }
 
     //

+ 29 - 0
lib/Runtime/ByteCode/Symbol.h

@@ -40,6 +40,8 @@ private:
     BYTE isGlobalCatch : 1;
     BYTE isCommittedToSlot : 1;
     BYTE hasNonCommittedReference : 1;
+    BYTE hasRealBlockVarRef : 1;
+    BYTE hasBlockFncVarRedecl : 1;
     BYTE hasVisitedCapturingFunc : 1;
     BYTE isTrackedForDebugger : 1; // Whether the sym is tracked for debugger scope. This is fine because a sym can only be added to (not more than) one scope.
     BYTE isModuleExportStorage : 1; // If true, this symbol should be stored in the global scope export storage array.
@@ -72,6 +74,8 @@ public:
         isGlobalCatch(false),
         isCommittedToSlot(false),
         hasNonCommittedReference(false),
+        hasRealBlockVarRef(false),
+        hasBlockFncVarRedecl(false),
         hasVisitedCapturingFunc(false),
         isTrackedForDebugger(false),
         isNonSimpleParameter(false),
@@ -300,6 +304,31 @@ public:
         isUsed = is;
     }
 
+    bool HasRealBlockVarRef() const
+    {
+        return hasRealBlockVarRef;
+    }
+
+    void SetHasRealBlockVarRef(bool has = true)
+    {
+        hasRealBlockVarRef = has;
+    }
+
+    bool HasBlockFncVarRedecl() const
+    {
+        return hasBlockFncVarRedecl;
+    }
+
+    void SetHasBlockFncVarRedecl(bool has = true)
+    {
+        hasBlockFncVarRedecl = has;
+    }
+
+    AssignmentState GetAssignmentState() const
+    {
+        return assignmentState;
+    }
+
     void PromoteAssignmentState()
     {
         if (assignmentState == NotAssigned)