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

recursive-patterns(11): use stack spiller for switch expressions #25697

Merged
merged 5 commits into from
Mar 30, 2018
Merged
Show file tree
Hide file tree
Changes from 7 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
698 changes: 375 additions & 323 deletions src/Compilers/CSharp/Portable/Binder/DecisionDagBuilder.cs

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ internal override BoundStatement BindSwitchStatementCore(SwitchStatementSyntax n
ImmutableArray<BoundPatternSwitchSection> switchSections = BindPatternSwitchSections(originalBinder, diagnostics, out BoundPatternSwitchLabel defaultLabel);
ImmutableArray<LocalSymbol> locals = GetDeclaredLocalsForScope(node);
ImmutableArray<LocalFunctionSymbol> functions = GetDeclaredLocalFunctionsForScope(node);
BoundDecisionDag decisionDag = DecisionDagBuilder.CreateDecisionDag(
BoundDecisionDag decisionDag = DecisionDagBuilder.CreateDecisionDagForSwitchStatement(
compilation: this.Compilation,
syntax: node,
switchGoverningExpression: boundSwitchGoverningExpression,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ private bool CheckSwitchExpressionExhaustive(
DiagnosticBag diagnostics)
{
defaultLabel = new GeneratedLabelSymbol("default");
decisionDag = DecisionDagBuilder.CreateDecisionDag(this.Compilation, node, boundInputExpression, switchArms, defaultLabel, diagnostics);
decisionDag = DecisionDagBuilder.CreateDecisionDagForSwitchExpression(this.Compilation, node, boundInputExpression, switchArms, defaultLabel, diagnostics);
HashSet<LabelSymbol> reachableLabels = decisionDag.ReachableLabels;
foreach (BoundSwitchExpressionArm arm in switchArms)
{
Expand Down
78 changes: 44 additions & 34 deletions src/Compilers/CSharp/Portable/BoundTree/BoundDecisionDag.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,27 @@

namespace Microsoft.CodeAnalysis.CSharp
{
partial class BoundDecisionDag
internal partial class BoundDecisionDag
{
public IEnumerable<BoundDecisionDag> Successors()
private HashSet<LabelSymbol> _reachableLabels;
private ImmutableArray<BoundDecisionDagNode> _topologicallySortedNodes;

private static IEnumerable<BoundDecisionDagNode> Successors(BoundDecisionDagNode node)
{
switch (this)
switch (node)
{
case BoundEvaluationPoint p:
case BoundEvaluationDecisionDagNode p:
yield return p.Next;
yield break;
case BoundDecisionPoint p:
case BoundTestDecisionDagNode p:
Debug.Assert(p.WhenFalse != null);
yield return p.WhenFalse;
Debug.Assert(p.WhenTrue != null);
yield return p.WhenTrue;
yield break;
case BoundDecision d:
case BoundLeafDecisionDagNode d:
yield break;
case BoundWhenClause w:
case BoundWhenDecisionDagNode w:
Debug.Assert(w.WhenTrue != null);
yield return w.WhenTrue;
if (w.WhenFalse != null)
Expand All @@ -37,12 +40,10 @@ public IEnumerable<BoundDecisionDag> Successors()

yield break;
default:
throw ExceptionUtilities.UnexpectedValue(this.Kind);
throw ExceptionUtilities.UnexpectedValue(node.Kind);
}
}

private HashSet<LabelSymbol> _reachableLabels;

public HashSet<LabelSymbol> ReachableLabels
{
get
Expand All @@ -51,28 +52,36 @@ public HashSet<LabelSymbol> ReachableLabels
{
// PROTOTYPE(patterns2): avoid linq?
_reachableLabels = new HashSet<LabelSymbol>(
this.TopologicallySortedNodes().OfType<BoundDecision>().Select(d => d.Label));
this.TopologicallySortedNodes.OfType<BoundLeafDecisionDagNode>().Select(d => d.Label));
}

return _reachableLabels;
}
}

/// <summary>
/// Return a list of all the nodes reachable from this one, in a topologically sorted order.
/// A list of all the nodes reachable from this one, in a topologically sorted order.
/// </summary>
public ImmutableArray<BoundDecisionDag> TopologicallySortedNodes()
public ImmutableArray<BoundDecisionDagNode> TopologicallySortedNodes
{
// We use an iterative topological sort to avoid overflowing the compiler's runtime stack for a large switch statement.
return TopologicalSort.IterativeSort<BoundDecisionDag>(new[] { this }, d => d.Successors());
get
{
if (_topologicallySortedNodes.IsDefault)
{
// We use an iterative topological sort to avoid overflowing the compiler's runtime stack for a large switch statement.
_topologicallySortedNodes = TopologicalSort.IterativeSort<BoundDecisionDagNode>(new[] { this.RootNode }, Successors);
}

return _topologicallySortedNodes;
}
}

/// <summary>
/// Given a decision dag and a constant-valued input, produce a simplified decision dag that has removed all the
/// tests that are unnecessary due to that constant value. This simplification affects flow analysis (reachability
/// and definite assignment) and permits us to simplify the generated code.
/// </summary>
internal BoundDecisionDag SimplifyDecisionDagForConstantInput(
public BoundDecisionDag SimplifyDecisionDagForConstantInput(
BoundExpression input,
Conversions conversions,
DiagnosticBag diagnostics)
Expand All @@ -83,44 +92,45 @@ internal BoundDecisionDag SimplifyDecisionDagForConstantInput(
// First, we topologically sort the nodes of the dag so that we can translate the nodes bottom-up.
// This will avoid overflowing the compiler's runtime stack which would occur for a large switch
// statement if we were using a recursive strategy.
ImmutableArray<BoundDecisionDag> sortedNodes = this.TopologicallySortedNodes();
ImmutableArray<BoundDecisionDagNode> sortedNodes = this.TopologicallySortedNodes;

// Cache simplified/translated replacement for each translated dag node. Since we always visit
// a node's successors before the node, the replacement should always be in the cache when we need it.
var replacement = PooledDictionary<BoundDecisionDag, BoundDecisionDag>.GetInstance();
var replacement = PooledDictionary<BoundDecisionDagNode, BoundDecisionDagNode>.GetInstance();

// Loop backwards through the topologically sorted nodes to translate them, so that we always visit a node after its successors
for (int i = sortedNodes.Length - 1; i >= 0; i--)
{
BoundDecisionDag node = sortedNodes[i];
BoundDecisionDagNode node = sortedNodes[i];
Debug.Assert(!replacement.ContainsKey(node));
BoundDecisionDag newNode = makeReplacement(node);
BoundDecisionDagNode newNode = makeReplacement(node);
replacement.Add(node, newNode);
}

var result = replacement[this];
// Return the computed replacement root node
var newRoot = replacement[this.RootNode];
replacement.Free();
return result;
return this.Update(newRoot);

// Make a replacement for a given node, using the precomputed replacements for its successors.
BoundDecisionDag makeReplacement(BoundDecisionDag dag)
BoundDecisionDagNode makeReplacement(BoundDecisionDagNode dag)
{
switch (dag)
{
case BoundEvaluationPoint p:
case BoundEvaluationDecisionDagNode p:
Copy link
Member

@VSadov VSadov Mar 29, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these types sealed? (for faster typechecks) #Resolved

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The bound node generator only generates abstract or sealed types.


In reply to: 178199580 [](ancestors = 178199580)

return p.Update(p.Evaluation, replacement[p.Next]);
case BoundDecisionPoint p:
case BoundTestDecisionDagNode p:
// This is the key to the optimization. The result of a top-level test might be known if the input is constant.
switch (knownResult(p.Decision))
switch (knownResult(p.Test))
{
case true:
return replacement[p.WhenTrue];
case false:
return replacement[p.WhenFalse];
default:
return p.Update(p.Decision, replacement[p.WhenTrue], replacement[p.WhenFalse]);
return p.Update(p.Test, replacement[p.WhenTrue], replacement[p.WhenFalse]);
}
case BoundWhenClause p:
case BoundWhenDecisionDagNode p:
if (p.WhenExpression == null || p.WhenExpression.ConstantValue == ConstantValue.True)
{
return p.Update(p.Bindings, p.WhenExpression, replacement[p.WhenTrue], null);
Expand All @@ -136,15 +146,15 @@ BoundDecisionDag makeReplacement(BoundDecisionDag dag)
{
return p.Update(p.Bindings, p.WhenExpression, replacement[p.WhenTrue], replacement[p.WhenFalse]);
}
case BoundDecision p:
case BoundLeafDecisionDagNode p:
return p;
default:
throw ExceptionUtilities.UnexpectedValue(dag);
}
}

// Is the decision's result known because the input is a constant?
bool? knownResult(BoundDagDecision choice)
bool? knownResult(BoundDagTest choice)
{
if (choice.Input.Source != null)
{
Expand All @@ -154,13 +164,13 @@ BoundDecisionDag makeReplacement(BoundDecisionDag dag)

switch (choice)
{
case BoundNullValueDecision d:
case BoundDagNullTest d:
return inputConstant.IsNull;
case BoundNonNullDecision d:
case BoundDagNonNullTest d:
return !inputConstant.IsNull;
case BoundNonNullValueDecision d:
case BoundDagValueTest d:
return d.Value == inputConstant;
case BoundTypeDecision d:
case BoundDagTypeTest d:
HashSet<DiagnosticInfo> useSiteDiagnostics = null;
bool? known = Binder.ExpressionOfTypeMatchesPatternType(conversions, input.Type, d.Type, ref useSiteDiagnostics, out Conversion conversion, inputConstant, inputConstant.IsNull);
diagnostics.Add(d.Syntax, useSiteDiagnostics);
Expand Down
Loading