Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Offset independent nodes #143

Merged
merged 9 commits into from
Aug 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 34 additions & 6 deletions docs/guides/core/cfg-basics.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Refer to the platform-specific documentation for more details.
## Nodes

Nodes in a control flow graph represent the individual basic blocks in the code, and are implemented by the `ControlFlowNode<TInstruction>` class.
They can be accessed from the `Nodes` property:
They can be accessed from the `Nodes` property, which can be iterated:

```csharp
ControlFlowGraph<TInstruction> cfg = ...;
Expand All @@ -37,14 +37,29 @@ foreach (var node in cfg.Nodes)
Console.WriteLine($"{node.Offset:X8}");
```

Nodes are indexed by offset.
They can be obtained via the `GetNodeByOffset` method:
Individual nodes can be obtained by looking them up by offset:

```csharp
var node = cfg.GetNodeByOffset(offset: 0x1234);
var node = cfg.Nodes.GetByOffset(offset: 0x1234);
```

Every node exposes a basic blockc containing the instructions it executes:
This performs a linear search through all the nodes, and finds the first basic block that matches.
To ensure all nodes have updated offsets according to their contents, use the `UpdateOffsets` method:

```csharp
cfg.Nodes.UpdateOffsets();
```

When doing many lookups by offset, consider first creating an offset map for faster lookups.

```csharp
var offsetMap = cfg.Nodes.CreateOffsetMap();
var n1 = offsetMap[0x0001];
var n2 = offsetMap[0x0004];
var n3 = offsetMap[0x0010];
```

Every node exposes a basic block containing the instructions it executes:

```csharp
ControlFlowNode<TInstruction> node = ...;
Expand Down Expand Up @@ -105,7 +120,7 @@ If only interested in the target nodes, `GetSuccessors()` can be used instead:
```csharp
foreach (var successor in node.GetSuccessors())
Console.WriteLine(successor);
```
```[dfg-basics.md](dfg-basics.md)

Similarly, incoming edges can also be obtained using `GetIncomingEdges()` and `GetPredecessors()`:

Expand All @@ -117,6 +132,19 @@ foreach (var predecessor in node.GetPredecessors())
```


New edges can be drawn by either mutating the outgoing edges properties, or by using the `ConnectWith` helper method:

```csharp
ControlFlowNode<TInstruction> node1 = ...;
ControlFlowNode<TInstruction> node2 = ...;
ControlFlowNode<TInstruction> node3 = ...;
ControlFlowNode<TInstruction> node4 = ...;

node1.ConnectWith(node2);
node2.ConnectWith(node3, ControlFlowEdgeType.Conditional);
node2.ConnectWith(node4, ControlFlowEdgeType.FallThrough);
```

## Regions

Control flow graphs can be subdivided into regions.
Expand Down
25 changes: 21 additions & 4 deletions docs/guides/core/cfg-construction.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ Each architecture that supports static control flow graph building implements th
A graph can then be constructed using the `StaticFlowGraphBuilder` class:

```csharp
using Echo.ControlFlow.Construction;

IArchitecture<TInstruction> architecture = ...;
IStaticSuccessorResolver<TInstruction> resolver = ...;

Expand Down Expand Up @@ -66,8 +68,10 @@ This interface takes a symbolic input state, and transforms it into a set of all


```csharp
using Echo.DataFlow.Construction;

IArchitecture<TInstruction> architecture = ...;
IStateTransitioner<TInstruction> transitioner = ...;
StateTransitioner<TInstruction> transitioner = ...;

IList<TInstruction> instructions = ...;
var builder = new SymbolicFlowGraphBuilder<TInstruction>(
Expand All @@ -79,12 +83,25 @@ var builder = new SymbolicFlowGraphBuilder<TInstruction>(
var cfg = builder.ConstructFlowGraph();
```

A by-product of symbolic graph building is that it also produces a **data flow graph**:
Most state transitioners produce a data flow graph as a by-product.

```csharp
var dfg = builder.DataFlowGraph;
```
// First create the CFG.
var cfg = builder.ConstructFlowGraph();

// After building the CFG, a DFG is populated in the transitioner.
var dfg = transitioner.DataFlowGraph;
```

> [!WARNING]
> While symbolic graph construction usually is more accurate, it is significantly slower than static graph construction and can take a lot of memory.


> [!NOTE]
> Often, a backend platform has this boilerplate already implemented by extension methods.
> For instance, `Echo.Platforms.AsmResolver` defines an extension method on `CilMethodBody` called `ConstructStaticFlowGraph`.
> ```csharp
> CilMethodBody methodBody = ...;
> var cfg = methodBody.ConstructSymbolicFlowGraph(out var dfg);
> ```
> Refer to the platform-specific documentation to see how these graphs can be constructed easily.
23 changes: 15 additions & 8 deletions docs/guides/core/dfg-basics.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,23 +37,30 @@ DataFlowGraph<TInstruction> dfg = ...;

// Iterate over all nodes in a data flow graph:
foreach (var node in dfg.Nodes)
Console.WriteLine(node.Contents);
Console.WriteLine(node.Instruction);
```

Nodes are indexed by offset.
They can be obtained via the `GetNodeById` method:
Individual nodes can be obtained by looking them up by offset:

```csharp
var node = dfg.GetNodeById(id: 0x1234);
var node = dfg.Nodes.GetByOffset(offset: 0x1234);
```

Every node exposes a basic blockc containing the instructions that introduces or requires dependencies:
This performs a linear search through all the nodes, and finds the first node that matches in offset.
To ensure all nodes have updated offsets according to their contents, use the `UpdateOffsets` method:

```csharp
DataFlowNode<TInstruction> node = ...;
var instruction = node.Contents;
dfg.Nodes.UpdateOffsets();
```

When doing many lookups by offset, consider first creating an offset map for faster lookups.

```csharp
var offsetMap = dfg.Nodes.CreateOffsetMap();
var n1 = offsetMap[0x0001];
var n2 = offsetMap[0x0004];
var n3 = offsetMap[0x0010];
```

## Edges

Expand Down Expand Up @@ -105,7 +112,7 @@ var dependencies = node.GetOrderedDependencies();

By default, `GetOrderedDependencies` traverses all edges in the data flow graph.
This includes variable dependencies that were registered in the graph.
If only the stack dependnecies are meant to be traversed (e.g. to get the instructions that make up a single expression), additional flags can be specified to alter the behaviour of the traversal.
If only the stack dependencies are meant to be traversed (e.g. to get the instructions that make up a single expression), additional flags can be specified to alter the behaviour of the traversal.

```csharp
DataFlowGraph<TInstruction> node = ...
Expand Down
1 change: 1 addition & 0 deletions src/Core/Echo.Ast/Analysis/AstPurityClassifier.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ namespace Echo.Ast.Analysis;
/// </summary>
/// <typeparam name="TInstruction">The type of instructions the statements store.</typeparam>
public class AstPurityClassifier<TInstruction> : IPurityClassifier<Statement<TInstruction>>
where TInstruction : notnull
{
/// <summary>
/// Creates a new instance of the <see cref="AstPurityClassifier{TInstruction}"/> class.
Expand Down
1 change: 1 addition & 0 deletions src/Core/Echo.Ast/Analysis/AstPurityVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ namespace Echo.Ast.Analysis;
/// </summary>
/// <typeparam name="TInstruction">The type of instructions to store in each expression.</typeparam>
public class AstPurityVisitor<TInstruction> : IAstNodeVisitor<TInstruction, IPurityClassifier<TInstruction>, Trilean>
where TInstruction : notnull
{
/// <summary>
/// Gets the singleton instance of the <see cref="AstPurityVisitor{TInstruction}"/> class.
Expand Down
1 change: 1 addition & 0 deletions src/Core/Echo.Ast/Analysis/FlowControlDeterminer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ namespace Echo.Ast.Analysis
{
internal sealed class FlowControlDeterminer<TInstruction>
: IAstNodeVisitor<TInstruction, object?, InstructionFlowControl>
where TInstruction : notnull
{
private readonly IArchitecture<TInstruction> _isa;

Expand Down
14 changes: 2 additions & 12 deletions src/Core/Echo.Ast/Analysis/ReadVariableFinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Echo.Ast.Analysis
{
internal sealed class ReadVariableFinder<TInstruction> : AstNodeListener<TInstruction>
where TInstruction : notnull
{
private readonly IArchitecture<TInstruction> _architecture;

Expand All @@ -23,18 +24,7 @@ public override void ExitVariableExpression(VariableExpression<TInstruction> exp

public override void ExitInstructionExpression(InstructionExpression<TInstruction> expression)
{
int count = _architecture.GetReadVariablesCount(expression.Instruction);
if (count == 0)
return;

var variables = ArrayPool<IVariable>.Shared.Rent(count);

int actualCount = _architecture.GetReadVariables(expression.Instruction, variables);
for (int i = 0; i < actualCount; i++)
Variables.Add(variables[i]);

ArrayPool<IVariable>.Shared.Return(variables);

_architecture.GetReadVariables(expression.Instruction, Variables);
base.ExitInstructionExpression(expression);
}
}
Expand Down
14 changes: 2 additions & 12 deletions src/Core/Echo.Ast/Analysis/WrittenVariableFinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Echo.Ast.Analysis
{
internal sealed class WrittenVariableFinder<TInstruction> : AstNodeListener<TInstruction>
where TInstruction : notnull
{
private readonly IArchitecture<TInstruction> _architecture;

Expand All @@ -30,18 +31,7 @@ public override void ExitPhiStatement(PhiStatement<TInstruction> phiStatement)

public override void ExitInstructionExpression(InstructionExpression<TInstruction> expression)
{
int count = _architecture.GetWrittenVariablesCount(expression.Instruction);
if (count == 0)
return;

var variables = ArrayPool<IVariable>.Shared.Rent(count);

int actualCount = _architecture.GetWrittenVariables(expression.Instruction, variables);
for (int i = 0; i < actualCount; i++)
Variables.Add(variables[i]);

ArrayPool<IVariable>.Shared.Return(variables);

_architecture.GetWrittenVariables(expression.Instruction, Variables);
base.ExitInstructionExpression(expression);
}
}
Expand Down
1 change: 1 addition & 0 deletions src/Core/Echo.Ast/AssignmentStatement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ namespace Echo.Ast
/// Represents a statement that assigns a value to a (set of) variable(s).
/// </summary>
public sealed class AssignmentStatement<TInstruction> : Statement<TInstruction>
where TInstruction : notnull
{
private Expression<TInstruction> _expression = null!;

Expand Down
33 changes: 7 additions & 26 deletions src/Core/Echo.Ast/AstArchitecture.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using Echo.Ast.Analysis;
using Echo.Code;

Expand All @@ -10,6 +11,7 @@ namespace Echo.Ast
/// <typeparam name="TInstruction">The instructions defined by the satck-based platform.</typeparam>
public class AstArchitecture<TInstruction>
: IArchitecture<Statement<TInstruction>>
where TInstruction : notnull
{
private readonly IArchitecture<TInstruction> _baseArchitecture;
private readonly FlowControlDeterminer<TInstruction> _flowControlDeterminer;
Expand Down Expand Up @@ -43,45 +45,23 @@ public InstructionFlowControl GetFlowControl(in Statement<TInstruction> instruct
public int GetStackPopCount(in Statement<TInstruction> instruction) => 0;

/// <inheritdoc />
public int GetReadVariablesCount(in Statement<TInstruction> instruction)
{
var finder = new ReadVariableFinder<TInstruction>(_baseArchitecture);
AstNodeWalker<TInstruction>.Walk(finder, instruction);
return finder.Variables.Count;
}

/// <inheritdoc />
public int GetReadVariables(in Statement<TInstruction> instruction, Span<IVariable> variablesBuffer)
public void GetReadVariables(in Statement<TInstruction> instruction, ICollection<IVariable> variablesBuffer)
{
var finder = new ReadVariableFinder<TInstruction>(_baseArchitecture);
AstNodeWalker<TInstruction>.Walk(finder, instruction);

int i = 0;
foreach (var variable in finder.Variables)
variablesBuffer[i++] = variable;

return finder.Variables.Count;
variablesBuffer.Add(variable);
}

/// <inheritdoc />
public int GetWrittenVariablesCount(in Statement<TInstruction> instruction)
{
var finder = new WrittenVariableFinder<TInstruction>(_baseArchitecture);
AstNodeWalker<TInstruction>.Walk(finder, instruction);
return finder.Variables.Count;
}

/// <inheritdoc />
public int GetWrittenVariables(in Statement<TInstruction> instruction, Span<IVariable> variablesBuffer)
public void GetWrittenVariables(in Statement<TInstruction> instruction, ICollection<IVariable> variablesBuffer)
{
var finder = new WrittenVariableFinder<TInstruction>(_baseArchitecture);
AstNodeWalker<TInstruction>.Walk(finder, instruction);

int i = 0;
foreach (var variable in finder.Variables)
variablesBuffer[i++] = variable;

return finder.Variables.Count;
variablesBuffer.Add(variable);
}
}

Expand All @@ -97,6 +77,7 @@ public static class AstArchitectureExtensions
/// <typeparam name="TInstruction">The type of instructions defined by the architecture.</typeparam>
/// <returns>The lifted architecture.</returns>
public static AstArchitecture<TInstruction> ToAst<TInstruction>(this IArchitecture<TInstruction> self)
where TInstruction : notnull
=> new(self);
}
}
2 changes: 2 additions & 0 deletions src/Core/Echo.Ast/AstFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ public static class AstFormatter
/// <typeparam name="TInstruction">The type of instructions stored in the AST.</typeparam>
/// <returns>The constructed formatter.</returns>
public static AstFormatter<TInstruction> ToAstFormatter<TInstruction>(this IInstructionFormatter<TInstruction> self)
where TInstruction : notnull
{
return new AstFormatter<TInstruction>(self);
}
Expand All @@ -28,6 +29,7 @@ public static AstFormatter<TInstruction> ToAstFormatter<TInstruction>(this IInst
public class AstFormatter<TInstruction> :
IAstNodeVisitor<TInstruction, IndentedTextWriter>,
IInstructionFormatter<Statement<TInstruction>>
where TInstruction : notnull
{
/// <summary>
/// Creates a new AST formatter using the default instruction formatter.
Expand Down
5 changes: 1 addition & 4 deletions src/Core/Echo.Ast/AstNode.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
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
{
/// <summary>
/// Represents a single node in an Abstract Syntax Tree (AST).
/// </summary>
public abstract class AstNode<TInstruction> : TreeNodeBase
where TInstruction : notnull
{
/// <summary>
/// Gets the direct parent of the AST node.
Expand Down
1 change: 1 addition & 0 deletions src/Core/Echo.Ast/AstNodeListener.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ namespace Echo.Ast
/// </summary>
/// <typeparam name="TInstruction">The type of instructions stored in the AST.</typeparam>
public abstract class AstNodeListener<TInstruction> : IAstNodeListener<TInstruction>
where TInstruction : notnull
{
/// <inheritdoc />
public void EnterCompilationUnit(CompilationUnit<TInstruction> unit) {}
Expand Down
1 change: 1 addition & 0 deletions src/Core/Echo.Ast/AstNodeWalker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ namespace Echo.Ast
/// </summary>
/// <typeparam name="TInstruction">The instruction stored in the AST.</typeparam>
public class AstNodeWalker<TInstruction> : IAstNodeVisitor<TInstruction>
where TInstruction : notnull
{
private readonly IAstNodeListener<TInstruction> _listener;

Expand Down
2 changes: 2 additions & 0 deletions src/Core/Echo.Ast/AstVariableExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public static class AstVariableExtensions
public static IEnumerable<VariableExpression<TInstruction>> GetIsUsedBy<TInstruction>(
this IVariable self,
CompilationUnit<TInstruction> unit)
where TInstruction : notnull
{
return unit.GetVariableUses(self);
}
Expand All @@ -30,6 +31,7 @@ public static IEnumerable<VariableExpression<TInstruction>> GetIsUsedBy<TInstruc
public static IEnumerable<Statement<TInstruction>> GetIsWrittenBy<TInstruction>(
this IVariable self,
CompilationUnit<TInstruction> unit)
where TInstruction : notnull
{
return unit.GetVariableWrites(self);
}
Expand Down
Loading
Loading