-
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
Changes from 3 commits
cf5caa3
9a350d3
648b8e4
a7b4e52
0cfc45e
53e3149
c65c6e1
686fcb8
5baa27d
2b2e452
62a48fa
8402ec6
317d29e
6eb222d
e5c45e9
8a174ea
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -52,9 +52,9 @@ internal partial class DefiniteAssignmentPass : LocalDataFlowPass< | |
/// 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. | ||
/// tracked variable. Slots less than 0 are illegal. | ||
/// </summary> | ||
protected VariableIdentifier[] variableBySlot = new VariableIdentifier[1]; | ||
protected readonly ArrayBuilder<VariableIdentifier> variableBySlot = ArrayBuilder<VariableIdentifier>.GetInstance(1, default); | ||
|
||
/// <summary> | ||
/// Variable slots are allocated to local variables sequentially and never reused. This is | ||
|
@@ -216,6 +216,7 @@ internal DefiniteAssignmentPass( | |
|
||
protected override void Free() | ||
{ | ||
variableBySlot.Free(); | ||
_variableSlot.Free(); | ||
_usedVariables.Free(); | ||
_readParameters?.Free(); | ||
|
@@ -238,11 +239,10 @@ protected override int AddVariable(VariableIdentifier identifier) | |
{ | ||
int slot = nextVariableSlot++; | ||
_variableSlot.Add(identifier, slot); | ||
if (slot >= variableBySlot.Length) | ||
while (slot >= variableBySlot.Count) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Is there ever a case when we are not doing exactly one iteration? #Closed |
||
{ | ||
Array.Resize(ref this.variableBySlot, slot * 2); | ||
variableBySlot.Add(default); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
In reply to: 560532232 [](ancestors = 560532232) |
||
} | ||
|
||
variableBySlot[slot] = identifier; | ||
return slot; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2603,16 +2603,15 @@ private void VisitStatementsWithLocalFunctions(BoundBlock block) | |
var state = TopState(); | ||
var startingState = localFunctionState.StartingState; | ||
startingState.ForEach( | ||
static (slot, arg) => | ||
(slot, variables) => | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider marking this lambda static, or making it a regular foreach loop if it can't be. #Closed There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unfortunately, there are several locals from the containing method, In reply to: 560551991 [](ancestors = 560551991) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Then why not just make this a regular In reply to: 560600004 [](ancestors = 560600004,560551991) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The method traverses In reply to: 561181556 [](ancestors = 561181556,560600004,560551991) |
||
{ | ||
var (variables, state, localFunc, startingState) = arg; | ||
var symbol = variables[variables.RootSlot(slot)].Symbol; | ||
if (Symbol.IsCaptured(symbol, localFunc)) | ||
{ | ||
state[slot] = startingState[slot]; | ||
} | ||
}, | ||
(_variables, state, localFunc, startingState)); | ||
_variables); | ||
localFunctionState.Visited = true; | ||
|
||
AnalyzeLocalFunctionOrLambda( | ||
|
@@ -2681,25 +2680,6 @@ private void AnalyzeLocalFunctionOrLambda( | |
ImmutableArray<PendingBranch> pendingReturns = RemoveReturns(); | ||
RestorePending(oldPending); | ||
|
||
var location = lambdaOrFunctionSymbol.Locations.FirstOrNone(); | ||
LeaveParameters(lambdaOrFunctionSymbol.Parameters, lambdaOrFunction.Syntax, location); | ||
|
||
// Intersect the state of all branches out of the local function | ||
RikkiGibson marked this conversation as resolved.
Show resolved
Hide resolved
|
||
var stateAtReturn = this.State; | ||
foreach (PendingBranch pending in pendingReturns) | ||
{ | ||
this.State = pending.State; | ||
BoundNode branch = pending.Branch; | ||
|
||
// Pass the local function identifier as a location if the branch | ||
// is null or compiler generated. | ||
LeaveParameters(lambdaOrFunctionSymbol.Parameters, | ||
branch?.Syntax, | ||
branch?.WasCompilerGenerated == false ? null : location); | ||
|
||
Join(ref stateAtReturn, ref this.State); | ||
} | ||
|
||
_snapshotBuilderOpt?.ExitWalker(this.SaveSharedState(), previousSlot); | ||
_variables = _variables.Container!; | ||
this.State = oldState; | ||
|
@@ -9603,32 +9583,42 @@ internal LocalStateSnapshot(int id, LocalStateSnapshot? container, BitVector sta | |
} | ||
|
||
[DebuggerDisplay("{GetDebuggerDisplay(), nq}")] | ||
internal sealed class LocalState : ILocalDataFlowState // PROTOTYPE: Pool instances. | ||
internal struct LocalState : ILocalDataFlowState | ||
{ | ||
private sealed class Boxed | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Nit: I think just There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We use the same name for In reply to: 560562315 [](ancestors = 560562315) |
||
{ | ||
internal LocalState Value; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 commentThe reason will be displayed to describe this comment to others. Learn more. The field needs to be mutable since In reply to: 560562550 [](ancestors = 560562550) |
||
|
||
internal Boxed(LocalState value) | ||
{ | ||
Value = value; | ||
} | ||
} | ||
|
||
internal readonly int Id; | ||
internal LocalState? Container; | ||
private readonly Boxed? _container; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Consider documenting this hierarchy and how it relates to other data structures. #Closed |
||
|
||
// The representation of a state is a bit vector with two bits per slot: | ||
// (false, false) => NotNull, (false, true) => MaybeNull, (true, true) => MaybeDefault. | ||
// Slot 0 is used to represent whether the state is reachable (true) or not. | ||
private BitVector _state; | ||
|
||
private LocalState(int id, LocalState? container, BitVector state) | ||
private LocalState(int id, Boxed? container, BitVector state) | ||
{ | ||
Id = id; | ||
Container = container; | ||
_container = container; | ||
_state = state; | ||
} | ||
|
||
internal static LocalState Create(LocalStateSnapshot snapshot) | ||
{ | ||
var container = snapshot.Container is null ? null : Create(snapshot.Container); | ||
var container = snapshot.Container is null ? null : new Boxed(Create(snapshot.Container)); | ||
return new LocalState(snapshot.Id, container, snapshot.State.Clone()); | ||
} | ||
|
||
internal LocalStateSnapshot CreateSnapshot() | ||
{ | ||
return new LocalStateSnapshot(Id, Container?.CreateSnapshot(), _state.Clone()); | ||
return new LocalStateSnapshot(Id, _container?.Value.CreateSnapshot(), _state.Clone()); | ||
} | ||
|
||
public bool Reachable => _state[0]; | ||
|
@@ -9649,15 +9639,15 @@ private static LocalState CreateReachableOrUnreachableState(Variables variables, | |
{ | ||
var container = variables.Container is null ? | ||
null : | ||
CreateReachableOrUnreachableState(variables.Container, reachable); | ||
new Boxed(CreateReachableOrUnreachableState(variables.Container, reachable)); | ||
int capacity = reachable ? variables.Count : 1; | ||
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 commentThe reason will be displayed to describe this comment to others. Learn more.
"CreateNestedFunctionState"? #Closed |
||
{ | ||
Debug.Assert(Id == variables.Container!.Id); | ||
return new LocalState(variables.Id, container: this, CreateBitVector(capacity: variables.Count, reachable: true)); | ||
return new LocalState(variables.Id, container: new Boxed(this), CreateBitVector(capacity: variables.Count, reachable: true)); | ||
} | ||
|
||
private static BitVector CreateBitVector(int capacity, bool reachable) | ||
|
@@ -9691,7 +9681,7 @@ private bool HasVariable(int id, int index) | |
{ | ||
if (Id > id) | ||
{ | ||
return Container!.HasValue(id, index); | ||
return _container!.Value.HasValue(id, index); | ||
} | ||
else | ||
{ | ||
|
@@ -9714,7 +9704,7 @@ private bool HasValue(int id, int index) | |
if (Id != id) | ||
{ | ||
Debug.Assert(Id > id); | ||
return Container!.HasValue(id, index); | ||
return _container!.Value.HasValue(id, index); | ||
} | ||
else | ||
{ | ||
|
@@ -9731,7 +9721,7 @@ public void Normalize(NullableWalker walker, Variables variables) | |
} | ||
else | ||
{ | ||
Container?.Normalize(walker, variables.Container!); | ||
_container?.Value.Normalize(walker, variables.Container!); | ||
int start = Capacity; | ||
EnsureCapacity(variables.Count); | ||
Populate(walker, start); | ||
|
@@ -9740,7 +9730,7 @@ public void Normalize(NullableWalker walker, Variables variables) | |
|
||
public void PopulateAll(NullableWalker walker) | ||
{ | ||
Container?.PopulateAll(walker); | ||
_container?.Value.PopulateAll(walker); | ||
Populate(walker, start: 1); | ||
} | ||
|
||
|
@@ -9773,7 +9763,7 @@ private NullableFlowState GetValue(int id, int index) | |
if (Id != id) | ||
{ | ||
Debug.Assert(Id > id); | ||
return Container!.GetValue(id, index); | ||
return _container!.Value.GetValue(id, index); | ||
} | ||
else | ||
{ | ||
|
@@ -9798,7 +9788,7 @@ private void SetValue(int id, int index, NullableFlowState value) | |
if (Id != id) | ||
{ | ||
Debug.Assert(Id > id); | ||
Container!.SetValue(id, index, value); | ||
_container!.Value.SetValue(id, index, value); | ||
} | ||
else | ||
{ | ||
|
@@ -9812,7 +9802,7 @@ private void SetValue(int id, int index, NullableFlowState value) | |
|
||
internal void ForEach<TArg>(Action<int, TArg> action, TArg arg) | ||
{ | ||
Container?.ForEach(action, arg); | ||
_container?.Value.ForEach(action, arg); | ||
for (int index = 1; index < Capacity; index++) | ||
{ | ||
action(Variables.ConstructSlot(Id, index), arg); | ||
|
@@ -9824,7 +9814,7 @@ internal LocalState GetStateForVariables(int id) | |
var state = this; | ||
while (state.Id != id) | ||
{ | ||
state = state.Container!; | ||
state = state._container!.Value; | ||
} | ||
return state; | ||
} | ||
|
@@ -9835,15 +9825,15 @@ internal LocalState GetStateForVariables(int id) | |
/// <returns></returns> | ||
public LocalState Clone() | ||
{ | ||
var container = Container?.Clone(); | ||
var container = _container is null ? null : new Boxed(_container.Value.Clone()); | ||
return new LocalState(Id, container, _state.Clone()); | ||
} | ||
|
||
public bool Join(in LocalState other) | ||
{ | ||
Debug.Assert(Id == other.Id); | ||
bool result = false; | ||
if (Container is { } && Container.Join(in other.Container!)) | ||
if (_container is { } && _container.Value.Join(in other._container!.Value)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Consider using |
||
{ | ||
result = true; | ||
} | ||
|
@@ -9858,7 +9848,7 @@ public bool Meet(in LocalState other) | |
{ | ||
Debug.Assert(Id == other.Id); | ||
bool result = false; | ||
if (Container is { } && Container.Meet(in other.Container!)) | ||
if (_container is { } && _container.Value.Meet(in other._container!.Value)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Consider using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
{ | ||
result = true; | ||
} | ||
|
@@ -9896,7 +9886,7 @@ internal string Dump(Variables variables) | |
|
||
private void Dump(StringBuilder builder, Variables variables) | ||
{ | ||
Container?.Dump(builder, variables.Container!); | ||
_container?.Value.Dump(builder, variables.Container!); | ||
|
||
for (int index = 1; index < Capacity; index++) | ||
{ | ||
|
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.
Would it make sense to get rid of this field altogether? Can we simply look at
_variableSlot
to figure out the next available slot? #Closed