Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
ff4ace5
Implement support for weak symbols in ILC
jkoritzinsky Jun 13, 2025
7b35d82
Initial sketch of conditionally rooting IDIC when IDIC is actually used.
jkoritzinsky Jun 13, 2025
27a0fae
Manually use weak symbols in the bootstrapper to handle weak IDIC
jkoritzinsky Jun 13, 2025
c03f3b2
Remove SymbolFlags now that we're using a bootstrapper-based weak sym…
jkoritzinsky Jun 13, 2025
465e703
Only ask for maximally constructable interface types for casting if I…
jkoritzinsky Jun 13, 2025
d88c99e
Remove Weak field on RuntimeExportAttribute
jkoritzinsky Jun 13, 2025
5815948
Reduce diff
jkoritzinsky Jun 13, 2025
fe157a2
Make IDynamicCastableSupport trimmable
AustinWise Jun 16, 2025
4e7b795
Make the IDIC static constructor check live elsewhere
jkoritzinsky Jun 16, 2025
ff74f16
Remove slots and fix C99 _Pragma usage
jkoritzinsky Jun 16, 2025
7856559
Merge branch 'main' of https://github.com/dotnet/runtime into idic-co…
jkoritzinsky Jun 16, 2025
465ec2b
Downgrade interface type symbols in interface dispatch stubs
jkoritzinsky Jun 17, 2025
8ac6d6e
Substitute away the code we can't trim out easily
jkoritzinsky Jun 17, 2025
7d2a548
Scan the "feature body removed" helper always. We might remove featur…
jkoritzinsky Jun 18, 2025
515b35c
Look for constructed implementing type instead of IDIC itself.
jkoritzinsky Jun 19, 2025
68b40fb
Fix substitution condition
jkoritzinsky Jun 19, 2025
7635ea4
Merge branch 'main' of https://github.com/dotnet/runtime into idic-co…
jkoritzinsky Jun 20, 2025
ec645e4
Add ThrowFeatureBodyRemoved helper to Test.CoreLib
jkoritzinsky Jun 20, 2025
1b19031
Remove leftovers from previous impl
jkoritzinsky Jul 1, 2025
993d1e8
PR feedback
jkoritzinsky Jul 1, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions src/coreclr/nativeaot/Bootstrap/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ void* PalGetModuleHandleFromPointer(void* pointer);
#define STRINGIFY(s) #s
#define MANAGED_RUNTIME_EXPORT_ALTNAME(_method) STRINGIFY(/alternatename:_##_method=_method)
#define MANAGED_RUNTIME_EXPORT(_name) \
__pragma(comment (linker, MANAGED_RUNTIME_EXPORT_ALTNAME(_name))) \
_Pragma(comment (linker, MANAGED_RUNTIME_EXPORT_ALTNAME(_name))) \
extern "C" void __cdecl _name();
#define MANAGED_RUNTIME_EXPORT_NAME(_name) _name
#define CDECL __cdecl
Expand All @@ -125,8 +125,6 @@ MANAGED_RUNTIME_EXPORT(AppendExceptionStackFrame)
MANAGED_RUNTIME_EXPORT(GetSystemArrayEEType)
MANAGED_RUNTIME_EXPORT(OnFirstChanceException)
MANAGED_RUNTIME_EXPORT(OnUnhandledException)
MANAGED_RUNTIME_EXPORT(IDynamicCastableIsInterfaceImplemented)
MANAGED_RUNTIME_EXPORT(IDynamicCastableGetInterfaceImplementation)
#ifdef FEATURE_OBJCMARSHAL
MANAGED_RUNTIME_EXPORT(ObjectiveCMarshalTryGetTaggedMemory)
MANAGED_RUNTIME_EXPORT(ObjectiveCMarshalGetIsTrackedReferenceCallback)
Expand All @@ -145,8 +143,8 @@ static const pfn c_classlibFunctions[] = {
&MANAGED_RUNTIME_EXPORT_NAME(GetSystemArrayEEType),
&MANAGED_RUNTIME_EXPORT_NAME(OnFirstChanceException),
&MANAGED_RUNTIME_EXPORT_NAME(OnUnhandledException),
&MANAGED_RUNTIME_EXPORT_NAME(IDynamicCastableIsInterfaceImplemented),
&MANAGED_RUNTIME_EXPORT_NAME(IDynamicCastableGetInterfaceImplementation),
nullptr, // IDynamicCastableIsInterfaceImplemented,
nullptr, // IDynamicCastableGetInterfaceImplementation,
#ifdef FEATURE_OBJCMARSHAL
&MANAGED_RUNTIME_EXPORT_NAME(ObjectiveCMarshalTryGetTaggedMemory),
&MANAGED_RUNTIME_EXPORT_NAME(ObjectiveCMarshalGetIsTrackedReferenceCallback),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ private static IntPtr RhResolveDynamicInterfaceCastableDispatchOnType(MethodTabl
return result;
}

internal static unsafe delegate*<object, MethodTable*, ushort, IntPtr> s_IDynamicCastableGetInterfaceImplementation;

private static unsafe IntPtr RhResolveDispatchWorker(object pObject, void* cell, ref DispatchCellInfo cellInfo)
{
// Type of object we're dispatching on.
Expand All @@ -138,9 +140,8 @@ private static unsafe IntPtr RhResolveDispatchWorker(object pObject, void* cell,
{
// Dispatch not resolved through normal dispatch map, try using the IDynamicInterfaceCastable
// This will either give us the appropriate result, or throw.
var pfnGetInterfaceImplementation = (delegate*<object, MethodTable*, ushort, IntPtr>)
pInstanceType->GetClasslibFunction(ClassLibFunctionId.IDynamicCastableGetInterfaceImplementation);
pTargetCode = pfnGetInterfaceImplementation(pObject, cellInfo.InterfaceType, cellInfo.InterfaceSlot);
Diagnostics.Debug.Assert(s_IDynamicCastableGetInterfaceImplementation != null);
pTargetCode = s_IDynamicCastableGetInterfaceImplementation(pObject, cellInfo.InterfaceType, cellInfo.InterfaceSlot);
Diagnostics.Debug.Assert(pTargetCode != IntPtr.Zero);
}
return pTargetCode;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace System.Runtime.CompilerServices
{
// When applied to a type this custom attribute will cause any static class constructor to be run eagerly
// at module load time rather than deferred till just before the class is used.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = false, AllowMultiple = false)]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface, Inherited = false, AllowMultiple = false)]
public class EagerStaticClassConstructionAttribute : Attribute
{
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ internal enum ClassLibFunctionId
GetSystemArrayEEType = 5,
OnFirstChance = 6,
OnUnhandledException = 7,
IDynamicCastableIsInterfaceImplemented = 8,
IDynamicCastableGetInterfaceImplementation = 9,
// IDynamicCastableIsInterfaceImplemented = 8,
// IDynamicCastableGetInterfaceImplementation = 9,
ObjectiveCMarshalTryGetTaggedMemory = 10,
ObjectiveCMarshalGetIsTrackedReferenceCallback = 11,
ObjectiveCMarshalGetOnEnteredFinalizerQueueCallback = 12,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -467,11 +467,12 @@ private static unsafe object CheckCastClassSpecial(MethodTable* pTargetType, obj
return ThrowInvalidCastException(pTargetType);
}

internal static unsafe delegate*<object, MethodTable*, bool, bool> s_IDynamicCastableIsInterfaceImplemented;

private static unsafe bool IsInstanceOfInterfaceViaIDynamicInterfaceCastable(MethodTable* pTargetType, object obj, bool throwing)
{
var pfnIsInterfaceImplemented = (delegate*<object, MethodTable*, bool, bool>)
pTargetType->GetClasslibFunction(ClassLibFunctionId.IDynamicCastableIsInterfaceImplemented);
return pfnIsInterfaceImplemented(obj, pTargetType, throwing);
Diagnostics.Debug.Assert(s_IDynamicCastableIsInterfaceImplemented != null);
return s_IDynamicCastableIsInterfaceImplemented(obj, pTargetType, throwing);
}

internal static unsafe bool IsDerived(MethodTable* pDerivedType, MethodTable* pBaseType)
Expand Down
4 changes: 2 additions & 2 deletions src/coreclr/nativeaot/Runtime/ICodeManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,8 @@ enum class ClasslibFunctionId
GetSystemArrayEEType = 5,
OnFirstChanceException = 6,
OnUnhandledException = 7,
IDynamicCastableIsInterfaceImplemented = 8,
IDynamicCastableGetInterfaceImplementation = 9,
// IDynamicCastableIsInterfaceImplemented = 8,
// IDynamicCastableGetInterfaceImplementation = 9,
ObjectiveCMarshalTryGetTaggedMemory = 10,
ObjectiveCMarshalGetIsTrackedReferenceCallback = 11,
ObjectiveCMarshalGetOnEnteredFinalizerQueueCallback = 12,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using System.Runtime;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading;

Expand All @@ -15,16 +16,30 @@ namespace Internal.Runtime
{
internal static unsafe class IDynamicCastableSupport
{
[RuntimeExport("IDynamicCastableIsInterfaceImplemented")]
internal static bool IDynamicCastableIsInterfaceImplemented(IDynamicInterfaceCastable instance, MethodTable* interfaceType, bool throwIfNotImplemented)
internal static unsafe void Register()
{
System.Runtime.TypeCast.s_IDynamicCastableIsInterfaceImplemented = &IDynamicCastableIsInterfaceImplemented;
System.Runtime.CachedInterfaceDispatch.s_IDynamicCastableGetInterfaceImplementation = &IDynamicCastableGetInterfaceImplementation;
}

internal static bool IDynamicCastableIsInterfaceImplemented(object instance, MethodTable* interfaceType, bool throwIfNotImplemented)
{
return IDynamicCastableIsInterfaceImplemented(Unsafe.As<IDynamicInterfaceCastable>(instance), interfaceType, throwIfNotImplemented);
}

private static bool IDynamicCastableIsInterfaceImplemented(IDynamicInterfaceCastable instance, MethodTable* interfaceType, bool throwIfNotImplemented)
{
return instance.IsInterfaceImplemented(new RuntimeTypeHandle(interfaceType), throwIfNotImplemented);
}

private static readonly object s_thunkPoolHeap = RuntimeAugments.CreateThunksHeap(RuntimeImports.GetInteropCommonStubAddress());

[RuntimeExport("IDynamicCastableGetInterfaceImplementation")]
internal static IntPtr IDynamicCastableGetInterfaceImplementation(IDynamicInterfaceCastable instance, MethodTable* interfaceType, ushort slot)
internal static IntPtr IDynamicCastableGetInterfaceImplementation(object instance, MethodTable* interfaceType, ushort slot)
{
return IDynamicCastableGetInterfaceImplementation(Unsafe.As<IDynamicInterfaceCastable>(instance), interfaceType, slot);
}

private static IntPtr IDynamicCastableGetInterfaceImplementation(IDynamicInterfaceCastable instance, MethodTable* interfaceType, ushort slot)
{
RuntimeTypeHandle handle = instance.GetInterfaceImplementation(new RuntimeTypeHandle(interfaceType));
MethodTable* implType = handle.ToMethodTable();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ namespace System.Runtime.CompilerServices
{
// When applied to a type this custom attribute will cause any static class constructor to be run eagerly
// at module load time rather than deferred till just before the class is used.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = false, AllowMultiple = false)]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface, Inherited = false, AllowMultiple = false)]
internal class EagerStaticClassConstructionAttribute : Attribute
{
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ protected override RuntimeInterfacesAlgorithm GetRuntimeInterfacesAlgorithmForDe
return s_noMetadataRuntimeInterfacesAlgorithm;
}

protected internal sealed override bool IsIDynamicInterfaceCastableInterface(DefType type)
public sealed override bool IsIDynamicInterfaceCastableInterface(DefType type)
{
throw new NotImplementedException();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,11 @@ protected virtual MethodDesc ResolveVirtualMethod(MethodDesc declMethod, DefType
public virtual bool CanReferenceConstructedTypeOrCanonicalFormOfType(TypeDesc type) => true;

public virtual TypeDesc[] GetImplementingClasses(TypeDesc type) => null;

public virtual bool CanHaveDynamicInterfaceImplementations()
{
return true;
}
#endif
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ protected internal sealed override bool ComputeHasStaticConstructor(TypeDesc typ
return false;
}

protected internal sealed override bool IsIDynamicInterfaceCastableInterface(DefType type)
public sealed override bool IsIDynamicInterfaceCastableInterface(DefType type)
{
MetadataType t = (MetadataType)type;
return t.Module == SystemModule
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -778,7 +778,7 @@ internal TypeFlags ComputeTypeFlags(TypeDesc type, TypeFlags flags, TypeFlags ma
/// <summary>
/// Determine if the type implements <code>IDynamicInterfaceCastable</code>
/// </summary>
protected internal abstract bool IsIDynamicInterfaceCastableInterface(DefType type);
public abstract bool IsIDynamicInterfaceCastableInterface(DefType type);

public virtual bool SupportsTypeEquivalence => false;
public virtual bool SupportsCOMInterop => false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -309,10 +309,27 @@ public ISymbolNode ComputeConstantLookup(ReadyToRunHelperId lookupKind, object t
var type = (TypeDesc)targetOfLookup;

// We counter-intuitively ask for a constructed type symbol. This is needed due to IDynamicInterfaceCastable.
// If this cast happens with an object that implements IDynamicIntefaceCastable, user code will
// If this cast happens with an object that implements IDynamicInterfaceCastable, user code will
// see a RuntimeTypeHandle representing this interface.
// We don't need to do this for IDynamicInterfaceCastable itself, as the type system will never ask an object
// that implements IDynamicInterfaceCastable statically if it dynamically implements it.
// If we've determined that IDynamicInterfaceCastable is not implemented by any type,
// we can return a necessary type symbol.
if (type.IsInterface)
return NodeFactory.MaximallyConstructableType(type);
{
if (TypeSystemContext.IsIDynamicInterfaceCastableInterface((DefType)type))
{
return NecessaryTypeSymbolIfPossible(type);
}
else if (NodeFactory.DevirtualizationManager.CanHaveDynamicInterfaceImplementations())
{
return NodeFactory.MaximallyConstructableType(type);
}
else
{
return NecessaryTypeSymbolIfPossible(type);
}
}

if (type.IsNullable)
type = type.Instantiation[0];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ protected override DependencyList ComputeNonRelocationBasedDependencies(NodeFact

factory.InteropStubManager.AddInterestingInteropConstructedTypeDependencies(ref dependencyList, factory, _type);

if (factory.TypeSystemContext.IsIDynamicInterfaceCastableInterface(closestDefType))
{
Debug.Assert(factory.PreinitializationManager.HasEagerStaticConstructor(_type));
dependencyList.Add(factory.EagerCctorIndirection(_type.GetStaticConstructor()), "IDynamicInterfaceCastable eager .cctor");
}

return dependencyList;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1468,15 +1468,15 @@ public StructMarshallingDataNode StructMarshallingData(DefType type)
/// Returns alternative symbol name that object writer should produce for given symbols
/// in addition to the regular one.
/// </summary>
public string GetSymbolAlternateName(ISymbolNode node, out bool isHidden)
public string GetSymbolAlternateName(ISymbolNode node, out bool hidden)
{
if (!NodeAliases.TryGetValue(node, out var value))
{
isHidden = false;
hidden = false;
return null;
}

isHidden = value.Hidden;
hidden = value.Hidden;
return value.Name;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -444,10 +444,12 @@ private sealed class ScannedDevirtualizationManager : DevirtualizationManager
private HashSet<TypeDesc> _disqualifiedTypes = new();
private HashSet<MethodDesc> _overriddenMethods = new();
private HashSet<MethodDesc> _generatedVirtualMethods = new();
private readonly bool _canHaveDynamicInterfaceImplementations;

public ScannedDevirtualizationManager(NodeFactory factory, ImmutableArray<DependencyNodeCore<NodeFactory>> markedNodes)
{
var vtables = new Dictionary<TypeDesc, List<MethodDesc>>();
var dynamicInterfaceCastableImplementationTargets = new HashSet<TypeDesc>();

foreach (var node in markedNodes)
{
Expand Down Expand Up @@ -487,7 +489,7 @@ public ScannedDevirtualizationManager(NodeFactory factory, ImmutableArray<Depend
// If the interface is implemented through IDynamicInterfaceCastable, there might be
// no real upper bound on the number of actual classes implementing it.
if (CanAssumeWholeProgramViewOnTypeUse(factory, type, baseInterface))
_disqualifiedTypes.Add(baseInterface);
dynamicInterfaceCastableImplementationTargets.Add(baseInterface);
}
}
}
Expand All @@ -513,6 +515,16 @@ public ScannedDevirtualizationManager(NodeFactory factory, ImmutableArray<Depend
{
RecordImplementation(baseInterface, type);
}

if (factory.TypeSystemContext.IsIDynamicInterfaceCastableInterface(baseInterface))
{
// We've seen a type that implements IDynamicInterfaceCastable.
// This means that we might not be able to see all interface implementations statically.
// We can't analyze what the IDynamicInterfaceCastable implementation does,
// so we have to assume it can implement any interface
// (that has a DynamicInterfaceCastableImplementation-attributed derived type).
_canHaveDynamicInterfaceImplementations = true;
}
}

// Record all base types of this class
Expand Down Expand Up @@ -619,6 +631,11 @@ static List<MethodDesc> BuildVTable(NodeFactory factory, TypeDesc currentType, T
}
}
}

if (_canHaveDynamicInterfaceImplementations)
{
_disqualifiedTypes.UnionWith(dynamicInterfaceCastableImplementationTargets);
}
}

private static bool CanAssumeWholeProgramViewOnTypeUse(NodeFactory factory, TypeDesc implementingType, DefType baseType)
Expand Down Expand Up @@ -774,6 +791,8 @@ public override TypeDesc[] GetImplementingClasses(TypeDesc type)
}
return null;
}

public override bool CanHaveDynamicInterfaceImplementations() => _canHaveDynamicInterfaceImplementations;
}

private sealed class ScannedInliningPolicy : IInliningPolicy
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ protected override DependencyList ComputeNonRelocationBasedDependencies(NodeFact
DependencyList dependencies = _nonRelocationDependencies != null ? new DependencyList(_nonRelocationDependencies) : null;

TypeDesc owningType = _method.OwningType;

if (factory.PreinitializationManager.HasEagerStaticConstructor(owningType))
{
dependencies ??= new DependencyList();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Runtime.CompilerServices;

namespace System.Runtime.InteropServices
{
/// <summary>
Expand All @@ -10,8 +12,17 @@ namespace System.Runtime.InteropServices
/// Implementation of this interface on a value type will be ignored. Only non-value types are allowed
/// to participate in a type cast failure through this interface.
/// </remarks>
#if NATIVEAOT
[EagerStaticClassConstruction]
#endif
public interface IDynamicInterfaceCastable
{
#if NATIVEAOT
static IDynamicInterfaceCastable()
{
Internal.Runtime.IDynamicCastableSupport.Register();
}
#endif
/// <summary>
/// Called when an implementing class instance is cast to an interface type that
/// is not contained in the class's metadata.
Expand Down Expand Up @@ -56,7 +67,7 @@ public interface IDynamicInterfaceCastable
/// <see cref="IDynamicInterfaceCastable" /> scenarios trimming friendly.
/// </remarks>
[AttributeUsage(AttributeTargets.Interface, AllowMultiple = false, Inherited = false)]
public sealed class DynamicInterfaceCastableImplementationAttribute : Attribute
public sealed partial class DynamicInterfaceCastableImplementationAttribute : Attribute
{
public DynamicInterfaceCastableImplementationAttribute()
{
Expand Down
Loading