diff --git a/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/AnalysisCharacteristicAttribute.cs b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/AnalysisCharacteristicAttribute.cs
new file mode 100644
index 00000000000000..63dbbfe07e173e
--- /dev/null
+++ b/src/coreclr/nativeaot/System.Private.CoreLib/src/System/Runtime/CompilerServices/AnalysisCharacteristicAttribute.cs
@@ -0,0 +1,11 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace System.Runtime.CompilerServices
+{
+ // When applied to an intrinsic method, the method will become a characteristic check.
+ [AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false)]
+ internal class AnalysisCharacteristicAttribute : Attribute
+ {
+ }
+}
diff --git a/src/coreclr/nativeaot/System.Private.StackTraceMetadata/src/Internal/StackTraceMetadata/StackTraceMetadata.cs b/src/coreclr/nativeaot/System.Private.StackTraceMetadata/src/Internal/StackTraceMetadata/StackTraceMetadata.cs
index 008119ebd10cad..352568239c0d4e 100644
--- a/src/coreclr/nativeaot/System.Private.StackTraceMetadata/src/Internal/StackTraceMetadata/StackTraceMetadata.cs
+++ b/src/coreclr/nativeaot/System.Private.StackTraceMetadata/src/Internal/StackTraceMetadata/StackTraceMetadata.cs
@@ -5,6 +5,7 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection.Runtime.General;
+using System.Runtime.CompilerServices;
using Internal.Metadata.NativeFormat;
using Internal.NativeFormat;
@@ -41,6 +42,10 @@ internal static void Initialize()
RuntimeAugments.InitializeStackTraceMetadataSupport(new StackTraceMetadataCallbacksImpl());
}
+ [Intrinsic]
+ [AnalysisCharacteristic]
+ internal static extern bool StackTraceHiddenMetadataPresent();
+
///
/// Locate the containing module for a method and try to resolve its name based on start address.
///
@@ -75,21 +80,24 @@ public static unsafe string GetMethodNameFromStartAddressIfAvailable(IntPtr meth
out TypeDefinitionHandle typeHandle,
out MethodHandle methodHandle))
{
- foreach (CustomAttributeHandle cah in reader.GetTypeDefinition(typeHandle).CustomAttributes)
+ if (StackTraceHiddenMetadataPresent())
{
- if (cah.IsCustomAttributeOfType(reader, ["System", "Diagnostics"], "StackTraceHiddenAttribute"))
+ foreach (CustomAttributeHandle cah in reader.GetTypeDefinition(typeHandle).CustomAttributes)
{
- isStackTraceHidden = true;
- break;
+ if (cah.IsCustomAttributeOfType(reader, ["System", "Diagnostics"], "StackTraceHiddenAttribute"))
+ {
+ isStackTraceHidden = true;
+ break;
+ }
}
- }
- foreach (CustomAttributeHandle cah in reader.GetMethod(methodHandle).CustomAttributes)
- {
- if (cah.IsCustomAttributeOfType(reader, ["System", "Diagnostics"], "StackTraceHiddenAttribute"))
+ foreach (CustomAttributeHandle cah in reader.GetMethod(methodHandle).CustomAttributes)
{
- isStackTraceHidden = true;
- break;
+ if (cah.IsCustomAttributeOfType(reader, ["System", "Diagnostics"], "StackTraceHiddenAttribute"))
+ {
+ isStackTraceHidden = true;
+ break;
+ }
}
}
diff --git a/src/coreclr/nativeaot/System.Private.StackTraceMetadata/src/System.Private.StackTraceMetadata.csproj b/src/coreclr/nativeaot/System.Private.StackTraceMetadata/src/System.Private.StackTraceMetadata.csproj
index cd40e0ec8ba54d..1fe8f87c09d76e 100644
--- a/src/coreclr/nativeaot/System.Private.StackTraceMetadata/src/System.Private.StackTraceMetadata.csproj
+++ b/src/coreclr/nativeaot/System.Private.StackTraceMetadata/src/System.Private.StackTraceMetadata.csproj
@@ -23,5 +23,7 @@
Internal\Runtime\StackTraceData.cs
+
+
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/AnalysisCharacteristicNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/AnalysisCharacteristicNode.cs
new file mode 100644
index 00000000000000..1d667fd833d72c
--- /dev/null
+++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/AnalysisCharacteristicNode.cs
@@ -0,0 +1,26 @@
+// 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.DependencyAnalysisFramework;
+
+namespace ILCompiler.DependencyAnalysis
+{
+ public class AnalysisCharacteristicNode : DependencyNodeCore
+ {
+ public AnalysisCharacteristicNode(string characteristic)
+ => Characteristic = characteristic;
+
+ public string Characteristic { get; }
+
+ public override bool InterestingForDynamicDependencyAnalysis => false;
+ public override bool HasDynamicDependencies => false;
+ public override bool HasConditionalStaticDependencies => false;
+ public override bool StaticDependenciesAreComputed => true;
+ public override IEnumerable GetConditionalStaticDependencies(NodeFactory context) => null;
+ public override IEnumerable GetStaticDependencies(NodeFactory context) => null;
+ public override IEnumerable SearchDynamicDependencies(List> markedNodes, int firstNode, NodeFactory context) => null;
+ protected override string GetName(NodeFactory context) => $"Analysis characteristic: {Characteristic}";
+ }
+}
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/MethodMetadataNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/MethodMetadataNode.cs
index 8cccdf4b93ea93..f145aa9504eaaa 100644
--- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/MethodMetadataNode.cs
+++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/MethodMetadataNode.cs
@@ -40,7 +40,9 @@ public MethodMetadataNode(MethodDesc method, bool isMinimal)
public override IEnumerable GetStaticDependencies(NodeFactory factory)
{
DependencyList dependencies = new DependencyList();
- dependencies.Add(factory.TypeMetadata((MetadataType)_method.OwningType), "Owning type metadata");
+
+ var owningType = (MetadataType)_method.OwningType;
+ dependencies.Add(factory.TypeMetadata(owningType), "Owning type metadata");
if (!_isMinimal)
{
@@ -77,6 +79,12 @@ public override IEnumerable GetStaticDependencies(NodeFacto
{
GenericArgumentDataFlow.ProcessGenericArgumentDataFlow(ref dependencies, factory, new MessageOrigin(_method), parameterType, _method);
}
+
+ if (_method.HasCustomAttribute("System.Diagnostics", "StackTraceHiddenAttribute")
+ || owningType.HasCustomAttribute("System.Diagnostics", "StackTraceHiddenAttribute"))
+ {
+ dependencies.Add(factory.AnalysisCharacteristic("StackTraceHiddenMetadataPresent"), "Method is StackTraceHidden");
+ }
}
return dependencies;
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 ef6a4fed1c1949..8afd930fb88383 100644
--- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs
+++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs
@@ -604,6 +604,11 @@ private void CreateNodeCaches()
return new ProxyTypeMapRequestNode(type);
});
+ _analysisCharacteristics = new NodeCache(c =>
+ {
+ return new AnalysisCharacteristicNode(c);
+ });
+
NativeLayout = new NativeLayoutHelper(this);
}
@@ -1526,6 +1531,12 @@ public ProxyTypeMapRequestNode ProxyTypeMapRequest(TypeDesc type)
return _proxyTypeMapRequests.GetOrAdd(type);
}
+ private NodeCache _analysisCharacteristics;
+ public AnalysisCharacteristicNode AnalysisCharacteristic(string ch)
+ {
+ return _analysisCharacteristics.GetOrAdd(ch);
+ }
+
///
/// Returns alternative symbol name that object writer should produce for given symbols
/// in addition to the regular one.
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScanner.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScanner.cs
index d9ad0888ee2f4b..18fdd2f65550eb 100644
--- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScanner.cs
+++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/ILScanner.cs
@@ -310,6 +310,15 @@ public TypeMapManager GetTypeMapManager()
return new ScannedTypeMapManager(_factory);
}
+ public IEnumerable GetAnalysisCharacteristics()
+ {
+ foreach (DependencyNodeCore n in MarkedNodes)
+ {
+ if (n is AnalysisCharacteristicNode acn)
+ yield return acn.Characteristic;
+ }
+ }
+
private sealed class ScannedVTableProvider : VTableSliceProvider
{
private readonly Dictionary _vtableSlices = new Dictionary();
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/SubstitutedILProvider.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/SubstitutedILProvider.cs
index 8476dd391808bd..d455183bb0b5ae 100644
--- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/SubstitutedILProvider.cs
+++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/SubstitutedILProvider.cs
@@ -10,6 +10,7 @@
using ILCompiler.DependencyAnalysis;
using Internal.IL;
+using Internal.IL.Stubs;
using Internal.TypeSystem;
using Internal.TypeSystem.Ecma;
@@ -24,13 +25,15 @@ public class SubstitutedILProvider : ILProvider
private readonly SubstitutionProvider _substitutionProvider;
private readonly DevirtualizationManager _devirtualizationManager;
private readonly MetadataManager _metadataManager;
+ private readonly HashSet _characteristics;
- public SubstitutedILProvider(ILProvider nestedILProvider, SubstitutionProvider substitutionProvider, DevirtualizationManager devirtualizationManager, MetadataManager metadataManager = null)
+ public SubstitutedILProvider(ILProvider nestedILProvider, SubstitutionProvider substitutionProvider, DevirtualizationManager devirtualizationManager, MetadataManager metadataManager = null, IEnumerable characteristics = null)
{
_nestedILProvider = nestedILProvider;
_substitutionProvider = substitutionProvider;
_devirtualizationManager = devirtualizationManager;
_metadataManager = metadataManager;
+ _characteristics = characteristics != null ? new HashSet(characteristics) : null;
}
public override MethodIL GetMethodIL(MethodDesc method)
@@ -41,6 +44,13 @@ public override MethodIL GetMethodIL(MethodDesc method)
return substitution.EmitIL(method);
}
+ if (TryGetCharacteristicValue(method, out bool characteristicEnabled))
+ {
+ return new ILStubMethodIL(method,
+ [characteristicEnabled ? (byte)ILOpCode.Ldc_i4_1 : (byte)ILOpCode.Ldc_i4_0, (byte)ILOpCode.Ret],
+ [], []);
+ }
+
// BEGIN TEMPORARY WORKAROUND
//
// The following lines should just be:
@@ -819,6 +829,11 @@ private bool TryGetConstantArgument(MethodIL methodIL, byte[] body, OpcodeFlags[
{
return true;
}
+ else if (TryGetCharacteristicValue(method, out bool characteristic))
+ {
+ constant = characteristic ? 1 : 0;
+ return true;
+ }
else
{
constant = 0;
@@ -1127,6 +1142,19 @@ private static bool ReadGetTypeFromHandle(ref ILReader reader, MethodIL methodIL
return true;
}
+ private bool TryGetCharacteristicValue(MethodDesc maybeCharacteristicMethod, out bool value)
+ {
+ if (maybeCharacteristicMethod.IsIntrinsic
+ && maybeCharacteristicMethod.HasCustomAttribute("System.Runtime.CompilerServices", "AnalysisCharacteristicAttribute"))
+ {
+ value = _characteristics == null || _characteristics.Contains(maybeCharacteristicMethod.Name);
+ return true;
+ }
+
+ value = false;
+ return false;
+ }
+
private sealed class SubstitutedMethodIL : MethodIL
{
private readonly byte[] _body;
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs
index 92e8126b482dd1..799a17899a6779 100644
--- a/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs
+++ b/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs
@@ -61,6 +61,8 @@ public enum ImportState : byte
private bool _isReadOnly;
private TypeDesc _constrained;
+ private int _currentInstructionOffset;
+ private int _previousInstructionOffset;
private DependencyList _dependencies;
private BasicBlock _lateBasicBlocks;
@@ -258,6 +260,13 @@ private void StartImportingBasicBlock(BasicBlock basicBlock)
_typeEqualityPatternAnalyzer = default;
_isInstCheckPatternAnalyzer = default;
+ _currentInstructionOffset = 0;
+ _previousInstructionOffset = -1;
+ }
+
+ private void StartImportingInstruction()
+ {
+ _currentInstructionOffset = _currentOffset;
}
partial void StartImportingInstruction(ILOpcode opcode)
@@ -271,6 +280,8 @@ private void EndImportingInstruction()
// The instruction should have consumed any prefixes.
_constrained = null;
_isReadOnly = false;
+
+ _previousInstructionOffset = _currentInstructionOffset;
}
private void ImportCasting(ILOpcode opcode, int token)
@@ -853,6 +864,17 @@ private void ImportBranch(ILOpcode opcode, BasicBlock target, BasicBlock fallthr
}
}
+ if (opcode == ILOpcode.brfalse && _previousInstructionOffset >= 0)
+ {
+ var reader = new ILReader(_ilBytes, _previousInstructionOffset);
+ if (reader.ReadILOpcode() == ILOpcode.call
+ && _methodIL.GetObject(reader.ReadILToken()) is MethodDesc { IsIntrinsic: true } intrinsicMethod
+ && intrinsicMethod.HasCustomAttribute("System.Runtime.CompilerServices", "AnalysisCharacteristicAttribute"))
+ {
+ condition = _factory.AnalysisCharacteristic(intrinsicMethod.Name);
+ }
+ }
+
ImportFallthrough(target);
if (fallthrough != null)
@@ -1531,7 +1553,6 @@ private DefType GetWellKnownType(WellKnownType wellKnownType)
return _compilation.TypeSystemContext.GetWellKnownType(wellKnownType);
}
- private static void StartImportingInstruction() { }
private static void ImportNop() { }
private static void ImportBreak() { }
private static void ImportLoadVar(int index, bool argument) { }
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj
index 3ac5cfb27289d4..862404c49d4eb6 100644
--- a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj
+++ b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj
@@ -347,6 +347,7 @@
+
diff --git a/src/coreclr/tools/aot/ILCompiler/Program.cs b/src/coreclr/tools/aot/ILCompiler/Program.cs
index 6a2b680f279ab6..04795875d6d071 100644
--- a/src/coreclr/tools/aot/ILCompiler/Program.cs
+++ b/src/coreclr/tools/aot/ILCompiler/Program.cs
@@ -534,7 +534,7 @@ void RunScanner()
substitutionProvider = new SubstitutionProvider(logger, featureSwitches, substitutions);
- ilProvider = new SubstitutedILProvider(unsubstitutedILProvider, substitutionProvider, devirtualizationManager, metadataManager);
+ ilProvider = new SubstitutedILProvider(unsubstitutedILProvider, substitutionProvider, devirtualizationManager, metadataManager, scanResults.GetAnalysisCharacteristics());
// Use a more precise IL provider that uses whole program analysis for dead branch elimination
builder.UseILProvider(ilProvider);
diff --git a/src/tests/nativeaot/SmokeTests/AttributeTrimming/AttributeTrimming.cs b/src/tests/nativeaot/SmokeTests/AttributeTrimming/AttributeTrimming.cs
new file mode 100644
index 00000000000000..863b2708f1b6bc
--- /dev/null
+++ b/src/tests/nativeaot/SmokeTests/AttributeTrimming/AttributeTrimming.cs
@@ -0,0 +1,36 @@
+// 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.Diagnostics.CodeAnalysis;
+using System.Reflection;
+
+[Type]
+class Program
+{
+ [Method]
+ static int Main()
+ {
+ // Sanity check: we don't currently expect attributes on types to be optimized away
+ if (GetTypeSecretly(nameof(TypeAttribute)) == null)
+ throw new Exception("Type");
+
+ // Main should be reflection visible
+ if (MethodBase.GetCurrentMethod().Name != nameof(Main))
+ throw new Exception("Name");
+
+#if !DEBUG
+ // But we should have optimized out the attributes on it
+ if (GetTypeSecretly(nameof(MethodAttribute)) != null)
+ throw new Exception("Method");
+#endif
+
+ return 100;
+ }
+
+ [UnconditionalSuppressMessage("Trimming", "IL2057", Justification = "That's the point")]
+ static Type GetTypeSecretly(string name) => Type.GetType(name);
+}
+
+class MethodAttribute : Attribute;
+class TypeAttribute : Attribute;
diff --git a/src/tests/nativeaot/SmokeTests/AttributeTrimming/AttributeTrimming.csproj b/src/tests/nativeaot/SmokeTests/AttributeTrimming/AttributeTrimming.csproj
new file mode 100644
index 00000000000000..e4650db0af37db
--- /dev/null
+++ b/src/tests/nativeaot/SmokeTests/AttributeTrimming/AttributeTrimming.csproj
@@ -0,0 +1,12 @@
+
+
+ Exe
+ 0
+ true
+ true
+ false
+
+
+
+
+