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 + + + + +