-
Notifications
You must be signed in to change notification settings - Fork 4.1k
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
Clear hoisted locals when disposing iterator and async-iterator state machines #75908
Changes from 19 commits
accbe6c
04902e1
61ca295
e19d7ab
386a056
cdcadd0
e170504
0d40220
b52733e
3ac0137
37d3b3d
e948e85
b1a5ebb
fdd444c
3eb020f
58ab13e
0df2e4b
0f63ecd
5529fd6
57cafd7
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 |
---|---|---|
|
@@ -2,6 +2,7 @@ | |
// The .NET Foundation licenses this file to you under the MIT license. | ||
// See the LICENSE file in the project root for more information. | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Collections.Immutable; | ||
using System.Diagnostics; | ||
|
@@ -72,12 +73,13 @@ internal AsyncMethodToStateMachineRewriter( | |
FieldSymbol? instanceIdField, | ||
IReadOnlySet<Symbol> hoistedVariables, | ||
IReadOnlyDictionary<Symbol, CapturedSymbolReplacement> nonReusableLocalProxies, | ||
ImmutableArray<FieldSymbol> nonReusableFieldsForCleanup, | ||
SynthesizedLocalOrdinalsDispenser synthesizedLocalOrdinals, | ||
ArrayBuilder<StateMachineStateDebugInfo> stateMachineStateDebugInfoBuilder, | ||
VariableSlotAllocator? slotAllocatorOpt, | ||
int nextFreeHoistedLocalSlot, | ||
BindingDiagnosticBag diagnostics) | ||
: base(F, method, state, instanceIdField, hoistedVariables, nonReusableLocalProxies, synthesizedLocalOrdinals, stateMachineStateDebugInfoBuilder, slotAllocatorOpt, nextFreeHoistedLocalSlot, diagnostics) | ||
: base(F, method, state, instanceIdField, hoistedVariables, nonReusableLocalProxies, nonReusableFieldsForCleanup, synthesizedLocalOrdinals, stateMachineStateDebugInfoBuilder, slotAllocatorOpt, nextFreeHoistedLocalSlot, diagnostics) | ||
{ | ||
_method = method; | ||
_asyncMethodBuilderMemberCollection = asyncMethodBuilderMemberCollection; | ||
|
@@ -155,7 +157,7 @@ internal void GenerateMoveNext(BoundStatement body, MethodSymbol moveNextMethod) | |
// [body] | ||
rewrittenBody | ||
), | ||
F.CatchBlocks(GenerateExceptionHandling(exceptionLocal, rootScopeHoistedLocals))) | ||
F.CatchBlocks(generateExceptionHandling(exceptionLocal, rootScopeHoistedLocals))) | ||
); | ||
|
||
// ReturnLabel (for the rewritten return expressions in the user's method body) | ||
|
@@ -176,7 +178,7 @@ internal void GenerateMoveNext(BoundStatement body, MethodSymbol moveNextMethod) | |
// The remaining code is hidden to hide the fact that it can run concurrently with the task's continuation | ||
} | ||
|
||
bodyBuilder.Add(GenerateHoistedLocalsCleanup(rootScopeHoistedLocals)); | ||
bodyBuilder.Add(GenerateHoistedLocalsCleanupForExit(rootScopeHoistedLocals)); | ||
|
||
bodyBuilder.Add(GenerateSetResultCall()); | ||
|
||
|
@@ -206,6 +208,42 @@ internal void GenerateMoveNext(BoundStatement body, MethodSymbol moveNextMethod) | |
newBody = F.Instrument(newBody, instrumentation); | ||
|
||
F.CloseMethod(newBody); | ||
return; | ||
|
||
BoundCatchBlock generateExceptionHandling(LocalSymbol exceptionLocal, ImmutableArray<StateMachineFieldSymbol> rootHoistedLocals) | ||
{ | ||
// catch (Exception ex) | ||
// { | ||
// _state = finishedState; | ||
// | ||
// for each hoisted local: | ||
// <>x__y = default | ||
// | ||
// builder.SetException(ex); OR if (this.combinedTokens != null) this.combinedTokens.Dispose(); _promiseOfValueOrEnd.SetException(ex); /* for async-iterator method */ | ||
// return; | ||
// } | ||
|
||
// _state = finishedState; | ||
BoundStatement assignFinishedState = | ||
F.ExpressionStatement(F.AssignmentExpression(F.Field(F.This(), stateField), F.Literal(StateMachineState.FinishedState))); | ||
|
||
// builder.SetException(ex); OR if (this.combinedTokens != null) this.combinedTokens.Dispose(); _promiseOfValueOrEnd.SetException(ex); | ||
BoundStatement callSetException = GenerateSetExceptionCall(exceptionLocal); | ||
|
||
return new BoundCatchBlock( | ||
F.Syntax, | ||
ImmutableArray.Create(exceptionLocal), | ||
F.Local(exceptionLocal), | ||
exceptionLocal.Type, | ||
exceptionFilterPrologueOpt: null, | ||
exceptionFilterOpt: null, | ||
body: F.Block( | ||
assignFinishedState, // _state = finishedState; | ||
GenerateHoistedLocalsCleanupForExit(rootHoistedLocals), | ||
callSetException, // builder.SetException(ex); OR _promiseOfValueOrEnd.SetException(ex); | ||
GenerateReturn(false)), // return; | ||
isSynthesizedAsyncCatchAll: true); | ||
} | ||
} | ||
|
||
protected virtual BoundStatement GenerateTopLevelTry(BoundBlock tryBlock, ImmutableArray<BoundCatchBlock> catchBlocks) | ||
|
@@ -223,48 +261,13 @@ protected virtual BoundStatement GenerateSetResultCall() | |
: ImmutableArray<BoundExpression>.Empty)); | ||
} | ||
|
||
protected BoundCatchBlock GenerateExceptionHandling(LocalSymbol exceptionLocal, ImmutableArray<StateMachineFieldSymbol> hoistedLocals) | ||
{ | ||
// catch (Exception ex) | ||
// { | ||
// _state = finishedState; | ||
// | ||
// for each hoisted local: | ||
// <>x__y = default | ||
// | ||
// builder.SetException(ex); OR if (this.combinedTokens != null) this.combinedTokens.Dispose(); _promiseOfValueOrEnd.SetException(ex); /* for async-iterator method */ | ||
// return; | ||
// } | ||
|
||
// _state = finishedState; | ||
BoundStatement assignFinishedState = | ||
F.ExpressionStatement(F.AssignmentExpression(F.Field(F.This(), stateField), F.Literal(StateMachineState.FinishedState))); | ||
|
||
// builder.SetException(ex); OR if (this.combinedTokens != null) this.combinedTokens.Dispose(); _promiseOfValueOrEnd.SetException(ex); | ||
BoundStatement callSetException = GenerateSetExceptionCall(exceptionLocal); | ||
|
||
return new BoundCatchBlock( | ||
F.Syntax, | ||
ImmutableArray.Create(exceptionLocal), | ||
F.Local(exceptionLocal), | ||
exceptionLocal.Type, | ||
exceptionFilterPrologueOpt: null, | ||
exceptionFilterOpt: null, | ||
body: F.Block( | ||
assignFinishedState, // _state = finishedState; | ||
GenerateHoistedLocalsCleanup(hoistedLocals), | ||
callSetException, // builder.SetException(ex); OR _promiseOfValueOrEnd.SetException(ex); | ||
GenerateReturn(false)), // return; | ||
isSynthesizedAsyncCatchAll: true); | ||
} | ||
|
||
protected BoundStatement GenerateHoistedLocalsCleanup(ImmutableArray<StateMachineFieldSymbol> hoistedLocals) | ||
protected virtual BoundStatement GenerateHoistedLocalsCleanupForExit(ImmutableArray<StateMachineFieldSymbol> rootHoistedLocals) | ||
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 removing "HoistedLocals" from the name to completely avoid any confusion that the clean up is limited to the locals in the
|
||
{ | ||
var builder = ArrayBuilder<BoundStatement>.GetInstance(); | ||
|
||
// Cleanup all hoisted local variables | ||
// so that they can be collected by GC if needed | ||
foreach (var hoistedLocal in hoistedLocals) | ||
foreach (var hoistedLocal in rootHoistedLocals) | ||
{ | ||
var useSiteInfo = new CompoundUseSiteInfo<AssemblySymbol>(F.Diagnostics, F.Compilation.Assembly); | ||
var isManagedType = hoistedLocal.Type.IsManagedType(ref useSiteInfo); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -61,12 +61,13 @@ internal IteratorMethodToStateMachineRewriter( | |
FieldSymbol? instanceIdField, | ||
IReadOnlySet<Symbol> hoistedVariables, | ||
IReadOnlyDictionary<Symbol, CapturedSymbolReplacement> nonReusableLocalProxies, | ||
ImmutableArray<FieldSymbol> nonReusableFieldsForCleanup, | ||
SynthesizedLocalOrdinalsDispenser synthesizedLocalOrdinals, | ||
ArrayBuilder<StateMachineStateDebugInfo> stateMachineStateDebugInfoBuilder, | ||
VariableSlotAllocator slotAllocatorOpt, | ||
int nextFreeHoistedLocalSlot, | ||
BindingDiagnosticBag diagnostics) | ||
: base(F, originalMethod, state, instanceIdField, hoistedVariables, nonReusableLocalProxies, synthesizedLocalOrdinals, stateMachineStateDebugInfoBuilder, slotAllocatorOpt, nextFreeHoistedLocalSlot, diagnostics) | ||
: base(F, originalMethod, state, instanceIdField, hoistedVariables, nonReusableLocalProxies, nonReusableFieldsForCleanup, synthesizedLocalOrdinals, stateMachineStateDebugInfoBuilder, slotAllocatorOpt, nextFreeHoistedLocalSlot, diagnostics) | ||
{ | ||
_current = current; | ||
|
||
|
@@ -160,7 +161,11 @@ internal void GenerateMoveNextAndDispose(BoundStatement body, SynthesizedImpleme | |
if (rootFrame.knownStates == null) | ||
{ | ||
// nothing to finalize | ||
F.CloseMethod(F.Return()); | ||
var disposeBody = F.Block( | ||
GenerateAllHoistedLocalsCleanup(), | ||
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. Yes, see the diff between commits 1 and 2 for VerifyIL differences. |
||
F.Return()); | ||
|
||
F.CloseMethod(disposeBody); | ||
} | ||
else | ||
{ | ||
|
@@ -171,6 +176,7 @@ internal void GenerateMoveNextAndDispose(BoundStatement body, SynthesizedImpleme | |
ImmutableArray.Create<LocalSymbol>(stateLocal), | ||
F.Assignment(F.Local(stateLocal), F.Field(F.This(), stateField)), | ||
EmitFinallyFrame(rootFrame, state), | ||
GenerateAllHoistedLocalsCleanup(), | ||
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. Yes. See |
||
F.Return()); | ||
|
||
F.CloseMethod(disposeBody); | ||
|
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 isn't obvious. It looks like there are two call sites of this method and it is not clear why we want to have this behavior for all of them. Instead of completely ignoring the passed argument, consider adjusting the call sites to construct the right set of locals to clear. We can discuss offline in more details. #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.
This behavior could be a source of bugs in the future, since the method doesn't do what a reasonable dev would expect it to do.