Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
45 changes: 44 additions & 1 deletion 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 @@ -134,6 +134,49 @@ MANAGED_RUNTIME_EXPORT(ObjectiveCMarshalGetOnEnteredFinalizerQueueCallback)
MANAGED_RUNTIME_EXPORT(ObjectiveCMarshalGetUnhandledExceptionPropagationHandler)
#endif

// Define "default" implementations for IDynamicInterfaceCastable methods.
// The runtime will only export these if IDynamicInterfaceCastable is used in the application.

#if defined(HOST_WINDOWS)
#if defined(HOST_X86)

#pragma comment(linker, "/alternatename:_IDynamicCastableIsInterfaceImplemented=_IDynamicCastableIsInterfaceImplementedFallback")
extern "C" void __cdecl IDynamicCastableIsInterfaceImplementedFallback()
{
abort();
}

#pragma comment(linker, "/alternatename:_IDynamicCastableGetInterfaceImplementation=_IDynamicCastableGetInterfaceImplementationFallback")
extern "C" void __cdecl IDynamicCastableGetInterfaceImplementationFallback()
{
abort();
}
#else
#pragma comment(linker, "/alternatename:IDynamicCastableIsInterfaceImplemented=IDynamicCastableIsInterfaceImplementedFallback")
extern "C" void __cdecl IDynamicCastableIsInterfaceImplementedFallback()
{
abort();
}

#pragma comment(linker, "/alternatename:IDynamicCastableGetInterfaceImplementation=IDynamicCastableGetInterfaceImplementationFallback")
extern "C" void __cdecl IDynamicCastableGetInterfaceImplementationFallback()
{
abort();
}
#endif // HOST_X86

#else
extern "C" __attribute__((weak)) void IDynamicCastableIsInterfaceImplemented()
{
abort();
}

extern "C" __attribute__((weak)) void IDynamicCastableGetInterfaceImplementation()
{
abort();
}
#endif // HOST_WINDOWS

typedef void (CDECL *pfn)();

static const pfn c_classlibFunctions[] = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@ namespace Internal.Runtime
{
internal static unsafe class IDynamicCastableSupport
{
[RuntimeExport("IDynamicCastableIsInterfaceImplemented")]
[RuntimeExport("IDynamicCastableIsInterfaceImplemented", ConditionalConstructedDependency = typeof(IDynamicInterfaceCastable))]
internal 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")]
[RuntimeExport("IDynamicCastableGetInterfaceImplementation", ConditionalConstructedDependency = typeof(IDynamicInterfaceCastable))]
internal static IntPtr IDynamicCastableGetInterfaceImplementation(IDynamicInterfaceCastable instance, MethodTable* interfaceType, ushort slot)
{
RuntimeTypeHandle handle = instance.GetInterfaceImplementation(new RuntimeTypeHandle(interfaceType));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ internal sealed class RuntimeExportAttribute : Attribute
{
public string EntryPoint;

public Type? ConditionalConstructedDependency;

public RuntimeExportAttribute(string entry)
{
EntryPoint = entry;
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
}
}
27 changes: 21 additions & 6 deletions src/coreclr/tools/Common/Compiler/MethodExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,24 +40,33 @@ public static string GetRuntimeImportDllName(this EcmaMethod This)
return null;
}

public static string GetRuntimeExportName(this EcmaMethod This)
public static RuntimeExportInfo GetRuntimeExportInfo(this EcmaMethod This)
{
var decoded = This.GetDecodedCustomAttribute("System.Runtime", "RuntimeExportAttribute");
if (decoded == null)
return null;
return default;

var decodedValue = decoded.Value;

if (decodedValue.FixedArguments.Length != 0)
return (string)decodedValue.FixedArguments[0].Value;
RuntimeExportInfo exportInfo = default;

if (decodedValue.FixedArguments.Length != 0)
{
exportInfo.Name = (string)decodedValue.FixedArguments[0].Value;
}
foreach (var argument in decodedValue.NamedArguments)
{
if (argument.Name == "EntryPoint")
return (string)argument.Value;
{
exportInfo.Name = (string)argument.Value;
}
else if (argument.Name == "ConditionalConstructedDependency")
{
exportInfo.ConditionalConstructedDependency = (TypeDesc)argument.Value;
}
}

return null;
return exportInfo;
}

public static string GetUnmanagedCallersOnlyExportName(this EcmaMethod This)
Expand Down Expand Up @@ -136,4 +145,10 @@ public static bool NotCallableWithoutOwningEEType(this MethodDesc method)
!method.IsSharedByGenericInstantiations; /* Current impl limitation; can be lifted */
}
}

public struct RuntimeExportInfo
{
public string Name;
public TypeDesc ConditionalConstructedDependency;
}
}
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 @@ -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 @@ -3,7 +3,8 @@

using System.Collections.Generic;
using System.Reflection.Metadata;

using ILCompiler.DependencyAnalysis;
using ILCompiler.DependencyAnalysisFramework;
using Internal.TypeSystem;
using Internal.TypeSystem.Ecma;

Expand Down Expand Up @@ -43,8 +44,13 @@ public IEnumerable<EcmaMethod> ExportedMethods
&& comparer.Equals(nsHandle, "System.Runtime"))
{
var method = (EcmaMethod)_module.GetMethod(ca.Parent);
if (method.GetRuntimeExportName() != null)

RuntimeExportInfo exportInfo = method.GetRuntimeExportInfo();

if (exportInfo.Name != null)
{
yield return method;
}
}

if (comparer.Equals(nameHandle, "UnmanagedCallersOnlyAttribute")
Expand All @@ -62,17 +68,61 @@ public void AddCompilationRoots(IRootingServiceProvider rootProvider)
{
foreach (var ecmaMethod in ExportedMethods)
{
if (ecmaMethod.IsUnmanagedCallersOnly)
if (ecmaMethod.GetUnmanagedCallersOnlyExportName() != null)
{
string unmanagedCallersOnlyExportName = ecmaMethod.GetUnmanagedCallersOnlyExportName();
rootProvider.AddCompilationRoot((MethodDesc)ecmaMethod, "Native callable", unmanagedCallersOnlyExportName, Hidden);
rootProvider.AddCompilationRoot(ecmaMethod, "Native callable", unmanagedCallersOnlyExportName, Hidden);
}
else
{
string runtimeExportName = ecmaMethod.GetRuntimeExportName();
rootProvider.AddCompilationRoot((MethodDesc)ecmaMethod, "Runtime export", runtimeExportName, Hidden);
RuntimeExportInfo runtimeExportInfo = ecmaMethod.GetRuntimeExportInfo();

if (runtimeExportInfo.ConditionalConstructedDependency is null)
{
rootProvider.AddCompilationRoot(ecmaMethod, "Runtime export", runtimeExportInfo.Name, Hidden);
}
else
{
rootProvider.AddCompilationRoot(new ConditionalRuntimeExportNode(ecmaMethod, runtimeExportInfo.Name, Hidden, runtimeExportInfo.ConditionalConstructedDependency), "Runtime export");
}
}
}
}

private sealed class ConditionalRuntimeExportNode(EcmaMethod method, string exportName, bool hidden, TypeDesc dependency) : DependencyNodeCore<NodeFactory>
{
public override bool StaticDependenciesAreComputed => true;
public override bool HasConditionalStaticDependencies => true;

public override bool InterestingForDynamicDependencyAnalysis => false;

public override bool HasDynamicDependencies => true;

public override IEnumerable<CombinedDependencyListEntry> GetConditionalStaticDependencies(NodeFactory context)
{
List<CombinedDependencyListEntry> dependencies = [];
MethodDesc canonMethod = method.GetCanonMethodTarget(CanonicalFormKind.Specific);
IMethodNode methodEntryPoint = context.MethodEntrypoint(canonMethod);

dependencies.Add(new CombinedDependencyListEntry(methodEntryPoint, context.MaximallyConstructableType(dependency), "Conditional type constructed"));

if (exportName != null)
{
exportName = context.NameMangler.NodeMangler.ExternMethod(exportName, method);
context.NodeAliases.Add(methodEntryPoint, (exportName, hidden));
}

if (canonMethod != method && method.HasInstantiation)
{
dependencies.Add(new CombinedDependencyListEntry(context.MethodGenericDictionary(method), context.MaximallyConstructableType(dependency), "Conditional type constructed"));
}

return dependencies;
}

public override IEnumerable<DependencyListEntry> GetStaticDependencies(NodeFactory context) => [];
public override IEnumerable<CombinedDependencyListEntry> SearchDynamicDependencies(List<DependencyNodeCore<NodeFactory>> markedNodes, int firstNode, NodeFactory context) => [];
protected override string GetName(NodeFactory context) => $"Conditional runtime export on usage of {dependency}";
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ public sealed override string ExternMethod(string unmangledName, MethodDesc meth
}
else
{
Debug.Assert(method is Internal.TypeSystem.Ecma.EcmaMethod ecmaMethod && (ecmaMethod.GetRuntimeImportName() != null || ecmaMethod.GetRuntimeExportName() != null));
Debug.Assert(method is Internal.TypeSystem.Ecma.EcmaMethod ecmaMethod && (ecmaMethod.GetRuntimeImportName() != null || ecmaMethod.GetRuntimeExportInfo().Name != null));
return unmangledName;
}

Expand Down
Loading