Skip to content
Merged
88 changes: 88 additions & 0 deletions src/coreclr/tools/Common/Compiler/ObjectDataInterner.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Pluggable strategy for identifying and deduplicating equivalent object data.
/// </summary>
public interface IObjectDataDeduplicator
{
/// <summary>
/// Performs one deduplication pass, adding entries to <paramref name="symbolRemapping"/>.
/// Called iteratively until the overall mapping converges.
/// </summary>
void DeduplicatePass(NodeFactory factory, Dictionary<ISymbolNode, ISymbolNode> previousSymbolRemapping, Dictionary<ISymbolNode, ISymbolNode> symbolRemapping);
}
Comment thread
jkoritzinsky marked this conversation as resolved.

public sealed class ObjectDataInterner
{
private readonly IObjectDataDeduplicator[] _deduplicators;
private Dictionary<ISymbolNode, ISymbolNode> _symbolRemapping;

public static ObjectDataInterner Null { get; } = new ObjectDataInterner() { _symbolRemapping = new() };

public ObjectDataInterner(params IObjectDataDeduplicator[] deduplicators)
{
_deduplicators = deduplicators;
}
Comment thread
jkoritzinsky marked this conversation as resolved.

private void EnsureMap(NodeFactory factory)
{
Debug.Assert(factory.MarkingComplete);

if (_symbolRemapping != null)
return;

Dictionary<ISymbolNode, ISymbolNode> previousSymbolRemapping;
Dictionary<ISymbolNode, ISymbolNode> symbolRemapping = null;

do
{
previousSymbolRemapping = symbolRemapping;
symbolRemapping = new Dictionary<ISymbolNode, ISymbolNode>((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<ISymbolNode, ISymbolNode> a, Dictionary<ISymbolNode, ISymbolNode> b)
{
if (a is null)
return false;

if (a.Count != b.Count)
return false;

foreach (KeyValuePair<ISymbolNode, ISymbolNode> 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;
}
}
}
19 changes: 7 additions & 12 deletions src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -400,14 +400,15 @@ public virtual void EmitObject(Stream outputFileStream, IReadOnlyCollection<Depe

ISymbolNode symbolNode = node as ISymbolNode;

#if !READYTORUN
ISymbolNode deduplicatedSymbolNode = _nodeFactory.ObjectInterner.GetDeduplicatedSymbol(_nodeFactory, symbolNode);
if (deduplicatedSymbolNode != symbolNode)
if (symbolNode is not null)
{
dumper?.ReportFoldedNode(_nodeFactory, node, deduplicatedSymbolNode);
continue;
ISymbolNode deduplicatedSymbolNode = _nodeFactory.ObjectInterner.GetDeduplicatedSymbol(_nodeFactory, symbolNode);
if (deduplicatedSymbolNode != symbolNode)
{
dumper?.ReportFoldedNode(_nodeFactory, node, deduplicatedSymbolNode);
continue;
}
}
#endif

ObjectData nodeContents = node.GetData(_nodeFactory);

Expand Down Expand Up @@ -578,10 +579,8 @@ public virtual void EmitObject(Stream outputFileStream, IReadOnlyCollection<Depe
continue;
}

#if !READYTORUN
startNode = _nodeFactory.ObjectInterner.GetDeduplicatedSymbol(_nodeFactory, startNode);
endNode = _nodeFactory.ObjectInterner.GetDeduplicatedSymbol(_nodeFactory, endNode);
#endif
Utf8String startNodeName = GetMangledName(startNode);
Utf8String endNodeName = GetMangledName(endNode);

Expand All @@ -600,11 +599,7 @@ public virtual void EmitObject(Stream outputFileStream, IReadOnlyCollection<Depe
ArrayBuilder<Relocation> 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)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@ internal ObjectDataInterner ObjectInterner
get;
}

protected virtual bool CanFold(MethodDesc method) => false;

public TypeMapManager TypeMapManager
{
get;
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -1204,7 +1206,7 @@ internal TypeGVMEntriesNode TypeGVMEntries(TypeDesc type)
private NodeCache<MethodDesc, AddressTakenMethodNode> _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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<ISymbolNode, ISymbolNode> _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;

Expand All @@ -39,63 +32,36 @@ public bool CanFold(MethodDesc method)
return false;
}

private void EnsureMap(NodeFactory factory)
public void DeduplicatePass(NodeFactory factory, Dictionary<ISymbolNode, ISymbolNode> previousSymbolRemapping, Dictionary<ISymbolNode, ISymbolNode> symbolRemapping)
{
Debug.Assert(factory.MarkingComplete);

if (_symbolRemapping != null)
return;

HashSet<MethodInternKey> previousMethodHash;
HashSet<MethodInternKey> methodHash = null;
Dictionary<ISymbolNode, ISymbolNode> previousSymbolRemapping;
Dictionary<ISymbolNode, ISymbolNode> symbolRemapping = null;
var methodHash = new HashSet<MethodInternKey>(_previousHashCount, new MethodInternComparer(factory, previousSymbolRemapping, _genericsOnly));

do
foreach (IMethodBodyNode body in factory.MetadataManager.GetCompiledMethodBodies())
{
previousMethodHash = methodHash;
previousSymbolRemapping = symbolRemapping;
methodHash = new HashSet<MethodInternKey>(previousMethodHash?.Count ?? 0, new MethodInternComparer(factory, previousSymbolRemapping, _genericsOnly));
symbolRemapping = new Dictionary<ISymbolNode, ISymbolNode>((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
Expand Down Expand Up @@ -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));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -503,7 +503,8 @@
<Compile Include="Compiler\DependencyAnalysis\ImportedNodeProvider.cs" />
<Compile Include="Compiler\ExpectedIsaFeaturesRootProvider.cs" />
<Compile Include="Compiler\ExternSymbolMappedField.cs" />
<Compile Include="Compiler\ObjectDataInterner.cs" />
<Compile Include="Compiler\MethodBodyDeduplicator.cs" />
<Compile Include="..\..\Common\Compiler\ObjectDataInterner.cs" Link="Compiler\ObjectDataInterner.cs" />
<Compile Include="Compiler\ReachabilityInstrumentationFilter.cs" />
<Compile Include="Compiler\ReachabilityInstrumentationProvider.cs" />
<Compile Include="Compiler\SourceLinkWriter.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -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<IEnumerable<CopiedMethodILNode>> _nodesProvider;
private Dictionary<ISymbolNode, ISymbolNode> _cachedMapping;

public CopiedMethodILDeduplicator(Func<IEnumerable<CopiedMethodILNode>> nodesProvider)
{
_nodesProvider = nodesProvider;
}

public void DeduplicatePass(NodeFactory factory, Dictionary<ISymbolNode, ISymbolNode> previousSymbolRemapping, Dictionary<ISymbolNode, ISymbolNode> symbolRemapping)
{
if (_cachedMapping is null)
{
_cachedMapping = new Dictionary<ISymbolNode, ISymbolNode>();

var sortedNodes = new List<CopiedMethodILNode>(_nodesProvider());
sortedNodes.Sort(CompilerComparer.Instance);

var hashSet = new HashSet<InternKey>(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<ISymbolNode, ISymbolNode> entry in _cachedMapping)
{
symbolRemapping.TryAdd(entry.Key, entry.Value);
Comment thread
jkoritzinsky marked this conversation as resolved.
}
}

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();
Comment thread
jkoritzinsky marked this conversation as resolved.
}
}

private sealed class InternComparer : IEqualityComparer<InternKey>
{
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);
}
}
}
}
Loading
Loading