- graph builder
- block, edge
- graph visitor
This commit is contained in:
Jakub Míšek 2016-02-23 01:41:29 +01:00
Родитель 6cb9b1b306
Коммит 4b137d80ad
11 изменённых файлов: 1575 добавлений и 23 удалений

Просмотреть файл

@ -22,7 +22,7 @@ namespace Pchp.CodeAnalysis
readonly PEModuleBuilder _moduleBuilder;
readonly bool _emittingPdb;
readonly DiagnosticBag _diagnostics;
readonly Worklist _worklist;
readonly Worklist<BoundBlock> _worklist;
// readonly CallGraph _callgraph; //keeps graph of what methods call specific method // used to reanalyze caller methods when return type ot arg. type changes
private SourceCompiler(PhpCompilation compilation, PEModuleBuilder moduleBuilder, bool emittingPdb, DiagnosticBag diagnostics)
@ -36,7 +36,7 @@ namespace Pchp.CodeAnalysis
_emittingPdb = emittingPdb;
_diagnostics = diagnostics;
_worklist = new Worklist(); // parallel worklist algorithm
_worklist = new Worklist<BoundBlock>(AnalyzeMethod); // parallel worklist algorithm
// semantic model
}
@ -62,19 +62,14 @@ namespace Pchp.CodeAnalysis
internal void AnalyzeMethods()
{
//this.WalkMethods(m => worklist.Enlist(m, this.AnalyzeMethod))
//worklist.Do
// DEBUG
this.WalkMethods(this.AnalyzeMethod);
this.WalkMethods(m => _worklist.Enqueue(BindMethod(m)));
_worklist.DoAll();
}
private void AnalyzeMethod(SourceBaseMethodSymbol method)
private void AnalyzeMethod(BoundBlock method)
{
Contract.ThrowIfNull(method);
var bound = method.BoundBlock;
// Initial State // declared locals, initial types
// TypeAnalysis + ResolveSymbols
// if (LowerBody(bound)) Enlist(method)

Просмотреть файл

@ -12,11 +12,66 @@ namespace Pchp.CodeAnalysis.FlowAnalysis
/// <summary>
/// Queue of work items to do.
/// </summary>
internal class Worklist
internal class Worklist<T>
{
readonly object _syncRoot = new object();
/// <summary>
/// Action performed on blocks.
/// </summary>
readonly Action<T> _analyzer;
//public event EventHandler MethodDone;
///// <summary>
///// Set of blocks being analyzed.
///// Used for recursion prevention.
///// </summary>
//readonly HashSet<T> _pending;
/// <summary>
/// List of blocks to be processed.
/// </summary>
readonly DistinctQueue<BoundBlock> _queue = new DistinctQueue<BoundBlock>();
readonly DistinctQueue<T> _queue = new DistinctQueue<T>();
public Worklist(Action<T> analyzer)
{
Contract.ThrowIfNull(analyzer);
_analyzer = analyzer;
}
/// <summary>
/// Adds block to the queue.
/// </summary>
public void Enqueue(T block)
{
_queue.Enqueue(block);
}
/// <summary>
/// Processes all tasks until the queue is not empty.
/// </summary>
public void DoAll()
{
for (; DoNext();) ;
}
/// <summary>
/// Pop next item from the queue and process it.
/// </summary>
/// <returns><c>true</c> if there was an item, otherwise <c>false</c>.</returns>
public bool DoNext()
{
T block;
if (!_queue.TryDequeue(out block))
return false;
//
_analyzer(block);
//
return true;
}
}
}

Просмотреть файл

@ -59,6 +59,11 @@
<Compile Include="FlowAnalysis\Worklist.cs" />
<Compile Include="Semantics\BoundMethodBody.cs" />
<Compile Include="Semantics\Expressions.cs" />
<Compile Include="Semantics\Graph\Block.cs" />
<Compile Include="Semantics\Graph\BuilderVisitor.cs" />
<Compile Include="Semantics\Graph\ControlFlowGraph.cs" />
<Compile Include="Semantics\Graph\Edge.cs" />
<Compile Include="Semantics\Graph\GraphVisitor.cs" />
<Compile Include="Semantics\SemanticsBinder.cs" />
<Compile Include="Semantics\Statements.cs" />
<Compile Include="CodeGen\MethodGenerator.cs" />

Просмотреть файл

@ -0,0 +1,189 @@
using Microsoft.CodeAnalysis.Semantics;
using Pchp.Syntax;
using Pchp.Syntax.AST;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using System;
using System.Collections.Immutable;
namespace Pchp.CodeAnalysis.Semantics.Graph
{
/// <summary>
/// Represents control flow block.
/// </summary>
[DebuggerDisplay("Block")]
public class Block : AstNode, IBlockStatement
{
readonly List<BoundStatement>/*!*/_statements;
Edge _next;
/// <summary>
/// Tag used for graph algorithms.
/// </summary>
public int Tag { get { return _tag; } set { _tag = value; } }
private int _tag;
/// <summary>
/// Gets statements contained in this block.
/// </summary>
public List<BoundStatement>/*!!*/Statements => _statements;
/// <summary>
/// Gets edge pointing out of this block.
/// </summary>
public Edge NextEdge
{
get { return _next; }
internal set { _next = value; }
}
internal Block()
{
_statements = new List<BoundStatement>();
}
/// <summary>
/// Adds statement to the block.
/// </summary>
internal void AddStatement(BoundStatement stmt)
{
Contract.ThrowIfNull(stmt);
_statements.Add(stmt);
}
/// <summary>
/// Traverses empty blocks to their non-empty successor. Skips duplicities.
/// </summary>
internal static List<Block>/*!*/SkipEmpty(IEnumerable<Block>/*!*/blocks)
{
Contract.ThrowIfNull(blocks);
var result = new HashSet<Block>();
foreach (var x in blocks)
{
var block = x;
while (block != null && block.GetType() == typeof(Block) && block.Statements.Count == 0)
{
var edge = block.NextEdge as SimpleEdge;
if (edge != null || block.NextEdge == null)
{
block = (edge != null && edge.Target != block) ? edge.Target : null;
}
else
{
break;
}
}
if (block != null)
{
result.Add(block);
}
}
//
return result.ToList();
}
public virtual void Accept(GraphVisitor visitor) => visitor.VisitCFGBlock(this);
#region IBlockStatement
ImmutableArray<IStatement> IBlockStatement.Statements => _statements.AsImmutable<IStatement>();
ImmutableArray<ILocalSymbol> IBlockStatement.Locals => Locals;
protected virtual ImmutableArray<ILocalSymbol> Locals => ImmutableArray<ILocalSymbol>.Empty;
OperationKind IOperation.Kind => OperationKind.BlockStatement;
bool IOperation.IsInvalid => false;
SyntaxNode IOperation.Syntax => null;
void IOperation.Accept(OperationVisitor visitor)
=> visitor.VisitBlockStatement(this);
TResult IOperation.Accept<TArgument, TResult>(OperationVisitor<TArgument, TResult> visitor, TArgument argument)
=> visitor.VisitBlockStatement(this, argument);
#endregion
}
/// <summary>
/// Represents a start block.
/// </summary>
[DebuggerDisplay("Start")]
public sealed class StartBlock : Block
{
}
/// <summary>
/// Represents an exit block.
/// </summary>
[DebuggerDisplay("Exit")]
public sealed class ExitBlock : Block
{
}
/// <summary>
/// Represents control flow block of catch item.
/// </summary>
[DebuggerDisplay("CatchBlock({ClassName.QualifiedName})")]
public class CatchBlock : Block
{
/// <summary>
/// Catch variable type.
/// </summary>
public DirectTypeRef TypeRef { get { return _typeRef; } }
private readonly DirectTypeRef _typeRef;
/// <summary>
/// A variable where an exception is assigned in.
/// </summary>
public VariableName VariableName { get { return _variableName; } }
private readonly VariableName _variableName;
public CatchBlock(CatchItem item)
: base()
{
_typeRef = item.TypeRef;
_variableName = item.Variable.VarName;
}
public override void Accept(GraphVisitor visitor) => visitor.VisitCFGCatchBlock(this);
}
/// <summary>
/// Represents control flow block of case item.
/// </summary>
[DebuggerDisplay("CaseBlock")]
public class CaseBlock : Block
{
/// <summary>
/// Gets case value expression. In case of default item, returns <c>null</c>.
/// </summary>
public BoundExpression CaseValue { get { return _caseValue; } }
private readonly BoundExpression _caseValue;
/// <summary>
/// Gets value indicating whether the case represents a default.
/// </summary>
public bool IsDefault => _caseValue == null;
public CaseBlock(SwitchItem item)
: base()
{
var caseItem = item as CaseItem;
_caseValue = (caseItem != null)
? SemanticsBinder.BindExpression(caseItem.CaseVal)
: null; // DefaultItem has no value.
}
public override void Accept(GraphVisitor visitor) => visitor.VisitCFGCaseBlock(this);
}
}

Просмотреть файл

@ -0,0 +1,635 @@
using Pchp.Syntax;
using Pchp.Syntax.AST;
using Pchp.Syntax.Text;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pchp.CodeAnalysis.Semantics.Graph
{
/// <summary>
/// Visitor implementation that constructs the graph.
/// </summary>
internal sealed class BuilderVisitor : TreeVisitor
{
private Block/*!*/_current;
private Dictionary<string, ControlFlowGraph.LabelBlockState> _labels;
private List<BreakTargetScope> _breakTargets;
private Stack<TryCatchEdge> _tryTargets;
public Block/*!*/Start { get; private set; }
public Block/*!*/Exit { get; private set; }
public Block Exception { get; private set; }
/// <summary>
/// Gets labels defined within the routine.
/// </summary>
public ControlFlowGraph.LabelBlockState[] Labels
{
get { return (_labels != null) ? _labels.Values.ToArray() : EmptyArray<ControlFlowGraph.LabelBlockState>.Instance; }
}
/// <summary>
/// Blocks we know nothing is pointing to (right after jump, throw, etc.).
/// </summary>
public List<Block>/*!*/DeadBlocks { get { return _deadBlocks; } }
private readonly List<Block>/*!*/_deadBlocks = new List<Block>();
#region BreakTargetScope
/// <summary>
/// Represents break scope.
/// </summary>
private struct BreakTargetScope
{
public readonly Block/*!*/BreakTarget;
public readonly Block/*!*/ContinueTarget;
public BreakTargetScope(Block breakBlock, Block continueBlock)
{
BreakTarget = breakBlock;
ContinueTarget = continueBlock;
}
}
private BreakTargetScope GetBreakTarget(int level)
{
if (level < 1) level = 1;
if (_breakTargets == null || _breakTargets.Count < level)
return default(BreakTargetScope);
return _breakTargets[_breakTargets.Count - level];
}
private void EnterBreakTarget(Block breakBlock, Block continueBlock)
{
if (_breakTargets == null) _breakTargets = new List<BreakTargetScope>(1);
_breakTargets.Add(new BreakTargetScope(breakBlock, continueBlock));
}
private void ExitBreakTarget()
{
Debug.Assert(_breakTargets != null && _breakTargets.Count != 0);
_breakTargets.RemoveAt(_breakTargets.Count - 1);
}
#endregion
#region TryTargetScope
private TryCatchEdge GetTryTarget()
{
if (_tryTargets == null || _tryTargets.Count == 0)
return null;
return _tryTargets.Peek();
}
private void EnterTryTarget(TryCatchEdge edge)
{
if (_tryTargets == null) _tryTargets = new Stack<TryCatchEdge>();
_tryTargets.Push(edge);
}
private void ExitTryTarget()
{
Debug.Assert(_tryTargets != null && _tryTargets.Count != 0);
_tryTargets.Pop();
}
#endregion
#region Construction
private BuilderVisitor(IList<Statement>/*!*/statements)
{
Contract.ThrowIfNull(statements);
this.Start = new StartBlock();
this.Exit = new ExitBlock();
_current = this.Start;
statements.ForEach(this.VisitElement);
_current = Connect(_current, this.Exit);
}
public static BuilderVisitor/*!*/Build(IList<Statement>/*!*/statements)
{
return new BuilderVisitor(statements);
}
#endregion
#region Helper Methods
private Block/*!*/GetExceptionBlock()
{
if (this.Exception == null)
this.Exception = new ExitBlock();
return this.Exception;
}
private void Add(Statement stmt)
{
_current.AddStatement(SemanticsBinder.BindStatement(stmt));
}
private Block/*!*/NewBlock()
{
return new Block();
}
/// <summary>
/// Creates block we know nothing is pointing to.
/// Such block will be analysed later whether it is empty or whether it contains some statements (which will be reported as unreachable).
/// </summary>
private Block/*!*/NewDeadBlock()
{
var block = NewBlock();
_deadBlocks.Add(block);
return block;
}
private CatchBlock/*!*/NewBlock(CatchItem item)
{
return new CatchBlock(item);
}
private CaseBlock/*!*/NewBlock(SwitchItem item)
{
return new CaseBlock(item);
}
private Block/*!*/Connect(Block/*!*/source, Block/*!*/ifTarget, Block/*!*/elseTarget, Expression/*!*/condition)
{
new ConditionalEdge(source, ifTarget, elseTarget, SemanticsBinder.BindExpression(condition));
return ifTarget;
}
private Block/*!*/Connect(Block/*!*/source, Block/*!*/target)
{
new SimpleEdge(source, target);
return target;
}
private ControlFlowGraph.LabelBlockState/*!*/GetLabelBlock(string label)
{
if (_labels == null)
_labels = new Dictionary<string, ControlFlowGraph.LabelBlockState>(StringComparer.Ordinal); // goto is case sensitive
ControlFlowGraph.LabelBlockState result;
if (!_labels.TryGetValue(label, out result))
{
_labels[label] = result = new ControlFlowGraph.LabelBlockState()
{
Block = NewBlock(),
GotoSpan = Span.Invalid,
LabelSpan = Span.Invalid,
Label = label,
Flags = ControlFlowGraph.LabelBlockFlags.None,
};
}
return result;
}
#endregion
#region Declaration Statements
public override void VisitTypeDecl(TypeDecl x)
{
if (x.IsConditional)
{
Add(x);
}
// ignored
}
public override void VisitMethodDecl(MethodDecl x)
{
// ignored
}
public override void VisitConstDeclList(ConstDeclList x)
{
// ignored
}
public override void VisitFunctionDecl(FunctionDecl x)
{
if (x.IsConditional)
{
Add(x);
}
// ignored
}
#endregion
#region Flow-Thru Statements
public override void VisitEmptyStmt(EmptyStmt x)
{
// ignored
}
public override void VisitEchoStmt(EchoStmt x)
{
Add(x);
}
public override void VisitBlockStmt(BlockStmt x)
{
base.VisitBlockStmt(x); // visit nested statements
}
public override void VisitDeclareStmt(DeclareStmt x)
{
Add(x);
}
public override void VisitGlobalCode(GlobalCode x)
{
throw new InvalidOperationException();
}
public override void VisitGlobalStmt(GlobalStmt x)
{
Add(x);
}
public override void VisitStaticStmt(StaticStmt x)
{
Add(x);
}
public override void VisitExpressionStmt(ExpressionStmt x)
{
Add(x);
VisitElement(x.Expression as ExitEx);
}
public override void VisitUnsetStmt(UnsetStmt x)
{
Add(x);
}
public override void VisitPHPDocStmt(PHPDocStmt x)
{
if (x.GetProperty<LangElement>() == null)
{
// if PHPDoc is not associated with any declaration yet
Add(x);
}
}
#endregion
#region Conditional Statements
public override void VisitConditionalStmt(ConditionalStmt x)
{
throw new InvalidOperationException(); // should be handled by IfStmt
}
public override void VisitExitEx(ExitEx x)
{
// NOTE: Added by VisitExpressionStmt already
// NOTE: similar to ThrowEx but unhandleable
// connect to Exception block
Connect(_current, this.GetExceptionBlock()); // unreachable
_current = NewDeadBlock();
}
public override void VisitForeachStmt(ForeachStmt x)
{
var end = NewBlock();
var move = NewBlock();
var body = NewBlock();
// _current -> move -> body -> move -> ...
// ForeachEnumereeEdge : SimpleEdge
// x.Enumeree.GetEnumerator();
var enumereeEdge = new ForeachEnumereeEdge(_current, move, SemanticsBinder.BindExpression(x.Enumeree));
// ContinueTarget:
EnterBreakTarget(end, move);
// ForeachMoveNextEdge : ConditionalEdge
var moveEdge = new ForeachMoveNextEdge(move, body, end, enumereeEdge, x.KeyVariable, x.ValueVariable);
// while (enumerator.MoveNext()) {
// var key = enumerator.Current.Key
// var value = enumerator.Current.Value
// Block
// { x.Body }
_current = body;
VisitElement(x.Body);
// goto ContinueTarget;
Connect(_current, move);
// BreakTarget:
ExitBreakTarget();
//
_current = end;
}
private void BuildForLoop(List<Expression> initExpr, List<Expression> condExpr, List<Expression> actionExpr, Statement/*!*/bodyStmt)
{
var end = NewBlock();
bool hasActions = actionExpr != null && actionExpr.Count != 0;
bool hasConditions = condExpr != null && condExpr.Count != 0;
// { initializer }
if (initExpr != null && initExpr.Count != 0)
initExpr.ForEach(expr => this.Add(new ExpressionStmt(expr.Span, expr)));
var body = NewBlock();
var cond = hasConditions ? NewBlock() : body;
var action = hasActions ? NewBlock() : cond;
EnterBreakTarget(end, action);
// while (x.Codition) {
_current = 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());
}
else
{
_deadBlocks.Add(end);
}
// { x.Body }
VisitElement(bodyStmt);
// { x.Action }
if (hasActions)
{
_current = Connect(_current, action);
actionExpr.ForEach(expr => this.Add(new ExpressionStmt(expr.Span, expr)));
}
// }
Connect(_current, cond);
//
ExitBreakTarget();
//
_current = end;
}
public override void VisitForStmt(ForStmt x)
{
BuildForLoop(x.InitExList, x.CondExList, x.ActionExList, x.Body);
}
public override void VisitGotoStmt(GotoStmt x)
{
Add(x);
var/*!*/label = GetLabelBlock(x.LabelName.Value);
label.Flags |= ControlFlowGraph.LabelBlockFlags.Used; // label is used
label.GotoSpan = x.Span;
Connect(_current, label.Block);
_current = NewDeadBlock(); // any statement inside this block would be unreachable unless it is LabelStmt
}
public override void VisitJumpStmt(JumpStmt x)
{
Add(x);
if (x.Type == JumpStmt.Types.Return)
{
Connect(_current, this.Exit);
}
else if (x.Type == JumpStmt.Types.Break || x.Type == JumpStmt.Types.Continue)
{
int level = (x.Expression is IntLiteral)
? ((IntLiteral)x.Expression).Value
: 1;
var brk = GetBreakTarget(level);
var target = (x.Type == JumpStmt.Types.Break) ? brk.BreakTarget : brk.ContinueTarget;
if (target != null)
{
Connect(_current, target);
}
else
{
Connect(_current, this.GetExceptionBlock()); // unreachable // fatal error in PHP
}
}
else
{
throw new InvalidOperationException();
}
_current = NewDeadBlock(); // anything after these statements is unreachable
}
public override void VisitIfStmt(IfStmt x)
{
var end = NewBlock();
var conditions = x.Conditions;
Debug.Assert(conditions.Count != 0);
Block elseBlock = null;
for (int i = 0; i < conditions.Count; i++)
{
var cond = conditions[i];
if (cond.Condition != null) // if (Condition) ...
{
elseBlock = (i == conditions.Count - 1) ? end : NewBlock();
_current = Connect(_current, NewBlock(), elseBlock, cond.Condition);
}
else // else ...
{
Debug.Assert(i != 0 && elseBlock != null);
var body = elseBlock;
elseBlock = end; // last ConditionalStmt
_current = Connect(_current, body);
}
VisitElement(cond.Statement);
Connect(_current, end);
_current = elseBlock;
}
Debug.Assert(_current == end);
}
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
label.Flags |= ControlFlowGraph.LabelBlockFlags.Defined; // label is defined
label.LabelSpan = x.Span;
_current = Connect(_current, label.Block);
Add(x);
}
public override void VisitSwitchStmt(SwitchStmt x)
{
var items = x.SwitchItems;
if (items == null || items.Length == 0)
return;
var end = NewBlock();
bool hasDefault = false;
var cases = new List<CaseBlock>(items.Length);
for (int i = 0; i < items.Length; i++)
{
cases.Add(NewBlock(items[i]));
hasDefault |= (items[i] is DefaultItem);
}
if (!hasDefault)
{
// create implicit default:
cases.Add(NewBlock(new DefaultItem(x.Span, EmptyArray<Statement>.Instance)));
}
// SwitchEdge // Connects _current to cases
var edge = new SwitchEdge(_current, SemanticsBinder.BindExpression(x.SwitchValue), cases.ToArray());
_current = cases[0];
EnterBreakTarget(end, end); // NOTE: inside switch, Continue ~ Break
for (int i = 0; i < cases.Count; i++)
{
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]);
}
ExitBreakTarget();
Debug.Assert(_current == end);
}
public override void VisitThrowStmt(ThrowStmt x)
{
Add(x);
//var tryedge = GetTryTarget();
//if (tryedge != null)
//{
// // find handling catch block
// QualifiedName qname;
// var newex = x.Expression as NewEx;
// if (newex != null && newex.ClassNameRef is DirectTypeRef)
// {
// qname = ((DirectTypeRef)newex.ClassNameRef).ClassName;
// }
// else
// {
// qname = new QualifiedName(Name.EmptyBaseName);
// }
// CatchBlock handlingCatch = tryedge.HandlingCatch(qname);
// if (handlingCatch != null)
// {
// // throw jumps to a catch item in runtime
// }
//}
// connect to Exception block
Connect(_current, this.GetExceptionBlock());
_current = NewDeadBlock(); // unreachable
}
public override void VisitTryStmt(TryStmt x)
{
// try {
// x.Body
// }
// catch (E1) { body }
// catch (E2) { body }
// finally { body }
// end
var end = NewBlock();
var body = NewBlock();
// init catch blocks and finally block
var catchBlocks = new CatchBlock[(x.Catches == null) ? 0 : x.Catches.Length];
Block finallyBlock = null;
for (int i = 0; i < catchBlocks.Length; i++)
catchBlocks[i] = NewBlock(x.Catches[i]);
if (x.FinallyItem != null)
finallyBlock = NewBlock();
// TryCatchEdge // Connects _current to body, catch blocks and finally
var edge = new TryCatchEdge(_current, body, catchBlocks, finallyBlock);
// build try body
EnterTryTarget(edge);
_current = body;
x.Statements.ForEach(VisitElement);
ExitTryTarget();
_current = Connect(_current, finallyBlock ?? end);
// built catches
for (int i = 0; i < catchBlocks.Length; i++)
{
_current = catchBlocks[i];
x.Catches[i].Statements.ForEach(VisitElement);
_current = Connect(_current, finallyBlock ?? end);
}
// build finally
if (finallyBlock != null)
{
_current = finallyBlock;
x.FinallyItem.Statements.ForEach(VisitElement);
_current = Connect(_current, end);
}
// _current == end
}
public override void VisitWhileStmt(WhileStmt x)
{
if (x.LoopType == WhileStmt.Type.Do)
{
var end = NewBlock();
var body = NewBlock();
EnterBreakTarget(end, body);
_current = Connect(_current, body);
// do {
VisitElement(x.Body);
Connect(_current, body, end, x.CondExpr);
// } while (x.CondExpr)
ExitBreakTarget();
_current = end;
}
else if (x.LoopType == WhileStmt.Type.While)
{
Debug.Assert(x.CondExpr != null);
BuildForLoop(null, new List<Expression>(1) { x.CondExpr }, null, x.Body);
}
}
#endregion
}
}

Просмотреть файл

@ -0,0 +1,156 @@
using Pchp.Syntax.AST;
using Pchp.Syntax.Text;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pchp.CodeAnalysis.Semantics.Graph
{
/// <summary>
/// Represents statements control flow graph.
/// </summary>
public sealed class ControlFlowGraph : AstNode
{
#region LabelBlockFlags, LabelBlockInfo
/// <summary>
/// Found label reference (definition or target) information.
/// </summary>
[Flags]
public enum LabelBlockFlags : byte
{
/// <summary>
/// Not used nor defined.
/// </summary>
None = 0,
/// <summary>
/// Label is defined.
/// </summary>
Defined = 1,
/// <summary>
/// Label is used as a target.
/// </summary>
Used = 2,
/// <summary>
/// Label was defined twice or more.
/// </summary>
Redefined = 4,
}
/// <summary>
/// Label state.
/// </summary>
public sealed class LabelBlockState
{
/// <summary>
/// Label identifier.
/// </summary>
public string Label;
/// <summary>
/// Positions of label definition and last label use.
/// </summary>
public Span GotoSpan, LabelSpan;
/// <summary>
/// Lable target block.
/// </summary>
public Block Block;
/// <summary>
/// Label information.
/// </summary>
public LabelBlockFlags Flags;
}
#endregion
#region Fields & Properties
/// <summary>
/// Gets the control flow start block. Cannot be <c>null</c>.
/// </summary>
public Block/*!*/Start { get { return _start; } }
readonly Block/*!*/_start;
/// <summary>
/// Gets the control flow exit block. Cannot be <c>null</c>.
/// </summary>
public Block/*!*/Exit { get { return _exit; } }
readonly Block/*!*/_exit;
/// <summary>
/// Exception block. Can be <c>null</c>.
/// If set, code can throw an exception or be terminated by call to <c>exit</c>, before reaching exit block.
/// This block is connected with blocks ending with <c>throw</c> statement.
/// </summary>
public Block Throws { get { return _exception; } }
readonly Block _exception;
/// <summary>
/// Array of labels within routine. Can be <c>null</c>.
/// </summary>
public LabelBlockState[] Labels { get { return _labels; } }
readonly LabelBlockState[] _labels;
/// <summary>
/// List of blocks that are unreachable syntactically (statements after JumpStmt etc.).
/// </summary>
public List<Block>/*!*/UnreachableBlocks { get { return _unrecachable; } }
readonly List<Block>/*!*/_unrecachable;
/// <summary>
/// Last "tag" color used. Used internally for graph algorithms.
/// </summary>
int _lastcolor = 0;
#endregion
#region Construction
public ControlFlowGraph(IList<Statement>/*!*/statements)
: this(BuilderVisitor.Build(statements))
{
}
private ControlFlowGraph(BuilderVisitor/*!*/builder)
: this(builder.Start, builder.Exit, builder.Exception, builder.Labels, builder.DeadBlocks)
{
}
private ControlFlowGraph(Block/*!*/start, Block/*!*/exit, Block exception, LabelBlockState[] labels, List<Block> unreachable)
{
Contract.ThrowIfNull(start);
Contract.ThrowIfNull(exit);
_start = start;
_exit = exit;
_exception = exception;
_labels = labels;
_unrecachable = unreachable ?? new List<Block>();
}
#endregion
/// <summary>
/// Gets new (unique) color for use by graph algorithms.
/// </summary>
/// <returns>New color index.</returns>
public int NewColor()
{
return unchecked(++_lastcolor);
}
/// <summary>
/// Visits control flow blocks and contained statements, in deep.
/// Unreachable blocks are not visited.
/// </summary>
/// <remarks>Visitor does not implement infinite recursion prevention.</remarks>
public void Visit(GraphVisitor/*!*/visitor) => visitor.VisitCFG(this);
}
}

Просмотреть файл

@ -0,0 +1,364 @@
using Microsoft.CodeAnalysis.Semantics;
using Pchp.Syntax;
using Pchp.Syntax.AST;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pchp.CodeAnalysis.Semantics.Graph
{
/// <summary>
/// Represents edge between to graph blocks.
/// </summary>
public abstract class Edge : AstNode
{
/// <summary>
/// Properties key as a recursion prevention when visiting edges recursively.
/// </summary>
protected static readonly object/*!*/RecursionLockKey = new object();
/// <summary>
/// Target blocks.
/// </summary>
public abstract IEnumerable<Block>/*!!*/Targets { get; }
/// <summary>
/// Gets value indicating whether the edge represents a conditional edge.
/// </summary>
public virtual bool IsConditional => false;
/// <summary>
/// Gets value indicating whether the edge represents try/catch.
/// </summary>
public virtual bool IsTryCatch => false;
/// <summary>
/// Gets value indicating whether the edge represents switch.
/// </summary>
public virtual bool IsSwitch => false;
/// <summary>
/// Condition expression of conditional edge.
/// </summary>
public virtual BoundExpression Condition => null;
/// <summary>
/// Catch blocks if try/catch edge.
/// </summary>
public virtual CatchBlock[] CatchBlocks => EmptyArray<CatchBlock>.Instance;
/// <summary>
/// Finally block of try/catch edge.
/// </summary>
public virtual Block FinallyBlock => null;
/// <summary>
/// Enumeration with single case blocks.
/// </summary>
public virtual CaseBlock[] CaseBlocks => EmptyArray<CaseBlock>.Instance;
internal Edge(Block/*!*/source)
{
Contract.ThrowIfNull(source);
}
protected void Connect(Block/*!*/source)
{
source.NextEdge = this;
}
/// <summary>
/// Visits the object by given visitor.
/// </summary>
public abstract void Visit(GraphVisitor visitor);
}
/// <summary>
/// Represents simple unconditional jump.
/// </summary>
[DebuggerDisplay("SimpleEdge")]
public class SimpleEdge : Edge
{
/// <summary>
/// Gets the target block if the simple edge.
/// </summary>
public Block Target { get { return _target; } }
private readonly Block _target;
internal SimpleEdge(Block source, Block target)
: base(source)
{
_target = target;
Connect(source);
}
/// <summary>
/// Target blocks.
/// </summary>
public override IEnumerable<Block> Targets => new Block[] { _target };
/// <summary>
/// Visits the object by given visitor.
/// </summary>
public override void Visit(GraphVisitor visitor) => visitor.VisitCFGSimpleEdge(this);
}
/// <summary>
/// Conditional edge.
/// </summary>
[DebuggerDisplay("ConditionalEdge")]
public sealed class ConditionalEdge : Edge
{
private readonly Block _true, _false;
private readonly BoundExpression _condition;
/// <summary>
/// Target true block
/// </summary>
public Block/*!*/TrueTarget => _true;
/// <summary>
/// Target false block.
/// </summary>
public Block/*!*/FalseTarget => _false;
internal ConditionalEdge(Block source, Block @true, Block @false, BoundExpression cond)
: base(source)
{
Debug.Assert(@true != @false);
_true = @true;
_false = @false;
_condition = cond;
Connect(source);
}
/// <summary>
/// All target blocks.
/// </summary>
public override IEnumerable<Block> Targets => new Block[] { _true, _false };
public override bool IsConditional
{
get { return true; }
}
public override BoundExpression Condition => _condition;
/// <summary>
/// Visits the object by given visitor.
/// </summary>
public override void Visit(GraphVisitor visitor) => visitor.VisitCFGConditionalEdge(this);
}
/// <summary>
/// Represents try/catch edge.
/// </summary>
[DebuggerDisplay("TryCatchEdge")]
public sealed class TryCatchEdge : Edge
{
private readonly Block _body;
private readonly CatchBlock[] _catchBlocks;
private readonly Block _finallyBlock;
/// <summary>
/// Try block.
/// </summary>
public Block BodyBlock => _body;
/// <summary>
/// Whether the given class name is equal to <c>Exception</c>.
/// </summary>
private static bool IsExceptionClassName(DirectTypeRef tref)
{
return
tref.GenericParams.Count == 0 &&
tref.ClassName == NameUtils.SpecialNames.Exception;
}
internal CatchBlock HandlingCatch(QualifiedName exceptionClassName)
{
foreach (var block in _catchBlocks)
if (block.TypeRef.ClassName == exceptionClassName || IsExceptionClassName(block.TypeRef))
return block;
return null;
}
internal TryCatchEdge(Block source, Block body, CatchBlock[] catchBlocks, Block finallyBlock)
: base(source)
{
_body = body;
_catchBlocks = catchBlocks;
_finallyBlock = finallyBlock;
Connect(source);
}
/// <summary>
/// All target blocks.
/// </summary>
public override IEnumerable<Block> Targets
{
get
{
var list = new List<Block>(_catchBlocks.Length + 2);
list.Add(_body);
list.AddRange(_catchBlocks);
if (_finallyBlock != null)
list.Add(_finallyBlock);
return list;
}
}
public override bool IsTryCatch => true;
public override CatchBlock[] CatchBlocks => _catchBlocks;
public override Block FinallyBlock => _finallyBlock;
/// <summary>
/// Visits the object by given visitor.
/// </summary>
public override void Visit(GraphVisitor visitor)
{
visitor.VisitCFGTryCatchEdge(this);
}
}
/// <summary>
/// Represents foreach edge through the enumeree invocation.
/// </summary>
[DebuggerDisplay("ForeachEnumeree")]
public sealed class ForeachEnumereeEdge : SimpleEdge
{
/// <summary>
/// Array to enumerate through.
/// </summary>
public BoundExpression Enumeree => _enumeree;
private readonly BoundExpression _enumeree;
internal ForeachEnumereeEdge(Block/*!*/source, Block/*!*/target, BoundExpression/*!*/enumeree)
: base(source, target)
{
Contract.ThrowIfNull(enumeree);
_enumeree = enumeree;
}
/// <summary>
/// Visits the object by given visitor.
/// </summary>
public override void Visit(GraphVisitor visitor)
{
visitor.VisitCFGForeachEnumereeEdge(this);
}
}
/// <summary>
/// Represents foreach edge from enumeree invocation through <c>MoveNext</c> to body block or end.
/// </summary>
[DebuggerDisplay("ForeachMoveNextEdge")]
public sealed class ForeachMoveNextEdge : Edge
{
/// <summary>
/// Content of the foreach.
/// </summary>
public Block BodyBlock => _body;
/// <summary>
/// Block after the foreach.
/// </summary>
public Block EndBlock => _end;
readonly Block _body, _end;
/// <summary>
/// Reference to the edge defining the enumeree.
/// </summary>
public ForeachEnumereeEdge EnumereeEdge => _enumereeEdge;
readonly ForeachEnumereeEdge _enumereeEdge;
/// <summary>
/// Variable to store key in (can be null).
/// </summary>
public ForeachVar KeyVariable { get { return _keyVariable; } }
readonly ForeachVar _keyVariable;
/// <summary>
/// Variable to store value in
/// </summary>
public ForeachVar ValueVariable { get { return _valueVariable; } }
readonly ForeachVar _valueVariable;
internal ForeachMoveNextEdge(Block/*!*/source, Block/*!*/body, Block/*!*/end, ForeachEnumereeEdge/*!*/enumereeEdge, ForeachVar keyVar, ForeachVar/*!*/valueVar)
: base(source)
{
Contract.ThrowIfNull(body);
Contract.ThrowIfNull(end);
Contract.ThrowIfNull(enumereeEdge);
_body = body;
_end = end;
_enumereeEdge = enumereeEdge;
_keyVariable = keyVar;
_valueVariable = valueVar;
Connect(source);
}
public override IEnumerable<Block> Targets
{
get { return new Block[] { _body, _end }; }
}
/// <summary>
/// Visits the object by given visitor.
/// </summary>
public override void Visit(GraphVisitor visitor)
{
visitor.VisitCFGForeachMoveNextEdge(this);
}
}
/// <summary>
/// Represents switch edge.
/// </summary>
[DebuggerDisplay("SwitchEdge")]
public sealed class SwitchEdge : Edge
{
readonly BoundExpression _switchValue;
readonly CaseBlock[] _caseBlocks;
/// <summary>
/// The expression representing the switch value.
/// </summary>
public BoundExpression SwitchValue => _switchValue;
public override IEnumerable<Block> Targets => _caseBlocks;
public override bool IsSwitch => true;
public override CaseBlock[] CaseBlocks => _caseBlocks;
internal SwitchEdge(Block source, BoundExpression switchValue, CaseBlock[] caseBlocks)
: base(source)
{
Contract.ThrowIfNull(caseBlocks);
_switchValue = switchValue;
_caseBlocks = caseBlocks;
Connect(source);
}
/// <summary>
/// Visits the object by given visitor.
/// </summary>
public override void Visit(GraphVisitor visitor)
{
visitor.VisitCFGSwitchEdge(this);
}
}
}

Просмотреть файл

@ -0,0 +1,158 @@
using Microsoft.CodeAnalysis.Semantics;
using Pchp.Syntax.AST;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Pchp.CodeAnalysis.Semantics.Graph
{
/// <summary>
/// Control flow graph visitor.
/// </summary>
/// <remarks>Visitor does not implement infinite recursion prevention.</remarks>
public class GraphVisitor : TreeVisitor
{
#region Declarations (ignored)
public override void VisitTypeDecl(TypeDecl x)
{
}
public override void VisitMethodDecl(MethodDecl x)
{
}
public override void VisitConstDeclList(ConstDeclList x)
{
}
public override void VisitFunctionDecl(FunctionDecl x)
{
}
public override void VisitLambdaFunctionExpr(LambdaFunctionExpr x)
{
}
#endregion
#region Bound
/// <summary>
/// Visitor for bound operations.
/// </summary>
readonly OperationVisitor _opvisitor;
/// <summary>
/// Forwards the operation to the <see cref="OperationVisitor"/>.
/// </summary>
void Accept(IOperation op) => op.Accept(_opvisitor);
#endregion
#region ControlFlowGraph
public GraphVisitor(OperationVisitor opvisitor)
{
Contract.ThrowIfNull(opvisitor);
_opvisitor = opvisitor;
}
public virtual void VisitCFG(ControlFlowGraph x) => x.Start.Accept(this);
#endregion
#region Graph.Block
void VisitCFGBlockStatements(Block x)
{
for (int i = 0; i < x.Statements.Count; i++)
{
Accept(x.Statements[i]);
}
}
/// <summary>
/// Visits block statements and its edge to next block.
/// </summary>
protected virtual void VisitCFGBlockInternal(Block x)
{
VisitCFGBlockStatements(x);
if (x.NextEdge != null)
x.NextEdge.Visit(this);
}
public virtual void VisitCFGBlock(Block x)
{
VisitCFGBlockInternal(x);
}
public virtual void VisitCFGCatchBlock(CatchBlock x)
{
VisitCFGBlockInternal(x);
}
public virtual void VisitCFGCaseBlock(CaseBlock x)
{
Accept(x.CaseValue);
VisitCFGBlockInternal(x);
}
#endregion
#region Graph.Edge
public virtual void VisitCFGSimpleEdge(SimpleEdge x)
{
Debug.Assert(x.Target != null);
x.Target.Accept(this);
}
public virtual void VisitCFGConditionalEdge(ConditionalEdge x)
{
Accept(x.Condition);
x.TrueTarget.Accept(this);
x.FalseTarget.Accept(this);
}
public virtual void VisitCFGTryCatchEdge(TryCatchEdge x)
{
x.BodyBlock.Accept(this);
foreach (var c in x.CatchBlocks)
c.Accept(this);
if (x.FinallyBlock != null)
x.FinallyBlock.Accept(this);
}
public virtual void VisitCFGForeachEnumereeEdge(ForeachEnumereeEdge x)
{
Accept(x.Enumeree);
x.Target.Accept(this);
}
public virtual void VisitCFGForeachMoveNextEdge(ForeachMoveNextEdge x)
{
x.BodyBlock.Accept(this);
x.EndBlock.Accept(this);
}
public virtual void VisitCFGSwitchEdge(SwitchEdge x)
{
Accept(x.SwitchValue);
//
var arr = x.CaseBlocks;
for (int i = 0; i < arr.Length; i++)
arr[i].Accept(this);
}
#endregion
}
}

Просмотреть файл

@ -12,7 +12,7 @@ namespace Pchp.CodeAnalysis.Semantics
/// Represents PHP semantics.
/// Used to query semantic questions about the compilation in specific context.
/// </summary>
/// <remarks>Use <see cref="Microsoft.CodeAnalysis.SemanticModel"/> once we implement <see cref="SyntaxTree"/>.</remarks>
/// <remarks>Use <see cref="SemanticModel"/> once we implement <see cref="SyntaxTree"/>.</remarks>
internal interface ISemanticModel
{
// TODO: source file, constant, local variable

Просмотреть файл

@ -15,12 +15,9 @@ namespace Pchp.CodeAnalysis.Semantics
/// </summary>
internal static class SemanticsBinder
{
public static IEnumerable<BoundStatement> BindStatements(IList<AST.Statement> statements)
public static IEnumerable<BoundStatement> BindStatements(IEnumerable<AST.Statement> statements)
{
Debug.Assert(statements != null);
for (int i = 0; i < statements.Count; i++)
yield return BindStatement(statements[i]);
return statements.Select(BindStatement);
}
public static BoundStatement BindStatement(AST.Statement stmt)

Просмотреть файл

@ -47,24 +47,22 @@ namespace Pchp.CodeAnalysis.Utilities
/// <summary>
/// Dequeues item from the queue.
/// </summary>
public T TryDequeue()
public bool TryDequeue(out T value)
{
T value;
lock (_syncRoot)
{
if (_queue.Count != 0)
{
value = _queue.Dequeue();
_set.Remove(value);
return true;
}
else
{
value = default(T);
return false;
}
}
return value;
}
}
}