-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
Non-PGO calli/delegate call transformation #109679
base: main
Are you sure you want to change the base?
Conversation
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch |
@MihuBot -dependsOn 108579 |
@MihuBot -dependsOn 108579 |
@MihuBot -dependsOn 108579 |
@jkotas It seems to me like the CONTRACT_ASSERT has some bug where it crashes with some non null terminated string instead of properly asserting, should I open an issue for it?
|
Yes, it looks like a bug somewhere. Do you have a link to the crashdump or are you able to tell where the non-terminated string comes from? |
The crash is from the |
Multiple things went wrong:
The non-null terminated string is a random pointer that came from the corrupted I have tried to introduce an artificial contract violation locally and it was reported fine (no
This command is mentioned in the how-to-debug-dump.md doc that is part of the test artifacts: https://dev.azure.com/dnceng-public/public/_build/results?buildId=865877&view=ms.vss-test-web.build-test-results-tab&runId=22530538&paneView=dotnet-dnceng.dnceng-build-release-tasks.helix-test-information-tab&resultId=214160 |
I think the current version should be fine now, could you check if all looks good with the new JIT-EE call? |
src/coreclr/vm/jitinterface.cpp
Outdated
@@ -5066,7 +5101,7 @@ void CEEInfo::getCallInfo( | |||
} | |||
else | |||
{ | |||
if (!exactType.IsTypeDesc() && !pTargetMD->IsArray()) | |||
if (!exactType.IsTypeDesc() && !pTargetMD->IsArray() && !pTargetMD->IsDynamicMethod()) |
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.
@jkotas CoreCLR is asserting here on DynamicMethod
without adding a check here, is this the right solution? With it we can emit direct calls to them but they're still not inlined cause VM says they're DONTINLINE
.
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.
What's the assert?
Dynamic methods can call each other today. This code is able to handle that. How is the case that you are introducing different?
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.
What's the assert?
It's the null return assert inside of this if.
How is the case that you are introducing different?
The MethodTable*
from the DynamicMethod is different from the one this method obtains from the JIT which in turn gets it from getMethodClass(replacementMethod)
. It seems like getMethodClass
causes the pointer to be normalized to the real one while the VM here has a special DynamicMethodTable*
which causes GetExactDeclaringType
to fail.
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 am not following. DynamicMethodTable
and MethodTable
are unrelated types. DynamicMethodTable
should never show up in the JIT/EE interface calls.
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 am not following.
DynamicMethodTable
andMethodTable
are unrelated types.DynamicMethodTable
should never show up in the JIT/EE interface calls.
I mean, the JIT is passing the proper class handle (the class the DynamicMethod is being attached to, which it gets from getMethodClass
), but the method table stored in the dynamic MethodDesc is a custom one with the debug name for it being dynamicClass
, which makes GetExactDeclaringType
fail cause that seems to have no relation to the parent type.
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.
Ok, so the problem is that getMethodClass
returns the owning class handle for dynamic methods, but CORINFO_RESOLVED_TOKEN
is expected to have the actual class handle for dynamic methods.
I think it would be best to fix it in the place that fills in CORINFO_RESOLVED_TOKEN
to have the actual class handle. It will make the dynamic methods path introduced by this PR work the same way as the existing paths taken by the dynamic methods.
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.
Ok, so the problem is that
getMethodClass
returns the owning class handle for dynamic methods, butCORINFO_RESOLVED_TOKEN
is expected to have the actual class handle for dynamic methods.I think it would be best to fix it in the place that fills in
CORINFO_RESOLVED_TOKEN
to have the actual class handle. It will make the dynamic methods path introduced by this PR work the same way as the existing paths taken by the dynamic methods.
How would I grab that from the JIT? This is where I grab that currently with getMethodClass
.
EDIT: Would you suggest to add it to the getMethodFromDelegate
JIT-EE api?
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.
Also to be honest, this seems to me a bit like CoreCLR is leaking implementation details of itself to the JIT, for me it'd make more sense if it always returned the handle of the owner class to the JIT and then mapped back to its dynamic one on its end reliably. Otherwise we're polluting the JIT with behaviours specific to one VM.
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 agree that CoreCLR is leaking the implementation details here, but I have different opinion about what the correct behavior should be.
The dynamic method type owner is optional and it is only meant to be used for visibility checks. All visibility checks should be done on the EE side. I am not sure why the JIT ever needs to see the type owner. I think it would make more sense for the JIT to always see the special MethodTable that the dynamic methods are attached to.
Would you suggest to add it to the getMethodFromDelegate JIT-EE api?
I would think so. It may also help you with communicating precise type to the JIT. I think MethodDesc*
may not always have the precise type in case of shared generic code - notice that Delegate.GetMethodImpl
does extra work to figure out the precise type for generic types https://github.com/dotnet/runtime/blob/main/src/coreclr/System.Private.CoreLib/src/System/Delegate.CoreCLR.cs#L169-L170.
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 would think so. It may also help you with communicating precise type to the JIT. I think
MethodDesc*
may not always have the precise type in case of shared generic code - notice thatDelegate.GetMethodImpl
does extra work to figure out the precise type for generic types https://github.com/dotnet/runtime/blob/main/src/coreclr/System.Private.CoreLib/src/System/Delegate.CoreCLR.cs#L169-L170.
I can't find a case where this is an issue, the JIT seems to always have the proper generic context to resolve stuff so I wonder if this is just a reflection only issue?
If not, could you assist me with handling this cause I couldn't figure out how to replicate this logic in VMs?
@@ -106,7 +106,6 @@ public TypeDesc Type | |||
public DelegateInfo(TypeDesc delegateType, DelegateFeature features) | |||
{ | |||
Debug.Assert(delegateType.IsDelegate); | |||
Debug.Assert(delegateType.IsTypeDefinition); |
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'm not sure if this is safe but NativeAOT otherwise asserts here.
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.
It isn't, the implementation is non-sensical for instantiated types.
Is this only used to get signature of the delegate? If so, replace this:
DelegateInfo delegateInfo = _compilation.TypeSystemContext.GetDelegateInfo(calledType);
int paramCountDelegateClosed = delegateInfo.Signature.Length + 1;
with this:
int paramCountDelegateClosed = calledType.GetMethod("Invoke", null).Signature.Length + 1;
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 made it check _firstParameter
instead since AFAIR I can just do that on NativeAOT since it seems to not support closed static delegates or instance ones closed over null.
src/coreclr/tools/aot/ILCompiler.RyuJit/JitInterface/CorInfoImpl.RyuJit.cs
Outdated
Show resolved
Hide resolved
Copying and slightly editing part of my note from #108579 (comment)
Thinking about it a bit more, you might also be able to re-express this as a special case of GDV, where the analysis you do here creates a guarded delegate invoke, and then later JIT optimizations prove it's the only possibility and remove the guard test and alternative paths. @jakobbotsch might have been hinting at something like this recently. |
I've decided to split my earlier attempt from #85197 into first adding just the transformation and only then making it find more cases with substitution.
TODO before merge:
TODO after merge:
DynamicMethod
s asDONTINLINE
.