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

JIT: work around issues invoking the unboxed entry when prejitting #52605

Merged
merged 1 commit into from
May 12, 2021

Conversation

AndyAyersMS
Copy link
Member

If the jit devirtualizes a call to a value class method, it will try
and update the call to invoke the unboxed entry for the method. This
entry sometimes requires an explicit type context argument.

There are cases where the prejit host seemingly does not properly deduce
when the unboxed entry requires this extra argument. Until this is resolved,
defer this optimization when prejitting.

See #52483 for details.

If the jit devirtualizes a call to a value class method, it will try
and update the call to invoke the unboxed entry for the method. This
entry sometimes requires an explicit type context argument.

There are cases where the prejit host seemingly does not properly deduce
when the unboxed entry requires this extra argument. Until this is resolved,
defer this optimization when prejitting.

See dotnet#52483 for details.
@dotnet-issue-labeler dotnet-issue-labeler bot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label May 11, 2021
@AndyAyersMS
Copy link
Member Author

@dotnet/jit-contrib PTAL
cc @jkotas @trylek

Linked issue above has details on my attempt at understanding where things go wrong in crossgen2.

Copy link
Member

@trylek trylek left a comment

Choose a reason for hiding this comment

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

Looks good, thanks for fixing this. We should investigate and fix this at the Crossgen2 side but with the tight preview shipping cycle it's important to keep the main branch in solid health.

@@ -21008,6 +21008,14 @@ void Compiler::impDevirtualizeCall(GenTreeCall* call,
{
JITDUMP("Have a direct explicit tail call to boxed entry point; can't optimize further\n");
}
else if (opts.jitFlags->IsSet(JitFlags::JIT_FLAG_PREJIT))
Copy link
Member

Choose a reason for hiding this comment

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

Why not abort this in resolveVirtualMethod on the crossgen2? We could limit this to the cases when the devirtualized method body is shared. Do we want to give up on this optimizations for nongeneric valuetypes or unshared generic valuetypes?

Copy link
Member

Choose a reason for hiding this comment

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

The fix would be something along the lines of adding

if (impl.GetCanonMethodTarget(CanonicalFormKind.Specific).IsCanonicalMethod(CanonicalFormKind.Any))
{
    return false;
}

to the "if (impl.OwningType.IsValueType)" block in resolveVirtualMethod.

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'm hoping whatever fix we take here is perhaps temporary.

So far the devirtualization hasn't lead to bad codegen -- just the follow-on attempt to update the call site to invoke the unboxed entry.

I suppose you could argue that we're lucky and the (nominal) unshared unboxing method and the shared unboxing method are really the same method and neither can be inlined, so currently nothing bad comes of confusing the two.

The runtime and crossgen1 should allow devirtualization here too. But currently that implementation of resolveVirtualMethod is blocked because we present it with a mixture of an exact implementation type and a shared interface method (and hence type) and this leads the runtime to deduce that the exact type does not implement the interface.

I find it fairly tricky to reason about correct behavior in these mixed shared/exact cases. We will see more and more of this as via PGO the jit is able to get a hold of exact types in many places where it would not have been possible before.

Copy link
Member

Choose a reason for hiding this comment

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

I just meant that if we happen to ship with this for .NET 6, it would be unfortunate to disable all of this if it works only because it doesn't work for KeyValuePair (there are very few other popular generic structs).

#51982 is an example of a devirtualization problem with shared code (this one affects all of the: VM, crossgen and crossgen2). There are various weird corner cases like this that we probably don't have tests for.

Copy link
Member Author

Choose a reason for hiding this comment

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

So far the devirtualization hasn't lead to bad codegen

Evidently this is not the case -- looking at #51982 and #51983 it looks like the problems are more widespread.

@AndyAyersMS
Copy link
Member Author

Failures seem to be cases of #52596.

@AndyAyersMS
Copy link
Member Author

Looks like I need to bounce this to get past the CI glitches.

@AndyAyersMS AndyAyersMS reopened this May 12, 2021
@AndyAyersMS
Copy link
Member Author

Going to merge to unblock the SDK update (dotnet/sdk#17498).

@AndyAyersMS AndyAyersMS merged commit e0dffa1 into dotnet:main May 12, 2021
@karelz karelz added this to the 6.0.0 milestone May 20, 2021
davidwrighton added a commit that referenced this pull request Jun 11, 2021
Address deficiencies in current devirtualization infrastructure

- Remove the responsibility of creating a CORINFO_RESOLVED_TOKEN structure from the JIT and make it a responsibility of the VM side of the jit interface.
  - This enables the component (crossgen2) which has deeper understanding of the requirements here to correctly handle scenarios that would otherwise require expressing crossgen2 specific details across the jit interface.
- Add a new set of fixups (`READYTORUN_FIXUP_Check_VirtualFunctionOverride` and `READYTORUN_FIXUP_Verify_VirtualFunctionOverride`) these are used to validate that the behavior of the runtime and crossgen2 compiler is equivalent for a virtual resolution event
  - `READYTORUN_FIXUP_Check_VirtualFunctionOverride` will ensure that the virtual resolution decision is the same at crossgen2 time and runtime, and if the decision differs, any generated code affected by the decision will not be used.
  - `READYTORUN_FIXUP_Verify_VirtualFunctionOverride` will perform the same checks as `READYTORUN_FIXUP_Check_VirtualFunctionOverride`, but if it fails the check, the process will be terminated with a fail-fast. It is intended for use under the `--verify-type-and-field-layout` stress mode.
  - Currently only the `READYTORUN_FIXUP_Verify_VirtualFunctionOverride` is actually generated, and it is only generated when using the `--verify-type-and-field-layout` switch to crossgen2. Future work will identify if there are scenarios where we need to generate the `READYTORUN_FIXUP_Check_VirtualFunctionOverride` flag. One area of possible concern is around covariant returns, another is around handling of type equivalence.
- In order to express the fixup signature for the VirtualFunctionOverride fixups, a new flag has been added to `ReadyToRunMethodSigFlags`. `READYTORUN_METHOD_SIG_UpdateContext` will allow the method signature to internally specify the assembly which is associated with the method token, instead of relying on the ambient context.
- R2RDump and the ReadyToRun format documentation have been updated with the details of the new fixups/flags.
- Update the rules for handling unboxing stubs
  - See #51918 for details. This adds a new test, as well as proper handling for unboxing stubs to match the JIT behavior
  - Also revert #52605, which avoided the problem by simply disabling devirtualization in the presence of structs
- Adjust the rules for when it is legal to devirtualize and maintain version resiliency
  - The VersionsWithCode and VersionsWithType rules are unnecessarily restrictive.
  - Instead Validate that the metadata is safely checkable, and rely on the canInline logic to ensure that no IL that can't be handled is inlined.
  - This also involved adding a check that the chain of types from the implementation type to the declaration method table type is within the version bubble.
  - And changing the `VersionsWithType` check on the implementation type, to a new `VersionsWithTypeReference` check which can be used to validate that the type can be referred to, in combination with using `VersionsWithType` on the type definition.
  - By adjusting the way that the declMethod is referred to, it becomes possible to use the declMethod without checking the full method is `VersionsWithCode`, and it just needs a relationship to version matching code.
- In `resolveVirtualMethod` generate the `CORINFO_RESOLVED_TOKEN` structures for the jit
  - In particular we are now able to resolve to methods where the decl method is the resolution result but is not within the version bubble itself. This can happen if we can prove that the decl method is the only method which can possibly implement a virtual.
- Add support for devirtualization reasons to crossgen2
- Port all devirtualization abort conditions to crossgen2 from runtime that were not already present
- Fix devirtualization from a canonical virtual method when the actual implementation is more exact
- Fix variant interface override scenario where there is an interface that requires implementation of the variant interface as well as the variant interface itself.
@ghost ghost locked as resolved and limited conversation to collaborators Jun 19, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants