Skip to content

Commit e17146d

Browse files
Add mechanism to allow CoreLib trimming to leverage whole program analysis (#118769)
A couple times in the past I needed a way to say "if X is not part of the program, eliminate the entire basic block". We can do this for allocated types (this is how branches under `is` checks elimination works), but we can't do this for more general "characteristics". This introduces a mechanism where AOT compiler and CoreLib (or System.Private.* universe in general) can define whole program tags such as "whole program has X in it" and CoreLib can condition code on the presence of this tag. This is easier shown than described, so I extracted the first use of this into a separate commit. In this commit, we eliminate code that tries looking for `StackTraceHiddenAttribute` if we know the whole program has no `StackTraceHiddenAttribute` in it. With this code eliminated, #118640 can then eliminate all custom attributes on methods, which in turn plays into #118718 and we can eliminate enum boxing even when StackTraceSupport is not set to false (right now #118718 really needs the StackTraceSupport=false to get rid of boxed enums; we get more boxed enums from method attributes). We have a new node that represents the characteristic. The node can be dropped into the graph wherever needed. ILScanner then uses this to condition parts of the method body on this characteristic node. We need similar logic in the substitution IL provider because we need to guarantee that RyuJIT is not going to see basic blocks we didn't scan. So we treat it as a substitution during codegen phase too.
1 parent 6f0ce34 commit e17146d

File tree

13 files changed

+187
-14
lines changed

13 files changed

+187
-14
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace System.Runtime.CompilerServices
5+
{
6+
// When applied to an intrinsic method, the method will become a characteristic check.
7+
[AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false)]
8+
internal class AnalysisCharacteristicAttribute : Attribute
9+
{
10+
}
11+
}

src/coreclr/nativeaot/System.Private.StackTraceMetadata/src/Internal/StackTraceMetadata/StackTraceMetadata.cs

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Collections.Generic;
66
using System.Diagnostics;
77
using System.Reflection.Runtime.General;
8+
using System.Runtime.CompilerServices;
89

910
using Internal.Metadata.NativeFormat;
1011
using Internal.NativeFormat;
@@ -41,6 +42,10 @@ internal static void Initialize()
4142
RuntimeAugments.InitializeStackTraceMetadataSupport(new StackTraceMetadataCallbacksImpl());
4243
}
4344

45+
[Intrinsic]
46+
[AnalysisCharacteristic]
47+
internal static extern bool StackTraceHiddenMetadataPresent();
48+
4449
/// <summary>
4550
/// Locate the containing module for a method and try to resolve its name based on start address.
4651
/// </summary>
@@ -75,21 +80,24 @@ public static unsafe string GetMethodNameFromStartAddressIfAvailable(IntPtr meth
7580
out TypeDefinitionHandle typeHandle,
7681
out MethodHandle methodHandle))
7782
{
78-
foreach (CustomAttributeHandle cah in reader.GetTypeDefinition(typeHandle).CustomAttributes)
83+
if (StackTraceHiddenMetadataPresent())
7984
{
80-
if (cah.IsCustomAttributeOfType(reader, ["System", "Diagnostics"], "StackTraceHiddenAttribute"))
85+
foreach (CustomAttributeHandle cah in reader.GetTypeDefinition(typeHandle).CustomAttributes)
8186
{
82-
isStackTraceHidden = true;
83-
break;
87+
if (cah.IsCustomAttributeOfType(reader, ["System", "Diagnostics"], "StackTraceHiddenAttribute"))
88+
{
89+
isStackTraceHidden = true;
90+
break;
91+
}
8492
}
85-
}
8693

87-
foreach (CustomAttributeHandle cah in reader.GetMethod(methodHandle).CustomAttributes)
88-
{
89-
if (cah.IsCustomAttributeOfType(reader, ["System", "Diagnostics"], "StackTraceHiddenAttribute"))
94+
foreach (CustomAttributeHandle cah in reader.GetMethod(methodHandle).CustomAttributes)
9095
{
91-
isStackTraceHidden = true;
92-
break;
96+
if (cah.IsCustomAttributeOfType(reader, ["System", "Diagnostics"], "StackTraceHiddenAttribute"))
97+
{
98+
isStackTraceHidden = true;
99+
break;
100+
}
93101
}
94102
}
95103

src/coreclr/nativeaot/System.Private.StackTraceMetadata/src/System.Private.StackTraceMetadata.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,7 @@
2323
<Compile Include="$(CompilerCommonPath)\Internal\Runtime\StackTraceData.cs">
2424
<Link>Internal\Runtime\StackTraceData.cs</Link>
2525
</Compile>
26+
<Compile Include="..\..\System.Private.CoreLib\src\System\Runtime\CompilerServices\AnalysisCharacteristicAttribute.cs" />
27+
<Compile Include="..\..\Runtime.Base\src\System\Runtime\CompilerServices\IntrinsicAttribute.cs" />
2628
</ItemGroup>
2729
</Project>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Collections.Generic;
5+
6+
using ILCompiler.DependencyAnalysisFramework;
7+
8+
namespace ILCompiler.DependencyAnalysis
9+
{
10+
public class AnalysisCharacteristicNode : DependencyNodeCore<NodeFactory>
11+
{
12+
public AnalysisCharacteristicNode(string characteristic)
13+
=> Characteristic = characteristic;
14+
15+
public string Characteristic { get; }
16+
17+
public override bool InterestingForDynamicDependencyAnalysis => false;
18+
public override bool HasDynamicDependencies => false;
19+
public override bool HasConditionalStaticDependencies => false;
20+
public override bool StaticDependenciesAreComputed => true;
21+
public override IEnumerable<CombinedDependencyListEntry> GetConditionalStaticDependencies(NodeFactory context) => null;
22+
public override IEnumerable<DependencyListEntry> GetStaticDependencies(NodeFactory context) => null;
23+
public override IEnumerable<CombinedDependencyListEntry> SearchDynamicDependencies(List<DependencyNodeCore<NodeFactory>> markedNodes, int firstNode, NodeFactory context) => null;
24+
protected override string GetName(NodeFactory context) => $"Analysis characteristic: {Characteristic}";
25+
}
26+
}

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/MethodMetadataNode.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@ public MethodMetadataNode(MethodDesc method, bool isMinimal)
4040
public override IEnumerable<DependencyListEntry> GetStaticDependencies(NodeFactory factory)
4141
{
4242
DependencyList dependencies = new DependencyList();
43-
dependencies.Add(factory.TypeMetadata((MetadataType)_method.OwningType), "Owning type metadata");
43+
44+
var owningType = (MetadataType)_method.OwningType;
45+
dependencies.Add(factory.TypeMetadata(owningType), "Owning type metadata");
4446

4547
if (!_isMinimal)
4648
{
@@ -77,6 +79,12 @@ public override IEnumerable<DependencyListEntry> GetStaticDependencies(NodeFacto
7779
{
7880
GenericArgumentDataFlow.ProcessGenericArgumentDataFlow(ref dependencies, factory, new MessageOrigin(_method), parameterType, _method);
7981
}
82+
83+
if (_method.HasCustomAttribute("System.Diagnostics", "StackTraceHiddenAttribute")
84+
|| owningType.HasCustomAttribute("System.Diagnostics", "StackTraceHiddenAttribute"))
85+
{
86+
dependencies.Add(factory.AnalysisCharacteristic("StackTraceHiddenMetadataPresent"), "Method is StackTraceHidden");
87+
}
8088
}
8189

8290
return dependencies;

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -604,6 +604,11 @@ private void CreateNodeCaches()
604604
return new ProxyTypeMapRequestNode(type);
605605
});
606606

607+
_analysisCharacteristics = new NodeCache<string, AnalysisCharacteristicNode>(c =>
608+
{
609+
return new AnalysisCharacteristicNode(c);
610+
});
611+
607612
NativeLayout = new NativeLayoutHelper(this);
608613
}
609614

@@ -1526,6 +1531,12 @@ public ProxyTypeMapRequestNode ProxyTypeMapRequest(TypeDesc type)
15261531
return _proxyTypeMapRequests.GetOrAdd(type);
15271532
}
15281533

1534+
private NodeCache<string, AnalysisCharacteristicNode> _analysisCharacteristics;
1535+
public AnalysisCharacteristicNode AnalysisCharacteristic(string ch)
1536+
{
1537+
return _analysisCharacteristics.GetOrAdd(ch);
1538+
}
1539+
15291540
/// <summary>
15301541
/// Returns alternative symbol name that object writer should produce for given symbols
15311542
/// in addition to the regular one.

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScanner.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,15 @@ public TypeMapManager GetTypeMapManager()
310310
return new ScannedTypeMapManager(_factory);
311311
}
312312

313+
public IEnumerable<string> GetAnalysisCharacteristics()
314+
{
315+
foreach (DependencyNodeCore<NodeFactory> n in MarkedNodes)
316+
{
317+
if (n is AnalysisCharacteristicNode acn)
318+
yield return acn.Characteristic;
319+
}
320+
}
321+
313322
private sealed class ScannedVTableProvider : VTableSliceProvider
314323
{
315324
private readonly Dictionary<TypeDesc, MethodDesc[]> _vtableSlices = new Dictionary<TypeDesc, MethodDesc[]>();

src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/SubstitutedILProvider.cs

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using ILCompiler.DependencyAnalysis;
1111

1212
using Internal.IL;
13+
using Internal.IL.Stubs;
1314
using Internal.TypeSystem;
1415
using Internal.TypeSystem.Ecma;
1516

@@ -24,13 +25,15 @@ public class SubstitutedILProvider : ILProvider
2425
private readonly SubstitutionProvider _substitutionProvider;
2526
private readonly DevirtualizationManager _devirtualizationManager;
2627
private readonly MetadataManager _metadataManager;
28+
private readonly HashSet<string> _characteristics;
2729

28-
public SubstitutedILProvider(ILProvider nestedILProvider, SubstitutionProvider substitutionProvider, DevirtualizationManager devirtualizationManager, MetadataManager metadataManager = null)
30+
public SubstitutedILProvider(ILProvider nestedILProvider, SubstitutionProvider substitutionProvider, DevirtualizationManager devirtualizationManager, MetadataManager metadataManager = null, IEnumerable<string> characteristics = null)
2931
{
3032
_nestedILProvider = nestedILProvider;
3133
_substitutionProvider = substitutionProvider;
3234
_devirtualizationManager = devirtualizationManager;
3335
_metadataManager = metadataManager;
36+
_characteristics = characteristics != null ? new HashSet<string>(characteristics) : null;
3437
}
3538

3639
public override MethodIL GetMethodIL(MethodDesc method)
@@ -41,6 +44,13 @@ public override MethodIL GetMethodIL(MethodDesc method)
4144
return substitution.EmitIL(method);
4245
}
4346

47+
if (TryGetCharacteristicValue(method, out bool characteristicEnabled))
48+
{
49+
return new ILStubMethodIL(method,
50+
[characteristicEnabled ? (byte)ILOpCode.Ldc_i4_1 : (byte)ILOpCode.Ldc_i4_0, (byte)ILOpCode.Ret],
51+
[], []);
52+
}
53+
4454
// BEGIN TEMPORARY WORKAROUND
4555
//
4656
// The following lines should just be:
@@ -819,6 +829,11 @@ private bool TryGetConstantArgument(MethodIL methodIL, byte[] body, OpcodeFlags[
819829
{
820830
return true;
821831
}
832+
else if (TryGetCharacteristicValue(method, out bool characteristic))
833+
{
834+
constant = characteristic ? 1 : 0;
835+
return true;
836+
}
822837
else
823838
{
824839
constant = 0;
@@ -1127,6 +1142,19 @@ private static bool ReadGetTypeFromHandle(ref ILReader reader, MethodIL methodIL
11271142
return true;
11281143
}
11291144

1145+
private bool TryGetCharacteristicValue(MethodDesc maybeCharacteristicMethod, out bool value)
1146+
{
1147+
if (maybeCharacteristicMethod.IsIntrinsic
1148+
&& maybeCharacteristicMethod.HasCustomAttribute("System.Runtime.CompilerServices", "AnalysisCharacteristicAttribute"))
1149+
{
1150+
value = _characteristics == null || _characteristics.Contains(maybeCharacteristicMethod.Name);
1151+
return true;
1152+
}
1153+
1154+
value = false;
1155+
return false;
1156+
}
1157+
11301158
private sealed class SubstitutedMethodIL : MethodIL
11311159
{
11321160
private readonly byte[] _body;

src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ public enum ImportState : byte
6161

6262
private bool _isReadOnly;
6363
private TypeDesc _constrained;
64+
private int _currentInstructionOffset;
65+
private int _previousInstructionOffset;
6466

6567
private DependencyList _dependencies;
6668
private BasicBlock _lateBasicBlocks;
@@ -258,6 +260,13 @@ private void StartImportingBasicBlock(BasicBlock basicBlock)
258260

259261
_typeEqualityPatternAnalyzer = default;
260262
_isInstCheckPatternAnalyzer = default;
263+
_currentInstructionOffset = 0;
264+
_previousInstructionOffset = -1;
265+
}
266+
267+
private void StartImportingInstruction()
268+
{
269+
_currentInstructionOffset = _currentOffset;
261270
}
262271

263272
partial void StartImportingInstruction(ILOpcode opcode)
@@ -271,6 +280,8 @@ private void EndImportingInstruction()
271280
// The instruction should have consumed any prefixes.
272281
_constrained = null;
273282
_isReadOnly = false;
283+
284+
_previousInstructionOffset = _currentInstructionOffset;
274285
}
275286

276287
private void ImportCasting(ILOpcode opcode, int token)
@@ -853,6 +864,17 @@ private void ImportBranch(ILOpcode opcode, BasicBlock target, BasicBlock fallthr
853864
}
854865
}
855866

867+
if (opcode == ILOpcode.brfalse && _previousInstructionOffset >= 0)
868+
{
869+
var reader = new ILReader(_ilBytes, _previousInstructionOffset);
870+
if (reader.ReadILOpcode() == ILOpcode.call
871+
&& _methodIL.GetObject(reader.ReadILToken()) is MethodDesc { IsIntrinsic: true } intrinsicMethod
872+
&& intrinsicMethod.HasCustomAttribute("System.Runtime.CompilerServices", "AnalysisCharacteristicAttribute"))
873+
{
874+
condition = _factory.AnalysisCharacteristic(intrinsicMethod.Name);
875+
}
876+
}
877+
856878
ImportFallthrough(target);
857879

858880
if (fallthrough != null)
@@ -1531,7 +1553,6 @@ private DefType GetWellKnownType(WellKnownType wellKnownType)
15311553
return _compilation.TypeSystemContext.GetWellKnownType(wellKnownType);
15321554
}
15331555

1534-
private static void StartImportingInstruction() { }
15351556
private static void ImportNop() { }
15361557
private static void ImportBreak() { }
15371558
private static void ImportLoadVar(int index, bool argument) { }

src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,7 @@
347347
<Compile Include="Compiler\BodySubstitution.cs" />
348348
<Compile Include="Compiler\BodySubstitutionParser.cs" />
349349
<Compile Include="Compiler\DependencyAnalysis\AddressTakenMethodNode.cs" />
350+
<Compile Include="Compiler\DependencyAnalysis\AnalysisCharacteristicNode.cs" />
350351
<Compile Include="Compiler\DependencyAnalysis\AnalyzedExternalTypeMapNode.cs" />
351352
<Compile Include="Compiler\DependencyAnalysis\AnalyzedProxyTypeMapNode.cs" />
352353
<Compile Include="Compiler\DependencyAnalysis\ByRefTypeMapNode.cs" />

0 commit comments

Comments
 (0)