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

Track state separately in NullableWalker for nested and containing methods #50417

Merged
merged 16 commits into from
Jan 23, 2021

Conversation

cston
Copy link
Member

@cston cston commented Jan 13, 2021

NullableWalker now tracks the LocalState separately for separate lambdas or local functions within the same containing method to avoid duplicated state across nested methods. Similarly, the dictionaries that map variables to slots and types are now tracked separately.

The state for a nested method is now a linked list of state for that method and each of the containing methods.

If the method contains M nested functions with a total of N variables and fields across the method and all nested functions, the existing implementation holds O(M*N) state and this implementation holds O(N + M*N0) state where N0 is the number of variables and fields in the outer method. (The duplication of N0 could be addressed by storing one copy of the LocalState for the outer method rather than including a copy with each nested function.)

Fixes #49745

@cston cston force-pushed the NestedFunctionState branch 4 times, most recently from ddafaae to 65f28fb Compare January 15, 2021 06:51
@cston cston marked this pull request as ready for review January 15, 2021 08:51
@cston cston requested a review from a team as a code owner January 15, 2021 08:51
@RikkiGibson RikkiGibson self-assigned this Jan 15, 2021
@333fred
Copy link
Member

333fred commented Jan 15, 2021

@cston looks like there's still a prototype marker in the code.

/// The first slot, slot 0, is reserved for indicating reachability, so the first tracked variable will
/// be given slot 1. When referring to VariableIdentifier.ContainingSlot, slot 0 indicates
/// that the variable in VariableIdentifier.Symbol is a root, i.e. not nested within another
/// tracked variable. Slots < 0 are illegal.
Copy link
Member

@333fred 333fred Jan 15, 2021

Choose a reason for hiding this comment

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

< [](start = 36, length = 4)

Consider just saying "less than" in english so I don't have to remember that &lt; is how you write < in xml :) #Closed

_variableSlot.Add(identifier, slot);
if (slot >= variableBySlot.Length)
{
Array.Resize(ref this.variableBySlot, slot * 2);
Copy link
Member

@333fred 333fred Jan 15, 2021

Choose a reason for hiding this comment

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

I know this stuff is just copied, but do we want to consider just making variableBySlot an ArrayBuilder, which handles this already? #Closed

@333fred
Copy link
Member

333fred commented Jan 16, 2021

Got the the nullable walker. I'll get back to this on Tuesday. #Closed

@cston cston requested review from AlekseyTs and a team January 19, 2021 01:48
_variableSlot.Add(identifier, slot);
while (slot >= variableBySlot.Count)
{
variableBySlot.Add(default);
Copy link
Member

@333fred 333fred Jan 19, 2021

Choose a reason for hiding this comment

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

Consider using AddMany. #Closed

Copy link
Member Author

@cston cston Jan 20, 2021

Choose a reason for hiding this comment

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

slot should be at most equal to, not greater than, variableBySlot.Count so the while loop is executed at most once. The alternative seems a little more complicated:

if (slot >= variableBySlot.Count)
{
    variableBySlot.AddMany(default, variableBySlot.Count - slot + 1);
}

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

_hasComplexity = true;
return node;
}
_hasInitialState = variables is { };
Copy link
Member

@333fred 333fred Jan 19, 2021

Choose a reason for hiding this comment

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

{ } [](start = 44, length = 3)

Consider using not null for clarity. #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 prefer this syntax since it's shorter.


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

Copy link
Member

Choose a reason for hiding this comment

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

@jaredpar what have you done.


In reply to: 560598649 [](ancestors = 560598649,560538257)

ImmutableDictionary.CreateRange(_variableTypes));
}

internal Variables CreateNestedFunction(MethodSymbol method)
Copy link
Contributor

@AlekseyTs AlekseyTs Jan 22, 2021

Choose a reason for hiding this comment

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

CreateNestedFunction [](start = 31, length = 20)

The name "CreateNestedFunction" feels confusing. The method doesn't create a function. Consider using the name that reflects the purpose of the method. Given the terminology used in doc comments, "CreateNestedMethodScope" comes to mind. #Closed

internal Variables CreateNestedFunction(MethodSymbol method)
{
Debug.Assert(GetVariablesForSymbol(method) is null or { Symbol: null });
return new Variables(id: GetNextId(), this, method);
Copy link
Contributor

@AlekseyTs AlekseyTs Jan 22, 2021

Choose a reason for hiding this comment

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

this [](start = 54, length = 4)

Can we assert that this is the right enclosing scope? #Closed

return GetVariablesRoot();
}

private Variables GetVariablesRoot()
Copy link
Contributor

@AlekseyTs AlekseyTs Jan 22, 2021

Choose a reason for hiding this comment

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

"GetRootScope"? #Closed

}
else
{
state.TryAdd(key, new Data(walker._variableSlot.Count, requiresAnalysis));
state.TryAdd(key, new Data(_variables.GetTotalVariableCount(), requiredAnalysis));
Copy link
Contributor

@AlekseyTs AlekseyTs Jan 22, 2021

Choose a reason for hiding this comment

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

_variables.GetTotalVariableCount() [](start = 47, length = 34)

No need to subtract 1 any more? I see that we didn't subtract initially. #Closed

/// A bit array containing the nullability of variables associated with a method scope. If the method is a
/// nested function (a lambda or a local function), there is a reference to the corresponding instance for
/// the containing method scope. The instances in the chain are associated with a corresponding
/// Variables chain, and the Id field in this type matches the Id in the Variables chain.
Copy link
Contributor

@AlekseyTs AlekseyTs Jan 22, 2021

Choose a reason for hiding this comment

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

Variables [](start = 12, length = 9)

Consider using cref to point to types and members throughout this statement. #Closed

return new LocalState(variables.Id, container, CreateBitVector(capacity, reachable));
}

public LocalState CreateNestedFunction(Variables variables)
Copy link
Contributor

@AlekseyTs AlekseyTs Jan 22, 2021

Choose a reason for hiding this comment

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

CreateNestedFunction [](start = 30, length = 20)

"CreateNestedFunctionState"? #Closed

internal partial class NullableWalker
{
/// <summary>
/// An immutable copy of Variables.
Copy link
Contributor

@AlekseyTs AlekseyTs Jan 22, 2021

Choose a reason for hiding this comment

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

Variables [](start = 33, length = 9)

cref? #Closed

@AlekseyTs
Copy link
Contributor

AlekseyTs commented Jan 22, 2021

Done with review pass (commit 13) #Closed

@cston
Copy link
Member Author

cston commented Jan 22, 2021

@AlekseyTs, @333fred, all feedback has been addressed, thanks.

@cston cston requested a review from a team January 22, 2021 18:05
internal Variables CreateNestedMethodScope(MethodSymbol method)
{
Debug.Assert(GetVariablesForMethodScope(method) is null);
Debug.Assert(!(method.ContainingSymbol is MethodSymbol containingMethod) ||
Copy link
Member

@333fred 333fred Jan 22, 2021

Choose a reason for hiding this comment

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

!(method.ContainingSymbol is MethodSymbol containingMethod) [](start = 29, length = 59)

Consider simplifying with methodSymbol.ContainingSymbol is not MethodSymbol containingMethod #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.

See #49262.


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

Copy link
Member

@333fred 333fred 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 15). Just a minor formatting comment.

{
Debug.Assert(GetVariablesForMethodScope(method) is null);
Debug.Assert(!(method.ContainingSymbol is MethodSymbol containingMethod) ||
((object?)GetVariablesForMethodScope(containingMethod) == this) ||
Copy link
Contributor

Choose a reason for hiding this comment

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

((object?)GetVariablesForMethodScope(containingMethod) == this) [](start = 20, length = 63)

Symbol == (object)containingMethod?

Debug.Assert(GetVariablesForMethodScope(method) is null);
Debug.Assert(!(method.ContainingSymbol is MethodSymbol containingMethod) ||
((object?)GetVariablesForMethodScope(containingMethod) == this) ||
Container is null);
Copy link
Contributor

Choose a reason for hiding this comment

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

Container is null [](start = 20, length = 17)

Symbol is null?

var variables = this;
while (true)
{
if (variables.Symbol is null)
Copy link
Contributor

@AlekseyTs AlekseyTs Jan 22, 2021

Choose a reason for hiding this comment

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

if (variables.Symbol is null) [](start = 20, length = 29)

The check feels redundant, it looks like the behavior will be the same without this if. #Closed

/// A bit array containing the nullability of variables associated with a method scope. If the method is a
/// nested function (a lambda or a local function), there is a reference to the corresponding instance for
/// the containing method scope. The instances in the chain are associated with a corresponding
/// <see cref="Variables"/> chain, and the Id field in this type matches the Id in the <see cref="Variables"/> chain.
Copy link
Contributor

@AlekseyTs AlekseyTs Jan 22, 2021

Choose a reason for hiding this comment

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

Id [](start = 51, length = 2)

cref? #Closed

/// A bit array containing the nullability of variables associated with a method scope. If the method is a
/// nested function (a lambda or a local function), there is a reference to the corresponding instance for
/// the containing method scope. The instances in the chain are associated with a corresponding
/// <see cref="Variables"/> chain, and the Id field in this type matches the Id in the <see cref="Variables"/> chain.
Copy link
Contributor

@AlekseyTs AlekseyTs Jan 22, 2021

Choose a reason for hiding this comment

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

Id [](start = 85, length = 2)

cref? #Closed

protected override LocalFunctionState CreateLocalFunctionState(LocalFunctionSymbol symbol)
{
var variables = (symbol.ContainingSymbol is MethodSymbol containingMethod ? _variables.GetVariablesForMethodScope(containingMethod) : null) ??
_variables.GetRootScope();
Copy link
Contributor

Choose a reason for hiding this comment

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

(symbol.ContainingSymbol is MethodSymbol containingMethod ? _variables.GetVariablesForMethodScope(containingMethod) : _variables.GetRootScope())?

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

@cston cston merged commit 6a28cdc into dotnet:master Jan 23, 2021
@ghost ghost added this to the Next milestone Jan 23, 2021
@cston cston deleted the NestedFunctionState branch January 23, 2021 01:06
@Cosifne Cosifne modified the milestones: Next, 16.9 Jan 27, 2021
@JoeRobich JoeRobich modified the milestones: 16.9, 16.9.P4 Jan 27, 2021
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
7 participants