From 98bfe321608eb4e12c7c4704c7e61c661b16605a Mon Sep 17 00:00:00 2001 From: Alireza Habibi Date: Thu, 8 Dec 2022 00:32:50 +0330 Subject: [PATCH] WIP --- .../Portable/BoundTree/BoundDecisionDag.cs | 14 +- .../LocalRewriter_IsPatternOperator.cs | 351 ++++-------------- 2 files changed, 92 insertions(+), 273 deletions(-) diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundDecisionDag.cs b/src/Compilers/CSharp/Portable/BoundTree/BoundDecisionDag.cs index 8292b3878d0da..9c9746ec6cfbc 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundDecisionDag.cs +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundDecisionDag.cs @@ -84,7 +84,7 @@ public ImmutableArray TopologicallySortedNodes /// takes as its input the node to be rewritten and a function that returns the previously computed /// rewritten node for successor nodes. /// - public BoundDecisionDag Rewrite(Func, BoundDecisionDagNode> makeReplacement) + public T Rewrite(Func, T> makeReplacement) { // 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 @@ -93,20 +93,26 @@ public BoundDecisionDag Rewrite(Func.GetInstance(); + var replacement = PooledDictionary.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--) { BoundDecisionDagNode node = sortedNodes[i]; Debug.Assert(!replacement.ContainsKey(node)); - BoundDecisionDagNode newNode = makeReplacement(node, replacement); + T newNode = makeReplacement(node, replacement); replacement.Add(node, newNode); } // Return the computed replacement root node - var newRoot = replacement[this.RootNode]; + var result = replacement[this.RootNode]; replacement.Free(); + return result; + } + + public BoundDecisionDag Rewrite(Func, BoundDecisionDagNode> makeReplacement) + { + var newRoot = Rewrite(makeReplacement); return this.Update(newRoot); } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IsPatternOperator.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IsPatternOperator.cs index 3ea8a02242513..19083214f8eda 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IsPatternOperator.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_IsPatternOperator.cs @@ -2,8 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; +using System.Linq; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; @@ -14,310 +17,120 @@ internal sealed partial class LocalRewriter { public override BoundNode VisitIsPatternExpression(BoundIsPatternExpression node) { - BoundDecisionDag decisionDag = node.GetDecisionDagForLowering(_factory.Compilation); - bool negated = node.IsNegated; - BoundExpression result; - if (canProduceLinearSequence(decisionDag.RootNode, whenTrueLabel: node.WhenTrueLabel, whenFalseLabel: node.WhenFalseLabel)) - { - // If we can build a linear test sequence `(e1 && e2 && e3)` for the dag, do so. - var isPatternRewriter = new IsPatternExpressionLinearLocalRewriter(node, this); - result = isPatternRewriter.LowerIsPatternAsLinearTestSequence(node, decisionDag, whenTrueLabel: node.WhenTrueLabel, whenFalseLabel: node.WhenFalseLabel); - isPatternRewriter.Free(); - } - else if (canProduceLinearSequence(decisionDag.RootNode, whenTrueLabel: node.WhenFalseLabel, whenFalseLabel: node.WhenTrueLabel)) - { - // If we can build a linear test sequence with the whenTrue and whenFalse labels swapped, then negate the - // result. This would typically arise when the source contains `e is not pattern`. - negated = !negated; - var isPatternRewriter = new IsPatternExpressionLinearLocalRewriter(node, this); - result = isPatternRewriter.LowerIsPatternAsLinearTestSequence(node, decisionDag, whenTrueLabel: node.WhenFalseLabel, whenFalseLabel: node.WhenTrueLabel); - isPatternRewriter.Free(); - } - else - { - // We need to lower a generalized dag, so we produce a label for the true and false branches and assign to a temporary containing the result. - var isPatternRewriter = new IsPatternExpressionGeneralLocalRewriter(node.Syntax, this); - result = isPatternRewriter.LowerGeneralIsPattern(node, decisionDag); - isPatternRewriter.Free(); - } + var isPatternRewriter = new IsPatternExpressionLinearLocalRewriter(node, this); + BoundExpression result = isPatternRewriter.LowerIsPatternAsLinearTestSequence(node); + isPatternRewriter.Free(); - if (negated) + if (node.IsNegated) { result = this._factory.Not(result); } + return result; - - // Can the given decision dag node, and its successors, be generated as a sequence of - // linear tests with a single "golden" path to the true label and all other paths leading - // to the false label? This occurs with an is-pattern expression that uses no "or" or "not" - // pattern forms. - static bool canProduceLinearSequence( - BoundDecisionDagNode node, - LabelSymbol whenTrueLabel, - LabelSymbol whenFalseLabel) - { - while (true) - { - switch (node) - { - case BoundWhenDecisionDagNode w: - Debug.Assert(w.WhenFalse is null); - node = w.WhenTrue; - break; - case BoundLeafDecisionDagNode n: - return n.Label == whenTrueLabel; - case BoundEvaluationDecisionDagNode e: - node = e.Next; - break; - case BoundTestDecisionDagNode t: - bool falseFail = IsFailureNode(t.WhenFalse, whenFalseLabel); - if (falseFail == IsFailureNode(t.WhenTrue, whenFalseLabel)) - return false; - node = falseFail ? t.WhenTrue : t.WhenFalse; - break; - default: - throw ExceptionUtilities.UnexpectedValue(node); - } - } - } - } - - /// - /// A local rewriter for lowering an is-pattern expression. This handles the general case by lowering - /// the decision dag, and returning a "true" or "false" value as the result at the end. - /// - private sealed class IsPatternExpressionGeneralLocalRewriter : DecisionDagRewriter - { - private readonly ArrayBuilder _statements = ArrayBuilder.GetInstance(); - - public IsPatternExpressionGeneralLocalRewriter( - SyntaxNode node, - LocalRewriter localRewriter) : base(node, localRewriter, generateInstrumentation: false) - { - } - - protected override ArrayBuilder BuilderForSection(SyntaxNode section) => _statements; - - public new void Free() - { - base.Free(); - _statements.Free(); - } - - internal BoundExpression LowerGeneralIsPattern(BoundIsPatternExpression node, BoundDecisionDag decisionDag) - { - _factory.Syntax = node.Syntax; - var resultBuilder = ArrayBuilder.GetInstance(); - var inputExpression = _localRewriter.VisitExpression(node.Expression); - decisionDag = ShareTempsIfPossibleAndEvaluateInput(decisionDag, inputExpression, resultBuilder, out _); - - // lower the decision dag. - ImmutableArray loweredDag = LowerDecisionDagCore(decisionDag); - resultBuilder.Add(_factory.Block(loweredDag)); - Debug.Assert(node.Type is { SpecialType: SpecialType.System_Boolean }); - LocalSymbol resultTemp = _factory.SynthesizedLocal(node.Type, node.Syntax, kind: SynthesizedLocalKind.LoweringTemp); - LabelSymbol afterIsPatternExpression = _factory.GenerateLabel("afterIsPatternExpression"); - LabelSymbol trueLabel = node.WhenTrueLabel; - LabelSymbol falseLabel = node.WhenFalseLabel; - if (_statements.Count != 0) - resultBuilder.Add(_factory.Block(_statements.ToArray())); - resultBuilder.Add(_factory.Label(trueLabel)); - resultBuilder.Add(_factory.Assignment(_factory.Local(resultTemp), _factory.Literal(true))); - resultBuilder.Add(_factory.Goto(afterIsPatternExpression)); - resultBuilder.Add(_factory.Label(falseLabel)); - resultBuilder.Add(_factory.Assignment(_factory.Local(resultTemp), _factory.Literal(false))); - resultBuilder.Add(_factory.Label(afterIsPatternExpression)); - _localRewriter._needsSpilling = true; - return _factory.SpillSequence(_tempAllocator.AllTemps().Add(resultTemp), resultBuilder.ToImmutableAndFree(), _factory.Local(resultTemp)); - } - } - - private static bool IsFailureNode(BoundDecisionDagNode node, LabelSymbol whenFalseLabel) - { - if (node is BoundWhenDecisionDagNode w) - node = w.WhenTrue; - return node is BoundLeafDecisionDagNode l && l.Label == whenFalseLabel; } private sealed class IsPatternExpressionLinearLocalRewriter : PatternLocalRewriter { - /// - /// Accumulates side-effects that come before the next conjunct. - /// - private readonly ArrayBuilder _sideEffectBuilder; - - /// - /// Accumulates conjuncts (conditions that must all be true) for the translation. When a conjunct is added, - /// elements of the _sideEffectBuilder, if any, should be added as part of a sequence expression for - /// the conjunct being added. - /// - private readonly ArrayBuilder _conjunctBuilder; - public IsPatternExpressionLinearLocalRewriter(BoundIsPatternExpression node, LocalRewriter localRewriter) : base(node.Syntax, localRewriter, generateInstrumentation: false) { - _conjunctBuilder = ArrayBuilder.GetInstance(); - _sideEffectBuilder = ArrayBuilder.GetInstance(); } - public new void Free() + public BoundExpression LowerIsPatternAsLinearTestSequence(BoundIsPatternExpression isPatternExpression) { - _conjunctBuilder.Free(); - _sideEffectBuilder.Free(); - base.Free(); - } + BoundDecisionDag decisionDag = isPatternExpression.GetDecisionDagForLowering(_factory.Compilation); + LabelSymbol whenTrueLabel = isPatternExpression.WhenTrueLabel; + BoundExpression loweredInput = _localRewriter.VisitExpression(isPatternExpression.Expression); + + var sideEffectBuilder = ArrayBuilder.GetInstance(); + // The optimization of sharing pattern-matching temps with user variables can always apply to + // an is-pattern expression because there is no when clause that could possibly intervene during + // the execution of the pattern-matching automaton and change one of those variables. + decisionDag = ShareTempsAndEvaluateInput(loweredInput, decisionDag, expr => sideEffectBuilder.Add(expr), out _); - private void AddConjunct(BoundExpression test) - { - // When in error recovery, the generated code doesn't matter. - if (test.Type?.IsErrorType() != false) - return; - Debug.Assert(test.Type.SpecialType == SpecialType.System_Boolean); - if (_sideEffectBuilder.Count != 0) + BoundWhenDecisionDagNode? whenNodeOpt = null; + BoundExpression result = decisionDag.Rewrite(makeReplacement); + + if (sideEffectBuilder.Any()) { - test = _factory.Sequence(ImmutableArray.Empty, _sideEffectBuilder.ToImmutable(), test); - _sideEffectBuilder.Clear(); + result = _factory.Sequence(ImmutableArray.Empty, sideEffectBuilder.ToImmutableAndClear(), result); } - - _conjunctBuilder.Add(test); - } - - /// - /// Translate the single test into _sideEffectBuilder and _conjunctBuilder. - /// - private void LowerOneTest(BoundDagTest test, bool invert = false) - { - _factory.Syntax = test.Syntax; - switch (test) + + if (whenNodeOpt is not null) { - case BoundDagEvaluation eval: - { - var sideEffect = LowerEvaluation(eval); - _sideEffectBuilder.Add(sideEffect); - return; - } - case var _: + foreach (BoundPatternBinding binding in whenNodeOpt.Bindings) + { + BoundExpression left = _localRewriter.VisitExpression(binding.VariableAccess); + BoundExpression right = _tempAllocator.GetTemp(binding.TempContainingValue); + if (left != right) { - var testExpression = LowerTest(test); - if (testExpression != null) - { - if (invert) - testExpression = _factory.Not(testExpression); - - AddConjunct(testExpression); - } - - return; + sideEffectBuilder.Add(_factory.AssignmentExpression(left, right)); } + } } - } - - public BoundExpression LowerIsPatternAsLinearTestSequence( - BoundIsPatternExpression isPatternExpression, - BoundDecisionDag decisionDag, - LabelSymbol whenTrueLabel, - LabelSymbol whenFalseLabel) - { - BoundExpression loweredInput = _localRewriter.VisitExpression(isPatternExpression.Expression); - // The optimization of sharing pattern-matching temps with user variables can always apply to - // an is-pattern expression because there is no when clause that could possibly intervene during - // the execution of the pattern-matching automaton and change one of those variables. - decisionDag = ShareTempsAndEvaluateInput(loweredInput, decisionDag, expr => _sideEffectBuilder.Add(expr), out _); - var node = decisionDag.RootNode; - return ProduceLinearTestSequence(node, whenTrueLabel, whenFalseLabel); - } + if (sideEffectBuilder.Any()) + { + result = _factory.LogicalAnd(result, _factory.Sequence(ImmutableArray.Empty, sideEffectBuilder.ToImmutable(), _factory.Literal(true))); + } + + var allTemps = _tempAllocator.AllTemps(); + if (allTemps.Any()) + { + result = _factory.Sequence(allTemps, ImmutableArray.Empty, result); + } + + sideEffectBuilder.Free(); + return result; - /// - /// Translate an is-pattern expression into a sequence of tests separated by the control-flow-and operator. - /// - private BoundExpression ProduceLinearTestSequence( - BoundDecisionDagNode node, - LabelSymbol whenTrueLabel, - LabelSymbol whenFalseLabel) - { - // We follow the "good" path in the decision dag. We depend on it being nicely linear in structure. - // If we add "or" patterns that assumption breaks down. - while (node.Kind != BoundKind.LeafDecisionDagNode && node.Kind != BoundKind.WhenDecisionDagNode) + BoundExpression makeReplacement(BoundDecisionDagNode node, IReadOnlyDictionary map) { switch (node) { case BoundEvaluationDecisionDagNode evalNode: - { - LowerOneTest(evalNode.Evaluation); - node = evalNode.Next; - } - break; - case BoundTestDecisionDagNode testNode: - { - if (testNode.WhenTrue is BoundEvaluationDecisionDagNode e && - TryLowerTypeTestAndCast(testNode.Test, e.Evaluation, out BoundExpression? sideEffect, out BoundExpression? testExpression)) - { - _sideEffectBuilder.Add(sideEffect); - AddConjunct(testExpression); - node = e.Next; - } - else - { - bool invertTest = IsFailureNode(testNode.WhenTrue, whenFalseLabel); - LowerOneTest(testNode.Test, invertTest); - node = invertTest ? testNode.WhenFalse : testNode.WhenTrue; - } - } - break; - } - } + BoundExpression next = map[evalNode.Next]; + return next is BoundSequence seq + ? seq.Update(seq.Locals, seq.SideEffects.Insert(0, LowerEvaluation(evalNode.Evaluation)), seq.Value, seq.Type) + : _factory.Sequence(ImmutableArray.Empty, ImmutableArray.Create(LowerEvaluation(evalNode.Evaluation)), next); - // When we get to "the end", it is a success node. - switch (node) - { - case BoundLeafDecisionDagNode leafNode: - Debug.Assert(leafNode.Label == whenTrueLabel); - break; + case BoundLeafDecisionDagNode leafNode: + return _factory.Literal(leafNode.Label == whenTrueLabel); - case BoundWhenDecisionDagNode whenNode: - { - Debug.Assert(whenNode.WhenExpression == null); - Debug.Assert(whenNode.WhenTrue is BoundLeafDecisionDagNode d && d.Label == whenTrueLabel); - foreach (BoundPatternBinding binding in whenNode.Bindings) + case BoundTestDecisionDagNode testNode: + if (testNode.WhenTrue is BoundEvaluationDecisionDagNode e && + TryLowerTypeTestAndCast(testNode.Test, e.Evaluation, out BoundExpression? sideEffect, out BoundExpression? testExpression)) { - BoundExpression left = _localRewriter.VisitExpression(binding.VariableAccess); - BoundExpression right = _tempAllocator.GetTemp(binding.TempContainingValue); - if (left != right) - { - _sideEffectBuilder.Add(_factory.AssignmentExpression(left, right)); - } + return _factory.LogicalAnd(_factory.Sequence(ImmutableArray.Empty, ImmutableArray.Create(sideEffect), testExpression), map[e.Next]); } - } - - break; - - default: - throw ExceptionUtilities.UnexpectedValue(node.Kind); - } - - if (_sideEffectBuilder.Count > 0 || _conjunctBuilder.Count == 0) - { - AddConjunct(_factory.Literal(true)); - } - - Debug.Assert(_sideEffectBuilder.Count == 0); - BoundExpression? result = null; - foreach (BoundExpression conjunct in _conjunctBuilder) - { - result = (result == null) ? conjunct : _factory.LogicalAnd(result, conjunct); - } - _conjunctBuilder.Clear(); - Debug.Assert(result != null); - var allTemps = _tempAllocator.AllTemps(); - if (allTemps.Length > 0) - { - result = _factory.Sequence(allTemps, ImmutableArray.Empty, result); + BoundExpression whenTrue = map[testNode.WhenTrue]; + BoundExpression whenFalse = map[testNode.WhenFalse]; + return (whenTrue.ConstantValue?.BooleanValue, whenFalse.ConstantValue?.BooleanValue) switch + { + (true, true) => whenTrue, + (false, false) => whenFalse, + (true, false) => LowerTest(testNode.Test), + (false, true) => _factory.Not(LowerTest(testNode.Test)), + (null, true) => _factory.LogicalOr(_factory.Not(LowerTest(testNode.Test)), whenTrue), + (null, false) => _factory.LogicalAnd(LowerTest(testNode.Test), whenTrue), + (true, null) => _factory.LogicalOr(LowerTest(testNode.Test), whenFalse), + (false, null) => _factory.LogicalAnd(_factory.Not(LowerTest(testNode.Test)), whenFalse), + (null, null) => _factory.Conditional(LowerTest(testNode.Test), whenTrue, whenFalse, whenTrue.Type) + }; + + case BoundWhenDecisionDagNode whenNode: + Debug.Assert(whenNode.WhenExpression is null); + Debug.Assert(whenNode.WhenTrue is BoundLeafDecisionDagNode d && d.Label == whenTrueLabel); + Debug.Assert(whenNode.WhenFalse is null); + Debug.Assert(whenNodeOpt is null); + whenNodeOpt = whenNode; + return map[whenNode.WhenTrue]; + + case var v: + throw ExceptionUtilities.UnexpectedValue(v); + } } - - return result; } } }