diff --git a/src/coreclr/tools/Common/Compiler/ObjectDataInterner.cs b/src/coreclr/tools/Common/Compiler/ObjectDataInterner.cs
new file mode 100644
index 00000000000000..d986e238ad3943
--- /dev/null
+++ b/src/coreclr/tools/Common/Compiler/ObjectDataInterner.cs
@@ -0,0 +1,88 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Collections.Generic;
+
+using ILCompiler.DependencyAnalysis;
+
+using Debug = System.Diagnostics.Debug;
+
+namespace ILCompiler
+{
+ ///
+ /// Pluggable strategy for identifying and deduplicating equivalent object data.
+ ///
+ public interface IObjectDataDeduplicator
+ {
+ ///
+ /// Performs one deduplication pass, adding entries to .
+ /// Called iteratively until the overall mapping converges.
+ ///
+ void DeduplicatePass(NodeFactory factory, Dictionary previousSymbolRemapping, Dictionary symbolRemapping);
+ }
+
+ public sealed class ObjectDataInterner
+ {
+ private readonly IObjectDataDeduplicator[] _deduplicators;
+ private Dictionary _symbolRemapping;
+
+ public static ObjectDataInterner Null { get; } = new ObjectDataInterner() { _symbolRemapping = new() };
+
+ public ObjectDataInterner(params IObjectDataDeduplicator[] deduplicators)
+ {
+ _deduplicators = deduplicators;
+ }
+
+ private void EnsureMap(NodeFactory factory)
+ {
+ Debug.Assert(factory.MarkingComplete);
+
+ if (_symbolRemapping != null)
+ return;
+
+ Dictionary previousSymbolRemapping;
+ Dictionary symbolRemapping = null;
+
+ do
+ {
+ previousSymbolRemapping = symbolRemapping;
+ symbolRemapping = new Dictionary((int)(1.05 * (previousSymbolRemapping?.Count ?? 0)));
+
+ foreach (IObjectDataDeduplicator deduplicator in _deduplicators)
+ {
+ deduplicator.DeduplicatePass(factory, previousSymbolRemapping, symbolRemapping);
+ }
+ } while (!MappingsEqual(previousSymbolRemapping, symbolRemapping));
+
+ _symbolRemapping = symbolRemapping;
+ }
+
+ private static bool MappingsEqual(Dictionary a, Dictionary b)
+ {
+ if (a is null)
+ return false;
+
+ if (a.Count != b.Count)
+ return false;
+
+ foreach (KeyValuePair kvp in a)
+ {
+ if (!b.TryGetValue(kvp.Key, out ISymbolNode value) || value != kvp.Value)
+ return false;
+ }
+
+ return true;
+ }
+
+ public ISymbolNode GetDeduplicatedSymbol(NodeFactory factory, ISymbolNode original)
+ {
+ EnsureMap(factory);
+
+ ISymbolNode target = original;
+ if (target is ISymbolNodeWithLinkage symbolWithLinkage)
+ target = symbolWithLinkage.NodeForLinkage(factory);
+
+ return _symbolRemapping.TryGetValue(target, out ISymbolNode result) ? result : original;
+ }
+ }
+}
diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs
index a4eca181141b60..b9c1cfcf1832a5 100644
--- a/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs
+++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs
@@ -400,14 +400,15 @@ public virtual void EmitObject(Stream outputFileStream, IReadOnlyCollection checksumRelocationsBuilder = default;
foreach (Relocation reloc in blockToRelocate.Relocations)
{
-#if READYTORUN
- ISymbolNode relocTarget = reloc.Target;
-#else
ISymbolNode relocTarget = _nodeFactory.ObjectInterner.GetDeduplicatedSymbol(_nodeFactory, reloc.Target);
-#endif
if (reloc.RelocType == RelocType.IMAGE_REL_FILE_CHECKSUM_CALLBACK)
{
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs
index d69693286b0bcb..bdec43b6ce9873 100644
--- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs
+++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs
@@ -134,6 +134,8 @@ internal ObjectDataInterner ObjectInterner
get;
}
+ protected virtual bool CanFold(MethodDesc method) => false;
+
public TypeMapManager TypeMapManager
{
get;
@@ -1138,7 +1140,7 @@ public IMethodNode FatFunctionPointer(MethodDesc method, bool isUnboxingStub = f
public IMethodNode FatAddressTakenFunctionPointer(MethodDesc method, bool isUnboxingStub = false)
{
- if (!ObjectInterner.CanFold(method))
+ if (!CanFold(method))
return FatFunctionPointer(method, isUnboxingStub);
return _fatAddressTakenFunctionPointers.GetOrAdd(new MethodKey(method, isUnboxingStub));
@@ -1204,7 +1206,7 @@ internal TypeGVMEntriesNode TypeGVMEntries(TypeDesc type)
private NodeCache _addressTakenMethods;
public IMethodNode AddressTakenMethodEntrypoint(MethodDesc method, bool unboxingStub = false)
{
- if (unboxingStub || !ObjectInterner.CanFold(method))
+ if (unboxingStub || !CanFold(method))
return MethodEntrypoint(method, unboxingStub);
return _addressTakenMethods.GetOrAdd(method);
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectDataInterner.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/MethodBodyDeduplicator.cs
similarity index 67%
rename from src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectDataInterner.cs
rename to src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/MethodBodyDeduplicator.cs
index 9092b870f818da..94880f401f49b3 100644
--- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectDataInterner.cs
+++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/MethodBodyDeduplicator.cs
@@ -9,27 +9,20 @@
using Internal.IL.Stubs;
using Internal.TypeSystem;
-using Debug = System.Diagnostics.Debug;
-
namespace ILCompiler
{
- public sealed class ObjectDataInterner
+ public sealed class MethodBodyDeduplicator : IObjectDataDeduplicator
{
private readonly bool _genericsOnly;
- private Dictionary _symbolRemapping;
-
- public static ObjectDataInterner Null { get; } = new ObjectDataInterner(genericsOnly: false) { _symbolRemapping = new() };
+ private int _previousHashCount;
- public ObjectDataInterner(bool genericsOnly)
+ public MethodBodyDeduplicator(bool genericsOnly)
{
_genericsOnly = genericsOnly;
}
public bool CanFold(MethodDesc method)
{
- if (this == Null)
- return false;
-
if (!_genericsOnly || method.HasInstantiation || method.OwningType.HasInstantiation)
return true;
@@ -39,63 +32,36 @@ public bool CanFold(MethodDesc method)
return false;
}
- private void EnsureMap(NodeFactory factory)
+ public void DeduplicatePass(NodeFactory factory, Dictionary previousSymbolRemapping, Dictionary symbolRemapping)
{
- Debug.Assert(factory.MarkingComplete);
-
- if (_symbolRemapping != null)
- return;
-
- HashSet previousMethodHash;
- HashSet methodHash = null;
- Dictionary previousSymbolRemapping;
- Dictionary symbolRemapping = null;
+ var methodHash = new HashSet(_previousHashCount, new MethodInternComparer(factory, previousSymbolRemapping, _genericsOnly));
- do
+ foreach (IMethodBodyNode body in factory.MetadataManager.GetCompiledMethodBodies())
{
- previousMethodHash = methodHash;
- previousSymbolRemapping = symbolRemapping;
- methodHash = new HashSet(previousMethodHash?.Count ?? 0, new MethodInternComparer(factory, previousSymbolRemapping, _genericsOnly));
- symbolRemapping = new Dictionary((int)(1.05 * (previousSymbolRemapping?.Count ?? 0)));
+ if (!CanFold(body.Method))
+ continue;
- foreach (IMethodBodyNode body in factory.MetadataManager.GetCompiledMethodBodies())
- {
- if (!CanFold(body.Method))
- continue;
-
- // We don't track special unboxing thunks as virtual method use related so ignore them
- if (body is ISpecialUnboxThunkNode unboxThunk && unboxThunk.IsSpecialUnboxingThunk)
- continue;
+ // We don't track special unboxing thunks as virtual method use related so ignore them
+ if (body is ISpecialUnboxThunkNode unboxThunk && unboxThunk.IsSpecialUnboxingThunk)
+ continue;
- // Bodies that are visible from outside should not be folded because we don't know
- // if they're address taken.
- if (!factory.GetSymbolAlternateName(body, out _).IsNull)
- continue;
+ // Bodies that are visible from outside should not be folded because we don't know
+ // if they're address taken.
+ if (!factory.GetSymbolAlternateName(body, out _).IsNull)
+ continue;
- var key = new MethodInternKey(body, factory);
- if (methodHash.TryGetValue(key, out MethodInternKey found))
- {
- symbolRemapping.Add(body, found.Method);
- }
- else
- {
- methodHash.Add(key);
- }
+ var key = new MethodInternKey(body, factory);
+ if (methodHash.TryGetValue(key, out MethodInternKey found))
+ {
+ symbolRemapping.TryAdd(body, found.Method);
}
- } while (previousSymbolRemapping == null || previousSymbolRemapping.Count < symbolRemapping.Count);
-
- _symbolRemapping = symbolRemapping;
- }
-
- public ISymbolNode GetDeduplicatedSymbol(NodeFactory factory, ISymbolNode original)
- {
- EnsureMap(factory);
-
- ISymbolNode target = original;
- if (target is ISymbolNodeWithLinkage symbolWithLinkage)
- target = symbolWithLinkage.NodeForLinkage(factory);
+ else
+ {
+ methodHash.Add(key);
+ }
+ }
- return _symbolRemapping.TryGetValue(target, out ISymbolNode result) ? result : original;
+ _previousHashCount = methodHash.Count;
}
private sealed class MethodInternKey
@@ -218,7 +184,7 @@ public bool Equals(MethodInternKey a, MethodInternKey b)
if (o1eh == o2eh)
return true;
- if (o1eh == null || o2eh == null)
+ if (o1eh is null || o2eh is null)
return false;
return AreSame(o1eh.GetData(_factory, relocsOnly: false), o2eh.GetData(_factory, relocsOnly: false));
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj
index 4592ac765a86a7..a0e5f77c7c533b 100644
--- a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj
+++ b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj
@@ -503,7 +503,8 @@
-
+
+
diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/CopiedMethodILDeduplicator.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/CopiedMethodILDeduplicator.cs
new file mode 100644
index 00000000000000..879ce8d4076174
--- /dev/null
+++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/CopiedMethodILDeduplicator.cs
@@ -0,0 +1,88 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Collections.Generic;
+
+using ILCompiler.DependencyAnalysis;
+
+namespace ILCompiler.DependencyAnalysis.ReadyToRun
+{
+ public sealed class CopiedMethodILDeduplicator : IObjectDataDeduplicator
+ {
+ private readonly Func> _nodesProvider;
+ private Dictionary _cachedMapping;
+
+ public CopiedMethodILDeduplicator(Func> nodesProvider)
+ {
+ _nodesProvider = nodesProvider;
+ }
+
+ public void DeduplicatePass(NodeFactory factory, Dictionary previousSymbolRemapping, Dictionary symbolRemapping)
+ {
+ if (_cachedMapping is null)
+ {
+ _cachedMapping = new Dictionary();
+
+ var sortedNodes = new List(_nodesProvider());
+ sortedNodes.Sort(CompilerComparer.Instance);
+
+ var hashSet = new HashSet(new InternComparer());
+
+ foreach (CopiedMethodILNode node in sortedNodes)
+ {
+ // No need to deduplicate unmarked nodes.
+ // They won't be emitted anyway.
+ if (!node.Marked)
+ {
+ continue;
+ }
+ var key = new InternKey(node, factory);
+ if (hashSet.TryGetValue(key, out InternKey existing))
+ {
+ _cachedMapping[node] = existing.Node;
+ }
+ else
+ {
+ hashSet.Add(key);
+ }
+ }
+ }
+
+ foreach (KeyValuePair entry in _cachedMapping)
+ {
+ symbolRemapping.TryAdd(entry.Key, entry.Value);
+ }
+ }
+
+ private sealed class InternKey
+ {
+ public CopiedMethodILNode Node { get; }
+ public int HashCode { get; }
+ public byte[] Data { get; }
+
+ public InternKey(CopiedMethodILNode node, NodeFactory factory)
+ {
+ Node = node;
+
+ Data = node.GetData(factory, relocsOnly: false).Data;
+ var hashCode = new HashCode();
+ hashCode.AddBytes(Data);
+ HashCode = hashCode.ToHashCode();
+ }
+ }
+
+ private sealed class InternComparer : IEqualityComparer
+ {
+ public int GetHashCode(InternKey key) => key.HashCode;
+
+ public bool Equals(InternKey a, InternKey b)
+ {
+ if (a.HashCode != b.HashCode)
+ return false;
+
+ return a.Data.AsSpan().SequenceEqual(b.Data);
+ }
+ }
+ }
+}
diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs
index d544c741e2d11b..f18ae2b51389b4 100644
--- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs
+++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs
@@ -45,6 +45,8 @@ public TValue GetOrAdd(TKey key)
{
return _cache.GetOrAdd(key, _creator);
}
+
+ public ICollection Values => _cache.Values;
}
public enum TypeValidationRule
@@ -87,6 +89,8 @@ public sealed class NodeFactory
public MetadataManager MetadataManager { get; }
+ public ObjectDataInterner ObjectInterner { get; }
+
public CompositeImageSettings CompositeImageSettings { get; set; }
public readonly NodeFactoryOptimizationFlags OptimizationFlags;
@@ -240,6 +244,8 @@ public NodeFactory(
CreateNodeCaches();
+ ObjectInterner = new ObjectDataInterner(new CopiedMethodILDeduplicator(() => _copiedMethodIL.Values));
+
if (genericCycleBreadthCutoff >= 0 || genericCycleDepthCutoff >= 0)
{
_genericCycleDetector = new LazyGenericsSupport.GenericCycleDetector(
diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.cs b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.cs
index d254986e09d2a3..931a6cb846c99c 100644
--- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.cs
+++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilation.cs
@@ -534,6 +534,7 @@ private void RewriteComponentFile(string inputFile, string outputFile, string ow
}
componentGraph.ComputeMarkedNodes();
componentFactory.Header.Add(Internal.Runtime.ReadyToRunSectionType.OwnerCompositeExecutable, ownerExecutableNode);
+ componentFactory.SetMarkingComplete();
ReadyToRunObjectWriter.EmitObject(
outputFile,
componentModule: inputModule,
diff --git a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj
index cf18c52c27f3d2..54cba4f077a7fc 100644
--- a/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj
+++ b/src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj
@@ -71,6 +71,7 @@
+
@@ -235,6 +236,7 @@
+
diff --git a/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/DependencyAnalysis/RyuJitNodeFactory.cs b/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/DependencyAnalysis/RyuJitNodeFactory.cs
index 0e5574b7dd652c..8992e89f2d54e4 100644
--- a/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/DependencyAnalysis/RyuJitNodeFactory.cs
+++ b/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/DependencyAnalysis/RyuJitNodeFactory.cs
@@ -9,13 +9,19 @@ namespace ILCompiler.DependencyAnalysis
{
public sealed class RyuJitNodeFactory : NodeFactory
{
+ private readonly MethodBodyDeduplicator _methodBodyDeduplicator;
+
public RyuJitNodeFactory(CompilerTypeSystemContext context, CompilationModuleGroup compilationModuleGroup, MetadataManager metadataManager,
InteropStubManager interopStubManager, NameMangler nameMangler, VTableSliceProvider vtableSliceProvider, DictionaryLayoutProvider dictionaryLayoutProvider, InlinedThreadStatics inlinedThreadStatics, PreinitializationManager preinitializationManager,
- DevirtualizationManager devirtualizationManager, ObjectDataInterner dataInterner, TypeMapManager typeMapManager)
+ DevirtualizationManager devirtualizationManager, ObjectDataInterner dataInterner, MethodBodyDeduplicator methodBodyDeduplicator, TypeMapManager typeMapManager)
: base(context, compilationModuleGroup, metadataManager, interopStubManager, nameMangler, new LazyGenericsDisabledPolicy(), vtableSliceProvider, dictionaryLayoutProvider, inlinedThreadStatics, new ExternSymbolsImportedNodeProvider(), preinitializationManager, devirtualizationManager, dataInterner, typeMapManager)
{
+ _methodBodyDeduplicator = methodBodyDeduplicator;
}
+ protected override bool CanFold(MethodDesc method)
+ => _methodBodyDeduplicator?.CanFold(method) ?? false;
+
protected override IMethodNode CreateMethodEntrypointNode(MethodDesc method)
{
if (method.IsInternalCall)
diff --git a/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/RyuJitCompilationBuilder.cs b/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/RyuJitCompilationBuilder.cs
index d4c69d52a3aa93..bbec7799701b31 100644
--- a/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/RyuJitCompilationBuilder.cs
+++ b/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/RyuJitCompilationBuilder.cs
@@ -136,14 +136,18 @@ public override ICompilation ToCompilation()
if (_resilient)
options |= RyuJitCompilationOptions.UseResilience;
- ObjectDataInterner interner = _methodBodyFolding switch
+ MethodBodyDeduplicator methodBodyDeduplicator = _methodBodyFolding switch
{
- MethodBodyFoldingMode.Generic => new ObjectDataInterner(genericsOnly: true),
- MethodBodyFoldingMode.All => new ObjectDataInterner(genericsOnly: false),
- _ => ObjectDataInterner.Null,
+ MethodBodyFoldingMode.Generic => new MethodBodyDeduplicator(genericsOnly: true),
+ MethodBodyFoldingMode.All => new MethodBodyDeduplicator(genericsOnly: false),
+ _ => null,
};
- var factory = new RyuJitNodeFactory(_context, _compilationGroup, _metadataManager, _interopStubManager, _nameMangler, _vtableSliceProvider, _dictionaryLayoutProvider, _inlinedThreadStatics, GetPreinitializationManager(), _devirtualizationManager, interner, _typeMapManager);
+ ObjectDataInterner interner = methodBodyDeduplicator is not null
+ ? new ObjectDataInterner(methodBodyDeduplicator)
+ : ObjectDataInterner.Null;
+
+ var factory = new RyuJitNodeFactory(_context, _compilationGroup, _metadataManager, _interopStubManager, _nameMangler, _vtableSliceProvider, _dictionaryLayoutProvider, _inlinedThreadStatics, GetPreinitializationManager(), _devirtualizationManager, interner, methodBodyDeduplicator, _typeMapManager);
JitConfigProvider.Initialize(_context.Target, jitFlagBuilder.ToArray(), _ryujitOptions, _jitPath);
DependencyAnalyzerBase graph = CreateDependencyGraph(factory, new ObjectNode.ObjectNodeComparer(CompilerComparer.Instance));