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));