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

Introduce local caching of "this" in async methods #14959

Merged
merged 2 commits into from
Nov 12, 2016

Conversation

VSadov
Copy link
Member

@VSadov VSadov commented Nov 4, 2016

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

@VSadov
Copy link
Member Author

VSadov commented Nov 4, 2016

@dotnet/roslyn-compiler - please review

@VSadov
Copy link
Member Author

VSadov commented Nov 11, 2016

@dotnet/roslyn-compiler - ping. Anyone can review this?

1 similar comment
@VSadov
Copy link
Member Author

VSadov commented Nov 11, 2016

@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)
Copy link
Member

@jaredpar jaredpar Nov 11, 2016

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?
Copy link
Member

@jaredpar jaredpar Nov 11, 2016

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

Copy link
Member Author

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)

Copy link
Member Author

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())
Copy link
Member

@jaredpar jaredpar Nov 11, 2016

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

Copy link
Contributor

@AlekseyTs AlekseyTs Nov 11, 2016

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

Copy link
Member Author

Choose a reason for hiding this comment

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

Right. Non-static accesses always have an instance.


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

//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
Copy link
Member

@jaredpar jaredpar Nov 11, 2016

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());
Copy link
Member

@jaredpar jaredpar Nov 11, 2016

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;
Copy link
Member

@jaredpar jaredpar Nov 11, 2016

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)
Copy link
Member

@jaredpar jaredpar Nov 11, 2016

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)
Copy link
Member

@jaredpar jaredpar Nov 11, 2016

Choose a reason for hiding this comment

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

Same feedback. #Resolved

@jcouv
Copy link
Member

jcouv commented Nov 11, 2016

        // switch(cachedState) {

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(
Copy link
Member

@jcouv jcouv Nov 11, 2016

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

Copy link
Member Author

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):
Copy link
Member

@jcouv jcouv Nov 11, 2016

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

Copy link
Member Author

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)
Copy link
Member

@jcouv jcouv Nov 11, 2016

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

Copy link
Member Author

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
Copy link
Member

@jcouv jcouv Nov 11, 2016

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

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 think the xml-doc looks fine and still applies.


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

Copy link
Member

@jcouv jcouv left a 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());
Copy link
Contributor

@AlekseyTs AlekseyTs Nov 11, 2016

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();
Copy link
Contributor

@AlekseyTs AlekseyTs Nov 11, 2016

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

@AlekseyTs
Copy link
Contributor

LGTM, but please address Jared's feedback about missing conversions to object.

@VSadov
Copy link
Member Author

VSadov commented Nov 12, 2016

test this please

@VSadov
Copy link
Member Author

VSadov commented Nov 12, 2016

test windows_debug_unit32_prtest please

@VSadov VSadov merged commit bf8d2ec into dotnet:master Nov 12, 2016
@VSadov
Copy link
Member Author

VSadov commented Nov 15, 2016

CC: @AndyAyersMS

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.

5 participants