-
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
Introduce local caching of "this" in async methods #14959
Conversation
@dotnet/roslyn-compiler - please review |
@dotnet/roslyn-compiler - ping. Anyone can review this? |
1 similar comment
@dotnet/roslyn-compiler - ping. Anyone can review this? |
EmitExpression(fieldAccess.ReceiverOpt, used: false); | ||
return; | ||
// fetching unused captured frame is a no-op (like reading "this") | ||
if(field.IsCapturedFrame) |
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.
Missing space between if and ( #Resolved
return; | ||
} | ||
|
||
//TODO: For static field access this may require ..ctor to run. Is this a side-effect? |
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 realize this is an old comment but we should take the time to correct. If this is a real bug please file an issue for the TODO and reference it here. Otherwise if it's not a bug let's clean up this comment. #Resolved
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.
Old comment. accessing static fields is sideeffecting when we have .cctor. The code is not actually trying to be smart about this.
Will clean up comments around this code.
In reply to: 87618871 [](ancestors = 87618871)
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.
Note that generally we cannot generally know whether we have .cctor or not, since the type maybe in a referenced assembly.
In some cases we might know, but does not seem to be worth special casing.
In reply to: 87655591 [](ancestors = 87655591,87618871)
|
||
//TODO: For static field access this may require ..ctor to run. Is this a side-effect? | ||
// Accessing unused instance field on a struct is a noop. Just emit the receiver. | ||
if (!field.IsVolatile && !field.IsStatic && fieldAccess.ReceiverOpt.Type.IsVerifierValue()) |
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.
Why are we confident that ReceiverOpt will be non-null here? #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.
I guess, non-static field access should never have a null receiver. #Resolved
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 that has already happen, we will just propagate the behavior. | ||
//however, we assume it is responsibility of the caller to nullcheck "this" | ||
//if we already have access to "this", we must be in a member and should | ||
//not redo the check |
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.
Nit: space between comment and word. #Resolved
@@ -425,7 +431,7 @@ private BoundBlock GenerateAwaitForIncompleteTask(LocalSymbol awaiterTemp) | |||
// this.state = cachedState = NotStartedStateMachine | |||
F.Assignment(F.Field(F.This(), stateField), F.AssignmentExpression(F.Local(cachedState), F.Literal(StateMachineStates.NotStartedStateMachine)))); | |||
|
|||
return F.Block(blockBuilder.ToImmutableAndFree()); | |||
return F.Block(blockBuilder.ToImmutableAndFree()); |
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.
Nit: extra space? #Resolved
@@ -44,6 +44,8 @@ internal abstract class MethodToStateMachineRewriter : MethodToClassRewriter | |||
/// </summary> | |||
protected readonly LocalSymbol cachedState; | |||
|
|||
protected readonly LocalSymbol cachedThis; |
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.
Can we add a comment to the purpose of this local. In particular the cases where it will be non-null? #Resolved
{ | ||
// restore "this" cache, if there is a cache | ||
if (this.cachedThis != 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.
((object)cachedThis) != null
``` #Resolved
@@ -768,6 +802,13 @@ public override BoundNode VisitThisReference(BoundThisReference node) | |||
public override BoundNode VisitBaseReference(BoundBaseReference node) | |||
{ | |||
// TODO: fix up the type of the resulting node to be the base type | |||
|
|||
// if "this" is cached, return it. | |||
if (this.cachedThis != 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.
Same feedback. #Resolved
Update comment with optional cached this? #Resolved Refers to: src/Compilers/CSharp/Portable/Lowering/IteratorRewriter/IteratorMethodToStateMachineRewriter.cs:89 in 87831b6. [](commit_id = 87831b671faf977b1c3c8b34c8bb88afc9a090b0, deletion_comment = False) |
@@ -95,11 +95,13 @@ internal void GenerateMoveNextAndDispose(BoundStatement body, SynthesizedImpleme | |||
// state_0: | |||
// state = -1; | |||
// [[rewritten body]] | |||
newBody = F.Block(ImmutableArray.Create(cachedState), | |||
F.Block( |
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.
We didn't need this nested block? #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.
Inner block declares no locals, so it is definitely unnecessary.
Blocks without locals are rarely useful - mostly just for constructs that require blocks (like Try).
In reply to: 87635280 [](ancestors = 87635280)
@@ -386,8 +388,11 @@ public override BoundNode VisitTryStatement(BoundTryStatement node) | |||
// return; | |||
// } | |||
Debug.Assert(frame.parent.finalizeState == _currentFinallyFrame.finalizeState); | |||
rewrittenHandler = F.Block( | |||
rewrittenHandler = F.Block((object)this.cachedThis != null? | |||
ImmutableArray.Create(this.cachedThis): |
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.
Maybe we should add an extension on ImmutableArray for this common pattern. #WontFix
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.
The extension would be on T: class
. It feels like it would be very noisy.
In reply to: 87635783 [](ancestors = 87635783)
if ((object)thisParameter != null && | ||
thisParameter.Type.IsReferenceType && | ||
proxies.TryGetValue(thisParameter, out thisProxy) && | ||
F.Compilation.Options.OptimizationLevel == OptimizationLevel.Release) |
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.
Can you verify we have a test with a struct (but we compile in release mode)? #WontFix
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.
We have existing tests which fail if the optimization does not check for a class.
In reply to: 87637057 [](ancestors = 87637057)
@@ -61,5 +63,10 @@ IMethodSymbol ISynthesizedMethodBodyImplementationSymbol.Method | |||
{ | |||
get { return ((ISynthesizedMethodBodyImplementationSymbol)ContainingSymbol).Method; } | |||
} | |||
|
|||
internal override bool IsCapturedFrame |
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.
Could you take a look at IsCapturedFrame
in FieldSymbol.cs
? Do you think the xml-doc needs to be updated? #WontFix
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
proxies.TryGetValue(thisParameter, out thisProxy) && | ||
F.Compilation.Options.OptimizationLevel == OptimizationLevel.Release) | ||
{ | ||
var thisProxyReplacement = thisProxy.Replacement(F.Syntax, frameType => F.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.
var [](start = 16, length = 3)
Consider spelling out the type. #Resolved
} | ||
|
||
// do nothing | ||
return F.Block(); |
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.
return F.Block(); [](start = 12, length = 17)
Could we return an empty statement list instead? Might be cheaper in terms of memory. #Resolved
LGTM, but please address Jared's feedback about missing conversions to object. |
test this please |
test windows_debug_unit32_prtest please |
CC: @AndyAyersMS |
Introduce local caching of "this" in async methods
JIT has no knowledge that captured "this" is semantically readonly, so it would fetch the captured "this" repeatedly if instance members are accessed in the method (which is fairly common). Compiler has such knowledge and can help JIT by caching the reference into a local.
Also, we do not need to call nonvirtual methods off captured "this" via calvirt. For the same reasons we do not use calvirt when calling through the actual "this" directly.
Calvirt here has extra costs. It forces the JIT to insert IR for null checks and later JIT tries to proof that these checks might be unnecessary, which it often can't, since again, it does not have that knowledge that we have at compile time.
Related to: https://github.com/dotnet/coreclr/issues/7914
Fixes:#14873