-
Notifications
You must be signed in to change notification settings - Fork 4k
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
Conversation
ddafaae
to
65f28fb
Compare
65f28fb
to
648b8e4
Compare
@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. |
There was a problem hiding this comment.
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 <
is how you write < in xml :) #Closed
_variableSlot.Add(identifier, slot); | ||
if (slot >= variableBySlot.Length) | ||
{ | ||
Array.Resize(ref this.variableBySlot, slot * 2); |
There was a problem hiding this comment.
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
Got the the nullable walker. I'll get back to this on Tuesday. #Closed |
92c8d13
to
da04d3d
Compare
da04d3d
to
686fcb8
Compare
_variableSlot.Add(identifier, slot); | ||
while (slot >= variableBySlot.Count) | ||
{ | ||
variableBySlot.Add(default); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider using AddMany
. #Closed
There was a problem hiding this comment.
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 { }; |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ImmutableDictionary.CreateRange(_variableTypes)); | ||
} | ||
|
||
internal Variables CreateNestedFunction(MethodSymbol method) |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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() |
There was a problem hiding this comment.
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)); |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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
Done with review pass (commit 13) #Closed |
@AlekseyTs, @333fred, all feedback has been addressed, thanks. |
internal Variables CreateNestedMethodScope(MethodSymbol method) | ||
{ | ||
Debug.Assert(GetVariablesForMethodScope(method) is null); | ||
Debug.Assert(!(method.ContainingSymbol is MethodSymbol containingMethod) || |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this 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) || |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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())
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM (commit 15)
NullableWalker
now tracks theLocalState
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