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

Avoid sharing NullableWalker state between method and nested functions #49765

Merged
merged 9 commits into from
Dec 4, 2020

Conversation

cston
Copy link
Member

@cston cston commented Dec 3, 2020

When analyzing a method in NullableWalker, avoid sharing _variableTypes and _variableSlots between that method and any lambdas or local functions. Currently, the parameters and locals for any nested functions are added to the _variableTypes and _variableSlots tables in the containing method, adding to the state tracked for containing method and any subsequent nested functions. If there are many nested functions, this can significantly increase the size of the tables that are tracked, particularly for the semantic model.

Fixes #49745

@AlekseyTs
Copy link
Contributor

AlekseyTs commented Dec 3, 2020

@cston Could you provide more details about the issue? Is this just a matter of "lifetime" for the slots, or did we add new slots for the same things every time we analyzed the same local function on a new pass? #Closed

@@ -2623,6 +2623,18 @@ private void VisitStatementsWithLocalFunctions(BoundBlock block)

var oldState = this.State;
this.State = state;
var oldVariableSlot = _variableSlot;
_variableSlot = PooledDictionary<VariableIdentifier, int>.GetInstance();
Copy link
Contributor

@AlekseyTs AlekseyTs Dec 3, 2020

Choose a reason for hiding this comment

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

_variableSlot [](start = 12, length = 13)

Should we also capture and reset variableBySlot and nextVariableSlot? #Closed

Copy link
Member Author

Choose a reason for hiding this comment

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

Good catch, thanks.


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

@cston
Copy link
Member Author

cston commented Dec 3, 2020

@AlekseyTs, I've updated the description to give details, thanks.

_variableTypes.Free();
_variableTypes = oldVariableTypes;
_variableSlot.Free();
_variableSlot = oldVariableSlot;
Copy link
Contributor

@AlekseyTs AlekseyTs Dec 3, 2020

Choose a reason for hiding this comment

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

_variableSlot [](start = 12, length = 13)

Is it possible that, while analyzing a local function, we allocate a slot for something that is not scoped to the body of the function and, therefore, the slot should be shared with containing method? #Closed

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 containing method state is reset after the nested function is visited so any slots added in the method can be ignored.


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

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 containing method state is reset after the nested function is visited so any slots added in the method can be ignored.

That may not be true. _localFuncVarUsages may have been updated and that tracks state as well.


In reply to: 535547721 [](ancestors = 535547721,535419178)

@AlekseyTs
Copy link
Contributor

AlekseyTs commented Dec 3, 2020

Done with review pass (commit 1) #Closed

@cston cston changed the base branch from master to release/dev16.8 December 3, 2020 22:02
// with variables in the outer function that were first used in the nested function,
// such as a field access on a captured local, but the state associated with
// any such entries are dropped, so the slots can be dropped as well.)
// We don't drop slots and types if there are local function usages but the
Copy link
Contributor

@RikkiGibson RikkiGibson Dec 3, 2020

Choose a reason for hiding this comment

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

Should this read "...if there are local function usages, because the _localFuncVarUsages dictionary..."? #Resolved

{
_variableTypes.Add(pair.Key, pair.Value);
}
variableBySlot = new VariableIdentifier[oldVariableBySlot.Length];
Copy link
Contributor

Choose a reason for hiding this comment

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

variableBySlot = new VariableIdentifier[oldVariableBySlot.Length]; [](start = 16, length = 66)

Consider if we can simply zero out elements in the array starting with oldNextVariableSlot after the analysis.

Copy link
Contributor

@AlekseyTs AlekseyTs left a comment

Choose a reason for hiding this comment

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

LGTM (commit 5), modulo the CI failure.


public override BoundNode? Visit(BoundNode? node)
{
if (node is null)
Copy link
Contributor

@AlekseyTs AlekseyTs Dec 4, 2020

Choose a reason for hiding this comment

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

if (node is null) [](start = 16, length = 17)

Consider checking _hasComplexity and returning early when it is already true. #Closed

{
if (node is BoundMethodBodyBase methodBody)
{
node = methodBody.BlockBody ?? methodBody.ExpressionBody;
Copy link
Contributor

@AlekseyTs AlekseyTs Dec 4, 2020

Choose a reason for hiding this comment

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

node = methodBody.BlockBody ?? methodBody.ExpressionBody; [](start = 20, length = 57)

It feels like we should check both, we are visiting both if present in AbstractFlowPass.VisitMethodBodies.
For BoundConstructorMethodBody we also visit initializer. #Closed

@@ -418,6 +424,68 @@ protected override void Free()
}
}

internal sealed class IsSimpleMethodVisitor : BoundTreeWalkerWithStackGuard
Copy link
Contributor

@AlekseyTs AlekseyTs Dec 4, 2020

Choose a reason for hiding this comment

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

BoundTreeWalkerWithStackGuard [](start = 54, length = 29)

Consider inheriting from BoundTreeWalkerWithStackGuardWithoutRecursionOnTheLeftOfBinaryOperator to avoid stack overflow on deeply nested binary operators.

Copy link
Contributor

@AlekseyTs AlekseyTs left a comment

Choose a reason for hiding this comment

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

LGTM (commit 9)

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.

Avoid sharing NullableWalker state between containing method and lambdas and local functions
6 participants