From 0b3df04aeb3f9555861036f890317bbeb1bbbd26 Mon Sep 17 00:00:00 2001 From: Washi Date: Sun, 5 Nov 2023 15:32:36 +0100 Subject: [PATCH 01/33] New AST builder. --- .../Echo.Ast/Analysis/AstPurityClassifier.cs | 34 + .../Echo.Ast/Analysis/AstPurityVisitor.cs | 44 + .../Analysis/FlowControlDeterminer.cs | 16 +- .../Analysis/ReadVariableFinderWalker.cs | 2 +- .../Analysis/WrittenVariableFinderWalker.cs | 4 +- src/Core/Echo.Ast/AstArchitecture.cs | 18 +- .../Echo.Ast/{AstNodeBase.cs => AstNode.cs} | 2 +- ...{AstNodeWalkerBase.cs => AstNodeWalker.cs} | 42 +- src/Core/Echo.Ast/AstVariable.cs | 42 - src/Core/Echo.Ast/Construction/AstBuilder.cs | 383 +++++++++ .../Construction/AstBuilderExtensions.cs | 24 + src/Core/Echo.Ast/Construction/AstParser.cs | 159 ---- .../Echo.Ast/Construction/AstParserContext.cs | 113 --- .../Construction/AstVariableListComparer.cs | 31 - .../Echo.Ast/Construction/BlockTransformer.cs | 298 ------- src/Core/Echo.Ast/Construction/LiftedNode.cs | 81 ++ src/Core/Echo.Ast/Construction/StackSlot.cs | 28 + src/Core/Echo.Ast/Construction/StackState.cs | 67 ++ .../Echo.Ast/Construction/VariableSnapshot.cs | 38 - src/Core/Echo.Ast/Expression.cs | 2 +- src/Core/Echo.Ast/IAstNodeVisitor.cs | 20 +- src/Core/Echo.Ast/InstructionExpression.cs | 1 - .../Echo.Ast/Patterns/PhiStatementPattern.cs | 5 +- src/Core/Echo.Ast/PhiStatement.cs | 36 +- src/Core/Echo.Ast/Statement.cs | 2 +- src/Core/Echo.Ast/SyntheticVariable.cs | 74 ++ src/Core/Echo.Ast/SyntheticVariableKind.cs | 23 + .../Analysis/Domination/DominatorTree.cs | 14 +- .../Construction/FlowGraphBuilderBase.cs | 2 +- src/Core/Echo.ControlFlow/ControlFlowGraph.cs | 14 +- .../Regions/ControlFlowRegion.cs | 2 +- .../Detection/RangedEHRegionDetector.cs | 8 +- .../Regions/ExceptionHandlerRegion.cs | 2 +- .../Echo.ControlFlow/Regions/HandlerRegion.cs | 8 +- .../Regions/IControlFlowRegion.cs | 14 +- .../Echo.ControlFlow/Regions/ScopeRegion.cs | 10 +- .../Serialization/Blocks/BlockBuilder.cs | 2 +- .../Serialization/Blocks/BlockSorter.cs | 2 +- .../Blocks/UnbreakablePathsView.cs | 6 +- test/Core/Echo.Ast.Tests/AstModelTest.cs | 230 ----- test/Core/Echo.Ast.Tests/AstParserTest.cs | 472 ----------- .../Construction/AstBuilderTest.cs | 802 ++++++++++++++++++ .../Core/Echo.Ast.Tests/Echo.Ast.Tests.csproj | 5 + .../Patterns/PhiStatementPatternTest.cs | 2 +- .../Domination/DominanceFrontierTest.cs | 10 +- .../Analysis/Domination/DominatorTreeTest.cs | 16 +- .../Static/StaticGraphBuilderTest.cs | 16 +- .../ControlFlowGraphTest.cs | 4 +- .../ControlFlowNodeTest.cs | 2 +- .../ControlFlowTestGraphs.cs | 14 +- .../Serialization/Blocks/BlockSorterTest.cs | 32 +- .../StateTransitionResolverTest.cs | 2 +- .../StaticSuccessorResolverTest.cs | 4 +- .../StateTransitionResolverTest.cs | 4 +- .../StaticFlowGraphTest.cs | 4 +- .../StaticSuccessorResolverTest.cs | 4 +- .../SymbolicFlowGraphTest.cs | 4 +- .../Code/DummyArchitecture.cs | 4 +- .../Code/DummyPurityClassifier.cs | 30 + .../DummyStaticSuccessorResolver.cs | 5 + 60 files changed, 1789 insertions(+), 1550 deletions(-) create mode 100644 src/Core/Echo.Ast/Analysis/AstPurityClassifier.cs create mode 100644 src/Core/Echo.Ast/Analysis/AstPurityVisitor.cs rename src/Core/Echo.Ast/{AstNodeBase.cs => AstNode.cs} (91%) rename src/Core/Echo.Ast/{AstNodeWalkerBase.cs => AstNodeWalker.cs} (77%) delete mode 100644 src/Core/Echo.Ast/AstVariable.cs create mode 100644 src/Core/Echo.Ast/Construction/AstBuilder.cs create mode 100644 src/Core/Echo.Ast/Construction/AstBuilderExtensions.cs delete mode 100644 src/Core/Echo.Ast/Construction/AstParser.cs delete mode 100644 src/Core/Echo.Ast/Construction/AstParserContext.cs delete mode 100644 src/Core/Echo.Ast/Construction/AstVariableListComparer.cs delete mode 100644 src/Core/Echo.Ast/Construction/BlockTransformer.cs create mode 100644 src/Core/Echo.Ast/Construction/LiftedNode.cs create mode 100644 src/Core/Echo.Ast/Construction/StackSlot.cs create mode 100644 src/Core/Echo.Ast/Construction/StackState.cs delete mode 100644 src/Core/Echo.Ast/Construction/VariableSnapshot.cs create mode 100644 src/Core/Echo.Ast/SyntheticVariable.cs create mode 100644 src/Core/Echo.Ast/SyntheticVariableKind.cs delete mode 100644 test/Core/Echo.Ast.Tests/AstModelTest.cs delete mode 100644 test/Core/Echo.Ast.Tests/AstParserTest.cs create mode 100644 test/Core/Echo.Ast.Tests/Construction/AstBuilderTest.cs create mode 100644 test/Platforms/Echo.Platforms.DummyPlatform/Code/DummyPurityClassifier.cs diff --git a/src/Core/Echo.Ast/Analysis/AstPurityClassifier.cs b/src/Core/Echo.Ast/Analysis/AstPurityClassifier.cs new file mode 100644 index 00000000..50f67252 --- /dev/null +++ b/src/Core/Echo.Ast/Analysis/AstPurityClassifier.cs @@ -0,0 +1,34 @@ +using Echo.Code; + +namespace Echo.Ast.Analysis; + +/// +/// Provides a wrapper around a that is able to classify statements +/// and expressions with instructions by purity. +/// +/// The type of instructions the statements store. +public class AstPurityClassifier : IPurityClassifier> +{ + /// + /// Creates a new instance of the class. + /// + /// The base classifier to use for classifying individual instructions in the AST. + public AstPurityClassifier(IPurityClassifier baseClassifier) + { + BaseClassifier = baseClassifier; + } + + /// + /// Gets the base classifier to use for classifying individual instructions in the AST. + /// + public IPurityClassifier BaseClassifier + { + get; + } + + /// + public Trilean IsPure(in Statement instruction) + { + return instruction.Accept(AstPurityVisitor.Instance, BaseClassifier); + } +} \ No newline at end of file diff --git a/src/Core/Echo.Ast/Analysis/AstPurityVisitor.cs b/src/Core/Echo.Ast/Analysis/AstPurityVisitor.cs new file mode 100644 index 00000000..254a9d1a --- /dev/null +++ b/src/Core/Echo.Ast/Analysis/AstPurityVisitor.cs @@ -0,0 +1,44 @@ +using Echo.Code; + +namespace Echo.Ast.Analysis; + +/// +/// Provides a mechanism for traversing an AST and determining its purity. +/// +/// The type of instructions to store in each expression. +public class AstPurityVisitor : IAstNodeVisitor, Trilean> +{ + /// + /// Gets the singleton instance of the class. + /// + public static AstPurityVisitor Instance + { + get; + } = new(); + + /// + public Trilean Visit(AssignmentStatement statement, IPurityClassifier state) => false; + + /// + public Trilean Visit(ExpressionStatement statement, IPurityClassifier state) + { + return statement.Expression.Accept(this, state); + } + + /// + public Trilean Visit(PhiStatement statement, IPurityClassifier state) => false; + + /// + public Trilean Visit(InstructionExpression expression, IPurityClassifier state) + { + var result = state.IsPure(expression.Instruction); + + for (int i = 0; i < expression.Arguments.Count && result != Trilean.False; i++) + result &= expression.Arguments[i].Accept(this, state); + + return result; + } + + /// + public Trilean Visit(VariableExpression expression, IPurityClassifier state) => true; +} \ No newline at end of file diff --git a/src/Core/Echo.Ast/Analysis/FlowControlDeterminer.cs b/src/Core/Echo.Ast/Analysis/FlowControlDeterminer.cs index c2642a64..1b0558c4 100644 --- a/src/Core/Echo.Ast/Analysis/FlowControlDeterminer.cs +++ b/src/Core/Echo.Ast/Analysis/FlowControlDeterminer.cs @@ -10,19 +10,19 @@ internal sealed class FlowControlDeterminer internal FlowControlDeterminer(IArchitecture isa) => _isa = isa; - public InstructionFlowControl Visit(AssignmentStatement assignmentStatement, object state) => - assignmentStatement.Expression.Accept(this, state); + public InstructionFlowControl Visit(AssignmentStatement statement, object state) => + statement.Expression.Accept(this, state); - public InstructionFlowControl Visit(ExpressionStatement expressionStatement, object state) => - expressionStatement.Expression.Accept(this, state); + public InstructionFlowControl Visit(ExpressionStatement statement, object state) => + statement.Expression.Accept(this, state); - public InstructionFlowControl Visit(PhiStatement phiStatement, object state) => + public InstructionFlowControl Visit(PhiStatement statement, object state) => InstructionFlowControl.Fallthrough; - public InstructionFlowControl Visit(InstructionExpression instructionExpression, object state) => - _isa.GetFlowControl(instructionExpression.Instruction); + public InstructionFlowControl Visit(InstructionExpression expression, object state) => + _isa.GetFlowControl(expression.Instruction); - public InstructionFlowControl Visit(VariableExpression variableExpression, object state) => + public InstructionFlowControl Visit(VariableExpression expression, object state) => InstructionFlowControl.Fallthrough; } } \ No newline at end of file diff --git a/src/Core/Echo.Ast/Analysis/ReadVariableFinderWalker.cs b/src/Core/Echo.Ast/Analysis/ReadVariableFinderWalker.cs index 7eec0849..19f2e551 100644 --- a/src/Core/Echo.Ast/Analysis/ReadVariableFinderWalker.cs +++ b/src/Core/Echo.Ast/Analysis/ReadVariableFinderWalker.cs @@ -3,7 +3,7 @@ namespace Echo.Ast.Analysis { - internal sealed class ReadVariableFinderWalker : AstNodeWalkerBase + internal sealed class ReadVariableFinderWalker : AstNodeWalker { internal int Count => Variables.Count; diff --git a/src/Core/Echo.Ast/Analysis/WrittenVariableFinderWalker.cs b/src/Core/Echo.Ast/Analysis/WrittenVariableFinderWalker.cs index 65be646d..ae105b98 100644 --- a/src/Core/Echo.Ast/Analysis/WrittenVariableFinderWalker.cs +++ b/src/Core/Echo.Ast/Analysis/WrittenVariableFinderWalker.cs @@ -3,7 +3,7 @@ namespace Echo.Ast.Analysis { - internal sealed class WrittenVariableFinderWalker : AstNodeWalkerBase + internal sealed class WrittenVariableFinderWalker : AstNodeWalker { internal int Count => Variables.Count; @@ -19,6 +19,6 @@ protected override void ExitAssignmentStatement(AssignmentStatement phiStatement) => - Variables.Add(phiStatement.Target); + Variables.Add(phiStatement.Representative); } } \ No newline at end of file diff --git a/src/Core/Echo.Ast/AstArchitecture.cs b/src/Core/Echo.Ast/AstArchitecture.cs index cd4a1d0e..8dbca6a4 100644 --- a/src/Core/Echo.Ast/AstArchitecture.cs +++ b/src/Core/Echo.Ast/AstArchitecture.cs @@ -14,11 +14,13 @@ public class AstArchitecture private readonly FlowControlDeterminer _flowControlDeterminer; /// - /// Create a new decorator around the + /// Create a new decorator around the /// - /// The to decorate - public AstArchitecture(IArchitecture isa) => - _flowControlDeterminer = new FlowControlDeterminer(isa); + /// The to decorate + public AstArchitecture(IArchitecture baseArchitecture) + { + _flowControlDeterminer = new FlowControlDeterminer(baseArchitecture); + } /// public long GetOffset(in Statement instruction) => throw new NotSupportedException(); @@ -27,9 +29,11 @@ public AstArchitecture(IArchitecture isa) => public int GetSize(in Statement instruction) => 1; /// - public InstructionFlowControl GetFlowControl(in Statement instruction) => - instruction.Accept(_flowControlDeterminer, null); - + public InstructionFlowControl GetFlowControl(in Statement instruction) + { + return instruction.Accept(_flowControlDeterminer, null); + } + /// public int GetStackPushCount(in Statement instruction) => 0; diff --git a/src/Core/Echo.Ast/AstNodeBase.cs b/src/Core/Echo.Ast/AstNode.cs similarity index 91% rename from src/Core/Echo.Ast/AstNodeBase.cs rename to src/Core/Echo.Ast/AstNode.cs index 2eb29d8a..9e0fbd4c 100644 --- a/src/Core/Echo.Ast/AstNodeBase.cs +++ b/src/Core/Echo.Ast/AstNode.cs @@ -6,7 +6,7 @@ namespace Echo.Ast /// /// Provides a base contract for all Ast nodes /// - public abstract class AstNodeBase : TreeNodeBase + public abstract class AstNode : TreeNodeBase { /// /// Implements the visitor pattern diff --git a/src/Core/Echo.Ast/AstNodeWalkerBase.cs b/src/Core/Echo.Ast/AstNodeWalker.cs similarity index 77% rename from src/Core/Echo.Ast/AstNodeWalkerBase.cs rename to src/Core/Echo.Ast/AstNodeWalker.cs index 49e04c7d..0c88ba25 100644 --- a/src/Core/Echo.Ast/AstNodeWalkerBase.cs +++ b/src/Core/Echo.Ast/AstNodeWalker.cs @@ -4,7 +4,7 @@ namespace Echo.Ast /// Provides a base contract for Ast walkers /// /// The type of the instruction - public abstract class AstNodeWalkerBase : IAstNodeVisitor + public abstract class AstNodeWalker : IAstNodeVisitor { /// /// Begin visiting a given @@ -64,60 +64,60 @@ protected virtual void ExitInstructionExpression(InstructionExpressionThe that is will be visited protected virtual void VisitVariableExpression(VariableExpression variableExpression) { } - private void VisitChildren(AstNodeBase node) + private void VisitChildren(AstNode node) { foreach (var child in node.GetChildren()) - ((AstNodeBase) child).Accept(this, null); + ((AstNode) child).Accept(this, null); } /// - void IAstNodeVisitor.Visit(AssignmentStatement assignmentStatement, + void IAstNodeVisitor.Visit(AssignmentStatement statement, object state) { - EnterAssignmentStatement(assignmentStatement); + EnterAssignmentStatement(statement); - assignmentStatement.Expression.Accept(this, state); + statement.Expression.Accept(this, state); - ExitAssignmentStatement(assignmentStatement); + ExitAssignmentStatement(statement); } /// - void IAstNodeVisitor.Visit(ExpressionStatement expressionStatement, + void IAstNodeVisitor.Visit(ExpressionStatement expression, object state) { - EnterExpressionStatement(expressionStatement); + EnterExpressionStatement(expression); - expressionStatement.Expression.Accept(this, state); + expression.Expression.Accept(this, state); - ExitExpressionStatement(expressionStatement); + ExitExpressionStatement(expression); } /// - void IAstNodeVisitor.Visit(PhiStatement phiStatement, object state) + void IAstNodeVisitor.Visit(PhiStatement statement, object state) { - EnterPhiStatement(phiStatement); + EnterPhiStatement(statement); - foreach (var source in phiStatement.Sources) + foreach (var source in statement.Sources) source.Accept(this, state); - ExitPhiStatement(phiStatement); + ExitPhiStatement(statement); } /// - void IAstNodeVisitor.Visit(InstructionExpression instructionExpression, object state) + void IAstNodeVisitor.Visit(InstructionExpression expression, object state) { - EnterInstructionExpression(instructionExpression); + EnterInstructionExpression(expression); - foreach (var parameter in instructionExpression.Arguments) + foreach (var parameter in expression.Arguments) parameter.Accept(this, state); - ExitInstructionExpression(instructionExpression); + ExitInstructionExpression(expression); } /// - void IAstNodeVisitor.Visit(VariableExpression variableExpression, object state) + void IAstNodeVisitor.Visit(VariableExpression expression, object state) { - VisitVariableExpression(variableExpression); + VisitVariableExpression(expression); } } } \ No newline at end of file diff --git a/src/Core/Echo.Ast/AstVariable.cs b/src/Core/Echo.Ast/AstVariable.cs deleted file mode 100644 index 425db741..00000000 --- a/src/Core/Echo.Ast/AstVariable.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Echo.Code; - -namespace Echo.Ast -{ - /// - /// Represents a variable in the AST - /// - public sealed class AstVariable : IVariable - { - /// - /// Creates a new variable with the given - /// - /// The name to give to the variable - public AstVariable(string name) - { - Name = name; - } - - /// - public string Name - { - get; - } - - /// - public override bool Equals(object obj) - { - if (ReferenceEquals(this, obj)) - return true; - if (!(obj is AstVariable other)) - return false; - - return Name == other.Name; - } - - /// - public override int GetHashCode() => Name.GetHashCode(); - - /// - public override string ToString() => Name; - } -} \ No newline at end of file diff --git a/src/Core/Echo.Ast/Construction/AstBuilder.cs b/src/Core/Echo.Ast/Construction/AstBuilder.cs new file mode 100644 index 00000000..2c6cd0b8 --- /dev/null +++ b/src/Core/Echo.Ast/Construction/AstBuilder.cs @@ -0,0 +1,383 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Echo.Ast.Analysis; +using Echo.Code; +using Echo.ControlFlow; +using Echo.ControlFlow.Regions; + +namespace Echo.Ast.Construction; + +/// +/// Lifts every node in a control flow graph to its AST representation. +/// +public sealed class AstBuilder +{ + private readonly ControlFlowGraph _original; + private readonly ControlFlowGraph> _transformed; + private readonly IPurityClassifier _purityClassifier; + private readonly Dictionary, LiftedNode> _liftedNodes = new(); + + private AstBuilder(ControlFlowGraph original, IPurityClassifier purityClassifier) + { + _original = original; + _purityClassifier = purityClassifier; + _transformed = new ControlFlowGraph>(new AstArchitecture(original.Architecture)); + } + + /// + /// Transforms a control flow graph by lifting each basic block into an AST representation. + /// + /// The control flow graph. + /// A classifier used for determining whether expressions are pure or not. + /// The transformed control flow graph. + public static ControlFlowGraph> Lift( + ControlFlowGraph cfg, + IPurityClassifier purityClassifier) + { + var builder = new AstBuilder(cfg, purityClassifier); + builder.Run(); + return builder._transformed; + } + + private void Run() + { + // Strategy: + // + // We lift each node in the control flow graph in isolation. Control flow nodes have the property that they + // are executed as a single unit, and as such, within a single node we can assume an isolated eval stack. + // This avoids full data flow analysis (i.e., building a full DFG) to build ASTs, at the cost of a slight + // chance of overproducing some synthetic variables to communicate non-zero stack deltas between cfg nodes. + // In practice however this is much rarer to occur than not. + // + // We only use SSA form and PHI nodes for synthetic stack variables. This is because many languages/platforms + // allow for variables to be accessed by reference and thus it is not clear just from the access alone whether + // it is just a read or also a write. We leave this decision up to the consumer of this API. + + LiftNodes(); + PopulatePhiStatements(); + InsertPhiStatements(); + AddEdges(); + TransformRegions(); + } + + private void LiftNodes() + { + foreach (var node in _original.Nodes) + { + var liftedNode = LiftNode(node); + _liftedNodes.Add(node, liftedNode); + _transformed.Nodes.Add(liftedNode.Transformed); + } + } + + private LiftedNode LiftNode(ControlFlowNode node) + { + var result = new LiftedNode(node); + + // For each node we keep track of a local stack. + var stack = new Stack>(); + + // Lift every instruction, processing stack states. + for (int i = 0; i < node.Contents.Instructions.Count; i++) + LiftInstruction(result, node.Contents.Instructions[i], stack); + + // Any values left on the stack we move into synthetic out variables. + FlushStack(result, stack); + + return result; + } + + private void LiftInstruction( + LiftedNode node, + in TInstruction instruction, + Stack> stack) + { + var architecture = _original.Architecture; + + // Wrap the instruction into an expression. + var expression = new InstructionExpression(instruction); + + // Determine the arguments. + int popCount = architecture.GetStackPopCount(instruction); + if (popCount == -1) + { + while (stack.Count > 0) + node.Transformed.Contents.Instructions.Add(new ExpressionStatement(stack.Pop())); + } + else + { + for (int i = 0; i < popCount; i++) + expression.Arguments.Insert(0, Pop(node, stack)); + } + + // Determine the produced values. + int pushCount = architecture.GetStackPushCount(instruction); + switch (pushCount) + { + case 0: + // No value produced means we are dealing with a new independent statement. + + // If we are the final terminator or branch instruction at the end of the block, we want to flush any + // remaining values on the stack *before* the instruction statement. + if ((architecture.GetFlowControl(instruction) + & (InstructionFlowControl.CanBranch | InstructionFlowControl.IsTerminator)) != 0) + { + FlushStack(node, stack); + } + + // Ensure order of operations is preserved if expression is potentially impure. + if (!expression.Accept(AstPurityVisitor.Instance, _purityClassifier).ToBooleanOrFalse()) + FlushStackAndPush(node, stack); + + // Wrap the expression into an independent statement and add it. + node.Transformed.Contents.Instructions.Add(new ExpressionStatement(expression)); + break; + + case 1: + // There is one value produced, push it on the local stack. + stack.Push(expression); + break; + + default: + // Multiple values are produced, move them into separate variables and push them on eval stack. + + // Ensure order of operations is preserved if expression is potentially impure. + if (!expression.Accept(AstPurityVisitor.Instance, _purityClassifier).ToBooleanOrFalse()) + FlushStackAndPush(node, stack); + + // Declare new intermediate variables. + var variables = new IVariable[pushCount]; + for (int i = 0; i < pushCount; i++) + variables[i] = node.DeclareStackIntermediate(); + + // Add the assignment statement. + node.Transformed.Contents.Instructions.Add(new AssignmentStatement(variables, expression)); + + // Push the intermediate variables. + foreach (var variable in variables) + stack.Push(new VariableExpression(variable)); + + break; + } + } + + private static Expression Pop(LiftedNode node, Stack> stack) + { + return stack.Count == 0 + ? new VariableExpression(node.DeclareStackInput()) + : stack.Pop(); + } + + private static void FlushStack(LiftedNode node, Stack> stack) + { + FlushStackInternal(node, stack, n => n.DeclareStackOutput()); + } + + private static void FlushStackAndPush(LiftedNode node, Stack> stack) + { + var variables = FlushStackInternal(node, stack, n => n.DeclareStackIntermediate()); + for (int i = 0; i < variables.Count; i++) + stack.Push(new VariableExpression(variables[i])); + } + + private static IList FlushStackInternal( + LiftedNode node, + Stack> stack, + Func, IVariable> declareVariable) + { + // Declare new variables. + var variables = new IVariable[stack.Count]; + for (int i = 0; i < stack.Count; i++) + variables[i] = declareVariable(node); + + // Create assignment statements. + var assignments = new AssignmentStatement[stack.Count]; + for (int i = variables.Length - 1; i >= 0; i--) + assignments[i] = new AssignmentStatement(variables[i], stack.Pop()); + + // Add them. + foreach (var assignment in assignments) + node.Transformed.Contents.Instructions.Add(assignment); + + return variables; + } + + private void PopulatePhiStatements() + { + var recordedStates = new Dictionary, StackState>(); + + var agenda = new Queue>(); + agenda.Enqueue(new StackState(_original.EntryPoint)); + + while (agenda.Count > 0) + { + var current = agenda.Dequeue(); + var liftedNode = _liftedNodes[current.Node]; + + // Have we visited this block before? + bool changed = false; + if (!recordedStates.TryGetValue(liftedNode, out var previousState)) + { + // We have never visited this block before. Register the new state. + recordedStates[liftedNode] = current; + changed = true; + } + else if (previousState.MergeWith(current, out var newState)) + { + // Merging the states resulted in a change. We have to revisit this path. + current = newState; + recordedStates[liftedNode] = newState; + changed = true; + } + + // If we did not make any change to the states, we can stop. + if (!changed) + continue; + + // Consume stack values, and add them to the phi statements. + for (int i = liftedNode.StackInputs.Count - 1; i >= 0; i--) + { + var input = liftedNode.StackInputs[i]; + + // Protection against malformed code streams with stack underflow. + if (current.Stack.IsEmpty) + break; + + current = current.Pop(out var value); + foreach (var source in value.Sources) + { + if (input.Sources.All(x => x.Variable != source)) + input.Sources.Add(new VariableExpression(source)); + } + } + + // Push new values on stack. + foreach (var output in liftedNode.StackOutputs) + current = current.Push(new StackSlot(output)); + + // Schedule successors. + foreach (var successor in current.Node.GetSuccessors()) + agenda.Enqueue(current.MoveTo(successor)); + } + } + + private void InsertPhiStatements() + { + foreach (var block in _liftedNodes.Values) + { + for (int i = block.StackInputs.Count - 1; i >= 0; i--) + { + var input = block.StackInputs[i]; + if (input.Sources.Count == 1) + { + // Optimization: if there is one source only for the phi node, pre-emptively remove the + // phi node and replace it with a normal assignment. + var singleSource = input.Sources[0]; + input.Sources.RemoveAt(0); + + var simplified = new AssignmentStatement(input.Representative, singleSource); + block.Transformed.Contents.Instructions.Insert(0, simplified); + } + else + { + // Insert the phi node in front of the block. + block.Transformed.Contents.Instructions.Insert(0, input); + } + } + } + } + + private void AddEdges() + { + foreach (var lifted in _liftedNodes.Values) + { + if (lifted.Original.UnconditionalEdge is { } x) + lifted.Transformed.ConnectWith(_liftedNodes[x.Target].Transformed, x.Type); + + foreach (var edge in lifted.Original.ConditionalEdges) + lifted.Transformed.ConnectWith(_liftedNodes[edge.Target].Transformed, ControlFlowEdgeType.Conditional); + + foreach (var edge in lifted.Original.AbnormalEdges) + lifted.Transformed.ConnectWith(_liftedNodes[edge.Target].Transformed, ControlFlowEdgeType.Abnormal); + } + } + + private void TransformRegions() + { + _transformed.EntryPoint = _liftedNodes[_original.EntryPoint].Transformed; + foreach (var region in _original.Regions) + TransformRegion(x => _transformed.Regions.Add((ControlFlowRegion>) x), region); + } + + private void TransformRegion( + Action>> addSection, + IControlFlowRegion region + ) + { + switch (region) + { + case ScopeRegion scopeRegion: + var newBasicRegion = new ScopeRegion>(); + addSection(newBasicRegion); + + TransformScope(scopeRegion, newBasicRegion); + break; + + case ExceptionHandlerRegion ehRegion: + var newEhRegion = new ExceptionHandlerRegion>(); + addSection(newEhRegion); + + TransformScope(ehRegion.ProtectedRegion, newEhRegion.ProtectedRegion); + foreach (var subRegion in ehRegion.Handlers) + TransformHandlerRegion(newEhRegion, subRegion); + + break; + + default: + throw new ArgumentOutOfRangeException(nameof(region)); + } + } + + private void TransformScope(ScopeRegion scopeRegion, ScopeRegion> newScopeRegion) + { + // Add the lifted nodes within the current scope. + foreach (var node in scopeRegion.Nodes) + newScopeRegion.Nodes.Add(_liftedNodes[node].Transformed); + + // Set up entry point. + newScopeRegion.EntryPoint = _liftedNodes[scopeRegion.EntryPoint].Transformed; + + // Recursively traverse the region tree. + TransformSubRegions(scopeRegion, newScopeRegion); + } + + private void TransformHandlerRegion( + ExceptionHandlerRegion> parentRegion, + HandlerRegion handlerRegion + ) + { + var result = new HandlerRegion>(); + parentRegion.Handlers.Add(result); + + if (handlerRegion.Prologue is not null) + TransformRegion(x => result.Prologue = (ScopeRegion>) x, handlerRegion.Prologue); + + if (handlerRegion.Epilogue is not null) + TransformRegion(x => result.Epilogue = (ScopeRegion>) x, handlerRegion.Epilogue); + + TransformScope(handlerRegion.Contents, result.Contents); + + result.Tag = handlerRegion.Tag; + } + + private void TransformSubRegions( + ScopeRegion originalRegion, + ScopeRegion> newRegion + ) + { + foreach (var subRegion in originalRegion.Regions) + TransformRegion(x => newRegion.Regions.Add((ControlFlowRegion>) x), subRegion); + } + +} \ No newline at end of file diff --git a/src/Core/Echo.Ast/Construction/AstBuilderExtensions.cs b/src/Core/Echo.Ast/Construction/AstBuilderExtensions.cs new file mode 100644 index 00000000..da833c99 --- /dev/null +++ b/src/Core/Echo.Ast/Construction/AstBuilderExtensions.cs @@ -0,0 +1,24 @@ +using Echo.Code; +using Echo.ControlFlow; + +namespace Echo.Ast.Construction; + +/// +/// Provides utility extensions for the construction of new AST control flow graphs. +/// +public static class AstBuilderExtensions +{ + /// + /// Lifts the instructions in every node of the provided control flow graph to expressions and statements. + /// + /// The provided control flow graph to lift. + /// The object responsible for determining whether an instruction is pure or not. + /// The type of instructions stored in the input graph. + /// The lifted graph. + public static ControlFlowGraph> ToAst( + this ControlFlowGraph cfg, + IPurityClassifier classifier) + { + return AstBuilder.Lift(cfg, classifier); + } +} \ No newline at end of file diff --git a/src/Core/Echo.Ast/Construction/AstParser.cs b/src/Core/Echo.Ast/Construction/AstParser.cs deleted file mode 100644 index 5b8133a5..00000000 --- a/src/Core/Echo.Ast/Construction/AstParser.cs +++ /dev/null @@ -1,159 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Echo.ControlFlow; -using Echo.ControlFlow.Regions; -using Echo.ControlFlow.Serialization.Blocks; -using Echo.DataFlow; - -namespace Echo.Ast.Construction -{ - /// - /// Transforms a and a into an Ast - /// - public sealed class AstParser - { - private readonly ControlFlowGraph _controlFlowGraph; - private readonly AstArchitecture _architecture; - private readonly BlockTransformer _transformer; - private readonly Dictionary, ScopeRegion>> _regionsMapping = - new Dictionary, ScopeRegion>>(); - - /// - /// Creates a new Ast parser with the given - /// - /// The to parse - /// The to parse - public AstParser(ControlFlowGraph controlFlowGraph, DataFlowGraph dataFlowGraph) - { - if (dataFlowGraph == null) - throw new ArgumentNullException(nameof(dataFlowGraph)); - _controlFlowGraph = controlFlowGraph ?? throw new ArgumentNullException(nameof(controlFlowGraph)); - _architecture = new AstArchitecture(controlFlowGraph.Architecture); - - var context = new AstParserContext(_controlFlowGraph, dataFlowGraph); - _transformer = new BlockTransformer(context); - } - - /// - /// Parses the given - /// - /// A representing the Ast - public ControlFlowGraph> Parse() - { - var newGraph = new ControlFlowGraph>(_architecture); - var rootScope = _controlFlowGraph.ConstructBlocks(); - - // Transform and add regions. - foreach (var originalRegion in _controlFlowGraph.Regions) - { - var newRegion = TransformRegion(originalRegion); - newGraph.Regions.Add(newRegion); - } - - // Transform and add nodes. - foreach (var originalBlock in rootScope.GetAllBlocks()) - { - var originalNode = _controlFlowGraph.Nodes[originalBlock.Offset]; - var transformedBlock = _transformer.Transform(originalBlock); - var newNode = new ControlFlowNode>(originalBlock.Offset, transformedBlock); - newGraph.Nodes.Add(newNode); - - // Move node to newly created region. - if (originalNode.ParentRegion is ScopeRegion basicRegion) - newNode.MoveToRegion(_regionsMapping[basicRegion]); - } - - // Clone edges. - foreach (var originalEdge in _controlFlowGraph.GetEdges()) - { - var newOrigin = newGraph.Nodes[originalEdge.Origin.Offset]; - var newTarget = newGraph.Nodes[originalEdge.Target.Offset]; - newOrigin.ConnectWith(newTarget, originalEdge.Type); - } - - // Fix entry point(s). - newGraph.Entrypoint = newGraph.Nodes[_controlFlowGraph.Entrypoint.Offset]; - FixEntryPoint(_controlFlowGraph); - - return newGraph; - - void FixEntryPoint(IControlFlowRegion region) - { - foreach (var child in region.GetSubRegions()) - FixEntryPoint(child); - - if (!(region is ScopeRegion basicControlFlowRegion)) - return; - - var entry = basicControlFlowRegion.Entrypoint; - if (entry is null) - return; - - _regionsMapping[basicControlFlowRegion].Entrypoint = newGraph.Nodes[entry.Offset]; - } - } - - private ControlFlowRegion> TransformRegion(IControlFlowRegion region) - { - switch (region) - { - case ScopeRegion basicRegion: - // Create new basic region. - var newBasicRegion = new ScopeRegion>(); - TransformSubRegions(basicRegion, newBasicRegion); - - // Register basic region pair. - _regionsMapping[basicRegion] = newBasicRegion; - - return newBasicRegion; - - case ExceptionHandlerRegion ehRegion: - var newEhRegion = new ExceptionHandlerRegion>(); - - // ProtectedRegion is read-only, so instead we just transform all sub regions and add it to the - // existing protected region. - TransformSubRegions(ehRegion.ProtectedRegion, newEhRegion.ProtectedRegion); - _regionsMapping[ehRegion.ProtectedRegion] = newEhRegion.ProtectedRegion; - - // Add handler regions. - foreach (var subRegion in ehRegion.Handlers) - newEhRegion.Handlers.Add(TransformHandlerRegion(subRegion)); - - return newEhRegion; - - case HandlerRegion handlerRegion: - return TransformHandlerRegion(handlerRegion); - - default: - throw new ArgumentOutOfRangeException(nameof(region)); - } - } - - private HandlerRegion> TransformHandlerRegion(HandlerRegion handlerRegion) - { - var result = new HandlerRegion>(); - - if (handlerRegion.Prologue != null) - result.Prologue = (ScopeRegion>) TransformRegion(handlerRegion.Prologue); - - if (handlerRegion.Epilogue != null) - result.Epilogue = (ScopeRegion>) TransformRegion(handlerRegion.Epilogue); - - // Contents is read-only, so instead we just transform all sub regions and add it to the - // existing protected region. - TransformSubRegions(handlerRegion.Contents, result.Contents); - _regionsMapping[handlerRegion.Contents] = result.Contents; - - return result; - } - - private void TransformSubRegions( - ScopeRegion originalRegion, - ScopeRegion> newRegion) - { - foreach (var subRegion in originalRegion.Regions) - newRegion.Regions.Add(TransformRegion(subRegion)); - } - } -} \ No newline at end of file diff --git a/src/Core/Echo.Ast/Construction/AstParserContext.cs b/src/Core/Echo.Ast/Construction/AstParserContext.cs deleted file mode 100644 index 3f46cee0..00000000 --- a/src/Core/Echo.Ast/Construction/AstParserContext.cs +++ /dev/null @@ -1,113 +0,0 @@ -using System.Collections.Generic; -using Echo.ControlFlow; -using Echo.Code; -using Echo.DataFlow; - -namespace Echo.Ast.Construction -{ - internal sealed class AstParserContext - { - private readonly ControlFlowGraph _controlFlowGraph; - - internal AstParserContext( - ControlFlowGraph controlFlowGraph, - DataFlowGraph dataFlowGraph) - { - _controlFlowGraph = controlFlowGraph; - DataFlowGraph = dataFlowGraph; - } - - internal IArchitecture Architecture => _controlFlowGraph.Architecture; - - internal Dictionary StackSlots - { - get; - } = new Dictionary(); - - internal Dictionary, AstVariable> ExternalSources - { - get; - } = new Dictionary, AstVariable>(); - - internal Dictionary VariableVersions - { - get; - } = new Dictionary(); - - internal Dictionary VersionedVariables - { - get; - } = new Dictionary(); - - internal Dictionary, AstVariable> VariableSourcesToPhiVariable - { - get; - } = new Dictionary, AstVariable>(new AstVariableListComparer()); - - internal Dictionary> VariableStates - { - get; - } = new Dictionary>(); - - internal DataFlowGraph DataFlowGraph - { - get; - } - - internal int PhiCount - { - get; - set; - } - - private int StackSlotCount - { - get; - set; - } - - internal AstVariable CreateStackSlot() - { - return new AstVariable($"stack_slot_{StackSlotCount++}"); - } - - internal int GetVariableVersion(IVariable variable) - { - if (!VariableVersions.ContainsKey(variable)) - VariableVersions.Add(variable, 0); - - return VariableVersions[variable]; - } - - internal int IncrementVariableVersion(IVariable variable) - { - // Ensure variable is created first. - GetVariableVersion(variable); - - // Increment the version and return the incremented result - return ++VariableVersions[variable]; - } - - internal AstVariable GetVersionedVariable(VariableSnapshot snapshot) - { - if (VersionedVariables.TryGetValue(snapshot, out var variable)) - return variable; - - variable = new AstVariable(snapshot.ToString()); - VersionedVariables.Add(snapshot, variable); - - return variable; - } - - internal AstVariable GetExternalVariable(ExternalDataSourceNode external) - { - if (ExternalSources.TryGetValue(external, out var variable)) - return variable; - - variable = new AstVariable(external.Name); - ExternalSources.Add(external, variable); - - return variable; - } - } -} \ No newline at end of file diff --git a/src/Core/Echo.Ast/Construction/AstVariableListComparer.cs b/src/Core/Echo.Ast/Construction/AstVariableListComparer.cs deleted file mode 100644 index 4cadcb0d..00000000 --- a/src/Core/Echo.Ast/Construction/AstVariableListComparer.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Echo.Ast.Construction -{ - internal sealed class AstVariableListComparer : EqualityComparer> - { - public override bool Equals(List x, List y) - { - if (x == null) - throw new ArgumentNullException(nameof(x)); - if (y == null) - throw new ArgumentNullException(nameof(y)); - - return x.SequenceEqual(y); - } - - public override int GetHashCode(List obj) - { - int hash = 1337; - unchecked - { - foreach (var item in obj) - hash += item.GetHashCode() ^ 397; - } - - return hash; - } - } -} \ No newline at end of file diff --git a/src/Core/Echo.Ast/Construction/BlockTransformer.cs b/src/Core/Echo.Ast/Construction/BlockTransformer.cs deleted file mode 100644 index 2dd5459a..00000000 --- a/src/Core/Echo.Ast/Construction/BlockTransformer.cs +++ /dev/null @@ -1,298 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using Echo.ControlFlow.Blocks; -using Echo.Code; -using Echo.DataFlow; - -namespace Echo.Ast.Construction -{ - internal sealed class BlockTransformer - { - private readonly AstParserContext _context; - - internal BlockTransformer(AstParserContext context) - { - _context = context; - } - - internal BasicBlock> Transform(BasicBlock originalBlock) - { - int phiStatementCount = 0; - var result = new BasicBlock>(); - result.Offset = originalBlock.Offset; - - foreach (var instruction in originalBlock.Instructions) - { - long offset = _context.Architecture.GetOffset(instruction); - var dataFlowNode = _context.DataFlowGraph.Nodes[offset]; - var stackDependencies = dataFlowNode.StackDependencies; - var variableDependencies = dataFlowNode.VariableDependencies; - var buffer = CreateVariableBuffer(stackDependencies.Count + variableDependencies.Count); - - // First step is to collect all the stack dependencies. - CollectStackDependencies(result, dataFlowNode, buffer, ref phiStatementCount); - - // Second step is to collect all the variable dependencies. - var slice = buffer.AsSpan().Slice(stackDependencies.Count); - CollectVariableDependencies(result, dataFlowNode, slice, ref phiStatementCount); - - // Third step is to increment the "version" of the written variables. - var writtenVariables = VersionWrittenVariables(instruction); - - // Create the instruction expression. - var variableExpressions = buffer.Select(v => new VariableExpression(v)); - var expression = new InstructionExpression(instruction, variableExpressions); - - // Fourth step is to create an assignment- or an expression statement depending - // on whether the data flow node has any dependants and how many variables - // the instruction writes to. - if (dataFlowNode.InDegree == 0 && writtenVariables.Length == 0) - { - // The data flow node has no dependants and doesn't write to any variables. - // So we can create a simple expression statement. - result.Instructions.Add(new ExpressionStatement(expression)); - } - else - { - // Otherwise an assignment statement will be added. - result.Instructions.Add(CreateAssignment(expression, instruction, writtenVariables)); - } - } - - return result; - } - - private void CollectStackDependencies( - BasicBlock> block, - DataFlowNode dataFlowNode, - Span buffer, - ref int phiStatementCount) - { - var stackDependencies = dataFlowNode.StackDependencies; - - for (int i = 0; i < stackDependencies.Count; i++) - { - var sources = stackDependencies[i]; - if (sources.Count == 1) - { - // If the dependency only has 1 possible data source, we can simply - // create a stack slot and an assignment to that slot. - buffer[i] = GetStackSlot(sources.First()); - } - else - { - // If we have more than 1 possible data source, we - // will add a "phi" node. This basically means - // that the result of the "phi function" will return - // the value based on prior control flow. - var phi = CreatePhiSlot(); - var slots = sources - .Select(source => new VariableExpression(GetStackSlot(source))) - .ToArray(); - - block.Instructions.Insert(phiStatementCount++, new PhiStatement(phi, slots)); - buffer[i] = phi; - } - } - } - - private void CollectVariableDependencies( - BasicBlock> block, - DataFlowNode dataFlowNode, - Span buffer, - ref int phiStatementCount) - { - int index = 0; - var variableDependencies = dataFlowNode.VariableDependencies; - - foreach (var dependency in variableDependencies) - { - var variable = dependency.Variable; - - if (dependency.Count <= 1) - { - // If the dependency has only 1 possible source we just simply - // get the variable. But since the AST utilizes SSA, all of the - // variables are versioned. This is good because everything is - // "immutable". One "real" variable will have a new versioned - // variable created every time it is assigned to. - int version = _context.GetVariableVersion(variable); - var snapshot = new VariableSnapshot(variable, version); - - buffer[index++] = _context.GetVersionedVariable(snapshot); - continue; - } - - // Otherwise (>0), we will get the list of versioned(!) variables - // that could reach the instruction and create a "phi" statement - // like in the stack dependencies. - var sources = CollectVariables(); - - if (_context.VariableSourcesToPhiVariable.TryGetValue(sources, out var phi)) - { - // If a phi slot already exists for the list of variables, - // reuse the same phi slot. - buffer[index++] = phi; - } - else - { - // Otherwise, create a new phi slot for the list of variables - // and save it if we encounter the same variables again. - phi = CreatePhiSlot(); - var slots = sources - .Select(source => new VariableExpression(source)) - .ToArray(); - - _context.VariableSourcesToPhiVariable.Add(sources, phi); - block.Instructions.Insert(phiStatementCount++, new PhiStatement(phi, slots)); - buffer[index++] = phi; - } - - List CollectVariables() - { - var result = new List(); - - foreach (var instruction in dependency.Select(dep => dep.Node.Contents)) - { - if (_context.VariableStates.TryGetValue(instruction, out var versions)) - { - // If we already have a dictionary for the instruction, we will - // just get the versioned variable from the existing dictionary. - var snapshot = new VariableSnapshot(variable, versions[variable]); - result.Add(_context.VersionedVariables[snapshot]); - } - else - { - // Otherwise, we will create a new dictionary for the instruction. - var snapshot = new VariableSnapshot(variable, 0); - var slot = _context.GetVersionedVariable(snapshot); - - _context.VariableStates.Add(instruction, new Dictionary - { - [variable] = 0 - }); - - result.Add(slot); - } - } - - return result; - } - } - } - - private IVariable[] VersionWrittenVariables(TInstruction instruction) - { - var buffer = GetWrittenVariables(instruction); - - foreach (var variable in buffer) - { - if (_context.VariableStates.TryGetValue(instruction, out var states)) - { - // If we already have a dictionary that contains versions for this instruction, - // we can reuse the same instance. - - // If the instruction hasn't "seen" the variable before, we add a new entry. - if (!states.ContainsKey(variable)) - states.Add(variable, _context.VariableVersions[variable]++); - - // Create a new snapshot of the variable and store the versioned variable - // created from the snapshot. - var snapshot = new VariableSnapshot(variable, _context.VariableVersions[variable]); - var versionedVariable = _context.GetVersionedVariable(snapshot); - _context.VersionedVariables[snapshot] = versionedVariable; - } - else - { - // Otherwise we will create a new dictionary and store it so it can be - // reused later. - - // Increment the version of the variable, create a snapshot and - // add it to the newly created dictionary. - int version = _context.IncrementVariableVersion(variable); - var snapshot = new VariableSnapshot(variable, version); - states = new Dictionary - { - [variable] = version - }; - - // Save the dictionary for later reuse and get a variable for the snapshot. - _context.VariableStates[instruction] = states; - _context.VersionedVariables[snapshot] = _context.GetVersionedVariable(snapshot); - } - } - - return buffer; - } - - private IVariable[] GetWrittenVariables(TInstruction instruction) - { - int count = _context.Architecture.GetWrittenVariablesCount(instruction); - if (count == 0) - return Array.Empty(); - - var buffer = new IVariable[count]; - _context.Architecture.GetWrittenVariables(instruction, buffer); - - return buffer; - } - - [SuppressMessage("ReSharper", "SuggestBaseTypeForParameter")] - private AssignmentStatement CreateAssignment( - Expression expression, - TInstruction instruction, - IVariable[] writtenVariables) - { - // We will create stack slots for every push and "simulate" an assignment - // to them. This way the resulting AST won't have the "concept" of a stack. - int pushes = _context.Architecture.GetStackPushCount(instruction); - var slots = GetStackSlots(pushes); - var buffer = new AstVariable[slots.Length + writtenVariables.Length]; - slots.CopyTo(buffer.AsSpan()); - - // Also assign to written variables. - for (int i = 0; i < writtenVariables.Length; i++) - { - // Get the correct version of the variable to assign to. - var variable = writtenVariables[i]; - int version = _context.GetVariableVersion(variable); - var snapshot = new VariableSnapshot(variable, version); - var versionedVariable = _context.GetVersionedVariable(snapshot); - - buffer[slots.Length + i] = versionedVariable; - } - - // Assign stack slots. - _context.StackSlots[instruction] = slots; - return new AssignmentStatement(buffer, expression); - } - - private IVariable GetStackSlot(StackDataSource dataSource) - { - var node = dataSource.Node; - if (node is ExternalDataSourceNode external) - return _context.GetExternalVariable(external); - - return _context.StackSlots[node.Contents][dataSource.SlotIndex]; - } - - private AstVariable[] GetStackSlots(int pushes) - { - if (pushes == 0) - return Array.Empty(); - - var buffer = new AstVariable[pushes]; - for (int i = 0; i < buffer.Length; i++) - buffer[i] = _context.CreateStackSlot(); - - return buffer; - } - - private AstVariable CreatePhiSlot() => new AstVariable($"phi_{_context.PhiCount++}"); - - private static IVariable[] CreateVariableBuffer(int length) => - length == 0 ? Array.Empty() : new IVariable[length]; - } -} \ No newline at end of file diff --git a/src/Core/Echo.Ast/Construction/LiftedNode.cs b/src/Core/Echo.Ast/Construction/LiftedNode.cs new file mode 100644 index 00000000..f3889119 --- /dev/null +++ b/src/Core/Echo.Ast/Construction/LiftedNode.cs @@ -0,0 +1,81 @@ +using System.Collections.Generic; +using Echo.ControlFlow; + +namespace Echo.Ast.Construction; + +/// +/// Represents a single node that is in the process of being lifted to its AST representation. +/// +/// +internal sealed class LiftedNode +{ + /// + /// Creates a new lifted node based on the provided base node. + /// + /// The node. + public LiftedNode(ControlFlowNode original) + { + Original = original; + Transformed = new ControlFlowNode>(original.Offset); + } + + /// + /// Gets the original control flow node that this lifted node was based on. + /// + public ControlFlowNode Original { get; } + + /// + /// Gets the new node that is being produced. + /// + public ControlFlowNode> Transformed { get; } + + /// + /// Gets an ordered list of stack input variables (and their data sources) defined by this lifted block. + /// Variables are in push-order, that is, the last variable in this list represents the top-most stack value. + /// + public List> StackInputs { get; } = new(); + + /// + /// Gets a collection of synthetic intermediate stack variables defined by this lifted block. + /// + public List StackIntermediates { get; } = new(); + + /// + /// Gets an ordered list of synthetic output stack variables this lifted block produces. + /// Variables are in push-order, that is, the last variable in this list represents the top-most stack value. + /// + public List StackOutputs { get; } = new(); + + /// + /// Defines a new synthetic stack input. + /// + /// The variable representing the stack input. + public SyntheticVariable DeclareStackInput() + { + var result = new SyntheticVariable(Original.Offset, StackInputs.Count, SyntheticVariableKind.StackIn); + StackInputs.Insert(0, new PhiStatement(result)); + return result; + } + + /// + /// Defines a new synthetic stack intermediate variable. + /// + /// The variable representing the stack value. + public SyntheticVariable DeclareStackIntermediate() + { + var result = new SyntheticVariable(Original.Offset, StackIntermediates.Count, SyntheticVariableKind.StackIntermediate); + StackIntermediates.Add(result); + return result; + } + + /// + /// Defines a new synthetic stack output variable. + /// + /// The variable representing the stack value. + public SyntheticVariable DeclareStackOutput() + { + var result = new SyntheticVariable(Original.Offset, StackOutputs.Count, SyntheticVariableKind.StackOut); + StackOutputs.Add(result); + return result; + } +} \ No newline at end of file diff --git a/src/Core/Echo.Ast/Construction/StackSlot.cs b/src/Core/Echo.Ast/Construction/StackSlot.cs new file mode 100644 index 00000000..c64dfb4d --- /dev/null +++ b/src/Core/Echo.Ast/Construction/StackSlot.cs @@ -0,0 +1,28 @@ +using System.Collections.Immutable; +using System.Diagnostics; +using Echo.Code; + +namespace Echo.Ast.Construction; + +[DebuggerDisplay("{DebuggerDisplay}")] +internal readonly struct StackSlot +{ + public StackSlot(IVariable source) + { + Sources = ImmutableHashSet.Empty.Add(source); + } + + public StackSlot(ImmutableHashSet sources) + { + Sources = sources; + } + + public ImmutableHashSet Sources + { + get; + } + + public StackSlot Union(StackSlot other) => new(Sources.Union(other.Sources)); + + public string DebuggerDisplay => $"{{{string.Join(", ", Sources)}}}"; +} \ No newline at end of file diff --git a/src/Core/Echo.Ast/Construction/StackState.cs b/src/Core/Echo.Ast/Construction/StackState.cs new file mode 100644 index 00000000..b3bc0ab6 --- /dev/null +++ b/src/Core/Echo.Ast/Construction/StackState.cs @@ -0,0 +1,67 @@ +using System.Collections.Immutable; +using System.Diagnostics; +using System.Linq; +using Echo.ControlFlow; + +namespace Echo.Ast.Construction; + +[DebuggerDisplay("{Node} (Stack: {Stack})")] +internal readonly struct StackState +{ + public StackState(ControlFlowNode node) + { + Node = node; + Stack = ImmutableStack.Empty; + } + + public StackState(ControlFlowNode node, ImmutableStack stack) + { + Node = node; + Stack = stack; + } + + public ControlFlowNode Node { get; } + + public ImmutableStack Stack { get; } + + public StackState Pop(out StackSlot variable) => new(Node, Stack.Pop(out variable)); + + public StackState Push(StackSlot variable) => new(Node, Stack.Push(variable)); + + public StackState MoveTo(ControlFlowNode node) => new(node, Stack); + + public bool MergeWith(StackState other, out StackState newState) + { + newState = this; + + bool changed = false; + + // Verify stack depths are the same, otherwise we cannot merge. + int count = Stack.Count(); + if (other.Stack.Count() != count) + throw new DataFlow.Emulation.StackImbalanceException(Node.Offset); + + var mergedSlots = new StackSlot[count]; + + var current = this; + for (int i = 0; i < count; i++) + { + // Get values of both stacks. + current = current.Pop(out var value1); + other = other.Pop(out var value2); + + // Unify them. + var union = value1.Union(value2); + if (union.Sources.Count != value1.Sources.Count) + changed = true; + + // Store in new state. + mergedSlots[count - 1 - i] = union; + } + + if (changed) + newState = new StackState(Node, ImmutableStack.Create(mergedSlots)); + + return changed; + } +} \ No newline at end of file diff --git a/src/Core/Echo.Ast/Construction/VariableSnapshot.cs b/src/Core/Echo.Ast/Construction/VariableSnapshot.cs deleted file mode 100644 index 02d36c5b..00000000 --- a/src/Core/Echo.Ast/Construction/VariableSnapshot.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using Echo.Code; - -namespace Echo.Ast.Construction -{ - internal readonly struct VariableSnapshot : IEquatable - { - internal VariableSnapshot(IVariable variable, int version) - { - Variable = variable ?? throw new ArgumentNullException(nameof(variable)); - Version = version; - } - - internal IVariable Variable - { - get; - } - - internal int Version - { - get; - } - - public bool Equals(VariableSnapshot other) => Variable.Equals(other.Variable) && Version == other.Version; - - public override bool Equals(object obj) => obj is VariableSnapshot other && Equals(other); - - public override int GetHashCode() - { - unchecked - { - return (Variable.GetHashCode() * 397) ^ Version; - } - } - - public override string ToString() => $"{Variable.Name}_v{Version}"; - } -} \ No newline at end of file diff --git a/src/Core/Echo.Ast/Expression.cs b/src/Core/Echo.Ast/Expression.cs index 02641c20..a04f7d8b 100644 --- a/src/Core/Echo.Ast/Expression.cs +++ b/src/Core/Echo.Ast/Expression.cs @@ -3,5 +3,5 @@ /// /// Provides a base contract for expressions in the AST /// - public abstract class Expression : AstNodeBase { } + public abstract class Expression : AstNode { } } \ No newline at end of file diff --git a/src/Core/Echo.Ast/IAstNodeVisitor.cs b/src/Core/Echo.Ast/IAstNodeVisitor.cs index 9ffd8fea..baa6a802 100644 --- a/src/Core/Echo.Ast/IAstNodeVisitor.cs +++ b/src/Core/Echo.Ast/IAstNodeVisitor.cs @@ -10,27 +10,27 @@ public interface IAstNodeVisitor /// /// Visits a given /// - void Visit(AssignmentStatement assignmentStatement, TState state); + void Visit(AssignmentStatement statement, TState state); /// /// Visits a given /// - void Visit(ExpressionStatement expressionStatement, TState state); + void Visit(ExpressionStatement expression, TState state); /// /// Visits a given /// - void Visit(PhiStatement phiStatement, TState state); + void Visit(PhiStatement statement, TState state); /// /// Visits a given /// - void Visit(InstructionExpression instructionExpression, TState state); + void Visit(InstructionExpression expression, TState state); /// /// Visits a given /// - void Visit(VariableExpression variableExpression, TState state); + void Visit(VariableExpression expression, TState state); } /// @@ -44,26 +44,26 @@ public interface IAstNodeVisitor /// /// Visits a given /// - TOut Visit(AssignmentStatement assignmentStatement, TState state); + TOut Visit(AssignmentStatement statement, TState state); /// /// Visits a given /// - TOut Visit(ExpressionStatement expressionStatement, TState state); + TOut Visit(ExpressionStatement statement, TState state); /// /// Visits a given /// - TOut Visit(PhiStatement phiStatement, TState state); + TOut Visit(PhiStatement statement, TState state); /// /// Visits a given /// - TOut Visit(InstructionExpression instructionExpression, TState state); + TOut Visit(InstructionExpression expression, TState state); /// /// Visits a given /// - TOut Visit(VariableExpression variableExpression, TState state); + TOut Visit(VariableExpression expression, TState state); } } \ No newline at end of file diff --git a/src/Core/Echo.Ast/InstructionExpression.cs b/src/Core/Echo.Ast/InstructionExpression.cs index 8517c1e6..65d9ca54 100644 --- a/src/Core/Echo.Ast/InstructionExpression.cs +++ b/src/Core/Echo.Ast/InstructionExpression.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using Echo.ControlFlow.Serialization.Dot; using Echo.Graphing; diff --git a/src/Core/Echo.Ast/Patterns/PhiStatementPattern.cs b/src/Core/Echo.Ast/Patterns/PhiStatementPattern.cs index edcb528e..63ed8466 100644 --- a/src/Core/Echo.Ast/Patterns/PhiStatementPattern.cs +++ b/src/Core/Echo.Ast/Patterns/PhiStatementPattern.cs @@ -69,7 +69,7 @@ protected override void MatchChildren(Statement input, MatchResult } // Match contents. - Target.Match(statement.Target, result); + Target.Match(statement.Representative, result); if (!result.IsSuccess) return; @@ -82,8 +82,7 @@ protected override void MatchChildren(Statement input, MatchResult return; } - // TODO: remove ToArray() and use indexing in the Sources property directly. - var sources = statement.Sources.ToArray(); + var sources = statement.Sources; for (int i = 0; i < Sources.Count && result.IsSuccess; i++) Sources[i].Match(sources[i], result); } diff --git a/src/Core/Echo.Ast/PhiStatement.cs b/src/Core/Echo.Ast/PhiStatement.cs index aaef90c0..8e404fb0 100644 --- a/src/Core/Echo.Ast/PhiStatement.cs +++ b/src/Core/Echo.Ast/PhiStatement.cs @@ -13,13 +13,23 @@ public sealed class PhiStatement : Statement /// /// Creates a new Phi statement /// - /// The target variable that will be assigned to - /// The possible sources for the assignment - public PhiStatement(IVariable target, IEnumerable> sources) + /// The target variable that will be assigned to + public PhiStatement(IVariable representative) { + Representative = representative; Sources = new TreeNodeCollection, VariableExpression>(this); - Target = target; + } + + /// + /// Creates a new Phi statement + /// + /// The target variable that will be assigned to + /// The possible sources for the assignment + public PhiStatement(IVariable representative, IEnumerable> sources) + { + Representative = representative; + Sources = new TreeNodeCollection, VariableExpression>(this); foreach (var source in sources) Sources.Add(source); } @@ -27,16 +37,16 @@ public PhiStatement(IVariable target, IEnumerable /// The variable that will be assigned to /// - public IVariable Target + public IVariable Representative { get; private set; } /// - /// The possible sources for that could be assigned to + /// The possible sources for that could be assigned to /// - public ICollection> Sources + public IList> Sources { get; } @@ -53,13 +63,13 @@ public override TOut Accept(IAstNodeVisitor - /// Modifies the current to assign to + /// Modifies the current to assign to /// - /// The new target to assign - /// The same instance but with the new - public PhiStatement WithTarget(IVariable target) + /// The new target to assign + /// The same instance but with the new + public PhiStatement WithRepresentative(IVariable variable) { - Target = target; + Representative = variable; return this; } @@ -87,7 +97,7 @@ public PhiStatement WithSources(IEnumerable - public override string ToString() => $"{Target} = φ({string.Join(", ", Sources)})"; + public override string ToString() => $"{Representative.Name} = φ({string.Join(", ", Sources)})"; internal override string Format(IInstructionFormatter instructionFormatter) => ToString(); } diff --git a/src/Core/Echo.Ast/Statement.cs b/src/Core/Echo.Ast/Statement.cs index 1c0f5ca7..3915d003 100644 --- a/src/Core/Echo.Ast/Statement.cs +++ b/src/Core/Echo.Ast/Statement.cs @@ -3,5 +3,5 @@ /// /// Provides a base contract for statements in the AST /// - public abstract class Statement : AstNodeBase { } + public abstract class Statement : AstNode { } } \ No newline at end of file diff --git a/src/Core/Echo.Ast/SyntheticVariable.cs b/src/Core/Echo.Ast/SyntheticVariable.cs new file mode 100644 index 00000000..bff447b0 --- /dev/null +++ b/src/Core/Echo.Ast/SyntheticVariable.cs @@ -0,0 +1,74 @@ +using System; +using Echo.Code; + +namespace Echo.Ast; + +/// +/// Represents a synthetic variable that was introduced after lifting a sequence of instructions into its AST +/// representation. +/// +public class SyntheticVariable : IVariable +{ + /// + /// Creates a new synthetic variable. + /// + /// The offset at which the synthetic variable was created. + /// The synthetic stack or variable slot index. + /// The type of synthetic variable. + public SyntheticVariable(long offset, int index, SyntheticVariableKind kind) + { + Offset = offset; + Index = index; + Kind = kind; + Name = GenerateName(); + } + + /// + /// Gets the offset at which the synthetic variable was created. + /// + public long Offset + { + get; + } + + /// + /// Gets the synthetic stack or variable slot index. The exact semantics of the index depend on the value + /// of . + /// + public int Index + { + get; + } + + /// + /// Gets the type of the synthetic variable. + /// + public SyntheticVariableKind Kind + { + get; + } + + /// + /// Gets the name of the synthetic variable. + /// + public string Name + { + get; + } + + private string GenerateName() + { + string kind = Kind switch + { + SyntheticVariableKind.StackIn => "in", + SyntheticVariableKind.StackIntermediate => "tmp", + SyntheticVariableKind.StackOut => "out", + _ => throw new ArgumentOutOfRangeException() + }; + + return $"{kind}_{Offset:X4}_{Index}"; + } + + /// + public override string ToString() => Name; +} \ No newline at end of file diff --git a/src/Core/Echo.Ast/SyntheticVariableKind.cs b/src/Core/Echo.Ast/SyntheticVariableKind.cs new file mode 100644 index 00000000..a625bbe8 --- /dev/null +++ b/src/Core/Echo.Ast/SyntheticVariableKind.cs @@ -0,0 +1,23 @@ +namespace Echo.Ast; + +/// +/// Provides members describing all possible synthetic variable kinds. +/// +public enum SyntheticVariableKind +{ + /// + /// Indicates the variable represents a stack input of a basic block. + /// + StackIn, + + /// + /// Indicates the variable represents an intermediate stack value that was pushed onto the stack in the middle + /// of a basic block. + /// + StackIntermediate, + + /// + /// Indicates the variable represents a stack output of a basic block. + /// + StackOut, +} \ No newline at end of file diff --git a/src/Core/Echo.ControlFlow/Analysis/Domination/DominatorTree.cs b/src/Core/Echo.ControlFlow/Analysis/Domination/DominatorTree.cs index 4425d31d..7fc9a999 100644 --- a/src/Core/Echo.ControlFlow/Analysis/Domination/DominatorTree.cs +++ b/src/Core/Echo.ControlFlow/Analysis/Domination/DominatorTree.cs @@ -26,12 +26,12 @@ public class DominatorTree : IGraph /// The constructed dominator tree. public static DominatorTree FromGraph(ControlFlowGraph graph) { - if (graph.Entrypoint == null) + if (graph.EntryPoint == null) throw new ArgumentException("Control flow graph does not have an entrypoint."); - var idoms = GetImmediateDominators(graph.Entrypoint); - var nodes = ConstructTreeNodes(idoms, graph.Entrypoint); - return new DominatorTree(nodes, graph.Entrypoint); + var idoms = GetImmediateDominators(graph.EntryPoint); + var nodes = ConstructTreeNodes(idoms, graph.EntryPoint); + return new DominatorTree(nodes, graph.EntryPoint); } /// @@ -123,7 +123,7 @@ int GetPredecessors(ControlFlowNode node) // all the nodes in the protected region as predecessor. However, for this algorithm, // it should be enough to only schedule the entrypoint of the protected region. bool isHandlerEntrypoint = node.GetParentHandler() is { } parentHandler - && node == parentHandler.GetEntrypoint(); + && node == parentHandler.GetEntryPoint(); int actualInDegree1 = node.InDegree; if (isHandlerEntrypoint) @@ -142,7 +142,7 @@ int GetPredecessors(ControlFlowNode node) // Copy over protected entrypoint if we were a handler entrypoint. if (isHandlerEntrypoint) - predecessorBuffer[actualInDegree1 - 1] = node.GetParentExceptionHandler().ProtectedRegion.Entrypoint; + predecessorBuffer[actualInDegree1 - 1] = node.GetParentExceptionHandler().ProtectedRegion.EntryPoint; return actualInDegree1; } @@ -181,7 +181,7 @@ private static TraversalResult TraverseGraph(ControlFlowNode entrypoint) && currentNode.IsInRegion(parentEh.ProtectedRegion)) { for (int i = 0; i < parentEh.Handlers.Count; i++) - Schedule(currentNode, parentEh.Handlers[i].GetEntrypoint()); + Schedule(currentNode, parentEh.Handlers[i].GetEntryPoint()); } } diff --git a/src/Core/Echo.ControlFlow/Construction/FlowGraphBuilderBase.cs b/src/Core/Echo.ControlFlow/Construction/FlowGraphBuilderBase.cs index 938308ba..924c9f97 100644 --- a/src/Core/Echo.ControlFlow/Construction/FlowGraphBuilderBase.cs +++ b/src/Core/Echo.ControlFlow/Construction/FlowGraphBuilderBase.cs @@ -25,7 +25,7 @@ public ControlFlowGraph ConstructFlowGraph(long entrypoint, IEnume var graph = new ControlFlowGraph(Architecture); CreateNodes(graph, traversalResult); ConnectNodes(graph, traversalResult); - graph.Entrypoint = graph.GetNodeByOffset(entrypoint); + graph.EntryPoint = graph.GetNodeByOffset(entrypoint); return graph; } diff --git a/src/Core/Echo.ControlFlow/ControlFlowGraph.cs b/src/Core/Echo.ControlFlow/ControlFlowGraph.cs index e75f6139..a4fbe086 100644 --- a/src/Core/Echo.ControlFlow/ControlFlowGraph.cs +++ b/src/Core/Echo.ControlFlow/ControlFlowGraph.cs @@ -16,9 +16,9 @@ namespace Echo.ControlFlow /// object in a type safe manner. /// /// The type of data that each node in the graph stores. - public class ControlFlowGraph : IGraph, IControlFlowRegion + public class ControlFlowGraph : IGraph, IScopeControlFlowRegion { - private ControlFlowNode _entrypoint; + private ControlFlowNode _entryPoint; /// /// Creates a new empty graph. @@ -34,16 +34,16 @@ public ControlFlowGraph(IArchitecture architecture) /// /// Gets or sets the node that is executed first in the control flow graph. /// - public ControlFlowNode Entrypoint + public ControlFlowNode EntryPoint { - get => _entrypoint; + get => _entryPoint; set { - if (_entrypoint != value) + if (_entryPoint != value) { if (!Nodes.Contains(value)) throw new ArgumentException("Node is not present in the graph.", nameof(value)); - _entrypoint = value; + _entryPoint = value; } } } @@ -99,7 +99,7 @@ public IEnumerable> GetEdges() => /// IEnumerable ISubGraph.GetSubGraphs() => Regions; - ControlFlowNode IControlFlowRegion.GetEntrypoint() => Entrypoint; + ControlFlowNode IControlFlowRegion.GetEntryPoint() => EntryPoint; /// IEnumerable> IControlFlowRegion.GetSubRegions() => Regions; diff --git a/src/Core/Echo.ControlFlow/Regions/ControlFlowRegion.cs b/src/Core/Echo.ControlFlow/Regions/ControlFlowRegion.cs index ec0a8094..37906d6e 100644 --- a/src/Core/Echo.ControlFlow/Regions/ControlFlowRegion.cs +++ b/src/Core/Echo.ControlFlow/Regions/ControlFlowRegion.cs @@ -48,7 +48,7 @@ public object Tag } /// - public abstract ControlFlowNode GetEntrypoint(); + public abstract ControlFlowNode GetEntryPoint(); /// public virtual ControlFlowNode GetNodeByOffset(long offset) diff --git a/src/Core/Echo.ControlFlow/Regions/Detection/RangedEHRegionDetector.cs b/src/Core/Echo.ControlFlow/Regions/Detection/RangedEHRegionDetector.cs index 92fbe47b..7a51b8fb 100644 --- a/src/Core/Echo.ControlFlow/Regions/Detection/RangedEHRegionDetector.cs +++ b/src/Core/Echo.ControlFlow/Regions/Detection/RangedEHRegionDetector.cs @@ -157,16 +157,16 @@ private static void DetermineRegionEntrypoints(ControlFlowGraph> Handlers } /// - public override ControlFlowNode GetEntrypoint() => ProtectedRegion.Entrypoint; + public override ControlFlowNode GetEntryPoint() => ProtectedRegion.EntryPoint; /// public override IEnumerable> GetNodes() => diff --git a/src/Core/Echo.ControlFlow/Regions/HandlerRegion.cs b/src/Core/Echo.ControlFlow/Regions/HandlerRegion.cs index 88c1a80e..81707196 100644 --- a/src/Core/Echo.ControlFlow/Regions/HandlerRegion.cs +++ b/src/Core/Echo.ControlFlow/Regions/HandlerRegion.cs @@ -68,11 +68,11 @@ private void UpdateChildRegion(ref ScopeRegion field, ScopeRegion< } /// - public override ControlFlowNode GetEntrypoint() + public override ControlFlowNode GetEntryPoint() { - var entrypoint = _prologue?.GetEntrypoint(); - entrypoint ??= Contents.GetEntrypoint(); - entrypoint ??= _epilogue?.GetEntrypoint(); + var entrypoint = _prologue?.GetEntryPoint(); + entrypoint ??= Contents.GetEntryPoint(); + entrypoint ??= _epilogue?.GetEntryPoint(); return entrypoint; } diff --git a/src/Core/Echo.ControlFlow/Regions/IControlFlowRegion.cs b/src/Core/Echo.ControlFlow/Regions/IControlFlowRegion.cs index 08b252d7..468e742b 100644 --- a/src/Core/Echo.ControlFlow/Regions/IControlFlowRegion.cs +++ b/src/Core/Echo.ControlFlow/Regions/IControlFlowRegion.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Echo.ControlFlow.Collections; using Echo.Graphing; namespace Echo.ControlFlow.Regions @@ -32,7 +33,7 @@ IControlFlowRegion ParentRegion /// Obtains the first node that is executed in the region (if available). /// /// The node, or null if no entrypoint was specified.. - ControlFlowNode GetEntrypoint(); + ControlFlowNode GetEntryPoint(); /// /// Gets a collection of all nested regions defined in this region. @@ -67,6 +68,17 @@ IControlFlowRegion ParentRegion /// The nodes. IEnumerable> GetSuccessors(); } + + public interface IScopeControlFlowRegion : IControlFlowRegion + { + /// + /// Gets a collection of nested sub regions that this region defines. + /// + public RegionCollection> Regions + { + get; + } + } /// /// Provides extensions to the interface. diff --git a/src/Core/Echo.ControlFlow/Regions/ScopeRegion.cs b/src/Core/Echo.ControlFlow/Regions/ScopeRegion.cs index 029a6da6..f7d16a72 100644 --- a/src/Core/Echo.ControlFlow/Regions/ScopeRegion.cs +++ b/src/Core/Echo.ControlFlow/Regions/ScopeRegion.cs @@ -10,7 +10,7 @@ namespace Echo.ControlFlow.Regions /// The type of data that each node in the graph stores. public class ScopeRegion : ControlFlowRegion { - private ControlFlowNode _entrypoint; + private ControlFlowNode _entryPoint; /// /// Creates a new instance of the class. @@ -24,10 +24,10 @@ public ScopeRegion() /// /// Gets or sets the first node that is executed in the region. /// - public ControlFlowNode Entrypoint + public ControlFlowNode EntryPoint { - get => _entrypoint; - set => _entrypoint = value; + get => _entryPoint; + set => _entryPoint = value; } /// @@ -63,7 +63,7 @@ public override IEnumerable> GetNodes() } /// - public override ControlFlowNode GetEntrypoint() => Entrypoint; + public override ControlFlowNode GetEntryPoint() => EntryPoint; /// public override IEnumerable> GetSubRegions() => Regions; diff --git a/src/Core/Echo.ControlFlow/Serialization/Blocks/BlockBuilder.cs b/src/Core/Echo.ControlFlow/Serialization/Blocks/BlockBuilder.cs index 44fc1e13..ec99abdb 100644 --- a/src/Core/Echo.ControlFlow/Serialization/Blocks/BlockBuilder.cs +++ b/src/Core/Echo.ControlFlow/Serialization/Blocks/BlockBuilder.cs @@ -250,7 +250,7 @@ public void AddBlock(IBlock basicBlock) public override string ToString() { - return $"{Region.GetType().Name}, Offset: {Region.GetEntrypoint().Offset:X8}"; + return $"{Region.GetType().Name}, Offset: {Region.GetEntryPoint().Offset:X8}"; } } diff --git a/src/Core/Echo.ControlFlow/Serialization/Blocks/BlockSorter.cs b/src/Core/Echo.ControlFlow/Serialization/Blocks/BlockSorter.cs index 8e04fbed..87328a83 100644 --- a/src/Core/Echo.ControlFlow/Serialization/Blocks/BlockSorter.cs +++ b/src/Core/Echo.ControlFlow/Serialization/Blocks/BlockSorter.cs @@ -50,7 +50,7 @@ public static IEnumerable> SortNodes var sorter = new TopologicalSorter>(pathsView.GetImpliedNeighbours, true); return sorter - .GetTopologicalSorting(cfg.Entrypoint) + .GetTopologicalSorting(cfg.EntryPoint) .Reverse() .SelectMany(n => pathsView.GetUnbreakablePath(n)); } diff --git a/src/Core/Echo.ControlFlow/Serialization/Blocks/UnbreakablePathsView.cs b/src/Core/Echo.ControlFlow/Serialization/Blocks/UnbreakablePathsView.cs index b3a87b7b..48044e10 100644 --- a/src/Core/Echo.ControlFlow/Serialization/Blocks/UnbreakablePathsView.cs +++ b/src/Core/Echo.ControlFlow/Serialization/Blocks/UnbreakablePathsView.cs @@ -116,7 +116,7 @@ private void AddHandlerEntrypoints( var handlerRegion = ehRegion.Handlers[i]; // Ensure the handler that we can jump to has an entrypoint node. - var handlerEntry = handlerRegion.GetEntrypoint(); + var handlerEntry = handlerRegion.GetEntryPoint(); if (handlerEntry is null) { throw new InvalidOperationException( @@ -133,9 +133,9 @@ private void AddNextHandlerRegion(ICollection> res ControlFlowNode nextEntry = null; if (node.ParentRegion == handlerRegion.Prologue) - nextEntry = handlerRegion.Contents.Entrypoint; + nextEntry = handlerRegion.Contents.EntryPoint; if (nextEntry is null && node.ParentRegion == handlerRegion.Contents) - nextEntry = handlerRegion.Epilogue?.Entrypoint; + nextEntry = handlerRegion.Epilogue?.EntryPoint; if (nextEntry != null) AddSuccessorToResult(result, nextEntry); diff --git a/test/Core/Echo.Ast.Tests/AstModelTest.cs b/test/Core/Echo.Ast.Tests/AstModelTest.cs deleted file mode 100644 index 5d027f01..00000000 --- a/test/Core/Echo.Ast.Tests/AstModelTest.cs +++ /dev/null @@ -1,230 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Echo.Ast.Construction; -using Echo.ControlFlow; -using Echo.ControlFlow.Construction; -using Echo.ControlFlow.Construction.Symbolic; -using Echo.Platforms.DummyPlatform.Code; -using Echo.Platforms.DummyPlatform.ControlFlow; -using Xunit; - -namespace Echo.Ast.Tests -{ - public class AstModelTest - { - private ControlFlowGraph> ConstructAst( - IEnumerable instructions) - { - var architecture = DummyArchitecture.Instance; - - var dfgBuilder = new DummyTransitioner(); - var cfgBuilder = new SymbolicFlowGraphBuilder(architecture, instructions, dfgBuilder); - - var cfg = cfgBuilder.ConstructFlowGraph(0); - var astBuilder = new AstParser(cfg, dfgBuilder.DataFlowGraph); - return astBuilder.Parse(); - } - - [Fact] - public void RootNodesShouldHaveNoParent() - { - var ast = ConstructAst(new[] - { - DummyInstruction.Push(0, 1), - DummyInstruction.Push(1, 2), - DummyInstruction.Op(2, 2, 1), - DummyInstruction.Op(3, 2, 0), - DummyInstruction.Ret(4) - }); - - Assert.Single(ast.Nodes); - var block = ast.Entrypoint.Contents.Instructions; - Assert.Equal(5, block.Count); - Assert.All(block, node => Assert.Null(node.Parent)); - } - - [Fact] - public void ReplacingVariablesInAssignmentShouldGetRidOfOldOnes() - { - var var1 = new DummyVariable("1"); - var ast = ConstructAst(new[] - { - DummyInstruction.Push(0, 1), - DummyInstruction.Set(1, var1), - DummyInstruction.Ret(2), - }); - - Assert.Single(ast.Nodes); - var block = ast.Entrypoint.Contents.Instructions; - Assert.Equal(3, block.Count); - Assert.All(block, node => Assert.Null(node.Parent)); - - var var2 = new DummyVariable("2"); - var var3 = new DummyVariable("3"); - var asmt = Assert.IsType>(block[1]); - Assert.Equal(2, asmt.WithVariables(var2, var3).Variables.Count); - Assert.Equal(asmt.Variables[0], var2); - Assert.Equal(asmt.Variables[1], var3); - } - - [Fact] - public void ReplacingExpressionInAssignmentShouldChangeParent() - { - var var1 = new DummyVariable("1"); - var ast = ConstructAst(new[] - { - DummyInstruction.Push(0, 1), - DummyInstruction.Set(1, var1), - DummyInstruction.Ret(2) - }); - - Assert.Single(ast.Nodes); - var block = ast.Entrypoint.Contents.Instructions; - Assert.Equal(3, block.Count); - Assert.All(block, node => Assert.Null(node.Parent)); - - var expr = new InstructionExpression(DummyInstruction.Jmp(0, 69), - Array.Empty>()); - var asmt = Assert.IsType>(block[1]); - Assert.Equal(expr, asmt.WithExpression(expr).Expression); - Assert.Equal(asmt, expr.Parent); - } - - [Fact] - public void ReplacingExpressionInStatementShouldChangeParent() - { - var ast = ConstructAst(new[] - { - DummyInstruction.Push(0, 2), - DummyInstruction.Pop(1, 2), - DummyInstruction.Ret(2) - }); - - Assert.Single(ast.Nodes); - var block = ast.Entrypoint.Contents.Instructions; - Assert.Equal(3, block.Count); - Assert.All(block, node => Assert.Null(node.Parent)); - - var expr = new InstructionExpression(DummyInstruction.Jmp(0, 69), - Array.Empty>()); - var stmt = Assert.IsType>(block[1]); - Assert.Equal(expr, stmt.WithExpression(expr).Expression); - Assert.Equal(stmt, expr.Parent); - } - - [Fact] - public void ReplacingInstructionInInstructionExpression() - { - var ast = ConstructAst(new[] - { - DummyInstruction.Push(0, 1), - DummyInstruction.Pop(1, 1), - DummyInstruction.Ret(2) - }); - - Assert.Single(ast.Nodes); - var block = ast.Entrypoint.Contents.Instructions; - Assert.Equal(3, block.Count); - Assert.All(block, node => Assert.Null(node.Parent)); - - var instruction = DummyInstruction.Jmp(0, 69); - var asmt = Assert.IsType>(block[0]); - var expr = Assert.IsType>(asmt.Expression); - - Assert.Equal(instruction, expr.WithInstruction(instruction).Instruction); - } - - [Fact] - public void ReplacingArgumentsInInstructionExpression() - { - var ast = ConstructAst(new[] - { - DummyInstruction.Push(0, 1), - DummyInstruction.Pop(1, 1), - DummyInstruction.Ret(2) - }); - - Assert.Single(ast.Nodes); - var block = ast.Entrypoint.Contents.Instructions; - Assert.Equal(3, block.Count); - Assert.All(block, node => Assert.Null(node.Parent)); - - var stmt = Assert.IsType>(block[1]); - var expr = Assert.IsType>(stmt.Expression); - - Assert.Single(expr.Arguments); - var original = Assert.IsType>(expr.Arguments[0]); - var newOne = new VariableExpression(new DummyVariable("dummy")); - - expr.WithArguments(original, newOne); - - Assert.Equal(2, expr.Arguments.Count); - Assert.Equal(original, expr.Arguments[0]); - Assert.Equal(newOne, expr.Arguments[1]); - Assert.Equal(expr, newOne.Parent); - } - - [Fact] - public void ReplacingTargetVariableInPhiStatement() - { - var target1 = new DummyVariable("old"); - var target2 = new DummyVariable("new"); - var sources = new[] - { - new DummyVariable("source1"), - new DummyVariable("source2"), - new DummyVariable("source3"), - new DummyVariable("source4") - }; - var phi = new PhiStatement( - target1, sources.Select(s => new VariableExpression(s))); - - Assert.Equal(target2, phi.WithTarget(target2).Target); - } - - [Fact] - public void ReplacingSourcesInPhiStatement() - { - var target = new DummyVariable("target"); - var sources1 = new[] - { - new DummyVariable("source1"), - new DummyVariable("source2"), - new DummyVariable("source3"), - new DummyVariable("source4") - }; - var sources2 = new[] - { - new DummyVariable("newSource1"), - new DummyVariable("newSource2"), - new DummyVariable("newSource3"), - new DummyVariable("newSource4"), - new DummyVariable("newSource5") - }; - var phi = new PhiStatement( - target, sources1.Select(s => new VariableExpression(s))); - - var sourceExpr = sources2.Select(s => new VariableExpression(s)).ToList(); - phi.WithSources(sourceExpr); - - Assert.Equal(5, phi.Sources.Count); - Assert.All(phi.Sources, s => - { - Assert.Equal(phi, s.Parent); - Assert.Equal(sources2[sourceExpr.IndexOf(s)], s.Variable); - }); - } - - [Fact] - public void ReplacingVariableInVariableExpression() - { - var var1 = new DummyVariable("old"); - var var2 = new DummyVariable("new"); - - var expr = new VariableExpression(var1); - - Assert.Equal(var2, expr.WithVariable(var2).Variable); - } - } -} \ No newline at end of file diff --git a/test/Core/Echo.Ast.Tests/AstParserTest.cs b/test/Core/Echo.Ast.Tests/AstParserTest.cs deleted file mode 100644 index 15310512..00000000 --- a/test/Core/Echo.Ast.Tests/AstParserTest.cs +++ /dev/null @@ -1,472 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Echo.Ast.Construction; -using Echo.Ast.Patterns; -using Echo.Ast.Tests.Patterns; -using Echo.ControlFlow; -using Echo.ControlFlow.Construction; -using Echo.ControlFlow.Construction.Symbolic; -using Echo.Code; -using Echo.Platforms.DummyPlatform.Code; -using Echo.Platforms.DummyPlatform.ControlFlow; -using Xunit; - -namespace Echo.Ast.Tests -{ - public class AstParserTest - { - private ControlFlowGraph> ConstructAst( - IEnumerable instructions) - { - var architecture = DummyArchitecture.Instance; - - var dfgBuilder = new DummyTransitioner(); - var cfgBuilder = new SymbolicFlowGraphBuilder(architecture, instructions, dfgBuilder); - - var cfg = cfgBuilder.ConstructFlowGraph(0); - var astBuilder = new AstParser(cfg, dfgBuilder.DataFlowGraph); - return astBuilder.Parse(); - } - - [Fact] - public void SingleInstructionNoArgumentsShouldResultInSingleExpressionStatement() - { - var cfg = ConstructAst(new[] - { - DummyInstruction.Ret(0) - }); - - var pattern = StatementPattern - .Instruction(new DummyInstructionPattern(DummyOpCode.Ret)); - - var result = pattern.Match(cfg.Nodes[0].Contents.Header); - Assert.True(result.IsSuccess); - } - - [Fact] - public void InstructionWithOneStackArgumentShouldResultInAssignmentAndExpressionStatementWithArgument() - { - var cfg = ConstructAst(new[] - { - DummyInstruction.Push(0, 1), - DummyInstruction.Pop(1, 1), - DummyInstruction.Ret(2) - }); - - var variableCapture = new CaptureGroup("variable"); - - var pattern = new SequencePattern>( - // stack_slot = push 1() - StatementPattern - .Assignment() - .WithExpression(ExpressionPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Push))) - .CaptureVariables(variableCapture), - - // pop(stack_slot) - StatementPattern.Expression(ExpressionPattern - .Instruction(new DummyInstructionPattern(DummyOpCode.Pop)) - .WithArguments(ExpressionPattern - .Variable() - .CaptureVariable(variableCapture))), - - // ret() - StatementPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Ret)) - ); - - var result = pattern.Match(cfg.Nodes[0].Contents.Instructions); - - Assert.True(result.IsSuccess); - Assert.Single(result.Captures[variableCapture].Distinct()); - } - - [Fact] - public void PushingTwoValuesOnStackShouldResultInTwoVariablesAssigned() - { - var cfg = ConstructAst(new[] - { - DummyInstruction.Push(0, 2), - DummyInstruction.Pop(1, 2), - DummyInstruction.Ret(2) - }); - - var variableCapture = new CaptureGroup("variable"); - var argumentsCapture = new CaptureGroup("argument"); - - var pattern = new SequencePattern>( - // stack_slot_1, stack_slot_2 = push 2() - StatementPattern - .Assignment() - .WithVariables(2) - .CaptureVariables(variableCapture), - - // pop(?, ?) - StatementPattern.Expression(ExpressionPattern - .Instruction() - .WithArguments(2) - .CaptureArguments(argumentsCapture)), - - // ret() - StatementPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Ret)) - ); - - var result = pattern.Match(cfg.Nodes[0].Contents.Instructions); - Assert.True(result.IsSuccess); - - var variables = result.Captures[variableCapture] - .Cast() - .ToArray(); - - var arguments = result.Captures[argumentsCapture] - .Cast>() - .Select(e => e.Variable) - .ToArray(); - - Assert.Equal(variables, arguments); - } - - [Fact] - public void PushingTwoValuesOnStackWithDifferentConsumers() - { - var cfg = ConstructAst(new[] - { - DummyInstruction.Push(0, 2), - DummyInstruction.Pop(1, 1), - DummyInstruction.Pop(2, 1), - DummyInstruction.Ret(3) - }); - - var variableCapture = new CaptureGroup("variable"); - var argumentsCapture1 = new CaptureGroup("argument1"); - var argumentsCapture2 = new CaptureGroup("argument2"); - - var pattern = new SequencePattern>( - // stack_slot_1, stack_slot_2 = push 2() - StatementPattern - .Assignment() - .WithVariables(2) - .CaptureVariables(variableCapture), - - // pop(?) - StatementPattern.Expression(ExpressionPattern - .Instruction() - .WithArguments(1) - .CaptureArguments(argumentsCapture1)), - - // pop(?) - StatementPattern.Expression(ExpressionPattern - .Instruction() - .WithArguments(1) - .CaptureArguments(argumentsCapture2)), - - // ret() - StatementPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Ret)) - ); - - var result = pattern.Match(cfg.Nodes[0].Contents.Instructions); - Assert.True(result.IsSuccess); - - var variables = result.Captures[variableCapture] - .Cast() - .ToArray(); - - var argument1 = (VariableExpression) result.Captures[argumentsCapture1][0]; - var argument2 = (VariableExpression) result.Captures[argumentsCapture2][0]; - - // Note: we expect the first pop statement to use the second variable that was pushed by the push instruction. - Assert.Equal(variables[1], argument1.Variable); - Assert.Equal(variables[0], argument2.Variable); - } - - [Fact] - public void JoiningControlFlowPathsWithPositiveStackDeltaShouldResultInPhiNode() - { - var cfg = ConstructAst(new[] - { - DummyInstruction.Push(0, 1), - DummyInstruction.JmpCond(1,4), - - DummyInstruction.Push(2, 1), - DummyInstruction.Jmp(3, 5), - - DummyInstruction.Push(4, 1), - - DummyInstruction.Pop(5, 1), - DummyInstruction.Ret(6) - }); - - var phiSourcesCapture = new CaptureGroup("sources"); - var variablesCapture = new CaptureGroup("variables"); - - // stack_slot = push 1() - var assignPattern = StatementPattern - .Assignment() - .CaptureVariables(variablesCapture); - - // variable = phi(?, ?) - var phiPattern = StatementPattern - .Phi() - .WithSources(2) - .CaptureSources(phiSourcesCapture); - - var push1Result = assignPattern.Match(cfg.Nodes[2].Contents.Header); - var push2Result = assignPattern.Match(cfg.Nodes[4].Contents.Header); - var phiResult = phiPattern.Match(cfg.Nodes[5].Contents.Header); - - Assert.True(push1Result.IsSuccess, "Node 2 was expected to start with an assignment statement."); - Assert.True(push2Result.IsSuccess, "Node 4 was expected to start with an assignment statement."); - Assert.True(phiResult.IsSuccess, "Node 5 was expected to start with a phi statement."); - - var sources = phiResult.Captures[phiSourcesCapture] - .Cast>() - .Select(s => s.Variable); - - var allVariables = new[] - { - (IVariable) push1Result.Captures[variablesCapture][0], - (IVariable) push2Result.Captures[variablesCapture][0] - }; - - Assert.Equal(allVariables.ToHashSet(), sources.ToHashSet()); - } - - [Fact] - public void JoiningControlFlowPathsWithDifferentVariableVersionsShouldResultInPhiNode() - { - var variable = new DummyVariable("temp"); - - var cfg = ConstructAst(new[] - { - DummyInstruction.Push(0, 1), - DummyInstruction.JmpCond(1,5), - - DummyInstruction.Push(2, 1), - DummyInstruction.Set(3, variable), - DummyInstruction.Jmp(4, 7), - - DummyInstruction.Push(5, 1), - DummyInstruction.Set(6, variable), - - DummyInstruction.Get(7, variable), - DummyInstruction.Pop(8, 1), - DummyInstruction.Ret(9) - }); - - var phiSourcesCapture = new CaptureGroup("sources"); - var variablesCapture = new CaptureGroup("variables"); - - // temp_vx = set(?) - var assignPattern = StatementPattern - .Assignment() - .WithExpression(ExpressionPattern - .Instruction(new DummyInstructionPattern(DummyOpCode.Set)) - .WithArguments(1)) - .CaptureVariables(variablesCapture); - - // variable = phi(?, ?) - var phiPattern = StatementPattern - .Phi() - .WithSources(2) - .CaptureSources(phiSourcesCapture); - - var set1Result = assignPattern.FindFirstMatch(cfg.Nodes[2].Contents.Instructions); - var set2Result = assignPattern.FindFirstMatch(cfg.Nodes[5].Contents.Instructions); - var phiResult = phiPattern.Match(cfg.Nodes[7].Contents.Header); - - Assert.True(set1Result.IsSuccess, "Node 2 was expected to contain an assignment statement."); - Assert.True(set2Result.IsSuccess, "Node 5 was expected to contain an assignment statement."); - Assert.True(phiResult.IsSuccess, "Node 7 was expected to start with a phi statement."); - - var sources = phiResult.Captures[phiSourcesCapture] - .Cast>() - .Select(s => s.Variable); - - var allVariables = new[] - { - (IVariable) set1Result.Captures[variablesCapture][0], - (IVariable) set2Result.Captures[variablesCapture][0] - }; - - Assert.Equal(allVariables.ToHashSet(), sources.ToHashSet()); - } - - [Fact] - public void LoopCounterShouldResultInOnlyOnePhiNode() - { - var counterVariable = new DummyVariable("i"); - - var cfg = ConstructAst(new[] - { - // i = initial(); - DummyInstruction.Push(0, 1), - DummyInstruction.Set(1, counterVariable), - - // loop: - // if (cond(i)) goto end; - DummyInstruction.Get(2, counterVariable), - DummyInstruction.JmpCond(3, 9), - - // Loop body. - DummyInstruction.Op(4, 0, 0), - - // i = next(i); - // goto loop - DummyInstruction.Get(5, counterVariable), - DummyInstruction.Op(6, 1, 1), - DummyInstruction.Set(7, counterVariable), - DummyInstruction.Jmp(8, 2), - - // end: - DummyInstruction.Ret(9) - }); - - // variable = phi(?, ?) - var phiPattern = StatementPattern - .Phi() - .WithSources(2); - - Assert.True(phiPattern.Matches(cfg.Nodes[2].Contents.Header)); - Assert.All(cfg.Nodes[0].Contents.Instructions, s => Assert.False(phiPattern.Matches(s))); - Assert.All(cfg.Nodes[4].Contents.Instructions, s => Assert.False(phiPattern.Matches(s))); - Assert.All(cfg.Nodes[9].Contents.Instructions, s => Assert.False(phiPattern.Matches(s))); - } - - [Fact] - public void JoiningPathsWithMultipleAssignmentsShouldOnlyUseLatestVersion() - { - var variable = new DummyVariable("temp"); - - var cfg = ConstructAst(new[] - { - // if (cond) goto else: - DummyInstruction.Push(0, 1), - DummyInstruction.JmpCond(1, 7), - - // temp = some_value (not used). - DummyInstruction.Push(2, 1), - DummyInstruction.Set(3, variable), - // temp = some_value (render previous value useless) - DummyInstruction.Push(4, 1), - DummyInstruction.Set(5, variable), - - // goto end - DummyInstruction.Jmp(6, 9), - - // else: - // temp = some_value - DummyInstruction.Push(7, 1), - DummyInstruction.Set(8, variable), - - // end: - // pop(temp) - DummyInstruction.Get(9, variable), - DummyInstruction.Pop(10, 1), - - // return - DummyInstruction.Ret(11) - }); - - var phiSourcesCapture = new CaptureGroup("sources"); - var variablesCapture = new CaptureGroup("variables"); - - // variable = phi(?, ?) - var phiPattern = StatementPattern - .Phi() - .WithSources(2) - .CaptureSources(phiSourcesCapture); - - // temp_vx = set(?) - var assignPattern = StatementPattern - .Assignment() - .WithExpression(ExpressionPattern - .Instruction(new DummyInstructionPattern(DummyOpCode.Set)) - .WithArguments(1)) - .CaptureVariables(variablesCapture); - - var assignment1Results = assignPattern - .FindAllMatches(cfg.Nodes[7].Contents.Instructions) - .ToArray(); - - var assignment2Results = assignPattern - .FindAllMatches(cfg.Nodes[2].Contents.Instructions) - .ToArray(); - - var phiResult = phiPattern.Match(cfg.Nodes[9].Contents.Header); - - Assert.True(assignment1Results.Length == 1, "Node 7 was expected to have one assignment to 'temp'."); - Assert.True(assignment2Results.Length == 2, "Node 2 was expected to have two assignments to 'temp'."); - Assert.True(phiResult.IsSuccess, "Node 9 was expected to start with a phi node with two sources."); - - var sources = phiResult.Captures[phiSourcesCapture] - .Cast>() - .Select(s => s.Variable); - - var allVariables = new[] - { - (IVariable) assignment1Results[0].Captures[variablesCapture][0], - (IVariable) assignment2Results[1].Captures[variablesCapture][0] - }; - - Assert.Equal(allVariables.ToHashSet(), sources.ToHashSet()); - } - - [Fact] - public void SwitchWithAssignmentsToSameVariableShouldResultInPhiNodeWithTheSameNumberOfSources() - { - var variable = new DummyVariable("temp"); - - var cfg = ConstructAst(new[] - { - // switch(some_value) - DummyInstruction.Push(0, 1), - DummyInstruction.Switch(1, 5, 8, 11), - - // default: - DummyInstruction.Push(2, 1), - DummyInstruction.Set(3, variable), - DummyInstruction.Jmp(4, 13), - - // case 0: - DummyInstruction.Push(5, 1), - DummyInstruction.Set(6, variable), - DummyInstruction.Jmp(7, 13), - - // case 1: - DummyInstruction.Push(8, 1), - DummyInstruction.Set(9, variable), - DummyInstruction.Jmp(10, 13), - - // case 2: - DummyInstruction.Push(11, 1), - DummyInstruction.Set(12, variable), - - // end: - DummyInstruction.Get(13, variable), - DummyInstruction.Pop(14, 1), - DummyInstruction.Ret(15) - }); - - // variable = phi(?, ?) - var phiPattern = StatementPattern - .Phi() - .WithSources(4); - - Assert.True(phiPattern.Matches(cfg.Nodes[13].Contents.Header), - "Node 13 was expected to start with a phi node with 4 sources."); - } - - [Fact] - public void VariableAccessThatHasntBeenWrittenToBeforeShouldntResultInPhi() - { - var variable = new DummyVariable("dummy"); - var cfg = ConstructAst(new[] - { - DummyInstruction.Get(0, variable), - DummyInstruction.Op(1, 1, 0), - DummyInstruction.Ret(2) - }); - - Assert.Single(cfg.Nodes); - Assert.All(cfg.Entrypoint.Contents.Instructions, Assert.IsNotType>); - } - } -} \ No newline at end of file diff --git a/test/Core/Echo.Ast.Tests/Construction/AstBuilderTest.cs b/test/Core/Echo.Ast.Tests/Construction/AstBuilderTest.cs new file mode 100644 index 00000000..15e46269 --- /dev/null +++ b/test/Core/Echo.Ast.Tests/Construction/AstBuilderTest.cs @@ -0,0 +1,802 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Echo.Ast.Construction; +using Echo.Ast.Patterns; +using Echo.Ast.Tests.Patterns; +using Echo.Code; +using Echo.ControlFlow; +using Echo.ControlFlow.Construction; +using Echo.ControlFlow.Construction.Static; +using Echo.ControlFlow.Regions; +using Echo.ControlFlow.Regions.Detection; +using Echo.Platforms.DummyPlatform.Code; +using Echo.Platforms.DummyPlatform.ControlFlow; +using Xunit; + +namespace Echo.Ast.Tests.Construction; + +public class AstBuilderTest +{ + private static ControlFlowGraph> ConstructGraph( + IList instructions, + params ExceptionHandlerRange[] exceptionHandlerRanges) + { + var cfg = new StaticFlowGraphBuilder( + DummyArchitecture.Instance, + instructions, + DummyStaticSuccessorResolver.Instance) + .ConstructFlowGraph(0, exceptionHandlerRanges); + + cfg.DetectExceptionHandlerRegions(exceptionHandlerRanges); + + return cfg.ToAst(DummyPurityClassifier.Instance); + } + + [Fact] + public void StatementNoArguments() + { + var result = ConstructGraph(new[] + { + DummyInstruction.Ret(0), + }); + + var node = Assert.Single(result.Nodes); + var statement = Assert.Single(node.Contents.Instructions); + + var pattern = StatementPattern + .Instruction(new DummyInstructionPattern(DummyOpCode.Ret)); + Assert.True(pattern.Match(statement).IsSuccess); + } + + [Fact] + public void StatementWithTwoArguments() + { + // Construct + var cfg = ConstructGraph(new[] + { + // pop(push(), push()) + DummyInstruction.Push(0, 1), + DummyInstruction.Push(1, 1), + DummyInstruction.Pop(2, 2), + + // ret() + DummyInstruction.Ret(3) + }); + + // Verify + var node = Assert.Single(cfg.Nodes); + + var arguments = new CaptureGroup("arguments"); + + // pop(push(), push()) + var match = StatementPattern + .Expression(ExpressionPattern + .Instruction(new DummyInstructionPattern(DummyOpCode.Pop)) + .WithArguments( + ExpressionPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Push)), + ExpressionPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Push)) + ).CaptureArguments(arguments) + ) + .Match(node.Contents.Instructions[0]); + + Assert.True(match.IsSuccess); + Assert.Equal( + new long[] {0, 1}, + match.Captures[arguments] + .Cast>() + .Select(x => x.Instruction.Offset)); + } + + [Fact] + public void TwoStatementWithDifferentArguments() + { + // Construct + var cfg = ConstructGraph(new[] + { + // pop(push()) + DummyInstruction.Push(0, 1), + DummyInstruction.Pop(1, 1), + + // pop(push(), push()) + DummyInstruction.Push(2, 1), + DummyInstruction.Push(3, 1), + DummyInstruction.Pop(4, 2), + + // ret() + DummyInstruction.Ret(5) + }); + + // Verify + var node = Assert.Single(cfg.Nodes); + + var arguments = new CaptureGroup("arguments"); + + // pop(push()) + var match = StatementPattern + .Expression(ExpressionPattern + .Instruction(new DummyInstructionPattern(DummyOpCode.Pop)) + .WithArguments( + ExpressionPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Push)) + ).CaptureArguments(arguments) + ) + .Match(node.Contents.Instructions[0]); + Assert.True(match.IsSuccess); + Assert.Equal( + new long[] {0}, + match.Captures[arguments] + .Cast>() + .Select(x => x.Instruction.Offset)); + + // pop(push(), push()) + match = StatementPattern + .Expression(ExpressionPattern + .Instruction(new DummyInstructionPattern(DummyOpCode.Pop)) + .WithArguments( + ExpressionPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Push)), + ExpressionPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Push)) + ).CaptureArguments(arguments) + ) + .Match(node.Contents.Instructions[1]); + + Assert.True(match.IsSuccess); + Assert.Equal( + new long[] {2, 3}, + match.Captures[arguments] + .Cast>() + .Select(x => x.Instruction.Offset)); + } + + [Fact] + public void ExpressionWithMultipleReturnValues() + { + // Construct + var cfg = ConstructGraph(new[] + { + // x, y = op() + DummyInstruction.Op(0, 0, 2), + + // pop(y) + // pop(x) + DummyInstruction.Pop(1, 1), + DummyInstruction.Pop(2, 1), + + // ret() + DummyInstruction.Ret(3) + }); + + Assert.True(StatementPattern + .Assignment( + new[] {Pattern.Any(), Pattern.Any()}, + ExpressionPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Op)) + ).Match(cfg.Nodes[0].Contents.Instructions[0]).IsSuccess); + } + + [Fact] + public void NestedExpressions() + { + // Construct + var cfg = ConstructGraph(new[] + { + // op(push(), op(push())) + DummyInstruction.Push(0, 1), + DummyInstruction.Push(1, 1), + DummyInstruction.Op(2, 1, 1), + DummyInstruction.Op(3, 2, 0), + + // ret() + DummyInstruction.Ret(4) + }); + + // Verify + var node = Assert.Single(cfg.Nodes); + + var match = StatementPattern + .Expression(ExpressionPattern + .Instruction(new DummyInstructionPattern(DummyOpCode.Op)) + .WithArguments( + ExpressionPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Push)), + ExpressionPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Op)) + .WithArguments( + ExpressionPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Push)) + ) + ) + ) + .Match(node.Contents.Instructions[0]); + + Assert.True(match.IsSuccess); + } + + [Fact] + public void PushArgumentBeforeImpureStatement() + { + // Construct + var cfg = ConstructGraph(new[] + { + // tmp1 = push() + // tmp2 = push() + DummyInstruction.Push(0, 1), + DummyInstruction.Push(1, 1), + + // op(push()) + DummyInstruction.Push(2, 1), + DummyInstruction.Op(3, 1, 0), + + // pop(tmp2) + // pop(tmp1) + DummyInstruction.Pop(4, 1), + DummyInstruction.Pop(5, 1), + + // ret() + DummyInstruction.Ret(6) + }); + + // Verify + var node = Assert.Single(cfg.Nodes); + var variable = new CaptureGroup("variable"); + + // tmp1 = push() + // tmp2 = push() + var pattern1 = StatementPattern + .Assignment( + Pattern.Any().CaptureAs(variable), + ExpressionPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Push)) + ); + var match1 = pattern1.Match(node.Contents.Instructions[0]); + var match2 = pattern1.Match(node.Contents.Instructions[1]); + + Assert.True(match1.IsSuccess); + Assert.True(match2.IsSuccess); + + // op(push()) + Assert.True(StatementPattern + .Expression(ExpressionPattern + .Instruction(new DummyInstructionPattern(DummyOpCode.Op)) + .WithArguments(1) + ) + .Match(node.Contents.Instructions[2]).IsSuccess); + + // pop(tmp) + var pattern2 = StatementPattern + .Expression(ExpressionPattern + .Instruction(new DummyInstructionPattern(DummyOpCode.Pop)) + .WithArguments(ExpressionPattern.Variable( + Pattern.Any().CaptureAs(variable)) + ) + ); + var match3 = pattern2.Match(node.Contents.Instructions[3]); + var match4 = pattern2.Match(node.Contents.Instructions[4]); + + Assert.True(match3.IsSuccess); + Assert.True(match4.IsSuccess); + + Assert.Same(match1.Captures[variable][0], match4.Captures[variable][0]); + Assert.Same(match2.Captures[variable][0], match3.Captures[variable][0]); + } + + [Fact] + public void TwoNodes() + { + // Construct + var cfg = ConstructGraph(new[] + { + DummyInstruction.Op(0, 0,0), + DummyInstruction.Jmp(1, 10), + + DummyInstruction.Op(10, 0,0), + DummyInstruction.Ret(11) + }); + + Assert.Equal(2, cfg.Nodes.Count); + var (n1, n2) = (cfg.Nodes[0], cfg.Nodes[10]); + + Assert.True(StatementPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Op)) + .Match(n1.Contents.Instructions[0]) + .IsSuccess); + Assert.True(StatementPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Jmp)) + .Match(n1.Contents.Instructions[1]) + .IsSuccess); + + Assert.True(StatementPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Op)) + .Match(n2.Contents.Instructions[0]) + .IsSuccess); + Assert.True(StatementPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Ret)) + .Match(n2.Contents.Instructions[1]) + .IsSuccess); + + Assert.Same(n2, n1.UnconditionalNeighbour); + } + + [Fact] + public void TwoNodesWithStackDeltas() + { + // Construct + var cfg = ConstructGraph(new[] + { + DummyInstruction.Push(0, 1), + DummyInstruction.Jmp(1, 10), + + DummyInstruction.Pop(10, 1), + DummyInstruction.Ret(11) + }); + + Assert.Equal(2, cfg.Nodes.Count); + var (n1, n2) = (cfg.Nodes[0], cfg.Nodes[10]); + + var variable = new CaptureGroup("variable"); + + // out = push() + var match1 = StatementPattern + .Assignment( + Pattern.Any().CaptureAs(variable), + ExpressionPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Push)) + ) + .Match(n1.Contents.Instructions[0]); + Assert.True(match1.IsSuccess); + + // in = out + var match2 = StatementPattern + .Assignment( + Pattern.Any().CaptureAs(variable), + ExpressionPattern.Variable(Pattern.Any().CaptureAs(variable)) + ) + .Match(n2.Contents.Instructions[0]); + Assert.True(match2.IsSuccess); + + // pop(in) + var match3 = StatementPattern + .Expression(ExpressionPattern + .Instruction(new DummyInstructionPattern(DummyOpCode.Pop)) + .WithArguments( + ExpressionPattern.Variable(Pattern.Any().CaptureAs(variable)) + ) + ) + .Match(n2.Contents.Instructions[1]); + Assert.True(match3.IsSuccess); + + Assert.Same(match1.Captures[variable][0], match2.Captures[variable][1]); + Assert.Same(match2.Captures[variable][0], match3.Captures[variable][0]); + + Assert.Same(n2, n1.UnconditionalNeighbour); + } + + [Fact] + public void TwoNodesPushBeforeImpure() + { + // Construct + var cfg = ConstructGraph(new[] + { + DummyInstruction.Push(0, 1), + DummyInstruction.Op(1, 0, 0), + DummyInstruction.Jmp(2, 10), + + DummyInstruction.Pop(10, 1), + DummyInstruction.Ret(11) + }); + + var block = cfg.Nodes[0].Contents; + Assert.IsAssignableFrom>(block.Instructions[0]); + Assert.IsAssignableFrom>(block.Instructions[1]); + } + + [Fact] + public void TwoNodesWithNestedStackDelta() + { + // Construct + var cfg = ConstructGraph(new[] + { + DummyInstruction.Push(0, 1), + DummyInstruction.Push(1, 1), + DummyInstruction.Jmp(2, 10), + + DummyInstruction.Pop(10, 1), + DummyInstruction.Pop(11, 1), + DummyInstruction.Ret(12) + }); + + // Verify + var variable = new CaptureGroup("variable"); + var value = new CaptureGroup("value"); + var pattern = StatementPattern.Assignment( + Pattern.Any().CaptureAs(variable), + ExpressionPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Push)).CaptureAs(value) + ); + + // Ensure expressions are pushed as variables. + var match1 = pattern.Match(cfg.Nodes[0].Contents.Instructions[0]); + var match2 = pattern.Match(cfg.Nodes[0].Contents.Instructions[1]); + Assert.True(match1.IsSuccess); + Assert.True(match2.IsSuccess); + + // Ensure order of operations is preserved. + var a = (InstructionExpression) match1.Captures[value][0]; + var b = (InstructionExpression) match2.Captures[value][0]; + Assert.Equal(0, a.Instruction.Offset); + Assert.Equal(1, b.Instruction.Offset); + } + + [Fact] + public void StackDeltaConvergingControlFlowPaths() + { + // Construct + var cfg = ConstructGraph(new[] + { + // jmpcond(push()) + DummyInstruction.Push(0, 1), + DummyInstruction.JmpCond(1, 10), + + // x = push() + DummyInstruction.Push(2, 1), + DummyInstruction.Jmp(3, 11), + + // y = push() + DummyInstruction.Push(10, 1), + + // pop(phi(x, y)) + DummyInstruction.Pop(11, 1), + DummyInstruction.Ret(12) + }); + + // Verify graph structure. + var (n1, n2, n3, n4) = (cfg.Nodes[0], cfg.Nodes[2], cfg.Nodes[10], cfg.Nodes[11]); + Assert.Same(n2, n1.UnconditionalNeighbour); + Assert.Same(n3, Assert.Single(n1.ConditionalEdges).Target); + Assert.Same(n4, n2.UnconditionalNeighbour); + Assert.Same(n4, n3.UnconditionalNeighbour); + + // Verify phi variables. + var variable = new CaptureGroup("variables"); + + var match1 = StatementPattern.Assignment( + Pattern.Any().CaptureAs(variable), + ExpressionPattern.Any() + ).Match(n2.Contents.Instructions[0]); + var match2 = StatementPattern.Assignment( + Pattern.Any().CaptureAs(variable), + ExpressionPattern.Any() + ).Match(n3.Contents.Instructions[0]); + var match3 = StatementPattern.Phi() + .WithSources(2) + .CaptureSources(variable) + .Match(n4.Contents.Instructions[0]); + + Assert.True(match1.IsSuccess); + Assert.True(match2.IsSuccess); + Assert.True(match3.IsSuccess); + + var sources = match3.Captures[variable] + .OfType>() + .Select(x => x.Variable) + .ToArray(); + + Assert.Contains(match1.Captures[variable][0], sources); + Assert.Contains(match2.Captures[variable][0], sources); + } + + [Fact] + public void StackDeltaTwoLayers() + { + // Construct + var cfg = ConstructGraph(new[] + { + // jmpcond(push()) + DummyInstruction.Push(0, 1), + DummyInstruction.JmpCond(1, 20), + + // jmpcond(push()) + DummyInstruction.Push(2, 1), + DummyInstruction.JmpCond(3, 10), + + // x = push() + DummyInstruction.Push(4, 1), + DummyInstruction.Jmp(5, 21), + + // y = push() + DummyInstruction.Push(10, 1), + DummyInstruction.Jmp(11, 21), + + // z = push() + DummyInstruction.Push(20, 1), + + // pop(phi(x, y, z)) + DummyInstruction.Pop(21, 1), + DummyInstruction.Ret(22) + }); + + // Verify graph structure. + var (n1, n2, n3, n4, n5, n6) = + (cfg.Nodes[0], cfg.Nodes[2], cfg.Nodes[4], cfg.Nodes[10], cfg.Nodes[20], cfg.Nodes[21]); + + Assert.Same(n2, n1.UnconditionalNeighbour); + Assert.Same(n5, Assert.Single(n1.ConditionalEdges).Target); + + Assert.Same(n3, n2.UnconditionalNeighbour); + Assert.Same(n4, Assert.Single(n2.ConditionalEdges).Target); + + Assert.Same(n6, n3.UnconditionalNeighbour); + Assert.Same(n6, n4.UnconditionalNeighbour); + Assert.Same(n6, n5.UnconditionalNeighbour); + + // Verify phi variables. + var variable = new CaptureGroup("variables"); + + var match1 = StatementPattern.Assignment( + Pattern.Any().CaptureAs(variable), + ExpressionPattern.Any() + ).Match(n3.Contents.Instructions[0]); + var match2 = StatementPattern.Assignment( + Pattern.Any().CaptureAs(variable), + ExpressionPattern.Any() + ).Match(n4.Contents.Instructions[0]); + var match3 = StatementPattern.Assignment( + Pattern.Any().CaptureAs(variable), + ExpressionPattern.Any() + ).Match(n5.Contents.Instructions[0]); + var match4 = StatementPattern.Phi() + .WithSources(3) + .CaptureSources(variable) + .Match(n6.Contents.Instructions[0]); + + Assert.True(match1.IsSuccess); + Assert.True(match2.IsSuccess); + Assert.True(match3.IsSuccess); + Assert.True(match4.IsSuccess); + + var sources = match4.Captures[variable] + .OfType>() + .Select(x => x.Variable) + .ToArray(); + + Assert.Contains(match1.Captures[variable][0], sources); + Assert.Contains(match2.Captures[variable][0], sources); + Assert.Contains(match3.Captures[variable][0], sources); + } + + [Fact] + public void ConditionalReplaceStackSlot() + { + var cfg = ConstructGraph(new[] + { + DummyInstruction.Push(0, 1), + DummyInstruction.Op(1, 1, 2), + DummyInstruction.JmpCond(2, 5), + + DummyInstruction.Pop(3, 1), + DummyInstruction.Push(4, 1), + + DummyInstruction.Pop(5, 1), + DummyInstruction.Ret(6), + }); + + var variables = new CaptureGroup("variables"); + + var pattern = StatementPattern.Assignment( + Pattern.Any().CaptureAs(variables), + ExpressionPattern.Any() + ); + + var match1 = pattern.Match(cfg.Nodes[0].Contents.Instructions[1]); + var match2 = pattern.Match(cfg.Nodes[3].Contents.Instructions[^1]); + + var match3 = StatementPattern.Phi() + .WithSources(2) + .CaptureSources(variables) + .Match(cfg.Nodes[5].Contents.Instructions[0]); + + Assert.True(match1.IsSuccess); + Assert.True(match2.IsSuccess); + Assert.True(match3.IsSuccess); + + var sources = match3.Captures[variables] + .OfType>() + .Select(x => x.Variable) + .ToArray(); + + Assert.Contains(match1.Captures[variables][0], sources); + Assert.Contains(match2.Captures[variables][0], sources); + } + + [Fact] + public void ConditionalReplaceStackSlotNested() + { + var cfg = ConstructGraph(new[] + { + DummyInstruction.Push(0, 1), + + DummyInstruction.Push(1, 1), + DummyInstruction.Op(2, 1, 2), + DummyInstruction.JmpCond(3, 6), + + DummyInstruction.Pop(4, 1), + DummyInstruction.Push(5, 1), + + DummyInstruction.Pop(6, 2), + DummyInstruction.Ret(7), + }); + + var variables = new CaptureGroup("variables"); + + var pattern = StatementPattern.Assignment( + Pattern.Any().CaptureAs(variables), + ExpressionPattern.Any() + ); + + var match1 = pattern.Match(cfg.Nodes[0].Contents.Instructions[^2]); + var match2 = pattern.Match(cfg.Nodes[4].Contents.Instructions[^1]); + + var match3 = StatementPattern.Phi() + .WithSources(2) + .CaptureSources(variables) + .Match(cfg.Nodes[6].Contents.Instructions[1]); + + Assert.True(match1.IsSuccess); + Assert.True(match3.IsSuccess); + + var sources = match3.Captures[variables] + .OfType>() + .Select(x => x.Variable) + .ToArray(); + + Assert.Contains(match1.Captures[variables][0], sources); + Assert.Contains(match2.Captures[variables][0], sources); + } + + [Fact] + public void Loop() + { + // Construct + var cfg = ConstructGraph(new[] + { + DummyInstruction.Op(0, 0, 0), + + DummyInstruction.Op(1, 0, 0), + + DummyInstruction.Push(2, 1), + DummyInstruction.JmpCond(3, 1), + + DummyInstruction.Ret(4), + }); + + // Verify + var (n1, n2, n3) = (cfg.Nodes[0], cfg.Nodes[1], cfg.Nodes[4]); + + Assert.Same(n2, n1.UnconditionalNeighbour); + Assert.Same(n2, Assert.Single(n2.ConditionalEdges).Target); + Assert.Same(n3, n2.UnconditionalNeighbour); + } + + [Fact] + public void LoopWithStackDelta() + { + // Construct + var cfg = ConstructGraph(new[] + { + DummyInstruction.Push(0, 1), + + // loop start: + DummyInstruction.Op(1, 1, 0), + + DummyInstruction.Push(2, 1), + + DummyInstruction.Push(3, 1), + DummyInstruction.JmpCond(4, 1), + + DummyInstruction.Ret(5), + }); + + // Verify + var (n1, n2, n3) = (cfg.Nodes[0], cfg.Nodes[1], cfg.Nodes[5]); + Assert.Same(n2, n1.UnconditionalNeighbour); + Assert.Same(n2, Assert.Single(n2.ConditionalEdges).Target); + Assert.Same(n3, n2.UnconditionalNeighbour); + + var variable = new CaptureGroup("variable"); + + var match1 = StatementPattern + .Assignment( + Pattern.Any().CaptureAs(variable), + ExpressionPattern.Any()) + .Match(n1.Contents.Instructions[0]); + var match2 = StatementPattern.Phi() + .WithSources(2) + .CaptureSources(variable) + .Match(n2.Contents.Instructions[0]); + var match3 = StatementPattern + .Assignment( + Pattern.Any().CaptureAs(variable), + ExpressionPattern.Any()) + .Match(n2.Contents.Instructions[^2]); + + Assert.True(match1.IsSuccess); + Assert.True(match2.IsSuccess); + Assert.True(match3.IsSuccess); + + var sources = match2.Captures[variable] + .OfType>() + .Select(x => x.Variable) + .ToArray(); + + Assert.Contains(match1.Captures[variable][0], sources); + Assert.Contains(match3.Captures[variable][0], sources); + } + + [Fact] + public void Handler() + { + // Construct + var cfg = ConstructGraph(new[] + { + DummyInstruction.Op(0, 0, 0), + + // try start: + DummyInstruction.Op(1, 0, 0), + DummyInstruction.Jmp(2, 5), + + // handler start + DummyInstruction.Op(3, 0, 0), + DummyInstruction.Jmp(4, 5), + + DummyInstruction.Ret(5), + }, new ExceptionHandlerRange( + new AddressRange(1, 3), + new AddressRange(3, 5) + )); + + // Verify + var (n1, n2, n3, n4) = (cfg.Nodes[0], cfg.Nodes[1], cfg.Nodes[3], cfg.Nodes[5]); + + var eh = Assert.IsAssignableFrom>>(Assert.Single(cfg.Regions)); + Assert.Same(n2, eh.ProtectedRegion.EntryPoint); + Assert.Same(n3, Assert.Single(eh.Handlers).GetEntryPoint()); + } + + [Fact] + public void HandlerPopException() + { + // Construct + var cfg = ConstructGraph(new[] + { + DummyInstruction.Op(0, 0, 0), + + // try start + DummyInstruction.Op(1, 0, 0), + DummyInstruction.Jmp(2, 6), + + // handler start + DummyInstruction.Pop(3, 1), + DummyInstruction.Op(4, 0, 0), + DummyInstruction.Jmp(5, 6), + + DummyInstruction.Ret(6), + }, new ExceptionHandlerRange( + new AddressRange(1, 3), + new AddressRange(3, 6) + )); + + // Verify + var (n1, n2, n3, n4) = (cfg.Nodes[0], cfg.Nodes[1], cfg.Nodes[3], cfg.Nodes[6]); + + var eh = Assert.IsAssignableFrom>>(Assert.Single(cfg.Regions)); + Assert.Same(n2, eh.ProtectedRegion.EntryPoint); + Assert.Same(n3, Assert.Single(eh.Handlers).GetEntryPoint()); + } + + [Fact] + public void StackUnderflow() + { + // Construct + var cfg = ConstructGraph(new[] + { + DummyInstruction.Pop(0,1), + DummyInstruction.Ret(1), + }); + + var n1 = Assert.Single(cfg.Nodes); + Assert.True(StatementPattern + .Phi() + .WithSources(0) + .Match(n1.Contents.Instructions[0]) + .IsSuccess); + } + +} \ No newline at end of file diff --git a/test/Core/Echo.Ast.Tests/Echo.Ast.Tests.csproj b/test/Core/Echo.Ast.Tests/Echo.Ast.Tests.csproj index afc5bc4f..8fbe2bc9 100644 --- a/test/Core/Echo.Ast.Tests/Echo.Ast.Tests.csproj +++ b/test/Core/Echo.Ast.Tests/Echo.Ast.Tests.csproj @@ -18,4 +18,9 @@ + + + + + diff --git a/test/Core/Echo.Ast.Tests/Patterns/PhiStatementPatternTest.cs b/test/Core/Echo.Ast.Tests/Patterns/PhiStatementPatternTest.cs index 76755d62..ae0be3c2 100644 --- a/test/Core/Echo.Ast.Tests/Patterns/PhiStatementPatternTest.cs +++ b/test/Core/Echo.Ast.Tests/Patterns/PhiStatementPatternTest.cs @@ -42,7 +42,7 @@ public void AnyPhiWithSpecificTargetPattern() var result = pattern.Match(statement); Assert.True(result.IsSuccess); Assert.Contains(group, result.Captures); - Assert.Contains(statement.Target, result.Captures[group]); + Assert.Contains(statement.Representative, result.Captures[group]); } [Fact] diff --git a/test/Core/Echo.ControlFlow.Tests/Analysis/Domination/DominanceFrontierTest.cs b/test/Core/Echo.ControlFlow.Tests/Analysis/Domination/DominanceFrontierTest.cs index 42548d98..13a13667 100644 --- a/test/Core/Echo.ControlFlow.Tests/Analysis/Domination/DominanceFrontierTest.cs +++ b/test/Core/Echo.ControlFlow.Tests/Analysis/Domination/DominanceFrontierTest.cs @@ -15,7 +15,7 @@ public void Simple() var n = new ControlFlowNode(0, 0); cfg.Nodes.Add(n); - cfg.Entrypoint = n; + cfg.EntryPoint = n; var tree = DominatorTree.FromGraph(cfg); Assert.Empty(tree.GetDominanceFrontier(n)); @@ -35,7 +35,7 @@ public void Path() nodes[i - 1].ConnectWith(nodes[i]); } - cfg.Entrypoint = nodes[0]; + cfg.EntryPoint = nodes[0]; var tree = DominatorTree.FromGraph(cfg); Assert.All(nodes, n => Assert.Empty(tree.GetDominanceFrontier(n))); @@ -58,7 +58,7 @@ public void If() nodes[1].ConnectWith(nodes[3]); nodes[2].ConnectWith(nodes[3]); - cfg.Entrypoint = nodes[0]; + cfg.EntryPoint = nodes[0]; var tree = DominatorTree.FromGraph(cfg); Assert.Equal(new[] {nodes[3]}, tree.GetDominanceFrontier(nodes[1])); @@ -82,7 +82,7 @@ public void Loop() nodes[2].ConnectWith(nodes[1], ControlFlowEdgeType.Conditional); nodes[2].ConnectWith(nodes[3]); - cfg.Entrypoint = nodes[0]; + cfg.EntryPoint = nodes[0]; var tree = DominatorTree.FromGraph(cfg); Assert.Equal(new HashSet {nodes[2]}, tree.GetDominanceFrontier(nodes[1])); @@ -123,7 +123,7 @@ public void Complex() nodes[9].ConnectWith(nodes[1]); nodes[10].ConnectWith(nodes[7]); - cfg.Entrypoint = nodes[0]; + cfg.EntryPoint = nodes[0]; var tree = DominatorTree.FromGraph(cfg); Assert.Empty(tree.GetDominanceFrontier(nodes[0])); diff --git a/test/Core/Echo.ControlFlow.Tests/Analysis/Domination/DominatorTreeTest.cs b/test/Core/Echo.ControlFlow.Tests/Analysis/Domination/DominatorTreeTest.cs index 76918a2c..05892790 100644 --- a/test/Core/Echo.ControlFlow.Tests/Analysis/Domination/DominatorTreeTest.cs +++ b/test/Core/Echo.ControlFlow.Tests/Analysis/Domination/DominatorTreeTest.cs @@ -15,8 +15,8 @@ public void SingleNode() var graph = TestGraphs.CreateSingularGraph(); var dominatorTree = DominatorTree.FromGraph(graph); - Assert.Equal(graph.Entrypoint, dominatorTree.Root.OriginalNode); - Assert.True(dominatorTree.Dominates(graph.Entrypoint, graph.Entrypoint)); + Assert.Equal(graph.EntryPoint, dominatorTree.Root.OriginalNode); + Assert.True(dominatorTree.Dominates(graph.EntryPoint, graph.EntryPoint)); } [Fact] @@ -30,7 +30,7 @@ public void Path() var n4 = graph.GetNodeByOffset(3); var dominatorTree = DominatorTree.FromGraph(graph); - Assert.Equal(graph.Entrypoint, dominatorTree.Root.OriginalNode); + Assert.Equal(graph.EntryPoint, dominatorTree.Root.OriginalNode); Assert.True(dominatorTree.Dominates(n1, n1)); Assert.True(dominatorTree.Dominates(n1, n2)); @@ -64,7 +64,7 @@ public void If() var n4 = graph.GetNodeByOffset(4); var dominatorTree = DominatorTree.FromGraph(graph); - Assert.Equal(graph.Entrypoint, dominatorTree.Root.OriginalNode); + Assert.Equal(graph.EntryPoint, dominatorTree.Root.OriginalNode); Assert.True(dominatorTree.Dominates(n1, n1)); Assert.True(dominatorTree.Dominates(n1, n2)); @@ -98,7 +98,7 @@ public void Loop() var n4 = graph.GetNodeByOffset(4); var dominatorTree = DominatorTree.FromGraph(graph); - Assert.Equal(graph.Entrypoint, dominatorTree.Root.OriginalNode); + Assert.Equal(graph.EntryPoint, dominatorTree.Root.OriginalNode); Assert.True(dominatorTree.Dominates(n1, n1)); Assert.True(dominatorTree.Dominates(n1, n2)); @@ -129,7 +129,7 @@ public void ExceptionHandler() for (int i = 0; i < 7; i++) cfg.Nodes.Add(new ControlFlowNode(i)); - cfg.Entrypoint = cfg.Nodes[0]; + cfg.EntryPoint = cfg.Nodes[0]; cfg.Nodes[0].ConnectWith(cfg.Nodes[1]); cfg.Nodes[1].ConnectWith(cfg.Nodes[2], ControlFlowEdgeType.Conditional); @@ -142,7 +142,7 @@ public void ExceptionHandler() var ehRegion = new ExceptionHandlerRegion(); cfg.Regions.Add(ehRegion); - ehRegion.ProtectedRegion.Entrypoint = cfg.Nodes[1]; + ehRegion.ProtectedRegion.EntryPoint = cfg.Nodes[1]; ehRegion.ProtectedRegion.Nodes.AddRange(new[] { cfg.Nodes[1], @@ -154,7 +154,7 @@ public void ExceptionHandler() var handler = new HandlerRegion(); ehRegion.Handlers.Add(handler); handler.Contents.Nodes.Add(cfg.Nodes[5]); - handler.Contents.Entrypoint = cfg.Nodes[5]; + handler.Contents.EntryPoint = cfg.Nodes[5]; var tree = DominatorTree.FromGraph(cfg); Assert.True(tree.Dominates(cfg.Nodes[1], cfg.Nodes[6])); diff --git a/test/Core/Echo.ControlFlow.Tests/Construction/Static/StaticGraphBuilderTest.cs b/test/Core/Echo.ControlFlow.Tests/Construction/Static/StaticGraphBuilderTest.cs index 01b3fc66..d3f9b166 100644 --- a/test/Core/Echo.ControlFlow.Tests/Construction/Static/StaticGraphBuilderTest.cs +++ b/test/Core/Echo.ControlFlow.Tests/Construction/Static/StaticGraphBuilderTest.cs @@ -40,7 +40,7 @@ public void SingleBlock() Assert.Single(graph.Nodes); Assert.Equal(instructions, graph.Nodes.First().Contents.Instructions); - Assert.Equal(graph.Nodes.First(), graph.Entrypoint); + Assert.Equal(graph.Nodes.First(), graph.EntryPoint); } [Fact] @@ -106,11 +106,11 @@ public void If() var graph = BuildControlFlowGraph(instructions); Assert.Equal(4, graph.Nodes.Count); - Assert.Single(graph.Entrypoint.ConditionalEdges); - Assert.NotNull(graph.Entrypoint.UnconditionalEdge); + Assert.Single(graph.EntryPoint.ConditionalEdges); + Assert.NotNull(graph.EntryPoint.UnconditionalEdge); Assert.Equal( - graph.Entrypoint.UnconditionalNeighbour.UnconditionalNeighbour, - graph.Entrypoint.ConditionalEdges.First().Target.UnconditionalNeighbour); + graph.EntryPoint.UnconditionalNeighbour.UnconditionalNeighbour, + graph.EntryPoint.ConditionalEdges.First().Target.UnconditionalNeighbour); } [Fact] @@ -145,11 +145,11 @@ public void Loop() Assert.Equal(4, graph.Nodes.Count); // Entrypoint. - Assert.NotNull(graph.Entrypoint.UnconditionalNeighbour); - Assert.Empty(graph.Entrypoint.ConditionalEdges); + Assert.NotNull(graph.EntryPoint.UnconditionalNeighbour); + Assert.Empty(graph.EntryPoint.ConditionalEdges); // Loop header - var loopHeader = graph.Entrypoint.UnconditionalNeighbour; + var loopHeader = graph.EntryPoint.UnconditionalNeighbour; Assert.NotNull(loopHeader.UnconditionalEdge); Assert.Single(loopHeader.ConditionalEdges); diff --git a/test/Core/Echo.ControlFlow.Tests/ControlFlowGraphTest.cs b/test/Core/Echo.ControlFlow.Tests/ControlFlowGraphTest.cs index 468b1336..ae568648 100644 --- a/test/Core/Echo.ControlFlow.Tests/ControlFlowGraphTest.cs +++ b/test/Core/Echo.ControlFlow.Tests/ControlFlowGraphTest.cs @@ -54,10 +54,10 @@ public void Entrypoint() var node = new ControlFlowNode(0, 0); graph.Nodes.Add(node); - graph.Entrypoint = node; + graph.EntryPoint = node; Assert.Single(graph.Nodes); - Assert.Equal(node, graph.Entrypoint); + Assert.Equal(node, graph.EntryPoint); } [Fact] diff --git a/test/Core/Echo.ControlFlow.Tests/ControlFlowNodeTest.cs b/test/Core/Echo.ControlFlow.Tests/ControlFlowNodeTest.cs index 19fae22f..26432deb 100644 --- a/test/Core/Echo.ControlFlow.Tests/ControlFlowNodeTest.cs +++ b/test/Core/Echo.ControlFlow.Tests/ControlFlowNodeTest.cs @@ -445,7 +445,7 @@ public void SplittingNodeShouldPreserveParentRegion() handler.Contents.Nodes.Add(n2); n1.ConnectWith(n2, ControlFlowEdgeType.Abnormal); - graph.Entrypoint = n1; + graph.EntryPoint = n1; var (first, second) = n1.SplitAtIndex(2); Assert.Equal(first.ParentRegion, second.ParentRegion); diff --git a/test/Core/Echo.ControlFlow.Tests/ControlFlowTestGraphs.cs b/test/Core/Echo.ControlFlow.Tests/ControlFlowTestGraphs.cs index 17da84b4..942877e6 100644 --- a/test/Core/Echo.ControlFlow.Tests/ControlFlowTestGraphs.cs +++ b/test/Core/Echo.ControlFlow.Tests/ControlFlowTestGraphs.cs @@ -11,7 +11,7 @@ public static ControlFlowGraph CreateSingularGraph() var n1 = new ControlFlowNode(0, DummyInstruction.Ret(0)); graph.Nodes.Add(n1); - graph.Entrypoint = n1; + graph.EntryPoint = n1; return graph; } @@ -33,7 +33,7 @@ public static ControlFlowGraph CreatePath() DummyInstruction.Ret(3)); graph.Nodes.AddRange(new[] {n1, n2, n3, n4}); - graph.Entrypoint = n1; + graph.EntryPoint = n1; n1.ConnectWith(n2); n2.ConnectWith(n3); @@ -57,7 +57,7 @@ public static ControlFlowGraph CreateIf() DummyInstruction.Ret(3)); graph.Nodes.AddRange(new[] {n1, n2, n3}); - graph.Entrypoint = n1; + graph.EntryPoint = n1; n1.ConnectWith(n2); n1.ConnectWith(n3, ControlFlowEdgeType.Conditional); @@ -84,7 +84,7 @@ public static ControlFlowGraph CreateIfElse() DummyInstruction.Ret(4)); graph.Nodes.AddRange(new[] {n1, n2, n3, n4}); - graph.Entrypoint = n1; + graph.EntryPoint = n1; n1.ConnectWith(n2); n1.ConnectWith(n3, ControlFlowEdgeType.Conditional); @@ -119,7 +119,7 @@ public static ControlFlowGraph CreateIfElseNested() DummyInstruction.Ret(7)); graph.Nodes.AddRange(new[] {n1, n2, n3, n4, n5, n6}); - graph.Entrypoint = n1; + graph.EntryPoint = n1; n1.ConnectWith(n2); n1.ConnectWith(n6, ControlFlowEdgeType.Conditional); @@ -150,7 +150,7 @@ public static ControlFlowGraph CreateLoop() DummyInstruction.Ret(4)); graph.Nodes.AddRange(new[] {n1, n2, n3, n4}); - graph.Entrypoint = n1; + graph.EntryPoint = n1; n1.ConnectWith(n3); n3.ConnectWith(n2, ControlFlowEdgeType.Conditional); @@ -178,7 +178,7 @@ public static ControlFlowGraph CreateSwitch() DummyInstruction.Ret(5)); graph.Nodes.AddRange(new[] {n1, n2, n3, n4, n5, n6}); - graph.Entrypoint = n1; + graph.EntryPoint = n1; n1.ConnectWith(n2); n1.ConnectWith(n3,ControlFlowEdgeType.Conditional); diff --git a/test/Core/Echo.ControlFlow.Tests/Serialization/Blocks/BlockSorterTest.cs b/test/Core/Echo.ControlFlow.Tests/Serialization/Blocks/BlockSorterTest.cs index 206e802e..3a948e07 100644 --- a/test/Core/Echo.ControlFlow.Tests/Serialization/Blocks/BlockSorterTest.cs +++ b/test/Core/Echo.ControlFlow.Tests/Serialization/Blocks/BlockSorterTest.cs @@ -18,7 +18,7 @@ private static ControlFlowGraph GenerateGraph(int nodeCount) var cfg = new ControlFlowGraph(IntArchitecture.Instance); for (int i = 0; i < nodeCount; i++) cfg.Nodes.Add(new ControlFlowNode(i, i)); - cfg.Entrypoint = cfg.Nodes[0]; + cfg.EntryPoint = cfg.Nodes[0]; return cfg; } @@ -63,7 +63,7 @@ private static void AssertHasCluster(ControlFlowNode[] ordering, params int public void SequenceShouldStartWithEntrypointNode(long entrypoint) { var cfg = GenerateGraph(2); - cfg.Entrypoint = cfg.Nodes[entrypoint]; + cfg.EntryPoint = cfg.Nodes[entrypoint]; cfg.Nodes[0].ConnectWith(cfg.Nodes[1], ControlFlowEdgeType.Unconditional); cfg.Nodes[1].ConnectWith(cfg.Nodes[0], ControlFlowEdgeType.Unconditional); @@ -123,7 +123,7 @@ public void PreferExitPointsLastInDoLoop(long[] indices) cfg.Nodes[indices[1]].ConnectWith(cfg.Nodes[indices[2]], ControlFlowEdgeType.FallThrough); cfg.Nodes[indices[2]].ConnectWith(cfg.Nodes[indices[3]], ControlFlowEdgeType.FallThrough); cfg.Nodes[indices[2]].ConnectWith(cfg.Nodes[indices[1]], ControlFlowEdgeType.Conditional); - cfg.Entrypoint = cfg.Nodes[indices[0]]; + cfg.EntryPoint = cfg.Nodes[indices[0]]; var sorting = cfg .SortNodes() @@ -148,7 +148,7 @@ public void PreferExitPointsLastInWhileLoop(long[] indices) cfg.Nodes[indices[2]].ConnectWith(cfg.Nodes[indices[3]], ControlFlowEdgeType.FallThrough); cfg.Nodes[indices[2]].ConnectWith(cfg.Nodes[indices[1]], ControlFlowEdgeType.Conditional); - cfg.Entrypoint = cfg.Nodes[indices[0]]; + cfg.EntryPoint = cfg.Nodes[indices[0]]; var sorting = cfg .SortNodes() @@ -183,7 +183,7 @@ public void NodesInExceptionHandlerBlocksShouldStickTogether(int[] indices) // post cfg.Nodes[indices[4]].ConnectWith(cfg.Nodes[indices[6]], ControlFlowEdgeType.Unconditional); - cfg.Entrypoint = cfg.Nodes[indices[0]]; + cfg.EntryPoint = cfg.Nodes[indices[0]]; // Set up regions. var ehRegion = new ExceptionHandlerRegion(); @@ -195,7 +195,7 @@ public void NodesInExceptionHandlerBlocksShouldStickTogether(int[] indices) var handlerRegion = new HandlerRegion(); ehRegion.Handlers.Add(handlerRegion); handlerRegion.Contents.Nodes.Add(cfg.Nodes[indices[5]]); - handlerRegion.Contents.Entrypoint = cfg.Nodes[indices[5]]; + handlerRegion.Contents.EntryPoint = cfg.Nodes[indices[5]]; // Sort var sorting = cfg @@ -217,7 +217,7 @@ public void NodesInExceptionHandlerBlocksShouldStickTogether(int[] indices) public void HandlerRegionWithNoExitShouldBeOrderedBeforeNormalExit(int[] indices) { var cfg = GenerateGraph(4); - cfg.Entrypoint = cfg.Nodes[indices[0]]; + cfg.EntryPoint = cfg.Nodes[indices[0]]; cfg.Nodes[indices[0]].ConnectWith(cfg.Nodes[indices[1]], ControlFlowEdgeType.FallThrough); cfg.Nodes[indices[1]].ConnectWith(cfg.Nodes[indices[3]], ControlFlowEdgeType.Unconditional); @@ -230,7 +230,7 @@ public void HandlerRegionWithNoExitShouldBeOrderedBeforeNormalExit(int[] indices var handlerRegion = new HandlerRegion(); ehRegion.Handlers.Add(handlerRegion); handlerRegion.Contents.Nodes.Add(cfg.Nodes[indices[2]]); - handlerRegion.Contents.Entrypoint = cfg.Nodes[indices[2]]; + handlerRegion.Contents.EntryPoint = cfg.Nodes[indices[2]]; // Sort var sorting = cfg @@ -248,27 +248,27 @@ public void HandlerRegionWithNoExitShouldBeOrderedBeforeNormalExit(int[] indices public void PrologueAndEpilogueRegionsShouldHaveCorrectPrecedence(int[] indices) { var cfg = GenerateGraph(6); - cfg.Entrypoint = cfg.Nodes[indices[0]]; + cfg.EntryPoint = cfg.Nodes[indices[0]]; var eh = new ExceptionHandlerRegion(); cfg.Regions.Add(eh); eh.ProtectedRegion.Nodes.Add(cfg.Nodes[indices[1]]); - eh.ProtectedRegion.Entrypoint = cfg.Nodes[indices[1]]; + eh.ProtectedRegion.EntryPoint = cfg.Nodes[indices[1]]; var handler = new HandlerRegion(); eh.Handlers.Add(handler); handler.Prologue = new ScopeRegion(); handler.Prologue.Nodes.Add(cfg.Nodes[indices[2]]); - handler.Prologue.Entrypoint = cfg.Nodes[indices[2]]; + handler.Prologue.EntryPoint = cfg.Nodes[indices[2]]; handler.Contents.Nodes.Add(cfg.Nodes[indices[3]]); - handler.Contents.Entrypoint = cfg.Nodes[indices[3]]; + handler.Contents.EntryPoint = cfg.Nodes[indices[3]]; handler.Epilogue = new ScopeRegion(); handler.Epilogue.Nodes.Add(cfg.Nodes[indices[4]]); - handler.Epilogue.Entrypoint = cfg.Nodes[indices[4]]; + handler.Epilogue.EntryPoint = cfg.Nodes[indices[4]]; cfg.Nodes[indices[0]].ConnectWith(cfg.Nodes[indices[1]]); cfg.Nodes[indices[1]].ConnectWith(cfg.Nodes[indices[5]], ControlFlowEdgeType.Unconditional); @@ -292,7 +292,7 @@ public void PrologueAndEpilogueRegionsShouldHaveCorrectPrecedence(int[] indices) public void HandlerWithNoLeaveBranch(int[] indices) { var cfg = GenerateGraph(6); - cfg.Entrypoint = cfg.Nodes[indices[0]]; + cfg.EntryPoint = cfg.Nodes[indices[0]]; var eh = new ExceptionHandlerRegion(); cfg.Regions.Add(eh); @@ -303,12 +303,12 @@ public void HandlerWithNoLeaveBranch(int[] indices) cfg.Nodes[indices[2]], cfg.Nodes[indices[3]] }); - eh.ProtectedRegion.Entrypoint = cfg.Nodes[indices[1]]; + eh.ProtectedRegion.EntryPoint = cfg.Nodes[indices[1]]; var handler = new HandlerRegion(); eh.Handlers.Add(handler); handler.Contents.Nodes.Add(cfg.Nodes[indices[4]]); - handler.Contents.Entrypoint = cfg.Nodes[indices[4]]; + handler.Contents.EntryPoint = cfg.Nodes[indices[4]]; cfg.Nodes[indices[0]].ConnectWith(cfg.Nodes[indices[1]]); cfg.Nodes[indices[1]].ConnectWith(cfg.Nodes[indices[2]]); diff --git a/test/Platforms/Echo.Platforms.AsmResolver.Tests/StateTransitionResolverTest.cs b/test/Platforms/Echo.Platforms.AsmResolver.Tests/StateTransitionResolverTest.cs index 88de982b..c4f419c2 100644 --- a/test/Platforms/Echo.Platforms.AsmResolver.Tests/StateTransitionResolverTest.cs +++ b/test/Platforms/Echo.Platforms.AsmResolver.Tests/StateTransitionResolverTest.cs @@ -40,7 +40,7 @@ public void If() var body = method.CilMethodBody!; var cfg = body.ConstructSymbolicFlowGraph(out var dfg); - Assert.Single(cfg.Entrypoint.ConditionalEdges); + Assert.Single(cfg.EntryPoint.ConditionalEdges); var ldstrAdult = FindLdstr("Adult"); var ldstrChild = FindLdstr("Child"); diff --git a/test/Platforms/Echo.Platforms.AsmResolver.Tests/StaticSuccessorResolverTest.cs b/test/Platforms/Echo.Platforms.AsmResolver.Tests/StaticSuccessorResolverTest.cs index 2d374384..04f2826e 100644 --- a/test/Platforms/Echo.Platforms.AsmResolver.Tests/StaticSuccessorResolverTest.cs +++ b/test/Platforms/Echo.Platforms.AsmResolver.Tests/StaticSuccessorResolverTest.cs @@ -39,7 +39,7 @@ public void If() var body = method.CilMethodBody!; var cfg = body.ConstructStaticFlowGraph(); - Assert.Single(cfg.Entrypoint.ConditionalEdges); + Assert.Single(cfg.EntryPoint.ConditionalEdges); } [Fact] @@ -50,7 +50,7 @@ public void Switch() var body = method.CilMethodBody!; var cfg = body.ConstructStaticFlowGraph(); - Assert.Equal(3, cfg.Entrypoint.ConditionalEdges.Count); + Assert.Equal(3, cfg.EntryPoint.ConditionalEdges.Count); } [Fact] diff --git a/test/Platforms/Echo.Platforms.Dnlib.Tests/StateTransitionResolverTest.cs b/test/Platforms/Echo.Platforms.Dnlib.Tests/StateTransitionResolverTest.cs index b47d67f5..bc60453e 100644 --- a/test/Platforms/Echo.Platforms.Dnlib.Tests/StateTransitionResolverTest.cs +++ b/test/Platforms/Echo.Platforms.Dnlib.Tests/StateTransitionResolverTest.cs @@ -35,7 +35,7 @@ public void If() var method = type.Methods.First(m => m.Name == nameof(SimpleClass.If)); var cfg = method.ConstructSymbolicFlowGraph(out var dfg); - Assert.Single(cfg.Entrypoint.ConditionalEdges); + Assert.Single(cfg.EntryPoint.ConditionalEdges); var ldstrAdult = FindLdstr("Adult"); var ldstrChild = FindLdstr("Child"); @@ -80,7 +80,7 @@ public void Switch() var method = type.Methods.First(m => m.Name == nameof(SimpleClass.SwitchColor)); var cfg = method.ConstructSymbolicFlowGraph(out _); - Assert.Equal(3, cfg.Entrypoint.ConditionalEdges.Count); + Assert.Equal(3, cfg.EntryPoint.ConditionalEdges.Count); } [Fact] diff --git a/test/Platforms/Echo.Platforms.Dnlib.Tests/StaticFlowGraphTest.cs b/test/Platforms/Echo.Platforms.Dnlib.Tests/StaticFlowGraphTest.cs index 4bbe0cdb..4d2f046e 100644 --- a/test/Platforms/Echo.Platforms.Dnlib.Tests/StaticFlowGraphTest.cs +++ b/test/Platforms/Echo.Platforms.Dnlib.Tests/StaticFlowGraphTest.cs @@ -22,7 +22,7 @@ public void BranchlessMethodShouldHaveSingleBlock() var graph = graphBuilder.ConstructFlowGraph(0); Assert.Single(graph.Nodes); Assert.Empty(graph.GetEdges()); - Assert.Equal(0, graph.Entrypoint.OutDegree); + Assert.Equal(0, graph.EntryPoint.OutDegree); } [Fact] @@ -35,7 +35,7 @@ public void ConditionalBranchShouldHaveTwoOutgoingEdges() var graphBuilder = new StaticFlowGraphBuilder(arch, arch.Method.Body.Instructions, resolver); var graph = graphBuilder.ConstructFlowGraph(0); - Assert.Equal(2, graph.Entrypoint.OutDegree); + Assert.Equal(2, graph.EntryPoint.OutDegree); } [Fact] diff --git a/test/Platforms/Echo.Platforms.Dnlib.Tests/StaticSuccessorResolverTest.cs b/test/Platforms/Echo.Platforms.Dnlib.Tests/StaticSuccessorResolverTest.cs index a55b0e78..168b99fb 100644 --- a/test/Platforms/Echo.Platforms.Dnlib.Tests/StaticSuccessorResolverTest.cs +++ b/test/Platforms/Echo.Platforms.Dnlib.Tests/StaticSuccessorResolverTest.cs @@ -33,7 +33,7 @@ public void If() var method = type.Methods.First(m => m.Name == nameof(SimpleClass.If)); var cfg = method.ConstructStaticFlowGraph(); - Assert.Single(cfg.Entrypoint.ConditionalEdges); + Assert.Single(cfg.EntryPoint.ConditionalEdges); } [Fact] @@ -43,7 +43,7 @@ public void Switch() var method = type.Methods.First(m => m.Name == nameof(SimpleClass.SwitchColor)); var cfg = method.ConstructStaticFlowGraph(); - Assert.Equal(3, cfg.Entrypoint.ConditionalEdges.Count); + Assert.Equal(3, cfg.EntryPoint.ConditionalEdges.Count); } [Fact] diff --git a/test/Platforms/Echo.Platforms.Dnlib.Tests/SymbolicFlowGraphTest.cs b/test/Platforms/Echo.Platforms.Dnlib.Tests/SymbolicFlowGraphTest.cs index a08b2fba..ce9bfd21 100644 --- a/test/Platforms/Echo.Platforms.Dnlib.Tests/SymbolicFlowGraphTest.cs +++ b/test/Platforms/Echo.Platforms.Dnlib.Tests/SymbolicFlowGraphTest.cs @@ -22,7 +22,7 @@ public void BranchlessMethodShouldHaveSingleBlock() var graph = graphBuilder.ConstructFlowGraph(0); Assert.Single(graph.Nodes); Assert.Empty(graph.GetEdges()); - Assert.Equal(0, graph.Entrypoint.OutDegree); + Assert.Equal(0, graph.EntryPoint.OutDegree); } [Fact] @@ -35,7 +35,7 @@ public void ConditionalBranchShouldHaveTwoOutgoingEdges() var graphBuilder = new SymbolicFlowGraphBuilder(arch, arch.Method.Body.Instructions, resolver); var graph = graphBuilder.ConstructFlowGraph(0); - Assert.Equal(2, graph.Entrypoint.OutDegree); + Assert.Equal(2, graph.EntryPoint.OutDegree); } [Fact] diff --git a/test/Platforms/Echo.Platforms.DummyPlatform/Code/DummyArchitecture.cs b/test/Platforms/Echo.Platforms.DummyPlatform/Code/DummyArchitecture.cs index 9453b540..eccf4838 100644 --- a/test/Platforms/Echo.Platforms.DummyPlatform/Code/DummyArchitecture.cs +++ b/test/Platforms/Echo.Platforms.DummyPlatform/Code/DummyArchitecture.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; using Echo.ControlFlow.Construction.Static; using Echo.Code; using Echo.Platforms.DummyPlatform.ControlFlow; @@ -12,7 +10,7 @@ public class DummyArchitecture : IArchitecture public static DummyArchitecture Instance { get; - } = new DummyArchitecture(); + } = new(); public IStaticSuccessorResolver SuccessorResolver { diff --git a/test/Platforms/Echo.Platforms.DummyPlatform/Code/DummyPurityClassifier.cs b/test/Platforms/Echo.Platforms.DummyPlatform/Code/DummyPurityClassifier.cs new file mode 100644 index 00000000..bcba44df --- /dev/null +++ b/test/Platforms/Echo.Platforms.DummyPlatform/Code/DummyPurityClassifier.cs @@ -0,0 +1,30 @@ +using System; +using Echo.Code; + +namespace Echo.Platforms.DummyPlatform.Code; + +public class DummyPurityClassifier : IPurityClassifier +{ + public static DummyPurityClassifier Instance + { + get; + } = new(); + + public Trilean IsPure(in DummyInstruction instruction) + { + return instruction.OpCode switch + { + DummyOpCode.Op => Trilean.Unknown, + DummyOpCode.Push => true, + DummyOpCode.Pop => true, + DummyOpCode.Get => true, + DummyOpCode.Set => false, + DummyOpCode.Jmp => true, + DummyOpCode.JmpCond => true, + DummyOpCode.Ret => true, + DummyOpCode.Switch => true, + DummyOpCode.PushOffset => true, + _ => throw new ArgumentOutOfRangeException() + }; + } +} \ No newline at end of file diff --git a/test/Platforms/Echo.Platforms.DummyPlatform/ControlFlow/DummyStaticSuccessorResolver.cs b/test/Platforms/Echo.Platforms.DummyPlatform/ControlFlow/DummyStaticSuccessorResolver.cs index e65f26e2..2bbd81ec 100644 --- a/test/Platforms/Echo.Platforms.DummyPlatform/ControlFlow/DummyStaticSuccessorResolver.cs +++ b/test/Platforms/Echo.Platforms.DummyPlatform/ControlFlow/DummyStaticSuccessorResolver.cs @@ -8,6 +8,11 @@ namespace Echo.Platforms.DummyPlatform.ControlFlow { public class DummyStaticSuccessorResolver : IStaticSuccessorResolver { + public static DummyStaticSuccessorResolver Instance + { + get; + } = new(); + /// public int GetSuccessorsCount(in DummyInstruction instruction) { From 3ea949cd64a2addac218271a0a54483890b74856 Mon Sep 17 00:00:00 2001 From: Washi Date: Sun, 5 Nov 2023 15:37:16 +0100 Subject: [PATCH 02/33] Re-enable missing unit tests. --- .../Core/Echo.Ast.Tests/Echo.Ast.Tests.csproj | 5 --- .../Patterns/AssignmentStatementPattern.cs | 7 +++-- .../Patterns/PhiStatementPatternTest.cs | 31 ++++++++++--------- 3 files changed, 20 insertions(+), 23 deletions(-) diff --git a/test/Core/Echo.Ast.Tests/Echo.Ast.Tests.csproj b/test/Core/Echo.Ast.Tests/Echo.Ast.Tests.csproj index 8fbe2bc9..afc5bc4f 100644 --- a/test/Core/Echo.Ast.Tests/Echo.Ast.Tests.csproj +++ b/test/Core/Echo.Ast.Tests/Echo.Ast.Tests.csproj @@ -18,9 +18,4 @@ - - - - - diff --git a/test/Core/Echo.Ast.Tests/Patterns/AssignmentStatementPattern.cs b/test/Core/Echo.Ast.Tests/Patterns/AssignmentStatementPattern.cs index 7b759a87..b60737bd 100644 --- a/test/Core/Echo.Ast.Tests/Patterns/AssignmentStatementPattern.cs +++ b/test/Core/Echo.Ast.Tests/Patterns/AssignmentStatementPattern.cs @@ -1,6 +1,7 @@ using System; using Echo.Ast.Patterns; using Echo.Code; +using Echo.Platforms.DummyPlatform.Code; using Xunit; namespace Echo.Ast.Tests.Patterns @@ -12,7 +13,7 @@ public void AnyVariableAndAnyExpression() { var statement = new AssignmentStatement(new IVariable[] { - new AstVariable("var1"), + new DummyVariable("var1"), }, new InstructionExpression(1, ArraySegment>.Empty)); var pattern = StatementPattern.Assignment(); @@ -27,7 +28,7 @@ public void AnyVariableWithSpecificExpression() var statement = new AssignmentStatement(new IVariable[] { - new AstVariable("var1"), + new DummyVariable("var1"), }, new InstructionExpression(1, ArraySegment>.Empty)); var pattern = StatementPattern @@ -47,7 +48,7 @@ public void SpecificVariable() var statement = new AssignmentStatement(new IVariable[] { - new AstVariable("var1"), + new DummyVariable("var1"), }, new InstructionExpression(1, ArraySegment>.Empty)); var pattern = StatementPattern diff --git a/test/Core/Echo.Ast.Tests/Patterns/PhiStatementPatternTest.cs b/test/Core/Echo.Ast.Tests/Patterns/PhiStatementPatternTest.cs index ae0be3c2..f3cee370 100644 --- a/test/Core/Echo.Ast.Tests/Patterns/PhiStatementPatternTest.cs +++ b/test/Core/Echo.Ast.Tests/Patterns/PhiStatementPatternTest.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using Echo.Ast.Patterns; using Echo.Code; +using Echo.Platforms.DummyPlatform.Code; using Xunit; namespace Echo.Ast.Tests.Patterns @@ -10,12 +11,12 @@ public class PhiStatementPattern [Fact] public void AnyPhiWithAnyVariables() { - var statement = new PhiStatement(new AstVariable("phi1"), new List> + var statement = new PhiStatement(new DummyVariable("phi1"), new List> { - new VariableExpression(new AstVariable("v1")), - new VariableExpression(new AstVariable("v2")), - new VariableExpression(new AstVariable("v3")), - new VariableExpression(new AstVariable("v4")), + new VariableExpression(new DummyVariable("v1")), + new VariableExpression(new DummyVariable("v2")), + new VariableExpression(new DummyVariable("v3")), + new VariableExpression(new DummyVariable("v4")), }); var pattern = StatementPattern.Phi(); @@ -28,12 +29,12 @@ public void AnyPhiWithSpecificTargetPattern() { var group = new CaptureGroup("phiGroup"); - var statement = new PhiStatement(new AstVariable("phi1"), new List> + var statement = new PhiStatement(new DummyVariable("phi1"), new List> { - new VariableExpression(new AstVariable("v1")), - new VariableExpression(new AstVariable("v2")), - new VariableExpression(new AstVariable("v3")), - new VariableExpression(new AstVariable("v4")), + new VariableExpression(new DummyVariable("v1")), + new VariableExpression(new DummyVariable("v2")), + new VariableExpression(new DummyVariable("v3")), + new VariableExpression(new DummyVariable("v4")), }); var pattern = StatementPattern.Phi() @@ -48,11 +49,11 @@ public void AnyPhiWithSpecificTargetPattern() [Fact] public void AnyPhiWithFixedVariables() { - var statement = new PhiStatement(new AstVariable("phi1"), new List> + var statement = new PhiStatement(new DummyVariable("phi1"), new List> { - new VariableExpression(new AstVariable("v1")), - new VariableExpression(new AstVariable("v2")), - new VariableExpression(new AstVariable("v3")), + new VariableExpression(new DummyVariable("v1")), + new VariableExpression(new DummyVariable("v2")), + new VariableExpression(new DummyVariable("v3")), }); var pattern = StatementPattern.Phi() @@ -63,7 +64,7 @@ public void AnyPhiWithFixedVariables() Assert.True(pattern.Matches(statement)); - statement.Sources.Add(new VariableExpression(new AstVariable("v4"))); + statement.Sources.Add(new VariableExpression(new DummyVariable("v4"))); Assert.False(pattern.Matches(statement)); } From 3b2d013f9fefcd18e7c9fb9a1815e3f78ce30f50 Mon Sep 17 00:00:00 2001 From: Washi Date: Wed, 15 Nov 2023 23:43:52 +0100 Subject: [PATCH 03/33] Flush only when stack is not impure. --- src/Core/Echo.Ast/AstNode.cs | 16 ++++++++- src/Core/Echo.Ast/Construction/AstBuilder.cs | 33 ++++++++++++++----- .../CilPurityClassifier.cs | 3 ++ 3 files changed, 43 insertions(+), 9 deletions(-) diff --git a/src/Core/Echo.Ast/AstNode.cs b/src/Core/Echo.Ast/AstNode.cs index 9e0fbd4c..e3af597b 100644 --- a/src/Core/Echo.Ast/AstNode.cs +++ b/src/Core/Echo.Ast/AstNode.cs @@ -1,13 +1,27 @@ +using Echo.Code; using Echo.ControlFlow.Serialization.Dot; using Echo.Graphing; namespace Echo.Ast { /// - /// Provides a base contract for all Ast nodes + /// Provides a base contract for all AST nodes /// public abstract class AstNode : TreeNodeBase { + /// + /// Determines whether the AST node consists of only pure expressions that do not affect state. + /// + /// The object responsible for classifying individual instructions for purity + /// + /// true if the node is fully pure, false if fully impure, and Unknown if purity could + /// not be determined for (parts of) the AST node. + /// + public Trilean IsPure(IPurityClassifier classifier) + { + return Accept(Analysis.AstPurityVisitor.Instance, classifier); + } + /// /// Implements the visitor pattern /// diff --git a/src/Core/Echo.Ast/Construction/AstBuilder.cs b/src/Core/Echo.Ast/Construction/AstBuilder.cs index 2c6cd0b8..906e3bff 100644 --- a/src/Core/Echo.Ast/Construction/AstBuilder.cs +++ b/src/Core/Echo.Ast/Construction/AstBuilder.cs @@ -120,15 +120,11 @@ private void LiftInstruction( // If we are the final terminator or branch instruction at the end of the block, we want to flush any // remaining values on the stack *before* the instruction statement. - if ((architecture.GetFlowControl(instruction) - & (InstructionFlowControl.CanBranch | InstructionFlowControl.IsTerminator)) != 0) - { + if ((architecture.GetFlowControl(instruction) & (InstructionFlowControl.CanBranch | InstructionFlowControl.IsTerminator)) != 0) FlushStack(node, stack); - } // Ensure order of operations is preserved if expression is potentially impure. - if (!expression.Accept(AstPurityVisitor.Instance, _purityClassifier).ToBooleanOrFalse()) - FlushStackAndPush(node, stack); + FlushStackIfImpure(node, stack, expression); // Wrap the expression into an independent statement and add it. node.Transformed.Contents.Instructions.Add(new ExpressionStatement(expression)); @@ -143,8 +139,7 @@ private void LiftInstruction( // Multiple values are produced, move them into separate variables and push them on eval stack. // Ensure order of operations is preserved if expression is potentially impure. - if (!expression.Accept(AstPurityVisitor.Instance, _purityClassifier).ToBooleanOrFalse()) - FlushStackAndPush(node, stack); + FlushStackIfImpure(node, stack, expression); // Declare new intermediate variables. var variables = new IVariable[pushCount]; @@ -181,6 +176,28 @@ private static void FlushStackAndPush(LiftedNode node, Stack(variables[i])); } + private void FlushStackIfImpure( + LiftedNode node, + Stack> stack, + Expression expression) + { + if (expression.IsPure(_purityClassifier).ToBooleanOrFalse()) + return; + + bool fullyPureStack = true; + foreach (var value in stack) + { + if (!value.IsPure(_purityClassifier).ToBooleanOrFalse()) + { + fullyPureStack = false; + break; + } + } + + if (!fullyPureStack) + FlushStackAndPush(node, stack); + } + private static IList FlushStackInternal( LiftedNode node, Stack> stack, diff --git a/src/Platforms/Echo.Platforms.AsmResolver/CilPurityClassifier.cs b/src/Platforms/Echo.Platforms.AsmResolver/CilPurityClassifier.cs index f3ec7f13..d20155a9 100644 --- a/src/Platforms/Echo.Platforms.AsmResolver/CilPurityClassifier.cs +++ b/src/Platforms/Echo.Platforms.AsmResolver/CilPurityClassifier.cs @@ -268,6 +268,9 @@ private Trilean ClassifyInlineType(in CilInstruction instruction) { switch (instruction.OpCode.Code) { + case CilCode.Newarr: + return false; + case CilCode.Stelem: return ArrayWritePurity | DefaultTypeAccessPurity; From 0f6bde00c4b40a1a0652cf1eb7dd950fc60833cb Mon Sep 17 00:00:00 2001 From: Washi Date: Fri, 17 Nov 2023 19:35:35 +0100 Subject: [PATCH 04/33] Fix purity in dummy test arch. --- test/Core/Echo.Ast.Tests/Construction/AstBuilderTest.cs | 3 +++ .../Code/DummyPurityClassifier.cs | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/test/Core/Echo.Ast.Tests/Construction/AstBuilderTest.cs b/test/Core/Echo.Ast.Tests/Construction/AstBuilderTest.cs index 15e46269..4458f501 100644 --- a/test/Core/Echo.Ast.Tests/Construction/AstBuilderTest.cs +++ b/test/Core/Echo.Ast.Tests/Construction/AstBuilderTest.cs @@ -374,6 +374,9 @@ public void TwoNodesPushBeforeImpure() DummyInstruction.Ret(11) }); + using var fs = File.CreateText("/tmp/output.dot"); + cfg.ToDotGraph(fs); + var block = cfg.Nodes[0].Contents; Assert.IsAssignableFrom>(block.Instructions[0]); Assert.IsAssignableFrom>(block.Instructions[1]); diff --git a/test/Platforms/Echo.Platforms.DummyPlatform/Code/DummyPurityClassifier.cs b/test/Platforms/Echo.Platforms.DummyPlatform/Code/DummyPurityClassifier.cs index bcba44df..c99d826f 100644 --- a/test/Platforms/Echo.Platforms.DummyPlatform/Code/DummyPurityClassifier.cs +++ b/test/Platforms/Echo.Platforms.DummyPlatform/Code/DummyPurityClassifier.cs @@ -15,8 +15,8 @@ public Trilean IsPure(in DummyInstruction instruction) return instruction.OpCode switch { DummyOpCode.Op => Trilean.Unknown, - DummyOpCode.Push => true, - DummyOpCode.Pop => true, + DummyOpCode.Push => false, + DummyOpCode.Pop => false, DummyOpCode.Get => true, DummyOpCode.Set => false, DummyOpCode.Jmp => true, From 66d841d794b1189b85d411dd1f8cd9d60f8f00a2 Mon Sep 17 00:00:00 2001 From: Washi Date: Sun, 19 Nov 2023 15:23:10 +0100 Subject: [PATCH 05/33] Add stateful block visitor interface. --- .../Echo.ControlFlow/Blocks/BasicBlock.cs | 6 ++- .../Blocks/ExceptionHandlerBlock.cs | 6 ++- .../Echo.ControlFlow/Blocks/HandlerBlock.cs | 6 ++- src/Core/Echo.ControlFlow/Blocks/IBlock.cs | 7 ++++ .../Echo.ControlFlow/Blocks/IBlockVisitor.cs | 38 +++++++++++++++++++ .../Echo.ControlFlow/Blocks/ScopeBlock.cs | 10 +++-- 6 files changed, 66 insertions(+), 7 deletions(-) diff --git a/src/Core/Echo.ControlFlow/Blocks/BasicBlock.cs b/src/Core/Echo.ControlFlow/Blocks/BasicBlock.cs index f829ff87..89d7a9f1 100644 --- a/src/Core/Echo.ControlFlow/Blocks/BasicBlock.cs +++ b/src/Core/Echo.ControlFlow/Blocks/BasicBlock.cs @@ -61,7 +61,7 @@ public long Offset } /// - /// Gets a collection of isntructions that are executed in sequence when this basic block is executed. + /// Gets a collection of instructions that are executed in sequence when this basic block is executed. /// public IList Instructions { @@ -96,5 +96,9 @@ public IList Instructions /// public void AcceptVisitor(IBlockVisitor visitor) => visitor.VisitBasicBlock(this); + + /// + public TResult AcceptVisitor(IBlockVisitor visitor, TState state) + => visitor.VisitBasicBlock(this, state); } } \ No newline at end of file diff --git a/src/Core/Echo.ControlFlow/Blocks/ExceptionHandlerBlock.cs b/src/Core/Echo.ControlFlow/Blocks/ExceptionHandlerBlock.cs index ed7b7a57..890445f0 100644 --- a/src/Core/Echo.ControlFlow/Blocks/ExceptionHandlerBlock.cs +++ b/src/Core/Echo.ControlFlow/Blocks/ExceptionHandlerBlock.cs @@ -15,7 +15,7 @@ public class ExceptionHandlerBlock : IBlock public ScopeBlock ProtectedBlock { get; - } = new ScopeBlock(); + } = new(); /// /// Gets a collection of handler blocks. @@ -68,6 +68,10 @@ public BasicBlock GetLastBlock() /// public void AcceptVisitor(IBlockVisitor visitor) => visitor.VisitExceptionHandlerBlock(this); + /// + public TResult AcceptVisitor(IBlockVisitor visitor, TState state) + => visitor.VisitExceptionHandlerBlock(this, state); + /// public override string ToString() => BlockFormatter.Format(this); } diff --git a/src/Core/Echo.ControlFlow/Blocks/HandlerBlock.cs b/src/Core/Echo.ControlFlow/Blocks/HandlerBlock.cs index 080a8b1a..7b8a5d50 100644 --- a/src/Core/Echo.ControlFlow/Blocks/HandlerBlock.cs +++ b/src/Core/Echo.ControlFlow/Blocks/HandlerBlock.cs @@ -23,7 +23,7 @@ public ScopeBlock Prologue public ScopeBlock Contents { get; - } = new ScopeBlock(); + } = new(); /// /// Gets or sets the epilogue block that gets executed after the main handler block (if available). @@ -76,5 +76,9 @@ public BasicBlock GetLastBlock() => /// public void AcceptVisitor(IBlockVisitor visitor) => visitor.VisitHandlerBlock(this); + + /// + public TResult AcceptVisitor(IBlockVisitor visitor, TState state) + => visitor.VisitHandlerBlock(this, state); } } \ No newline at end of file diff --git a/src/Core/Echo.ControlFlow/Blocks/IBlock.cs b/src/Core/Echo.ControlFlow/Blocks/IBlock.cs index 39b6274a..d9ea6bf7 100644 --- a/src/Core/Echo.ControlFlow/Blocks/IBlock.cs +++ b/src/Core/Echo.ControlFlow/Blocks/IBlock.cs @@ -31,5 +31,12 @@ public interface IBlock /// /// The visitor to accept. void AcceptVisitor(IBlockVisitor visitor); + + /// + /// Visit the current block using the provided visitor. + /// + /// The visitor to accept. + /// An argument to pass onto the visitor. + TResult AcceptVisitor(IBlockVisitor visitor, TState state); } } \ No newline at end of file diff --git a/src/Core/Echo.ControlFlow/Blocks/IBlockVisitor.cs b/src/Core/Echo.ControlFlow/Blocks/IBlockVisitor.cs index 28870371..a67dee96 100644 --- a/src/Core/Echo.ControlFlow/Blocks/IBlockVisitor.cs +++ b/src/Core/Echo.ControlFlow/Blocks/IBlockVisitor.cs @@ -30,4 +30,42 @@ public interface IBlockVisitor /// The block. void VisitHandlerBlock(HandlerBlock block); } + + /// + /// Provides members for visiting blocks in a scoped block tree. + /// + /// The type of instructions in the blocks. + /// The type of state to pass onto the visitor. + /// The type of the result for every visited block. + public interface IBlockVisitor + { + /// + /// Visits a basic block. + /// + /// The block. + /// The argument to pass along the visitor. + TResult VisitBasicBlock(BasicBlock block, TState state); + + /// + /// Visits a scope block. + /// + /// The block. + /// The argument to pass along the visitor. + TResult VisitScopeBlock(ScopeBlock block, TState state); + + /// + /// Visits an exception handler block. + /// + /// The block. + /// The argument to pass along the visitor. + TResult VisitExceptionHandlerBlock(ExceptionHandlerBlock block, TState state); + + /// + /// Visits a handler block inside an . + /// + /// The block. + /// The argument to pass along the visitor. + TResult VisitHandlerBlock(HandlerBlock block, TState state); + } + } \ No newline at end of file diff --git a/src/Core/Echo.ControlFlow/Blocks/ScopeBlock.cs b/src/Core/Echo.ControlFlow/Blocks/ScopeBlock.cs index 640a69be..63145f71 100644 --- a/src/Core/Echo.ControlFlow/Blocks/ScopeBlock.cs +++ b/src/Core/Echo.ControlFlow/Blocks/ScopeBlock.cs @@ -32,11 +32,13 @@ public BasicBlock GetLastBlock() => Blocks.Count > 0 : null; /// - public void AcceptVisitor(IBlockVisitor visitor) => - visitor.VisitScopeBlock(this); + public void AcceptVisitor(IBlockVisitor visitor) => visitor.VisitScopeBlock(this); /// - public override string ToString() => - BlockFormatter.Format(this); + public TResult AcceptVisitor(IBlockVisitor visitor, TState state) + => visitor.VisitScopeBlock(this, state); + + /// + public override string ToString() => BlockFormatter.Format(this); } } \ No newline at end of file From 9aa14449d8956d5c9a01214e0cbaf7da659b6372 Mon Sep 17 00:00:00 2001 From: Washi Date: Sun, 19 Nov 2023 15:23:23 +0100 Subject: [PATCH 06/33] Add TreeNodeBase::OnParentChanged. --- src/Core/Echo/Graphing/TreeNodeBase.cs | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/Core/Echo/Graphing/TreeNodeBase.cs b/src/Core/Echo/Graphing/TreeNodeBase.cs index b82340ff..a08a4c44 100644 --- a/src/Core/Echo/Graphing/TreeNodeBase.cs +++ b/src/Core/Echo/Graphing/TreeNodeBase.cs @@ -5,19 +5,28 @@ namespace Echo.Graphing { /// - /// Provides a base contract for nodes that will be used in a tree + /// Provides a base contract for nodes that will be used in a tree. /// public abstract class TreeNodeBase : INode { private readonly object _lock = new(); - + private TreeNodeBase? _parent; + /// /// The parent of this /// public TreeNodeBase? Parent { - get; - internal set; + get => _parent; + internal set + { + if (_parent != value) + { + var original = _parent; + _parent = value; + OnParentChanged(original); + } + } } /// @@ -79,5 +88,13 @@ protected void UpdateChild(ref T? child, T? value) child.Parent = this; } } + + /// + /// Called when the parent of the tree node changes. + /// + /// The original tree node parent. + protected virtual void OnParentChanged(TreeNodeBase? old) + { + } } } \ No newline at end of file From 6e0180abca556712bd95a131b2ae08cb496f8159 Mon Sep 17 00:00:00 2001 From: Washi Date: Sun, 19 Nov 2023 15:40:05 +0100 Subject: [PATCH 07/33] Add nested impure expression test. --- .../Construction/AstBuilderTest.cs | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/test/Core/Echo.Ast.Tests/Construction/AstBuilderTest.cs b/test/Core/Echo.Ast.Tests/Construction/AstBuilderTest.cs index 4458f501..f244031b 100644 --- a/test/Core/Echo.Ast.Tests/Construction/AstBuilderTest.cs +++ b/test/Core/Echo.Ast.Tests/Construction/AstBuilderTest.cs @@ -207,6 +207,53 @@ public void NestedExpressions() Assert.True(match.IsSuccess); } + [Fact] + public void NestedImpureExpressions() + { + // Construct + var cfg = ConstructGraph(new[] + { + // op(op(), op()) + DummyInstruction.Op(0, 0, 1), + DummyInstruction.Op(1, 0, 1), + DummyInstruction.Op(2, 2, 1), + + // op(op(), op()) + DummyInstruction.Op(3, 0, 1), + DummyInstruction.Op(4, 0, 1), + DummyInstruction.Op(5, 2, 1), + + // op( op(op(), op()) , op(op(), op()) ) + DummyInstruction.Op(6, 2, 0), + + // ret() + DummyInstruction.Ret(7), + }); + + // Verify + var n1 = Assert.Single(cfg.Nodes); + + var pattern = StatementPattern.Expression(ExpressionPattern + .Instruction(new DummyInstructionPattern(DummyOpCode.Op)) + .WithArguments( + ExpressionPattern + .Instruction(new DummyInstructionPattern(DummyOpCode.Op)) + .WithArguments( + ExpressionPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Op)), + ExpressionPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Op)) + ), + ExpressionPattern + .Instruction(new DummyInstructionPattern(DummyOpCode.Op)) + .WithArguments( + ExpressionPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Op)), + ExpressionPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Op)) + ) + ) + ); + + Assert.True(pattern.Match(n1.Contents.Instructions[0]).IsSuccess); + } + [Fact] public void PushArgumentBeforeImpureStatement() { From fe66cacb4b4eafda99261b2087a759eedc5be96b Mon Sep 17 00:00:00 2001 From: Washi Date: Sun, 19 Nov 2023 16:02:47 +0100 Subject: [PATCH 08/33] Add AstNode::OriginalRange. --- src/Core/Echo.Ast/AssignmentStatement.cs | 1 + src/Core/Echo.Ast/AstNode.cs | 10 ++++++++++ src/Core/Echo.Ast/Construction/AstBuilder.cs | 18 ++++++++++++++---- src/Core/Echo.Ast/ExpressionStatement.cs | 18 +++++++++++------- src/Core/Echo.Ast/InstructionExpression.cs | 14 +++++++++----- src/Core/Echo/AddressRange.cs | 14 ++++++++++++++ 6 files changed, 59 insertions(+), 16 deletions(-) diff --git a/src/Core/Echo.Ast/AssignmentStatement.cs b/src/Core/Echo.Ast/AssignmentStatement.cs index 70d5d0fb..a1236783 100644 --- a/src/Core/Echo.Ast/AssignmentStatement.cs +++ b/src/Core/Echo.Ast/AssignmentStatement.cs @@ -30,6 +30,7 @@ public AssignmentStatement(IEnumerable variables, Expression diff --git a/src/Core/Echo.Ast/AstNode.cs b/src/Core/Echo.Ast/AstNode.cs index e3af597b..639c379f 100644 --- a/src/Core/Echo.Ast/AstNode.cs +++ b/src/Core/Echo.Ast/AstNode.cs @@ -9,6 +9,16 @@ namespace Echo.Ast /// public abstract class AstNode : TreeNodeBase { + /// + /// Gets or sets the original address range this AST node mapped to in the raw disassembly of the code + /// (if available). + /// + public AddressRange? OriginalRange + { + get; + set; + } + /// /// Determines whether the AST node consists of only pure expressions that do not affect state. /// diff --git a/src/Core/Echo.Ast/Construction/AstBuilder.cs b/src/Core/Echo.Ast/Construction/AstBuilder.cs index 906e3bff..e2cbcf0c 100644 --- a/src/Core/Echo.Ast/Construction/AstBuilder.cs +++ b/src/Core/Echo.Ast/Construction/AstBuilder.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using Echo.Ast.Analysis; using Echo.Code; using Echo.ControlFlow; using Echo.ControlFlow.Regions; @@ -94,9 +93,16 @@ private void LiftInstruction( Stack> stack) { var architecture = _original.Architecture; - + long startOffset = architecture.GetOffset(instruction); + // Wrap the instruction into an expression. - var expression = new InstructionExpression(instruction); + var expression = new InstructionExpression(instruction) + { + OriginalRange = new AddressRange( + startOffset, + startOffset + architecture.GetSize(instruction) + ) + }; // Determine the arguments. int popCount = architecture.GetStackPopCount(instruction); @@ -108,7 +114,11 @@ private void LiftInstruction( else { for (int i = 0; i < popCount; i++) - expression.Arguments.Insert(0, Pop(node, stack)); + { + var argument = Pop(node, stack); + expression.Arguments.Insert(0, argument); + expression.OriginalRange = expression.OriginalRange!.Value!.Expand(argument.OriginalRange!.Value!); + } } // Determine the produced values. diff --git a/src/Core/Echo.Ast/ExpressionStatement.cs b/src/Core/Echo.Ast/ExpressionStatement.cs index 87376b73..f0ebf2b5 100644 --- a/src/Core/Echo.Ast/ExpressionStatement.cs +++ b/src/Core/Echo.Ast/ExpressionStatement.cs @@ -15,7 +15,11 @@ public sealed class ExpressionStatement : Statement /// Creates a new expression statement /// /// The expression - public ExpressionStatement(Expression expression) => Expression = expression; + public ExpressionStatement(Expression expression) + { + Expression = expression; + OriginalRange = expression.OriginalRange; + } /// /// The expression that this holds @@ -33,12 +37,12 @@ public override IEnumerable GetChildren() } /// - public override void Accept(IAstNodeVisitor visitor, TState state) => - visitor.Visit(this, state); + public override void Accept(IAstNodeVisitor visitor, TState state) + => visitor.Visit(this, state); /// - public override TOut Accept(IAstNodeVisitor visitor, TState state) => - visitor.Visit(this, state); + public override TOut Accept(IAstNodeVisitor visitor, TState state) + => visitor.Visit(this, state); /// /// Modifies the current to have @@ -54,7 +58,7 @@ public ExpressionStatement WithExpression(Expression /// public override string ToString() => $"{Expression}"; - internal override string Format(IInstructionFormatter instructionFormatter) => - $"{Expression.Format(instructionFormatter)}"; + internal override string Format(IInstructionFormatter instructionFormatter) + => $"{Expression.Format(instructionFormatter)}"; } } \ No newline at end of file diff --git a/src/Core/Echo.Ast/InstructionExpression.cs b/src/Core/Echo.Ast/InstructionExpression.cs index 65d9ca54..c1e344fe 100644 --- a/src/Core/Echo.Ast/InstructionExpression.cs +++ b/src/Core/Echo.Ast/InstructionExpression.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using Echo.ControlFlow.Serialization.Dot; using Echo.Graphing; @@ -15,15 +14,20 @@ public sealed class InstructionExpression : Expression /// The instruction public InstructionExpression(TInstruction instruction) - : this(instruction, Array.Empty>()) { } - + { + Instruction = instruction; + Arguments = new TreeNodeCollection, Expression>(this); + } + /// /// Creates a new instruction expression node /// /// The instruction /// The parameters to the public InstructionExpression(TInstruction instruction, params Expression[] arguments) - : this(instruction, arguments as IEnumerable>) { } + : this(instruction, arguments as IEnumerable>) + { + } /// /// Creates a new instruction expression node diff --git a/src/Core/Echo/AddressRange.cs b/src/Core/Echo/AddressRange.cs index 13352cdc..0e3fa5c2 100644 --- a/src/Core/Echo/AddressRange.cs +++ b/src/Core/Echo/AddressRange.cs @@ -77,6 +77,20 @@ public long End /// true if the sub range falls within the range, false otherwise. public bool Contains(AddressRange range) => Contains(range.Start) && Contains(range.End); + /// + /// Expands the address range such that the provided offset is included in the range. + /// + /// The offset to include. + /// The new expanded range. + public AddressRange Expand(long offset) => new(Math.Min(Start, offset), Math.Max(End, offset)); + + /// + /// Expands the address range such that the provided address range is included in the total range. + /// + /// The other address range to include. + /// The new expanded range. + public AddressRange Expand(AddressRange other) => new(Math.Min(Start, other.Start), Math.Max(End, other.End)); + /// /// Determines whether the range is considered equal with the provided range. /// From 3f6109add92a230c43c936f5dc639b7b67cf3270 Mon Sep 17 00:00:00 2001 From: Washi Date: Sun, 19 Nov 2023 16:07:53 +0100 Subject: [PATCH 09/33] BUGFIX: Check argument range for null before expanding containing range. --- src/Core/Echo.Ast/Construction/AstBuilder.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Core/Echo.Ast/Construction/AstBuilder.cs b/src/Core/Echo.Ast/Construction/AstBuilder.cs index e2cbcf0c..5868ead5 100644 --- a/src/Core/Echo.Ast/Construction/AstBuilder.cs +++ b/src/Core/Echo.Ast/Construction/AstBuilder.cs @@ -117,7 +117,8 @@ private void LiftInstruction( { var argument = Pop(node, stack); expression.Arguments.Insert(0, argument); - expression.OriginalRange = expression.OriginalRange!.Value!.Expand(argument.OriginalRange!.Value!); + if (argument.OriginalRange is not null) + expression.OriginalRange = expression.OriginalRange.Value.Expand(argument.OriginalRange.Value); } } From f4c8fe0863cc34ebd04aafccafce9676d548cb8f Mon Sep 17 00:00:00 2001 From: Washi Date: Mon, 25 Dec 2023 13:20:36 +0100 Subject: [PATCH 10/33] Shorten some pattern factory methods. --- .../Echo.Ast/Patterns/ExpressionPattern.cs | 22 +++++------ src/Core/Echo.Ast/Patterns/Pattern.cs | 4 +- .../Echo.Ast/Patterns/StatementPattern.cs | 38 ++++++------------- 3 files changed, 24 insertions(+), 40 deletions(-) diff --git a/src/Core/Echo.Ast/Patterns/ExpressionPattern.cs b/src/Core/Echo.Ast/Patterns/ExpressionPattern.cs index 1209664c..7b02c736 100644 --- a/src/Core/Echo.Ast/Patterns/ExpressionPattern.cs +++ b/src/Core/Echo.Ast/Patterns/ExpressionPattern.cs @@ -11,46 +11,44 @@ public static class ExpressionPattern /// Creates a new pattern that matches any type of expressions. /// /// The pattern. - public static AnyPattern> Any() => - new AnyPattern>(); + public static AnyPattern> Any() => new(); /// /// Creates a new pattern that matches on instances of . /// /// The instruction to match on. /// The pattern. - public static InstructionExpressionPattern InstructionLiteral(TInstruction instruction) => - new InstructionExpressionPattern(Pattern.Literal(instruction)); + public static InstructionExpressionPattern InstructionLiteral(TInstruction instruction) + => new(Pattern.Literal(instruction)); /// /// Creates a new pattern that matches on instances of . /// /// The pattern. - public static InstructionExpressionPattern Instruction() => - new InstructionExpressionPattern(Pattern.Any()); + public static InstructionExpressionPattern Instruction() + => new(Pattern.Any()); /// /// Creates a new pattern that matches on instances of . /// /// The instruction pattern to match on. /// The pattern. - public static InstructionExpressionPattern Instruction(Pattern instruction) => - new InstructionExpressionPattern(instruction); + public static InstructionExpressionPattern Instruction(Pattern instruction) + => new(instruction); /// /// Creates a new pattern that matches any type of variable expression. /// /// The pattern. - public static VariableExpressionPattern Variable() => - new VariableExpressionPattern(); + public static VariableExpressionPattern Variable() => new(); /// /// Creates a new pattern that matches any type of variable expression. /// /// The pattern describing the referenced variable. /// The pattern. - public static VariableExpressionPattern Variable(Pattern variable) => - new VariableExpressionPattern(variable); + public static VariableExpressionPattern Variable(Pattern variable) + => new(variable); } /// diff --git a/src/Core/Echo.Ast/Patterns/Pattern.cs b/src/Core/Echo.Ast/Patterns/Pattern.cs index 5d4a673b..67128ced 100644 --- a/src/Core/Echo.Ast/Patterns/Pattern.cs +++ b/src/Core/Echo.Ast/Patterns/Pattern.cs @@ -11,14 +11,14 @@ public static class Pattern /// Creates a new pattern that matches any object instance of the specified type. /// /// The pattern. - public static AnyPattern Any() => new AnyPattern(); + public static AnyPattern Any() => new(); /// /// Creates a new pattern that value-matches the input with an object instance of the specified type. /// /// The instance to match with. /// - public static LiteralPattern Literal(T o) => new LiteralPattern(o); + public static LiteralPattern Literal(T o) => new(o); } /// diff --git a/src/Core/Echo.Ast/Patterns/StatementPattern.cs b/src/Core/Echo.Ast/Patterns/StatementPattern.cs index d0b95dce..fa57f6fb 100644 --- a/src/Core/Echo.Ast/Patterns/StatementPattern.cs +++ b/src/Core/Echo.Ast/Patterns/StatementPattern.cs @@ -12,22 +12,15 @@ public static class StatementPattern /// Creates a new pattern that matches any type of statement. /// /// The pattern. - public static AnyPattern> Any() - { - return new AnyPattern>(); - } + public static AnyPattern> Any() => new(); /// /// Creates a new pattern that matches on instances of with /// a single variable on the left hand side of the equals sign. /// - public static AssignmentStatementPattern Assignment() - { - return new AssignmentStatementPattern( - Pattern.Any(), - Pattern.Any>()); - } - + public static AssignmentStatementPattern Assignment() + => new(Pattern.Any(), Pattern.Any>()); + /// /// Creates a new pattern that matches on instances of . /// @@ -56,16 +49,14 @@ public static AssignmentStatementPattern Assignment( /// Creates a new pattern that matches on instances of with /// any kind of embedded expression. /// - public static ExpressionStatementPattern Expression() - { - return new ExpressionStatementPattern(); - } - + public static ExpressionStatementPattern Expression() => new(); + /// /// Creates a new pattern that matches on instances of . /// /// The pattern for the embedded expression. - public static ExpressionStatementPattern Expression(Pattern> expression) + public static ExpressionStatementPattern Expression( + Pattern> expression) { return new ExpressionStatementPattern(expression); } @@ -76,7 +67,8 @@ public static ExpressionStatementPattern Expression( /// /// The instruction pattern to match on. /// The pattern. - public static ExpressionStatementPattern Instruction(Pattern instruction) + public static ExpressionStatementPattern Instruction( + Pattern instruction) { return new ExpressionStatementPattern(ExpressionPattern.Instruction(instruction)); } @@ -86,10 +78,7 @@ public static ExpressionStatementPattern Instruction /// any target and any number of source variables. /// /// The pattern. - public static PhiStatementPattern Phi() - { - return new PhiStatementPattern(); - } + public static PhiStatementPattern Phi() => new(); /// /// Creates a new pattern that matches on instances of with @@ -97,10 +86,7 @@ public static PhiStatementPattern Phi() /// /// The target pattern to match on. /// The pattern. - public static PhiStatementPattern Phi(Pattern target) - { - return new PhiStatementPattern(target); - } + public static PhiStatementPattern Phi(Pattern target) => new(target); } /// From 4e0ff1e5030fefcbe078683b0da5e45ec22bc0dd Mon Sep 17 00:00:00 2001 From: Washi Date: Mon, 25 Dec 2023 14:14:17 +0100 Subject: [PATCH 11/33] Strongly typed AST capture groups. --- .../Patterns/AssignmentStatementPattern.cs | 2 +- src/Core/Echo.Ast/Patterns/CaptureGroup.cs | 38 ++++-- .../Patterns/InstructionExpressionPattern.cs | 2 +- src/Core/Echo.Ast/Patterns/MatchResult.cs | 54 ++++++-- src/Core/Echo.Ast/Patterns/OrPattern.cs | 10 +- src/Core/Echo.Ast/Patterns/Pattern.cs | 9 +- .../Echo.Ast/Patterns/PhiStatementPattern.cs | 6 +- .../Patterns/VariableExpressionPattern.cs | 2 +- .../Construction/AstBuilderTest.cs | 119 +++++++++--------- .../Patterns/AssignmentStatementPattern.cs | 34 +++-- .../ExpressionStatementPatternTest.cs | 20 ++- .../Echo.Ast.Tests/Patterns/PatternTest.cs | 10 +- .../Patterns/PhiStatementPatternTest.cs | 43 +++---- 13 files changed, 197 insertions(+), 152 deletions(-) diff --git a/src/Core/Echo.Ast/Patterns/AssignmentStatementPattern.cs b/src/Core/Echo.Ast/Patterns/AssignmentStatementPattern.cs index 6b078182..f784a25d 100644 --- a/src/Core/Echo.Ast/Patterns/AssignmentStatementPattern.cs +++ b/src/Core/Echo.Ast/Patterns/AssignmentStatementPattern.cs @@ -125,7 +125,7 @@ public AssignmentStatementPattern WithExpression(Pattern /// The group. /// The current pattern. - public AssignmentStatementPattern CaptureVariables(CaptureGroup captureGroup) + public AssignmentStatementPattern CaptureVariables(CaptureGroup captureGroup) { foreach (var variable in Variables) variable.CaptureAs(captureGroup); diff --git a/src/Core/Echo.Ast/Patterns/CaptureGroup.cs b/src/Core/Echo.Ast/Patterns/CaptureGroup.cs index 4d099e22..2cc7fa28 100644 --- a/src/Core/Echo.Ast/Patterns/CaptureGroup.cs +++ b/src/Core/Echo.Ast/Patterns/CaptureGroup.cs @@ -1,19 +1,13 @@ -using System; - namespace Echo.Ast.Patterns { /// /// Defines a capture group in a pattern. /// - public class CaptureGroup + public abstract class CaptureGroup { - /// - /// Creates a new named capture group. - /// - /// The name of the capture. - public CaptureGroup(string name) + internal CaptureGroup(string name) { - Name = name ?? throw new ArgumentNullException(nameof(name)); + Name = name; } /// @@ -23,8 +17,32 @@ public string Name { get; } + } + + /// + /// Defines a capture group in a pattern of instances. + /// + /// The type of objects to capture in the group. + public class CaptureGroup : CaptureGroup + { + /// + /// Creates a new unnamed capture group. + /// + public CaptureGroup() + : base(null) + { + } + + /// + /// Creates a new named capture group. + /// + /// The name of the capture. + public CaptureGroup(string name) + : base(name) + { + } /// - public override string ToString() => Name; + public override string ToString() => Name ?? $"(Anonymous {typeof(T)} Group)"; } } \ No newline at end of file diff --git a/src/Core/Echo.Ast/Patterns/InstructionExpressionPattern.cs b/src/Core/Echo.Ast/Patterns/InstructionExpressionPattern.cs index c8801ff1..005ac553 100644 --- a/src/Core/Echo.Ast/Patterns/InstructionExpressionPattern.cs +++ b/src/Core/Echo.Ast/Patterns/InstructionExpressionPattern.cs @@ -178,7 +178,7 @@ public InstructionExpressionPattern WithAnyArguments() /// /// The group. /// The current pattern. - public InstructionExpressionPattern CaptureArguments(CaptureGroup captureGroup) + public InstructionExpressionPattern CaptureArguments(CaptureGroup> captureGroup) { foreach (var argument in Arguments) argument.CaptureAs(captureGroup); diff --git a/src/Core/Echo.Ast/Patterns/MatchResult.cs b/src/Core/Echo.Ast/Patterns/MatchResult.cs index e8e6a014..58bcb7fe 100644 --- a/src/Core/Echo.Ast/Patterns/MatchResult.cs +++ b/src/Core/Echo.Ast/Patterns/MatchResult.cs @@ -1,4 +1,6 @@ +using System.Collections; using System.Collections.Generic; +using System.Collections.Immutable; namespace Echo.Ast.Patterns { @@ -7,6 +9,8 @@ namespace Echo.Ast.Patterns /// public class MatchResult { + private readonly Dictionary _captureGroups = new(); + /// /// Gets a value indicating whether the matching was successful. /// @@ -17,24 +21,58 @@ public bool IsSuccess } = true; /// - /// Provides a collection of objects extracted from the input object. + /// Gets all the groups that were captured during the matching process. /// - public IDictionary> Captures + /// The groups. + public IEnumerable GetCaptureGroups() => _captureGroups.Keys; + + /// + /// Gets all captured objects belonging to the provided capture group. + /// + /// The group to get the captured objects for. + /// The type of objects that were captured. + /// The captured groups + public IList GetCaptures(CaptureGroup captureGroup) { - get; - } = new Dictionary>(); + if (_captureGroups.TryGetValue(captureGroup, out var list)) + return (IList) list; + + return ImmutableList.Empty; + } + + internal void MergeWith(MatchResult other) + { + foreach (var entry in other._captureGroups) + { + if (!_captureGroups.TryGetValue(entry.Key, out var list)) + { + _captureGroups.Add(entry.Key, entry.Value); + } + else + { + foreach (object capture in entry.Value) + list.Add(capture); + } + } + } + + internal void Clear() + { + IsSuccess = true; + _captureGroups.Clear(); + } /// /// Adds an extracted object to a capture group. /// /// The capture group to add it to. /// The extracted object to add. - public void AddCapturedObject(CaptureGroup captureGroup, object value) + public void AddCapturedObject(CaptureGroup captureGroup, T value) { - if (!Captures.TryGetValue(captureGroup, out var objects)) + if (!_captureGroups.TryGetValue(captureGroup, out var objects)) { - objects = new List(); - Captures.Add(captureGroup, objects); + objects = new List(); + _captureGroups.Add(captureGroup, objects); } objects.Add(value); diff --git a/src/Core/Echo.Ast/Patterns/OrPattern.cs b/src/Core/Echo.Ast/Patterns/OrPattern.cs index c433beba..1c59079f 100644 --- a/src/Core/Echo.Ast/Patterns/OrPattern.cs +++ b/src/Core/Echo.Ast/Patterns/OrPattern.cs @@ -44,8 +44,7 @@ protected override void MatchChildren(T input, MatchResult result) foreach (var option in Options) { // Assume clean slate. - tempResult.IsSuccess = true; - tempResult.Captures.Clear(); + tempResult.Clear(); // Try matching the current option. option.Match(input, tempResult); @@ -53,12 +52,7 @@ protected override void MatchChildren(T input, MatchResult result) // If successful, copy over captured objects. if (tempResult.IsSuccess) { - foreach (var entry in result.Captures) - { - foreach (var o in entry.Value) - result.AddCapturedObject(entry.Key, o); - } - + result.MergeWith(tempResult); return; } } diff --git a/src/Core/Echo.Ast/Patterns/Pattern.cs b/src/Core/Echo.Ast/Patterns/Pattern.cs index 67128ced..752d54c8 100644 --- a/src/Core/Echo.Ast/Patterns/Pattern.cs +++ b/src/Core/Echo.Ast/Patterns/Pattern.cs @@ -63,7 +63,7 @@ public abstract class Pattern /// /// Gets or sets the capture group this pattern was assigned to. /// - public CaptureGroup CaptureGroup + public CaptureGroup CaptureGroup { get; set; @@ -124,8 +124,7 @@ public MatchResult FindFirstMatch(IEnumerable inputSequence) foreach (var input in inputSequence) { - result.Captures.Clear(); - result.IsSuccess = true; + result.Clear(); Match(input, result); @@ -133,7 +132,7 @@ public MatchResult FindFirstMatch(IEnumerable inputSequence) return result; } - result.Captures.Clear(); + result.Clear(); result.IsSuccess = false; return result; } @@ -159,7 +158,7 @@ public IEnumerable FindAllMatches(IEnumerable inputSequence) /// /// The capture group to add the object to. /// The pattern. - public Pattern CaptureAs(CaptureGroup captureGroup) + public Pattern CaptureAs(CaptureGroup captureGroup) { CaptureGroup = captureGroup; return this; diff --git a/src/Core/Echo.Ast/Patterns/PhiStatementPattern.cs b/src/Core/Echo.Ast/Patterns/PhiStatementPattern.cs index 63ed8466..5fece5b0 100644 --- a/src/Core/Echo.Ast/Patterns/PhiStatementPattern.cs +++ b/src/Core/Echo.Ast/Patterns/PhiStatementPattern.cs @@ -147,15 +147,15 @@ public PhiStatementPattern WithSources(params Pattern /// The patterns that describe the sources of the phi node. /// The current pattern. - public PhiStatementPattern WithSources(IEnumerable>> sources) => - WithSources(sources.ToArray()); + public PhiStatementPattern WithSources(IEnumerable>> sources) + => WithSources(sources.ToArray()); /// /// Indicates all sources should be captured in a certain group. /// /// The group. /// The current pattern. - public PhiStatementPattern CaptureSources(CaptureGroup captureGroup) + public PhiStatementPattern CaptureSources(CaptureGroup> captureGroup) { foreach (var source in Sources) source.CaptureAs(captureGroup); diff --git a/src/Core/Echo.Ast/Patterns/VariableExpressionPattern.cs b/src/Core/Echo.Ast/Patterns/VariableExpressionPattern.cs index 735d49fb..5fafcc1d 100644 --- a/src/Core/Echo.Ast/Patterns/VariableExpressionPattern.cs +++ b/src/Core/Echo.Ast/Patterns/VariableExpressionPattern.cs @@ -55,7 +55,7 @@ protected override void MatchChildren(Expression input, MatchResul /// /// The capture group to add the extracted variable to. /// The current pattern. - public Pattern> CaptureVariable(CaptureGroup captureGroup) + public Pattern> CaptureVariable(CaptureGroup captureGroup) { Variable.CaptureAs(captureGroup); return this; diff --git a/test/Core/Echo.Ast.Tests/Construction/AstBuilderTest.cs b/test/Core/Echo.Ast.Tests/Construction/AstBuilderTest.cs index f244031b..4b27d5f0 100644 --- a/test/Core/Echo.Ast.Tests/Construction/AstBuilderTest.cs +++ b/test/Core/Echo.Ast.Tests/Construction/AstBuilderTest.cs @@ -67,7 +67,7 @@ public void StatementWithTwoArguments() // Verify var node = Assert.Single(cfg.Nodes); - var arguments = new CaptureGroup("arguments"); + var arguments = new CaptureGroup>("arguments"); // pop(push(), push()) var match = StatementPattern @@ -83,7 +83,7 @@ public void StatementWithTwoArguments() Assert.True(match.IsSuccess); Assert.Equal( new long[] {0, 1}, - match.Captures[arguments] + match.GetCaptures(arguments) .Cast>() .Select(x => x.Instruction.Offset)); } @@ -110,7 +110,7 @@ public void TwoStatementWithDifferentArguments() // Verify var node = Assert.Single(cfg.Nodes); - var arguments = new CaptureGroup("arguments"); + var arguments = new CaptureGroup>("arguments"); // pop(push()) var match = StatementPattern @@ -124,9 +124,10 @@ public void TwoStatementWithDifferentArguments() Assert.True(match.IsSuccess); Assert.Equal( new long[] {0}, - match.Captures[arguments] + match.GetCaptures(arguments) .Cast>() - .Select(x => x.Instruction.Offset)); + .Select(x => x.Instruction.Offset) + ); // pop(push(), push()) match = StatementPattern @@ -142,9 +143,10 @@ public void TwoStatementWithDifferentArguments() Assert.True(match.IsSuccess); Assert.Equal( new long[] {2, 3}, - match.Captures[arguments] + match.GetCaptures(arguments) .Cast>() - .Select(x => x.Instruction.Offset)); + .Select(x => x.Instruction.Offset) + ); } [Fact] @@ -169,7 +171,8 @@ public void ExpressionWithMultipleReturnValues() .Assignment( new[] {Pattern.Any(), Pattern.Any()}, ExpressionPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Op)) - ).Match(cfg.Nodes[0].Contents.Instructions[0]).IsSuccess); + ).Match(cfg.Nodes[0].Contents.Instructions[0]).IsSuccess + ); } [Fact] @@ -280,7 +283,7 @@ public void PushArgumentBeforeImpureStatement() // Verify var node = Assert.Single(cfg.Nodes); - var variable = new CaptureGroup("variable"); + var variable = new CaptureGroup("variable"); // tmp1 = push() // tmp2 = push() @@ -317,8 +320,8 @@ public void PushArgumentBeforeImpureStatement() Assert.True(match3.IsSuccess); Assert.True(match4.IsSuccess); - Assert.Same(match1.Captures[variable][0], match4.Captures[variable][0]); - Assert.Same(match2.Captures[variable][0], match3.Captures[variable][0]); + Assert.Same(match1.GetCaptures(variable)[0], match4.GetCaptures(variable)[0]); + Assert.Same(match2.GetCaptures(variable)[0], match3.GetCaptures(variable)[0]); } [Fact] @@ -370,7 +373,7 @@ public void TwoNodesWithStackDeltas() Assert.Equal(2, cfg.Nodes.Count); var (n1, n2) = (cfg.Nodes[0], cfg.Nodes[10]); - var variable = new CaptureGroup("variable"); + var variable = new CaptureGroup("variable"); // out = push() var match1 = StatementPattern @@ -401,8 +404,8 @@ public void TwoNodesWithStackDeltas() .Match(n2.Contents.Instructions[1]); Assert.True(match3.IsSuccess); - Assert.Same(match1.Captures[variable][0], match2.Captures[variable][1]); - Assert.Same(match2.Captures[variable][0], match3.Captures[variable][0]); + Assert.Same(match1.GetCaptures(variable)[0], match2.GetCaptures(variable)[1]); + Assert.Same(match2.GetCaptures(variable)[0], match3.GetCaptures(variable)[0]); Assert.Same(n2, n1.UnconditionalNeighbour); } @@ -445,8 +448,8 @@ public void TwoNodesWithNestedStackDelta() }); // Verify - var variable = new CaptureGroup("variable"); - var value = new CaptureGroup("value"); + var variable = new CaptureGroup("variable"); + var value = new CaptureGroup>("value"); var pattern = StatementPattern.Assignment( Pattern.Any().CaptureAs(variable), ExpressionPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Push)).CaptureAs(value) @@ -459,8 +462,8 @@ public void TwoNodesWithNestedStackDelta() Assert.True(match2.IsSuccess); // Ensure order of operations is preserved. - var a = (InstructionExpression) match1.Captures[value][0]; - var b = (InstructionExpression) match2.Captures[value][0]; + var a = (InstructionExpression) match1.GetCaptures(value)[0]; + var b = (InstructionExpression) match2.GetCaptures(value)[0]; Assert.Equal(0, a.Instruction.Offset); Assert.Equal(1, b.Instruction.Offset); } @@ -495,32 +498,32 @@ public void StackDeltaConvergingControlFlowPaths() Assert.Same(n4, n3.UnconditionalNeighbour); // Verify phi variables. - var variable = new CaptureGroup("variables"); + var variableCapture = new CaptureGroup("variables"); + var sourcesCapture = new CaptureGroup>("sources"); var match1 = StatementPattern.Assignment( - Pattern.Any().CaptureAs(variable), + Pattern.Any().CaptureAs(variableCapture), ExpressionPattern.Any() ).Match(n2.Contents.Instructions[0]); var match2 = StatementPattern.Assignment( - Pattern.Any().CaptureAs(variable), + Pattern.Any().CaptureAs(variableCapture), ExpressionPattern.Any() ).Match(n3.Contents.Instructions[0]); var match3 = StatementPattern.Phi() .WithSources(2) - .CaptureSources(variable) + .CaptureSources(sourcesCapture) .Match(n4.Contents.Instructions[0]); Assert.True(match1.IsSuccess); Assert.True(match2.IsSuccess); Assert.True(match3.IsSuccess); - var sources = match3.Captures[variable] - .OfType>() + var sources = match3.GetCaptures(sourcesCapture) .Select(x => x.Variable) .ToArray(); - Assert.Contains(match1.Captures[variable][0], sources); - Assert.Contains(match2.Captures[variable][0], sources); + Assert.Contains(match1.GetCaptures(variableCapture)[0], sources); + Assert.Contains(match2.GetCaptures(variableCapture)[0], sources); } [Fact] @@ -568,23 +571,24 @@ public void StackDeltaTwoLayers() Assert.Same(n6, n5.UnconditionalNeighbour); // Verify phi variables. - var variable = new CaptureGroup("variables"); + var variablesCapture = new CaptureGroup("variables"); + var sourcesCapture = new CaptureGroup>("sources"); var match1 = StatementPattern.Assignment( - Pattern.Any().CaptureAs(variable), + Pattern.Any().CaptureAs(variablesCapture), ExpressionPattern.Any() ).Match(n3.Contents.Instructions[0]); var match2 = StatementPattern.Assignment( - Pattern.Any().CaptureAs(variable), + Pattern.Any().CaptureAs(variablesCapture), ExpressionPattern.Any() ).Match(n4.Contents.Instructions[0]); var match3 = StatementPattern.Assignment( - Pattern.Any().CaptureAs(variable), + Pattern.Any().CaptureAs(variablesCapture), ExpressionPattern.Any() ).Match(n5.Contents.Instructions[0]); var match4 = StatementPattern.Phi() .WithSources(3) - .CaptureSources(variable) + .CaptureSources(sourcesCapture) .Match(n6.Contents.Instructions[0]); Assert.True(match1.IsSuccess); @@ -592,14 +596,13 @@ public void StackDeltaTwoLayers() Assert.True(match3.IsSuccess); Assert.True(match4.IsSuccess); - var sources = match4.Captures[variable] - .OfType>() + var sources = match4.GetCaptures(sourcesCapture) .Select(x => x.Variable) .ToArray(); - Assert.Contains(match1.Captures[variable][0], sources); - Assert.Contains(match2.Captures[variable][0], sources); - Assert.Contains(match3.Captures[variable][0], sources); + Assert.Contains(match1.GetCaptures(variablesCapture)[0], sources); + Assert.Contains(match2.GetCaptures(variablesCapture)[0], sources); + Assert.Contains(match3.GetCaptures(variablesCapture)[0], sources); } [Fact] @@ -618,10 +621,11 @@ public void ConditionalReplaceStackSlot() DummyInstruction.Ret(6), }); - var variables = new CaptureGroup("variables"); + var variablesCapture = new CaptureGroup("variables"); + var sourcesCapture = new CaptureGroup>("sources"); var pattern = StatementPattern.Assignment( - Pattern.Any().CaptureAs(variables), + Pattern.Any().CaptureAs(variablesCapture), ExpressionPattern.Any() ); @@ -630,20 +634,19 @@ public void ConditionalReplaceStackSlot() var match3 = StatementPattern.Phi() .WithSources(2) - .CaptureSources(variables) + .CaptureSources(sourcesCapture) .Match(cfg.Nodes[5].Contents.Instructions[0]); Assert.True(match1.IsSuccess); Assert.True(match2.IsSuccess); Assert.True(match3.IsSuccess); - var sources = match3.Captures[variables] - .OfType>() + var sources = match3.GetCaptures(sourcesCapture) .Select(x => x.Variable) .ToArray(); - Assert.Contains(match1.Captures[variables][0], sources); - Assert.Contains(match2.Captures[variables][0], sources); + Assert.Contains(match1.GetCaptures(variablesCapture)[0], sources); + Assert.Contains(match2.GetCaptures(variablesCapture)[0], sources); } [Fact] @@ -664,10 +667,11 @@ public void ConditionalReplaceStackSlotNested() DummyInstruction.Ret(7), }); - var variables = new CaptureGroup("variables"); + var variablesCapture = new CaptureGroup("variables"); + var sourcesCapture = new CaptureGroup>("sources"); var pattern = StatementPattern.Assignment( - Pattern.Any().CaptureAs(variables), + Pattern.Any().CaptureAs(variablesCapture), ExpressionPattern.Any() ); @@ -676,19 +680,18 @@ public void ConditionalReplaceStackSlotNested() var match3 = StatementPattern.Phi() .WithSources(2) - .CaptureSources(variables) + .CaptureSources(sourcesCapture) .Match(cfg.Nodes[6].Contents.Instructions[1]); Assert.True(match1.IsSuccess); Assert.True(match3.IsSuccess); - var sources = match3.Captures[variables] - .OfType>() + var sources = match3.GetCaptures(sourcesCapture) .Select(x => x.Variable) .ToArray(); - Assert.Contains(match1.Captures[variables][0], sources); - Assert.Contains(match2.Captures[variables][0], sources); + Assert.Contains(match1.GetCaptures(variablesCapture)[0], sources); + Assert.Contains(match2.GetCaptures(variablesCapture)[0], sources); } [Fact] @@ -740,20 +743,21 @@ public void LoopWithStackDelta() Assert.Same(n2, Assert.Single(n2.ConditionalEdges).Target); Assert.Same(n3, n2.UnconditionalNeighbour); - var variable = new CaptureGroup("variable"); + var variableCapture = new CaptureGroup("variable"); + var sourcesCapture = new CaptureGroup>("source"); var match1 = StatementPattern .Assignment( - Pattern.Any().CaptureAs(variable), + Pattern.Any().CaptureAs(variableCapture), ExpressionPattern.Any()) .Match(n1.Contents.Instructions[0]); var match2 = StatementPattern.Phi() .WithSources(2) - .CaptureSources(variable) + .CaptureSources(sourcesCapture) .Match(n2.Contents.Instructions[0]); var match3 = StatementPattern .Assignment( - Pattern.Any().CaptureAs(variable), + Pattern.Any().CaptureAs(variableCapture), ExpressionPattern.Any()) .Match(n2.Contents.Instructions[^2]); @@ -761,13 +765,12 @@ public void LoopWithStackDelta() Assert.True(match2.IsSuccess); Assert.True(match3.IsSuccess); - var sources = match2.Captures[variable] - .OfType>() + var sources = match2.GetCaptures(sourcesCapture) .Select(x => x.Variable) .ToArray(); - Assert.Contains(match1.Captures[variable][0], sources); - Assert.Contains(match3.Captures[variable][0], sources); + Assert.Contains(match1.GetCaptures(variableCapture)[0], sources); + Assert.Contains(match3.GetCaptures(variableCapture)[0], sources); } [Fact] diff --git a/test/Core/Echo.Ast.Tests/Patterns/AssignmentStatementPattern.cs b/test/Core/Echo.Ast.Tests/Patterns/AssignmentStatementPattern.cs index b60737bd..810d4bcf 100644 --- a/test/Core/Echo.Ast.Tests/Patterns/AssignmentStatementPattern.cs +++ b/test/Core/Echo.Ast.Tests/Patterns/AssignmentStatementPattern.cs @@ -1,4 +1,3 @@ -using System; using Echo.Ast.Patterns; using Echo.Code; using Echo.Platforms.DummyPlatform.Code; @@ -11,10 +10,10 @@ public class AssignmentStatementPattern [Fact] public void AnyVariableAndAnyExpression() { - var statement = new AssignmentStatement(new IVariable[] - { + var statement = new AssignmentStatement( new DummyVariable("var1"), - }, new InstructionExpression(1, ArraySegment>.Empty)); + new InstructionExpression(1) + ); var pattern = StatementPattern.Assignment(); @@ -24,32 +23,31 @@ public void AnyVariableAndAnyExpression() [Fact] public void AnyVariableWithSpecificExpression() { - var group = new CaptureGroup("group"); - - var statement = new AssignmentStatement(new IVariable[] - { + var statement = new AssignmentStatement( new DummyVariable("var1"), - }, new InstructionExpression(1, ArraySegment>.Empty)); - + new InstructionExpression(1) + ); + + var group = new CaptureGroup>("group"); var pattern = StatementPattern .Assignment() .WithExpression(Pattern.Any>().CaptureAs(group)); var result = pattern.Match(statement); Assert.True(result.IsSuccess); - Assert.Contains(group, result.Captures); - Assert.Contains(statement.Expression, result.Captures[group]); + Assert.Contains(group, result.GetCaptureGroups()); + Assert.Contains(statement.Expression, result.GetCaptures(group)); } [Fact] public void SpecificVariable() { - var group = new CaptureGroup("group"); + var group = new CaptureGroup("group"); - var statement = new AssignmentStatement(new IVariable[] - { + var statement = new AssignmentStatement( new DummyVariable("var1"), - }, new InstructionExpression(1, ArraySegment>.Empty)); + new InstructionExpression(1) + ); var pattern = StatementPattern .Assignment() @@ -57,8 +55,8 @@ public void SpecificVariable() var result = pattern.Match(statement); Assert.True(result.IsSuccess); - Assert.Contains(group, result.Captures); - Assert.Contains(statement.Variables[0], result.Captures[group]); + Assert.Contains(group, result.GetCaptureGroups()); + Assert.Contains(statement.Variables[0], result.GetCaptures(group)); } } } \ No newline at end of file diff --git a/test/Core/Echo.Ast.Tests/Patterns/ExpressionStatementPatternTest.cs b/test/Core/Echo.Ast.Tests/Patterns/ExpressionStatementPatternTest.cs index 32dfb499..9f9e09d1 100644 --- a/test/Core/Echo.Ast.Tests/Patterns/ExpressionStatementPatternTest.cs +++ b/test/Core/Echo.Ast.Tests/Patterns/ExpressionStatementPatternTest.cs @@ -1,5 +1,3 @@ -using System; -using System.Collections.Generic; using Echo.Ast.Patterns; using Echo.Platforms.DummyPlatform.Code; using Xunit; @@ -11,16 +9,14 @@ public class ExpressionStatementPatternTest [Fact] public void TestComplexCapture() { - var valueExpression = new InstructionExpression(DummyInstruction.Push(0, 1), - ArraySegment>.Empty); - - var statement = new ExpressionStatement(new InstructionExpression(DummyInstruction.Ret(1), new List> - { - valueExpression - })); + // Define test "ret(push(0, 1))" expression/ + var expression = new InstructionExpression(DummyInstruction.Push(0, 1)); + var statement = new ExpressionStatement( + new InstructionExpression(DummyInstruction.Ret(1), expression) + ); // Define capture group. - var returnValueGroup = new CaptureGroup("returnValue"); + var returnValueGroup = new CaptureGroup>("returnValue"); // Create ret(?) pattern. var pattern = StatementPattern.Expression( @@ -35,9 +31,7 @@ public void TestComplexCapture() var result = pattern.Match(statement); // Extract return expression node. - var capturedObject = result.Captures[returnValueGroup][0]; - - Assert.Same(valueExpression, capturedObject); + Assert.Same(expression, Assert.Single(result.GetCaptures(returnValueGroup))); } } } \ No newline at end of file diff --git a/test/Core/Echo.Ast.Tests/Patterns/PatternTest.cs b/test/Core/Echo.Ast.Tests/Patterns/PatternTest.cs index edbe6b3d..d7a64108 100644 --- a/test/Core/Echo.Ast.Tests/Patterns/PatternTest.cs +++ b/test/Core/Echo.Ast.Tests/Patterns/PatternTest.cs @@ -5,7 +5,7 @@ namespace Echo.Ast.Tests.Patterns { public class PatternTest { - private readonly CaptureGroup _captureGroup = new CaptureGroup("MyCapture"); + private readonly CaptureGroup _captureGroup = new("MyCapture"); [Fact] public void NonCapturedAnyPatternShouldMatchAndExtractValue() @@ -16,7 +16,7 @@ public void NonCapturedAnyPatternShouldMatchAndExtractValue() var result = pattern.Match(myObject); Assert.True(result.IsSuccess); - Assert.DoesNotContain(_captureGroup, result.Captures); + Assert.DoesNotContain(_captureGroup, result.GetCaptures(_captureGroup)); } [Fact] @@ -25,12 +25,12 @@ public void CapturedAnyPatternShouldMatchAndExtractValue() var pattern = Pattern.Any() .CaptureAs(_captureGroup); - var myObject = new object(); + object myObject = new object(); var result = pattern.Match(myObject); Assert.True(result.IsSuccess); - Assert.Contains(_captureGroup, result.Captures); - Assert.Contains(myObject, result.Captures[_captureGroup]); + Assert.Contains(_captureGroup, result.GetCaptureGroups()); + Assert.Contains(myObject, result.GetCaptures(_captureGroup)); } } } \ No newline at end of file diff --git a/test/Core/Echo.Ast.Tests/Patterns/PhiStatementPatternTest.cs b/test/Core/Echo.Ast.Tests/Patterns/PhiStatementPatternTest.cs index f3cee370..95342759 100644 --- a/test/Core/Echo.Ast.Tests/Patterns/PhiStatementPatternTest.cs +++ b/test/Core/Echo.Ast.Tests/Patterns/PhiStatementPatternTest.cs @@ -11,12 +11,12 @@ public class PhiStatementPattern [Fact] public void AnyPhiWithAnyVariables() { - var statement = new PhiStatement(new DummyVariable("phi1"), new List> + var statement = new PhiStatement(new DummyVariable("phi1"), new VariableExpression[] { - new VariableExpression(new DummyVariable("v1")), - new VariableExpression(new DummyVariable("v2")), - new VariableExpression(new DummyVariable("v3")), - new VariableExpression(new DummyVariable("v4")), + new(new DummyVariable("v1")), + new(new DummyVariable("v2")), + new(new DummyVariable("v3")), + new(new DummyVariable("v4")), }); var pattern = StatementPattern.Phi(); @@ -27,40 +27,42 @@ public void AnyPhiWithAnyVariables() [Fact] public void AnyPhiWithSpecificTargetPattern() { - var group = new CaptureGroup("phiGroup"); - - var statement = new PhiStatement(new DummyVariable("phi1"), new List> + var statement = new PhiStatement(new DummyVariable("phi1"), new VariableExpression[] { - new VariableExpression(new DummyVariable("v1")), - new VariableExpression(new DummyVariable("v2")), - new VariableExpression(new DummyVariable("v3")), - new VariableExpression(new DummyVariable("v4")), + new(new DummyVariable("v1")), + new(new DummyVariable("v2")), + new(new DummyVariable("v3")), + new(new DummyVariable("v4")), }); + + var group = new CaptureGroup("phiGroup"); var pattern = StatementPattern.Phi() .WithTarget(Pattern.Any().CaptureAs(group)); var result = pattern.Match(statement); Assert.True(result.IsSuccess); - Assert.Contains(group, result.Captures); - Assert.Contains(statement.Representative, result.Captures[group]); + Assert.Contains(group, result.GetCaptureGroups()); + Assert.Contains(statement.Representative, result.GetCaptures(group)); } [Fact] public void AnyPhiWithFixedVariables() { - var statement = new PhiStatement(new DummyVariable("phi1"), new List> + var statement = new PhiStatement(new DummyVariable("phi1"), new VariableExpression[] { - new VariableExpression(new DummyVariable("v1")), - new VariableExpression(new DummyVariable("v2")), - new VariableExpression(new DummyVariable("v3")), + new(new DummyVariable("v1")), + new(new DummyVariable("v2")), + new(new DummyVariable("v3")), }); - var pattern = StatementPattern.Phi() + var pattern = StatementPattern + .Phi() .WithSources( Pattern.Any>(), Pattern.Any>(), - Pattern.Any>()); + Pattern.Any>() + ); Assert.True(pattern.Matches(statement)); @@ -69,6 +71,5 @@ public void AnyPhiWithFixedVariables() Assert.False(pattern.Matches(statement)); } - } } \ No newline at end of file From cf95b069d6ee3e527f519f7733c674be0d645fa2 Mon Sep 17 00:00:00 2001 From: Washi Date: Mon, 25 Dec 2023 14:52:32 +0100 Subject: [PATCH 12/33] Add factory methods for ast nodes analogous to the ast patterns. --- src/Core/Echo.Ast/Construction/AstBuilder.cs | 32 +++-- src/Core/Echo.Ast/Expression.cs | 73 ++++++++++- .../Echo.Ast/Patterns/ExpressionPattern.cs | 7 +- src/Core/Echo.Ast/Statement.cs | 75 ++++++++++- src/Core/Echo.Ast/VariableExpression.cs | 5 +- .../Construction/AstBuilderTest.cs | 52 ++++---- .../Patterns/AssignmentStatementPattern.cs | 12 +- .../ExpressionStatementPatternTest.cs | 19 ++- .../InstructionExpressionPatternTest.cs | 117 +++++++++--------- .../Echo.Ast.Tests/Patterns/OrPatternTest.cs | 6 +- .../Echo.Ast.Tests/Patterns/PatternTest.cs | 4 +- .../Patterns/PhiStatementPatternTest.cs | 7 +- .../Patterns/VariableExpressionPatternTest.cs | 6 +- 13 files changed, 277 insertions(+), 138 deletions(-) diff --git a/src/Core/Echo.Ast/Construction/AstBuilder.cs b/src/Core/Echo.Ast/Construction/AstBuilder.cs index 5868ead5..95c2e0e2 100644 --- a/src/Core/Echo.Ast/Construction/AstBuilder.cs +++ b/src/Core/Echo.Ast/Construction/AstBuilder.cs @@ -96,20 +96,18 @@ private void LiftInstruction( long startOffset = architecture.GetOffset(instruction); // Wrap the instruction into an expression. - var expression = new InstructionExpression(instruction) - { - OriginalRange = new AddressRange( - startOffset, - startOffset + architecture.GetSize(instruction) - ) - }; - + var expression = Expression.Instruction(instruction); + expression.OriginalRange = new AddressRange( + startOffset, + startOffset + architecture.GetSize(instruction) + ); + // Determine the arguments. int popCount = architecture.GetStackPopCount(instruction); if (popCount == -1) { while (stack.Count > 0) - node.Transformed.Contents.Instructions.Add(new ExpressionStatement(stack.Pop())); + node.Transformed.Contents.Instructions.Add(stack.Pop().ToStatement()); } else { @@ -138,7 +136,7 @@ private void LiftInstruction( FlushStackIfImpure(node, stack, expression); // Wrap the expression into an independent statement and add it. - node.Transformed.Contents.Instructions.Add(new ExpressionStatement(expression)); + node.Transformed.Contents.Instructions.Add(expression.ToStatement()); break; case 1: @@ -158,11 +156,11 @@ private void LiftInstruction( variables[i] = node.DeclareStackIntermediate(); // Add the assignment statement. - node.Transformed.Contents.Instructions.Add(new AssignmentStatement(variables, expression)); + node.Transformed.Contents.Instructions.Add(Statement.Assignment(variables, expression)); // Push the intermediate variables. foreach (var variable in variables) - stack.Push(new VariableExpression(variable)); + stack.Push(variable.ToExpression()); break; } @@ -171,7 +169,7 @@ private void LiftInstruction( private static Expression Pop(LiftedNode node, Stack> stack) { return stack.Count == 0 - ? new VariableExpression(node.DeclareStackInput()) + ? node.DeclareStackInput().ToExpression() : stack.Pop(); } @@ -184,7 +182,7 @@ private static void FlushStackAndPush(LiftedNode node, Stack n.DeclareStackIntermediate()); for (int i = 0; i < variables.Count; i++) - stack.Push(new VariableExpression(variables[i])); + stack.Push(variables[i].ToExpression()); } private void FlushStackIfImpure( @@ -222,7 +220,7 @@ private static IList FlushStackInternal( // Create assignment statements. var assignments = new AssignmentStatement[stack.Count]; for (int i = variables.Length - 1; i >= 0; i--) - assignments[i] = new AssignmentStatement(variables[i], stack.Pop()); + assignments[i] = Statement.Assignment(variables[i], stack.Pop()); // Add them. foreach (var assignment in assignments) @@ -276,7 +274,7 @@ private void PopulatePhiStatements() foreach (var source in value.Sources) { if (input.Sources.All(x => x.Variable != source)) - input.Sources.Add(new VariableExpression(source)); + input.Sources.Add(source.ToExpression()); } } @@ -304,7 +302,7 @@ private void InsertPhiStatements() var singleSource = input.Sources[0]; input.Sources.RemoveAt(0); - var simplified = new AssignmentStatement(input.Representative, singleSource); + var simplified = Statement.Assignment(input.Representative, singleSource); block.Transformed.Contents.Instructions.Insert(0, simplified); } else diff --git a/src/Core/Echo.Ast/Expression.cs b/src/Core/Echo.Ast/Expression.cs index a04f7d8b..d7bd3768 100644 --- a/src/Core/Echo.Ast/Expression.cs +++ b/src/Core/Echo.Ast/Expression.cs @@ -1,7 +1,76 @@ -namespace Echo.Ast +using System.Collections.Generic; +using Echo.Code; + +namespace Echo.Ast { + /// + /// Provides factory methods for constructing expressions. + /// + public static class Expression + { + /// + /// Wraps the provided variable into a variable expression. + /// + /// The variable. + /// The type of instruction. + /// The resulting expression. + public static VariableExpression Variable(IVariable variable) => new(variable); + + /// + /// Wraps an instruction into an expression with no arguments. + /// + /// The instruction. + /// The type of instruction. + /// The resulting expression. + public static InstructionExpression Instruction(TInstruction instruction) => new(instruction); + + /// + /// Wraps an instruction into an expression with the provided arguments. + /// + /// The instruction. + /// The arguments. + /// The type of instruction. + /// The resulting expression. + public static InstructionExpression Instruction( + TInstruction instruction, + params Expression[] arguments) + { + return new InstructionExpression(instruction, arguments); + } + + /// + /// Wraps an instruction into an expression with the provided arguments. + /// + /// The instruction. + /// The arguments. + /// The type of instruction. + /// The resulting expression. + public static InstructionExpression Instruction( + TInstruction instruction, + IEnumerable> arguments) + { + return new InstructionExpression(instruction, arguments); + } + + /// + /// Wraps the provided variable into a variable expression. + /// + /// The variable. + /// The type of instruction. + /// The resulting expression. + public static VariableExpression ToExpression(this IVariable variable) + => new(variable); + } + /// /// Provides a base contract for expressions in the AST /// - public abstract class Expression : AstNode { } + public abstract class Expression : AstNode + { + /// + /// Wraps the expression into an expression statement. + /// + /// The resulting statement. + public ExpressionStatement ToStatement() => new(this); + } } \ No newline at end of file diff --git a/src/Core/Echo.Ast/Patterns/ExpressionPattern.cs b/src/Core/Echo.Ast/Patterns/ExpressionPattern.cs index 7b02c736..9d29a84c 100644 --- a/src/Core/Echo.Ast/Patterns/ExpressionPattern.cs +++ b/src/Core/Echo.Ast/Patterns/ExpressionPattern.cs @@ -18,7 +18,7 @@ public static class ExpressionPattern /// /// The instruction to match on. /// The pattern. - public static InstructionExpressionPattern InstructionLiteral(TInstruction instruction) + public static InstructionExpressionPattern Instruction(TInstruction instruction) => new(Pattern.Literal(instruction)); /// @@ -57,5 +57,10 @@ public static VariableExpressionPattern Variable(Pat /// The type of instructions stored in the abstract syntax tree. public abstract class ExpressionPattern : Pattern> { + /// + /// Wraps the expression pattern in an expression statement pattern. + /// + /// The resulting statement pattern. + public ExpressionStatementPattern ToStatement() => new(this); } } \ No newline at end of file diff --git a/src/Core/Echo.Ast/Statement.cs b/src/Core/Echo.Ast/Statement.cs index 3915d003..87cbe03f 100644 --- a/src/Core/Echo.Ast/Statement.cs +++ b/src/Core/Echo.Ast/Statement.cs @@ -1,5 +1,78 @@ -namespace Echo.Ast +using System.Collections.Generic; +using System.Linq; +using Echo.Code; + +namespace Echo.Ast { + /// + /// Provides factory methods for creating new statements. + /// + public static class Statement + { + /// + /// Wraps an expression into an expression statement. + /// + /// The expression to wrap. + /// The type of instructions stored in the expression. + /// The resulting statement. + public static ExpressionStatement Expression(Expression expression) + => new(expression); + + /// + /// Constructs a new assignment statement that assigns a value to a single variable. + /// + /// The variable to assign the value to. + /// The expression representing the value. + /// The type of instructions stored in the expression. + /// The resulting statement. + public static AssignmentStatement Assignment( + IVariable variable, + Expression value) + { + return new AssignmentStatement(variable, value); + } + + /// + /// Constructs a new assignment statement that assigns a value to an ordered list of variables. + /// + /// The variables to assign the value to. + /// The expression representing the value. + /// The type of instructions stored in the expression. + /// The resulting statement. + public static AssignmentStatement Assignment( + IEnumerable variable, + Expression value) + { + return new AssignmentStatement(variable, value); + } + + /// + /// Constructs a new Phi statement that assigns a representative variable to a set of sources. + /// + /// The representative variable. + /// The sources. + /// The type of instructions stored in the expression. + /// The resulting statement. + public static PhiStatement Phi(IVariable representative, params IVariable[] sources) + { + return new PhiStatement(representative, sources.Select(x => x.ToExpression())); + } + + /// + /// Constructs a new Phi statement that assigns a representative variable to a set of sources. + /// + /// The representative variable. + /// The sources. + /// The type of instructions stored in the expression. + /// The resulting statement. + public static PhiStatement Phi( + IVariable representative, + params VariableExpression[] sources) + { + return new PhiStatement(representative, sources); + } + } + /// /// Provides a base contract for statements in the AST /// diff --git a/src/Core/Echo.Ast/VariableExpression.cs b/src/Core/Echo.Ast/VariableExpression.cs index e64e8af5..d9e3545b 100644 --- a/src/Core/Echo.Ast/VariableExpression.cs +++ b/src/Core/Echo.Ast/VariableExpression.cs @@ -15,7 +15,10 @@ public sealed class VariableExpression : Expression /// Creates a new variable expression /// /// The variable - public VariableExpression(IVariable variable) => Variable = variable; + public VariableExpression(IVariable variable) + { + Variable = variable; + } /// /// The variable that is represented by the AST node diff --git a/test/Core/Echo.Ast.Tests/Construction/AstBuilderTest.cs b/test/Core/Echo.Ast.Tests/Construction/AstBuilderTest.cs index 4b27d5f0..7c69547a 100644 --- a/test/Core/Echo.Ast.Tests/Construction/AstBuilderTest.cs +++ b/test/Core/Echo.Ast.Tests/Construction/AstBuilderTest.cs @@ -72,10 +72,10 @@ public void StatementWithTwoArguments() // pop(push(), push()) var match = StatementPattern .Expression(ExpressionPattern - .Instruction(new DummyInstructionPattern(DummyOpCode.Pop)) + .Instruction(new DummyInstructionPattern(DummyOpCode.Pop)) .WithArguments( - ExpressionPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Push)), - ExpressionPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Push)) + ExpressionPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Push)), + ExpressionPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Push)) ).CaptureArguments(arguments) ) .Match(node.Contents.Instructions[0]); @@ -115,9 +115,9 @@ public void TwoStatementWithDifferentArguments() // pop(push()) var match = StatementPattern .Expression(ExpressionPattern - .Instruction(new DummyInstructionPattern(DummyOpCode.Pop)) + .Instruction(new DummyInstructionPattern(DummyOpCode.Pop)) .WithArguments( - ExpressionPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Push)) + ExpressionPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Push)) ).CaptureArguments(arguments) ) .Match(node.Contents.Instructions[0]); @@ -132,10 +132,10 @@ public void TwoStatementWithDifferentArguments() // pop(push(), push()) match = StatementPattern .Expression(ExpressionPattern - .Instruction(new DummyInstructionPattern(DummyOpCode.Pop)) + .Instruction(new DummyInstructionPattern(DummyOpCode.Pop)) .WithArguments( - ExpressionPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Push)), - ExpressionPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Push)) + ExpressionPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Push)), + ExpressionPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Push)) ).CaptureArguments(arguments) ) .Match(node.Contents.Instructions[1]); @@ -170,7 +170,7 @@ public void ExpressionWithMultipleReturnValues() Assert.True(StatementPattern .Assignment( new[] {Pattern.Any(), Pattern.Any()}, - ExpressionPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Op)) + ExpressionPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Op)) ).Match(cfg.Nodes[0].Contents.Instructions[0]).IsSuccess ); } @@ -196,12 +196,12 @@ public void NestedExpressions() var match = StatementPattern .Expression(ExpressionPattern - .Instruction(new DummyInstructionPattern(DummyOpCode.Op)) + .Instruction(new DummyInstructionPattern(DummyOpCode.Op)) .WithArguments( - ExpressionPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Push)), - ExpressionPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Op)) + ExpressionPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Push)), + ExpressionPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Op)) .WithArguments( - ExpressionPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Push)) + ExpressionPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Push)) ) ) ) @@ -237,19 +237,19 @@ public void NestedImpureExpressions() var n1 = Assert.Single(cfg.Nodes); var pattern = StatementPattern.Expression(ExpressionPattern - .Instruction(new DummyInstructionPattern(DummyOpCode.Op)) + .Instruction(new DummyInstructionPattern(DummyOpCode.Op)) .WithArguments( ExpressionPattern - .Instruction(new DummyInstructionPattern(DummyOpCode.Op)) + .Instruction(new DummyInstructionPattern(DummyOpCode.Op)) .WithArguments( - ExpressionPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Op)), - ExpressionPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Op)) + ExpressionPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Op)), + ExpressionPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Op)) ), ExpressionPattern - .Instruction(new DummyInstructionPattern(DummyOpCode.Op)) + .Instruction(new DummyInstructionPattern(DummyOpCode.Op)) .WithArguments( - ExpressionPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Op)), - ExpressionPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Op)) + ExpressionPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Op)), + ExpressionPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Op)) ) ) ); @@ -290,7 +290,7 @@ public void PushArgumentBeforeImpureStatement() var pattern1 = StatementPattern .Assignment( Pattern.Any().CaptureAs(variable), - ExpressionPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Push)) + ExpressionPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Push)) ); var match1 = pattern1.Match(node.Contents.Instructions[0]); var match2 = pattern1.Match(node.Contents.Instructions[1]); @@ -301,7 +301,7 @@ public void PushArgumentBeforeImpureStatement() // op(push()) Assert.True(StatementPattern .Expression(ExpressionPattern - .Instruction(new DummyInstructionPattern(DummyOpCode.Op)) + .Instruction(new DummyInstructionPattern(DummyOpCode.Op)) .WithArguments(1) ) .Match(node.Contents.Instructions[2]).IsSuccess); @@ -309,7 +309,7 @@ public void PushArgumentBeforeImpureStatement() // pop(tmp) var pattern2 = StatementPattern .Expression(ExpressionPattern - .Instruction(new DummyInstructionPattern(DummyOpCode.Pop)) + .Instruction(new DummyInstructionPattern(DummyOpCode.Pop)) .WithArguments(ExpressionPattern.Variable( Pattern.Any().CaptureAs(variable)) ) @@ -379,7 +379,7 @@ public void TwoNodesWithStackDeltas() var match1 = StatementPattern .Assignment( Pattern.Any().CaptureAs(variable), - ExpressionPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Push)) + ExpressionPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Push)) ) .Match(n1.Contents.Instructions[0]); Assert.True(match1.IsSuccess); @@ -396,7 +396,7 @@ public void TwoNodesWithStackDeltas() // pop(in) var match3 = StatementPattern .Expression(ExpressionPattern - .Instruction(new DummyInstructionPattern(DummyOpCode.Pop)) + .Instruction(new DummyInstructionPattern(DummyOpCode.Pop)) .WithArguments( ExpressionPattern.Variable(Pattern.Any().CaptureAs(variable)) ) @@ -452,7 +452,7 @@ public void TwoNodesWithNestedStackDelta() var value = new CaptureGroup>("value"); var pattern = StatementPattern.Assignment( Pattern.Any().CaptureAs(variable), - ExpressionPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Push)).CaptureAs(value) + ExpressionPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Push)).CaptureAs(value) ); // Ensure expressions are pushed as variables. diff --git a/test/Core/Echo.Ast.Tests/Patterns/AssignmentStatementPattern.cs b/test/Core/Echo.Ast.Tests/Patterns/AssignmentStatementPattern.cs index 810d4bcf..93a42ad2 100644 --- a/test/Core/Echo.Ast.Tests/Patterns/AssignmentStatementPattern.cs +++ b/test/Core/Echo.Ast.Tests/Patterns/AssignmentStatementPattern.cs @@ -10,9 +10,9 @@ public class AssignmentStatementPattern [Fact] public void AnyVariableAndAnyExpression() { - var statement = new AssignmentStatement( + var statement = Statement.Assignment( new DummyVariable("var1"), - new InstructionExpression(1) + Expression.Instruction(1) ); var pattern = StatementPattern.Assignment(); @@ -23,9 +23,9 @@ public void AnyVariableAndAnyExpression() [Fact] public void AnyVariableWithSpecificExpression() { - var statement = new AssignmentStatement( + var statement = Statement.Assignment( new DummyVariable("var1"), - new InstructionExpression(1) + Expression.Instruction(1) ); var group = new CaptureGroup>("group"); @@ -44,9 +44,9 @@ public void SpecificVariable() { var group = new CaptureGroup("group"); - var statement = new AssignmentStatement( + var statement = Statement.Assignment( new DummyVariable("var1"), - new InstructionExpression(1) + Expression.Instruction(1) ); var pattern = StatementPattern diff --git a/test/Core/Echo.Ast.Tests/Patterns/ExpressionStatementPatternTest.cs b/test/Core/Echo.Ast.Tests/Patterns/ExpressionStatementPatternTest.cs index 9f9e09d1..93e04568 100644 --- a/test/Core/Echo.Ast.Tests/Patterns/ExpressionStatementPatternTest.cs +++ b/test/Core/Echo.Ast.Tests/Patterns/ExpressionStatementPatternTest.cs @@ -10,22 +10,19 @@ public class ExpressionStatementPatternTest public void TestComplexCapture() { // Define test "ret(push(0, 1))" expression/ - var expression = new InstructionExpression(DummyInstruction.Push(0, 1)); - var statement = new ExpressionStatement( - new InstructionExpression(DummyInstruction.Ret(1), expression) - ); + var expression = Expression.Instruction(DummyInstruction.Push(0, 1)); + var statement = Expression.Instruction(DummyInstruction.Ret(1), expression).ToStatement(); // Define capture group. var returnValueGroup = new CaptureGroup>("returnValue"); // Create ret(?) pattern. - var pattern = StatementPattern.Expression( - ExpressionPattern - .Instruction(new DummyInstructionPattern(DummyOpCode.Ret)) - .WithArguments( - ExpressionPattern.Any().CaptureAs(returnValueGroup) - ) - ); + var pattern = ExpressionPattern + .Instruction(new DummyInstructionPattern(DummyOpCode.Ret)) + .WithArguments( + ExpressionPattern.Any().CaptureAs(returnValueGroup) + ) + .ToStatement(); // Match. var result = pattern.Match(statement); diff --git a/test/Core/Echo.Ast.Tests/Patterns/InstructionExpressionPatternTest.cs b/test/Core/Echo.Ast.Tests/Patterns/InstructionExpressionPatternTest.cs index f09519e0..0adee3d6 100644 --- a/test/Core/Echo.Ast.Tests/Patterns/InstructionExpressionPatternTest.cs +++ b/test/Core/Echo.Ast.Tests/Patterns/InstructionExpressionPatternTest.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; using Echo.Ast.Patterns; -using Echo.Platforms.DummyPlatform.Code; using Xunit; namespace Echo.Ast.Tests.Patterns @@ -12,8 +8,8 @@ public class InstructionExpressionPatternTest [Fact] public void SameInstructionWithZeroArgumentsShouldMatch() { - var pattern = ExpressionPattern.InstructionLiteral(1234); - var input = new InstructionExpression(1234, ImmutableArray>.Empty); + var input = Expression.Instruction(1234); + var pattern = ExpressionPattern.Instruction(1234); Assert.True(pattern.Matches(input)); } @@ -21,8 +17,8 @@ public void SameInstructionWithZeroArgumentsShouldMatch() [Fact] public void DifferentInstructionWithZeroArgumentsShouldNotMatch() { - var pattern = ExpressionPattern.InstructionLiteral(1234); - var input = new InstructionExpression(5678, ImmutableArray>.Empty); + var input = Expression.Instruction(5678); + var pattern = ExpressionPattern.Instruction(1234); Assert.False(pattern.Matches(input)); } @@ -34,17 +30,16 @@ public void DifferentInstructionWithZeroArgumentsShouldNotMatch() [InlineData(true, 3)] public void InstructionWithAnyArgumentsShouldMatchIfInstructionIsEqual(bool sameInstruction, int argumentCount) { - var pattern = ExpressionPattern - .InstructionLiteral(1234) - .WithAnyArguments(); - - var arguments = new List>(argumentCount); + var arguments = new Expression[argumentCount]; for (int i = 0; i < argumentCount; i++) - arguments.Add(new InstructionExpression(0, ImmutableArray>.Empty)); + arguments[i] = Expression.Instruction(i); - var input = new InstructionExpression(sameInstruction ? 1234 : 5678, arguments); - - var result = pattern.Match(input); + var input = Expression.Instruction(sameInstruction ? 1234 : 5678, arguments); + + var result = ExpressionPattern + .Instruction(1234) + .WithAnyArguments() + .Match(input); Assert.Equal(sameInstruction, result.IsSuccess); } @@ -52,36 +47,33 @@ public void InstructionWithAnyArgumentsShouldMatchIfInstructionIsEqual(bool same [Fact] public void SameInstructionWithMatchingArgumentsShouldMatch() { + var input = Expression + .Instruction(1234) + .WithArguments( + Expression.Instruction(0), + Expression.Instruction(1) + ); + var pattern = ExpressionPattern - .InstructionLiteral(1234) + .Instruction(1234) .WithArguments(ExpressionPattern.Any(), ExpressionPattern.Any()); - var arguments = new List>(2) - { - new InstructionExpression(0, ImmutableArray>.Empty), - new InstructionExpression(1, ImmutableArray>.Empty), - }; - - var input = new InstructionExpression(1234, arguments); - Assert.True(pattern.Matches(input)); } [Fact] public void DifferentInstructionWithMatchingArgumentsShouldNotMatch() { + var input = new InstructionExpression( + 5678, + Expression.Instruction(0), + Expression.Instruction(1) + ); + var pattern = ExpressionPattern - .InstructionLiteral(1234) + .Instruction(1234) .WithArguments(ExpressionPattern.Any(), ExpressionPattern.Any()); - var arguments = new List>(2) - { - new InstructionExpression(0, ImmutableArray>.Empty), - new InstructionExpression(1, ImmutableArray>.Empty), - }; - - var input = new InstructionExpression(5678, arguments); - Assert.False(pattern.Matches(input)); } @@ -89,16 +81,18 @@ public void DifferentInstructionWithMatchingArgumentsShouldNotMatch() public void SameInstructionWithNonMatchingArgumentsShouldNotMatch() { var pattern = ExpressionPattern - .InstructionLiteral(1234) - .WithArguments(ExpressionPattern.InstructionLiteral(5678), ExpressionPattern.Any()); - - var arguments = new List>(2) - { - new InstructionExpression(0, ImmutableArray>.Empty), - new InstructionExpression(1, ImmutableArray>.Empty), - }; + .Instruction(1234) + .WithArguments( + ExpressionPattern.Instruction(5678), + ExpressionPattern.Any() + ); - var input = new InstructionExpression(1234, arguments); + var input = Expression + .Instruction(1234) + .WithArguments( + Expression.Instruction(0), + Expression.Instruction(1) + ); Assert.False(pattern.Matches(input)); } @@ -107,15 +101,17 @@ public void SameInstructionWithNonMatchingArgumentsShouldNotMatch() public void SameInstructionWithDifferentArgumentCountShouldNotMatch() { var pattern = ExpressionPattern - .InstructionLiteral(1234) - .WithArguments(ExpressionPattern.Any(), ExpressionPattern.Any()); - - var arguments = new List>(2) - { - new InstructionExpression(0, ImmutableArray>.Empty), - }; + .Instruction(1234) + .WithArguments( + ExpressionPattern.Any(), + ExpressionPattern.Any() + ); - var input = new InstructionExpression(1234, arguments); + var input = Expression + .Instruction(1234) + .WithArguments( + Expression.Instruction(0) + ); Assert.False(pattern.Matches(input)); } @@ -124,18 +120,17 @@ public void SameInstructionWithDifferentArgumentCountShouldNotMatch() public void SameInstructionWithComplexMatchingArgumentsShouldNotMatch() { var pattern = ExpressionPattern - .InstructionLiteral(1234) + .Instruction(1234) .WithArguments( - ExpressionPattern.InstructionLiteral(1234) | ExpressionPattern.InstructionLiteral(5678), + ExpressionPattern.Instruction(1234) | ExpressionPattern.Instruction(5678), ExpressionPattern.Any()); - var arguments = new List>(2) - { - new InstructionExpression(5678, ImmutableArray>.Empty), - new InstructionExpression(1, ImmutableArray>.Empty), - }; - - var input = new InstructionExpression(1234, arguments); + var input = Expression + .Instruction(1234) + .WithArguments( + Expression.Instruction(5678), + Expression.Instruction(1) + ); Assert.True(pattern.Matches(input)); } diff --git a/test/Core/Echo.Ast.Tests/Patterns/OrPatternTest.cs b/test/Core/Echo.Ast.Tests/Patterns/OrPatternTest.cs index 400ac60e..522b76ee 100644 --- a/test/Core/Echo.Ast.Tests/Patterns/OrPatternTest.cs +++ b/test/Core/Echo.Ast.Tests/Patterns/OrPatternTest.cs @@ -34,9 +34,9 @@ public void LeftHandSideOrPatternShouldBeFlattened() var pattern3 = Pattern.Literal(3); var combined = pattern1 | pattern2; - combined = combined | pattern3; + combined |= pattern3; - Assert.Equal(new[] {pattern1, pattern2, pattern3}, combined.Options); + Assert.Equal(new Pattern[] {pattern1, pattern2, pattern3}, combined.Options); } [Fact] @@ -49,7 +49,7 @@ public void RightHandSideOrPatternShouldBeFlattened() var combined = pattern2 | pattern3; combined = pattern1 | combined; - Assert.Equal(new[] {pattern1, pattern2, pattern3}, combined.Options); + Assert.Equal(new Pattern[] {pattern1, pattern2, pattern3}, combined.Options); } } diff --git a/test/Core/Echo.Ast.Tests/Patterns/PatternTest.cs b/test/Core/Echo.Ast.Tests/Patterns/PatternTest.cs index d7a64108..0956b785 100644 --- a/test/Core/Echo.Ast.Tests/Patterns/PatternTest.cs +++ b/test/Core/Echo.Ast.Tests/Patterns/PatternTest.cs @@ -12,7 +12,7 @@ public void NonCapturedAnyPatternShouldMatchAndExtractValue() { var pattern = Pattern.Any(); - var myObject = new object(); + object myObject = new(); var result = pattern.Match(myObject); Assert.True(result.IsSuccess); @@ -25,7 +25,7 @@ public void CapturedAnyPatternShouldMatchAndExtractValue() var pattern = Pattern.Any() .CaptureAs(_captureGroup); - object myObject = new object(); + object myObject = new(); var result = pattern.Match(myObject); Assert.True(result.IsSuccess); diff --git a/test/Core/Echo.Ast.Tests/Patterns/PhiStatementPatternTest.cs b/test/Core/Echo.Ast.Tests/Patterns/PhiStatementPatternTest.cs index 95342759..157aa141 100644 --- a/test/Core/Echo.Ast.Tests/Patterns/PhiStatementPatternTest.cs +++ b/test/Core/Echo.Ast.Tests/Patterns/PhiStatementPatternTest.cs @@ -1,4 +1,3 @@ -using System.Collections.Generic; using Echo.Ast.Patterns; using Echo.Code; using Echo.Platforms.DummyPlatform.Code; @@ -11,7 +10,7 @@ public class PhiStatementPattern [Fact] public void AnyPhiWithAnyVariables() { - var statement = new PhiStatement(new DummyVariable("phi1"), new VariableExpression[] + var statement = Statement.Phi(new DummyVariable("phi1"), new VariableExpression[] { new(new DummyVariable("v1")), new(new DummyVariable("v2")), @@ -27,7 +26,7 @@ public void AnyPhiWithAnyVariables() [Fact] public void AnyPhiWithSpecificTargetPattern() { - var statement = new PhiStatement(new DummyVariable("phi1"), new VariableExpression[] + var statement = Statement.Phi(new DummyVariable("phi1"), new VariableExpression[] { new(new DummyVariable("v1")), new(new DummyVariable("v2")), @@ -49,7 +48,7 @@ public void AnyPhiWithSpecificTargetPattern() [Fact] public void AnyPhiWithFixedVariables() { - var statement = new PhiStatement(new DummyVariable("phi1"), new VariableExpression[] + var statement = Statement.Phi(new DummyVariable("phi1"), new VariableExpression[] { new(new DummyVariable("v1")), new(new DummyVariable("v2")), diff --git a/test/Core/Echo.Ast.Tests/Patterns/VariableExpressionPatternTest.cs b/test/Core/Echo.Ast.Tests/Patterns/VariableExpressionPatternTest.cs index f13a8e2b..a085a32b 100644 --- a/test/Core/Echo.Ast.Tests/Patterns/VariableExpressionPatternTest.cs +++ b/test/Core/Echo.Ast.Tests/Patterns/VariableExpressionPatternTest.cs @@ -11,7 +11,7 @@ public void AnyVariableExpressionShouldMatch() { var variable = new DummyVariable("variable"); var pattern = ExpressionPattern.Variable(); - Assert.True(pattern.Matches(new VariableExpression(variable))); + Assert.True(pattern.Matches(variable.ToExpression())); } [Fact] @@ -19,7 +19,7 @@ public void SameSpecificVariableShouldMatch() { var variable = new DummyVariable("variable"); var pattern = ExpressionPattern.Variable(variable); - Assert.True(pattern.Matches(new VariableExpression(variable))); + Assert.True(pattern.Matches(variable.ToExpression())); } [Fact] @@ -29,7 +29,7 @@ public void DifferentSpecificVariableShouldMatch() var variable2 = new DummyVariable("variable2"); var pattern = ExpressionPattern.Variable(variable1); - Assert.False(pattern.Matches(new VariableExpression(variable2))); + Assert.False(pattern.Matches(variable2.ToExpression())); } } } \ No newline at end of file From eef2ac8234e6e3de03939a2baa99d722f8790eef Mon Sep 17 00:00:00 2001 From: Washi Date: Mon, 25 Dec 2023 16:16:07 +0100 Subject: [PATCH 13/33] Promote stack intermediate variables to output vars if they are left on the stack. --- src/Core/Echo.Ast/Construction/AstBuilder.cs | 82 ++++++++++++------- .../Echo.Ast/Patterns/ExpressionPattern.cs | 2 +- .../Construction/AstBuilderTest.cs | 76 +++++++++++++---- .../InstructionExpressionPatternTest.cs | 20 ++--- 4 files changed, 127 insertions(+), 53 deletions(-) diff --git a/src/Core/Echo.Ast/Construction/AstBuilder.cs b/src/Core/Echo.Ast/Construction/AstBuilder.cs index 95c2e0e2..c4acaea9 100644 --- a/src/Core/Echo.Ast/Construction/AstBuilder.cs +++ b/src/Core/Echo.Ast/Construction/AstBuilder.cs @@ -47,7 +47,7 @@ private void Run() // are executed as a single unit, and as such, within a single node we can assume an isolated eval stack. // This avoids full data flow analysis (i.e., building a full DFG) to build ASTs, at the cost of a slight // chance of overproducing some synthetic variables to communicate non-zero stack deltas between cfg nodes. - // In practice however this is much rarer to occur than not. + // In practice however this is much rarer to occur than not: Typically a block has a stack delta of 0. // // We only use SSA form and PHI nodes for synthetic stack variables. This is because many languages/platforms // allow for variables to be accessed by reference and thus it is not clear just from the access alone whether @@ -82,7 +82,7 @@ private LiftedNode LiftNode(ControlFlowNode node) LiftInstruction(result, node.Contents.Instructions[i], stack); // Any values left on the stack we move into synthetic out variables. - FlushStack(result, stack); + FlushStackAsOutput(result, stack); return result; } @@ -121,19 +121,24 @@ private void LiftInstruction( } // Determine the produced values. - int pushCount = architecture.GetStackPushCount(instruction); - switch (pushCount) + switch (architecture.GetStackPushCount(instruction)) { case 0: // No value produced means we are dealing with a new independent statement. - // If we are the final terminator or branch instruction at the end of the block, we want to flush any - // remaining values on the stack *before* the instruction statement. if ((architecture.GetFlowControl(instruction) & (InstructionFlowControl.CanBranch | InstructionFlowControl.IsTerminator)) != 0) - FlushStack(node, stack); - - // Ensure order of operations is preserved if expression is potentially impure. - FlushStackIfImpure(node, stack, expression); + { + // If we are the final terminator or branch instruction at the end of the block, we need to flush + // any remaining values on the stack *before* the instruction statement to ensure the operations + // are evaluated before jumping to the next block. + FlushStackAsOutput(node, stack); + } + else + { + // For any other case we may still need to flush the stack if the expression is potentially impure + // and the stack contains potentially impure items, to preserve order of impure operations. + FlushStackIfImpure(node, stack, expression); + } // Wrap the expression into an independent statement and add it. node.Transformed.Contents.Instructions.Add(expression.ToStatement()); @@ -144,7 +149,7 @@ private void LiftInstruction( stack.Push(expression); break; - default: + case var pushCount: // Multiple values are produced, move them into separate variables and push them on eval stack. // Ensure order of operations is preserved if expression is potentially impure. @@ -173,14 +178,36 @@ private static Expression Pop(LiftedNode node, Stack : stack.Pop(); } - private static void FlushStack(LiftedNode node, Stack> stack) + private static void FlushStackAsOutput(LiftedNode node, Stack> stack) { - FlushStackInternal(node, stack, n => n.DeclareStackOutput()); + FlushStackInternal(node, stack, (n, value) => + { + if (value is VariableExpression {Variable: SyntheticVariable variable}) + { + // If this output expression is already variable expression, we do not need to allocate a new variable + // and can instead just promote the variable to a stack output variable (inlining). + node.StackOutputs.Add(variable); + } + else + { + // Otherwise, declare and assign the value to a new stack output variable. + variable = n.DeclareStackOutput(); + n.Transformed.Contents.Instructions.Add(Statement.Assignment(variable, value)); + } + + return variable; + }); } private static void FlushStackAndPush(LiftedNode node, Stack> stack) { - var variables = FlushStackInternal(node, stack, n => n.DeclareStackIntermediate()); + var variables = FlushStackInternal(node, stack, (n, value) => + { + var intermediate = n.DeclareStackIntermediate(); + n.Transformed.Contents.Instructions.Add(Statement.Assignment(intermediate, value)); + return intermediate; + }); + for (int i = 0; i < variables.Count; i++) stack.Push(variables[i].ToExpression()); } @@ -190,9 +217,12 @@ private void FlushStackIfImpure( Stack> stack, Expression expression) { + // Is this expression potentially impure? if (expression.IsPure(_purityClassifier).ToBooleanOrFalse()) return; + // Does the stack contain potentially impure expressions? + // We then still need to flush to preserve order of operations. bool fullyPureStack = true; foreach (var value in stack) { @@ -210,21 +240,17 @@ private void FlushStackIfImpure( private static IList FlushStackInternal( LiftedNode node, Stack> stack, - Func, IVariable> declareVariable) + Func, Expression, SyntheticVariable> flush) { - // Declare new variables. - var variables = new IVariable[stack.Count]; - for (int i = 0; i < stack.Count; i++) - variables[i] = declareVariable(node); - - // Create assignment statements. - var assignments = new AssignmentStatement[stack.Count]; - for (int i = variables.Length - 1; i >= 0; i--) - assignments[i] = Statement.Assignment(variables[i], stack.Pop()); - - // Add them. - foreach (var assignment in assignments) - node.Transformed.Contents.Instructions.Add(assignment); + // Collect all values from the stack. + var values = new Expression[stack.Count]; + for (int i = values.Length - 1; i >= 0; i--) + values[i] = stack.Pop(); + + // Flush them to variables. + var variables = new IVariable[values.Length]; + for (int i = 0; i < values.Length; i++) + variables[i] = flush(node, values[i]); return variables; } diff --git a/src/Core/Echo.Ast/Patterns/ExpressionPattern.cs b/src/Core/Echo.Ast/Patterns/ExpressionPattern.cs index 9d29a84c..33a8857e 100644 --- a/src/Core/Echo.Ast/Patterns/ExpressionPattern.cs +++ b/src/Core/Echo.Ast/Patterns/ExpressionPattern.cs @@ -18,7 +18,7 @@ public static class ExpressionPattern /// /// The instruction to match on. /// The pattern. - public static InstructionExpressionPattern Instruction(TInstruction instruction) + public static InstructionExpressionPattern InstructionLiteral(TInstruction instruction) => new(Pattern.Literal(instruction)); /// diff --git a/test/Core/Echo.Ast.Tests/Construction/AstBuilderTest.cs b/test/Core/Echo.Ast.Tests/Construction/AstBuilderTest.cs index 7c69547a..2bd725f5 100644 --- a/test/Core/Echo.Ast.Tests/Construction/AstBuilderTest.cs +++ b/test/Core/Echo.Ast.Tests/Construction/AstBuilderTest.cs @@ -410,6 +410,42 @@ public void TwoNodesWithStackDeltas() Assert.Same(n2, n1.UnconditionalNeighbour); } + [Fact] + public void TwoNodesWithIndirectStackDeltaShouldInline() + { + // Construct + var cfg = ConstructGraph(new[] + { + DummyInstruction.Push(0, 1), + DummyInstruction.Op(1, 0, 0), + DummyInstruction.Jmp(2, 10), + + DummyInstruction.Pop(10, 1), + DummyInstruction.Ret(11) + }); + + var variable = new CaptureGroup(); + + var match1 = StatementPattern + .Assignment( + Pattern.Any().CaptureAs(variable), + ExpressionPattern.Any() + ) + .FollowedBy(ExpressionPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Op)).ToStatement()) + .FollowedBy(ExpressionPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Jmp)).ToStatement()) + .Match(cfg.Nodes[0].Contents.Instructions); + + var match2 = StatementPattern + .Assignment( + Pattern.Any(), + ExpressionPattern.Variable(Pattern.Any().CaptureAs(variable)) + ) + .Match(cfg.Nodes[10].Contents.Instructions[0]); + + Assert.True(match1.IsSuccess); + Assert.True(match2.IsSuccess); + } + [Fact] public void TwoNodesPushBeforeImpure() { @@ -624,13 +660,19 @@ public void ConditionalReplaceStackSlot() var variablesCapture = new CaptureGroup("variables"); var sourcesCapture = new CaptureGroup>("sources"); - var pattern = StatementPattern.Assignment( - Pattern.Any().CaptureAs(variablesCapture), - ExpressionPattern.Any() - ); - - var match1 = pattern.Match(cfg.Nodes[0].Contents.Instructions[1]); - var match2 = pattern.Match(cfg.Nodes[3].Contents.Instructions[^1]); + var match1 = StatementPattern + .Assignment() + .WithVariables( + Pattern.Any().CaptureAs(variablesCapture), + Pattern.Any() + ) + .Match(cfg.Nodes[0].Contents.Instructions[0]); + + var match2 = StatementPattern + .Assignment() + .WithVariables(1) + .CaptureVariables(variablesCapture) + .Match(cfg.Nodes[3].Contents.Instructions[^1]); var match3 = StatementPattern.Phi() .WithSources(2) @@ -669,14 +711,20 @@ public void ConditionalReplaceStackSlotNested() var variablesCapture = new CaptureGroup("variables"); var sourcesCapture = new CaptureGroup>("sources"); - - var pattern = StatementPattern.Assignment( - Pattern.Any().CaptureAs(variablesCapture), - ExpressionPattern.Any() - ); - var match1 = pattern.Match(cfg.Nodes[0].Contents.Instructions[^2]); - var match2 = pattern.Match(cfg.Nodes[4].Contents.Instructions[^1]); + var match1 = StatementPattern + .Assignment() + .WithVariables( + Pattern.Any().CaptureAs(variablesCapture), + Pattern.Any() + ) + .Match(cfg.Nodes[0].Contents.Instructions[^2]); + + var match2 = StatementPattern + .Assignment() + .WithVariables(1) + .CaptureVariables(variablesCapture) + .Match(cfg.Nodes[4].Contents.Instructions[^1]); var match3 = StatementPattern.Phi() .WithSources(2) diff --git a/test/Core/Echo.Ast.Tests/Patterns/InstructionExpressionPatternTest.cs b/test/Core/Echo.Ast.Tests/Patterns/InstructionExpressionPatternTest.cs index 0adee3d6..893d3be2 100644 --- a/test/Core/Echo.Ast.Tests/Patterns/InstructionExpressionPatternTest.cs +++ b/test/Core/Echo.Ast.Tests/Patterns/InstructionExpressionPatternTest.cs @@ -9,7 +9,7 @@ public class InstructionExpressionPatternTest public void SameInstructionWithZeroArgumentsShouldMatch() { var input = Expression.Instruction(1234); - var pattern = ExpressionPattern.Instruction(1234); + var pattern = ExpressionPattern.InstructionLiteral(1234); Assert.True(pattern.Matches(input)); } @@ -18,7 +18,7 @@ public void SameInstructionWithZeroArgumentsShouldMatch() public void DifferentInstructionWithZeroArgumentsShouldNotMatch() { var input = Expression.Instruction(5678); - var pattern = ExpressionPattern.Instruction(1234); + var pattern = ExpressionPattern.InstructionLiteral(1234); Assert.False(pattern.Matches(input)); } @@ -37,7 +37,7 @@ public void InstructionWithAnyArgumentsShouldMatchIfInstructionIsEqual(bool same var input = Expression.Instruction(sameInstruction ? 1234 : 5678, arguments); var result = ExpressionPattern - .Instruction(1234) + .InstructionLiteral(1234) .WithAnyArguments() .Match(input); @@ -55,7 +55,7 @@ public void SameInstructionWithMatchingArgumentsShouldMatch() ); var pattern = ExpressionPattern - .Instruction(1234) + .InstructionLiteral(1234) .WithArguments(ExpressionPattern.Any(), ExpressionPattern.Any()); Assert.True(pattern.Matches(input)); @@ -71,7 +71,7 @@ public void DifferentInstructionWithMatchingArgumentsShouldNotMatch() ); var pattern = ExpressionPattern - .Instruction(1234) + .InstructionLiteral(1234) .WithArguments(ExpressionPattern.Any(), ExpressionPattern.Any()); Assert.False(pattern.Matches(input)); @@ -81,9 +81,9 @@ public void DifferentInstructionWithMatchingArgumentsShouldNotMatch() public void SameInstructionWithNonMatchingArgumentsShouldNotMatch() { var pattern = ExpressionPattern - .Instruction(1234) + .InstructionLiteral(1234) .WithArguments( - ExpressionPattern.Instruction(5678), + ExpressionPattern.InstructionLiteral(5678), ExpressionPattern.Any() ); @@ -101,7 +101,7 @@ public void SameInstructionWithNonMatchingArgumentsShouldNotMatch() public void SameInstructionWithDifferentArgumentCountShouldNotMatch() { var pattern = ExpressionPattern - .Instruction(1234) + .InstructionLiteral(1234) .WithArguments( ExpressionPattern.Any(), ExpressionPattern.Any() @@ -120,9 +120,9 @@ public void SameInstructionWithDifferentArgumentCountShouldNotMatch() public void SameInstructionWithComplexMatchingArgumentsShouldNotMatch() { var pattern = ExpressionPattern - .Instruction(1234) + .InstructionLiteral(1234) .WithArguments( - ExpressionPattern.Instruction(1234) | ExpressionPattern.Instruction(5678), + ExpressionPattern.InstructionLiteral(1234) | ExpressionPattern.InstructionLiteral(5678), ExpressionPattern.Any()); var input = Expression From ab4e6eb44c9bd5c7494ff92c3321b0414ad3613e Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 28 Dec 2023 00:34:27 +0100 Subject: [PATCH 14/33] Enable NRT in AST package. --- .../Echo.Ast/Analysis/FlowControlDeterminer.cs | 12 ++++++------ src/Core/Echo.Ast/AssignmentStatement.cs | 8 +++++--- src/Core/Echo.Ast/AstNodeWalker.cs | 14 ++++++-------- src/Core/Echo.Ast/Echo.Ast.csproj | 1 + src/Core/Echo.Ast/ExpressionStatement.cs | 4 ++-- src/Core/Echo.Ast/Patterns/CaptureGroup.cs | 6 +++--- src/Core/Echo.Ast/Patterns/Pattern.cs | 2 +- src/Core/Echo/Graphing/TreeNodeBase.cs | 17 +++++++++++++++++ 8 files changed, 41 insertions(+), 23 deletions(-) diff --git a/src/Core/Echo.Ast/Analysis/FlowControlDeterminer.cs b/src/Core/Echo.Ast/Analysis/FlowControlDeterminer.cs index 1b0558c4..7a4a7195 100644 --- a/src/Core/Echo.Ast/Analysis/FlowControlDeterminer.cs +++ b/src/Core/Echo.Ast/Analysis/FlowControlDeterminer.cs @@ -3,26 +3,26 @@ namespace Echo.Ast.Analysis { internal sealed class FlowControlDeterminer - : IAstNodeVisitor + : IAstNodeVisitor { private readonly IArchitecture _isa; internal FlowControlDeterminer(IArchitecture isa) => _isa = isa; - public InstructionFlowControl Visit(AssignmentStatement statement, object state) => + public InstructionFlowControl Visit(AssignmentStatement statement, object? state) => statement.Expression.Accept(this, state); - public InstructionFlowControl Visit(ExpressionStatement statement, object state) => + public InstructionFlowControl Visit(ExpressionStatement statement, object? state) => statement.Expression.Accept(this, state); - public InstructionFlowControl Visit(PhiStatement statement, object state) => + public InstructionFlowControl Visit(PhiStatement statement, object? state) => InstructionFlowControl.Fallthrough; - public InstructionFlowControl Visit(InstructionExpression expression, object state) => + public InstructionFlowControl Visit(InstructionExpression expression, object? state) => _isa.GetFlowControl(expression.Instruction); - public InstructionFlowControl Visit(VariableExpression expression, object state) => + public InstructionFlowControl Visit(VariableExpression expression, object? state) => InstructionFlowControl.Fallthrough; } } \ No newline at end of file diff --git a/src/Core/Echo.Ast/AssignmentStatement.cs b/src/Core/Echo.Ast/AssignmentStatement.cs index a1236783..f3a5918b 100644 --- a/src/Core/Echo.Ast/AssignmentStatement.cs +++ b/src/Core/Echo.Ast/AssignmentStatement.cs @@ -11,7 +11,7 @@ namespace Echo.Ast /// public sealed class AssignmentStatement : Statement { - private Expression _expression; + private Expression _expression = null!; /// /// Creates a new assignment statement @@ -19,7 +19,9 @@ public sealed class AssignmentStatement : Statement /// The variable /// The expression public AssignmentStatement(IVariable variable, Expression expression) - : this(new[] { variable }, expression) { } + : this(new[] {variable}, expression) + { + } /// /// Creates a new assignment statement @@ -47,7 +49,7 @@ public IList Variables public Expression Expression { get => _expression; - set => UpdateChild(ref _expression, value); + set => UpdateChildNotNull(ref _expression, value); } /// diff --git a/src/Core/Echo.Ast/AstNodeWalker.cs b/src/Core/Echo.Ast/AstNodeWalker.cs index 0c88ba25..0a835da6 100644 --- a/src/Core/Echo.Ast/AstNodeWalker.cs +++ b/src/Core/Echo.Ast/AstNodeWalker.cs @@ -4,7 +4,7 @@ namespace Echo.Ast /// Provides a base contract for Ast walkers /// /// The type of the instruction - public abstract class AstNodeWalker : IAstNodeVisitor + public abstract class AstNodeWalker : IAstNodeVisitor { /// /// Begin visiting a given @@ -71,8 +71,7 @@ private void VisitChildren(AstNode node) } /// - void IAstNodeVisitor.Visit(AssignmentStatement statement, - object state) + void IAstNodeVisitor.Visit(AssignmentStatement statement, object? state) { EnterAssignmentStatement(statement); @@ -82,8 +81,7 @@ void IAstNodeVisitor.Visit(AssignmentStatement - void IAstNodeVisitor.Visit(ExpressionStatement expression, - object state) + void IAstNodeVisitor.Visit(ExpressionStatement expression, object? state) { EnterExpressionStatement(expression); @@ -93,7 +91,7 @@ void IAstNodeVisitor.Visit(ExpressionStatement - void IAstNodeVisitor.Visit(PhiStatement statement, object state) + void IAstNodeVisitor.Visit(PhiStatement statement, object? state) { EnterPhiStatement(statement); @@ -104,7 +102,7 @@ void IAstNodeVisitor.Visit(PhiStatement stat } /// - void IAstNodeVisitor.Visit(InstructionExpression expression, object state) + void IAstNodeVisitor.Visit(InstructionExpression expression, object? state) { EnterInstructionExpression(expression); @@ -115,7 +113,7 @@ void IAstNodeVisitor.Visit(InstructionExpression - void IAstNodeVisitor.Visit(VariableExpression expression, object state) + void IAstNodeVisitor.Visit(VariableExpression expression, object? state) { VisitVariableExpression(expression); } diff --git a/src/Core/Echo.Ast/Echo.Ast.csproj b/src/Core/Echo.Ast/Echo.Ast.csproj index 2399edb5..044cb33d 100644 --- a/src/Core/Echo.Ast/Echo.Ast.csproj +++ b/src/Core/Echo.Ast/Echo.Ast.csproj @@ -2,6 +2,7 @@ netstandard2.0 + enable diff --git a/src/Core/Echo.Ast/ExpressionStatement.cs b/src/Core/Echo.Ast/ExpressionStatement.cs index f0ebf2b5..0589a49f 100644 --- a/src/Core/Echo.Ast/ExpressionStatement.cs +++ b/src/Core/Echo.Ast/ExpressionStatement.cs @@ -9,7 +9,7 @@ namespace Echo.Ast /// public sealed class ExpressionStatement : Statement { - private Expression _expression; + private Expression _expression = null!; /// /// Creates a new expression statement @@ -27,7 +27,7 @@ public ExpressionStatement(Expression expression) public Expression Expression { get => _expression; - set => UpdateChild(ref _expression, value); + set => UpdateChildNotNull(ref _expression, value); } /// diff --git a/src/Core/Echo.Ast/Patterns/CaptureGroup.cs b/src/Core/Echo.Ast/Patterns/CaptureGroup.cs index 2cc7fa28..1c4faec6 100644 --- a/src/Core/Echo.Ast/Patterns/CaptureGroup.cs +++ b/src/Core/Echo.Ast/Patterns/CaptureGroup.cs @@ -5,7 +5,7 @@ namespace Echo.Ast.Patterns /// public abstract class CaptureGroup { - internal CaptureGroup(string name) + internal CaptureGroup(string? name) { Name = name; } @@ -13,7 +13,7 @@ internal CaptureGroup(string name) /// /// Gets the name of the capture group. /// - public string Name + public string? Name { get; } @@ -37,7 +37,7 @@ public CaptureGroup() /// Creates a new named capture group. /// /// The name of the capture. - public CaptureGroup(string name) + public CaptureGroup(string? name) : base(name) { } diff --git a/src/Core/Echo.Ast/Patterns/Pattern.cs b/src/Core/Echo.Ast/Patterns/Pattern.cs index 752d54c8..d19923a0 100644 --- a/src/Core/Echo.Ast/Patterns/Pattern.cs +++ b/src/Core/Echo.Ast/Patterns/Pattern.cs @@ -63,7 +63,7 @@ public abstract class Pattern /// /// Gets or sets the capture group this pattern was assigned to. /// - public CaptureGroup CaptureGroup + public CaptureGroup? CaptureGroup { get; set; diff --git a/src/Core/Echo/Graphing/TreeNodeBase.cs b/src/Core/Echo/Graphing/TreeNodeBase.cs index a08a4c44..a59e6034 100644 --- a/src/Core/Echo/Graphing/TreeNodeBase.cs +++ b/src/Core/Echo/Graphing/TreeNodeBase.cs @@ -68,6 +68,23 @@ public IEnumerable GetPredecessors() /// public bool HasSuccessor(INode node) => GetChildren().Contains(node); + /// + /// Updates the value and the parent of the node, ensuring that the new value is not + /// null. + /// + /// The child element to update. + /// The new value to assign to the . + /// When the new node is null. + /// When the node already has a parent. + protected void UpdateChildNotNull(ref T child, T value) + where T : TreeNodeBase + { + if (value is null) + throw new ArgumentNullException(nameof(value)); + + UpdateChild(ref child!, value); + } + /// /// Updates the value and the parent of the node. /// From acb236468651aff01e5b4d37c365985fdc6918f4 Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 28 Dec 2023 00:34:54 +0100 Subject: [PATCH 15/33] Change default purity of CIL field accesses to false. --- .../Echo.Platforms.AsmResolver/CilPurityClassifier.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Platforms/Echo.Platforms.AsmResolver/CilPurityClassifier.cs b/src/Platforms/Echo.Platforms.AsmResolver/CilPurityClassifier.cs index d20155a9..d4361fee 100644 --- a/src/Platforms/Echo.Platforms.AsmResolver/CilPurityClassifier.cs +++ b/src/Platforms/Echo.Platforms.AsmResolver/CilPurityClassifier.cs @@ -57,7 +57,7 @@ public Trilean DefaultFieldAccessPurity { get; set; - } = true; + } = false; /// /// Gets or sets a value indicating whether writes to field should be considered pure or not by default. @@ -76,7 +76,7 @@ public Trilean DefaultMethodAccessPurity { get; set; - } = true; + } = false; /// /// Gets or sets a value indicating whether method calls should be considered pure or not by default. From 32cc13cc5ccb5befbbd4e2b0561a2014704798af Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 28 Dec 2023 14:21:26 +0100 Subject: [PATCH 16/33] Inline input stack variables if PHI has only one source. --- src/Core/Echo.Ast/Construction/AstBuilder.cs | 26 +++++++++---- src/Core/Echo.Ast/Construction/LiftedNode.cs | 6 +++ src/Core/Echo.Ast/VariableExpression.cs | 2 +- .../Serialization/Dot/DotEntityStyle.cs | 6 +-- .../Graphing/Serialization/Dot/DotWriter.cs | 5 +-- .../Construction/AstBuilderTest.cs | 38 +++++++------------ 6 files changed, 43 insertions(+), 40 deletions(-) diff --git a/src/Core/Echo.Ast/Construction/AstBuilder.cs b/src/Core/Echo.Ast/Construction/AstBuilder.cs index c4acaea9..78e82f23 100644 --- a/src/Core/Echo.Ast/Construction/AstBuilder.cs +++ b/src/Core/Echo.Ast/Construction/AstBuilder.cs @@ -173,9 +173,17 @@ private void LiftInstruction( private static Expression Pop(LiftedNode node, Stack> stack) { - return stack.Count == 0 - ? node.DeclareStackInput().ToExpression() - : stack.Pop(); + if (stack.Count == 0) + { + var variable = node.DeclareStackInput(); + + var expression = variable.ToExpression(); + node.StackInputReferences.Add(variable, expression); + + return expression; + } + else + return stack.Pop(); } private static void FlushStackAsOutput(LiftedNode node, Stack> stack) @@ -323,13 +331,15 @@ private void InsertPhiStatements() var input = block.StackInputs[i]; if (input.Sources.Count == 1) { - // Optimization: if there is one source only for the phi node, pre-emptively remove the - // phi node and replace it with a normal assignment. + // Optimization: if there is one source only for the phi node, we can inline the input stack + // variable. Since an input stack slot is only consumed once, it thus only has one variable + // expression. Therefore, inlining is exactly one update of a variable expression. + var singleSource = input.Sources[0]; input.Sources.RemoveAt(0); - - var simplified = Statement.Assignment(input.Representative, singleSource); - block.Transformed.Contents.Instructions.Insert(0, simplified); + + if (block.StackInputReferences.TryGetValue(input.Representative, out var expression)) + expression.Variable = singleSource.Variable; } else { diff --git a/src/Core/Echo.Ast/Construction/LiftedNode.cs b/src/Core/Echo.Ast/Construction/LiftedNode.cs index f3889119..49c1b37c 100644 --- a/src/Core/Echo.Ast/Construction/LiftedNode.cs +++ b/src/Core/Echo.Ast/Construction/LiftedNode.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Echo.Code; using Echo.ControlFlow; namespace Echo.Ast.Construction; @@ -46,6 +47,11 @@ public LiftedNode(ControlFlowNode original) /// public List StackOutputs { get; } = new(); + /// + /// Gets a mapping from stack input variables to the expression the stack value is used. + /// + public Dictionary> StackInputReferences { get; } = new(); + /// /// Defines a new synthetic stack input. /// diff --git a/src/Core/Echo.Ast/VariableExpression.cs b/src/Core/Echo.Ast/VariableExpression.cs index d9e3545b..f97d401f 100644 --- a/src/Core/Echo.Ast/VariableExpression.cs +++ b/src/Core/Echo.Ast/VariableExpression.cs @@ -26,7 +26,7 @@ public VariableExpression(IVariable variable) public IVariable Variable { get; - private set; + internal set; } /// diff --git a/src/Core/Echo/Graphing/Serialization/Dot/DotEntityStyle.cs b/src/Core/Echo/Graphing/Serialization/Dot/DotEntityStyle.cs index a5fc21b0..93db44d4 100644 --- a/src/Core/Echo/Graphing/Serialization/Dot/DotEntityStyle.cs +++ b/src/Core/Echo/Graphing/Serialization/Dot/DotEntityStyle.cs @@ -10,7 +10,7 @@ public readonly struct DotEntityStyle /// /// The color of the entity. /// The line drawing style of the entity. - public DotEntityStyle(string color, string style) + public DotEntityStyle(string? color, string? style) { Color = color; Style = style; @@ -19,7 +19,7 @@ public DotEntityStyle(string color, string style) /// /// Gets the color of the entity. /// - public string Color + public string? Color { get; } @@ -27,7 +27,7 @@ public string Color /// /// Gets the line drawing style of the entity. /// - public string Style + public string? Style { get; } diff --git a/src/Core/Echo/Graphing/Serialization/Dot/DotWriter.cs b/src/Core/Echo/Graphing/Serialization/Dot/DotWriter.cs index ce2f47eb..fc930de1 100644 --- a/src/Core/Echo/Graphing/Serialization/Dot/DotWriter.cs +++ b/src/Core/Echo/Graphing/Serialization/Dot/DotWriter.cs @@ -146,7 +146,7 @@ private void WriteSubGraph(ISubGraph subGraph, HashSet scope) Writer.Indent++; var attributes = SubGraphAdorner.GetSubGraphAttributes(subGraph); - if (attributes?.Count > 0) + if (attributes.Count > 0) { string delimiter = IncludeSemicolons ? ";" @@ -234,9 +234,6 @@ protected virtual void WriteEdge(IEdge edge) private void WriteEntityAttributes(IEnumerable> attributes) { - if (attributes == null) - return; - var array = attributes as KeyValuePair[] ?? attributes.ToArray(); if (array.Length > 0) { diff --git a/test/Core/Echo.Ast.Tests/Construction/AstBuilderTest.cs b/test/Core/Echo.Ast.Tests/Construction/AstBuilderTest.cs index 2bd725f5..2e830993 100644 --- a/test/Core/Echo.Ast.Tests/Construction/AstBuilderTest.cs +++ b/test/Core/Echo.Ast.Tests/Construction/AstBuilderTest.cs @@ -379,33 +379,24 @@ public void TwoNodesWithStackDeltas() var match1 = StatementPattern .Assignment( Pattern.Any().CaptureAs(variable), - ExpressionPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Push)) + ExpressionPattern.Instruction(new DummyInstructionPattern(DummyOpCode.Push)) ) .Match(n1.Contents.Instructions[0]); Assert.True(match1.IsSuccess); - - // in = out - var match2 = StatementPattern - .Assignment( - Pattern.Any().CaptureAs(variable), - ExpressionPattern.Variable(Pattern.Any().CaptureAs(variable)) - ) - .Match(n2.Contents.Instructions[0]); - Assert.True(match2.IsSuccess); - // pop(in) - var match3 = StatementPattern + // pop(out) + var match2 = StatementPattern .Expression(ExpressionPattern - .Instruction(new DummyInstructionPattern(DummyOpCode.Pop)) + .Instruction(new DummyInstructionPattern(DummyOpCode.Pop)) .WithArguments( ExpressionPattern.Variable(Pattern.Any().CaptureAs(variable)) ) ) - .Match(n2.Contents.Instructions[1]); - Assert.True(match3.IsSuccess); + .Match(n2.Contents.Instructions[0]); + Assert.True(match2.IsSuccess); - Assert.Same(match1.GetCaptures(variable)[0], match2.GetCaptures(variable)[1]); - Assert.Same(match2.GetCaptures(variable)[0], match3.GetCaptures(variable)[0]); + Assert.Same(match1.GetCaptures(variable)[0], match2.GetCaptures(variable)[0]); + Assert.Same(match2.GetCaptures(variable)[0], match2.GetCaptures(variable)[0]); Assert.Same(n2, n1.UnconditionalNeighbour); } @@ -436,9 +427,11 @@ public void TwoNodesWithIndirectStackDeltaShouldInline() .Match(cfg.Nodes[0].Contents.Instructions); var match2 = StatementPattern - .Assignment( - Pattern.Any(), - ExpressionPattern.Variable(Pattern.Any().CaptureAs(variable)) + .Expression(ExpressionPattern + .Instruction(new DummyInstructionPattern(DummyOpCode.Pop)) + .WithArguments( + ExpressionPattern.Variable(Pattern.Any().CaptureAs(variable)) + ) ) .Match(cfg.Nodes[10].Contents.Instructions[0]); @@ -460,9 +453,6 @@ public void TwoNodesPushBeforeImpure() DummyInstruction.Ret(11) }); - using var fs = File.CreateText("/tmp/output.dot"); - cfg.ToDotGraph(fs); - var block = cfg.Nodes[0].Contents; Assert.IsAssignableFrom>(block.Instructions[0]); Assert.IsAssignableFrom>(block.Instructions[1]); @@ -729,7 +719,7 @@ public void ConditionalReplaceStackSlotNested() var match3 = StatementPattern.Phi() .WithSources(2) .CaptureSources(sourcesCapture) - .Match(cfg.Nodes[6].Contents.Instructions[1]); + .Match(cfg.Nodes[6].Contents.Instructions[0]); Assert.True(match1.IsSuccess); Assert.True(match3.IsSuccess); From 901c9ffbf9ad91c015349ea9930bec1ce92848fe Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 28 Dec 2023 14:24:16 +0100 Subject: [PATCH 17/33] Lazy init the synthetic variable collections. --- src/Core/Echo.Ast/Construction/LiftedNode.cs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/Core/Echo.Ast/Construction/LiftedNode.cs b/src/Core/Echo.Ast/Construction/LiftedNode.cs index 49c1b37c..d9774e5f 100644 --- a/src/Core/Echo.Ast/Construction/LiftedNode.cs +++ b/src/Core/Echo.Ast/Construction/LiftedNode.cs @@ -10,6 +10,11 @@ namespace Echo.Ast.Construction; /// internal sealed class LiftedNode { + private List>? _stackInputs; + private Dictionary>? _stackInputRefs; + private List? _stackIntermediates; + private List? _stackOutputs; + /// /// Creates a new lifted node based on the provided base node. /// @@ -34,23 +39,23 @@ public LiftedNode(ControlFlowNode original) /// Gets an ordered list of stack input variables (and their data sources) defined by this lifted block. /// Variables are in push-order, that is, the last variable in this list represents the top-most stack value. /// - public List> StackInputs { get; } = new(); + public List> StackInputs => _stackInputs ??= new(); /// /// Gets a collection of synthetic intermediate stack variables defined by this lifted block. /// - public List StackIntermediates { get; } = new(); + public List StackIntermediates => _stackIntermediates ??= new(); /// /// Gets an ordered list of synthetic output stack variables this lifted block produces. /// Variables are in push-order, that is, the last variable in this list represents the top-most stack value. /// - public List StackOutputs { get; } = new(); + public List StackOutputs => _stackOutputs ??= new(); /// /// Gets a mapping from stack input variables to the expression the stack value is used. /// - public Dictionary> StackInputReferences { get; } = new(); + public Dictionary> StackInputReferences => _stackInputRefs ??= new(); /// /// Defines a new synthetic stack input. From 36b8072076537db1005e2be590c7721669f5ae82 Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 28 Dec 2023 15:15:02 +0100 Subject: [PATCH 18/33] Extract AstFormatter and instruction formatters. --- src/Core/Echo.Ast/AssignmentStatement.cs | 6 - src/Core/Echo.Ast/AstFormatter.cs | 119 ++++++++++++++++++ src/Core/Echo.Ast/AstNode.cs | 15 ++- src/Core/Echo.Ast/ExpressionStatement.cs | 6 - src/Core/Echo.Ast/InstructionExpression.cs | 6 - src/Core/Echo.Ast/PhiStatement.cs | 5 - src/Core/Echo.Ast/StatementFormatter.cs | 25 ---- src/Core/Echo.Ast/VariableExpression.cs | 5 - src/Core/Echo.ControlFlow/ControlFlowGraph.cs | 12 +- .../Regions/IControlFlowRegion.cs | 4 + .../Dot/DefaultInstructionFormatter.cs | 6 +- 11 files changed, 151 insertions(+), 58 deletions(-) create mode 100644 src/Core/Echo.Ast/AstFormatter.cs delete mode 100644 src/Core/Echo.Ast/StatementFormatter.cs diff --git a/src/Core/Echo.Ast/AssignmentStatement.cs b/src/Core/Echo.Ast/AssignmentStatement.cs index f3a5918b..7c6d5a51 100644 --- a/src/Core/Echo.Ast/AssignmentStatement.cs +++ b/src/Core/Echo.Ast/AssignmentStatement.cs @@ -99,11 +99,5 @@ public AssignmentStatement WithExpression(Expression Expression = expression; return this; } - - /// - public override string ToString() => $"{string.Join(", ", Variables.Select(v => v.Name))} = {Expression}"; - - internal override string Format(IInstructionFormatter instructionFormatter) => - $"{string.Join(", ", Variables.Select(v => v.Name))} = {Expression.Format(instructionFormatter)}"; } } \ No newline at end of file diff --git a/src/Core/Echo.Ast/AstFormatter.cs b/src/Core/Echo.Ast/AstFormatter.cs new file mode 100644 index 00000000..ceec54dd --- /dev/null +++ b/src/Core/Echo.Ast/AstFormatter.cs @@ -0,0 +1,119 @@ +using System.Text; +using Echo.ControlFlow.Serialization.Dot; + +namespace Echo.Ast; + +/// +/// Provides extension methods for stringifying AST nodes. +/// +public static class AstFormatter +{ + /// + /// Wraps an instruction formatter into a new AST formatter. + /// + /// The instruction formatter. + /// The type of instructions stored in the AST. + /// The constructed formatter. + public static AstFormatter ToAstFormatter(this IInstructionFormatter self) + { + return new AstFormatter(self); + } +} + +/// +/// Provides a mechanism for stringifying AST nodes. +/// +/// The type of instructions stored in the AST. +public class AstFormatter : IAstNodeVisitor, IInstructionFormatter> +{ + /// + /// Gets the default instance of the formatter using the default formatter for . + /// + public static AstFormatter Default { get; } = new(); + + /// + /// Creates a new AST formatter using the default instruction formatter. + /// + public AstFormatter() + : this(DefaultInstructionFormatter.Instance) + { + } + + /// + /// Creates a new AST formatter using the provided instruction formatter. + /// + public AstFormatter(IInstructionFormatter instructionFormatter) + { + InstructionFormatter = instructionFormatter; + } + + /// + /// Gets the instruction formatter used for formatting instances. + /// + public IInstructionFormatter InstructionFormatter { get; } + + /// + public string Format(in Statement instruction) + { + var builder = new StringBuilder(); + instruction.Accept(this, builder); + return builder.ToString(); + } + + /// + public void Visit(AssignmentStatement statement, StringBuilder state) + { + for (int i = 0; i < statement.Variables.Count; i++) + { + if (i > 0) + state.Append(", "); + + state.Append(statement.Variables[i].Name); + } + + state.Append(" = "); + statement.Expression.Accept(this, state); + } + + /// + public void Visit(ExpressionStatement expression, StringBuilder state) + { + expression.Expression.Accept(this, state); + state.Append(';'); + } + + /// + public void Visit(PhiStatement statement, StringBuilder state) + { + state.Append(statement.Representative.Name); + state.Append(" = φ("); + for (int i = 0; i < statement.Sources.Count; i++) + { + if (i > 0) + state.Append(", "); + statement.Sources[i].Accept(this, state); + } + state.Append(");"); + } + + /// + public void Visit(InstructionExpression expression, StringBuilder state) + { + state.Append(InstructionFormatter.Format(expression.Instruction)); + state.Append("("); + for (int i = 0; i < expression.Arguments.Count; i++) + { + if (i > 0) + state.Append(", "); + expression.Arguments[i].Accept(this, state); + } + state.Append(")"); + } + + /// + public void Visit(VariableExpression expression, StringBuilder state) + { + state.Append(expression.Variable.Name); + } + +} \ No newline at end of file diff --git a/src/Core/Echo.Ast/AstNode.cs b/src/Core/Echo.Ast/AstNode.cs index 639c379f..08da070f 100644 --- a/src/Core/Echo.Ast/AstNode.cs +++ b/src/Core/Echo.Ast/AstNode.cs @@ -1,3 +1,4 @@ +using System.Text; using Echo.Code; using Echo.ControlFlow.Serialization.Dot; using Echo.Graphing; @@ -9,6 +10,11 @@ namespace Echo.Ast /// public abstract class AstNode : TreeNodeBase { + /// + /// Gets the parent of the AST node. + /// + public new AstNode? Parent => base.Parent as AstNode; + /// /// Gets or sets the original address range this AST node mapped to in the raw disassembly of the code /// (if available). @@ -42,6 +48,13 @@ public Trilean IsPure(IPurityClassifier classifier) /// public abstract TOut Accept(IAstNodeVisitor visitor, TState state); - internal abstract string Format(IInstructionFormatter instructionFormatter); + /// + public override string ToString() + { + var formatter = new AstFormatter(); + var builder = new StringBuilder(); + Accept(formatter, builder); + return builder.ToString(); + } } } \ No newline at end of file diff --git a/src/Core/Echo.Ast/ExpressionStatement.cs b/src/Core/Echo.Ast/ExpressionStatement.cs index 0589a49f..99335aa4 100644 --- a/src/Core/Echo.Ast/ExpressionStatement.cs +++ b/src/Core/Echo.Ast/ExpressionStatement.cs @@ -54,11 +54,5 @@ public ExpressionStatement WithExpression(Expression Expression = expression; return this; } - - /// - public override string ToString() => $"{Expression}"; - - internal override string Format(IInstructionFormatter instructionFormatter) - => $"{Expression.Format(instructionFormatter)}"; } } \ No newline at end of file diff --git a/src/Core/Echo.Ast/InstructionExpression.cs b/src/Core/Echo.Ast/InstructionExpression.cs index c1e344fe..7ed3b23f 100644 --- a/src/Core/Echo.Ast/InstructionExpression.cs +++ b/src/Core/Echo.Ast/InstructionExpression.cs @@ -104,11 +104,5 @@ public InstructionExpression WithArguments(IEnumerable - public override string ToString() => $"{Instruction}({string.Join(", ", Arguments)})"; - - internal override string Format(IInstructionFormatter instructionFormatter) => - $"{instructionFormatter.Format(Instruction)}({string.Join(", ", Arguments)})"; } } \ No newline at end of file diff --git a/src/Core/Echo.Ast/PhiStatement.cs b/src/Core/Echo.Ast/PhiStatement.cs index 8e404fb0..c5f2b439 100644 --- a/src/Core/Echo.Ast/PhiStatement.cs +++ b/src/Core/Echo.Ast/PhiStatement.cs @@ -95,10 +95,5 @@ public PhiStatement WithSources(IEnumerable - public override string ToString() => $"{Representative.Name} = φ({string.Join(", ", Sources)})"; - - internal override string Format(IInstructionFormatter instructionFormatter) => ToString(); } } \ No newline at end of file diff --git a/src/Core/Echo.Ast/StatementFormatter.cs b/src/Core/Echo.Ast/StatementFormatter.cs deleted file mode 100644 index ac0bedf5..00000000 --- a/src/Core/Echo.Ast/StatementFormatter.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Echo.ControlFlow.Serialization.Dot; - -namespace Echo.Ast -{ - /// - /// Formats the Ast so it looks nicer in - /// - public class StatementFormatter : IInstructionFormatter> - { - private readonly IInstructionFormatter _instructionFormatter; - - /// - /// Creates a new instance of with the specified - /// - /// - /// The used to - /// format s with - public StatementFormatter(IInstructionFormatter instructionFormatter) => - _instructionFormatter = instructionFormatter; - - /// - public string Format(in Statement instruction) => - instruction.Format(_instructionFormatter); - } -} \ No newline at end of file diff --git a/src/Core/Echo.Ast/VariableExpression.cs b/src/Core/Echo.Ast/VariableExpression.cs index f97d401f..7524cf4a 100644 --- a/src/Core/Echo.Ast/VariableExpression.cs +++ b/src/Core/Echo.Ast/VariableExpression.cs @@ -50,10 +50,5 @@ public VariableExpression WithVariable(IVariable variable) Variable = variable; return this; } - - /// - public override string ToString() => $"{Variable.Name}"; - - internal override string Format(IInstructionFormatter instructionFormatter) => ToString(); } } \ No newline at end of file diff --git a/src/Core/Echo.ControlFlow/ControlFlowGraph.cs b/src/Core/Echo.ControlFlow/ControlFlowGraph.cs index a4fbe086..32bfd517 100644 --- a/src/Core/Echo.ControlFlow/ControlFlowGraph.cs +++ b/src/Core/Echo.ControlFlow/ControlFlowGraph.cs @@ -117,11 +117,19 @@ IEnumerable> IControlFlowRegion.GetS /// /// The output stream. /// To customize the layout of the final graph, use the class. - public void ToDotGraph(TextWriter writer) + public void ToDotGraph(TextWriter writer) => ToDotGraph(writer, DefaultInstructionFormatter.Instance); + + /// + /// Serializes the control flow graph to the provided output stream, in graphviz dot format. + /// + /// The instruction formatter. + /// The output stream. + /// To customize the layout of the final graph, use the class. + public void ToDotGraph(TextWriter writer, IInstructionFormatter formatter) { var dotWriter = new DotWriter(writer) { - NodeAdorner = new ControlFlowNodeAdorner(), + NodeAdorner = new ControlFlowNodeAdorner(formatter), EdgeAdorner = new ControlFlowEdgeAdorner(), SubGraphAdorner = new ExceptionHandlerAdorner(), }; diff --git a/src/Core/Echo.ControlFlow/Regions/IControlFlowRegion.cs b/src/Core/Echo.ControlFlow/Regions/IControlFlowRegion.cs index 468e742b..0ae0d33b 100644 --- a/src/Core/Echo.ControlFlow/Regions/IControlFlowRegion.cs +++ b/src/Core/Echo.ControlFlow/Regions/IControlFlowRegion.cs @@ -69,6 +69,10 @@ IControlFlowRegion ParentRegion IEnumerable> GetSuccessors(); } + /// + /// Represents a scope of regions. + /// + /// The type of data that each node in the graph stores. public interface IScopeControlFlowRegion : IControlFlowRegion { /// diff --git a/src/Core/Echo.ControlFlow/Serialization/Dot/DefaultInstructionFormatter.cs b/src/Core/Echo.ControlFlow/Serialization/Dot/DefaultInstructionFormatter.cs index adec7680..6585f760 100644 --- a/src/Core/Echo.ControlFlow/Serialization/Dot/DefaultInstructionFormatter.cs +++ b/src/Core/Echo.ControlFlow/Serialization/Dot/DefaultInstructionFormatter.cs @@ -6,8 +6,10 @@ /// The type of the instruction to create a formatter of. public sealed class DefaultInstructionFormatter : IInstructionFormatter { - internal static readonly DefaultInstructionFormatter Instance = - new DefaultInstructionFormatter(); + /// + /// Gets a singleton instance of the class. + /// + public static DefaultInstructionFormatter Instance { get; } = new(); /// public string Format(in TInstruction instruction) => instruction.ToString(); From 1dc6f8740cc3cc72f9fadc0648e19029f5080e3f Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 28 Dec 2023 15:15:08 +0100 Subject: [PATCH 19/33] Update README.md --- README.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index bb378bf0..c271c1c8 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,8 @@ Main Features - Data flow analysis - Create data flow graphs - Inspect stack and variable dependencies of instructions. +- AST building + - Lift control flow graphs to Abstract Syntax Trees (ASTs). - Unified generic API. - Serialize any kind of graph to the dot file format. - Adding a new platform for flow analysis requires minimal effort @@ -21,12 +23,12 @@ Main Features Supported platforms: -| Architecture | Back-end | Control Flow | Data Flow | Purity Classification | Emulation | -|--------------|---------------------------------------------------------|--------------|-----------|-----------------------|-----------| -| CIL | [AsmResolver](https://github.com/Washi1337/AsmResolver) | ✓ | ✓ | ✓ | ✓ (WIP) | -| CIL | [dnlib](https://github.com/0xd4d/dnlib) | ✓ | ✓ | ✓ | | -| x86 (32-bit) | [Iced](https://github.com/icedland/iced) | ✓ | ✓ | | | -| x86 (64-bit) | [Iced](https://github.com/icedland/iced) | ✓ | ✓ | | | +| Architecture | Back-end | Control Flow | Data Flow | AST | Purity Classification | Emulation | +|--------------|---------------------------------------------------------|--------------|-----------|-----|-----------------------|-----------| +| CIL | [AsmResolver](https://github.com/Washi1337/AsmResolver) | ✓ | ✓ | ✓ | ✓ | ✓ (WIP) | +| CIL | [dnlib](https://github.com/0xd4d/dnlib) | ✓ | ✓ | ✓ | ✓ | | +| x86 (32-bit) | [Iced](https://github.com/icedland/iced) | ✓ | ✓ | ✓ | | | +| x86 (64-bit) | [Iced](https://github.com/icedland/iced) | ✓ | ✓ | ✓ | | | Compiling @@ -54,5 +56,4 @@ See [CONTRIBUTING.md](CONTRIBUTING.md). Found a bug or have questions? ------------------------------ -Please use the [issue tracker](https://github.com/Washi1337/Echo/issues). Try to be as descriptive as possible. - +Please use the [issue tracker](https://github.com/Washi1337/Echo/issues). Try to be as descriptive as possible. \ No newline at end of file From 7ed767fff8a0f895b6f45174128a0078b1bcabda Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 4 Jan 2024 21:40:44 +0100 Subject: [PATCH 20/33] Fix AstNodeWalker, add parameterless version of IAstNodeVisitor. --- .../Echo.Ast/Analysis/ReadVariableFinder.cs | 16 +++ .../Analysis/ReadVariableFinderWalker.cs | 18 --- .../Analysis/WrittenVariableFinder.cs | 23 ++++ .../Analysis/WrittenVariableFinderWalker.cs | 24 ---- src/Core/Echo.Ast/AssignmentStatement.cs | 15 ++- src/Core/Echo.Ast/AstArchitecture.cs | 36 +++-- src/Core/Echo.Ast/AstNode.cs | 10 +- src/Core/Echo.Ast/AstNodeListener.cs | 39 ++++++ src/Core/Echo.Ast/AstNodeWalker.cs | 127 ++++++------------ src/Core/Echo.Ast/ExpressionStatement.cs | 5 +- src/Core/Echo.Ast/IAstNodeListener.cs | 71 ++++++++++ src/Core/Echo.Ast/IAstNodeVisitor.cs | 32 +++++ src/Core/Echo.Ast/InstructionExpression.cs | 5 +- src/Core/Echo.Ast/PhiStatement.cs | 5 +- src/Core/Echo.Ast/VariableExpression.cs | 5 +- .../Code/DummyFormatter.cs | 19 +++ 16 files changed, 291 insertions(+), 159 deletions(-) create mode 100644 src/Core/Echo.Ast/Analysis/ReadVariableFinder.cs delete mode 100644 src/Core/Echo.Ast/Analysis/ReadVariableFinderWalker.cs create mode 100644 src/Core/Echo.Ast/Analysis/WrittenVariableFinder.cs delete mode 100644 src/Core/Echo.Ast/Analysis/WrittenVariableFinderWalker.cs create mode 100644 src/Core/Echo.Ast/AstNodeListener.cs create mode 100644 src/Core/Echo.Ast/IAstNodeListener.cs create mode 100644 test/Platforms/Echo.Platforms.DummyPlatform/Code/DummyFormatter.cs diff --git a/src/Core/Echo.Ast/Analysis/ReadVariableFinder.cs b/src/Core/Echo.Ast/Analysis/ReadVariableFinder.cs new file mode 100644 index 00000000..522e162c --- /dev/null +++ b/src/Core/Echo.Ast/Analysis/ReadVariableFinder.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using Echo.Code; + +namespace Echo.Ast.Analysis +{ + internal sealed class ReadVariableFinder : AstNodeListener + { + internal HashSet Variables { get; } = new(); + + public override void ExitVariableExpression(VariableExpression expression) + { + base.ExitVariableExpression(expression); + Variables.Add(expression.Variable); + } + } +} \ No newline at end of file diff --git a/src/Core/Echo.Ast/Analysis/ReadVariableFinderWalker.cs b/src/Core/Echo.Ast/Analysis/ReadVariableFinderWalker.cs deleted file mode 100644 index 19f2e551..00000000 --- a/src/Core/Echo.Ast/Analysis/ReadVariableFinderWalker.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System.Collections.Generic; -using Echo.Code; - -namespace Echo.Ast.Analysis -{ - internal sealed class ReadVariableFinderWalker : AstNodeWalker - { - internal int Count => Variables.Count; - - internal HashSet Variables - { - get; - } = new HashSet(); - - protected override void VisitVariableExpression(VariableExpression variableExpression) => - Variables.Add(variableExpression.Variable); - } -} \ No newline at end of file diff --git a/src/Core/Echo.Ast/Analysis/WrittenVariableFinder.cs b/src/Core/Echo.Ast/Analysis/WrittenVariableFinder.cs new file mode 100644 index 00000000..da2033c8 --- /dev/null +++ b/src/Core/Echo.Ast/Analysis/WrittenVariableFinder.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using Echo.Code; + +namespace Echo.Ast.Analysis +{ + internal sealed class WrittenVariableFinder : AstNodeListener + { + internal HashSet Variables { get; } = new(); + + public override void ExitAssignmentStatement(AssignmentStatement statement) + { + base.ExitAssignmentStatement(statement); + for (int i = 0; i < statement.Variables.Count; i++) + Variables.Add(statement.Variables[i]); + } + + public override void ExitPhiStatement(PhiStatement phiStatement) + { + base.ExitPhiStatement(phiStatement); + Variables.Add(phiStatement.Representative); + } + } +} \ No newline at end of file diff --git a/src/Core/Echo.Ast/Analysis/WrittenVariableFinderWalker.cs b/src/Core/Echo.Ast/Analysis/WrittenVariableFinderWalker.cs deleted file mode 100644 index ae105b98..00000000 --- a/src/Core/Echo.Ast/Analysis/WrittenVariableFinderWalker.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System.Collections.Generic; -using Echo.Code; - -namespace Echo.Ast.Analysis -{ - internal sealed class WrittenVariableFinderWalker : AstNodeWalker - { - internal int Count => Variables.Count; - - internal HashSet Variables - { - get; - } = new HashSet(); - - protected override void ExitAssignmentStatement(AssignmentStatement assignmentStatement) - { - foreach (var target in assignmentStatement.Variables) - Variables.Add(target); - } - - protected override void ExitPhiStatement(PhiStatement phiStatement) => - Variables.Add(phiStatement.Representative); - } -} \ No newline at end of file diff --git a/src/Core/Echo.Ast/AssignmentStatement.cs b/src/Core/Echo.Ast/AssignmentStatement.cs index 7c6d5a51..fa51d3a8 100644 --- a/src/Core/Echo.Ast/AssignmentStatement.cs +++ b/src/Core/Echo.Ast/AssignmentStatement.cs @@ -1,13 +1,12 @@ using System.Collections.Generic; using System.Linq; -using Echo.ControlFlow.Serialization.Dot; using Echo.Code; using Echo.Graphing; namespace Echo.Ast { /// - /// Represents an assignment in the AST + /// Represents a statement that assigns a value to a (set of) variable(s). /// public sealed class AssignmentStatement : Statement { @@ -59,12 +58,16 @@ public override IEnumerable GetChildren() } /// - public override void Accept(IAstNodeVisitor visitor, TState state) => - visitor.Visit(this, state); + public override void Accept(IAstNodeVisitor visitor) + => visitor.Visit(this); /// - public override TOut Accept(IAstNodeVisitor visitor, TState state) => - visitor.Visit(this, state); + public override void Accept(IAstNodeVisitor visitor, TState state) + => visitor.Visit(this, state); + + /// + public override TOut Accept(IAstNodeVisitor visitor, TState state) + => visitor.Visit(this, state); /// /// Modifies the current to assign to diff --git a/src/Core/Echo.Ast/AstArchitecture.cs b/src/Core/Echo.Ast/AstArchitecture.cs index 8dbca6a4..34ca4f6f 100644 --- a/src/Core/Echo.Ast/AstArchitecture.cs +++ b/src/Core/Echo.Ast/AstArchitecture.cs @@ -5,16 +5,16 @@ namespace Echo.Ast { /// - /// Provides a decorator around for the AST + /// Describes an architecture that is lifted from a stack-based platform to an expression-based platform. /// - /// The instruction + /// The instructions defined by the satck-based platform. public class AstArchitecture : IArchitecture> { private readonly FlowControlDeterminer _flowControlDeterminer; /// - /// Create a new decorator around the + /// Wraps the provided stack-based architecture to the lifted expression-based architecture. /// /// The to decorate public AstArchitecture(IArchitecture baseArchitecture) @@ -43,45 +43,43 @@ public InstructionFlowControl GetFlowControl(in Statement instruct /// public int GetReadVariablesCount(in Statement instruction) { - var visitor = new ReadVariableFinderWalker(); - instruction.Accept(visitor, null); - - return visitor.Count; + var finder = new ReadVariableFinder(); + AstNodeWalker.Walk(finder, instruction); + return finder.Variables.Count; } /// public int GetReadVariables(in Statement instruction, Span variablesBuffer) { - var visitor = new ReadVariableFinderWalker(); - instruction.Accept(visitor, null); + var finder = new ReadVariableFinder(); + AstNodeWalker.Walk(finder, instruction); int i = 0; - foreach (var variable in visitor.Variables) + foreach (var variable in finder.Variables) variablesBuffer[i++] = variable; - return visitor.Count; + return finder.Variables.Count; } /// public int GetWrittenVariablesCount(in Statement instruction) { - var visitor = new WrittenVariableFinderWalker(); - instruction.Accept(visitor, null); - - return visitor.Count; + var finder = new WrittenVariableFinder(); + AstNodeWalker.Walk(finder, instruction); + return finder.Variables.Count; } /// public int GetWrittenVariables(in Statement instruction, Span variablesBuffer) { - var visitor = new WrittenVariableFinderWalker(); - instruction.Accept(visitor, null); + var finder = new WrittenVariableFinder(); + AstNodeWalker.Walk(finder, instruction); int i = 0; - foreach (var variable in visitor.Variables) + foreach (var variable in finder.Variables) variablesBuffer[i++] = variable; - return visitor.Count; + return finder.Variables.Count; } } } \ No newline at end of file diff --git a/src/Core/Echo.Ast/AstNode.cs b/src/Core/Echo.Ast/AstNode.cs index 08da070f..9e57b304 100644 --- a/src/Core/Echo.Ast/AstNode.cs +++ b/src/Core/Echo.Ast/AstNode.cs @@ -1,17 +1,16 @@ using System.Text; using Echo.Code; -using Echo.ControlFlow.Serialization.Dot; using Echo.Graphing; namespace Echo.Ast { /// - /// Provides a base contract for all AST nodes + /// Represents a single node in an Abstract Syntax Tree (AST). /// public abstract class AstNode : TreeNodeBase { /// - /// Gets the parent of the AST node. + /// Gets the direct parent of the AST node. /// public new AstNode? Parent => base.Parent as AstNode; @@ -38,6 +37,11 @@ public Trilean IsPure(IPurityClassifier classifier) return Accept(Analysis.AstPurityVisitor.Instance, classifier); } + /// + /// Implements the visitor pattern + /// + public abstract void Accept(IAstNodeVisitor visitor); + /// /// Implements the visitor pattern /// diff --git a/src/Core/Echo.Ast/AstNodeListener.cs b/src/Core/Echo.Ast/AstNodeListener.cs new file mode 100644 index 00000000..b7d617db --- /dev/null +++ b/src/Core/Echo.Ast/AstNodeListener.cs @@ -0,0 +1,39 @@ +namespace Echo.Ast +{ + /// + /// Provides a base implementation for an . + /// + /// The type of instructions stored in the AST. + public abstract class AstNodeListener : IAstNodeListener + { + /// + public virtual void EnterAssignmentStatement(AssignmentStatement statement) {} + + /// + public virtual void ExitAssignmentStatement(AssignmentStatement statement) {} + + /// + public virtual void EnterExpressionStatement(ExpressionStatement statement) {} + + /// + public virtual void ExitExpressionStatement(ExpressionStatement statement) {} + + /// + public virtual void EnterPhiStatement(PhiStatement statement) {} + + /// + public virtual void ExitPhiStatement(PhiStatement statement) {} + + /// + public virtual void EnterVariableExpression(VariableExpression expression) {} + + /// + public virtual void ExitVariableExpression(VariableExpression expression) {} + + /// + public virtual void EnterInstructionExpression(InstructionExpression expression) {} + + /// + public virtual void ExitInstructionExpression(InstructionExpression expression) {} + } +} \ No newline at end of file diff --git a/src/Core/Echo.Ast/AstNodeWalker.cs b/src/Core/Echo.Ast/AstNodeWalker.cs index 0a835da6..1738fede 100644 --- a/src/Core/Echo.Ast/AstNodeWalker.cs +++ b/src/Core/Echo.Ast/AstNodeWalker.cs @@ -1,121 +1,78 @@ namespace Echo.Ast { /// - /// Provides a base contract for Ast walkers + /// Provides a mechanism for performing a full traversal of an abstract syntax tree (AST). /// - /// The type of the instruction - public abstract class AstNodeWalker : IAstNodeVisitor + /// The instruction stored in the AST. + public class AstNodeWalker : IAstNodeVisitor { - /// - /// Begin visiting a given - /// - /// The that is being entered - protected virtual void EnterAssignmentStatement(AssignmentStatement assignmentStatement) => - VisitChildren(assignmentStatement); - - /// - /// Finish visiting a given - /// - /// The that is being entered - protected virtual void ExitAssignmentStatement(AssignmentStatement assignmentStatement) { } - - /// - /// Begin visiting a given - /// - /// The that is being entered - protected virtual void EnterExpressionStatement(ExpressionStatement expressionStatement) => - VisitChildren(expressionStatement); - - /// - /// Finish visiting a given - /// - /// The that is being finished - protected virtual void ExitExpressionStatement(ExpressionStatement expressionStatement) { } - - /// - /// Begin visiting a given - /// - /// The that is being entered - protected virtual void EnterPhiStatement(PhiStatement phiStatement) => - VisitChildren(phiStatement); - - /// - /// Finish visiting a given - /// - /// The that is being finished - protected virtual void ExitPhiStatement(PhiStatement phiStatement) { } + private readonly IAstNodeListener _listener; /// - /// Begin visiting a given + /// Creates a new AST node walker. /// - /// The that is being entered - protected virtual void EnterInstructionExpression(InstructionExpression instructionExpression) => - VisitChildren(instructionExpression); + /// The listener to call after every traversal step. + public AstNodeWalker(IAstNodeListener listener) + { + _listener = listener; + } /// - /// Finish visiting a given + /// Walks the provided AST with the provided listener. /// - /// The that is being finished - protected virtual void ExitInstructionExpression(InstructionExpression instructionExpression) { } + /// The listener to callback after every traversal step. + /// The root node of the AST. + public static void Walk(IAstNodeListener listener, AstNode node) + { + var walker = new AstNodeWalker(listener); + walker.Walk(node); + } /// - /// Visiting a given + /// Walks the provided AST. /// - /// The that is will be visited - protected virtual void VisitVariableExpression(VariableExpression variableExpression) { } - - private void VisitChildren(AstNode node) - { - foreach (var child in node.GetChildren()) - ((AstNode) child).Accept(this, null); - } + /// The root node of the AST. + public void Walk(AstNode node) => node.Accept(this); /// - void IAstNodeVisitor.Visit(AssignmentStatement statement, object? state) + public void Visit(AssignmentStatement statement) { - EnterAssignmentStatement(statement); - - statement.Expression.Accept(this, state); - - ExitAssignmentStatement(statement); + _listener.EnterAssignmentStatement(statement); + statement.Expression.Accept(this); + _listener.ExitAssignmentStatement(statement); } /// - void IAstNodeVisitor.Visit(ExpressionStatement expression, object? state) + public void Visit(ExpressionStatement expression) { - EnterExpressionStatement(expression); - - expression.Expression.Accept(this, state); - - ExitExpressionStatement(expression); + _listener.EnterExpressionStatement(expression); + expression.Expression.Accept(this); + _listener.ExitExpressionStatement(expression); } /// - void IAstNodeVisitor.Visit(PhiStatement statement, object? state) + public void Visit(PhiStatement statement) { - EnterPhiStatement(statement); - - foreach (var source in statement.Sources) - source.Accept(this, state); - - ExitPhiStatement(statement); + _listener.EnterPhiStatement(statement); + for (int i = 0; i < statement.Sources.Count; i++) + statement.Sources[i].Accept(this); + _listener.ExitPhiStatement(statement); } /// - void IAstNodeVisitor.Visit(InstructionExpression expression, object? state) + public void Visit(InstructionExpression expression) { - EnterInstructionExpression(expression); - - foreach (var parameter in expression.Arguments) - parameter.Accept(this, state); - - ExitInstructionExpression(expression); + _listener.EnterInstructionExpression(expression); + for (int i = 0; i < expression.Arguments.Count; i++) + expression.Arguments[i].Accept(this); + _listener.ExitInstructionExpression(expression); } /// - void IAstNodeVisitor.Visit(VariableExpression expression, object? state) + public void Visit(VariableExpression expression) { - VisitVariableExpression(expression); + _listener.EnterVariableExpression(expression); + _listener.ExitVariableExpression(expression); } } } \ No newline at end of file diff --git a/src/Core/Echo.Ast/ExpressionStatement.cs b/src/Core/Echo.Ast/ExpressionStatement.cs index 99335aa4..0bed6d07 100644 --- a/src/Core/Echo.Ast/ExpressionStatement.cs +++ b/src/Core/Echo.Ast/ExpressionStatement.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using Echo.ControlFlow.Serialization.Dot; using Echo.Graphing; namespace Echo.Ast @@ -36,6 +35,10 @@ public override IEnumerable GetChildren() yield return _expression; } + /// + public override void Accept(IAstNodeVisitor visitor) + => visitor.Visit(this); + /// public override void Accept(IAstNodeVisitor visitor, TState state) => visitor.Visit(this, state); diff --git a/src/Core/Echo.Ast/IAstNodeListener.cs b/src/Core/Echo.Ast/IAstNodeListener.cs new file mode 100644 index 00000000..38603979 --- /dev/null +++ b/src/Core/Echo.Ast/IAstNodeListener.cs @@ -0,0 +1,71 @@ +namespace Echo.Ast +{ + /// + /// Provides methods for entering and exiting nodes in an AST during a traversal by an + /// . + /// + /// The type of instructions stored in the AST. + public interface IAstNodeListener + { + /// + /// Enters an assignment statement. + /// + /// The statement to enter. + void EnterAssignmentStatement(AssignmentStatement statement); + + /// + /// Exits an assignment statement. + /// + /// The statement to exit. + void ExitAssignmentStatement(AssignmentStatement statement); + + /// + /// Enters an expression statement. + /// + /// The statement to enter. + void EnterExpressionStatement(ExpressionStatement statement); + + /// + /// Exits an expression statement. + /// + /// The statement to exit. + void ExitExpressionStatement(ExpressionStatement statement); + + /// + /// Enters a PHI statement. + /// + /// The statement to enter. + void EnterPhiStatement(PhiStatement statement); + + /// + /// Exits a PHI statement. + /// + /// The statement to exit. + void ExitPhiStatement(PhiStatement statement); + + /// + /// Enters a variable expression. + /// + /// The expression to enter. + void EnterVariableExpression(VariableExpression expression); + + /// + /// Exits a variable expression. + /// + /// The expression to exit. + void ExitVariableExpression(VariableExpression expression); + + /// + /// Enters an instruction expression. + /// + /// The expression to enter. + void EnterInstructionExpression(InstructionExpression expression); + + /// + /// Exits an instruction expression. + /// + /// The expression to exit.. + void ExitInstructionExpression(InstructionExpression expression); + } + +} \ No newline at end of file diff --git a/src/Core/Echo.Ast/IAstNodeVisitor.cs b/src/Core/Echo.Ast/IAstNodeVisitor.cs index baa6a802..f632c279 100644 --- a/src/Core/Echo.Ast/IAstNodeVisitor.cs +++ b/src/Core/Echo.Ast/IAstNodeVisitor.cs @@ -1,5 +1,37 @@ namespace Echo.Ast { + /// + /// Provides a visitor interface to implement a visitor pattern on the AST + /// + /// The type of the instruction the AST models + public interface IAstNodeVisitor + { + /// + /// Visits a given + /// + void Visit(AssignmentStatement statement); + + /// + /// Visits a given + /// + void Visit(ExpressionStatement expression); + + /// + /// Visits a given + /// + void Visit(PhiStatement statement); + + /// + /// Visits a given + /// + void Visit(InstructionExpression expression); + + /// + /// Visits a given + /// + void Visit(VariableExpression expression); + } + /// /// Provides a visitor interface to implement a visitor pattern on the AST /// diff --git a/src/Core/Echo.Ast/InstructionExpression.cs b/src/Core/Echo.Ast/InstructionExpression.cs index 7ed3b23f..3ec42b14 100644 --- a/src/Core/Echo.Ast/InstructionExpression.cs +++ b/src/Core/Echo.Ast/InstructionExpression.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using Echo.ControlFlow.Serialization.Dot; using Echo.Graphing; namespace Echo.Ast @@ -63,6 +62,10 @@ public IList> Arguments /// public override IEnumerable GetChildren() => Arguments; + /// + public override void Accept(IAstNodeVisitor visitor) + => visitor.Visit(this); + /// public override void Accept(IAstNodeVisitor visitor, TState state) => visitor.Visit(this, state); diff --git a/src/Core/Echo.Ast/PhiStatement.cs b/src/Core/Echo.Ast/PhiStatement.cs index c5f2b439..28322aa1 100644 --- a/src/Core/Echo.Ast/PhiStatement.cs +++ b/src/Core/Echo.Ast/PhiStatement.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using Echo.ControlFlow.Serialization.Dot; using Echo.Code; using Echo.Graphing; @@ -54,6 +53,10 @@ public IList> Sources /// public override IEnumerable GetChildren() => Sources; + /// + public override void Accept(IAstNodeVisitor visitor) + => visitor.Visit(this); + /// public override void Accept(IAstNodeVisitor visitor, TState state) => visitor.Visit(this, state); diff --git a/src/Core/Echo.Ast/VariableExpression.cs b/src/Core/Echo.Ast/VariableExpression.cs index 7524cf4a..9d554d3c 100644 --- a/src/Core/Echo.Ast/VariableExpression.cs +++ b/src/Core/Echo.Ast/VariableExpression.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using Echo.ControlFlow.Serialization.Dot; using Echo.Code; using Echo.Graphing; @@ -32,6 +31,10 @@ public IVariable Variable /// public override IEnumerable GetChildren() => Array.Empty(); + /// + public override void Accept(IAstNodeVisitor visitor) + => visitor.Visit(this); + /// public override void Accept(IAstNodeVisitor visitor, TState state) => visitor.Visit(this, state); diff --git a/test/Platforms/Echo.Platforms.DummyPlatform/Code/DummyFormatter.cs b/test/Platforms/Echo.Platforms.DummyPlatform/Code/DummyFormatter.cs new file mode 100644 index 00000000..1a061a58 --- /dev/null +++ b/test/Platforms/Echo.Platforms.DummyPlatform/Code/DummyFormatter.cs @@ -0,0 +1,19 @@ +using Echo.ControlFlow.Serialization.Dot; + +namespace Echo.Platforms.DummyPlatform.Code; + +public class DummyFormatter : IInstructionFormatter +{ + public bool IncludeOffset + { + get; + set; + } = true; + + public string Format(in DummyInstruction instruction) + { + return IncludeOffset + ? $"Label_{instruction.Offset:X4}: {instruction.Mnemonic} {string.Join(", ", instruction.Operands)}" + : $"{instruction.Mnemonic} {string.Join(", ", instruction.Operands)}"; + } +} \ No newline at end of file From 9f85b59b8a782b2c8467e216ed8b58a75151c056 Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 4 Jan 2024 22:07:56 +0100 Subject: [PATCH 21/33] Reduce allocs in AST building. Add ToAst extension method to Architecture. --- src/Core/Echo.Ast/AstArchitecture.cs | 15 ++++ src/Core/Echo.Ast/Construction/AstBuilder.cs | 73 +++++++++----------- src/Core/Echo.Ast/PhiStatement.cs | 16 +++++ 3 files changed, 65 insertions(+), 39 deletions(-) diff --git a/src/Core/Echo.Ast/AstArchitecture.cs b/src/Core/Echo.Ast/AstArchitecture.cs index 34ca4f6f..587d2587 100644 --- a/src/Core/Echo.Ast/AstArchitecture.cs +++ b/src/Core/Echo.Ast/AstArchitecture.cs @@ -82,4 +82,19 @@ public int GetWrittenVariables(in Statement instruction, Span + /// Provides extension methods for AST architectures. + /// + public static class AstArchitectureExtensions + { + /// + /// Wraps the provided architecture to an AST architecture. + /// + /// The architecture. + /// The type of instructions defined by the architecture. + /// The lifted architecture. + public static AstArchitecture ToAst(this IArchitecture self) + => new(self); + } } \ No newline at end of file diff --git a/src/Core/Echo.Ast/Construction/AstBuilder.cs b/src/Core/Echo.Ast/Construction/AstBuilder.cs index 78e82f23..74334842 100644 --- a/src/Core/Echo.Ast/Construction/AstBuilder.cs +++ b/src/Core/Echo.Ast/Construction/AstBuilder.cs @@ -13,7 +13,7 @@ namespace Echo.Ast.Construction; public sealed class AstBuilder { private readonly ControlFlowGraph _original; - private readonly ControlFlowGraph> _transformed; + private readonly ControlFlowGraph> _lifted; private readonly IPurityClassifier _purityClassifier; private readonly Dictionary, LiftedNode> _liftedNodes = new(); @@ -21,7 +21,7 @@ private AstBuilder(ControlFlowGraph original, IPurityClassifier>(new AstArchitecture(original.Architecture)); + _lifted = new ControlFlowGraph>(original.Architecture.ToAst()); } /// @@ -36,7 +36,7 @@ public static ControlFlowGraph> Lift( { var builder = new AstBuilder(cfg, purityClassifier); builder.Run(); - return builder._transformed; + return builder._lifted; } private void Run() @@ -66,7 +66,7 @@ private void LiftNodes() { var liftedNode = LiftNode(node); _liftedNodes.Add(node, liftedNode); - _transformed.Nodes.Add(liftedNode.Transformed); + _lifted.Nodes.Add(liftedNode.Transformed); } } @@ -173,28 +173,25 @@ private void LiftInstruction( private static Expression Pop(LiftedNode node, Stack> stack) { - if (stack.Count == 0) - { - var variable = node.DeclareStackInput(); - - var expression = variable.ToExpression(); - node.StackInputReferences.Add(variable, expression); - - return expression; - } - else + // If there is something on the stack, we can pop it. Otherwise it is a stack-input for this basic block. + if (stack.Count != 0) return stack.Pop(); + + var variable = node.DeclareStackInput(); + var expression = variable.ToExpression(); + node.StackInputReferences.Add(variable, expression); + return expression; } private static void FlushStackAsOutput(LiftedNode node, Stack> stack) { - FlushStackInternal(node, stack, (n, value) => + FlushStackInternal(node, stack, static (n, value) => { if (value is VariableExpression {Variable: SyntheticVariable variable}) { // If this output expression is already variable expression, we do not need to allocate a new variable // and can instead just promote the variable to a stack output variable (inlining). - node.StackOutputs.Add(variable); + n.StackOutputs.Add(variable); } else { @@ -209,7 +206,7 @@ private static void FlushStackAsOutput(LiftedNode node, Stack node, Stack> stack) { - var variables = FlushStackInternal(node, stack, (n, value) => + var variables = FlushStackInternal(node, stack, static (n, value) => { var intermediate = n.DeclareStackIntermediate(); n.Transformed.Contents.Instructions.Add(Statement.Assignment(intermediate, value)); @@ -225,7 +222,7 @@ private void FlushStackIfImpure( Stack> stack, Expression expression) { - // Is this expression potentially impure? + // Is this expression pure with 100% certainty? if (expression.IsPure(_purityClassifier).ToBooleanOrFalse()) return; @@ -272,28 +269,26 @@ private void PopulatePhiStatements() while (agenda.Count > 0) { - var current = agenda.Dequeue(); - var liftedNode = _liftedNodes[current.Node]; + var currentState = agenda.Dequeue(); + var liftedNode = _liftedNodes[currentState.Node]; - // Have we visited this block before? - bool changed = false; if (!recordedStates.TryGetValue(liftedNode, out var previousState)) { // We have never visited this block before. Register the new state. - recordedStates[liftedNode] = current; - changed = true; + recordedStates[liftedNode] = currentState; } - else if (previousState.MergeWith(current, out var newState)) + else if (previousState.MergeWith(currentState, out var mergedState)) { // Merging the states resulted in a change. We have to revisit this path. - current = newState; - recordedStates[liftedNode] = newState; - changed = true; + currentState = mergedState; + recordedStates[liftedNode] = mergedState; } - - // If we did not make any change to the states, we can stop. - if (!changed) + else + { + // We did not change anything to the recorded input states, so there is no need to recompute the PHI + // nodes nor any of its successors. continue; + } // Consume stack values, and add them to the phi statements. for (int i = liftedNode.StackInputs.Count - 1; i >= 0; i--) @@ -301,24 +296,24 @@ private void PopulatePhiStatements() var input = liftedNode.StackInputs[i]; // Protection against malformed code streams with stack underflow. - if (current.Stack.IsEmpty) + if (currentState.Stack.IsEmpty) break; - current = current.Pop(out var value); + currentState = currentState.Pop(out var value); foreach (var source in value.Sources) { - if (input.Sources.All(x => x.Variable != source)) + if (!input.HasSource(source)) input.Sources.Add(source.ToExpression()); } } // Push new values on stack. foreach (var output in liftedNode.StackOutputs) - current = current.Push(new StackSlot(output)); + currentState = currentState.Push(new StackSlot(output)); // Schedule successors. - foreach (var successor in current.Node.GetSuccessors()) - agenda.Enqueue(current.MoveTo(successor)); + foreach (var successor in currentState.Node.GetSuccessors()) + agenda.Enqueue(currentState.MoveTo(successor)); } } @@ -367,9 +362,9 @@ private void AddEdges() private void TransformRegions() { - _transformed.EntryPoint = _liftedNodes[_original.EntryPoint].Transformed; + _lifted.EntryPoint = _liftedNodes[_original.EntryPoint].Transformed; foreach (var region in _original.Regions) - TransformRegion(x => _transformed.Regions.Add((ControlFlowRegion>) x), region); + TransformRegion(x => _lifted.Regions.Add((ControlFlowRegion>) x), region); } private void TransformRegion( diff --git a/src/Core/Echo.Ast/PhiStatement.cs b/src/Core/Echo.Ast/PhiStatement.cs index 28322aa1..789cd200 100644 --- a/src/Core/Echo.Ast/PhiStatement.cs +++ b/src/Core/Echo.Ast/PhiStatement.cs @@ -76,6 +76,22 @@ public PhiStatement WithRepresentative(IVariable variable) return this; } + /// + /// Determines whether the provided variable is a source for this PHI node. + /// + /// The variable. + /// true if the variable is a valid source, false otherwise. + public bool HasSource(IVariable variable) + { + for (int i = 0; i < Sources.Count; i++) + { + if (Sources[i].Variable == variable) + return true; + } + + return false; + } + /// /// Modifies the current to assign values from /// From 09e113fb4111d141fc4478695a3ca34a42662c49 Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 4 Jan 2024 22:39:09 +0100 Subject: [PATCH 22/33] Update AST read/written variable finders. --- .../Echo.Ast/Analysis/ReadVariableFinder.cs | 27 ++++++++++++++++++- .../Analysis/WrittenVariableFinder.cs | 25 +++++++++++++++++ src/Core/Echo.Ast/AstArchitecture.cs | 10 ++++--- 3 files changed, 57 insertions(+), 5 deletions(-) diff --git a/src/Core/Echo.Ast/Analysis/ReadVariableFinder.cs b/src/Core/Echo.Ast/Analysis/ReadVariableFinder.cs index 522e162c..2195e0d1 100644 --- a/src/Core/Echo.Ast/Analysis/ReadVariableFinder.cs +++ b/src/Core/Echo.Ast/Analysis/ReadVariableFinder.cs @@ -1,10 +1,18 @@ -using System.Collections.Generic; +using System.Buffers; +using System.Collections.Generic; using Echo.Code; namespace Echo.Ast.Analysis { internal sealed class ReadVariableFinder : AstNodeListener { + private readonly IArchitecture _architecture; + + public ReadVariableFinder(IArchitecture architecture) + { + _architecture = architecture; + } + internal HashSet Variables { get; } = new(); public override void ExitVariableExpression(VariableExpression expression) @@ -12,5 +20,22 @@ public override void ExitVariableExpression(VariableExpression exp base.ExitVariableExpression(expression); Variables.Add(expression.Variable); } + + public override void ExitInstructionExpression(InstructionExpression expression) + { + int count = _architecture.GetReadVariablesCount(expression.Instruction); + if (count == 0) + return; + + var variables = ArrayPool.Shared.Rent(count); + + int actualCount = _architecture.GetReadVariables(expression.Instruction, variables); + for (int i = 0; i < actualCount; i++) + Variables.Add(variables[i]); + + ArrayPool.Shared.Return(variables); + + base.ExitInstructionExpression(expression); + } } } \ No newline at end of file diff --git a/src/Core/Echo.Ast/Analysis/WrittenVariableFinder.cs b/src/Core/Echo.Ast/Analysis/WrittenVariableFinder.cs index da2033c8..dc3b8bb4 100644 --- a/src/Core/Echo.Ast/Analysis/WrittenVariableFinder.cs +++ b/src/Core/Echo.Ast/Analysis/WrittenVariableFinder.cs @@ -1,3 +1,4 @@ +using System.Buffers; using System.Collections.Generic; using Echo.Code; @@ -5,6 +6,13 @@ namespace Echo.Ast.Analysis { internal sealed class WrittenVariableFinder : AstNodeListener { + private readonly IArchitecture _architecture; + + public WrittenVariableFinder(IArchitecture architecture) + { + _architecture = architecture; + } + internal HashSet Variables { get; } = new(); public override void ExitAssignmentStatement(AssignmentStatement statement) @@ -19,5 +27,22 @@ public override void ExitPhiStatement(PhiStatement phiStatement) base.ExitPhiStatement(phiStatement); Variables.Add(phiStatement.Representative); } + + public override void ExitInstructionExpression(InstructionExpression expression) + { + int count = _architecture.GetWrittenVariablesCount(expression.Instruction); + if (count == 0) + return; + + var variables = ArrayPool.Shared.Rent(count); + + int actualCount = _architecture.GetWrittenVariables(expression.Instruction, variables); + for (int i = 0; i < actualCount; i++) + Variables.Add(variables[i]); + + ArrayPool.Shared.Return(variables); + + base.ExitInstructionExpression(expression); + } } } \ No newline at end of file diff --git a/src/Core/Echo.Ast/AstArchitecture.cs b/src/Core/Echo.Ast/AstArchitecture.cs index 587d2587..c091a70e 100644 --- a/src/Core/Echo.Ast/AstArchitecture.cs +++ b/src/Core/Echo.Ast/AstArchitecture.cs @@ -11,6 +11,7 @@ namespace Echo.Ast public class AstArchitecture : IArchitecture> { + private readonly IArchitecture _baseArchitecture; private readonly FlowControlDeterminer _flowControlDeterminer; /// @@ -19,6 +20,7 @@ public class AstArchitecture /// The to decorate public AstArchitecture(IArchitecture baseArchitecture) { + _baseArchitecture = baseArchitecture; _flowControlDeterminer = new FlowControlDeterminer(baseArchitecture); } @@ -43,7 +45,7 @@ public InstructionFlowControl GetFlowControl(in Statement instruct /// public int GetReadVariablesCount(in Statement instruction) { - var finder = new ReadVariableFinder(); + var finder = new ReadVariableFinder(_baseArchitecture); AstNodeWalker.Walk(finder, instruction); return finder.Variables.Count; } @@ -51,7 +53,7 @@ public int GetReadVariablesCount(in Statement instruction) /// public int GetReadVariables(in Statement instruction, Span variablesBuffer) { - var finder = new ReadVariableFinder(); + var finder = new ReadVariableFinder(_baseArchitecture); AstNodeWalker.Walk(finder, instruction); int i = 0; @@ -64,7 +66,7 @@ public int GetReadVariables(in Statement instruction, Span public int GetWrittenVariablesCount(in Statement instruction) { - var finder = new WrittenVariableFinder(); + var finder = new WrittenVariableFinder(_baseArchitecture); AstNodeWalker.Walk(finder, instruction); return finder.Variables.Count; } @@ -72,7 +74,7 @@ public int GetWrittenVariablesCount(in Statement instruction) /// public int GetWrittenVariables(in Statement instruction, Span variablesBuffer) { - var finder = new WrittenVariableFinder(); + var finder = new WrittenVariableFinder(_baseArchitecture); AstNodeWalker.Walk(finder, instruction); int i = 0; From 3f13ae042a6c6b906b9b87baca3f47f00f8cd595 Mon Sep 17 00:00:00 2001 From: Washi Date: Fri, 26 Jan 2024 13:17:51 +0100 Subject: [PATCH 23/33] Reduce casting in ast builder. --- src/Core/Echo.Ast/Construction/AstBuilder.cs | 8 ++++---- .../Echo.Ast/Patterns/InstructionExpressionPattern.cs | 6 ++++++ test/Core/Echo.Ast.Tests/Construction/AstBuilderTest.cs | 2 +- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/Core/Echo.Ast/Construction/AstBuilder.cs b/src/Core/Echo.Ast/Construction/AstBuilder.cs index 74334842..6f618642 100644 --- a/src/Core/Echo.Ast/Construction/AstBuilder.cs +++ b/src/Core/Echo.Ast/Construction/AstBuilder.cs @@ -364,12 +364,12 @@ private void TransformRegions() { _lifted.EntryPoint = _liftedNodes[_original.EntryPoint].Transformed; foreach (var region in _original.Regions) - TransformRegion(x => _lifted.Regions.Add((ControlFlowRegion>) x), region); + TransformRegion(x => _lifted.Regions.Add(x), region); } private void TransformRegion( - Action>> addSection, - IControlFlowRegion region + Action>> addSection, + ControlFlowRegion region ) { switch (region) @@ -434,7 +434,7 @@ ScopeRegion> newRegion ) { foreach (var subRegion in originalRegion.Regions) - TransformRegion(x => newRegion.Regions.Add((ControlFlowRegion>) x), subRegion); + TransformRegion(x => newRegion.Regions.Add(x), subRegion); } } \ No newline at end of file diff --git a/src/Core/Echo.Ast/Patterns/InstructionExpressionPattern.cs b/src/Core/Echo.Ast/Patterns/InstructionExpressionPattern.cs index 005ac553..b24d2406 100644 --- a/src/Core/Echo.Ast/Patterns/InstructionExpressionPattern.cs +++ b/src/Core/Echo.Ast/Patterns/InstructionExpressionPattern.cs @@ -173,6 +173,12 @@ public InstructionExpressionPattern WithAnyArguments() return this; } + public InstructionExpressionPattern CaptureContent(CaptureGroup captureGroup) + { + Content.CaptureAs(captureGroup); + return this; + } + /// /// Indicates all arguments should be captured in a certain group. /// diff --git a/test/Core/Echo.Ast.Tests/Construction/AstBuilderTest.cs b/test/Core/Echo.Ast.Tests/Construction/AstBuilderTest.cs index 2e830993..50bfd220 100644 --- a/test/Core/Echo.Ast.Tests/Construction/AstBuilderTest.cs +++ b/test/Core/Echo.Ast.Tests/Construction/AstBuilderTest.cs @@ -323,7 +323,7 @@ public void PushArgumentBeforeImpureStatement() Assert.Same(match1.GetCaptures(variable)[0], match4.GetCaptures(variable)[0]); Assert.Same(match2.GetCaptures(variable)[0], match3.GetCaptures(variable)[0]); } - + [Fact] public void TwoNodes() { From 786c926f7d96cad9b8c9eee293d50bb99a5820e9 Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 1 Feb 2024 19:49:39 +0100 Subject: [PATCH 24/33] Add block statement models. --- .../Echo.Ast/Analysis/AstPurityVisitor.cs | 45 ++++++ .../Analysis/FlowControlDeterminer.cs | 23 ++- src/Core/Echo.Ast/AstFormatter.cs | 151 ++++++++++++++---- src/Core/Echo.Ast/AstNode.cs | 8 +- src/Core/Echo.Ast/AstNodeListener.cs | 48 ++++++ src/Core/Echo.Ast/AstNodeWalker.cs | 60 +++++++ src/Core/Echo.Ast/BlockStatement.cs | 41 +++++ src/Core/Echo.Ast/CompilationUnit.cs | 44 +++++ .../Echo.Ast/ExceptionHandlerStatement.cs | 58 +++++++ src/Core/Echo.Ast/HandlerClause.cs | 80 ++++++++++ src/Core/Echo.Ast/IAstNodeListener.cs | 104 +++++++++++- src/Core/Echo.Ast/IAstNodeVisitor.cs | 63 +++++++- .../Graphing/Serialization/Dot/DotWriter.cs | 4 +- 13 files changed, 681 insertions(+), 48 deletions(-) create mode 100644 src/Core/Echo.Ast/BlockStatement.cs create mode 100644 src/Core/Echo.Ast/CompilationUnit.cs create mode 100644 src/Core/Echo.Ast/ExceptionHandlerStatement.cs create mode 100644 src/Core/Echo.Ast/HandlerClause.cs diff --git a/src/Core/Echo.Ast/Analysis/AstPurityVisitor.cs b/src/Core/Echo.Ast/Analysis/AstPurityVisitor.cs index 254a9d1a..8b289440 100644 --- a/src/Core/Echo.Ast/Analysis/AstPurityVisitor.cs +++ b/src/Core/Echo.Ast/Analysis/AstPurityVisitor.cs @@ -16,6 +16,12 @@ public static AstPurityVisitor Instance get; } = new(); + /// + public Trilean Visit(CompilationUnit unit, IPurityClassifier state) + { + return unit.Root.Accept(this, state); + } + /// public Trilean Visit(AssignmentStatement statement, IPurityClassifier state) => false; @@ -28,6 +34,45 @@ public Trilean Visit(ExpressionStatement statement, IPurityClassif /// public Trilean Visit(PhiStatement statement, IPurityClassifier state) => false; + /// + public Trilean Visit(BlockStatement statement, IPurityClassifier state) + { + var result = Trilean.True; + + for (int i = 0; i < statement.Statements.Count && result != Trilean.False; i++) + result &= statement.Statements[i].Accept(this, state); + + return result; + } + + /// + public Trilean Visit(ExceptionHandlerStatement statement, IPurityClassifier state) + { + var result = statement.ProtectedBlock.Accept(this, state); + + for (int i = 0; i < statement.Handlers.Count && result != Trilean.False; i++) + result &= statement.Handlers[i].Accept(this, state); + + return result; + } + + /// + public Trilean Visit(HandlerClause clause, IPurityClassifier state) + { + var result = Trilean.True; + + if (clause.Prologue is not null) + result &= clause.Prologue.Accept(this, state); + + if (result.ToBooleanOrFalse()) + result &= clause.Contents.Accept(this, state); + + if (clause.Epilogue is not null && result.ToBooleanOrFalse()) + result &= clause.Epilogue.Accept(this, state); + + return result; + } + /// public Trilean Visit(InstructionExpression expression, IPurityClassifier state) { diff --git a/src/Core/Echo.Ast/Analysis/FlowControlDeterminer.cs b/src/Core/Echo.Ast/Analysis/FlowControlDeterminer.cs index 7a4a7195..c94f4ee6 100644 --- a/src/Core/Echo.Ast/Analysis/FlowControlDeterminer.cs +++ b/src/Core/Echo.Ast/Analysis/FlowControlDeterminer.cs @@ -1,4 +1,5 @@ -using Echo.Code; +using System; +using Echo.Code; namespace Echo.Ast.Analysis { @@ -10,6 +11,9 @@ internal sealed class FlowControlDeterminer internal FlowControlDeterminer(IArchitecture isa) => _isa = isa; + public InstructionFlowControl Visit(CompilationUnit unit, object? state) => + throw new NotSupportedException(); + public InstructionFlowControl Visit(AssignmentStatement statement, object? state) => statement.Expression.Accept(this, state); @@ -19,6 +23,23 @@ public InstructionFlowControl Visit(ExpressionStatement statement, public InstructionFlowControl Visit(PhiStatement statement, object? state) => InstructionFlowControl.Fallthrough; + public InstructionFlowControl Visit(BlockStatement statement, object? state) + { + return statement.Statements.Count > 0 + ? statement.Statements[statement.Statements.Count - 1].Accept(this, state) + : InstructionFlowControl.Fallthrough; + } + + public InstructionFlowControl Visit(ExceptionHandlerStatement statement, object? state) + { + return statement.ProtectedBlock.Accept(this, state); + } + + public InstructionFlowControl Visit(HandlerClause clause, object? state) + { + throw new NotSupportedException(); + } + public InstructionFlowControl Visit(InstructionExpression expression, object? state) => _isa.GetFlowControl(expression.Instruction); diff --git a/src/Core/Echo.Ast/AstFormatter.cs b/src/Core/Echo.Ast/AstFormatter.cs index ceec54dd..3afd41d1 100644 --- a/src/Core/Echo.Ast/AstFormatter.cs +++ b/src/Core/Echo.Ast/AstFormatter.cs @@ -1,4 +1,5 @@ -using System.Text; +using System.CodeDom.Compiler; +using System.IO; using Echo.ControlFlow.Serialization.Dot; namespace Echo.Ast; @@ -24,13 +25,10 @@ public static AstFormatter ToAstFormatter(this IInst /// Provides a mechanism for stringifying AST nodes. /// /// The type of instructions stored in the AST. -public class AstFormatter : IAstNodeVisitor, IInstructionFormatter> +public class AstFormatter : + IAstNodeVisitor, + IInstructionFormatter> { - /// - /// Gets the default instance of the formatter using the default formatter for . - /// - public static AstFormatter Default { get; } = new(); - /// /// Creates a new AST formatter using the default instruction formatter. /// @@ -38,7 +36,7 @@ public AstFormatter() : this(DefaultInstructionFormatter.Instance) { } - + /// /// Creates a new AST formatter using the provided instruction formatter. /// @@ -50,70 +48,155 @@ public AstFormatter(IInstructionFormatter instructionFormatter) /// /// Gets the instruction formatter used for formatting instances. /// - public IInstructionFormatter InstructionFormatter { get; } - - /// - public string Format(in Statement instruction) + public IInstructionFormatter InstructionFormatter { - var builder = new StringBuilder(); - instruction.Accept(this, builder); - return builder.ToString(); + get; } + string IInstructionFormatter>.Format(in Statement instruction) => Format(instruction); + + /// + /// Formats a single AST node into a string. + /// + /// The node to format. + /// The formatted AST node. + public string Format(in AstNode node) + { + var writer = new StringWriter(); + node.Accept(this, new IndentedTextWriter(writer)); + return writer.ToString(); + } + + /// + public void Visit(CompilationUnit unit, IndentedTextWriter state) => unit.Root.Accept(this, state); + /// - public void Visit(AssignmentStatement statement, StringBuilder state) + public void Visit(AssignmentStatement statement, IndentedTextWriter state) { for (int i = 0; i < statement.Variables.Count; i++) { if (i > 0) - state.Append(", "); + state.Write(", "); - state.Append(statement.Variables[i].Name); + state.Write(statement.Variables[i].Name); } - state.Append(" = "); + state.Write(" = "); statement.Expression.Accept(this, state); } /// - public void Visit(ExpressionStatement expression, StringBuilder state) + public void Visit(ExpressionStatement expression, IndentedTextWriter state) { expression.Expression.Accept(this, state); - state.Append(';'); + state.Write(';'); } /// - public void Visit(PhiStatement statement, StringBuilder state) + public void Visit(PhiStatement statement, IndentedTextWriter state) { - state.Append(statement.Representative.Name); - state.Append(" = φ("); + state.Write(statement.Representative.Name); + state.Write(" = φ("); for (int i = 0; i < statement.Sources.Count; i++) { if (i > 0) - state.Append(", "); + state.Write(", "); statement.Sources[i].Accept(this, state); } - state.Append(");"); + + state.Write(");"); } /// - public void Visit(InstructionExpression expression, StringBuilder state) + public void Visit(BlockStatement statement, IndentedTextWriter state) { - state.Append(InstructionFormatter.Format(expression.Instruction)); - state.Append("("); + state.WriteLine("{"); + state.Indent++; + + for (int i = 0; i < statement.Statements.Count; i++) + { + if (i > 0) + state.WriteLine(); + statement.Statements[i].Accept(this, state); + } + + state.Indent--; + state.Write("}"); + } + + /// + public void Visit(ExceptionHandlerStatement statement, IndentedTextWriter state) + { + state.WriteLine("try"); + statement.ProtectedBlock.Accept(this, state); + state.WriteLine(); + + for (int i = 0; i < statement.Handlers.Count; i++) + { + if (i > 0) + state.WriteLine(); + + statement.Handlers[i].Accept(this, state); + } + } + + /// + public void Visit(HandlerClause clause, IndentedTextWriter state) + { + state.WriteLine("handler"); + state.WriteLine('{'); + state.Indent++; + + if (clause.Prologue is not null) + { + state.WriteLine("prologue"); + state.WriteLine('{'); + state.Indent++; + clause.Prologue.Accept(this, state); + state.Indent--; + state.WriteLine('}'); + } + + state.WriteLine("code"); + state.WriteLine('{'); + state.Indent++; + clause.Contents.Accept(this, state); + state.Indent--; + state.WriteLine('}'); + + if (clause.Epilogue is not null) + { + state.WriteLine("epilogue"); + state.WriteLine('{'); + state.Indent++; + clause.Epilogue.Accept(this, state); + state.Indent--; + state.WriteLine('}'); + } + + state.Indent--; + state.Write('}'); + } + + /// + public void Visit(InstructionExpression expression, IndentedTextWriter state) + { + state.Write(InstructionFormatter.Format(expression.Instruction)); + state.Write("("); + for (int i = 0; i < expression.Arguments.Count; i++) { if (i > 0) - state.Append(", "); + state.Write(", "); expression.Arguments[i].Accept(this, state); } - state.Append(")"); + + state.Write(")"); } /// - public void Visit(VariableExpression expression, StringBuilder state) + public void Visit(VariableExpression expression, IndentedTextWriter state) { - state.Append(expression.Variable.Name); + state.Write(expression.Variable.Name); } - } \ No newline at end of file diff --git a/src/Core/Echo.Ast/AstNode.cs b/src/Core/Echo.Ast/AstNode.cs index 9e57b304..891ed4f7 100644 --- a/src/Core/Echo.Ast/AstNode.cs +++ b/src/Core/Echo.Ast/AstNode.cs @@ -53,12 +53,6 @@ public Trilean IsPure(IPurityClassifier classifier) public abstract TOut Accept(IAstNodeVisitor visitor, TState state); /// - public override string ToString() - { - var formatter = new AstFormatter(); - var builder = new StringBuilder(); - Accept(formatter, builder); - return builder.ToString(); - } + public override string ToString() => new AstFormatter().Format(this); } } \ No newline at end of file diff --git a/src/Core/Echo.Ast/AstNodeListener.cs b/src/Core/Echo.Ast/AstNodeListener.cs index b7d617db..0359726d 100644 --- a/src/Core/Echo.Ast/AstNodeListener.cs +++ b/src/Core/Echo.Ast/AstNodeListener.cs @@ -6,6 +6,12 @@ namespace Echo.Ast /// The type of instructions stored in the AST. public abstract class AstNodeListener : IAstNodeListener { + /// + public void EnterCompilationUnit(CompilationUnit unit) {} + + /// + public void ExitCompilationUnit(CompilationUnit unit) {} + /// public virtual void EnterAssignmentStatement(AssignmentStatement statement) {} @@ -23,7 +29,49 @@ public virtual void EnterPhiStatement(PhiStatement statement) {} /// public virtual void ExitPhiStatement(PhiStatement statement) {} + + /// + public virtual void EnterBlockStatement(BlockStatement statement) {} + + /// + public virtual void ExitBlockStatement(BlockStatement statement) {} + + /// + public virtual void EnterExceptionHandlerStatement(ExceptionHandlerStatement statement) {} + + /// + public virtual void ExitExceptionHandlerBlock(ExceptionHandlerStatement statement) {} + + /// + public virtual void EnterProtectedBlock(ExceptionHandlerStatement statement) {} + + /// + public virtual void ExitProtectedBlock(ExceptionHandlerStatement statement) {} + /// + public virtual void EnterHandlerBlock(ExceptionHandlerStatement statement, int handlerIndex) {} + + /// + public virtual void ExitHandlerBlock(ExceptionHandlerStatement statement, int handlerIndex) {} + + /// + public virtual void EnterPrologueBlock(HandlerClause clause) {} + + /// + public virtual void ExitPrologueBlock(HandlerClause clause) {} + + /// + public virtual void EnterEpilogueBlock(HandlerClause clause) {} + + /// + public virtual void ExitEpilogueBlock(HandlerClause clause) {} + + /// + public virtual void EnterHandlerContents(HandlerClause clause) {} + + /// + public virtual void ExitHandlerContents(HandlerClause clause) {} + /// public virtual void EnterVariableExpression(VariableExpression expression) {} diff --git a/src/Core/Echo.Ast/AstNodeWalker.cs b/src/Core/Echo.Ast/AstNodeWalker.cs index 1738fede..f2204531 100644 --- a/src/Core/Echo.Ast/AstNodeWalker.cs +++ b/src/Core/Echo.Ast/AstNodeWalker.cs @@ -34,6 +34,14 @@ public static void Walk(IAstNodeListener listener, AstNodeThe root node of the AST. public void Walk(AstNode node) => node.Accept(this); + /// + public void Visit(CompilationUnit unit) + { + _listener.EnterCompilationUnit(unit); + unit.Root.Accept(this); + _listener.ExitCompilationUnit(unit); + } + /// public void Visit(AssignmentStatement statement) { @@ -59,6 +67,58 @@ public void Visit(PhiStatement statement) _listener.ExitPhiStatement(statement); } + /// + public void Visit(BlockStatement statement) + { + _listener.EnterBlockStatement(statement); + foreach (var s in statement.Statements) + s.Accept(this); + _listener.ExitBlockStatement(statement); + } + + /// + public void Visit(ExceptionHandlerStatement statement) + { + _listener.EnterExceptionHandlerStatement(statement); + + _listener.EnterProtectedBlock(statement); + statement.ProtectedBlock.Accept(this); + _listener.ExitProtectedBlock(statement); + + for (int i = 0; i < statement.Handlers.Count; i++) + { + var handlerBlock = statement.Handlers[i]; + + _listener.EnterHandlerBlock(statement, i); + handlerBlock.Accept(this); + _listener.ExitHandlerBlock(statement, i); + } + + _listener.ExitExceptionHandlerBlock(statement); + } + + /// + public void Visit(HandlerClause clause) + { + if (clause.Prologue is not null) + { + _listener.EnterPrologueBlock(clause); + clause.Prologue.Accept(this); + _listener.ExitPrologueBlock(clause); + } + + _listener.EnterHandlerContents(clause); + clause.Contents.Accept(this); + _listener.ExitHandlerContents(clause); + + if (clause.Epilogue is not null) + { + _listener.EnterEpilogueBlock(clause); + clause.Epilogue.Accept(this); + _listener.ExitEpilogueBlock(clause); + } + } + /// public void Visit(InstructionExpression expression) { diff --git a/src/Core/Echo.Ast/BlockStatement.cs b/src/Core/Echo.Ast/BlockStatement.cs new file mode 100644 index 00000000..7898b1e7 --- /dev/null +++ b/src/Core/Echo.Ast/BlockStatement.cs @@ -0,0 +1,41 @@ +using System.Collections.Generic; +using Echo.Graphing; + +namespace Echo.Ast; + +/// +/// Represents a single block statement in an AST. +/// +/// The type of instruction stored in the AST. +public class BlockStatement : Statement +{ + /// + /// Creates a new empty block. + /// + public BlockStatement() + { + Statements = new TreeNodeCollection, Statement>(this); + } + + /// + /// Gets the sequence of statements that are executed in the block. + /// + public IList> Statements + { + get; + } + + /// + public override IEnumerable GetChildren() => Statements; + + /// + public override void Accept(IAstNodeVisitor visitor) => visitor.Visit(this); + + /// + public override void Accept(IAstNodeVisitor visitor, TState state) => + visitor.Visit(this, state); + + /// + public override TOut Accept(IAstNodeVisitor visitor, TState state) => + visitor.Visit(this, state); +} \ No newline at end of file diff --git a/src/Core/Echo.Ast/CompilationUnit.cs b/src/Core/Echo.Ast/CompilationUnit.cs new file mode 100644 index 00000000..b97d6063 --- /dev/null +++ b/src/Core/Echo.Ast/CompilationUnit.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; +using Echo.Graphing; + +namespace Echo.Ast; + +/// +/// Represents a single compilation unit of code. This is the root node of any AST. +/// +/// The type of instructions to store in the AST. +public class CompilationUnit : AstNode +{ + private BlockStatement _root = null!; + + /// + /// Creates a new empty compilation unit. + /// + public CompilationUnit() + { + Root = new BlockStatement(); + } + + /// + /// Gets the root scope of the compilation unit. + /// + public BlockStatement Root + { + get => _root; + private set => UpdateChildNotNull(ref _root, value); + } + + /// + public override IEnumerable GetChildren() => new[] {Root}; + + /// + public override void Accept(IAstNodeVisitor visitor) => visitor.Visit(this); + + /// + public override void Accept(IAstNodeVisitor visitor, TState state) => + visitor.Visit(this, state); + + /// + public override TOut Accept(IAstNodeVisitor visitor, TState state) => + visitor.Visit(this, state); +} \ No newline at end of file diff --git a/src/Core/Echo.Ast/ExceptionHandlerStatement.cs b/src/Core/Echo.Ast/ExceptionHandlerStatement.cs new file mode 100644 index 00000000..c3540e32 --- /dev/null +++ b/src/Core/Echo.Ast/ExceptionHandlerStatement.cs @@ -0,0 +1,58 @@ +using System.Collections.Generic; +using Echo.Graphing; + +namespace Echo.Ast; + +/// +/// Represents a single exception handler block statement in an AST. +/// +/// The type of instruction stored in the AST. +public class ExceptionHandlerStatement : Statement +{ + private BlockStatement _protectedBlock = null!; + + /// + /// Creates a new empty exception handler statement. + /// + public ExceptionHandlerStatement() + { + ProtectedBlock = new BlockStatement(); + Handlers = new TreeNodeCollection, HandlerClause>(this); + } + + /// + /// Gets the block of code that is protected by the exception handler. + /// + public BlockStatement ProtectedBlock + { + get => _protectedBlock; + private set => UpdateChildNotNull(ref _protectedBlock, value); + } + + /// + /// Gets all handlers that are associated to the protected block. + /// + public IList> Handlers + { + get; + } + + /// + public override IEnumerable GetChildren() + { + yield return ProtectedBlock; + foreach (var handler in Handlers) + yield return handler; + } + + /// + public override void Accept(IAstNodeVisitor visitor) => visitor.Visit(this); + + /// + public override void Accept(IAstNodeVisitor visitor, TState state) => + visitor.Visit(this, state); + + /// + public override TOut Accept(IAstNodeVisitor visitor, TState state) => + visitor.Visit(this, state); +} \ No newline at end of file diff --git a/src/Core/Echo.Ast/HandlerClause.cs b/src/Core/Echo.Ast/HandlerClause.cs new file mode 100644 index 00000000..ac72a44d --- /dev/null +++ b/src/Core/Echo.Ast/HandlerClause.cs @@ -0,0 +1,80 @@ +using System.Collections.Generic; +using Echo.Graphing; + +namespace Echo.Ast; + +/// +/// Represents a single handler clause in an exception handler block. +/// +/// The type of instructions stored in the AST. +public class HandlerClause : AstNode +{ + private BlockStatement? _prologue; + private BlockStatement _contents = null!; + private BlockStatement? _epilogue; + + /// + /// Creates a new empty handler clause. + /// + public HandlerClause() + { + Contents = new BlockStatement(); + } + + /// + /// Gets or sets a user-defined tag further describing the handler clause. + /// + public object? Tag + { + get; + set; + } + + /// + /// Gets or sets the prologue block that is executed before the main code of the clause, if available. + /// + public BlockStatement? Prologue + { + get => _prologue; + set => UpdateChild(ref _prologue, value); + } + + /// + /// Gets the main code implementing the handler. + /// + public BlockStatement Contents + { + get => _contents; + private set => UpdateChildNotNull(ref _contents, value); + } + + /// + /// Gets or sets the epilogue block that is executed after the main code of the clause, if available. + /// + public BlockStatement? Epilogue + { + get => _epilogue; + set => UpdateChild(ref _epilogue, value); + } + + /// + public override IEnumerable GetChildren() + { + if (Prologue is not null) + yield return Prologue; + yield return Contents; + if (Epilogue is not null) + yield return Epilogue; + } + + /// + public override void Accept(IAstNodeVisitor visitor) => visitor.Visit(this); + + /// + public override void Accept(IAstNodeVisitor visitor, TState state) => + visitor.Visit(this, state); + + /// + public override TOut Accept(IAstNodeVisitor visitor, TState state) => + visitor.Visit(this, state); +} \ No newline at end of file diff --git a/src/Core/Echo.Ast/IAstNodeListener.cs b/src/Core/Echo.Ast/IAstNodeListener.cs index 38603979..836aaee3 100644 --- a/src/Core/Echo.Ast/IAstNodeListener.cs +++ b/src/Core/Echo.Ast/IAstNodeListener.cs @@ -7,6 +7,18 @@ namespace Echo.Ast /// The type of instructions stored in the AST. public interface IAstNodeListener { + /// + /// Enters a compilation unit. + /// + /// The unit to enter. + void EnterCompilationUnit(CompilationUnit unit); + + /// + /// Exits a compilation unit. + /// + /// The unit to exit. + void ExitCompilationUnit(CompilationUnit unit); + /// /// Enters an assignment statement. /// @@ -42,25 +54,111 @@ public interface IAstNodeListener /// /// The statement to exit. void ExitPhiStatement(PhiStatement statement); + + /// + /// Enters a block statement. + /// + /// The statement to enter. + void EnterBlockStatement(BlockStatement statement); + + /// + /// Exits a block statement. + /// + /// The statement to exit. + void ExitBlockStatement(BlockStatement statement); + + /// + /// Enters an exception handler statement. + /// + /// The statement to enter. + void EnterExceptionHandlerStatement(ExceptionHandlerStatement statement); + + /// + /// Exits an exception handler statement. + /// + /// The statement to exit. + void ExitExceptionHandlerBlock(ExceptionHandlerStatement statement); + + /// + /// Enters the protected block of an exception handler statement. + /// + /// The parent exception handler statement. + void EnterProtectedBlock(ExceptionHandlerStatement statement); + + /// + /// Exits the protected block of an exception handler statement. + /// + /// The parent exception handler statement. + void ExitProtectedBlock(ExceptionHandlerStatement statement); + + /// + /// Enters a handler clause of an exception handler statement. + /// + /// The parent exception handler statement. + /// The index of the handler clause to enter. + void EnterHandlerBlock(ExceptionHandlerStatement statement, int handlerIndex); + + /// + /// Exits a handler clause of an exception handler statement. + /// + /// The parent exception handler statement. + /// The index of the handler clause to exit. + void ExitHandlerBlock(ExceptionHandlerStatement statement, int handlerIndex); + + /// + /// Enters the prologue of a handler clause. + /// + /// The parent clause. + void EnterPrologueBlock(HandlerClause clause); + + /// + /// Exits the prologue of a handler clause. + /// + /// The parent clause. + void ExitPrologueBlock(HandlerClause clause); + + /// + /// Enters the epilogue of a handler clause. + /// + /// The parent clause. + void EnterEpilogueBlock(HandlerClause clause); + + /// + /// Exits the epilogue of a handler clause. + /// + /// The parent clause. + void ExitEpilogueBlock(HandlerClause clause); + + /// + /// Enters the main code of a handler clause. + /// + /// The parent clause. + void EnterHandlerContents(HandlerClause clause); + + /// + /// Exits the main code of a handler clause. + /// + /// The parent clause. + void ExitHandlerContents(HandlerClause clause); /// /// Enters a variable expression. /// /// The expression to enter. void EnterVariableExpression(VariableExpression expression); - + /// /// Exits a variable expression. /// /// The expression to exit. void ExitVariableExpression(VariableExpression expression); - + /// /// Enters an instruction expression. /// /// The expression to enter. void EnterInstructionExpression(InstructionExpression expression); - + /// /// Exits an instruction expression. /// diff --git a/src/Core/Echo.Ast/IAstNodeVisitor.cs b/src/Core/Echo.Ast/IAstNodeVisitor.cs index f632c279..b250dfbe 100644 --- a/src/Core/Echo.Ast/IAstNodeVisitor.cs +++ b/src/Core/Echo.Ast/IAstNodeVisitor.cs @@ -6,6 +6,11 @@ namespace Echo.Ast /// The type of the instruction the AST models public interface IAstNodeVisitor { + /// + /// Visits a given + /// + void Visit(CompilationUnit unit); + /// /// Visits a given /// @@ -20,7 +25,22 @@ public interface IAstNodeVisitor /// Visits a given /// void Visit(PhiStatement statement); - + + /// + /// Visits a given + /// + void Visit(BlockStatement statement); + + /// + /// Visits a given + /// + void Visit(ExceptionHandlerStatement statement); + + /// + /// Visits a given + /// + void Visit(HandlerClause clause); + /// /// Visits a given /// @@ -30,6 +50,7 @@ public interface IAstNodeVisitor /// Visits a given /// void Visit(VariableExpression expression); + } /// @@ -39,6 +60,11 @@ public interface IAstNodeVisitor /// The state to pass between visitors public interface IAstNodeVisitor { + /// + /// Visits a given + /// + void Visit(CompilationUnit unit, TState state); + /// /// Visits a given /// @@ -54,6 +80,21 @@ public interface IAstNodeVisitor /// void Visit(PhiStatement statement, TState state); + /// + /// Visits a given + /// + void Visit(BlockStatement statement, TState state); + + /// + /// Visits a given + /// + void Visit(ExceptionHandlerStatement statement, TState state); + + /// + /// Visits a given + /// + void Visit(HandlerClause clause, TState state); + /// /// Visits a given /// @@ -73,6 +114,11 @@ public interface IAstNodeVisitor /// The return type of the Visit methods public interface IAstNodeVisitor { + /// + /// Visits a given + /// + TOut Visit(CompilationUnit unit, TState state); + /// /// Visits a given /// @@ -88,6 +134,21 @@ public interface IAstNodeVisitor /// TOut Visit(PhiStatement statement, TState state); + /// + /// Visits a given + /// + TOut Visit(BlockStatement statement, TState state); + + /// + /// Visits a given + /// + TOut Visit(ExceptionHandlerStatement statement, TState state); + + /// + /// Visits a given + /// + TOut Visit(HandlerClause clause, TState state); + /// /// Visits a given /// diff --git a/src/Core/Echo/Graphing/Serialization/Dot/DotWriter.cs b/src/Core/Echo/Graphing/Serialization/Dot/DotWriter.cs index fc930de1..55d8608f 100644 --- a/src/Core/Echo/Graphing/Serialization/Dot/DotWriter.cs +++ b/src/Core/Echo/Graphing/Serialization/Dot/DotWriter.cs @@ -25,13 +25,13 @@ public class DotWriter /// The writer responsible for writing the output. public DotWriter(TextWriter writer) { - Writer = new IndentedTextWriter(writer ?? throw new ArgumentNullException(nameof(writer))); + Writer = new System.CodeDom.Compiler.IndentedTextWriter(writer ?? throw new ArgumentNullException(nameof(writer))); } /// /// Gets the writer that is used to write textual data to the output stream. /// - protected IndentedTextWriter Writer + protected System.CodeDom.Compiler.IndentedTextWriter Writer { get; } From 32fc2797daa681855306f9d5bc8a09cab4bb3945 Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 1 Feb 2024 20:25:47 +0100 Subject: [PATCH 25/33] Add compilation unit builder. --- src/Core/Echo.Ast/AstFormatter.cs | 18 +- src/Core/Echo.Ast/CompilationUnit.cs | 2 +- src/Core/Echo.Ast/Construction/AstBuilder.cs | 441 +----------------- .../Construction/AstBuilderExtensions.cs | 24 - .../Construction/CompilationUnitBuilder.cs | 82 ++++ .../Construction/ControlFlowGraphLifter.cs | 440 +++++++++++++++++ .../Echo.Ast/ExceptionHandlerStatement.cs | 2 +- src/Core/Echo.Ast/HandlerClause.cs | 2 +- .../CompilationUnitBuilderTest.cs | 94 ++++ ...rTest.cs => ControlFlowGraphLifterTest.cs} | 2 +- 10 files changed, 647 insertions(+), 460 deletions(-) delete mode 100644 src/Core/Echo.Ast/Construction/AstBuilderExtensions.cs create mode 100644 src/Core/Echo.Ast/Construction/CompilationUnitBuilder.cs create mode 100644 src/Core/Echo.Ast/Construction/ControlFlowGraphLifter.cs create mode 100644 test/Core/Echo.Ast.Tests/Construction/CompilationUnitBuilderTest.cs rename test/Core/Echo.Ast.Tests/Construction/{AstBuilderTest.cs => ControlFlowGraphLifterTest.cs} (99%) diff --git a/src/Core/Echo.Ast/AstFormatter.cs b/src/Core/Echo.Ast/AstFormatter.cs index 3afd41d1..67ba43bd 100644 --- a/src/Core/Echo.Ast/AstFormatter.cs +++ b/src/Core/Echo.Ast/AstFormatter.cs @@ -115,9 +115,8 @@ public void Visit(BlockStatement statement, IndentedTextWriter sta for (int i = 0; i < statement.Statements.Count; i++) { - if (i > 0) - state.WriteLine(); statement.Statements[i].Accept(this, state); + state.WriteLine(); } state.Indent--; @@ -150,28 +149,19 @@ public void Visit(HandlerClause clause, IndentedTextWriter state) if (clause.Prologue is not null) { state.WriteLine("prologue"); - state.WriteLine('{'); - state.Indent++; clause.Prologue.Accept(this, state); - state.Indent--; - state.WriteLine('}'); + state.WriteLine(); } state.WriteLine("code"); - state.WriteLine('{'); - state.Indent++; clause.Contents.Accept(this, state); - state.Indent--; - state.WriteLine('}'); + state.WriteLine(); if (clause.Epilogue is not null) { state.WriteLine("epilogue"); - state.WriteLine('{'); - state.Indent++; clause.Epilogue.Accept(this, state); - state.Indent--; - state.WriteLine('}'); + state.WriteLine(); } state.Indent--; diff --git a/src/Core/Echo.Ast/CompilationUnit.cs b/src/Core/Echo.Ast/CompilationUnit.cs index b97d6063..44568d1b 100644 --- a/src/Core/Echo.Ast/CompilationUnit.cs +++ b/src/Core/Echo.Ast/CompilationUnit.cs @@ -25,7 +25,7 @@ public CompilationUnit() public BlockStatement Root { get => _root; - private set => UpdateChildNotNull(ref _root, value); + internal set => UpdateChildNotNull(ref _root, value); } /// diff --git a/src/Core/Echo.Ast/Construction/AstBuilder.cs b/src/Core/Echo.Ast/Construction/AstBuilder.cs index 6f618642..291df0a9 100644 --- a/src/Core/Echo.Ast/Construction/AstBuilder.cs +++ b/src/Core/Echo.Ast/Construction/AstBuilder.cs @@ -1,440 +1,45 @@ -using System; -using System.Collections.Generic; -using System.Linq; using Echo.Code; using Echo.ControlFlow; -using Echo.ControlFlow.Regions; +using Echo.ControlFlow.Blocks; namespace Echo.Ast.Construction; /// -/// Lifts every node in a control flow graph to its AST representation. +/// Provides utility extensions for the construction of new AST control flow graphs. /// -public sealed class AstBuilder +public static class AstBuilder { - private readonly ControlFlowGraph _original; - private readonly ControlFlowGraph> _lifted; - private readonly IPurityClassifier _purityClassifier; - private readonly Dictionary, LiftedNode> _liftedNodes = new(); - - private AstBuilder(ControlFlowGraph original, IPurityClassifier purityClassifier) - { - _original = original; - _purityClassifier = purityClassifier; - _lifted = new ControlFlowGraph>(original.Architecture.ToAst()); - } - /// - /// Transforms a control flow graph by lifting each basic block into an AST representation. + /// Lifts the instructions in every node of the provided control flow graph to expressions and statements. /// - /// The control flow graph. - /// A classifier used for determining whether expressions are pure or not. - /// The transformed control flow graph. - public static ControlFlowGraph> Lift( - ControlFlowGraph cfg, - IPurityClassifier purityClassifier) - { - var builder = new AstBuilder(cfg, purityClassifier); - builder.Run(); - return builder._lifted; - } - - private void Run() - { - // Strategy: - // - // We lift each node in the control flow graph in isolation. Control flow nodes have the property that they - // are executed as a single unit, and as such, within a single node we can assume an isolated eval stack. - // This avoids full data flow analysis (i.e., building a full DFG) to build ASTs, at the cost of a slight - // chance of overproducing some synthetic variables to communicate non-zero stack deltas between cfg nodes. - // In practice however this is much rarer to occur than not: Typically a block has a stack delta of 0. - // - // We only use SSA form and PHI nodes for synthetic stack variables. This is because many languages/platforms - // allow for variables to be accessed by reference and thus it is not clear just from the access alone whether - // it is just a read or also a write. We leave this decision up to the consumer of this API. - - LiftNodes(); - PopulatePhiStatements(); - InsertPhiStatements(); - AddEdges(); - TransformRegions(); - } - - private void LiftNodes() - { - foreach (var node in _original.Nodes) - { - var liftedNode = LiftNode(node); - _liftedNodes.Add(node, liftedNode); - _lifted.Nodes.Add(liftedNode.Transformed); - } - } - - private LiftedNode LiftNode(ControlFlowNode node) - { - var result = new LiftedNode(node); - - // For each node we keep track of a local stack. - var stack = new Stack>(); - - // Lift every instruction, processing stack states. - for (int i = 0; i < node.Contents.Instructions.Count; i++) - LiftInstruction(result, node.Contents.Instructions[i], stack); - - // Any values left on the stack we move into synthetic out variables. - FlushStackAsOutput(result, stack); - - return result; - } - - private void LiftInstruction( - LiftedNode node, - in TInstruction instruction, - Stack> stack) - { - var architecture = _original.Architecture; - long startOffset = architecture.GetOffset(instruction); - - // Wrap the instruction into an expression. - var expression = Expression.Instruction(instruction); - expression.OriginalRange = new AddressRange( - startOffset, - startOffset + architecture.GetSize(instruction) - ); - - // Determine the arguments. - int popCount = architecture.GetStackPopCount(instruction); - if (popCount == -1) - { - while (stack.Count > 0) - node.Transformed.Contents.Instructions.Add(stack.Pop().ToStatement()); - } - else - { - for (int i = 0; i < popCount; i++) - { - var argument = Pop(node, stack); - expression.Arguments.Insert(0, argument); - if (argument.OriginalRange is not null) - expression.OriginalRange = expression.OriginalRange.Value.Expand(argument.OriginalRange.Value); - } - } - - // Determine the produced values. - switch (architecture.GetStackPushCount(instruction)) - { - case 0: - // No value produced means we are dealing with a new independent statement. - - if ((architecture.GetFlowControl(instruction) & (InstructionFlowControl.CanBranch | InstructionFlowControl.IsTerminator)) != 0) - { - // If we are the final terminator or branch instruction at the end of the block, we need to flush - // any remaining values on the stack *before* the instruction statement to ensure the operations - // are evaluated before jumping to the next block. - FlushStackAsOutput(node, stack); - } - else - { - // For any other case we may still need to flush the stack if the expression is potentially impure - // and the stack contains potentially impure items, to preserve order of impure operations. - FlushStackIfImpure(node, stack, expression); - } - - // Wrap the expression into an independent statement and add it. - node.Transformed.Contents.Instructions.Add(expression.ToStatement()); - break; - - case 1: - // There is one value produced, push it on the local stack. - stack.Push(expression); - break; - - case var pushCount: - // Multiple values are produced, move them into separate variables and push them on eval stack. - - // Ensure order of operations is preserved if expression is potentially impure. - FlushStackIfImpure(node, stack, expression); - - // Declare new intermediate variables. - var variables = new IVariable[pushCount]; - for (int i = 0; i < pushCount; i++) - variables[i] = node.DeclareStackIntermediate(); - - // Add the assignment statement. - node.Transformed.Contents.Instructions.Add(Statement.Assignment(variables, expression)); - - // Push the intermediate variables. - foreach (var variable in variables) - stack.Push(variable.ToExpression()); - - break; - } - } - - private static Expression Pop(LiftedNode node, Stack> stack) - { - // If there is something on the stack, we can pop it. Otherwise it is a stack-input for this basic block. - if (stack.Count != 0) - return stack.Pop(); - - var variable = node.DeclareStackInput(); - var expression = variable.ToExpression(); - node.StackInputReferences.Add(variable, expression); - return expression; - } - - private static void FlushStackAsOutput(LiftedNode node, Stack> stack) + /// The provided control flow graph to lift. + /// The object responsible for determining whether an instruction is pure or not. + /// The type of instructions stored in the input graph. + /// The lifted graph. + public static ControlFlowGraph> ToAst( + this ControlFlowGraph cfg, + IPurityClassifier classifier) { - FlushStackInternal(node, stack, static (n, value) => - { - if (value is VariableExpression {Variable: SyntheticVariable variable}) - { - // If this output expression is already variable expression, we do not need to allocate a new variable - // and can instead just promote the variable to a stack output variable (inlining). - n.StackOutputs.Add(variable); - } - else - { - // Otherwise, declare and assign the value to a new stack output variable. - variable = n.DeclareStackOutput(); - n.Transformed.Contents.Instructions.Add(Statement.Assignment(variable, value)); - } - - return variable; - }); + return ControlFlowGraphLifter.Lift(cfg, classifier); } - private static void FlushStackAndPush(LiftedNode node, Stack> stack) + public static CompilationUnit ToCompilationUnit( + this ControlFlowGraph self, + IPurityClassifier classifier) { - var variables = FlushStackInternal(node, stack, static (n, value) => - { - var intermediate = n.DeclareStackIntermediate(); - n.Transformed.Contents.Instructions.Add(Statement.Assignment(intermediate, value)); - return intermediate; - }); - - for (int i = 0; i < variables.Count; i++) - stack.Push(variables[i].ToExpression()); + return new CompilationUnitBuilder().Construct(self.ToAst(classifier)); } - private void FlushStackIfImpure( - LiftedNode node, - Stack> stack, - Expression expression) + public static CompilationUnit ToCompilationUnit( + this ControlFlowGraph> self) { - // Is this expression pure with 100% certainty? - if (expression.IsPure(_purityClassifier).ToBooleanOrFalse()) - return; - - // Does the stack contain potentially impure expressions? - // We then still need to flush to preserve order of operations. - bool fullyPureStack = true; - foreach (var value in stack) - { - if (!value.IsPure(_purityClassifier).ToBooleanOrFalse()) - { - fullyPureStack = false; - break; - } - } - - if (!fullyPureStack) - FlushStackAndPush(node, stack); + return new CompilationUnitBuilder().Construct(self); } - private static IList FlushStackInternal( - LiftedNode node, - Stack> stack, - Func, Expression, SyntheticVariable> flush) + public static CompilationUnit ToCompilationUnit( + this ScopeBlock> self) { - // Collect all values from the stack. - var values = new Expression[stack.Count]; - for (int i = values.Length - 1; i >= 0; i--) - values[i] = stack.Pop(); - - // Flush them to variables. - var variables = new IVariable[values.Length]; - for (int i = 0; i < values.Length; i++) - variables[i] = flush(node, values[i]); - - return variables; + return new CompilationUnitBuilder().Construct(self); } - - private void PopulatePhiStatements() - { - var recordedStates = new Dictionary, StackState>(); - - var agenda = new Queue>(); - agenda.Enqueue(new StackState(_original.EntryPoint)); - - while (agenda.Count > 0) - { - var currentState = agenda.Dequeue(); - var liftedNode = _liftedNodes[currentState.Node]; - - if (!recordedStates.TryGetValue(liftedNode, out var previousState)) - { - // We have never visited this block before. Register the new state. - recordedStates[liftedNode] = currentState; - } - else if (previousState.MergeWith(currentState, out var mergedState)) - { - // Merging the states resulted in a change. We have to revisit this path. - currentState = mergedState; - recordedStates[liftedNode] = mergedState; - } - else - { - // We did not change anything to the recorded input states, so there is no need to recompute the PHI - // nodes nor any of its successors. - continue; - } - - // Consume stack values, and add them to the phi statements. - for (int i = liftedNode.StackInputs.Count - 1; i >= 0; i--) - { - var input = liftedNode.StackInputs[i]; - - // Protection against malformed code streams with stack underflow. - if (currentState.Stack.IsEmpty) - break; - - currentState = currentState.Pop(out var value); - foreach (var source in value.Sources) - { - if (!input.HasSource(source)) - input.Sources.Add(source.ToExpression()); - } - } - - // Push new values on stack. - foreach (var output in liftedNode.StackOutputs) - currentState = currentState.Push(new StackSlot(output)); - - // Schedule successors. - foreach (var successor in currentState.Node.GetSuccessors()) - agenda.Enqueue(currentState.MoveTo(successor)); - } - } - - private void InsertPhiStatements() - { - foreach (var block in _liftedNodes.Values) - { - for (int i = block.StackInputs.Count - 1; i >= 0; i--) - { - var input = block.StackInputs[i]; - if (input.Sources.Count == 1) - { - // Optimization: if there is one source only for the phi node, we can inline the input stack - // variable. Since an input stack slot is only consumed once, it thus only has one variable - // expression. Therefore, inlining is exactly one update of a variable expression. - - var singleSource = input.Sources[0]; - input.Sources.RemoveAt(0); - - if (block.StackInputReferences.TryGetValue(input.Representative, out var expression)) - expression.Variable = singleSource.Variable; - } - else - { - // Insert the phi node in front of the block. - block.Transformed.Contents.Instructions.Insert(0, input); - } - } - } - } - - private void AddEdges() - { - foreach (var lifted in _liftedNodes.Values) - { - if (lifted.Original.UnconditionalEdge is { } x) - lifted.Transformed.ConnectWith(_liftedNodes[x.Target].Transformed, x.Type); - - foreach (var edge in lifted.Original.ConditionalEdges) - lifted.Transformed.ConnectWith(_liftedNodes[edge.Target].Transformed, ControlFlowEdgeType.Conditional); - - foreach (var edge in lifted.Original.AbnormalEdges) - lifted.Transformed.ConnectWith(_liftedNodes[edge.Target].Transformed, ControlFlowEdgeType.Abnormal); - } - } - - private void TransformRegions() - { - _lifted.EntryPoint = _liftedNodes[_original.EntryPoint].Transformed; - foreach (var region in _original.Regions) - TransformRegion(x => _lifted.Regions.Add(x), region); - } - - private void TransformRegion( - Action>> addSection, - ControlFlowRegion region - ) - { - switch (region) - { - case ScopeRegion scopeRegion: - var newBasicRegion = new ScopeRegion>(); - addSection(newBasicRegion); - - TransformScope(scopeRegion, newBasicRegion); - break; - - case ExceptionHandlerRegion ehRegion: - var newEhRegion = new ExceptionHandlerRegion>(); - addSection(newEhRegion); - - TransformScope(ehRegion.ProtectedRegion, newEhRegion.ProtectedRegion); - foreach (var subRegion in ehRegion.Handlers) - TransformHandlerRegion(newEhRegion, subRegion); - - break; - - default: - throw new ArgumentOutOfRangeException(nameof(region)); - } - } - - private void TransformScope(ScopeRegion scopeRegion, ScopeRegion> newScopeRegion) - { - // Add the lifted nodes within the current scope. - foreach (var node in scopeRegion.Nodes) - newScopeRegion.Nodes.Add(_liftedNodes[node].Transformed); - - // Set up entry point. - newScopeRegion.EntryPoint = _liftedNodes[scopeRegion.EntryPoint].Transformed; - - // Recursively traverse the region tree. - TransformSubRegions(scopeRegion, newScopeRegion); - } - - private void TransformHandlerRegion( - ExceptionHandlerRegion> parentRegion, - HandlerRegion handlerRegion - ) - { - var result = new HandlerRegion>(); - parentRegion.Handlers.Add(result); - - if (handlerRegion.Prologue is not null) - TransformRegion(x => result.Prologue = (ScopeRegion>) x, handlerRegion.Prologue); - - if (handlerRegion.Epilogue is not null) - TransformRegion(x => result.Epilogue = (ScopeRegion>) x, handlerRegion.Epilogue); - - TransformScope(handlerRegion.Contents, result.Contents); - - result.Tag = handlerRegion.Tag; - } - - private void TransformSubRegions( - ScopeRegion originalRegion, - ScopeRegion> newRegion - ) - { - foreach (var subRegion in originalRegion.Regions) - TransformRegion(x => newRegion.Regions.Add(x), subRegion); - } - + } \ No newline at end of file diff --git a/src/Core/Echo.Ast/Construction/AstBuilderExtensions.cs b/src/Core/Echo.Ast/Construction/AstBuilderExtensions.cs deleted file mode 100644 index da833c99..00000000 --- a/src/Core/Echo.Ast/Construction/AstBuilderExtensions.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Echo.Code; -using Echo.ControlFlow; - -namespace Echo.Ast.Construction; - -/// -/// Provides utility extensions for the construction of new AST control flow graphs. -/// -public static class AstBuilderExtensions -{ - /// - /// Lifts the instructions in every node of the provided control flow graph to expressions and statements. - /// - /// The provided control flow graph to lift. - /// The object responsible for determining whether an instruction is pure or not. - /// The type of instructions stored in the input graph. - /// The lifted graph. - public static ControlFlowGraph> ToAst( - this ControlFlowGraph cfg, - IPurityClassifier classifier) - { - return AstBuilder.Lift(cfg, classifier); - } -} \ No newline at end of file diff --git a/src/Core/Echo.Ast/Construction/CompilationUnitBuilder.cs b/src/Core/Echo.Ast/Construction/CompilationUnitBuilder.cs new file mode 100644 index 00000000..dba544cf --- /dev/null +++ b/src/Core/Echo.Ast/Construction/CompilationUnitBuilder.cs @@ -0,0 +1,82 @@ +using Echo.ControlFlow; +using Echo.ControlFlow.Blocks; +using Echo.ControlFlow.Serialization.Blocks; + +namespace Echo.Ast.Construction; + +/// +/// Provides a mechanism for constructing compilation unit from a lifted control flow graphs. +/// +/// The type of instructions stored in the AST. +public class CompilationUnitBuilder : IBlockVisitor, object?, AstNode> +{ + /// + /// Constructs a compilation unit based on a lifted control flow graph. + /// + /// The control flow graph to lift. + /// The constructed compilation unit. + public CompilationUnit Construct(ControlFlowGraph> cfg) + { + return Construct(cfg.ConstructBlocks()); + } + + /// + /// Constructs a compilation unit based on a lifted block tree. + /// + /// The block tree to lift. + /// The constructed compilation unit. + public CompilationUnit Construct(ScopeBlock> root) => new() + { + Root = (BlockStatement) root.AcceptVisitor(this, null) + }; + + /// + public AstNode VisitBasicBlock(BasicBlock> block, object? state) + { + var result = new BlockStatement(); + + foreach (var statement in block.Instructions) + result.Statements.Add(statement); + + return result; + } + + /// + public AstNode VisitScopeBlock(ScopeBlock> block, object? state) + { + var result = new BlockStatement(); + + foreach (var b in block.Blocks) + result.Statements.Add((Statement) b.AcceptVisitor(this, state)); + + return result; + } + + /// + public AstNode VisitExceptionHandlerBlock(ExceptionHandlerBlock> block, object? state) + { + var result = new ExceptionHandlerStatement(); + + result.ProtectedBlock = (BlockStatement) block.ProtectedBlock.AcceptVisitor(this, state); + foreach (var handler in block.Handlers) + result.Handlers.Add((HandlerClause) handler.AcceptVisitor(this, state)); + + return result; + } + + /// + public AstNode VisitHandlerBlock(HandlerBlock> block, object? state) + { + var result = new HandlerClause(); + + if (block.Prologue is not null) + result.Prologue = (BlockStatement?) block.Prologue.AcceptVisitor(this, state); + + result.Contents = (BlockStatement) block.Contents.AcceptVisitor(this, state); + + if (block.Epilogue is not null) + result.Epilogue = (BlockStatement?) block.Epilogue.AcceptVisitor(this, state); + + return result; + } +} \ No newline at end of file diff --git a/src/Core/Echo.Ast/Construction/ControlFlowGraphLifter.cs b/src/Core/Echo.Ast/Construction/ControlFlowGraphLifter.cs new file mode 100644 index 00000000..006d67aa --- /dev/null +++ b/src/Core/Echo.Ast/Construction/ControlFlowGraphLifter.cs @@ -0,0 +1,440 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Echo.Code; +using Echo.ControlFlow; +using Echo.ControlFlow.Regions; + +namespace Echo.Ast.Construction; + +/// +/// Lifts every node in a control flow graph to its AST representation. +/// +public sealed class ControlFlowGraphLifter +{ + private readonly ControlFlowGraph _original; + private readonly ControlFlowGraph> _lifted; + private readonly IPurityClassifier _purityClassifier; + private readonly Dictionary, LiftedNode> _liftedNodes = new(); + + private ControlFlowGraphLifter(ControlFlowGraph original, IPurityClassifier purityClassifier) + { + _original = original; + _purityClassifier = purityClassifier; + _lifted = new ControlFlowGraph>(original.Architecture.ToAst()); + } + + /// + /// Transforms a control flow graph by lifting each basic block into an AST representation. + /// + /// The control flow graph. + /// A classifier used for determining whether expressions are pure or not. + /// The transformed control flow graph. + public static ControlFlowGraph> Lift( + ControlFlowGraph cfg, + IPurityClassifier purityClassifier) + { + var builder = new ControlFlowGraphLifter(cfg, purityClassifier); + builder.Run(); + return builder._lifted; + } + + private void Run() + { + // Strategy: + // + // We lift each node in the control flow graph in isolation. Control flow nodes have the property that they + // are executed as a single unit, and as such, within a single node we can assume an isolated eval stack. + // This avoids full data flow analysis (i.e., building a full DFG) to build ASTs, at the cost of a slight + // chance of overproducing some synthetic variables to communicate non-zero stack deltas between cfg nodes. + // In practice however this is much rarer to occur than not: Typically a block has a stack delta of 0. + // + // We only use SSA form and PHI nodes for synthetic stack variables. This is because many languages/platforms + // allow for variables to be accessed by reference and thus it is not clear just from the access alone whether + // it is just a read or also a write. We leave this decision up to the consumer of this API. + + LiftNodes(); + PopulatePhiStatements(); + InsertPhiStatements(); + AddEdges(); + TransformRegions(); + } + + private void LiftNodes() + { + foreach (var node in _original.Nodes) + { + var liftedNode = LiftNode(node); + _liftedNodes.Add(node, liftedNode); + _lifted.Nodes.Add(liftedNode.Transformed); + } + } + + private LiftedNode LiftNode(ControlFlowNode node) + { + var result = new LiftedNode(node); + + // For each node we keep track of a local stack. + var stack = new Stack>(); + + // Lift every instruction, processing stack states. + for (int i = 0; i < node.Contents.Instructions.Count; i++) + LiftInstruction(result, node.Contents.Instructions[i], stack); + + // Any values left on the stack we move into synthetic out variables. + FlushStackAsOutput(result, stack); + + return result; + } + + private void LiftInstruction( + LiftedNode node, + in TInstruction instruction, + Stack> stack) + { + var architecture = _original.Architecture; + long startOffset = architecture.GetOffset(instruction); + + // Wrap the instruction into an expression. + var expression = Expression.Instruction(instruction); + expression.OriginalRange = new AddressRange( + startOffset, + startOffset + architecture.GetSize(instruction) + ); + + // Determine the arguments. + int popCount = architecture.GetStackPopCount(instruction); + if (popCount == -1) + { + while (stack.Count > 0) + node.Transformed.Contents.Instructions.Add(stack.Pop().ToStatement()); + } + else + { + for (int i = 0; i < popCount; i++) + { + var argument = Pop(node, stack); + expression.Arguments.Insert(0, argument); + if (argument.OriginalRange is not null) + expression.OriginalRange = expression.OriginalRange.Value.Expand(argument.OriginalRange.Value); + } + } + + // Determine the produced values. + switch (architecture.GetStackPushCount(instruction)) + { + case 0: + // No value produced means we are dealing with a new independent statement. + + if ((architecture.GetFlowControl(instruction) & (InstructionFlowControl.CanBranch | InstructionFlowControl.IsTerminator)) != 0) + { + // If we are the final terminator or branch instruction at the end of the block, we need to flush + // any remaining values on the stack *before* the instruction statement to ensure the operations + // are evaluated before jumping to the next block. + FlushStackAsOutput(node, stack); + } + else + { + // For any other case we may still need to flush the stack if the expression is potentially impure + // and the stack contains potentially impure items, to preserve order of impure operations. + FlushStackIfImpure(node, stack, expression); + } + + // Wrap the expression into an independent statement and add it. + node.Transformed.Contents.Instructions.Add(expression.ToStatement()); + break; + + case 1: + // There is one value produced, push it on the local stack. + stack.Push(expression); + break; + + case var pushCount: + // Multiple values are produced, move them into separate variables and push them on eval stack. + + // Ensure order of operations is preserved if expression is potentially impure. + FlushStackIfImpure(node, stack, expression); + + // Declare new intermediate variables. + var variables = new IVariable[pushCount]; + for (int i = 0; i < pushCount; i++) + variables[i] = node.DeclareStackIntermediate(); + + // Add the assignment statement. + node.Transformed.Contents.Instructions.Add(Statement.Assignment(variables, expression)); + + // Push the intermediate variables. + foreach (var variable in variables) + stack.Push(variable.ToExpression()); + + break; + } + } + + private static Expression Pop(LiftedNode node, Stack> stack) + { + // If there is something on the stack, we can pop it. Otherwise it is a stack-input for this basic block. + if (stack.Count != 0) + return stack.Pop(); + + var variable = node.DeclareStackInput(); + var expression = variable.ToExpression(); + node.StackInputReferences.Add(variable, expression); + return expression; + } + + private static void FlushStackAsOutput(LiftedNode node, Stack> stack) + { + FlushStackInternal(node, stack, static (n, value) => + { + if (value is VariableExpression {Variable: SyntheticVariable variable}) + { + // If this output expression is already variable expression, we do not need to allocate a new variable + // and can instead just promote the variable to a stack output variable (inlining). + n.StackOutputs.Add(variable); + } + else + { + // Otherwise, declare and assign the value to a new stack output variable. + variable = n.DeclareStackOutput(); + n.Transformed.Contents.Instructions.Add(Statement.Assignment(variable, value)); + } + + return variable; + }); + } + + private static void FlushStackAndPush(LiftedNode node, Stack> stack) + { + var variables = FlushStackInternal(node, stack, static (n, value) => + { + var intermediate = n.DeclareStackIntermediate(); + n.Transformed.Contents.Instructions.Add(Statement.Assignment(intermediate, value)); + return intermediate; + }); + + for (int i = 0; i < variables.Count; i++) + stack.Push(variables[i].ToExpression()); + } + + private void FlushStackIfImpure( + LiftedNode node, + Stack> stack, + Expression expression) + { + // Is this expression pure with 100% certainty? + if (expression.IsPure(_purityClassifier).ToBooleanOrFalse()) + return; + + // Does the stack contain potentially impure expressions? + // We then still need to flush to preserve order of operations. + bool fullyPureStack = true; + foreach (var value in stack) + { + if (!value.IsPure(_purityClassifier).ToBooleanOrFalse()) + { + fullyPureStack = false; + break; + } + } + + if (!fullyPureStack) + FlushStackAndPush(node, stack); + } + + private static IList FlushStackInternal( + LiftedNode node, + Stack> stack, + Func, Expression, SyntheticVariable> flush) + { + // Collect all values from the stack. + var values = new Expression[stack.Count]; + for (int i = values.Length - 1; i >= 0; i--) + values[i] = stack.Pop(); + + // Flush them to variables. + var variables = new IVariable[values.Length]; + for (int i = 0; i < values.Length; i++) + variables[i] = flush(node, values[i]); + + return variables; + } + + private void PopulatePhiStatements() + { + var recordedStates = new Dictionary, StackState>(); + + var agenda = new Queue>(); + agenda.Enqueue(new StackState(_original.EntryPoint)); + + while (agenda.Count > 0) + { + var currentState = agenda.Dequeue(); + var liftedNode = _liftedNodes[currentState.Node]; + + if (!recordedStates.TryGetValue(liftedNode, out var previousState)) + { + // We have never visited this block before. Register the new state. + recordedStates[liftedNode] = currentState; + } + else if (previousState.MergeWith(currentState, out var mergedState)) + { + // Merging the states resulted in a change. We have to revisit this path. + currentState = mergedState; + recordedStates[liftedNode] = mergedState; + } + else + { + // We did not change anything to the recorded input states, so there is no need to recompute the PHI + // nodes nor any of its successors. + continue; + } + + // Consume stack values, and add them to the phi statements. + for (int i = liftedNode.StackInputs.Count - 1; i >= 0; i--) + { + var input = liftedNode.StackInputs[i]; + + // Protection against malformed code streams with stack underflow. + if (currentState.Stack.IsEmpty) + break; + + currentState = currentState.Pop(out var value); + foreach (var source in value.Sources) + { + if (!input.HasSource(source)) + input.Sources.Add(source.ToExpression()); + } + } + + // Push new values on stack. + foreach (var output in liftedNode.StackOutputs) + currentState = currentState.Push(new StackSlot(output)); + + // Schedule successors. + foreach (var successor in currentState.Node.GetSuccessors()) + agenda.Enqueue(currentState.MoveTo(successor)); + } + } + + private void InsertPhiStatements() + { + foreach (var block in _liftedNodes.Values) + { + for (int i = block.StackInputs.Count - 1; i >= 0; i--) + { + var input = block.StackInputs[i]; + if (input.Sources.Count == 1) + { + // Optimization: if there is one source only for the phi node, we can inline the input stack + // variable. Since an input stack slot is only consumed once, it thus only has one variable + // expression. Therefore, inlining is exactly one update of a variable expression. + + var singleSource = input.Sources[0]; + input.Sources.RemoveAt(0); + + if (block.StackInputReferences.TryGetValue(input.Representative, out var expression)) + expression.Variable = singleSource.Variable; + } + else + { + // Insert the phi node in front of the block. + block.Transformed.Contents.Instructions.Insert(0, input); + } + } + } + } + + private void AddEdges() + { + foreach (var lifted in _liftedNodes.Values) + { + if (lifted.Original.UnconditionalEdge is { } x) + lifted.Transformed.ConnectWith(_liftedNodes[x.Target].Transformed, x.Type); + + foreach (var edge in lifted.Original.ConditionalEdges) + lifted.Transformed.ConnectWith(_liftedNodes[edge.Target].Transformed, ControlFlowEdgeType.Conditional); + + foreach (var edge in lifted.Original.AbnormalEdges) + lifted.Transformed.ConnectWith(_liftedNodes[edge.Target].Transformed, ControlFlowEdgeType.Abnormal); + } + } + + private void TransformRegions() + { + _lifted.EntryPoint = _liftedNodes[_original.EntryPoint].Transformed; + foreach (var region in _original.Regions) + TransformRegion(x => _lifted.Regions.Add(x), region); + } + + private void TransformRegion( + Action>> addSection, + ControlFlowRegion region + ) + { + switch (region) + { + case ScopeRegion scopeRegion: + var newBasicRegion = new ScopeRegion>(); + addSection(newBasicRegion); + + TransformScope(scopeRegion, newBasicRegion); + break; + + case ExceptionHandlerRegion ehRegion: + var newEhRegion = new ExceptionHandlerRegion>(); + addSection(newEhRegion); + + TransformScope(ehRegion.ProtectedRegion, newEhRegion.ProtectedRegion); + foreach (var subRegion in ehRegion.Handlers) + TransformHandlerRegion(newEhRegion, subRegion); + + break; + + default: + throw new ArgumentOutOfRangeException(nameof(region)); + } + } + + private void TransformScope(ScopeRegion scopeRegion, ScopeRegion> newScopeRegion) + { + // Add the lifted nodes within the current scope. + foreach (var node in scopeRegion.Nodes) + newScopeRegion.Nodes.Add(_liftedNodes[node].Transformed); + + // Set up entry point. + newScopeRegion.EntryPoint = _liftedNodes[scopeRegion.EntryPoint].Transformed; + + // Recursively traverse the region tree. + TransformSubRegions(scopeRegion, newScopeRegion); + } + + private void TransformHandlerRegion( + ExceptionHandlerRegion> parentRegion, + HandlerRegion handlerRegion + ) + { + var result = new HandlerRegion>(); + parentRegion.Handlers.Add(result); + + if (handlerRegion.Prologue is not null) + TransformRegion(x => result.Prologue = (ScopeRegion>) x, handlerRegion.Prologue); + + if (handlerRegion.Epilogue is not null) + TransformRegion(x => result.Epilogue = (ScopeRegion>) x, handlerRegion.Epilogue); + + TransformScope(handlerRegion.Contents, result.Contents); + + result.Tag = handlerRegion.Tag; + } + + private void TransformSubRegions( + ScopeRegion originalRegion, + ScopeRegion> newRegion + ) + { + foreach (var subRegion in originalRegion.Regions) + TransformRegion(x => newRegion.Regions.Add(x), subRegion); + } + +} \ No newline at end of file diff --git a/src/Core/Echo.Ast/ExceptionHandlerStatement.cs b/src/Core/Echo.Ast/ExceptionHandlerStatement.cs index c3540e32..11ef343d 100644 --- a/src/Core/Echo.Ast/ExceptionHandlerStatement.cs +++ b/src/Core/Echo.Ast/ExceptionHandlerStatement.cs @@ -26,7 +26,7 @@ public ExceptionHandlerStatement() public BlockStatement ProtectedBlock { get => _protectedBlock; - private set => UpdateChildNotNull(ref _protectedBlock, value); + internal set => UpdateChildNotNull(ref _protectedBlock, value); } /// diff --git a/src/Core/Echo.Ast/HandlerClause.cs b/src/Core/Echo.Ast/HandlerClause.cs index ac72a44d..f28ecda8 100644 --- a/src/Core/Echo.Ast/HandlerClause.cs +++ b/src/Core/Echo.Ast/HandlerClause.cs @@ -45,7 +45,7 @@ public BlockStatement? Prologue public BlockStatement Contents { get => _contents; - private set => UpdateChildNotNull(ref _contents, value); + internal set => UpdateChildNotNull(ref _contents, value); } /// diff --git a/test/Core/Echo.Ast.Tests/Construction/CompilationUnitBuilderTest.cs b/test/Core/Echo.Ast.Tests/Construction/CompilationUnitBuilderTest.cs new file mode 100644 index 00000000..6e4dd159 --- /dev/null +++ b/test/Core/Echo.Ast.Tests/Construction/CompilationUnitBuilderTest.cs @@ -0,0 +1,94 @@ +using Echo.Ast.Construction; +using Echo.ControlFlow.Blocks; +using Echo.Platforms.DummyPlatform.Code; +using Xunit; + +namespace Echo.Ast.Tests.Construction; + +public class CompilationUnitBuilderTest +{ + [Fact] + public void SingleBlock() + { + // Prepare + var root = new ScopeBlock> + { + Blocks = + { + new BasicBlock> + { + Instructions = {Statement.Expression(Expression.Instruction(DummyInstruction.Ret(0)))} + } + } + }; + + // Construct + var unit = root.ToCompilationUnit(); + + // Verify + var block = Assert.IsAssignableFrom>(Assert.Single(unit.Root.Statements)); + Assert.IsAssignableFrom>(Assert.Single(block.Statements)); + } + + [Fact] + public void TwoBlocks() + { + // Prepare + var root = new ScopeBlock> + { + Blocks = + { + new BasicBlock> + { + Instructions = {Statement.Expression(Expression.Instruction(DummyInstruction.Jmp(0, 1)))} + }, + new BasicBlock> + { + Instructions = {Statement.Expression(Expression.Instruction(DummyInstruction.Ret(1)))} + } + } + }; + + // Construct + var unit = root.ToCompilationUnit(); + + // Verify + Assert.Equal(2, unit.Root.Statements.Count); + var block1 = Assert.IsAssignableFrom>(unit.Root.Statements[0]); + var block2 = Assert.IsAssignableFrom>(unit.Root.Statements[1]); + Assert.IsAssignableFrom>(Assert.Single(block1.Statements)); + Assert.IsAssignableFrom>(Assert.Single(block2.Statements)); + } + + [Fact] + public void ExceptionHandler() + { + // Prepare + var root = new ScopeBlock>(); + var eh = new ExceptionHandlerBlock>(); + eh.ProtectedBlock.Blocks.Add(new BasicBlock> + { + Instructions = {Statement.Expression(Expression.Instruction(DummyInstruction.Jmp(0, 2)))} + }); + var handler = new HandlerBlock>(); + handler.Contents.Blocks.Add(new BasicBlock> + { + Instructions = {Statement.Expression(Expression.Instruction(DummyInstruction.Jmp(1, 2)))} + }); + eh.Handlers.Add(handler); + root.Blocks.Add(eh); + root.Blocks.Add(new BasicBlock> + { + Instructions = {Statement.Expression(Expression.Instruction(DummyInstruction.Ret(2)))} + }); + + // Construct + var unit = root.ToCompilationUnit(); + + Assert.Equal(2, unit.Root.Statements.Count); + var ehBlock = Assert.IsAssignableFrom>(unit.Root.Statements[0]); + Assert.Single(ehBlock.ProtectedBlock.Statements); + Assert.Single(ehBlock.Handlers); + Assert.IsAssignableFrom>(unit.Root.Statements[1]); + } +} \ No newline at end of file diff --git a/test/Core/Echo.Ast.Tests/Construction/AstBuilderTest.cs b/test/Core/Echo.Ast.Tests/Construction/ControlFlowGraphLifterTest.cs similarity index 99% rename from test/Core/Echo.Ast.Tests/Construction/AstBuilderTest.cs rename to test/Core/Echo.Ast.Tests/Construction/ControlFlowGraphLifterTest.cs index 50bfd220..7ac7bc02 100644 --- a/test/Core/Echo.Ast.Tests/Construction/AstBuilderTest.cs +++ b/test/Core/Echo.Ast.Tests/Construction/ControlFlowGraphLifterTest.cs @@ -16,7 +16,7 @@ namespace Echo.Ast.Tests.Construction; -public class AstBuilderTest +public class ControlFlowGraphLifterTest { private static ControlFlowGraph> ConstructGraph( IList instructions, From e6c2f8affbd7c402610403138a67d64572462fed Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 1 Feb 2024 21:06:21 +0100 Subject: [PATCH 26/33] Add variable cross-referencing. --- src/Core/Echo.Ast/AssignmentStatement.cs | 16 +++ src/Core/Echo.Ast/AstNode.cs | 44 ++++++- src/Core/Echo.Ast/AstVariableExtensions.cs | 36 ++++++ src/Core/Echo.Ast/BlockStatement.cs | 14 +++ src/Core/Echo.Ast/CompilationUnit.cs | 82 ++++++++++++- .../Construction/CompilationUnitBuilder.cs | 2 + .../Echo.Ast/ExceptionHandlerStatement.cs | 18 ++- src/Core/Echo.Ast/ExpressionStatement.cs | 6 + src/Core/Echo.Ast/HandlerClause.cs | 18 ++- src/Core/Echo.Ast/InstructionExpression.cs | 14 +++ src/Core/Echo.Ast/PhiStatement.cs | 16 +++ src/Core/Echo.Ast/VariableExpression.cs | 32 ++++- .../Echo.Ast.Tests/CompilationUnitTest.cs | 112 ++++++++++++++++++ 13 files changed, 404 insertions(+), 6 deletions(-) create mode 100644 src/Core/Echo.Ast/AstVariableExtensions.cs create mode 100644 test/Core/Echo.Ast.Tests/CompilationUnitTest.cs diff --git a/src/Core/Echo.Ast/AssignmentStatement.cs b/src/Core/Echo.Ast/AssignmentStatement.cs index fa51d3a8..f059b128 100644 --- a/src/Core/Echo.Ast/AssignmentStatement.cs +++ b/src/Core/Echo.Ast/AssignmentStatement.cs @@ -57,6 +57,22 @@ public override IEnumerable GetChildren() yield return Expression; } + /// + protected internal override void OnAttach(CompilationUnit newRoot) + { + for (int index = 0; index < Variables.Count; index++) + newRoot.RegisterVariableWrite(Variables[index], this); + Expression.OnAttach(newRoot); + } + + /// + protected internal override void OnDetach(CompilationUnit oldRoot) + { + for (int index = 0; index < Variables.Count; index++) + oldRoot.UnregisterVariableWrite(Variables[index], this); + Expression.OnDetach(oldRoot); + } + /// public override void Accept(IAstNodeVisitor visitor) => visitor.Visit(this); diff --git a/src/Core/Echo.Ast/AstNode.cs b/src/Core/Echo.Ast/AstNode.cs index 891ed4f7..3789c9d8 100644 --- a/src/Core/Echo.Ast/AstNode.cs +++ b/src/Core/Echo.Ast/AstNode.cs @@ -1,4 +1,3 @@ -using System.Text; using Echo.Code; using Echo.Graphing; @@ -23,6 +22,18 @@ public AddressRange? OriginalRange get; set; } + + /// + /// Obtains the parent compilation unit the AST is added to (if available). + /// + /// The compilation unit, or null if the node is detached from any compilation unit. + public CompilationUnit? GetParentCompilationUnit() + { + var current = this; + while (current is not CompilationUnit unit && current.Parent is not null) + current = current.Parent; + return current as CompilationUnit; + } /// /// Determines whether the AST node consists of only pure expressions that do not affect state. @@ -36,6 +47,37 @@ public Trilean IsPure(IPurityClassifier classifier) { return Accept(Analysis.AstPurityVisitor.Instance, classifier); } + + /// + /// Called when the node was added to a root compilation unit. + /// + /// The new root compilation unit of the node. + protected internal abstract void OnAttach(CompilationUnit newRoot); + + + /// + /// Called when the node was removed from a root compilation unit. + /// + /// The old root compilation unit the node was removed from. + protected internal abstract void OnDetach(CompilationUnit oldRoot); + + /// + protected override void OnParentChanged(TreeNodeBase? old) + { + base.OnParentChanged(old); + + var oldRoot = (old as AstNode)?.GetParentCompilationUnit(); + var newRoot = GetParentCompilationUnit(); + + if (oldRoot != newRoot) + { + if (oldRoot is not null) + OnDetach(oldRoot); + + if (newRoot is not null) + OnAttach(newRoot); + } + } /// /// Implements the visitor pattern diff --git a/src/Core/Echo.Ast/AstVariableExtensions.cs b/src/Core/Echo.Ast/AstVariableExtensions.cs new file mode 100644 index 00000000..5b957088 --- /dev/null +++ b/src/Core/Echo.Ast/AstVariableExtensions.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using Echo.Code; + +namespace Echo.Ast; + +/// +/// Provides a mechanism for cross-referencing variables in a compilation unit. +/// +public static class AstVariableExtensions +{ + /// + /// Gets all expressions in the compilation unit that reference the provided variable. + /// + /// The variable to cross-reference. + /// The compilation unit to cross-reference in. + /// The expressions referencing the variable. + public static IEnumerable> GetIsUsedBy( + this IVariable self, + CompilationUnit unit) + { + return unit.GetVariableUses(self); + } + + /// + /// Gets all statements in the compilation unit that write to the provided variable. + /// + /// The variable to cross-reference. + /// The compilation unit to cross-reference in. + /// The statements writing to the variable. + public static IEnumerable> GetIsWrittenBy( + this IVariable self, + CompilationUnit unit) + { + return unit.GetVariableWrites(self); + } +} \ No newline at end of file diff --git a/src/Core/Echo.Ast/BlockStatement.cs b/src/Core/Echo.Ast/BlockStatement.cs index 7898b1e7..eb69246a 100644 --- a/src/Core/Echo.Ast/BlockStatement.cs +++ b/src/Core/Echo.Ast/BlockStatement.cs @@ -28,6 +28,20 @@ public IList> Statements /// public override IEnumerable GetChildren() => Statements; + /// + protected internal override void OnAttach(CompilationUnit newRoot) + { + for (int i = 0; i < Statements.Count; i++) + Statements[i].OnAttach(newRoot); + } + + /// + protected internal override void OnDetach(CompilationUnit oldRoot) + { + for (int i = 0; i < Statements.Count; i++) + Statements[i].OnDetach(oldRoot); + } + /// public override void Accept(IAstNodeVisitor visitor) => visitor.Visit(this); diff --git a/src/Core/Echo.Ast/CompilationUnit.cs b/src/Core/Echo.Ast/CompilationUnit.cs index 44568d1b..041f203a 100644 --- a/src/Core/Echo.Ast/CompilationUnit.cs +++ b/src/Core/Echo.Ast/CompilationUnit.cs @@ -1,4 +1,7 @@ +using System; using System.Collections.Generic; +using System.Linq; +using Echo.Code; using Echo.Graphing; namespace Echo.Ast; @@ -9,6 +12,9 @@ namespace Echo.Ast; /// The type of instructions to store in the AST. public class CompilationUnit : AstNode { + private readonly Dictionary>> _variableUses = new(); + private readonly Dictionary>> _variableWrites = new(); + private BlockStatement _root = null!; /// @@ -28,9 +34,83 @@ public BlockStatement Root internal set => UpdateChildNotNull(ref _root, value); } + /// + /// Gets all expressions in the compilation unit that reference the provided variable. + /// + /// The variable to cross-reference. + /// The expressions referencing the variable. + public IEnumerable> GetVariableUses(IVariable variable) + { + return !_variableUses.TryGetValue(variable, out var uses) + ? Enumerable.Empty>() + : uses; + } + + /// + /// Gets all statements in the compilation unit that write to the provided variable. + /// + /// The variable to cross-reference. + /// The statements writing to the variable. + public IEnumerable> GetVariableWrites(IVariable variable) + { + return !_variableWrites.TryGetValue(variable, out var writes) + ? Enumerable.Empty>() + : writes; + } + + internal void RegisterVariableUse(VariableExpression expression) + { + if (!_variableUses.TryGetValue(expression.Variable, out var uses)) + { + uses = new List>(); + _variableUses.Add(expression.Variable, uses); + } + + uses.Add(expression); + } + + internal void UnregisterVariableUse(VariableExpression expression) + { + if (_variableUses.TryGetValue(expression.Variable, out var uses)) + { + uses.Remove(expression); + if (uses.Count == 0) + _variableUses.Remove(expression.Variable); + } + } + + internal void RegisterVariableWrite(IVariable variable, Statement statement) + { + if (!_variableWrites.TryGetValue(variable, out var writes)) + { + writes = new List>(); + _variableWrites.Add(variable, writes); + } + + writes.Add(statement); + } + + internal void UnregisterVariableWrite(IVariable variable, Statement statement) + { + if (_variableWrites.TryGetValue(variable, out var writes)) + { + writes.Remove(statement); + if (writes.Count == 0) + _variableUses.Remove(variable); + } + } + /// public override IEnumerable GetChildren() => new[] {Root}; - + + /// + protected internal override void OnAttach(CompilationUnit newRoot) => + throw new InvalidOperationException(); + + /// + protected internal override void OnDetach(CompilationUnit oldRoot) => + throw new InvalidOperationException(); + /// public override void Accept(IAstNodeVisitor visitor) => visitor.Visit(this); diff --git a/src/Core/Echo.Ast/Construction/CompilationUnitBuilder.cs b/src/Core/Echo.Ast/Construction/CompilationUnitBuilder.cs index dba544cf..c2d29b5e 100644 --- a/src/Core/Echo.Ast/Construction/CompilationUnitBuilder.cs +++ b/src/Core/Echo.Ast/Construction/CompilationUnitBuilder.cs @@ -69,6 +69,8 @@ public AstNode VisitHandlerBlock(HandlerBlock(); + result.Tag = block.Tag; + if (block.Prologue is not null) result.Prologue = (BlockStatement?) block.Prologue.AcceptVisitor(this, state); diff --git a/src/Core/Echo.Ast/ExceptionHandlerStatement.cs b/src/Core/Echo.Ast/ExceptionHandlerStatement.cs index 11ef343d..1059ac9f 100644 --- a/src/Core/Echo.Ast/ExceptionHandlerStatement.cs +++ b/src/Core/Echo.Ast/ExceptionHandlerStatement.cs @@ -44,7 +44,23 @@ public override IEnumerable GetChildren() foreach (var handler in Handlers) yield return handler; } - + + /// + protected internal override void OnAttach(CompilationUnit newRoot) + { + ProtectedBlock.OnAttach(newRoot); + for (int i = 0; i < Handlers.Count; i++) + Handlers[i].OnAttach(newRoot); + } + + /// + protected internal override void OnDetach(CompilationUnit oldRoot) + { + ProtectedBlock.OnDetach(oldRoot); + for (int i = 0; i < Handlers.Count; i++) + Handlers[i].OnDetach(oldRoot); + } + /// public override void Accept(IAstNodeVisitor visitor) => visitor.Visit(this); diff --git a/src/Core/Echo.Ast/ExpressionStatement.cs b/src/Core/Echo.Ast/ExpressionStatement.cs index 0bed6d07..48bca8db 100644 --- a/src/Core/Echo.Ast/ExpressionStatement.cs +++ b/src/Core/Echo.Ast/ExpressionStatement.cs @@ -35,6 +35,12 @@ public override IEnumerable GetChildren() yield return _expression; } + /// + protected internal override void OnAttach(CompilationUnit newRoot) => Expression.OnAttach(newRoot); + + /// + protected internal override void OnDetach(CompilationUnit oldRoot) => Expression.OnDetach(oldRoot); + /// public override void Accept(IAstNodeVisitor visitor) => visitor.Visit(this); diff --git a/src/Core/Echo.Ast/HandlerClause.cs b/src/Core/Echo.Ast/HandlerClause.cs index f28ecda8..20fbd102 100644 --- a/src/Core/Echo.Ast/HandlerClause.cs +++ b/src/Core/Echo.Ast/HandlerClause.cs @@ -66,7 +66,23 @@ public override IEnumerable GetChildren() if (Epilogue is not null) yield return Epilogue; } - + + /// + protected internal override void OnAttach(CompilationUnit newRoot) + { + Prologue?.OnAttach(newRoot); + Contents.OnAttach(newRoot); + Epilogue?.OnAttach(newRoot); + } + + /// + protected internal override void OnDetach(CompilationUnit oldRoot) + { + Prologue?.OnDetach(oldRoot); + Contents.OnDetach(oldRoot); + Epilogue?.OnDetach(oldRoot); + } + /// public override void Accept(IAstNodeVisitor visitor) => visitor.Visit(this); diff --git a/src/Core/Echo.Ast/InstructionExpression.cs b/src/Core/Echo.Ast/InstructionExpression.cs index 3ec42b14..462f61d4 100644 --- a/src/Core/Echo.Ast/InstructionExpression.cs +++ b/src/Core/Echo.Ast/InstructionExpression.cs @@ -62,6 +62,20 @@ public IList> Arguments /// public override IEnumerable GetChildren() => Arguments; + /// + protected internal override void OnAttach(CompilationUnit newRoot) + { + for (int i = 0; i < Arguments.Count; i++) + Arguments[i].OnAttach(newRoot); + } + + /// + protected internal override void OnDetach(CompilationUnit oldRoot) + { + for (int i = 0; i < Arguments.Count; i++) + Arguments[i].OnDetach(oldRoot); + } + /// public override void Accept(IAstNodeVisitor visitor) => visitor.Visit(this); diff --git a/src/Core/Echo.Ast/PhiStatement.cs b/src/Core/Echo.Ast/PhiStatement.cs index 789cd200..490161d7 100644 --- a/src/Core/Echo.Ast/PhiStatement.cs +++ b/src/Core/Echo.Ast/PhiStatement.cs @@ -53,6 +53,22 @@ public IList> Sources /// public override IEnumerable GetChildren() => Sources; + /// + protected internal override void OnAttach(CompilationUnit newRoot) + { + newRoot.RegisterVariableWrite(Representative, this); + for (int i = 0; i < Sources.Count; i++) + newRoot.RegisterVariableUse(Sources[i]); + } + + /// + protected internal override void OnDetach(CompilationUnit oldRoot) + { + oldRoot.UnregisterVariableWrite(Representative, this); + for (int i = 0; i < Sources.Count; i++) + oldRoot.UnregisterVariableUse(Sources[i]); + } + /// public override void Accept(IAstNodeVisitor visitor) => visitor.Visit(this); diff --git a/src/Core/Echo.Ast/VariableExpression.cs b/src/Core/Echo.Ast/VariableExpression.cs index 9d554d3c..ebee8530 100644 --- a/src/Core/Echo.Ast/VariableExpression.cs +++ b/src/Core/Echo.Ast/VariableExpression.cs @@ -10,6 +10,8 @@ namespace Echo.Ast /// public sealed class VariableExpression : Expression { + private IVariable _variable; + /// /// Creates a new variable expression /// @@ -24,13 +26,39 @@ public VariableExpression(IVariable variable) /// public IVariable Variable { - get; - internal set; + get => _variable; + internal set + { + if (_variable != value) + { + // Force detach/attach to update variable xrefs. + var root = GetParentCompilationUnit(); + if (root is not null) + OnDetach(root); + + _variable = value; + + if (root is not null) + OnAttach(root); + } + } } /// public override IEnumerable GetChildren() => Array.Empty(); + /// + protected internal override void OnAttach(CompilationUnit newRoot) + { + newRoot.RegisterVariableUse(this); + } + + /// + protected internal override void OnDetach(CompilationUnit oldRoot) + { + oldRoot.UnregisterVariableUse(this); + } + /// public override void Accept(IAstNodeVisitor visitor) => visitor.Visit(this); diff --git a/test/Core/Echo.Ast.Tests/CompilationUnitTest.cs b/test/Core/Echo.Ast.Tests/CompilationUnitTest.cs new file mode 100644 index 00000000..deeff7c3 --- /dev/null +++ b/test/Core/Echo.Ast.Tests/CompilationUnitTest.cs @@ -0,0 +1,112 @@ +using Echo.Platforms.DummyPlatform.Code; +using Xunit; + +namespace Echo.Ast.Tests; + +public class CompilationUnitTest +{ + [Fact] + public void AddVariableExpressionShouldRegisterUse() + { + var variable = new DummyVariable("x"); + + var unit = new CompilationUnit(); + var expression = variable.ToExpression(); + + // Detached expressions should not register variable use. + Assert.Empty(variable.GetIsUsedBy(unit)); + + // Add to the tree. + unit.Root.Statements.Add(expression.ToStatement()); + + // Attached expressions should be registered. + var use = Assert.Single(variable.GetIsUsedBy(unit)); + Assert.Same(expression, use); + } + + [Fact] + public void AddAssignmentShouldRegisterWrite() + { + var variable = new DummyVariable("x"); + + var unit = new CompilationUnit(); + var statement = Statement.Assignment(variable, Expression.Instruction(DummyInstruction.Op(0, 0, 1))); + + // Detached statements should not register variable use. + Assert.Empty(variable.GetIsWrittenBy(unit)); + + // Add to the tree. + unit.Root.Statements.Add(statement); + + // Attached statements should be registered. + var use = Assert.Single(variable.GetIsWrittenBy(unit)); + Assert.Same(statement, use); + } + + [Fact] + public void AddPhiShouldRegisterWrite() + { + var variable1 = new DummyVariable("x"); + var variable2 = new DummyVariable("y"); + + var unit = new CompilationUnit(); + var statement = Statement.Phi(variable1, variable2); + + // Detached statements should not register variable use. + Assert.Empty(variable1.GetIsWrittenBy(unit)); + + // Add to the tree. + unit.Root.Statements.Add(statement); + + // Attached statements should be registered. + var use = Assert.Single(variable1.GetIsWrittenBy(unit)); + Assert.Same(statement, use); + } + + [Fact] + public void RemoveVariableExpressionShouldUnregisterUse() + { + var variable = new DummyVariable("x"); + + var unit = new CompilationUnit(); + var expression = variable.ToExpression(); + unit.Root.Statements.Add(expression.ToStatement()); + + // Remove the statement. + unit.Root.Statements.Clear(); + + // Detached expressions should deregister variable use. + Assert.Empty(variable.GetIsUsedBy(unit)); + } + + [Fact] + public void RemoveAssignmentShouldUnregisterWrite() + { + var variable = new DummyVariable("x"); + + var unit = new CompilationUnit(); + unit.Root.Statements.Add(Statement.Assignment(variable, Expression.Instruction(DummyInstruction.Op(0, 0, 1)))); + + // Remove the statement. + unit.Root.Statements.Clear(); + + // Detached expressions should deregister variable use. + Assert.Empty(variable.GetIsWrittenBy(unit)); + } + + [Fact] + public void RemovePhiShouldUnregisterWrite() + { + var variable1 = new DummyVariable("x"); + var variable2 = new DummyVariable("y"); + + var unit = new CompilationUnit(); + unit.Root.Statements.Add(Statement.Phi(variable1, variable2)); + + // Remove the statement. + unit.Root.Statements.Clear(); + + // Detached expressions should deregister variable use. + Assert.Empty(variable1.GetIsWrittenBy(unit)); + } +} \ No newline at end of file From fe30e67e6bf31b77ac47df5ad866af662902065d Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 1 Feb 2024 21:48:06 +0100 Subject: [PATCH 27/33] BUGFIX: Register/unregister variables added/remove from existing assignment statement. --- src/Core/Echo.Ast/AssignmentStatement.cs | 5 +-- src/Core/Echo.Ast/AstNode.cs | 4 +++ src/Core/Echo.Ast/VariableCollection.cs | 46 ++++++++++++++++++++++++ 3 files changed, 53 insertions(+), 2 deletions(-) create mode 100644 src/Core/Echo.Ast/VariableCollection.cs diff --git a/src/Core/Echo.Ast/AssignmentStatement.cs b/src/Core/Echo.Ast/AssignmentStatement.cs index f059b128..e712e15d 100644 --- a/src/Core/Echo.Ast/AssignmentStatement.cs +++ b/src/Core/Echo.Ast/AssignmentStatement.cs @@ -1,5 +1,4 @@ using System.Collections.Generic; -using System.Linq; using Echo.Code; using Echo.Graphing; @@ -30,7 +29,9 @@ public AssignmentStatement(IVariable variable, Expression expressi public AssignmentStatement(IEnumerable variables, Expression expression) { Expression = expression; - Variables = variables.ToList(); + Variables = new VariableCollection(this); + foreach (var variable in variables) + Variables.Add(variable); OriginalRange = expression.OriginalRange; } diff --git a/src/Core/Echo.Ast/AstNode.cs b/src/Core/Echo.Ast/AstNode.cs index 3789c9d8..9b025e54 100644 --- a/src/Core/Echo.Ast/AstNode.cs +++ b/src/Core/Echo.Ast/AstNode.cs @@ -1,5 +1,9 @@ +using System.Collections.Generic; +using System.IO; using Echo.Code; +using Echo.ControlFlow.Serialization.Dot; using Echo.Graphing; +using Echo.Graphing.Serialization.Dot; namespace Echo.Ast { diff --git a/src/Core/Echo.Ast/VariableCollection.cs b/src/Core/Echo.Ast/VariableCollection.cs new file mode 100644 index 00000000..1dc44d31 --- /dev/null +++ b/src/Core/Echo.Ast/VariableCollection.cs @@ -0,0 +1,46 @@ +using System.Collections.ObjectModel; +using Echo.Code; + +namespace Echo.Ast; + +internal sealed class VariableCollection : Collection +{ + private readonly Statement _owner; + + public VariableCollection(Statement owner) + { + _owner = owner; + } + + protected override void InsertItem(int index, IVariable item) + { + base.InsertItem(index, item); + _owner.GetParentCompilationUnit()?.RegisterVariableWrite(item, _owner); + } + + protected override void SetItem(int index, IVariable item) + { + var root = _owner.GetParentCompilationUnit(); + root?.UnregisterVariableWrite(Items[index], _owner); + base.SetItem(index, item); + root?.RegisterVariableWrite(item, _owner); + } + + protected override void RemoveItem(int index) + { + var variable = Items[index]; + base.RemoveItem(index); + _owner.GetParentCompilationUnit()?.UnregisterVariableWrite(variable, _owner); + } + + protected override void ClearItems() + { + if (_owner.GetParentCompilationUnit() is {} root) + { + foreach (var item in Items) + root.UnregisterVariableWrite(item, _owner); + } + + base.ClearItems(); + } +} \ No newline at end of file From aee24b098fa1c973a15db28e31d3050e0f5bd2c1 Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 1 Feb 2024 22:35:13 +0100 Subject: [PATCH 28/33] Rename ToAst -> Lift --- src/Core/Echo.Ast/Construction/AstBuilder.cs | 24 +++++++++++++++++-- .../ControlFlowGraphLifterTest.cs | 2 +- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/src/Core/Echo.Ast/Construction/AstBuilder.cs b/src/Core/Echo.Ast/Construction/AstBuilder.cs index 291df0a9..8be55e4c 100644 --- a/src/Core/Echo.Ast/Construction/AstBuilder.cs +++ b/src/Core/Echo.Ast/Construction/AstBuilder.cs @@ -16,26 +16,46 @@ public static class AstBuilder /// The object responsible for determining whether an instruction is pure or not. /// The type of instructions stored in the input graph. /// The lifted graph. - public static ControlFlowGraph> ToAst( + public static ControlFlowGraph> Lift( this ControlFlowGraph cfg, IPurityClassifier classifier) { return ControlFlowGraphLifter.Lift(cfg, classifier); } + /// + /// Lifts all instructions in every node of the provided control flow graph to expressions and statements, and + /// constructs a rooted AST of all the resulting blocks. + /// + /// The control flow graph to lift. + /// The object responsible for determining whether an instruction is pure or not. + /// The type of instructions stored in the input graph. + /// The constructed compilation unit. public static CompilationUnit ToCompilationUnit( this ControlFlowGraph self, IPurityClassifier classifier) { - return new CompilationUnitBuilder().Construct(self.ToAst(classifier)); + return new CompilationUnitBuilder().Construct(self.Lift(classifier)); } + /// + /// Constructs a rooted AST of all the blocks containing expressions and statements. + /// + /// The control flow graph to transform. + /// The type of instructions stored in the input graph. + /// The constructed compilation unit. public static CompilationUnit ToCompilationUnit( this ControlFlowGraph> self) { return new CompilationUnitBuilder().Construct(self); } + /// + /// Converts a block tree to a rooted AST. + /// + /// The root scope to transform. + /// The type of instructions stored in the input graph. + /// The constructed compilation unit. public static CompilationUnit ToCompilationUnit( this ScopeBlock> self) { diff --git a/test/Core/Echo.Ast.Tests/Construction/ControlFlowGraphLifterTest.cs b/test/Core/Echo.Ast.Tests/Construction/ControlFlowGraphLifterTest.cs index 7ac7bc02..3d3e9112 100644 --- a/test/Core/Echo.Ast.Tests/Construction/ControlFlowGraphLifterTest.cs +++ b/test/Core/Echo.Ast.Tests/Construction/ControlFlowGraphLifterTest.cs @@ -30,7 +30,7 @@ private static ControlFlowGraph> ConstructGraph( cfg.DetectExceptionHandlerRegions(exceptionHandlerRanges); - return cfg.ToAst(DummyPurityClassifier.Instance); + return cfg.Lift(DummyPurityClassifier.Instance); } [Fact] From 4c340abfe7c2338987aa5bb072587d171ba3a2e1 Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 1 Feb 2024 22:36:45 +0100 Subject: [PATCH 29/33] BUGFIX: Clone x-ref list to prevent side-effects in caller. --- src/Core/Echo.Ast/CompilationUnit.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Core/Echo.Ast/CompilationUnit.cs b/src/Core/Echo.Ast/CompilationUnit.cs index 041f203a..e2a5458d 100644 --- a/src/Core/Echo.Ast/CompilationUnit.cs +++ b/src/Core/Echo.Ast/CompilationUnit.cs @@ -41,9 +41,9 @@ public BlockStatement Root /// The expressions referencing the variable. public IEnumerable> GetVariableUses(IVariable variable) { - return !_variableUses.TryGetValue(variable, out var uses) + return !_variableUses.TryGetValue(variable, out var uses) ? Enumerable.Empty>() - : uses; + : uses.ToArray(); // Clone to prevent list being modified after returning. } /// @@ -55,7 +55,7 @@ public IEnumerable> GetVariableWrites(IVariable variable { return !_variableWrites.TryGetValue(variable, out var writes) ? Enumerable.Empty>() - : writes; + : writes.ToArray(); // Clone to prevent list being modified after returning. } internal void RegisterVariableUse(VariableExpression expression) From 591698d3e02a5ca3fb3a5c47247b57c27831a2ff Mon Sep 17 00:00:00 2001 From: Washi Date: Thu, 1 Feb 2024 22:36:55 +0100 Subject: [PATCH 30/33] Resolve xml warnings. --- .../Patterns/InstructionExpressionPattern.cs | 39 +++++++++++-------- src/Core/Echo.Ast/VariableExpression.cs | 2 +- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/src/Core/Echo.Ast/Patterns/InstructionExpressionPattern.cs b/src/Core/Echo.Ast/Patterns/InstructionExpressionPattern.cs index b24d2406..7f105d5b 100644 --- a/src/Core/Echo.Ast/Patterns/InstructionExpressionPattern.cs +++ b/src/Core/Echo.Ast/Patterns/InstructionExpressionPattern.cs @@ -13,33 +13,33 @@ public class InstructionExpressionPattern : ExpressionPattern /// Creates a new instruction expression pattern describing an instruction expression with zero parameters. /// - /// The pattern describing the instruction that is stored in the expression. - public InstructionExpressionPattern(Pattern content) + /// The pattern describing the instruction that is stored in the expression. + public InstructionExpressionPattern(Pattern instruction) { - Content = content; + Instruction = instruction; AnyArguments = false; } /// /// Creates a new instruction expression pattern. /// - /// The pattern describing the instruction that is stored in the expression. + /// The pattern describing the instruction that is stored in the expression. /// true if any arguments should be accepted, false to indicate the /// expression should have zero parameters. - public InstructionExpressionPattern(Pattern content, bool anyArguments) + public InstructionExpressionPattern(Pattern instruction, bool anyArguments) { - Content = content; + Instruction = instruction; AnyArguments = anyArguments; } /// /// Creates a new instruction expression pattern. /// - /// The pattern describing the instruction that is stored in the expression. + /// The pattern describing the instruction that is stored in the expression. /// The list of patterns that describe the arguments of the input expression should match with. - public InstructionExpressionPattern(Pattern content, params Pattern>[] arguments) + public InstructionExpressionPattern(Pattern instruction, params Pattern>[] arguments) { - Content = content; + Instruction = instruction; AnyArguments = false; foreach (var argument in arguments) Arguments.Add(argument); @@ -48,11 +48,11 @@ public InstructionExpressionPattern(Pattern content, params Patter /// /// Creates a new instruction expression pattern. /// - /// The pattern describing the instruction that is stored in the expression. + /// The pattern describing the instruction that is stored in the expression. /// The list of patterns that describe the arguments of the input expression should match with. - public InstructionExpressionPattern(Pattern content, IEnumerable>> arguments) + public InstructionExpressionPattern(Pattern instruction, IEnumerable>> arguments) { - Content = content; + Instruction = instruction; AnyArguments = false; foreach (var argument in arguments) Arguments.Add(argument); @@ -61,7 +61,7 @@ public InstructionExpressionPattern(Pattern content, IEnumerable

/// Gets or sets a pattern describing the instruction that is stored in the expression. ///

- public Pattern Content + public Pattern Instruction { get; set; @@ -99,7 +99,7 @@ protected override void MatchChildren(Expression input, MatchResul } // Match contents. - Content.Match(expression.Instruction, result); + Instruction.Match(expression.Instruction, result); if (!result.IsSuccess) return; @@ -173,9 +173,14 @@ public InstructionExpressionPattern WithAnyArguments() return this; } - public InstructionExpressionPattern CaptureContent(CaptureGroup captureGroup) + /// + /// Indicates the instruction should be captured in a certain group. + /// + /// The group. + /// The current pattern. + public InstructionExpressionPattern CaptureInstruction(CaptureGroup captureGroup) { - Content.CaptureAs(captureGroup); + Instruction.CaptureAs(captureGroup); return this; } @@ -197,7 +202,7 @@ public override string ToString() { var builder = new StringBuilder(); - builder.Append(Content); + builder.Append(Instruction); builder.Append('('); if (AnyArguments) diff --git a/src/Core/Echo.Ast/VariableExpression.cs b/src/Core/Echo.Ast/VariableExpression.cs index ebee8530..3068443a 100644 --- a/src/Core/Echo.Ast/VariableExpression.cs +++ b/src/Core/Echo.Ast/VariableExpression.cs @@ -10,7 +10,7 @@ namespace Echo.Ast ///
public sealed class VariableExpression : Expression { - private IVariable _variable; + private IVariable _variable = null!; /// /// Creates a new variable expression From 4cf10d6e4ba988eb083c65346614be02e165265a Mon Sep 17 00:00:00 2001 From: Washi Date: Fri, 2 Feb 2024 14:07:43 +0100 Subject: [PATCH 31/33] Add original offset ranges to block statements. --- src/Core/Echo.Ast/BlockStatement.cs | 2 +- .../Construction/CompilationUnitBuilder.cs | 54 +++++++++++++++++-- 2 files changed, 51 insertions(+), 5 deletions(-) diff --git a/src/Core/Echo.Ast/BlockStatement.cs b/src/Core/Echo.Ast/BlockStatement.cs index eb69246a..90484fe3 100644 --- a/src/Core/Echo.Ast/BlockStatement.cs +++ b/src/Core/Echo.Ast/BlockStatement.cs @@ -24,7 +24,7 @@ public IList> Statements { get; } - + /// public override IEnumerable GetChildren() => Statements; diff --git a/src/Core/Echo.Ast/Construction/CompilationUnitBuilder.cs b/src/Core/Echo.Ast/Construction/CompilationUnitBuilder.cs index c2d29b5e..486aac84 100644 --- a/src/Core/Echo.Ast/Construction/CompilationUnitBuilder.cs +++ b/src/Core/Echo.Ast/Construction/CompilationUnitBuilder.cs @@ -34,10 +34,17 @@ public CompilationUnit Construct(ControlFlowGraph VisitBasicBlock(BasicBlock> block, object? state) { var result = new BlockStatement(); - + foreach (var statement in block.Instructions) result.Statements.Add(statement); + if (result.Statements.Count > 0 + && result.Statements[0].OriginalRange is { } start + && result.Statements[result.Statements.Count - 1].OriginalRange is { } end) + { + result.OriginalRange = new AddressRange(start.Start, end.End); + } + return result; } @@ -49,6 +56,13 @@ public AstNode VisitScopeBlock(ScopeBlock> foreach (var b in block.Blocks) result.Statements.Add((Statement) b.AcceptVisitor(this, state)); + if (result.Statements.Count > 0 + && result.Statements[0].OriginalRange is { } start + && result.Statements[result.Statements.Count - 1].OriginalRange is { } end) + { + result.OriginalRange = new AddressRange(start.Start, end.End); + } + return result; } @@ -61,6 +75,23 @@ public AstNode VisitExceptionHandlerBlock(ExceptionHandlerBlock) handler.AcceptVisitor(this, state)); + if (result.ProtectedBlock.OriginalRange is { } start) + { + AddressRange? end = null; + + for (int i = result.Handlers.Count - 1; i >= 0; i--) + { + if (result.Handlers[i].OriginalRange is { } candidate) + { + end = candidate; + break; + } + } + + end ??= start; + result.OriginalRange = new AddressRange(start.Start, end.Value.End); + } + return result; } @@ -71,13 +102,28 @@ public AstNode VisitHandlerBlock(HandlerBlock?) block.Prologue.AcceptVisitor(this, state); + { + result.Prologue = (BlockStatement) block.Prologue.AcceptVisitor(this, state); + start = result.Prologue.OriginalRange; + } result.Contents = (BlockStatement) block.Contents.AcceptVisitor(this, state); - + start ??= result.Contents.OriginalRange; + end = result.Contents.OriginalRange; + if (block.Epilogue is not null) - result.Epilogue = (BlockStatement?) block.Epilogue.AcceptVisitor(this, state); + { + result.Epilogue = (BlockStatement) block.Epilogue.AcceptVisitor(this, state); + start ??= result.Epilogue.OriginalRange; + end = result.Epilogue.OriginalRange; + } + + if (start is not null && end is not null) + result.OriginalRange = new AddressRange(start.Value.Start, end.Value.End); return result; } From 97fdd2f05fd4f6109e6df48145ae8b2b04b97bba Mon Sep 17 00:00:00 2001 From: Washi Date: Fri, 2 Feb 2024 14:08:08 +0100 Subject: [PATCH 32/33] BUGFIX: Prevent tree nodes from being added to themselves as children. --- src/Core/Echo/Graphing/TreeNodeBase.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Core/Echo/Graphing/TreeNodeBase.cs b/src/Core/Echo/Graphing/TreeNodeBase.cs index a59e6034..59fb0c64 100644 --- a/src/Core/Echo/Graphing/TreeNodeBase.cs +++ b/src/Core/Echo/Graphing/TreeNodeBase.cs @@ -22,6 +22,9 @@ internal set { if (_parent != value) { + if (value == this) + throw new ArgumentException("Cannot add a node to itself as a child."); + var original = _parent; _parent = value; OnParentChanged(original); From e00a46738c675eff7efdff96d73bd14b6afd55eb Mon Sep 17 00:00:00 2001 From: Washi Date: Fri, 2 Feb 2024 14:08:42 +0100 Subject: [PATCH 33/33] BUGFIX: Map EH region successors to their unbreakable path view nodes. --- .../Serialization/Blocks/UnbreakablePathsView.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Core/Echo.ControlFlow/Serialization/Blocks/UnbreakablePathsView.cs b/src/Core/Echo.ControlFlow/Serialization/Blocks/UnbreakablePathsView.cs index 48044e10..3f8e4be9 100644 --- a/src/Core/Echo.ControlFlow/Serialization/Blocks/UnbreakablePathsView.cs +++ b/src/Core/Echo.ControlFlow/Serialization/Blocks/UnbreakablePathsView.cs @@ -36,7 +36,7 @@ public IReadOnlyList> GetImpliedNeighbours(Control var n = path[i]; // Add unconditional edge. - if (n.UnconditionalEdge != null && n.UnconditionalEdge.Type == ControlFlowEdgeType.Unconditional) + if (n.UnconditionalEdge is {Type: ControlFlowEdgeType.Unconditional}) AddSuccessorToResult(result, n.UnconditionalNeighbour); // Add explicit conditional / abnormal edges. @@ -150,7 +150,7 @@ private void AddRegionSuccessors(ICollection> resu } for (int i = 0; i < successors.Count; i++) - result.Add(successors[i]); + AddSuccessorToResult(result, successors[i]); } } } \ No newline at end of file