diff --git a/src/Compiler/PhpCodeAnalysis/Semantics/Graph/BoundBlock.cs b/src/Compiler/PhpCodeAnalysis/Semantics/Graph/BoundBlock.cs index 83c80ebf2..64d393693 100644 --- a/src/Compiler/PhpCodeAnalysis/Semantics/Graph/BoundBlock.cs +++ b/src/Compiler/PhpCodeAnalysis/Semantics/Graph/BoundBlock.cs @@ -41,6 +41,28 @@ namespace Pchp.CodeAnalysis.Semantics.Graph get { return _next; } internal set { _next = value; } } + + #region Topological order & scoping + + /// + /// Gets block topological index. + /// Index is unique within the graph. + /// + public int Ordinal { get { return _ordinal; } internal set { _ordinal = value; } } + private int _ordinal; + + /// + /// Index of nearest block after the scope. + /// + public int ScopeTo { get { return _scopeTo; } internal set { _scopeTo = value; } } + private int _scopeTo; + + /// + /// Gets value indicating is valid. + /// + public bool ScopeToValid => _scopeTo >= _ordinal; + + #endregion internal BoundBlock() { @@ -150,7 +172,6 @@ namespace Pchp.CodeAnalysis.Semantics.Graph private readonly VariableName _variableName; public CatchBlock(CatchItem item) - : base() { _typeRef = item.TypeRef; _variableName = item.Variable.VarName; @@ -177,7 +198,6 @@ namespace Pchp.CodeAnalysis.Semantics.Graph public bool IsDefault => _caseValue == null; public CaseBlock(BoundExpression caseValue) - : base() { _caseValue = caseValue; } diff --git a/src/Compiler/PhpCodeAnalysis/Semantics/Graph/BuilderVisitor.cs b/src/Compiler/PhpCodeAnalysis/Semantics/Graph/BuilderVisitor.cs index cf075e188..d6adef84d 100644 --- a/src/Compiler/PhpCodeAnalysis/Semantics/Graph/BuilderVisitor.cs +++ b/src/Compiler/PhpCodeAnalysis/Semantics/Graph/BuilderVisitor.cs @@ -20,6 +20,8 @@ namespace Pchp.CodeAnalysis.Semantics.Graph private Dictionary _labels; private List _breakTargets; private Stack _tryTargets; + private Stack _scopes = new Stack(1); + private int _index = 0; public BoundBlock/*!*/Start { get; private set; } public BoundBlock/*!*/Exit { get; private set; } @@ -39,6 +41,34 @@ namespace Pchp.CodeAnalysis.Semantics.Graph public List/*!*/DeadBlocks { get { return _deadBlocks; } } private readonly List/*!*/_deadBlocks = new List(); + #region LocalScope + + private class LocalScopeInfo + { + public BoundBlock FirstBlock => _firstblock; + private BoundBlock _firstblock; + + public LocalScopeInfo(BoundBlock firstBlock) + { + _firstblock = firstBlock; + } + } + + private void OpenScope(BoundBlock block) + { + _scopes.Push(new LocalScopeInfo(block)); + } + + private void CloseScope() + { + if (_scopes.Count == 0) + throw new InvalidOperationException(); + + _scopes.Pop().FirstBlock.ScopeTo = _index; + } + + #endregion + #region BreakTargetScope /// @@ -56,22 +86,22 @@ namespace Pchp.CodeAnalysis.Semantics.Graph } } - private BreakTargetScope GetBreakTarget(int level) + private BreakTargetScope GetBreakScope(int level) { - if (level < 1) level = 1; + if (level < 1) level = 1; // PHP behavior if (_breakTargets == null || _breakTargets.Count < level) return default(BreakTargetScope); return _breakTargets[_breakTargets.Count - level]; } - private void EnterBreakTarget(BoundBlock breakBlock, BoundBlock continueBlock) + private void OpenBreakScope(BoundBlock breakBlock, BoundBlock continueBlock) { if (_breakTargets == null) _breakTargets = new List(1); _breakTargets.Add(new BreakTargetScope(breakBlock, continueBlock)); } - private void ExitBreakTarget() + private void CloseBreakScope() { Debug.Assert(_breakTargets != null && _breakTargets.Count != 0); _breakTargets.RemoveAt(_breakTargets.Count - 1); @@ -81,21 +111,23 @@ namespace Pchp.CodeAnalysis.Semantics.Graph #region TryTargetScope - private TryCatchEdge GetTryTarget() + private TryCatchEdge CurrentTryScope { - if (_tryTargets == null || _tryTargets.Count == 0) - return null; - - return _tryTargets.Peek(); + get + { + return (_tryTargets != null && _tryTargets.Count != 0) + ? _tryTargets.Peek() + : null; + } } - private void EnterTryTarget(TryCatchEdge edge) + private void OpenTryScope(TryCatchEdge edge) { if (_tryTargets == null) _tryTargets = new Stack(); _tryTargets.Push(edge); } - private void ExitTryTarget() + private void CloeTryScope() { Debug.Assert(_tryTargets != null && _tryTargets.Count != 0); _tryTargets.Pop(); @@ -115,10 +147,14 @@ namespace Pchp.CodeAnalysis.Semantics.Graph this.Start = new StartBlock(); this.Exit = new ExitBlock(); - _current = this.Start; + _current = WithOpenScope(this.Start); statements.ForEach(this.VisitElement); _current = Connect(_current, this.Exit); + + // + WithNewOrdinal(this.Exit); + CloseScope(); } public static BuilderVisitor/*!*/Build(IList/*!*/statements, SemanticsBinder/*!*/binder) @@ -144,7 +180,7 @@ namespace Pchp.CodeAnalysis.Semantics.Graph private BoundBlock/*!*/NewBlock() { - return new BoundBlock(); + return WithNewOrdinal(new BoundBlock()); } /// @@ -153,14 +189,14 @@ namespace Pchp.CodeAnalysis.Semantics.Graph /// private BoundBlock/*!*/NewDeadBlock() { - var block = NewBlock(); + var block = new BoundBlock(); _deadBlocks.Add(block); return block; } private CatchBlock/*!*/NewBlock(CatchItem item) { - return new CatchBlock(item); + return WithNewOrdinal(new CatchBlock(item)); } private CaseBlock/*!*/NewBlock(SwitchItem item) @@ -168,7 +204,7 @@ namespace Pchp.CodeAnalysis.Semantics.Graph var caseitem = item as CaseItem; BoundExpression caseValue = // null => DefaultItem (caseitem != null) ? _binder.BindExpression(caseitem.CaseVal) : null; - return new CaseBlock(caseValue); + return WithNewOrdinal(new CaseBlock(caseValue)); } private BoundBlock/*!*/Connect(BoundBlock/*!*/source, BoundBlock/*!*/ifTarget, BoundBlock/*!*/elseTarget, Expression/*!*/condition) @@ -203,6 +239,23 @@ namespace Pchp.CodeAnalysis.Semantics.Graph return result; } + /// + /// Gets new block index. + /// + private int NewOrdinal() => _index++; + + private T WithNewOrdinal(T block) where T : BoundBlock + { + block.Ordinal = NewOrdinal(); + return block; + } + + private T WithOpenScope(T block) where T : BoundBlock + { + OpenScope(block); + return block; + } + #endregion #region Declaration Statements @@ -327,7 +380,7 @@ namespace Pchp.CodeAnalysis.Semantics.Graph var enumereeEdge = new ForeachEnumereeEdge(_current, move, _binder.BindExpression(x.Enumeree)); // ContinueTarget: - EnterBreakTarget(end, move); + OpenBreakScope(end, move); // ForeachMoveNextEdge : ConditionalEdge var moveEdge = new ForeachMoveNextEdge(move, body, end, enumereeEdge, x.KeyVariable, x.ValueVariable); @@ -337,16 +390,17 @@ namespace Pchp.CodeAnalysis.Semantics.Graph // Block // { x.Body } - _current = body; + _current = WithOpenScope(WithNewOrdinal(body)); VisitElement(x.Body); + CloseScope(); // goto ContinueTarget; Connect(_current, move); // BreakTarget: - ExitBreakTarget(); + CloseBreakScope(); // - _current = end; + _current = WithNewOrdinal(end); } private void BuildForLoop(List initExpr, List condExpr, List actionExpr, Statement/*!*/bodyStmt) @@ -363,37 +417,42 @@ namespace Pchp.CodeAnalysis.Semantics.Graph var body = NewBlock(); var cond = hasConditions ? NewBlock() : body; var action = hasActions ? NewBlock() : cond; - EnterBreakTarget(end, action); + OpenBreakScope(end, action); // while (x.Codition) { - _current = Connect(_current, cond); + _current = WithNewOrdinal(Connect(_current, cond)); if (hasConditions) { if (condExpr.Count > 1) condExpr.Take(condExpr.Count - 1).ForEach(expr => this.Add(new ExpressionStmt(expr.Span, expr))); - _current = Connect(_current, body, end, condExpr.LastOrDefault()); + _current = WithNewOrdinal(Connect(_current, body, end, condExpr.LastOrDefault())); } else { _deadBlocks.Add(end); } + OpenScope(_current); + // { x.Body } VisitElement(bodyStmt); // { x.Action } if (hasActions) { - _current = Connect(_current, action); + _current = WithNewOrdinal(Connect(_current, action)); actionExpr.ForEach(expr => this.Add(new ExpressionStmt(expr.Span, expr))); } + + CloseScope(); + // } Connect(_current, cond); // - ExitBreakTarget(); + CloseBreakScope(); // - _current = end; + _current = WithNewOrdinal(end); } public override void VisitForStmt(ForStmt x) @@ -427,7 +486,7 @@ namespace Pchp.CodeAnalysis.Semantics.Graph ? ((IntLiteral)x.Expression).Value : 1; - var brk = GetBreakTarget(level); + var brk = GetBreakScope(level); var target = (x.Type == JumpStmt.Types.Break) ? brk.BreakTarget : brk.ContinueTarget; if (target != null) { @@ -466,26 +525,34 @@ namespace Pchp.CodeAnalysis.Semantics.Graph Debug.Assert(i != 0 && elseBlock != null); var body = elseBlock; elseBlock = end; // last ConditionalStmt - _current = Connect(_current, body); + _current = WithNewOrdinal(Connect(_current, body)); } + OpenScope(_current); VisitElement(cond.Statement); + CloseScope(); + Connect(_current, end); - _current = elseBlock; + _current = WithNewOrdinal(elseBlock); } Debug.Assert(_current == end); + _current.Ordinal = NewOrdinal(); } public override void VisitLabelStmt(LabelStmt x) { var/*!*/label = GetLabelBlock(x.Name.Value); if ((label.Flags & ControlFlowGraph.LabelBlockFlags.Defined) != 0) + { label.Flags |= ControlFlowGraph.LabelBlockFlags.Redefined; // label was defined already + return; // ignore label redefinition + } + label.Flags |= ControlFlowGraph.LabelBlockFlags.Defined; // label is defined label.LabelSpan = x.Span; - _current = Connect(_current, label.TargetBlock); + _current = WithNewOrdinal(Connect(_current, label.TargetBlock)); Add(x); } @@ -513,19 +580,23 @@ namespace Pchp.CodeAnalysis.Semantics.Graph // SwitchEdge // Connects _current to cases var edge = new SwitchEdge(_current, _binder.BindExpression(x.SwitchValue), cases.ToArray()); - _current = cases[0]; + _current = WithNewOrdinal(cases[0]); - EnterBreakTarget(end, end); // NOTE: inside switch, Continue ~ Break + OpenBreakScope(end, end); // NOTE: inside switch, Continue ~ Break for (int i = 0; i < cases.Count; i++) { + OpenScope(_current); + if (i < items.Length) items[i].Statements.ForEach(VisitElement); // any break will connect block to end - _current = Connect(_current, (i == cases.Count - 1) ? end : cases[i + 1]); + CloseScope(); + + _current = WithNewOrdinal(Connect(_current, (i == cases.Count - 1) ? end : cases[i + 1])); } - ExitBreakTarget(); + CloseBreakScope(); Debug.Assert(_current == end); } @@ -588,29 +659,34 @@ namespace Pchp.CodeAnalysis.Semantics.Graph var edge = new TryCatchEdge(_current, body, catchBlocks, finallyBlock); // build try body - EnterTryTarget(edge); - _current = body; + OpenTryScope(edge); + OpenScope(body); + _current = WithNewOrdinal(body); x.Statements.ForEach(VisitElement); - ExitTryTarget(); + CloseScope(); + CloeTryScope(); _current = Connect(_current, finallyBlock ?? end); // built catches for (int i = 0; i < catchBlocks.Length; i++) { - _current = catchBlocks[i]; + _current = WithOpenScope(WithNewOrdinal(catchBlocks[i])); x.Catches[i].Statements.ForEach(VisitElement); + CloseScope(); _current = Connect(_current, finallyBlock ?? end); } // build finally if (finallyBlock != null) { - _current = finallyBlock; + _current = WithOpenScope(WithNewOrdinal(finallyBlock)); x.FinallyItem.Statements.ForEach(VisitElement); + CloseScope(); _current = Connect(_current, end); } // _current == end + _current.Ordinal = NewOrdinal(); } public override void VisitWhileStmt(WhileStmt x) @@ -619,14 +695,16 @@ namespace Pchp.CodeAnalysis.Semantics.Graph { var end = NewBlock(); var body = NewBlock(); - EnterBreakTarget(end, body); - _current = Connect(_current, body); + OpenBreakScope(end, body); + _current = WithOpenScope(Connect(_current, body)); // do { VisitElement(x.Body); Connect(_current, body, end, x.CondExpr); // } while (x.CondExpr) - ExitBreakTarget(); - _current = end; + CloseScope(); + CloseBreakScope(); + + _current = WithNewOrdinal(end); } else if (x.LoopType == WhileStmt.Type.While) { diff --git a/src/Compiler/PhpCodeAnalysis/Semantics/Graph/Edge.cs b/src/Compiler/PhpCodeAnalysis/Semantics/Graph/Edge.cs index e5bfe5807..f0447b287 100644 --- a/src/Compiler/PhpCodeAnalysis/Semantics/Graph/Edge.cs +++ b/src/Compiler/PhpCodeAnalysis/Semantics/Graph/Edge.cs @@ -85,6 +85,7 @@ namespace Pchp.CodeAnalysis.Semantics.Graph internal SimpleEdge(BoundBlock source, BoundBlock target) : base(source) { + Debug.Assert(source != target); _target = target; Connect(source); }