Skip to content

Commit

Permalink
Flow analysis rework (#508)
Browse files Browse the repository at this point in the history
* Update FlowAnalysisErrors.cs

* Removed old crap

* Update FlowAnalysisTests.cs

* Create FlowAnalysisPass.cs

* Delete FlowAnalysisPass.cs

* Basic CFG

* More elaborate interfaces

* Create ControlFlowGraphBuilder.cs

* Update ControlFlowGraphBuilder.cs

* Update ControlFlowGraphBuilder.cs

* Update ControlFlowGraphBuilder.cs

* Update ControlFlowGraphBuilder.cs

* Slight fix

* Update ControlFlowGraphBuilder.cs

* Update ControlFlowGraphBuilder.cs

* Update ControlFlowGraphBuilder.cs

* Update ControlFlowGraphBuilder.cs

* Update ControlFlowGraphBuilder.cs

* Update ControlFlowGraphBuilder.cs

* Update ControlFlowGraphBuilder.cs

* Done

* Added ToDot

* Turned off flow analysis temporarily

* Fixes

* Update SourceFunctionSymbol.cs

* Update ControlFlowGraphBuilder.cs

* Update ControlFlowGraphBuilder.cs

* Update ControlFlowGraphBuilder.cs

* Update ControlFlowGraph.cs

* YEET

* Revert "YEET"

This reverts commit 46a9ebf.

* Edge info added

* Update ControlFlowGraph.cs

* Update FlowCondition.cs

* Update ControlFlowGraphBuilder.cs

* Update ControlFlowGraphBuilder.cs

* Update Template.cs

* Update ControlFlowGraph.cs

* Update ControlFlowGraph.cs

* Inlining

* Added exit point

* Update ControlFlowGraphBuilder.cs

* Create FlowDomain.cs

* Create FlowAnalysis.cs

* Update FlowAnalysis.cs

* Update FlowDomain.cs

* Added analysis passes

* Update FlowDomain.cs

* Update FlowDomain.cs

* Update FlowAnalysis.cs

* Added definite assignment

* Update FlowAnalysis.cs

* Update SourceFunctionSymbol.cs

* Update FlowAnalysis.cs

* Update ControlFlowGraph.cs

* Tweaks

* Update FlowDomain.cs

* Update FlowAnalysis.cs

* Update FlowAnalysis.cs

* Completed API

* Moved todot

* Update ControlFlowGraphToDot.cs

* Added optional stringification of domain state

* Log state

* Update DataFlowAnalysis.cs

* GenKill tostring util

* Added ranking name

* Update ControlFlowGraphBuilder.cs

* Update ControlFlowGraphToDot.cs

* Update ControlFlowGraphBuilder.cs

* Update ControlFlowGraphToDot.cs

* Added first snapshot test of CFG

* Fix, unconditional goto test

* Fix, simplification

* Added while cfg test

* Added early return test

* Added conditional goto in assignment test

* Fixed construction

* For loop test added

* Relation done

* Added return path domain

* Added a tuple domain

* Made domain be based on comparison

* Update DefiniteAssignmentDomain.cs

* Create CompleteFlowAnalysis.cs

* Update CompleteFlowAnalysis.cs

* Update CompleteFlowAnalysis.cs

* Update CompleteFlowAnalysis.cs

* Update CompleteFlowAnalysis.cs

* Update ConstraintSolver.cs

* Replaced

* Update SourceFunctionSymbol.cs

* Update CompleteFlowAnalysis.cs

* Fixes

* Update DataFlowAnalysis.cs

* Update DefiniteAssignmentDomain.cs

* Update DataFlowAnalysis.cs

* Update DataFlowAnalysis.cs

* Update FlowDomain.cs

* Update TupleDomain.cs

* New domains

* Updated

* Update DefiniteAssignmentDomain.cs

* Update FlowDomain.cs

* Update CompleteFlowAnalysis.cs

* Update CompleteFlowAnalysis.cs

* Update BoundTreeCollector.cs

* Update CompleteFlowAnalysis.cs

* Create ReturnsOnAllPathsTests.cs

* Update ReturnsOnAllPathsTests.cs

* More tests

* Update DefiniteAssignmentTests.cs

* Update DefiniteAssignmentTests.cs

* Update DefiniteAssignmentDomain.cs

* Fix

* Update DefiniteAssignmentDomain.cs

* Added single assignment tests

* More tests

* Cleanup

* Update ReturnsOnAllPathsTests.cs

* Simplified definite assignment back

* Cleanup

* Update ReturnsOnAllPathsTests.cs

* Update ControlFlowGraphBuilder.cs
  • Loading branch information
LPeter1997 authored Oct 23, 2024
1 parent b0181ed commit de75c46
Show file tree
Hide file tree
Showing 38 changed files with 2,789 additions and 1,117 deletions.
23 changes: 9 additions & 14 deletions src/Draco.Compiler.Tests/Draco.Compiler.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,24 @@
</PropertyGroup>

<ItemGroup>
<Using Include="Xunit" />
<PackageReference Include="coverlet.collector" Version="6.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="xunit" Version="2.9.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.8.2" />
<PackageReference Include="Verify.Xunit" Version="26.4.5" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.7.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.7.0" />
<PackageReference Include="xunit" Version="2.6.6" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.6">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<ProjectReference Include="..\Draco.Compiler\Draco.Compiler.csproj" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Draco.Compiler\Draco.Compiler.csproj" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.7.0" />
<PackageReference Include="Basic.Reference.Assemblies.Net80" Version="1.4.5" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Basic.Reference.Assemblies.Net80" Version="1.4.5" />
<Using Include="Xunit" />
</ItemGroup>

<ItemGroup>
Expand Down
268 changes: 268 additions & 0 deletions src/Draco.Compiler.Tests/Semantics/ControlFlowGraphTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
using DiffEngine;
using Draco.Compiler.Api.Syntax;
using Draco.Compiler.Internal.FlowAnalysis;
using Draco.Compiler.Internal.Symbols.Source;
using static Draco.Compiler.Api.Syntax.SyntaxFactory;
using static Draco.Compiler.Tests.TestUtilities;

namespace Draco.Compiler.Tests.Semantics;

// Test the building of CFGs itself
public sealed class ControlFlowGraphTests
{
private readonly VerifySettings settings = new();

public ControlFlowGraphTests()
{
DiffTools.UseOrder(DiffTool.VisualStudioCode, DiffTool.VisualStudio, DiffTool.Rider);

this.settings.UseDirectory("ControlFlowGraphs");
}

private static IControlFlowGraph FunctionToCfg(SyntaxTree tree, string? name = null)
{
var functionSyntax = tree.GetNode<FunctionDeclarationSyntax>(predicate: decl => name is null || decl.Name.Text == name);
var compilation = CreateCompilation(tree);
var semanticModel = compilation.GetSemanticModel(tree);
var functionSymbol = GetInternalSymbol<SourceFunctionSymbol>(semanticModel.GetDeclaredSymbol(functionSyntax));
return ControlFlowGraphBuilder.Build(functionSymbol.Body);
}

[Fact]
public async Task EmptyMethod()
{
// Arrange
// func main() {}
var program = SyntaxTree.Create(CompilationUnit(FunctionDeclaration(
"main",
ParameterList(),
null,
BlockFunctionBody())));

// Act
var cfg = FunctionToCfg(program);
var dot = cfg.ToDot();

// Assert
await Verify(dot, this.settings);
}

[Fact]
public async Task UnconditionalBackwardsJump()
{
// Arrange
// func main() {
// loop:
// goto loop;
// }
var program = SyntaxTree.Create(CompilationUnit(FunctionDeclaration(
"main",
ParameterList(),
null,
BlockFunctionBody(
DeclarationStatement(LabelDeclaration("loop")),
ExpressionStatement(GotoExpression(NameLabel("loop")))))));

// Act
var cfg = FunctionToCfg(program);
var dot = cfg.ToDot();

// Assert
await Verify(dot, this.settings);
}

[Fact]
public async Task IfElse()
{
// Arrange
// func foo(b: bool) {
// if (b) bar(); else baz();
// }
// func bar() {}
// func baz() {}
var program = SyntaxTree.Create(CompilationUnit(
FunctionDeclaration(
"main",
ParameterList(Parameter("b", NameType("bool"))),
null,
BlockFunctionBody(
ExpressionStatement(IfExpression(
NameExpression("b"),
CallExpression(NameExpression("bar")),
CallExpression(NameExpression("baz")))))),
FunctionDeclaration("bar", ParameterList(), null, BlockFunctionBody()),
FunctionDeclaration("baz", ParameterList(), null, BlockFunctionBody())));

// Act
var cfg = FunctionToCfg(program);
var dot = cfg.ToDot();

// Assert
await Verify(dot, this.settings);
}

[Fact]
public async Task WhileLoop()
{
// Arrange
// func foo() {
// var i = 0;
// while (i < 10) {
// bar();
// i += 1;
// }
// }
// func bar() {}
var program = SyntaxTree.Create(CompilationUnit(
FunctionDeclaration(
"main",
ParameterList(),
null,
BlockFunctionBody(
DeclarationStatement(VariableDeclaration("i", null, LiteralExpression(0))),
ExpressionStatement(WhileExpression(
RelationalExpression(NameExpression("i"), ComparisonElement(LessThan, LiteralExpression(10))),
BlockExpression(
ExpressionStatement(CallExpression(NameExpression("bar"))),
ExpressionStatement(BinaryExpression(NameExpression("i"), PlusAssign, LiteralExpression(1)))))))),
FunctionDeclaration("bar", ParameterList(), null, BlockFunctionBody())));

// Act
var cfg = FunctionToCfg(program);
var dot = cfg.ToDot();

// Assert
await Verify(dot, this.settings);
}

[Fact]
public async Task EarlyReturn()
{
// Arrange
// func foo(b: bool) {
// bar();
// if (b) return;
// baz();
// }
// func bar() {}
// func baz() {}
var program = SyntaxTree.Create(CompilationUnit(
FunctionDeclaration(
"main",
ParameterList(Parameter("b", NameType("bool"))),
null,
BlockFunctionBody(
ExpressionStatement(CallExpression(NameExpression("bar"))),
ExpressionStatement(IfExpression(
NameExpression("b"),
ReturnExpression())),
ExpressionStatement(CallExpression(NameExpression("baz"))))),
FunctionDeclaration("bar", ParameterList(), null, BlockFunctionBody()),
FunctionDeclaration("baz", ParameterList(), null, BlockFunctionBody())));

// Act
var cfg = FunctionToCfg(program);
var dot = cfg.ToDot();

// Assert
await Verify(dot, this.settings);
}

[Fact]
public async Task ConditionalGotoInAssignment()
{
// Arrange
// func foo(b: bool) {
// val x = if (b) goto noassign else 1;
// noassign:
// bar();
// }
// func bar() {}
var program = SyntaxTree.Create(CompilationUnit(
FunctionDeclaration(
"main",
ParameterList(Parameter("b", NameType("bool"))),
null,
BlockFunctionBody(
DeclarationStatement(VariableDeclaration("x", null, IfExpression(
NameExpression("b"),
GotoExpression(NameLabel("noassign")),
LiteralExpression(1)))),
DeclarationStatement(LabelDeclaration("noassign")),
ExpressionStatement(CallExpression(NameExpression("bar"))))),
FunctionDeclaration("bar", ParameterList(), null, BlockFunctionBody())));

// Act
var cfg = FunctionToCfg(program);
var dot = cfg.ToDot();

// Assert
await Verify(dot, this.settings);
}

[Fact]
public async Task ForLoop()
{
// Arrange
// func foo(s: Array<int32>) {
// for (i in s) bar(i);
// }
// func bar(x: int32) {}
var program = SyntaxTree.Create(CompilationUnit(
FunctionDeclaration(
"main",
ParameterList(Parameter("s", GenericType(NameType("Array"), NameType("int32")))),
null,
BlockFunctionBody(
ExpressionStatement(ForExpression(
"i",
NameExpression("s"),
CallExpression(NameExpression("bar"), NameExpression("i")))))),
FunctionDeclaration("bar", ParameterList(Parameter("x", NameType("int32"))), null, BlockFunctionBody())));

// Act
var cfg = FunctionToCfg(program);
var dot = cfg.ToDot();

// Assert
await Verify(dot, this.settings);
}

[Fact]
public async Task ChainedComparison()
{
// Arrange
// func foo(a: int32, b: int32, c: int32, d: int32): bool =
// { bar(); a } < { baz(); b } == { qux(); c } > { qwe(); d };
//
// func bar() {}
// func baz() {}
// func qux() {}
// func qwe() {}
var program = SyntaxTree.Create(CompilationUnit(
FunctionDeclaration(
"foo",
ParameterList(
Parameter("a", NameType("int32")),
Parameter("b", NameType("int32")),
Parameter("c", NameType("int32")),
Parameter("d", NameType("int32"))),
null,
InlineFunctionBody(RelationalExpression(
BlockExpression([ExpressionStatement(CallExpression(NameExpression("bar")))], NameExpression("a")),
ComparisonElement(LessThan, BlockExpression([ExpressionStatement(CallExpression(NameExpression("baz")))], NameExpression("b"))),
ComparisonElement(Equal, BlockExpression([ExpressionStatement(CallExpression(NameExpression("qux")))], NameExpression("c"))),
ComparisonElement(GreaterThan, BlockExpression([ExpressionStatement(CallExpression(NameExpression("qwe")))], NameExpression("d")))))),
FunctionDeclaration("bar", ParameterList(), null, BlockFunctionBody()),
FunctionDeclaration("baz", ParameterList(), null, BlockFunctionBody()),
FunctionDeclaration("qux", ParameterList(), null, BlockFunctionBody()),
FunctionDeclaration("qwe", ParameterList(), null, BlockFunctionBody())));

// Act
var cfg = FunctionToCfg(program);
var dot = cfg.ToDot();

// Assert
await Verify(dot, this.settings);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
digraph ControlFlowGraph {
0 [shape=rectangle, label=<e0: CallExpression(Method = bar, Arguments = [])<br align="left"/>e1: CallExpression(Method = baz, Arguments = [])<br align="left"/>>, xlabel="b0"];
1 [shape=rectangle, label=<e4: RelationalExpression(First = e5, Comparisons = [Comparison(Operator = op_LessThan, Next = e6), Comparison(Operator = op_Equality, Next = e7), Comparison(Operator = op_GreaterThan, Next = e8)])<br align="left"/>e9: ReturnExpression(Value = e4)<br align="left"/>>, xlabel="b1"];
2 [shape=rectangle, label=<e2: CallExpression(Method = qux, Arguments = [])<br align="left"/>>, xlabel="b2"];
3 [shape=rectangle, label=<e3: CallExpression(Method = qwe, Arguments = [])<br align="left"/>>, xlabel="b3"];
0 -> 1 [label="ComparisonFalse(b, op_Equality(Int32, Int32): Boolean, c)"];
0 -> 2 [label="ComparisonTrue(b, op_Equality(Int32, Int32): Boolean, c)"];
2 -> 1 [label="ComparisonFalse(c, op_GreaterThan(Int32, Int32): Boolean, d)"];
2 -> 3 [label="ComparisonTrue(c, op_GreaterThan(Int32, Int32): Boolean, d)"];
3 -> 1 [label="Always"];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
digraph ControlFlowGraph {
0 [shape=rectangle, label=< >, xlabel="b0"];
1 [shape=rectangle, label=< >, xlabel="b1"];
2 [shape=rectangle, label=< >, xlabel="b2"];
3 [shape=rectangle, label=<e0: AssignmentExpression(Left = x, Right = e1)<br align="left"/>>, xlabel="b4"];
4 [shape=rectangle, label=<e2: CallExpression(Method = bar, Arguments = [])<br align="left"/>e3: ReturnExpression(Value = unit)<br align="left"/>>, xlabel="b3"];
0 -> 1 [label="WhenTrue(b)"];
0 -> 2 [label="WhenFalse(b)"];
2 -> 3 [label="Always"];
3 -> 4 [label="Always"];
1 -> 4 [label="Always"];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
digraph ControlFlowGraph {
0 [shape=rectangle, label=<e0: CallExpression(Method = bar, Arguments = [])<br align="left"/>>, xlabel="b0"];
1 [shape=rectangle, label=<e3: ReturnExpression(Value = unit)<br align="left"/>>, xlabel="b1"];
2 [shape=rectangle, label=< >, xlabel="b2"];
3 [shape=rectangle, label=<e1: CallExpression(Method = baz, Arguments = [])<br align="left"/>e2: ReturnExpression(Value = unit)<br align="left"/>>, xlabel="b4"];
4 [shape=rectangle, label=< >, xlabel="b3"];
0 -> 1 [label="WhenTrue(b)"];
0 -> 2 [label="WhenFalse(b)"];
2 -> 3 [label="Always"];
3 -> 4 [label="Always"];
1 -> 4 [label="Always"];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
digraph ControlFlowGraph {
0 [shape=rectangle, label=<e0: ReturnExpression(Value = unit)<br align="left"/>>, xlabel="b0"];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
digraph ControlFlowGraph {
0 [shape=rectangle, label=< >, xlabel="b0"];
1 [shape=rectangle, label=< >, xlabel="b1"];
2 [shape=rectangle, label=<e0: CallExpression(Method = bar, Arguments = [i])<br align="left"/>>, xlabel="b2"];
3 [shape=rectangle, label=<e1: ReturnExpression(Value = unit)<br align="left"/>>, xlabel="b3"];
0 -> 1 [label="Always"];
1 -> 2 [label="SequenceItem(s)"];
1 -> 3 [label="SequenceEnd(s)"];
2 -> 1 [label="Always"];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
digraph ControlFlowGraph {
0 [shape=rectangle, label=< >, xlabel="b0"];
1 [shape=rectangle, label=<e2: CallExpression(Method = bar, Arguments = [])<br align="left"/>>, xlabel="b1"];
2 [shape=rectangle, label=<e0: CallExpression(Method = baz, Arguments = [])<br align="left"/>>, xlabel="b2"];
3 [shape=rectangle, label=<e1: ReturnExpression(Value = unit)<br align="left"/>>, xlabel="b3"];
0 -> 1 [label="WhenTrue(b)"];
0 -> 2 [label="WhenFalse(b)"];
2 -> 3 [label="Always"];
1 -> 3 [label="Always"];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
digraph ControlFlowGraph {
0 [shape=rectangle, label=< >, xlabel="b0"];
1 [shape=rectangle, label=< >, xlabel="b1"];
0 -> 1 [label="Always"];
1 -> 1 [label="Always"];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
digraph ControlFlowGraph {
0 [shape=rectangle, label=<e0: AssignmentExpression(Left = i, Right = 0)<br align="left"/>>, xlabel="b0"];
1 [shape=rectangle, label=<e1: RelationalExpression(First = i, Comparisons = [Comparison(Operator = op_LessThan, Next = 10)])<br align="left"/>>, xlabel="b1"];
2 [shape=rectangle, label=<e2: CallExpression(Method = bar, Arguments = [])<br align="left"/>e3: AssignmentExpression(CompoundOperator = op_Addition, Left = i, Right = 1)<br align="left"/>>, xlabel="b2"];
3 [shape=rectangle, label=<e4: ReturnExpression(Value = unit)<br align="left"/>>, xlabel="b3"];
0 -> 1 [label="Always"];
1 -> 2 [label="WhenTrue(e1)"];
1 -> 3 [label="WhenFalse(e1)"];
2 -> 1 [label="Always"];
}
Loading

0 comments on commit de75c46

Please sign in to comment.