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

Fix incorrect devirtualization in CG2 #37370

Merged
merged 2 commits into from
Jun 5, 2020
Merged

Conversation

nattress
Copy link
Contributor

@nattress nattress commented Jun 3, 2020

When making a constrained virtual method call on a boxed value type, CG2 devirtualizes the call incorrectly resulting in a direct call. This breaks code patterns like:

foreach (TestEnum val in Enum.GetValues(typeof(TestEnum)))
{
    buffer += val.ToString();
}

In the call to ToString(), getCallInfo is passed a boxed TestEnum and is calling object.ToString(). The constrained type is TestEnum so we end up making a direct call to object.ToString(). Fixed by removing the constrained type from devirtualization consideration which is how Crossgen treats this call.

cc @dotnet/crossgen-contrib

When making a constrained virtual method call on a boxed value type, CG2 devirtualizes the call incorrectly resulting in a direct call. This breaks code patterns like:

```
foreach (TestEnum val in Enum.GetValues(typeof(TestEnum)))
{
    buffer += val.ToString();
}
```

In the call to ToString(), `getCallInfo` is passed a boxed `TestEnum` and is calling `object.ToString()`. The constrained type is `TestEnum` so we end up making a direct call to `object.ToString()`. Fixed by removing the constrained type from devirtualization consideration which is how Crossgen treats this call.
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.

LGTM, thanks for fixing!

@@ -1301,7 +1301,7 @@ private void getFieldInfo(ref CORINFO_RESOLVED_TOKEN pResolvedToken, CORINFO_MET
//
// Note that it is safe to devirtualize in the following cases, since a servicing event cannot later modify it
// 1) Callvirt on a virtual final method of a value type - since value types are sealed types as per ECMA spec
devirt = (constrainedType ?? targetMethod.OwningType).IsValueType || targetMethod.IsInternalCall;
devirt = targetMethod.OwningType.IsValueType || targetMethod.IsInternalCall;
Copy link
Member

Choose a reason for hiding this comment

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

Btw, this code is still different from what crossgen does - the comment above ends at 1), but the VM side special cases 3 things - this seems to be missing an optimization around Delegate.Invoke - we probably generate less efficient code for that as a result.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I added the two other cases (delegates and intrinsics) so we line with up with CG1

Ensure our devirtualization matches crossgen1's behavior. We were not devirtualizing delegate invoke calls, and we were devirtualizing all internal calls (not just JIT intrinsics) which is not necessarily version resilient.
@nattress nattress merged commit 9f03517 into dotnet:master Jun 5, 2020
@ghost ghost locked as resolved and limited conversation to collaborators Dec 8, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants