diff --git a/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets b/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets index 348c20b9fcbf33..e57a63950b4009 100644 --- a/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets +++ b/src/coreclr/nativeaot/BuildIntegration/Microsoft.NETCore.Native.targets @@ -237,6 +237,12 @@ The .NET Foundation licenses this file to you under the MIT license. <_IlcNoSingleWarnAssemblies Include="@(_IlcNoSingleWarnAssembliesRaw)" Condition="!$([System.IO.File]::Exists('%(Identity)'))" /> + + <_IlcMethodBodyFoldingValue Condition="$(IlcFoldIdenticalMethodBodies) == 'true' or $(StackTraceSupport) == 'false'">all + <_IlcMethodBodyFoldingValue Condition="$(_IlcMethodBodyFoldingValue) == '' and $(IlcFoldIdenticalMethodBodies) != 'false' and $(IlcMultiModule) != 'true'">generic + <_IlcMethodBodyFoldingValue Condition="$(_IlcMethodBodyFoldingValue) == '' or $(Optimize) != 'true'">none + + @@ -275,7 +281,7 @@ The .NET Foundation licenses this file to you under the MIT license. - + diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/CompilationBuilder.Aot.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/CompilationBuilder.Aot.cs index 5eb049c74e442b..03818be44eb2a3 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/CompilationBuilder.Aot.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/CompilationBuilder.Aot.cs @@ -21,7 +21,7 @@ public partial class CompilationBuilder protected MethodImportationErrorProvider _methodImportationErrorProvider = new MethodImportationErrorProvider(); protected ReadOnlyFieldPolicy _readOnlyFieldPolicy = new ReadOnlyFieldPolicy(); protected IInliningPolicy _inliningPolicy; - protected bool _methodBodyFolding; + protected MethodBodyFoldingMode _methodBodyFolding; protected InstructionSetSupport _instructionSetSupport; protected SecurityMitigationOptions _mitigationOptions; protected bool _dehydrate; @@ -94,9 +94,9 @@ public CompilationBuilder UseDehydration(bool dehydrate) return this; } - public CompilationBuilder UseMethodBodyFolding(bool enable) + public CompilationBuilder UseMethodBodyFolding(MethodBodyFoldingMode mode) { - _methodBodyFolding = enable; + _methodBodyFolding = mode; return this; } @@ -154,4 +154,11 @@ public enum SecurityMitigationOptions { ControlFlowGuardAnnotations = 0x0001, } + + public enum MethodBodyFoldingMode + { + None, + Generic, + All, + } } 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 a6add520521126..34335295ff9fdf 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs @@ -1059,7 +1059,7 @@ public IMethodNode FatFunctionPointer(MethodDesc method, bool isUnboxingStub = f public IMethodNode FatAddressTakenFunctionPointer(MethodDesc method, bool isUnboxingStub = false) { - if (ObjectInterner.IsNull) + if (!ObjectInterner.CanFold(method)) return FatFunctionPointer(method, isUnboxingStub); return _fatAddressTakenFunctionPointers.GetOrAdd(new MethodKey(method, isUnboxingStub)); @@ -1125,7 +1125,7 @@ internal TypeGVMEntriesNode TypeGVMEntries(TypeDesc type) private NodeCache _addressTakenMethods; public IMethodNode AddressTakenMethodEntrypoint(MethodDesc method, bool unboxingStub = false) { - if (unboxingStub || ObjectInterner.IsNull) + if (unboxingStub || !ObjectInterner.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/ObjectDataInterner.cs index bbaf09e2c8cdc6..f54d68cf0fc154 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectDataInterner.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ObjectDataInterner.cs @@ -6,17 +6,29 @@ using ILCompiler.DependencyAnalysis; +using Internal.TypeSystem; + using Debug = System.Diagnostics.Debug; namespace ILCompiler { public sealed class ObjectDataInterner { + private readonly bool _genericsOnly; private Dictionary _symbolRemapping; - public static ObjectDataInterner Null { get; } = new ObjectDataInterner() { _symbolRemapping = new() }; + public static ObjectDataInterner Null { get; } = new ObjectDataInterner(genericsOnly: false) { _symbolRemapping = new() }; - public bool IsNull => _symbolRemapping != null && _symbolRemapping.Count == 0; + public ObjectDataInterner(bool genericsOnly) + { + _genericsOnly = genericsOnly; + } + + public bool CanFold(MethodDesc method) + { + return this != Null + && (!_genericsOnly || method.HasInstantiation || method.OwningType.HasInstantiation); + } private void EnsureMap(NodeFactory factory) { @@ -34,11 +46,14 @@ private void EnsureMap(NodeFactory factory) { previousMethodHash = methodHash; previousSymbolRemapping = symbolRemapping; - methodHash = new HashSet(previousMethodHash?.Count ?? 0, new MethodInternComparer(factory, previousSymbolRemapping)); + methodHash = new HashSet(previousMethodHash?.Count ?? 0, new MethodInternComparer(factory, previousSymbolRemapping, _genericsOnly)); symbolRemapping = new Dictionary((int)(1.05 * (previousSymbolRemapping?.Count ?? 0))); 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; @@ -107,9 +122,10 @@ private sealed class MethodInternComparer : IEqualityComparer { private readonly NodeFactory _factory; private readonly Dictionary _interner; + private readonly bool _genericsOnly; - public MethodInternComparer(NodeFactory factory, Dictionary interner) - => (_factory, _interner) = (factory, interner); + public MethodInternComparer(NodeFactory factory, Dictionary interner, bool genericsOnly) + => (_factory, _interner, _genericsOnly) = (factory, interner, genericsOnly); public int GetHashCode(MethodInternKey key) => key.HashCode; @@ -161,6 +177,10 @@ public bool Equals(MethodInternKey a, MethodInternKey b) if (a.HashCode != b.HashCode) return false; + if (_genericsOnly + && a.Method.Method.GetTypicalMethodDefinition() != b.Method.Method.GetTypicalMethodDefinition()) + return false; + ObjectNode.ObjectData o1data = ((ObjectNode)a.Method).GetData(_factory, relocsOnly: false); ObjectNode.ObjectData o2data = ((ObjectNode)b.Method).GetData(_factory, relocsOnly: false); diff --git a/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/RyuJitCompilationBuilder.cs b/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/RyuJitCompilationBuilder.cs index b1291ac837dce8..140d120ebd523f 100644 --- a/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/RyuJitCompilationBuilder.cs +++ b/src/coreclr/tools/aot/ILCompiler.RyuJit/Compiler/RyuJitCompilationBuilder.cs @@ -129,7 +129,12 @@ public override ICompilation ToCompilation() if (_resilient) options |= RyuJitCompilationOptions.UseResilience; - ObjectDataInterner interner = _methodBodyFolding ? new ObjectDataInterner() : ObjectDataInterner.Null; + ObjectDataInterner interner = _methodBodyFolding switch + { + MethodBodyFoldingMode.Generic => new ObjectDataInterner(genericsOnly: true), + MethodBodyFoldingMode.All => new ObjectDataInterner(genericsOnly: false), + _ => ObjectDataInterner.Null, + }; var factory = new RyuJitNodeFactory(_context, _compilationGroup, _metadataManager, _interopStubManager, _nameMangler, _vtableSliceProvider, _dictionaryLayoutProvider, _inlinedThreadStatics, GetPreinitializationManager(), _devirtualizationManager, interner, _typeMapManager); diff --git a/src/coreclr/tools/aot/ILCompiler/ILCompilerRootCommand.cs b/src/coreclr/tools/aot/ILCompiler/ILCompilerRootCommand.cs index 55b670ee7213da..efd422e2a0d788 100644 --- a/src/coreclr/tools/aot/ILCompiler/ILCompilerRootCommand.cs +++ b/src/coreclr/tools/aot/ILCompiler/ILCompilerRootCommand.cs @@ -99,8 +99,8 @@ internal sealed class ILCompilerRootCommand : RootCommand new("--noinlinetls") { Description = "Do not generate inline thread local statics" }; public Option EmitStackTraceData { get; } = new("--stacktracedata") { Description = "Emit data to support generating stack trace strings at runtime" }; - public Option MethodBodyFolding { get; } = - new("--methodbodyfolding") { Description = "Fold identical method bodies" }; + public Option MethodBodyFolding { get; } = + new("--methodbodyfolding") { Description = "Fold identical method bodies (one of: none, generic, all" }; public Option InitAssemblies { get; } = new("--initassembly") { DefaultValueFactory = _ => Array.Empty(), Description = "Assembly(ies) with a library initializer" }; public Option FeatureSwitches { get; } = diff --git a/src/coreclr/tools/aot/ILCompiler/Program.cs b/src/coreclr/tools/aot/ILCompiler/Program.cs index 0cf9f83308e207..3d537bb8bb2a5e 100644 --- a/src/coreclr/tools/aot/ILCompiler/Program.cs +++ b/src/coreclr/tools/aot/ILCompiler/Program.cs @@ -603,10 +603,14 @@ void RunScanner() compilationRoots.Add(metadataManager); compilationRoots.Add(interopStubManager); + MethodBodyFoldingMode foldingMode = string.IsNullOrEmpty(Get(_command.MethodBodyFolding)) + ? MethodBodyFoldingMode.None + : Enum.Parse(Get(_command.MethodBodyFolding), ignoreCase: true); + builder .UseInstructionSetSupport(instructionSetSupport) .UseBackendOptions(Get(_command.CodegenOptions)) - .UseMethodBodyFolding(enable: Get(_command.MethodBodyFolding)) + .UseMethodBodyFolding(foldingMode) .UseParallelism(parallelism) .UseMetadataManager(metadataManager) .UseInteropStubManager(interopStubManager)