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

Conversation

gafter
Copy link
Member

@gafter gafter commented Mar 24, 2018

This refactoring moves the spiller between initial lowering and lambda lowering

  • Added some improved debug-time display for synthesized locals
  • Added some invariant checking regarding bound nodes.
  • Move invocation of the spiller into LocalRewriter.Rewrite
  • Add an error for when an expression tree contains a switch expression
  • Made the local substituter apply at the top level in the expression spiller because expressions can now contain statements (BoundSpillSequence)
  • Move the error ERR_ByRefTypeAndAwait to IteratorAndAsyncCaptureWalker

There are two iterations:

  • Most of the changes
  • Renaming and moving a file

I separated them to make review easier, as the rename and move makes it look like a file was deleted and one added.

This is built on top of other pending reviews, so even though there are only two new iteration here, you will see iterations from previous PRs. Only the last two matter for this PR.

@agocke @cston Can you review this please?
/cc @dotnet/roslyn-compiler

@gafter gafter added Area-Compilers PR For Personal Review Only The PR doesn’t require anyone other than the developer to review it. Feature - Pattern Matching Pattern Matching labels Mar 24, 2018
@gafter gafter added this to the 16.0 milestone Mar 24, 2018
@gafter gafter self-assigned this Mar 24, 2018
@gafter gafter requested review from a team as code owners March 24, 2018 01:02
@gafter gafter changed the base branch from master to features/recursive-patterns March 24, 2018 01:02
@gafter gafter changed the title WIP recursive-patterns(11): use stack spiller for switch expressions recursive-patterns(11): use stack spiller for switch expressions Mar 25, 2018
@gafter gafter removed the PR For Personal Review Only The PR doesn’t require anyone other than the developer to review it. label Mar 25, 2018
@gafter gafter requested review from agocke and cston March 25, 2018 23:32
@gafter
Copy link
Member Author

gafter commented Mar 25, 2018

This refactoring moves the spiller between initial lowering and lambda lowering

  • Added some improved debug-time display for synthesized locals
  • Added some invariant checking regarding bound nodes.
  • Move invocation of the spiller into LocalRewriter.Rewrite
  • Add an error for when an expression tree contains a switch expression
  • Made the local substituter apply at the top level in the expression spiller because expressions can now contain statements (BoundSpillSequence)
  • Move the error ERR_ByRefTypeAndAwait to IteratorAndAsyncCaptureWalker

There are two iterations:

  • Most of the changes
  • Renaming and moving a file

I separated them to make review easier, as the rename and move makes it look like a file was deleted and one added.

This is built on top of other pending reviews, so even though there are only two new iteration here, you will see iterations from previous PRs. Only the last two matter for this PR.

@agocke @cston Can you review this please?
/cc @dotnet/roslyn-compiler
#Resolved

gafter added 2 commits March 26, 2018 12:39
- Added some improved debug-time display for synthesized locals
- Added some invariant checking regarding bound nodes.
- Move invocation of the spiller into LocalRewriter.Rewrite
- Add an error for when an expression tree contains a switch expression
- Made the local substituter apply at the top level in the expression spiller because expressions can now contain statements (BoundSpillSequence)
- Move the error ERR_ByRefTypeAndAwait to IteratorAndAsyncCaptureWalker
- Skip broken tests due to dotnet#25702
@gafter
Copy link
Member Author

gafter commented Mar 26, 2018

@dotnet/roslyn-interactive I had to skip about 50 interactive tests that are producing bound trees that violate bound tree invariants (they use locals that are not declared). Please see #25702 for details.
#Resolved

@cston
Copy link
Member

cston commented Mar 27, 2018

using System;

Copyright. #Resolved


Refers to: src/Compilers/Core/Portable/Collections/IOrderedReadOnlySet.cs:1 in f7b666d. [](commit_id = f7b666d, deletion_comment = False)

@cston
Copy link
Member

cston commented Mar 27, 2018

using System;

Alternatively, perhaps change MethodToStateMachineRewriter._hoistedVariables to OrderedSet<Symbol> and remove this interface.


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


Refers to: src/Compilers/Core/Portable/Collections/IOrderedReadOnlySet.cs:1 in f7b666d. [](commit_id = f7b666d, deletion_comment = False)

// There is at least one kind of anlysis that cares about this distinction - async stack spilling
// Used internally to track `In` arguments that were specified with `In` modifier
// as opposed to those that were specified with no modifiers and matched `In` parameter.
// There is at least one kind of anlysis that cares about this distinction - hoisting
Copy link
Member

@cston cston Mar 27, 2018

Choose a reason for hiding this comment

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

anlysis [](start = 41, length = 7)

Typo. #Resolved

@@ -5348,4 +5348,7 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
<data name="WRN_SwitchExpressionNotExhaustive_Title" xml:space="preserve">
<value>The switch expression does not handle all possible inputs (it is not exhaustive).</value>
</data>
<data name="ERR_ExpressionTreeContainsSwitchExpression" xml:space="preserve">
<value>An expression tree may not contain a switch-expression.</value>
Copy link
Member

@cston cston Mar 27, 2018

Choose a reason for hiding this comment

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

switch-expression [](start = 48, length = 17)

Perhaps switch expression (no hyphen) to match the other diagnostics. #Resolved

@@ -172,5 +194,227 @@ internal string GetDebuggerDisplay()
}
return result;
}

public void CheckLocalsDefined()
Copy link
Member

@cston cston Mar 27, 2018

Choose a reason for hiding this comment

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

CheckLocalsDefined [](start = 20, length = 18)

[Conditional("DEBUG")] #Resolved

localsScanner.Free();
}

void AddAll(ImmutableArray<LocalSymbol> locals)
Copy link
Member

@cston cston Mar 27, 2018

Choose a reason for hiding this comment

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

void [](start = 12, length = 4)

private for these three methods. #Resolved

{
if (_inExpressionLambda)
{
Error(ErrorCode.ERR_ExpressionTreeContainsSwitchExpression, node);
Copy link
Member

@cston cston Mar 27, 2018

Choose a reason for hiding this comment

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

ERR_ExpressionTreeContainsSwitchExpression [](start = 32, length = 42)

Is there a test for this code path? #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.

A test will be added in a forthcoming iteration.


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

builder.AddLocals(node.Locals);
var value = VisitExpression(ref builder, node.Value);
value = Spill(builder, value);
return builder.Update(value);
}

public override BoundNode VisitAddressOfOperator(BoundAddressOfOperator node)
Copy link
Member

@cston cston Mar 27, 2018

Choose a reason for hiding this comment

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

ic override BoundNode Vi [](start = 12, length = 24)

Why is this necessary? Consider adding a comment. #Resolved

public override BoundNode VisitLocalFunctionStatement(BoundLocalFunctionStatement node)
{
var oldCurrentMethod = _F.CurrentMethod;
_F.CurrentMethod = node.Symbol;
Copy link
Member

@cston cston Mar 27, 2018

Choose a reason for hiding this comment

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

node.Symbo [](start = 30, length = 11)

We don't need a separate SpillSequenceSpiller instance when visiting lamba and local function bodies? #ByDesign

Copy link
Member Author

Choose a reason for hiding this comment

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

Correct, because things are spilled up to the statement level. Lambdas and local functions have a top-level block containing statements at this stage.


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

{
// CS4013: Instance of type '{0}' cannot be used inside an anonymous function, query expression, iterator block or async method
diagnostics.Add(ErrorCode.ERR_SpecialByRefInLambda, syntax.Location, type);
diagnostics.Add(ErrorCode.ERR_ByRefTypeAndAwait, local.Locations[0], local.Type);
Copy link
Member

@cston cston Mar 27, 2018

Choose a reason for hiding this comment

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

ERR_ByRefTypeAndAwait [](start = 50, length = 21)

Does this correspond to the restricted type case previously in AwaitExpressionSpiller.AddLocal? If so, perhaps add Debug.Assert(local.Type.IsRestrictedType());. #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.

That is true by construction. There is only one line that adds to the set from which the symbol was fetched:

            if (type.IsRestrictedType())
            {
                // ...

                _lazyDisallowedCaptures.Add(variable, syntax);
            }

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

case SyntaxKind.SwitchExpression:
case SyntaxKind.AwaitExpression:
// These translate into a BoundSpillSequence, which is then translated into a block
// containins temps required for spilling subexpressions. That block has the syntax of the switch
Copy link
Member

@cston cston Mar 27, 2018

Choose a reason for hiding this comment

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

containins [](start = 23, length = 10)

Typo #Resolved

// The resulting nodes will be "spilled" to move such statements to the top
// level (i.e. into the enclosing statement list).
_needsSpilling = true;
BoundLocal replacement = _factory.StoreToTemp(
Copy link
Member Author

@gafter gafter Mar 29, 2018

Choose a reason for hiding this comment

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

Is it possible that this needs to be a ref temp? #Resolved

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.

await is never an lvalue, so this temp is never a ref temp.
In general case we can have byref spills.

In a case of await, they will be later decomposed into something that can be hoisted - like {array, index} pairs or will produce an error if it is a true byref spill - like a result of byref method call.

In a case when spill is caused by expression switch or stackalloc (in the future), byref spills would work just fine since we do not need to hoist - ref temps stay ref temps.

Perhaps need a bit more care to make sure that "readonliness" matches. - I.E. if spilling an in argument or a receiver of readonly struct member, the temp must be ref readonly. Not sure if this is already handled correctly.
It may be particularly important when spilling receivers of struct/generic calls, since mismatch in "readonly" may cause different behavior for the copying of the receiver. #Resolved

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.

The part about "readonly" is probably better handled as a separate workitem. It would be a new scenario for spiller.
In await case we decompose ref spills, so readonlyness of temps did not matter since they would not survive. #Resolved

Copy link
Member

@agocke agocke left a comment

Choose a reason for hiding this comment

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

LGTM

{
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)

if (localRewriter._needsSpilling && !loweredStatement.HasErrors)
{
// Move spill sequences to a top-level statement. This handles "lifting" await and the switch expression.
// PROTOTYPE(patterns2): should this be something that the caller does? It isn't really a "local" rewrite.
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.

I think it is better be handled by the caller, similarly to handling await in exception handling regions.

  • to have clear sequence of tree transformation.
  • to have better attribution when investigating perf impact of lowering passes. #ByDesign

Copy link
Member Author

Choose a reason for hiding this comment

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

I placed this here because there is more than one caller. However We could duplicate this code for each caller. I am not sure which is better, but this simplifies the job of the caller.


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

@@ -14,14 +14,14 @@

namespace Microsoft.CodeAnalysis.CSharp
{
internal sealed class AwaitExpressionSpiller : BoundTreeRewriterWithStackGuard
internal sealed class SpillSequenceSpiller : BoundTreeRewriterWithStackGuard
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.

We are likely unable to handle some expressions that are not permitted in async methods - unsafe stuff and such.

I think it is a separate workitem, but we need to make sure that all or nearly all bound expresions can be spilled. #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 switch expression can appear in an unsafe method, so spilling needs to work with almost everything. If you have specific scenarios that would illustrate the issues, that would be very helpful. Please file as a fresh issue.


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

Copy link
Member

@VSadov VSadov left a comment

Choose a reason for hiding this comment

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

LGTM
I had some comments, but I think it may be better to handle them as a separate change

DecisionPoint,
WhenClause,
Decision,
DecisionDag,
Copy link
Member

@alrz alrz Mar 29, 2018

Choose a reason for hiding this comment

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

Does the order of these members matters (in a sense that related nodes would be closer together for better switch optimizations)? Currently decision tree nodes come in the middle of statements. #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.

There are only four kinds of dag nodes, and they are rarely treated the same. I will file an issue to move them all after statements.


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

@gafter
Copy link
Member Author

gafter commented Mar 30, 2018

using System;

OrderedSet is mutable. I did this to avoid exposing mutability.


In reply to: 376628270 [](ancestors = 376628270,376626943)


Refers to: src/Compilers/Core/Portable/Collections/IOrderedReadOnlySet.cs:1 in f7b666d. [](commit_id = f7b666d, deletion_comment = False)

@gafter gafter merged commit 952f566 into dotnet:features/recursive-patterns Mar 30, 2018
gafter added a commit to gafter/roslyn that referenced this pull request Mar 30, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants