Skip to content

Conversation

MichalStrehovsky
Copy link
Member

@MichalStrehovsky MichalStrehovsky commented Jul 15, 2025

Resolves #107999.

The caching on CoreCLR VM side is wild. The native AOT cache is per-callsite. So we will have observable differences in how the cache works. I assume anyone who comes to us with "My Gizmo doesn't work for more than one type" where Gizmo is defined as:

class Gizmo : IDynamicInterfaceCastable
{
    public Type InterfaceType { get; init; }
    public Type ImplType { get; init; }

    public RuntimeTypeHandle GetInterfaceImplementation(RuntimeTypeHandle interfaceType)
        => interfaceType.Equals(InterfaceType.TypeHandle) ? ImplType.TypeHandle : throw new Exception();
    public bool IsInterfaceImplemented(RuntimeTypeHandle interfaceType, bool throwIfNotImplemented)
        => interfaceType.Equals(InterfaceType.TypeHandle) ? true : (throwIfNotImplemented ? throw new InvalidCastException() : false);
}

will be sent away, although I can't find docs that we could point them to saying this is illegal. The CoreCLR VM implementation that sends things through a cache doesn't seem to allow that, and neither will native AOT.

Cc @dotnet/ilc-contrib @AaronRobinsonMSFT @jkoritzinsky

Contributes to dotnet#107999.

We want _some_ caching. It's unclear what the right caching is (the caching on CoreCLR VM side is [wild](dotnet#107999 (comment))), but given that we sometimes might need to purge caches to avoid turning them into memory leaks, I think the only conclusion is that whoever implements things like:

```csharp
class Dyn : IDynamicInterfaceCastable
{
    public Type InterfaceType { get; init; }
    public Type ImplType { get; init; }

    public RuntimeTypeHandle GetInterfaceImplementation(RuntimeTypeHandle interfaceType)
        => interfaceType.Equals(InterfaceType.TypeHandle) ? ImplType.TypeHandle : throw new Exception();
    public bool IsInterfaceImplemented(RuntimeTypeHandle interfaceType, bool throwIfNotImplemented)
        => interfaceType.Equals(InterfaceType.TypeHandle) ? true : (throwIfNotImplemented ? throw new InvalidCastException() : false);
}
```

Should feel bad, and we don't support their use case.
@Copilot Copilot AI review requested due to automatic review settings July 15, 2025 06:19
Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR modifies the dispatch cache logic to remove the special-case for IDynamicInterfaceCastable, aiming to allow caching of its results in Native AOT.

  • Removed the branch that bypassed cache updates for IDynamicInterfaceCastable
  • Simplified the if (pTargetCode != IntPtr.Zero) block
Comments suppressed due to low confidence (1)

src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/CachedInterfaceDispatch.cs:30

  • By removing the else return pTargetCode branch, calls for IDynamicInterfaceCastable objects now fall through to the fail-fast path instead of returning the resolved target. Restore a direct return pTargetCode; for the dynamic-castable case or refactor so both paths correctly return.
            if (pTargetCode != IntPtr.Zero)

Copy link
Contributor

Tagging subscribers to this area: @agocke, @MichalStrehovsky, @jkotas
See info in area-owners.md if you want to be subscribed.

@jkotas
Copy link
Member

jkotas commented Jul 15, 2025

Do we need to add some tests?

@MichalStrehovsky
Copy link
Member Author

Do we need to add some tests?

Wouldn't it require building a lot of infrastructure so that we can control when/how the cache gets flushed to get anything reliable? The CoreCLR caching behavior is baffling and making a native AOT specific test for this really doesn't feel like worth the effort.

@MichalStrehovsky
Copy link
Member Author

So the ValidateOverriddenInterface test is failing because the test itself has a Gizmo equivalent from the top-post but just lucks out because the CoreCLR VM's version of the cache is super weird in how it caches things. This took a lot longer to root cause than I would be willing to admit.

I honestly think that the caching here is user-hostile, whoever disagrees, just try to root cause the failing test in a debugger.

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.

Be consistent about caching behavior of function resolutions through IDynamicInterfaceCastable

4 participants