From 9e5dd8f1b72d990289fcf8632cec8c1a99ac10fd Mon Sep 17 00:00:00 2001 From: vitek-karas <10670590+vitek-karas@users.noreply.github.com> Date: Tue, 14 Jun 2022 15:15:52 -0700 Subject: [PATCH 01/18] First --- .../Compiler/Dataflow/ArrayValue.cs | 4 +- .../Compiler/Dataflow/AttributeDataFlow.cs | 145 ++++++++ .../Compiler/Dataflow/DiagnosticContext.cs | 2 +- .../Compiler/Dataflow/EcmaExtensions.cs | 24 ++ .../Compiler/Dataflow/FieldReferenceValue.cs | 16 + .../Compiler/Dataflow/FlowAnnotations.cs | 139 +++++--- .../Dataflow/GenericArgumentDataFlow.cs | 59 ++++ .../Compiler/Dataflow/HandleCallAction.cs | 2 +- .../Dataflow/LocalVariableReferenceValue.cs | 17 + .../Compiler/Dataflow/MethodBodyScanner.cs | 322 ++++++++++++++++-- .../Compiler/Dataflow/MethodProxy.cs | 11 +- .../Dataflow/ParameterReferenceValue.cs | 20 ++ .../Compiler/Dataflow/README.md | 9 +- .../Dataflow/ReferenceSource/ArrayValue.cs | 4 +- .../ReferenceSource/AttributeDataFlow.cs | 73 ++++ .../ReferenceSource/DiagnosticContext.cs | 2 +- ...DynamicallyAccessedMembersTypeHierarchy.cs | 7 +- .../ReferenceSource/FieldReferenceValue.cs | 12 + .../ReferenceSource/FlowAnnotations.cs | 75 +++- .../GenericArgumentDataFlow.cs | 44 +++ .../ReferenceSource/HandleCallAction.cs | 9 +- .../LocalVariableReferenceValue.cs | 15 + .../ReferenceSource/MethodBodyScanner.cs | 297 +++++++++++++--- .../Dataflow/ReferenceSource/MethodProxy.cs | 11 +- .../ParameterReferenceValue.cs | 16 + .../ReferenceSource/ReferenceValue.cs | 11 + .../ReferenceSource/ReflectionMarker.cs | 86 ++++- .../ReflectionMethodBodyScanner.cs | 288 +++++++--------- ...RequireDynamicallyAccessedMembersAction.cs | 5 +- .../ReferenceSource/ScannerExtensions.cs | 6 - .../TrimAnalysisAssignmentPattern.cs | 39 +++ .../TrimAnalysisMethodCallPattern.cs | 57 ++++ .../TrimAnalysisPatternStore.cs | 47 +++ .../Compiler/Dataflow/ReferenceValue.cs | 14 + .../Compiler/Dataflow/ReflectionMarker.cs | 58 +++- .../Dataflow/ReflectionMethodBodyScanner.cs | 148 +------- ...RequireDynamicallyAccessedMembersAction.cs | 2 +- .../Compiler/Dataflow/ScannerExtensions.cs | 5 - .../Dataflow/TrimAnalysisAssignmentPattern.cs | 44 +++ .../Dataflow/TrimAnalysisMethodCallPattern.cs | 65 ++++ .../Dataflow/TrimAnalysisPatternStore.cs | 49 +++ .../Compiler/UsageBasedMetadataManager.cs | 4 +- .../ILCompiler.Compiler.csproj | 13 +- .../tools/aot/ILLink.Shared/DiagnosticId.cs | 5 +- .../aot/ILLink.Shared/SharedStrings.resx | 20 +- .../TrimAnalysis/HandleCallAction.cs | 10 +- .../ILLink.Shared/TrimAnalysis/IntrinsicId.cs | 1 + .../ILLink.Shared/TrimAnalysis/Intrinsics.cs | 1 + .../TrimAnalysis/ReferenceKind.cs | 14 + .../TypeSystemProxy/WellKnownType.cs | 4 +- 50 files changed, 1831 insertions(+), 500 deletions(-) create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/AttributeDataFlow.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/FieldReferenceValue.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/GenericArgumentDataFlow.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/LocalVariableReferenceValue.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ParameterReferenceValue.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/AttributeDataFlow.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/FieldReferenceValue.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/GenericArgumentDataFlow.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/LocalVariableReferenceValue.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ParameterReferenceValue.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ReferenceValue.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/TrimAnalysisAssignmentPattern.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/TrimAnalysisMethodCallPattern.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/TrimAnalysisPatternStore.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceValue.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisAssignmentPattern.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisMethodCallPattern.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisPatternStore.cs create mode 100644 src/coreclr/tools/aot/ILLink.Shared/TrimAnalysis/ReferenceKind.cs diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ArrayValue.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ArrayValue.cs index 5c48191c8eca6..23740b13ef31a 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ArrayValue.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ArrayValue.cs @@ -27,9 +27,9 @@ public static MultiValue Create(MultiValue size, TypeDesc elementType) return result; } - public static MultiValue Create(int size, TypeDesc elementType) + public static ArrayValue Create(int size, TypeDesc elementType) { - return new MultiValue(new ArrayValue(new ConstIntValue(size), elementType)); + return new ArrayValue(new ConstIntValue(size), elementType); } /// diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/AttributeDataFlow.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/AttributeDataFlow.cs new file mode 100644 index 0000000000000..ebfdb8999e44f --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/AttributeDataFlow.cs @@ -0,0 +1,145 @@ +// 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.Immutable; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Reflection.Metadata; + +using ILCompiler.DependencyAnalysis; +using ILCompiler.Logging; + +using ILLink.Shared.TrimAnalysis; + +using Internal.TypeSystem; + +using CustomAttributeValue = System.Reflection.Metadata.CustomAttributeValue; +using DependencyList = ILCompiler.DependencyAnalysisFramework.DependencyNodeCore.DependencyList; +using MultiValue = ILLink.Shared.DataFlow.ValueSet; + +#nullable enable + +namespace ILCompiler.Dataflow +{ + public readonly struct AttributeDataFlow + { + readonly Logger _logger; + readonly NodeFactory _factory; + readonly FlowAnnotations _annotations; + readonly MessageOrigin _origin; + + public AttributeDataFlow(Logger logger, NodeFactory factory, FlowAnnotations annotations, in MessageOrigin origin) + { + _annotations = annotations; + _factory = factory; + _logger = logger; + _origin = origin; + } + + public DependencyList? ProcessAttributeDataflow(MethodDesc method, CustomAttributeValue arguments) + { + DependencyList? result = null; + + // First do the dataflow for the constructor parameters if necessary. + if (_annotations.RequiresDataflowAnalysis(method)) + { + var builder = ImmutableArray.CreateBuilder(arguments.FixedArguments.Length); + foreach (var argument in arguments.FixedArguments) + { + builder.Add(argument.Value); + } + + ProcessAttributeDataflow(method, builder.ToImmutableArray(), ref result); + } + + // Named arguments next + TypeDesc attributeType = method.OwningType; + foreach (var namedArgument in arguments.NamedArguments) + { + if (namedArgument.Kind == CustomAttributeNamedArgumentKind.Field) + { + FieldDesc field = attributeType.GetField(namedArgument.Name); + if (field != null) + { + ProcessAttributeDataflow(field, namedArgument.Value, ref result); + } + } + else + { + Debug.Assert(namedArgument.Kind == CustomAttributeNamedArgumentKind.Property); + PropertyPseudoDesc property = ((MetadataType)attributeType).GetProperty(namedArgument.Name, null); + MethodDesc setter = property.SetMethod; + if (setter != null && setter.Signature.Length > 0 && !setter.Signature.IsStatic) + { + ProcessAttributeDataflow(method, ImmutableArray.Create(namedArgument.Value), ref result); + } + } + } + + return result; + } + + void ProcessAttributeDataflow(MethodDesc method, ImmutableArray arguments, ref DependencyList? result) + { + for (int i = 0; i < method.Signature.Length; i++) + { + var parameterValue = _annotations.GetMethodParameterValue(method, i); + if (parameterValue.DynamicallyAccessedMemberTypes != DynamicallyAccessedMemberTypes.None) + { + MultiValue value = GetValueForCustomAttributeArgument(arguments[i]); + var diagnosticContext = new DiagnosticContext(_origin, diagnosticsEnabled: true, _logger); + RequireDynamicallyAccessedMembers(diagnosticContext, value, parameterValue, parameterValue.ParameterOrigin, ref result); + } + } + } + + public void ProcessAttributeDataflow(FieldDesc field, object? value, ref DependencyList? result) + { + MultiValue valueNode = GetValueForCustomAttributeArgument(value); + var fieldValueCandidate = _annotations.GetFieldValue(field); + if (fieldValueCandidate is not ValueWithDynamicallyAccessedMembers fieldValue) + return; + + var diagnosticContext = new DiagnosticContext(_origin, diagnosticsEnabled: true, _logger); + RequireDynamicallyAccessedMembers(diagnosticContext, valueNode, fieldValue, new FieldOrigin(field), ref result); + } + + MultiValue GetValueForCustomAttributeArgument(object? argument) + { + if (argument is TypeDesc td) + { + return new SystemTypeValue(td); + } + + if (argument is string str) + { + return new KnownStringValue(str); + } + + // We shouldn't have gotten a non-null annotation for this from GetParameterAnnotation + throw new InvalidOperationException(); + } + + void RequireDynamicallyAccessedMembers( + in DiagnosticContext diagnosticContext, + in MultiValue value, + ValueWithDynamicallyAccessedMembers targetValue, + Origin memberWithRequirements, + ref DependencyList? result) + { + var reflectionMarker = new ReflectionMarker(_logger, _factory, _annotations, enabled: true); + var requireDynamicallyAccessedMembersAction = new RequireDynamicallyAccessedMembersAction(reflectionMarker, diagnosticContext, memberWithRequirements); + requireDynamicallyAccessedMembersAction.Invoke(value, targetValue); + + if (result == null) + { + result = reflectionMarker.Dependencies; + } + else + { + result.AddRange(reflectionMarker.Dependencies); + } + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/DiagnosticContext.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/DiagnosticContext.cs index afb38e0370a33..fb5551b1c6a00 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/DiagnosticContext.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/DiagnosticContext.cs @@ -8,7 +8,7 @@ namespace ILLink.Shared.TrimAnalysis { - readonly partial struct DiagnosticContext + public readonly partial struct DiagnosticContext { public readonly MessageOrigin Origin; public readonly bool DiagnosticsEnabled; diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/EcmaExtensions.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/EcmaExtensions.cs index 77266e84ddca4..870d13b92a5b0 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/EcmaExtensions.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/EcmaExtensions.cs @@ -8,6 +8,7 @@ using FieldAttributes = System.Reflection.FieldAttributes; using TypeAttributes = System.Reflection.TypeAttributes; using Debug = System.Diagnostics.Debug; +using ILLink.Shared.TypeSystemProxy; namespace ILCompiler.Dataflow { @@ -63,5 +64,28 @@ public static PropertyPseudoDesc GetProperty(this MetadataType mdType, string na return null; } + + public static ReferenceKind ParameterReferenceKind(this MethodDesc method, int index) + { + if (!method.Signature.IsStatic) + { + if (index == 0) + { + return method.OwningType.IsValueType ? ReferenceKind.Ref : ReferenceKind.None; + } + + index--; + } + + // Parameter metadata index 0 is for return parameter + var parameterMetadata = method.GetParameterMetadata()[index + 1]; + if (!method.Signature[index].IsByRef) + return ReferenceKind.None; + if (parameterMetadata.In) + return ReferenceKind.In; + if (parameterMetadata.Out) + return ReferenceKind.Out; + return ReferenceKind.Ref; + } } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/FieldReferenceValue.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/FieldReferenceValue.cs new file mode 100644 index 0000000000000..14f8998575f33 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/FieldReferenceValue.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using ILLink.Shared.DataFlow; + +using Internal.TypeSystem; + +#nullable enable + +namespace ILLink.Shared.TrimAnalysis +{ + public partial record FieldReferenceValue (FieldDesc FieldDefinition) : ReferenceValue + { + public override SingleValue DeepCopy () => this; + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/FlowAnnotations.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/FlowAnnotations.cs index 489c2726eb278..ba3f9e22beda9 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/FlowAnnotations.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/FlowAnnotations.cs @@ -16,6 +16,8 @@ using Debug = System.Diagnostics.Debug; using WellKnownType = Internal.TypeSystem.WellKnownType; +using System.Reflection; +using ILLink.Shared.DataFlow; #nullable enable @@ -29,10 +31,13 @@ public partial class FlowAnnotations private readonly TypeAnnotationsHashtable _hashtable; private readonly Logger _logger; + public ILProvider ILProvider { get; } + public FlowAnnotations(Logger logger, ILProvider ilProvider) { _hashtable = new TypeAnnotationsHashtable(logger, ilProvider); _logger = logger; + ILProvider = ilProvider; } public bool RequiresDataflowAnalysis(MethodDesc method) @@ -205,6 +210,37 @@ public bool ShouldWarnWhenAccessedForReflection(FieldDesc field) return GetAnnotations(field.OwningType).TryGetAnnotation(field, out _); } + public static bool IsTypeInterestingForDataflow(TypeDesc type) + { + // NOTE: this method is not particulary fast. It's assumed that the caller limits + // calls to this method as much as possible. + + if (type.IsWellKnownType(WellKnownType.String)) + return true; + + if (!type.IsDefType) + return false; + + var metadataType = (MetadataType)type; + + foreach (var intf in type.RuntimeInterfaces) + { + if (intf.Name == "IReflect" && intf.Namespace == "System.Reflection") + return true; + } + + if (metadataType.Name == "IReflect" && metadataType.Namespace == "System.Reflection") + return true; + + do + { + if (metadataType.Name == "Type" && metadataType.Namespace == "System") + return true; + } while ((metadataType = metadataType.MetadataBaseType) != null); + + return false; + } + private TypeAnnotations GetAnnotations(TypeDesc type) { return _hashtable.GetOrCreateValue(type); @@ -525,22 +561,39 @@ protected override TypeAnnotations CreateValueFromKey(TypeDesc key) } DynamicallyAccessedMemberTypes[]? typeGenericParameterAnnotations = null; - foreach (EcmaGenericParameter genericParameter in ecmaType.Instantiation) + if (ecmaType.Instantiation.Length > 0) { - GenericParameter genericParameterDef = reader.GetGenericParameter(genericParameter.Handle); - - var annotation = GetMemberTypesForDynamicallyAccessedMembersAttribute(reader, genericParameterDef.GetCustomAttributes()); - if (annotation != DynamicallyAccessedMemberTypes.None) + var attrs = GetGeneratedTypeAttributes(ecmaType); + for (int i = 0; i < ecmaType.Instantiation.Length; i++) { - if (typeGenericParameterAnnotations == null) - typeGenericParameterAnnotations = new DynamicallyAccessedMemberTypes[ecmaType.Instantiation.Length]; - typeGenericParameterAnnotations[genericParameter.Index] = annotation; + EcmaGenericParameter genericParameter = (EcmaGenericParameter)ecmaType.Instantiation[i]; + GenericParameter genericParameterDef = reader.GetGenericParameter(genericParameter.Handle); + var provider = attrs?[genericParameterIndex] ?? genericParameterDef; + + var annotation = GetMemberTypesForDynamicallyAccessedMembersAttribute(reader, genericParameterDef.GetCustomAttributes()); + if (annotation != DynamicallyAccessedMemberTypes.None) + { + if (typeGenericParameterAnnotations == null) + typeGenericParameterAnnotations = new DynamicallyAccessedMemberTypes[ecmaType.Instantiation.Length]; + typeGenericParameterAnnotations[genericParameter.Index] = annotation; + } } } return new TypeAnnotations(ecmaType, typeAnnotation, annotatedMethods.ToArray(), annotatedFields.ToArray(), typeGenericParameterAnnotations); } + private IReadOnlyList? GetGeneratedTypeAttributes(EcmaType typeDef) + { + if (!CompilerGeneratedNames.IsGeneratedType(typeDef.Name)) + { + return null; + } + var attrs = _context.CompilerGeneratedState.GetGeneratedTypeAttributes(typeDef); + Debug.Assert(attrs is null || attrs.Count == typeDef.Instantiation.Length); + return attrs; + } + private static bool ScanMethodBodyForFieldAccess(MethodIL body, bool write, out FieldDesc? found) { // Tries to find the backing field for a property getter/setter. @@ -595,37 +648,6 @@ private static bool ScanMethodBodyForFieldAccess(MethodIL body, bool write, out return true; } - - private bool IsTypeInterestingForDataflow(TypeDesc type) - { - // NOTE: this method is not particulary fast. It's assumed that the caller limits - // calls to this method as much as possible. - - if (type.IsWellKnownType(WellKnownType.String)) - return true; - - if (!type.IsDefType) - return false; - - var metadataType = (MetadataType)type; - - foreach (var intf in type.RuntimeInterfaces) - { - if (intf.Name == "IReflect" && intf.Namespace == "System.Reflection") - return true; - } - - if (metadataType.Name == "IReflect" && metadataType.Namespace == "System.Reflection") - return true; - - do - { - if (metadataType.Name == "Type" && metadataType.Namespace == "System") - return true; - } while ((metadataType = metadataType.MetadataBaseType) != null); - - return false; - } } internal void ValidateMethodAnnotationsAreSame(MethodDesc method, MethodDesc baseMethod) @@ -891,5 +913,44 @@ internal partial MethodParameterValue GetMethodParameterValue(MethodProxy method internal partial MethodParameterValue GetMethodParameterValue(MethodProxy method, int parameterIndex) => GetMethodParameterValue(method, parameterIndex, GetParameterAnnotation(method.Method, parameterIndex + (method.IsStatic() ? 0 : 1))); + + // Linker-specific dataflow value creation. Eventually more of these should be shared. + internal SingleValue GetFieldValue(FieldDesc field) + => field.Name switch + { + "EmptyTypes" when field.OwningType.IsTypeOf(ILLink.Shared.TypeSystemProxy.WellKnownType.System_Type) => ArrayValue.Create(0, field.OwningType), + "Empty" when field.OwningType.IsTypeOf(ILLink.Shared.TypeSystemProxy.WellKnownType.System_String) => new KnownStringValue(string.Empty), + _ => new FieldValue(field, GetFieldAnnotation(field)) + }; + + internal SingleValue GetTypeValueFromGenericArgument(TypeDesc genericArgument) + { + if (genericArgument is GenericParameterDesc inputGenericParameter) + { + return GetGenericParameterValue(inputGenericParameter); + } + else if (genericArgument is MetadataType genericArgumentType) + { + if (genericArgumentType.IsTypeOf(ILLink.Shared.TypeSystemProxy.WellKnownType.System_Nullable_T)) + { + var innerGenericArgument = genericArgumentType.Instantiation.Length == 1 ? genericArgumentType.Instantiation[0] : null; + switch (innerGenericArgument) + { + case GenericParameterDesc gp: + return new NullableValueWithDynamicallyAccessedMembers(genericArgumentType, + new GenericParameterValue(gp, GetGenericParameterAnnotation(gp))); + + case TypeDesc underlyingType: + return new NullableSystemTypeValue(genericArgumentType, new SystemTypeValue(underlyingType)); + } + } + // All values except for Nullable, including Nullable<> (with no type arguments) + return new SystemTypeValue(genericArgumentType); + } + else + { + return UnknownValue.Instance; + } + } } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/GenericArgumentDataFlow.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/GenericArgumentDataFlow.cs new file mode 100644 index 0000000000000..8d246cb0439cd --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/GenericArgumentDataFlow.cs @@ -0,0 +1,59 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; + +using ILCompiler.DependencyAnalysis; +using ILCompiler.Logging; + +using ILLink.Shared.TrimAnalysis; + +using Internal.TypeSystem; + +using DependencyList = ILCompiler.DependencyAnalysisFramework.DependencyNodeCore.DependencyList; +using MultiValue = ILLink.Shared.DataFlow.ValueSet; + +#nullable enabled + +namespace ILCompiler.Dataflow +{ + public readonly struct GenericArgumentDataFlow + { + readonly Logger _logger; + readonly NodeFactory _factory; + readonly FlowAnnotations _annotations; + readonly MessageOrigin _origin; + + public GenericArgumentDataFlow(Logger logger, NodeFactory factory, FlowAnnotations annotations, in MessageOrigin origin) + { + _logger = logger; + _factory = factory; + _annotations = annotations; + _origin = origin; + } + + public DependencyList ProcessGenericArgumentDataFlow(GenericParameterDesc genericParameter, TypeDesc genericArgument) + { + var genericParameterValue = _annotations.GetGenericParameterValue(genericParameter); + Debug.Assert(genericParameterValue.DynamicallyAccessedMemberTypes != DynamicallyAccessedMemberTypes.None); + + MultiValue genericArgumentValue = _annotations.GetTypeValueFromGenericArgument(genericArgument); + + var diagnosticContext = new DiagnosticContext(_origin, !_context.Annotations.ShouldSuppressAnalysisWarningsForRequiresUnreferencedCode(_origin.Provider), _logger); + return RequireDynamicallyAccessedMembers(diagnosticContext, genericArgumentValue, genericParameterValue, new GenericParameterOrigin(genericParameter)); + } + + DependencyList RequireDynamicallyAccessedMembers( + in DiagnosticContext diagnosticContext, + in MultiValue value, + ValueWithDynamicallyAccessedMembers targetValue, + Origin memberWithRequirements) + { + var reflectionMarker = new ReflectionMarker(_logger, _factory, _annotations, enabled: true); + var requireDynamicallyAccessedMembersAction = new RequireDynamicallyAccessedMembersAction(reflectionMarker, diagnosticContext, memberWithRequirements); + requireDynamicallyAccessedMembersAction.Invoke(value, targetValue); + return reflectionMarker.Dependencies; + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/HandleCallAction.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/HandleCallAction.cs index 9b41163b33ad4..e885dc796f9f5 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/HandleCallAction.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/HandleCallAction.cs @@ -75,7 +75,7 @@ private partial bool TryGetBaseType(TypeProxy type, out TypeProxy? baseType) return false; } - private partial bool TryResolveTypeNameForCreateInstance(in MethodProxy calledMethod, string assemblyName, string typeName, out TypeProxy resolvedType) + private partial bool TryResolveTypeNameForCreateInstanceAndMark(in MethodProxy calledMethod, string assemblyName, string typeName, out TypeProxy resolvedType) { // TODO: niche APIs that we probably shouldn't even have added // We have to issue a warning, otherwise we could break the app without a warning. diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/LocalVariableReferenceValue.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/LocalVariableReferenceValue.cs new file mode 100644 index 0000000000000..2d54ba80d8d1e --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/LocalVariableReferenceValue.cs @@ -0,0 +1,17 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using ILLink.Shared.DataFlow; + +#nullable enable + +namespace ILLink.Shared.TrimAnalysis +{ + public partial record LocalVariableReferenceValue(int LocalIndex) : ReferenceValue + { + public override SingleValue DeepCopy() + { + return this; + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/MethodBodyScanner.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/MethodBodyScanner.cs index 535ae2d1cf2f0..c15e355950951 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/MethodBodyScanner.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/MethodBodyScanner.cs @@ -3,7 +3,11 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; +using ILCompiler.Logging; +using ILLink.Shared; +using System.Reflection.Metadata; using ILLink.Shared.DataFlow; using ILLink.Shared.TrimAnalysis; using ILLink.Shared.TypeSystemProxy; @@ -11,6 +15,7 @@ using Internal.TypeSystem; using MultiValue = ILLink.Shared.DataFlow.ValueSet; +using System.Diagnostics.CodeAnalysis; #nullable enable @@ -23,36 +28,36 @@ readonly struct StackSlot { public MultiValue Value { get; } - /// - /// True if the value is on the stack as a byref - /// - public bool IsByRef { get; } - public StackSlot() { Value = new MultiValue(UnknownValue.Instance); - IsByRef = false; } - public StackSlot(SingleValue value, bool isByRef = false) + public StackSlot(SingleValue value) { Value = new MultiValue(value); - IsByRef = isByRef; } - public StackSlot(MultiValue value, bool isByRef = false) + public StackSlot(MultiValue value) { Value = value; - IsByRef = isByRef; } } abstract partial class MethodBodyScanner { protected static ValueSetLattice MultiValueLattice => default; + protected static ValueSetLattice MethodLattice => default; + + protected readonly FlowAnnotations _annotations; internal MultiValue ReturnValue { private set; get; } + protected MethodBodyScanner(FlowAnnotations annotations) + { + _annotations = annotations; + } + protected virtual void WarnAboutInvalidILInMethod(MethodIL method, int ilOffset) { } @@ -193,6 +198,34 @@ public int MoveNext(int offset) } } + [Conditional("DEBUG")] + static void ValidateNoReferenceToReference(ValueBasicBlockPair?[] locals, MethodIL method, int ilOffset) + { + for (int localVariableIndex = 0; localVariableIndex < locals.Length; localVariableIndex++) + { + ValueBasicBlockPair? localVariable = locals[localVariableIndex]; + if (localVariable == null) + continue; + + MultiValue localValue = localVariable.Value.Value; + foreach (var val in localValue) + { + if (val is LocalVariableReferenceValue reference) + { + ValueBasicBlockPair? referenceLocalVariable = locals[reference.LocalIndex]; + if (referenceLocalVariable.HasValue + && referenceLocalVariable.Value.Value.Any(v => v is ReferenceValue)) + { + throw new InvalidOperationException(MessageContainer.CreateErrorMessage( + $"In method {method.OwningMethod.GetDisplayName()}, local variable {localVariableIndex} references variable {reference.LocalIndex} which is a reference.", + (int)DiagnosticId.LinkerUnexpectedError, + origin: new MessageOrigin(method, ilOffset)).ToMSBuildString()); + } + } + } + } + } + private static void StoreMethodLocalValue( ValueBasicBlockPair?[] valueCollection, in MultiValue valueToStore, @@ -253,7 +286,82 @@ private static void StoreMethodLocalValue( } } - public void Scan(MethodIL methodBody) + // Scans the method as well as any nested functions (local functions or lambdas) and state machines + // reachable from it. + public virtual void InterproceduralScan(MethodIL methodBody) + { + var methodsInGroup = new ValueSet(methodBody.OwningMethod); + + // Optimization to prevent multiple scans of a method. + // Eventually we will need to allow re-scanning in some cases, for example + // when we discover new inputs to a method. But we aren't doing dataflow across + // lambdas and local functions yet, so no need for now. + HashSet scannedMethods = new HashSet(); + + while (true) + { + if (!TryGetNextMethodToScan(out MethodIL? methodToScan)) + break; + + scannedMethods.Add(methodToScan.OwningMethod); + Scan(methodToScan, ref methodsInGroup); + + // For state machine methods, also scan the state machine members. + // Simplification: assume that all generated methods of the state machine type are + // invoked at the point where the state machine method is called. + if (CompilerGeneratedState.TryGetStateMachineType(methodToScan, out TypeDefinition? stateMachineType)) + { + foreach (var method in stateMachineType.Methods) + { + Debug.Assert(!CompilerGeneratedNames.IsLambdaOrLocalFunction(method.Name)); + if (method.Body is MethodBody stateMachineBody) + Scan(stateMachineBody, ref methodsInGroup); + } + } + } + +#if DEBUG + // Validate that the compiler-generated callees tracked by the compiler-generated state + // are the same set of methods that we discovered and scanned above. + if (_context.CompilerGeneratedState.TryGetCompilerGeneratedCalleesForUserMethod(methodBody.Method, out List? compilerGeneratedCallees)) + { + var calleeMethods = compilerGeneratedCallees.OfType(); + Debug.Assert(methodsInGroup.Count() == 1 + calleeMethods.Count()); + foreach (var method in calleeMethods) + Debug.Assert(methodsInGroup.Contains(method)); + } + else + { + Debug.Assert(methodsInGroup.Count() == 1); + } +#endif + + bool TryGetNextMethodToScan([NotNullWhen(true)] out MethodIL? method) + { + foreach (var candidate in methodsInGroup) + { + var candidateMethod = candidate.Method; + MethodIL methodIL = _annotations.ILProvider.GetMethodIL(candidate.Method); + if (!scannedMethods.Contains(candidateMethod) && methodIL != null) + { + method = methodIL; + return true; + } + } + method = null; + return false; + } + } + + void TrackNestedFunctionReference(MethodDesc method, ref ValueSet methodsInGroup) + { + if (!CompilerGeneratedNames.IsLambdaOrLocalFunction(method.Name)) + return; + + methodsInGroup = MethodLattice.Meet(methodsInGroup, new(method)); + } + + protected virtual void Scan(MethodIL methodBody, ref ValueSet methodsInGroup) { MethodDesc thisMethod = methodBody.OwningMethod; @@ -270,6 +378,7 @@ public void Scan(MethodIL methodBody) ILReader reader = new ILReader(methodBody.GetILBytes()); while (reader.HasNext) { + ValidateNoReferenceToReference(locals, methodBody, reader.Offset); int curBasicBlock = blockIterator.MoveNext(reader.Offset); if (knownStacks.ContainsKey(reader.Offset)) @@ -377,7 +486,6 @@ public void Scan(MethodIL methodBody) break; case ILOpcode.arglist: - case ILOpcode.ldftn: case ILOpcode.sizeof_: case ILOpcode.ldc_i8: case ILOpcode.ldc_r4: @@ -386,6 +494,17 @@ public void Scan(MethodIL methodBody) reader.Skip(opcode); break; + case ILOpcode.ldftn: + { + if (methodBody.GetObject(reader.ReadILToken()) is MethodDesc methodOperand) + { + TrackNestedFunctionReference(methodOperand, ref methodsInGroup); + } + + PushUnknown(currentStack); + } + break; + case ILOpcode.ldarg: case ILOpcode.ldarg_0: case ILOpcode.ldarg_1: @@ -573,7 +692,7 @@ public void Scan(MethodIL methodBody) case ILOpcode.stind_r8: case ILOpcode.stind_ref: case ILOpcode.stobj: - ScanIndirectStore(methodBody, offset, currentStack); + ScanIndirectStore(methodBody, offset, currentStack, locals, curBasicBlock); reader.Skip(opcode); break; @@ -643,7 +762,11 @@ public void Scan(MethodIL methodBody) case ILOpcode.call: case ILOpcode.callvirt: case ILOpcode.newobj: - HandleCall(methodBody, opcode, offset, (MethodDesc)methodBody.GetObject(reader.ReadILToken()), currentStack, curBasicBlock); + { + MethodDesc methodOperand = (MethodDesc)methodBody.GetObject(reader.ReadILToken()); + TrackNestedFunctionReference(methodOperand, ref methodsInGroup); + HandleCall(methodBody, opcode, offset, methodOperand, currentStack, locals, curBasicBlock); + } break; case ILOpcode.jmp: @@ -680,7 +803,9 @@ public void Scan(MethodIL methodBody) if (hasReturnValue) { StackSlot retValue = PopUnknown(currentStack, 1, methodBody, offset); - ReturnValue = MultiValueLattice.Meet(ReturnValue, retValue.Value); + // If the return value is a reference, treat it as the value itself for now + // We can handle ref return values better later + ReturnValue = MultiValueLattice.Meet(ReturnValue, DereferenceValue(retValue.Value, locals)); } ClearStack(ref currentStack); break; @@ -760,12 +885,16 @@ private void ScanLdarg(ILOpcode opcode, int paramNum, Stack currentSt } else { + // This is semantically wrong if it returns true - we would representing a reference parameter as a reference to a parameter - but it should be fine for now isByRef = thisMethod.Signature[paramNum - (thisMethod.Signature.IsStatic ? 0 : 1)].IsByRefOrPointer(); } isByRef |= opcode == ILOpcode.ldarga || opcode == ILOpcode.ldarga_s; - StackSlot slot = new StackSlot(GetMethodParameterValue(thisMethod, paramNum), isByRef); + StackSlot slot = new StackSlot( + isByRef + ? new ParameterReferenceValue(thisMethod, paramNum) + : GetMethodParameterValue(thisMethod, paramNum)); currentStack.Push(slot); } @@ -796,14 +925,16 @@ private void ScanLdloc( || methodBody.GetLocals()[index].Type.IsByRefOrPointer(); ValueBasicBlockPair? localValue = locals[index]; - if (!localValue.HasValue) + StackSlot newSlot; + if (isByRef) { - currentStack.Push(new StackSlot(UnknownValue.Instance, isByRef)); + newSlot = new StackSlot(new LocalVariableReferenceValue(index)); } + else if (localValue.HasValue) + newSlot = new StackSlot(localValue.Value.Value); else - { - currentStack.Push(new StackSlot(localValue.Value.Value, isByRef)); - } + newSlot = new StackSlot(UnknownValue.Instance); + currentStack.Push(newSlot); } private static void ScanLdtoken(MethodIL methodBody, object operand, Stack currentStack) @@ -870,7 +1001,9 @@ private void ScanStloc( private void ScanIndirectStore( MethodIL methodBody, int offset, - Stack currentStack) + Stack currentStack, + ValueBasicBlockPair?[] locals, + int curBasicBlock) { StackSlot valueToStore = PopUnknown(currentStack, 1, methodBody, offset); StackSlot destination = PopUnknown(currentStack, 1, methodBody, offset); @@ -889,6 +1022,56 @@ private void ScanIndirectStore( } + /// + /// Handles storing the source value in a target or MultiValue of ReferenceValues. + /// + /// A set of that a value is being stored into + /// The value to store + /// The method body that contains the operation causing the store + /// The instruction offset causing the store + /// Throws if is not a valid target for an indirect store. + protected void StoreInReference(MultiValue target, MultiValue source, MethodIL method, int offset, ValueBasicBlockPair?[] locals, int curBasicBlock) + { + foreach (var value in target) + { + switch (value) + { + case LocalVariableReferenceValue localReference: + StoreMethodLocalValue(locals, source, localReference.LocalIndex, curBasicBlock); + break; + case FieldReferenceValue fieldReference + when GetFieldValue(fieldReference.FieldDefinition).AsSingleValue() is FieldValue fieldValue: + HandleStoreField(method, offset, fieldValue, source); + break; + case ParameterReferenceValue parameterReference + when GetMethodParameterValue(parameterReference.MethodDefinition, parameterReference.ParameterIndex) is MethodParameterValue parameterValue: + HandleStoreParameter(method, offset, parameterValue, source); + break; + case ParameterReferenceValue parameterReference + when GetMethodParameterValue(parameterReference.MethodDefinition, parameterReference.ParameterIndex) is MethodThisParameterValue thisParameterValue: + HandleStoreMethodThisParameter(method, offset, thisParameterValue, source); + break; + case MethodReturnValue methodReturnValue: + // Ref returns don't have special ReferenceValue values, so assume if the target here is a MethodReturnValue then it must be a ref return value + HandleStoreMethodReturnValue(method, offset, methodReturnValue, source); + break; + case IValueWithStaticType valueWithStaticType: + if (valueWithStaticType.StaticType is not null && FlowAnnotations.IsTypeInterestingForDataflow(valueWithStaticType.StaticType)) + throw new InvalidOperationException(MessageContainer.CreateErrorMessage( + $"Unhandled StoreReference call. Unhandled attempt to store a value in {value} of type {value.GetType()}.", + (int)DiagnosticId.LinkerUnexpectedError, + origin: new MessageOrigin(method, offset)).ToMSBuildString()); + // This should only happen for pointer derefs, which can't point to interesting types + break; + default: + // These cases should only be refs to array elements + // References to array elements are not yet tracked and since we don't allow annotations on arrays, they won't cause problems + break; + } + } + + } + protected abstract MultiValue GetFieldValue(FieldDesc field); private void ScanLdfld( @@ -904,7 +1087,10 @@ Stack currentStack bool isByRef = opcode == ILOpcode.ldflda || opcode == ILOpcode.ldsflda; - StackSlot slot = new StackSlot(GetFieldValue(field), isByRef); + MultiValue newValue = isByRef ? + new FieldReferenceValue(field) + : GetFieldValue(field); + StackSlot slot = new(newValue); currentStack.Push(slot); } @@ -916,6 +1102,14 @@ protected virtual void HandleStoreParameter(MethodIL method, int offset, MethodP { } + protected virtual void HandleStoreMethodThisParameter(MethodIL method, int offset, MethodThisParameterValue thisParameter, MultiValue sourceValue) + { + } + + protected virtual void HandleStoreMethodReturnValue(MethodIL method, int offset, MethodReturnValue thisParameter, MultiValue sourceValue) + { + } + private void ScanStfld( MethodIL methodBody, int offset, @@ -968,19 +1162,77 @@ private ValueNodeList PopCallArguments( return methodParams; } + internal MultiValue DereferenceValue(MultiValue maybeReferenceValue, ValueBasicBlockPair?[] locals) + { + MultiValue dereferencedValue = MultiValueLattice.Top; + foreach (var value in maybeReferenceValue) + { + switch (value) + { + case FieldReferenceValue fieldReferenceValue: + dereferencedValue = MultiValue.Meet( + dereferencedValue, + GetFieldValue(fieldReferenceValue.FieldDefinition)); + break; + case ParameterReferenceValue parameterReferenceValue: + dereferencedValue = MultiValue.Meet( + dereferencedValue, + GetMethodParameterValue(parameterReferenceValue.MethodDefinition, parameterReferenceValue.ParameterIndex)); + break; + case LocalVariableReferenceValue localVariableReferenceValue: + var valueBasicBlockPair = locals[localVariableReferenceValue.LocalIndex]; + if (valueBasicBlockPair.HasValue) + dereferencedValue = MultiValue.Meet(dereferencedValue, valueBasicBlockPair.Value.Value); + else + dereferencedValue = MultiValue.Meet(dereferencedValue, UnknownValue.Instance); + break; + case ReferenceValue referenceValue: + throw new NotImplementedException($"Unhandled dereference of ReferenceValue of type {referenceValue.GetType().FullName}"); + default: + dereferencedValue = MultiValue.Meet(dereferencedValue, value); + break; + } + } + return dereferencedValue; + } + + /// + /// Assigns a MethodParameterValue to the location of each parameter passed by reference. (i.e. assigns the value to x when passing `ref x` as a parameter) + /// + protected void AssignRefAndOutParameters( + MethodIL callingMethodBody, + MethodDesc calledMethod, + ValueNodeList methodArguments, + int offset, + ValueBasicBlockPair?[] locals, + int curBasicBlock) + { + int parameterOffset = !calledMethod.Signature.IsStatic ? 1 : 0; + int parameterIndex = 0; + for (int ilArgumentIndex = parameterOffset; ilArgumentIndex < methodArguments.Count; ilArgumentIndex++, parameterIndex++) + { + if (calledMethod.ParameterReferenceKind(ilArgumentIndex) is not (ReferenceKind.Ref or ReferenceKind.Out)) + continue; + SingleValue newByRefValue = _annotations.GetMethodParameterValue(calledMethod, parameterIndex); + StoreInReference(methodArguments[ilArgumentIndex], newByRefValue, callingMethodBody, offset, locals, curBasicBlock); + } + } + private void HandleCall( MethodIL callingMethodBody, ILOpcode opcode, int offset, MethodDesc calledMethod, Stack currentStack, + ValueBasicBlockPair?[] locals, int curBasicBlock) { bool isNewObj = opcode == ILOpcode.newobj; SingleValue? newObjValue; - ValueNodeList methodParams = PopCallArguments(currentStack, calledMethod, callingMethodBody, isNewObj, - offset, out newObjValue); + ValueNodeList methodArguments = PopCallArguments(currentStack, calledMethod, callingMethodBody, isNewObj, + offset, out newObjValue); + ValueNodeList dereferencedMethodArguments = new(methodArguments.Select(argument => DereferenceValue(argument, locals)).ToList()); MultiValue methodReturnValue; bool handledFunction = HandleCall( @@ -988,7 +1240,7 @@ private void HandleCall( calledMethod, opcode, offset, - methodParams, + dereferencedMethodArguments, out methodReturnValue); // Handle the return value or newobj result @@ -1011,9 +1263,11 @@ private void HandleCall( } if (isNewObj || !calledMethod.Signature.ReturnType.IsVoid) - currentStack.Push(new StackSlot(methodReturnValue, calledMethod.Signature.ReturnType.IsByRefOrPointer())); + currentStack.Push(new StackSlot(methodReturnValue)); - foreach (var param in methodParams) + AssignRefAndOutParameters(callingMethodBody, calledMethod, methodArguments, offset, locals, curBasicBlock); + + foreach (var param in methodArguments) { foreach (var v in param) { @@ -1088,6 +1342,7 @@ private void ScanLdelem( PushUnknown(currentStack); return; } + // We don't yet handle arrays of references or pointers bool isByRef = opcode == ILOpcode.ldelema; int? index = indexToLoadFrom.Value.AsConstInt(); @@ -1100,15 +1355,16 @@ private void ScanLdelem( } return; } - - if (arr.IndexValues.TryGetValue(index.Value, out ValueBasicBlockPair arrayIndexValue)) + // Don't try to track refs to array elements. Set it as unknown, then push unknown to the stack + else if (isByRef) { - currentStack.Push(new StackSlot(arrayIndexValue.Value, isByRef)); + arr.IndexValues[index.Value] = new ValueBasicBlockPair(UnknownValue.Instance, curBasicBlock); + PushUnknown(currentStack); } + else if (arr.IndexValues.TryGetValue(index.Value, out ValueBasicBlockPair arrayIndexValue)) + currentStack.Push(new StackSlot(arrayIndexValue.Value)); else - { PushUnknown(currentStack); - } } } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/MethodProxy.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/MethodProxy.cs index ed866e82d32d1..4795258de4a15 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/MethodProxy.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/MethodProxy.cs @@ -1,6 +1,7 @@ // 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.Immutable; using ILCompiler; using ILCompiler.Dataflow; @@ -11,7 +12,7 @@ namespace ILLink.Shared.TypeSystemProxy { - readonly partial struct MethodProxy + readonly partial struct MethodProxy : IEquatable { public MethodProxy(MethodDesc method) => Method = method; @@ -61,5 +62,13 @@ internal partial ImmutableArray GetGenericParameters() internal partial bool ReturnsVoid() => Method.Signature.ReturnType.IsVoid; public override string ToString() => Method.ToString(); + + public ReferenceKind ParameterReferenceKind(int index) => Method.ParameterReferenceKind(Method.Signature.IsStatic ? index : index + 1); + + public bool Equals(MethodProxy other) => Method.Equals(other.Method); + + public override bool Equals(object? obj) => obj is MethodProxy other && Equals(other); + + public override int GetHashCode() => Method.GetHashCode(); } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ParameterReferenceValue.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ParameterReferenceValue.cs new file mode 100644 index 0000000000000..6b4f1270c4781 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ParameterReferenceValue.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using ILLink.Shared.DataFlow; + +using Internal.TypeSystem; + +#nullable enable + +namespace ILLink.Shared.TrimAnalysis +{ + public partial record ParameterReferenceValue (MethodDesc MethodDefinition, int ParameterIndex) + : ReferenceValue + { + public override SingleValue DeepCopy () + { + return this; + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/README.md b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/README.md index 6388ad638a5b6..fb9471b534ada 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/README.md +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/README.md @@ -4,4 +4,11 @@ The purpose of this logic is to analyze dynamic behavior of the compiled code to Let's try to keep this in sync. The ReferenceSource contains sources at the time of porting. -It should be updated whenever we take fixes from IL linker. \ No newline at end of file +It should be updated whenever we take fixes from IL linker. + +Standard updates when taking files from the linker: +* Note: These rules apply only to the DataFlow directory, the files which we share verbatime should remain exactly as-is in linker for now +* Use the runtime's version of the license header in the file (starts with `Licensed to the .NET Foundation`) +* Use the formatting from runtime repo - in VS this can be done by reformatting the whole document: Edit -> Advanced -> Format whole document. +* Add `#nullable enable` since nullable is not globaly enabled in ILC yet + diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ArrayValue.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ArrayValue.cs index 71934bc28ca87..570714f6c8d23 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ArrayValue.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ArrayValue.cs @@ -24,9 +24,9 @@ public static MultiValue Create (MultiValue size, TypeReference elementType) return result; } - public static MultiValue Create (int size, TypeReference elementType) + public static ArrayValue Create (int size, TypeReference elementType) { - return new MultiValue (new ArrayValue (new ConstIntValue (size), elementType)); + return new ArrayValue (new ConstIntValue (size), elementType); } /// diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/AttributeDataFlow.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/AttributeDataFlow.cs new file mode 100644 index 0000000000000..357803a768d13 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/AttributeDataFlow.cs @@ -0,0 +1,73 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using ILLink.Shared.TrimAnalysis; +using Mono.Cecil; +using Mono.Linker.Steps; +using MultiValue = ILLink.Shared.DataFlow.ValueSet; + +namespace Mono.Linker.Dataflow +{ + public readonly struct AttributeDataFlow + { + readonly LinkContext _context; + readonly MarkStep _markStep; + readonly MessageOrigin _origin; + + public AttributeDataFlow (LinkContext context, MarkStep markStep, in MessageOrigin origin) + { + _context = context; + _markStep = markStep; + _origin = origin; + } + + public void ProcessAttributeDataflow (MethodDefinition method, IList arguments) + { + for (int i = 0; i < method.Parameters.Count; i++) { + var parameterValue = _context.Annotations.FlowAnnotations.GetMethodParameterValue (method, i); + if (parameterValue.DynamicallyAccessedMemberTypes != DynamicallyAccessedMemberTypes.None) { + MultiValue value = GetValueForCustomAttributeArgument (arguments[i]); + var diagnosticContext = new DiagnosticContext (_origin, diagnosticsEnabled: true, _context); + RequireDynamicallyAccessedMembers (diagnosticContext, value, parameterValue); + } + } + } + + public void ProcessAttributeDataflow (FieldDefinition field, CustomAttributeArgument value) + { + MultiValue valueNode = GetValueForCustomAttributeArgument (value); + var fieldValueCandidate = _context.Annotations.FlowAnnotations.GetFieldValue (field); + if (fieldValueCandidate is not ValueWithDynamicallyAccessedMembers fieldValue) + return; + + var diagnosticContext = new DiagnosticContext (_origin, diagnosticsEnabled: true, _context); + RequireDynamicallyAccessedMembers (diagnosticContext, valueNode, fieldValue); + } + + MultiValue GetValueForCustomAttributeArgument (CustomAttributeArgument argument) + { + if (argument.Type.Name == "Type") { + TypeDefinition? referencedType = ((TypeReference) argument.Value).ResolveToTypeDefinition (_context); + return referencedType == null + ? UnknownValue.Instance + : new SystemTypeValue (referencedType); + } + + if (argument.Type.MetadataType == MetadataType.String) + return new KnownStringValue ((string) argument.Value); + + // We shouldn't have gotten a non-null annotation for this from GetParameterAnnotation + throw new InvalidOperationException (); + } + + void RequireDynamicallyAccessedMembers (in DiagnosticContext diagnosticContext, in MultiValue value, ValueWithDynamicallyAccessedMembers targetValue) + { + var reflectionMarker = new ReflectionMarker (_context, _markStep, enabled: true); + var requireDynamicallyAccessedMembersAction = new RequireDynamicallyAccessedMembersAction (reflectionMarker, diagnosticContext); + requireDynamicallyAccessedMembersAction.Invoke (value, targetValue); + } + } +} \ No newline at end of file diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/DiagnosticContext.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/DiagnosticContext.cs index df43246bbb815..67fb9ec08d234 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/DiagnosticContext.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/DiagnosticContext.cs @@ -5,7 +5,7 @@ namespace ILLink.Shared.TrimAnalysis { - readonly partial struct DiagnosticContext + public readonly partial struct DiagnosticContext { public readonly MessageOrigin Origin; public readonly bool DiagnosticsEnabled; diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/DynamicallyAccessedMembersTypeHierarchy.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/DynamicallyAccessedMembersTypeHierarchy.cs index 56e9496e84e8f..103745ac54fc8 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/DynamicallyAccessedMembersTypeHierarchy.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/DynamicallyAccessedMembersTypeHierarchy.cs @@ -108,7 +108,7 @@ public DynamicallyAccessedMembersTypeHierarchy (LinkContext context, MarkStep ma // One of the base/interface types is already marked as having the annotation applied // so we need to apply the annotation to this type as well var origin = new MessageOrigin (type); - var reflectionMarker = new ReflectionMarker (_context, _markStep); + var reflectionMarker = new ReflectionMarker (_context, _markStep, enabled: true); // Report warnings on access to annotated members, with the annotated type as the origin. ApplyDynamicallyAccessedMembersToType (reflectionMarker, origin, type, annotation); } @@ -116,9 +116,7 @@ public DynamicallyAccessedMembersTypeHierarchy (LinkContext context, MarkStep ma return (annotation, apply); } - public DynamicallyAccessedMemberTypes ApplyDynamicallyAccessedMembersToTypeHierarchy ( - ReflectionMarker reflectionMarker, - TypeDefinition type) + public DynamicallyAccessedMemberTypes ApplyDynamicallyAccessedMembersToTypeHierarchy (TypeDefinition type) { (var annotation, var applied) = ProcessMarkedTypeForDynamicallyAccessedMembersHierarchy (type); @@ -129,6 +127,7 @@ public DynamicallyAccessedMemberTypes ApplyDynamicallyAccessedMembersToTypeHiera // Apply the effective annotation for the type var origin = new MessageOrigin (type); + var reflectionMarker = new ReflectionMarker (_context, _markStep, enabled: true); // Report warnings on access to annotated members, with the annotated type as the origin. ApplyDynamicallyAccessedMembersToType (reflectionMarker, origin, type, annotation); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/FieldReferenceValue.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/FieldReferenceValue.cs new file mode 100644 index 0000000000000..de0f2354189c9 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/FieldReferenceValue.cs @@ -0,0 +1,12 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +using ILLink.Shared.DataFlow; +using Mono.Cecil; + +namespace ILLink.Shared.TrimAnalysis +{ + public partial record FieldReferenceValue (FieldDefinition FieldDefinition) : ReferenceValue + { + public override SingleValue DeepCopy () => this; + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/FlowAnnotations.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/FlowAnnotations.cs index aea413b2c7ce4..32e148acd97ae 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/FlowAnnotations.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/FlowAnnotations.cs @@ -5,6 +5,8 @@ using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using System.Linq; +using ILLink.Shared.DataFlow; using ILLink.Shared.TypeSystemProxy; using Mono.Cecil; using Mono.Cecil.Cil; @@ -145,6 +147,17 @@ public bool ShouldWarnWhenAccessedForReflection (MethodDefinition method) public bool ShouldWarnWhenAccessedForReflection (FieldDefinition field) => GetAnnotations (field.DeclaringType).TryGetAnnotation (field, out _); + public bool IsTypeInterestingForDataflow (TypeReference typeReference) + { + if (typeReference.MetadataType == MetadataType.String) + return true; + + TypeDefinition? type = _context.TryResolve (typeReference); + return type != null && ( + _hierarchyInfo.IsSystemType (type) || + _hierarchyInfo.IsSystemReflectionIReflect (type)); + } + TypeAnnotations GetAnnotations (TypeDefinition type) { if (!_annotations.TryGetValue (type, out TypeAnnotations value)) { @@ -372,9 +385,10 @@ TypeAnnotations BuildTypeAnnotations (TypeDefinition type) DynamicallyAccessedMemberTypes[]? typeGenericParameterAnnotations = null; if (type.HasGenericParameters) { + var attrs = GetGeneratedTypeAttributes (type); for (int genericParameterIndex = 0; genericParameterIndex < type.GenericParameters.Count; genericParameterIndex++) { - var genericParameter = type.GenericParameters[genericParameterIndex]; - var annotation = GetMemberTypesForDynamicallyAccessedMembersAttribute (type, providerIfNotMember: genericParameter); + var provider = attrs?[genericParameterIndex] ?? type.GenericParameters[genericParameterIndex]; + var annotation = GetMemberTypesForDynamicallyAccessedMembersAttribute (type, providerIfNotMember: provider); if (annotation != DynamicallyAccessedMemberTypes.None) { if (typeGenericParameterAnnotations == null) typeGenericParameterAnnotations = new DynamicallyAccessedMemberTypes[type.GenericParameters.Count]; @@ -386,6 +400,16 @@ TypeAnnotations BuildTypeAnnotations (TypeDefinition type) return new TypeAnnotations (type, typeAnnotation, annotatedMethods.ToArray (), annotatedFields.ToArray (), typeGenericParameterAnnotations); } + private IReadOnlyList? GetGeneratedTypeAttributes (TypeDefinition typeDef) + { + if (!CompilerGeneratedNames.IsGeneratedType (typeDef.Name)) { + return null; + } + var attrs = _context.CompilerGeneratedState.GetGeneratedTypeAttributes (typeDef); + Debug.Assert (attrs is null || attrs.Count == typeDef.GenericParameters.Count); + return attrs; + } + bool ScanMethodBodyForFieldAccess (MethodBody body, bool write, out FieldDefinition? found) { // Tries to find the backing field for a property getter/setter. @@ -440,17 +464,6 @@ bool ScanMethodBodyForFieldAccess (MethodBody body, bool write, out FieldDefinit return true; } - bool IsTypeInterestingForDataflow (TypeReference typeReference) - { - if (typeReference.MetadataType == MetadataType.String) - return true; - - TypeDefinition? type = _context.TryResolve (typeReference); - return type != null && ( - _hierarchyInfo.IsSystemType (type) || - _hierarchyInfo.IsSystemReflectionIReflect (type)); - } - internal void ValidateMethodAnnotationsAreSame (MethodDefinition method, MethodDefinition baseMethod) { GetAnnotations (method.DeclaringType).TryGetAnnotation (method, out var methodAnnotations); @@ -694,5 +707,41 @@ internal partial MethodParameterValue GetMethodParameterValue (MethodProxy metho internal partial MethodParameterValue GetMethodParameterValue (MethodProxy method, int parameterIndex) => GetMethodParameterValue (method, parameterIndex, GetParameterAnnotation (method.Method, parameterIndex + (method.IsStatic () ? 0 : 1))); + + // Linker-specific dataflow value creation. Eventually more of these should be shared. + internal SingleValue GetFieldValue (FieldDefinition field) + => field.Name switch { + "EmptyTypes" when field.DeclaringType.IsTypeOf (WellKnownType.System_Type) => ArrayValue.Create (0, field.DeclaringType), + "Empty" when field.DeclaringType.IsTypeOf (WellKnownType.System_String) => new KnownStringValue (string.Empty), + _ => new FieldValue (field.FieldType.ResolveToTypeDefinition (_context), field, GetFieldAnnotation (field)) + }; + + internal SingleValue GetTypeValueFromGenericArgument (TypeReference genericArgument) + { + if (genericArgument is GenericParameter inputGenericParameter) { + // Technically this should be a new value node type as it's not a System.Type instance representation, but just the generic parameter + // That said we only use it to perform the dynamically accessed members checks and for that purpose treating it as System.Type is perfectly valid. + return GetGenericParameterValue (inputGenericParameter); + } else if (genericArgument.ResolveToTypeDefinition (_context) is TypeDefinition genericArgumentType) { + if (genericArgumentType.IsTypeOf (WellKnownType.System_Nullable_T)) { + var innerGenericArgument = (genericArgument as IGenericInstance)?.GenericArguments.FirstOrDefault (); + switch (innerGenericArgument) { + case GenericParameter gp: + return new NullableValueWithDynamicallyAccessedMembers (genericArgumentType, + new GenericParameterValue (gp, _context.Annotations.FlowAnnotations.GetGenericParameterAnnotation (gp))); + + case TypeReference underlyingType: + if (underlyingType.ResolveToTypeDefinition (_context) is TypeDefinition underlyingTypeDefinition) + return new NullableSystemTypeValue (genericArgumentType, new SystemTypeValue (underlyingTypeDefinition)); + else + return UnknownValue.Instance; + } + } + // All values except for Nullable, including Nullable<> (with no type arguments) + return new SystemTypeValue (genericArgumentType); + } else { + return UnknownValue.Instance; + } + } } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/GenericArgumentDataFlow.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/GenericArgumentDataFlow.cs new file mode 100644 index 0000000000000..a4092c4a9f805 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/GenericArgumentDataFlow.cs @@ -0,0 +1,44 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using ILLink.Shared.TrimAnalysis; +using Mono.Cecil; +using Mono.Linker.Steps; +using MultiValue = ILLink.Shared.DataFlow.ValueSet; + +namespace Mono.Linker.Dataflow +{ + public readonly struct GenericArgumentDataFlow + { + readonly LinkContext _context; + readonly MarkStep _markStep; + readonly MessageOrigin _origin; + + public GenericArgumentDataFlow (LinkContext context, MarkStep markStep, in MessageOrigin origin) + { + _context = context; + _markStep = markStep; + _origin = origin; + } + + public void ProcessGenericArgumentDataFlow (GenericParameter genericParameter, TypeReference genericArgument) + { + var genericParameterValue = _context.Annotations.FlowAnnotations.GetGenericParameterValue (genericParameter); + Debug.Assert (genericParameterValue.DynamicallyAccessedMemberTypes != DynamicallyAccessedMemberTypes.None); + + MultiValue genericArgumentValue = _context.Annotations.FlowAnnotations.GetTypeValueFromGenericArgument (genericArgument); + + var diagnosticContext = new DiagnosticContext (_origin, !_context.Annotations.ShouldSuppressAnalysisWarningsForRequiresUnreferencedCode (_origin.Provider), _context); + RequireDynamicallyAccessedMembers (diagnosticContext, genericArgumentValue, genericParameterValue); + } + + void RequireDynamicallyAccessedMembers (in DiagnosticContext diagnosticContext, in MultiValue value, ValueWithDynamicallyAccessedMembers targetValue) + { + var reflectionMarker = new ReflectionMarker (_context, _markStep, enabled: true); + var requireDynamicallyAccessedMembersAction = new RequireDynamicallyAccessedMembersAction (reflectionMarker, diagnosticContext); + requireDynamicallyAccessedMembersAction.Invoke (value, targetValue); + } + } +} \ No newline at end of file diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/HandleCallAction.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/HandleCallAction.cs index 3d402cef977ec..69641ecc65333 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/HandleCallAction.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/HandleCallAction.cs @@ -29,7 +29,7 @@ public HandleCallAction ( _diagnosticContext = diagnosticContext; _callingMethodDefinition = callingMethodDefinition; _annotations = context.Annotations.FlowAnnotations; - _requireDynamicallyAccessedMembersAction = new (context, reflectionMarker, diagnosticContext); + _requireDynamicallyAccessedMembersAction = new (reflectionMarker, diagnosticContext); } private partial bool MethodIsTypeConstructor (MethodProxy method) @@ -68,7 +68,7 @@ private partial bool TryGetBaseType (TypeProxy type, out TypeProxy? baseType) return false; } - private partial bool TryResolveTypeNameForCreateInstance (in MethodProxy calledMethod, string assemblyName, string typeName, out TypeProxy resolvedType) + private partial bool TryResolveTypeNameForCreateInstanceAndMark (in MethodProxy calledMethod, string assemblyName, string typeName, out TypeProxy resolvedType) { var resolvedAssembly = _context.TryResolve (assemblyName); if (resolvedAssembly == null) { @@ -79,9 +79,8 @@ private partial bool TryResolveTypeNameForCreateInstance (in MethodProxy calledM return false; } - if (!_context.TypeNameResolver.TryResolveTypeName (resolvedAssembly, typeName, out TypeReference? typeRef) - || _context.TryResolve (typeRef) is not TypeDefinition resolvedTypeDefinition - || typeRef is ArrayType) { + if (!_reflectionMarker.TryResolveTypeNameAndMark (resolvedAssembly, typeName, _diagnosticContext, out TypeDefinition? resolvedTypeDefinition) + || resolvedTypeDefinition.IsTypeOf (WellKnownType.System_Array)) { // It's not wrong to have a reference to non-existing type - the code may well expect to get an exception in this case // Note that we did find the assembly, so it's not a linker config problem, it's either intentional, or wrong versions of assemblies // but linker can't know that. In case a user tries to create an array using System.Activator we should simply ignore it, the user diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/LocalVariableReferenceValue.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/LocalVariableReferenceValue.cs new file mode 100644 index 0000000000000..92365210a80d7 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/LocalVariableReferenceValue.cs @@ -0,0 +1,15 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +using ILLink.Shared.DataFlow; +using Mono.Cecil.Cil; + +namespace ILLink.Shared.TrimAnalysis +{ + public partial record LocalVariableReferenceValue (VariableDefinition LocalDefinition) : ReferenceValue + { + public override SingleValue DeepCopy () + { + return this; + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/MethodBodyScanner.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/MethodBodyScanner.cs index b50de89788f1f..0562acee139f3 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/MethodBodyScanner.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/MethodBodyScanner.cs @@ -3,13 +3,17 @@ using System; using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; +using ILLink.Shared; using ILLink.Shared.DataFlow; using ILLink.Shared.TrimAnalysis; using ILLink.Shared.TypeSystemProxy; using Mono.Cecil; using Mono.Cecil.Cil; using Mono.Collections.Generic; +using LocalVariableStore = System.Collections.Generic.Dictionary; using MultiValue = ILLink.Shared.DataFlow.ValueSet; namespace Mono.Linker.Dataflow @@ -21,27 +25,19 @@ readonly struct StackSlot { public MultiValue Value { get; } - /// - /// True if the value is on the stack as a byref - /// - public bool IsByRef { get; } - public StackSlot () { Value = new MultiValue (UnknownValue.Instance); - IsByRef = false; } - public StackSlot (SingleValue value, bool isByRef = false) + public StackSlot (SingleValue value) { Value = new MultiValue (value); - IsByRef = isByRef; } - public StackSlot (MultiValue value, bool isByRef = false) + public StackSlot (MultiValue value) { Value = value; - IsByRef = isByRef; } } @@ -49,6 +45,7 @@ abstract partial class MethodBodyScanner { protected readonly LinkContext _context; protected static ValueSetLattice MultiValueLattice => default; + protected static ValueSetLattice MethodLattice => default; protected MethodBodyScanner (LinkContext context) { @@ -180,7 +177,25 @@ public int MoveNext (Instruction op) } } - private static void StoreMethodLocalValue ( + [Conditional ("DEBUG")] + static void ValidateNoReferenceToReference (LocalVariableStore locals, MethodDefinition method, int ilOffset) + { + foreach (var keyValuePair in locals) { + MultiValue localValue = keyValuePair.Value.Value; + VariableDefinition localVariable = keyValuePair.Key; + foreach (var val in localValue) { + if (val is LocalVariableReferenceValue reference + && locals[reference.LocalDefinition].Value.Any (v => v is ReferenceValue)) { + throw new LinkerFatalErrorException (MessageContainer.CreateCustomErrorMessage ( + $"In method {method.FullName}, local variable {localVariable.Index} references variable {reference.LocalDefinition.Index} which is a reference.", + (int) DiagnosticId.LinkerUnexpectedError, + origin: new MessageOrigin (method, ilOffset))); + } + } + } + } + + protected static void StoreMethodLocalValue ( Dictionary valueCollection, in MultiValue valueToStore, KeyType collectionKey, @@ -207,11 +222,80 @@ private static void StoreMethodLocalValue ( } } - public void Scan (MethodBody methodBody) + // Scans the method as well as any nested functions (local functions or lambdas) and state machines + // reachable from it. + public virtual void InterproceduralScan (MethodBody methodBody) + { + var methodsInGroup = new ValueSet (methodBody.Method); + + // Optimization to prevent multiple scans of a method. + // Eventually we will need to allow re-scanning in some cases, for example + // when we discover new inputs to a method. But we aren't doing dataflow across + // lambdas and local functions yet, so no need for now. + HashSet scannedMethods = new HashSet (); + + while (true) { + if (!TryGetNextMethodToScan (out MethodDefinition? methodToScan)) + break; + + scannedMethods.Add (methodToScan); + Scan (methodToScan.Body, ref methodsInGroup); + + // For state machine methods, also scan the state machine members. + // Simplification: assume that all generated methods of the state machine type are + // invoked at the point where the state machine method is called. + if (CompilerGeneratedState.TryGetStateMachineType (methodToScan, out TypeDefinition? stateMachineType)) { + foreach (var method in stateMachineType.Methods) { + Debug.Assert (!CompilerGeneratedNames.IsLambdaOrLocalFunction (method.Name)); + if (method.Body is MethodBody stateMachineBody) + Scan (stateMachineBody, ref methodsInGroup); + } + } + } + +#if DEBUG + // Validate that the compiler-generated callees tracked by the compiler-generated state + // are the same set of methods that we discovered and scanned above. + if (_context.CompilerGeneratedState.TryGetCompilerGeneratedCalleesForUserMethod (methodBody.Method, out List? compilerGeneratedCallees)) { + var calleeMethods = compilerGeneratedCallees.OfType (); + Debug.Assert (methodsInGroup.Count () == 1 + calleeMethods.Count ()); + foreach (var method in calleeMethods) + Debug.Assert (methodsInGroup.Contains (method)); + } else { + Debug.Assert (methodsInGroup.Count () == 1); + } +#endif + + bool TryGetNextMethodToScan ([NotNullWhen (true)] out MethodDefinition? method) + { + foreach (var candidate in methodsInGroup) { + var candidateMethod = candidate.Method; + if (!scannedMethods.Contains (candidateMethod) && candidateMethod.HasBody) { + method = candidateMethod; + return true; + } + } + method = null; + return false; + } + } + + void TrackNestedFunctionReference (MethodReference referencedMethod, ref ValueSet methodsInGroup) + { + if (_context.TryResolve (referencedMethod) is not MethodDefinition method) + return; + + if (!CompilerGeneratedNames.IsLambdaOrLocalFunction (method.Name)) + return; + + methodsInGroup = MethodLattice.Meet (methodsInGroup, new (method)); + } + + protected virtual void Scan (MethodBody methodBody, ref ValueSet methodsInGroup) { MethodDefinition thisMethod = methodBody.Method; - Dictionary locals = new Dictionary (methodBody.Variables.Count); + LocalVariableStore locals = new (methodBody.Variables.Count); Dictionary> knownStacks = new Dictionary> (); Stack? currentStack = new Stack (methodBody.MaxStackSize); @@ -222,6 +306,7 @@ public void Scan (MethodBody methodBody) ReturnValue = new (); foreach (Instruction operation in methodBody.Instructions) { + ValidateNoReferenceToReference (locals, methodBody.Method, operation.Offset); int curBasicBlock = blockIterator.MoveNext (operation); if (knownStacks.ContainsKey (operation.Offset)) { @@ -315,7 +400,6 @@ public void Scan (MethodBody methodBody) break; case Code.Arglist: - case Code.Ldftn: case Code.Sizeof: case Code.Ldc_I8: case Code.Ldc_R4: @@ -323,6 +407,11 @@ public void Scan (MethodBody methodBody) PushUnknown (currentStack); break; + case Code.Ldftn: + TrackNestedFunctionReference ((MethodReference) operation.Operand, ref methodsInGroup); + PushUnknown (currentStack); + break; + case Code.Ldarg: case Code.Ldarg_0: case Code.Ldarg_1: @@ -486,7 +575,7 @@ public void Scan (MethodBody methodBody) case Code.Stind_R8: case Code.Stind_Ref: case Code.Stobj: - ScanIndirectStore (operation, currentStack, methodBody); + ScanIndirectStore (operation, currentStack, methodBody, locals, curBasicBlock); break; case Code.Initobj: @@ -545,7 +634,8 @@ public void Scan (MethodBody methodBody) case Code.Call: case Code.Callvirt: case Code.Newobj: - HandleCall (methodBody, operation, currentStack, curBasicBlock); + TrackNestedFunctionReference ((MethodReference) operation.Operand, ref methodsInGroup); + HandleCall (methodBody, operation, currentStack, locals, curBasicBlock); break; case Code.Jmp: @@ -580,7 +670,9 @@ public void Scan (MethodBody methodBody) } if (hasReturnValue) { StackSlot retValue = PopUnknown (currentStack, 1, methodBody, operation.Offset); - ReturnValue = MultiValueLattice.Meet (ReturnValue, retValue.Value); + // If the return value is a reference, treat it as the value itself for now + // We can handle ref return values better later + ReturnValue = MultiValueLattice.Meet (ReturnValue, DereferenceValue (retValue.Value, locals)); } ClearStack (ref currentStack); break; @@ -675,12 +767,16 @@ private void ScanLdarg (Instruction operation, Stack currentStack, Me paramNum = paramDefinition.Index; } + // This is semantically wrong if it returns true - we would representing a reference parameter as a reference to a parameter - but it should be fine for now isByRef = paramDefinition.ParameterType.IsByRefOrPointer (); } isByRef |= code == Code.Ldarga || code == Code.Ldarga_S; - StackSlot slot = new StackSlot (GetMethodParameterValue (thisMethod, paramNum), isByRef); + StackSlot slot = new StackSlot ( + isByRef + ? new ParameterReferenceValue (thisMethod, paramNum) + : GetMethodParameterValue (thisMethod, paramNum)); currentStack.Push (slot); } @@ -703,7 +799,7 @@ private void ScanLdloc ( Instruction operation, Stack currentStack, MethodBody methodBody, - Dictionary locals) + LocalVariableStore locals) { VariableDefinition localDef = GetLocalDef (operation, methodBody.Variables); if (localDef == null) { @@ -711,13 +807,16 @@ private void ScanLdloc ( return; } - bool isByRef = operation.OpCode.Code == Code.Ldloca || operation.OpCode.Code == Code.Ldloca_S - || localDef.VariableType.IsByRefOrPointer (); + bool isByRef = operation.OpCode.Code == Code.Ldloca || operation.OpCode.Code == Code.Ldloca_S; - if (!locals.TryGetValue (localDef, out ValueBasicBlockPair localValue)) - currentStack.Push (new StackSlot (UnknownValue.Instance, isByRef)); + StackSlot newSlot; + if (isByRef) { + newSlot = new StackSlot (new LocalVariableReferenceValue (localDef)); + } else if (locals.TryGetValue (localDef, out ValueBasicBlockPair localValue)) + newSlot = new StackSlot (localValue.Value); else - currentStack.Push (new StackSlot (localValue.Value, isByRef)); + newSlot = new StackSlot (UnknownValue.Instance); + currentStack.Push (newSlot); } void ScanLdtoken (Instruction operation, Stack currentStack) @@ -763,7 +862,7 @@ private void ScanStloc ( Instruction operation, Stack currentStack, MethodBody methodBody, - Dictionary locals, + LocalVariableStore locals, int curBasicBlock) { StackSlot valueToStore = PopUnknown (currentStack, 1, methodBody, operation.Offset); @@ -779,16 +878,59 @@ private void ScanStloc ( private void ScanIndirectStore ( Instruction operation, Stack currentStack, - MethodBody methodBody) + MethodBody methodBody, + LocalVariableStore locals, + int curBasicBlock) { StackSlot valueToStore = PopUnknown (currentStack, 1, methodBody, operation.Offset); StackSlot destination = PopUnknown (currentStack, 1, methodBody, operation.Offset); - foreach (var uniqueDestination in destination.Value) { - if (uniqueDestination is FieldValue fieldDestination) { - HandleStoreField (methodBody.Method, fieldDestination, operation, valueToStore.Value); - } else if (uniqueDestination is MethodParameterValue parameterDestination) { - HandleStoreParameter (methodBody.Method, parameterDestination, operation, valueToStore.Value); + StoreInReference (destination.Value, valueToStore.Value, methodBody.Method, operation, locals, curBasicBlock); + } + + /// + /// Handles storing the source value in a target or MultiValue of ReferenceValues. + /// + /// A set of that a value is being stored into + /// The value to store + /// The method body that contains the operation causing the store + /// The instruction causing the store + /// Throws if is not a valid target for an indirect store. + protected void StoreInReference (MultiValue target, MultiValue source, MethodDefinition method, Instruction operation, LocalVariableStore locals, int curBasicBlock) + { + foreach (var value in target) { + switch (value) { + case LocalVariableReferenceValue localReference: + StoreMethodLocalValue (locals, source, localReference.LocalDefinition, curBasicBlock); + break; + case FieldReferenceValue fieldReference + when GetFieldValue (fieldReference.FieldDefinition).AsSingleValue () is FieldValue fieldValue: + HandleStoreField (method, fieldValue, operation, source); + break; + case ParameterReferenceValue parameterReference + when GetMethodParameterValue (parameterReference.MethodDefinition, parameterReference.ParameterIndex) is MethodParameterValue parameterValue: + HandleStoreParameter (method, parameterValue, operation, source); + break; + case ParameterReferenceValue parameterReference + when GetMethodParameterValue (parameterReference.MethodDefinition, parameterReference.ParameterIndex) is MethodThisParameterValue thisParameterValue: + HandleStoreMethodThisParameter (method, thisParameterValue, operation, source); + break; + case MethodReturnValue methodReturnValue: + // Ref returns don't have special ReferenceValue values, so assume if the target here is a MethodReturnValue then it must be a ref return value + HandleStoreMethodReturnValue (method, methodReturnValue, operation, source); + break; + case IValueWithStaticType valueWithStaticType: + if (valueWithStaticType.StaticType is not null && _context.Annotations.FlowAnnotations.IsTypeInterestingForDataflow (valueWithStaticType.StaticType)) + throw new LinkerFatalErrorException (MessageContainer.CreateErrorMessage ( + $"Unhandled StoreReference call. Unhandled attempt to store a value in {value} of type {value.GetType ()}.", + (int) DiagnosticId.LinkerUnexpectedError, + origin: new MessageOrigin (method, operation.Offset))); + // This should only happen for pointer derefs, which can't point to interesting types + break; + default: + // These cases should only be refs to array elements + // References to array elements are not yet tracked and since we don't allow annotations on arrays, they won't cause problems + break; } } @@ -809,7 +951,10 @@ private void ScanLdfld ( FieldDefinition? field = _context.TryResolve ((FieldReference) operation.Operand); if (field != null) { - StackSlot slot = new StackSlot (GetFieldValue (field), isByRef); + MultiValue newValue = isByRef ? + new FieldReferenceValue (field) + : GetFieldValue (field); + StackSlot slot = new (newValue); currentStack.Push (slot); return; } @@ -825,6 +970,14 @@ protected virtual void HandleStoreParameter (MethodDefinition method, MethodPara { } + protected virtual void HandleStoreMethodThisParameter (MethodDefinition method, MethodThisParameterValue thisParameter, Instruction operation, MultiValue sourceValue) + { + } + + protected virtual void HandleStoreMethodReturnValue (MethodDefinition method, MethodReturnValue thisParameter, Instruction operation, MultiValue sourceValue) + { + } + private void ScanStfld ( Instruction operation, Stack currentStack, @@ -887,10 +1040,67 @@ private ValueNodeList PopCallArguments ( return methodParams; } + internal MultiValue DereferenceValue (MultiValue maybeReferenceValue, Dictionary locals) + { + MultiValue dereferencedValue = MultiValueLattice.Top; + foreach (var value in maybeReferenceValue) { + switch (value) { + case FieldReferenceValue fieldReferenceValue: + dereferencedValue = MultiValue.Meet ( + dereferencedValue, + GetFieldValue (fieldReferenceValue.FieldDefinition)); + break; + case ParameterReferenceValue parameterReferenceValue: + dereferencedValue = MultiValue.Meet ( + dereferencedValue, + GetMethodParameterValue (parameterReferenceValue.MethodDefinition, parameterReferenceValue.ParameterIndex)); + break; + case LocalVariableReferenceValue localVariableReferenceValue: + if (locals.TryGetValue (localVariableReferenceValue.LocalDefinition, out var valueBasicBlockPair)) + dereferencedValue = MultiValue.Meet (dereferencedValue, valueBasicBlockPair.Value); + else + dereferencedValue = MultiValue.Meet (dereferencedValue, UnknownValue.Instance); + break; + case ReferenceValue referenceValue: + throw new NotImplementedException ($"Unhandled dereference of ReferenceValue of type {referenceValue.GetType ().FullName}"); + default: + dereferencedValue = MultiValue.Meet (dereferencedValue, value); + break; + } + } + return dereferencedValue; + } + + /// + /// Assigns a MethodParameterValue to the location of each parameter passed by reference. (i.e. assigns the value to x when passing `ref x` as a parameter) + /// + protected void AssignRefAndOutParameters ( + MethodBody callingMethodBody, + MethodReference calledMethod, + ValueNodeList methodArguments, + Instruction operation, + LocalVariableStore locals, + int curBasicBlock) + { + MethodDefinition? calledMethodDefinition = _context.Resolve (calledMethod); + bool methodIsResolved = calledMethodDefinition is not null; + int offset = calledMethod.HasImplicitThis () ? 1 : 0; + int parameterIndex = 0; + for (int ilArgumentIndex = offset; ilArgumentIndex < methodArguments.Count; ilArgumentIndex++, parameterIndex++) { + if (calledMethod.ParameterReferenceKind (ilArgumentIndex) is not (ReferenceKind.Ref or ReferenceKind.Out)) + continue; + SingleValue newByRefValue = methodIsResolved + ? _context.Annotations.FlowAnnotations.GetMethodParameterValue (calledMethodDefinition!, parameterIndex) + : UnknownValue.Instance; + StoreInReference (methodArguments[ilArgumentIndex], newByRefValue, callingMethodBody.Method, operation, locals, curBasicBlock); + } + } + private void HandleCall ( MethodBody callingMethodBody, Instruction operation, Stack currentStack, + LocalVariableStore locals, int curBasicBlock) { MethodReference calledMethod = (MethodReference) operation.Operand; @@ -898,15 +1108,15 @@ private void HandleCall ( bool isNewObj = operation.OpCode.Code == Code.Newobj; SingleValue? newObjValue; - ValueNodeList methodParams = PopCallArguments (currentStack, calledMethod, callingMethodBody, isNewObj, + ValueNodeList methodArguments = PopCallArguments (currentStack, calledMethod, callingMethodBody, isNewObj, operation.Offset, out newObjValue); - + ValueNodeList dereferencedMethodParams = new (methodArguments.Select (param => DereferenceValue (param, locals)).ToList ()); MultiValue methodReturnValue; bool handledFunction = HandleCall ( callingMethodBody, calledMethod, operation, - methodParams, + dereferencedMethodParams, out methodReturnValue); // Handle the return value or newobj result @@ -924,9 +1134,11 @@ private void HandleCall ( } if (isNewObj || !calledMethod.ReturnsVoid ()) - currentStack.Push (new StackSlot (methodReturnValue, calledMethod.ReturnType.IsByRefOrPointer ())); + currentStack.Push (new StackSlot (methodReturnValue)); + + AssignRefAndOutParameters (callingMethodBody, calledMethod, methodArguments, operation, locals, curBasicBlock); - foreach (var param in methodParams) { + foreach (var param in methodArguments) { foreach (var v in param) { if (v is ArrayValue arr) { MarkArrayValuesAsUnknown (arr, curBasicBlock); @@ -991,6 +1203,7 @@ private void ScanLdelem ( PushUnknown (currentStack); return; } + // We don't yet handle arrays of references or pointers bool isByRef = operation.OpCode.Code == Code.Ldelema; int? index = indexToLoadFrom.Value.AsConstInt (); @@ -999,11 +1212,13 @@ private void ScanLdelem ( if (isByRef) { MarkArrayValuesAsUnknown (arr, curBasicBlock); } - return; } - - if (arr.IndexValues.TryGetValue (index.Value, out ValueBasicBlockPair arrayIndexValue)) - currentStack.Push (new StackSlot (arrayIndexValue.Value, isByRef)); + // Don't try to track refs to array elements. Set it as unknown, then push unknown to the stack + else if (isByRef) { + arr.IndexValues[index.Value] = new ValueBasicBlockPair (UnknownValue.Instance, curBasicBlock); + PushUnknown (currentStack); + } else if (arr.IndexValues.TryGetValue (index.Value, out ValueBasicBlockPair arrayIndexValue)) + currentStack.Push (new StackSlot (arrayIndexValue.Value)); else PushUnknown (currentStack); } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/MethodProxy.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/MethodProxy.cs index 6180b2bdb4f20..2b22095662cb7 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/MethodProxy.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/MethodProxy.cs @@ -1,13 +1,14 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using System; using System.Collections.Immutable; using Mono.Cecil; using Mono.Linker; namespace ILLink.Shared.TypeSystemProxy { - readonly partial struct MethodProxy + readonly partial struct MethodProxy : IEquatable { public MethodProxy (MethodDefinition method) => Method = method; @@ -51,5 +52,13 @@ internal partial ImmutableArray GetGenericParameters () internal partial bool ReturnsVoid () => Method.ReturnsVoid (); public override string ToString () => Method.ToString (); + + public ReferenceKind ParameterReferenceKind (int index) => Method.ParameterReferenceKind (index); + + public bool Equals (MethodProxy other) => Method.Equals (other.Method); + + public override bool Equals (object? obj) => obj is MethodProxy other && Equals (other); + + public override int GetHashCode () => Method.GetHashCode (); } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ParameterReferenceValue.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ParameterReferenceValue.cs new file mode 100644 index 0000000000000..7f3804791f999 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ParameterReferenceValue.cs @@ -0,0 +1,16 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +using ILLink.Shared.DataFlow; +using Mono.Cecil; + +namespace ILLink.Shared.TrimAnalysis +{ + public partial record ParameterReferenceValue (MethodDefinition MethodDefinition, int ParameterIndex) +: ReferenceValue + { + public override SingleValue DeepCopy () + { + return this; + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ReferenceValue.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ReferenceValue.cs new file mode 100644 index 0000000000000..4e43a95cdc531 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ReferenceValue.cs @@ -0,0 +1,11 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. +using ILLink.Shared.DataFlow; + +namespace ILLink.Shared.TrimAnalysis +{ + /// + /// Acts as the base class for all values that represent a reference to another value. These should only be held in a ref type or on the stack as a result of a 'load address' instruction (e.g. ldloca). + /// + public abstract record ReferenceValue : SingleValue { } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ReflectionMarker.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ReflectionMarker.cs index 51268fdfac854..be699512ac586 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ReflectionMarker.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ReflectionMarker.cs @@ -2,8 +2,10 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Reflection; +using ILLink.Shared.TrimAnalysis; using Mono.Cecil; using Mono.Linker.Steps; @@ -13,15 +15,20 @@ public readonly struct ReflectionMarker { readonly LinkContext _context; readonly MarkStep _markStep; + readonly bool _enabled; - public ReflectionMarker (LinkContext context, MarkStep markStep) + public ReflectionMarker (LinkContext context, MarkStep markStep, bool enabled) { _context = context; _markStep = markStep; + _enabled = enabled; } internal void MarkTypeForDynamicallyAccessedMembers (in MessageOrigin origin, TypeDefinition typeDefinition, DynamicallyAccessedMemberTypes requiredMemberTypes, DependencyKind dependencyKind, bool declaredOnly = false) { + if (!_enabled) + return; + foreach (var member in typeDefinition.GetDynamicallyAccessedMembers (_context, requiredMemberTypes, declaredOnly)) { switch (member) { case MethodDefinition method: @@ -46,77 +53,146 @@ internal void MarkTypeForDynamicallyAccessedMembers (in MessageOrigin origin, Ty } } - internal bool TryResolveTypeNameAndMark (string typeName, MessageOrigin origin, bool needsAssemblyName, [NotNullWhen (true)] out TypeDefinition? type) + // Resolve a (potentially assembly qualified) type name based on the current context (taken from DiagnosticContext) and mark the type for reflection. + // This method will probe the current context assembly and if that fails CoreLib for the specified type. Emulates behavior of Type.GetType. + internal bool TryResolveTypeNameAndMark (string typeName, in DiagnosticContext diagnosticContext, bool needsAssemblyName, [NotNullWhen (true)] out TypeDefinition? type) + { + if (!_context.TypeNameResolver.TryResolveTypeName (typeName, diagnosticContext, out TypeReference? typeRef, out var typeResolutionRecords, needsAssemblyName) + || typeRef.ResolveToTypeDefinition (_context) is not TypeDefinition foundType) { + type = default; + return false; + } + + MarkResolvedType (diagnosticContext, typeRef, foundType, typeResolutionRecords); + + type = foundType; + return true; + } + + // Resolve a type from the specified assembly and mark it for reflection. + internal bool TryResolveTypeNameAndMark (AssemblyDefinition assembly, string typeName, in DiagnosticContext diagnosticContext, [NotNullWhen (true)] out TypeDefinition? type) { - if (!_context.TypeNameResolver.TryResolveTypeName (typeName, origin.Provider, out TypeReference? typeRef, out AssemblyDefinition? typeAssembly, needsAssemblyName) + if (!_context.TypeNameResolver.TryResolveTypeName (assembly, typeName, out TypeReference? typeRef, out var typeResolutionRecords) || typeRef.ResolveToTypeDefinition (_context) is not TypeDefinition foundType) { type = default; return false; } - _markStep.MarkTypeVisibleToReflection (typeRef, foundType, new DependencyInfo (DependencyKind.AccessedViaReflection, origin.Provider), origin); - _context.MarkingHelpers.MarkMatchingExportedType (foundType, typeAssembly, new DependencyInfo (DependencyKind.DynamicallyAccessedMember, foundType), origin); + MarkResolvedType (diagnosticContext, typeRef, foundType, typeResolutionRecords); type = foundType; return true; } + void MarkResolvedType ( + in DiagnosticContext diagnosticContext, + TypeReference typeReference, + TypeDefinition typeDefinition, + List typeResolutionRecords) + { + if (_enabled) { + // Mark the resolved type for reflection access, but also go over all types which were resolved in the process + // of resolving the outer type (typically generic arguments) and make sure we mark all type forwarders + // used for that resolution. + // This is necessary because if the app's code contains the input string as literal (which is pretty much always the case) + // that string has to work at runtime, and if it relies on type forwarders we need to preserve those as well. + var origin = diagnosticContext.Origin; + _markStep.MarkTypeVisibleToReflection (typeReference, typeDefinition, new DependencyInfo (DependencyKind.AccessedViaReflection, origin.Provider), origin); + foreach (var typeResolutionRecord in typeResolutionRecords) { + _context.MarkingHelpers.MarkMatchingExportedType (typeResolutionRecord.ResolvedType, typeResolutionRecord.ReferringAssembly, new DependencyInfo (DependencyKind.DynamicallyAccessedMember, typeDefinition), origin); + } + } + } + internal void MarkType (in MessageOrigin origin, TypeDefinition type, DependencyKind dependencyKind = DependencyKind.AccessedViaReflection) { + if (!_enabled) + return; + _markStep.MarkTypeVisibleToReflection (type, type, new DependencyInfo (dependencyKind, origin.Provider), origin); } internal void MarkMethod (in MessageOrigin origin, MethodDefinition method, DependencyKind dependencyKind = DependencyKind.AccessedViaReflection) { + if (!_enabled) + return; + _markStep.MarkMethodVisibleToReflection (method, new DependencyInfo (dependencyKind, origin.Provider), origin); } void MarkField (in MessageOrigin origin, FieldDefinition field, DependencyKind dependencyKind = DependencyKind.AccessedViaReflection) { + if (!_enabled) + return; + _markStep.MarkFieldVisibleToReflection (field, new DependencyInfo (dependencyKind, origin.Provider), origin); } internal void MarkProperty (in MessageOrigin origin, PropertyDefinition property, DependencyKind dependencyKind = DependencyKind.AccessedViaReflection) { + if (!_enabled) + return; + _markStep.MarkPropertyVisibleToReflection (property, new DependencyInfo (dependencyKind, origin.Provider), origin); } void MarkEvent (in MessageOrigin origin, EventDefinition @event, DependencyKind dependencyKind = DependencyKind.AccessedViaReflection) { + if (!_enabled) + return; + _markStep.MarkEventVisibleToReflection (@event, new DependencyInfo (dependencyKind, origin.Provider), origin); } void MarkInterfaceImplementation (in MessageOrigin origin, InterfaceImplementation interfaceImplementation, DependencyKind dependencyKind = DependencyKind.AccessedViaReflection) { + if (!_enabled) + return; + _markStep.MarkInterfaceImplementation (interfaceImplementation, null, new DependencyInfo (dependencyKind, origin.Provider)); } internal void MarkConstructorsOnType (in MessageOrigin origin, TypeDefinition type, Func? filter, BindingFlags? bindingFlags = null) { + if (!_enabled) + return; + foreach (var ctor in type.GetConstructorsOnType (filter, bindingFlags)) MarkMethod (origin, ctor); } internal void MarkFieldsOnTypeHierarchy (in MessageOrigin origin, TypeDefinition type, Func filter, BindingFlags? bindingFlags = BindingFlags.Default) { + if (!_enabled) + return; + foreach (var field in type.GetFieldsOnTypeHierarchy (_context, filter, bindingFlags)) MarkField (origin, field); } internal void MarkPropertiesOnTypeHierarchy (in MessageOrigin origin, TypeDefinition type, Func filter, BindingFlags? bindingFlags = BindingFlags.Default) { + if (!_enabled) + return; + foreach (var property in type.GetPropertiesOnTypeHierarchy (_context, filter, bindingFlags)) MarkProperty (origin, property); } internal void MarkEventsOnTypeHierarchy (in MessageOrigin origin, TypeDefinition type, Func filter, BindingFlags? bindingFlags = BindingFlags.Default) { + if (!_enabled) + return; + foreach (var @event in type.GetEventsOnTypeHierarchy (_context, filter, bindingFlags)) MarkEvent (origin, @event); } internal void MarkStaticConstructor (in MessageOrigin origin, TypeDefinition type) { + if (!_enabled) + return; + _markStep.MarkStaticConstructorVisibleToReflection (type, new DependencyInfo (DependencyKind.AccessedViaReflection, origin.Provider), origin); } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ReflectionMethodBodyScanner.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ReflectionMethodBodyScanner.cs index ee9cd02264cb3..7138685726dbf 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ReflectionMethodBodyScanner.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ReflectionMethodBodyScanner.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; -using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; @@ -24,6 +23,7 @@ class ReflectionMethodBodyScanner : MethodBodyScanner MessageOrigin _origin; readonly FlowAnnotations _annotations; readonly ReflectionMarker _reflectionMarker; + public readonly TrimAnalysisPatternStore TrimAnalysisPatterns; public static bool RequiresReflectionMethodBodyScannerForCallSite (LinkContext context, MethodReference calledMethod) { @@ -52,117 +52,34 @@ public static bool RequiresReflectionMethodBodyScannerForAccess (LinkContext con return context.Annotations.FlowAnnotations.RequiresDataFlowAnalysis (fieldDefinition); } - bool ShouldEnableReflectionPatternReporting (ICustomAttributeProvider? provider) - { - if (_markStep.ShouldSuppressAnalysisWarningsForRequiresUnreferencedCode (provider)) - return false; - - return true; - } - public ReflectionMethodBodyScanner (LinkContext context, MarkStep parent, MessageOrigin origin) : base (context) { _markStep = parent; _origin = origin; _annotations = context.Annotations.FlowAnnotations; - _reflectionMarker = new ReflectionMarker (context, parent); + _reflectionMarker = new ReflectionMarker (context, parent, enabled: false); + TrimAnalysisPatterns = new TrimAnalysisPatternStore (context); } - public void ScanAndProcessReturnValue (MethodBody methodBody) + public override void InterproceduralScan (MethodBody methodBody) { - Scan (methodBody); - - if (!methodBody.Method.ReturnsVoid ()) { - var method = methodBody.Method; - var methodReturnValue = _annotations.GetMethodReturnValue (method); - if (methodReturnValue.DynamicallyAccessedMemberTypes != 0) { - var diagnosticContext = new DiagnosticContext (_origin, ShouldEnableReflectionPatternReporting (_origin.Provider), _context); - RequireDynamicallyAccessedMembers (diagnosticContext, ReturnValue, methodReturnValue); - } - } - } + base.InterproceduralScan (methodBody); - public void ProcessAttributeDataflow (MethodDefinition method, IList arguments) - { - for (int i = 0; i < method.Parameters.Count; i++) { - var parameterValue = _annotations.GetMethodParameterValue (method, i); - if (parameterValue.DynamicallyAccessedMemberTypes != DynamicallyAccessedMemberTypes.None) { - MultiValue value = GetValueNodeForCustomAttributeArgument (arguments[i]); - var diagnosticContext = new DiagnosticContext (_origin, diagnosticsEnabled: true, _context); - RequireDynamicallyAccessedMembers (diagnosticContext, value, parameterValue); - } - } + var reflectionMarker = new ReflectionMarker (_context, _markStep, enabled: true); + TrimAnalysisPatterns.MarkAndProduceDiagnostics (reflectionMarker, _markStep); } - public void ProcessAttributeDataflow (FieldDefinition field, CustomAttributeArgument value) + protected override void Scan (MethodBody methodBody, ref ValueSet methodsInGroup) { - MultiValue valueNode = GetValueNodeForCustomAttributeArgument (value); - foreach (var fieldValueCandidate in GetFieldValue (field)) { - if (fieldValueCandidate is not ValueWithDynamicallyAccessedMembers fieldValue) - continue; + _origin = new MessageOrigin (methodBody.Method); + base.Scan (methodBody, ref methodsInGroup); - var diagnosticContext = new DiagnosticContext (_origin, diagnosticsEnabled: true, _context); - RequireDynamicallyAccessedMembers (diagnosticContext, valueNode, fieldValue); - } - } - - MultiValue GetValueNodeForCustomAttributeArgument (CustomAttributeArgument argument) - { - SingleValue value; - if (argument.Type.Name == "Type") { - TypeDefinition? referencedType = ResolveToTypeDefinition ((TypeReference) argument.Value); - if (referencedType == null) - value = UnknownValue.Instance; - else - value = new SystemTypeValue (referencedType); - } else if (argument.Type.MetadataType == MetadataType.String) { - value = new KnownStringValue ((string) argument.Value); - } else { - // We shouldn't have gotten a non-null annotation for this from GetParameterAnnotation - throw new InvalidOperationException (); - } - - Debug.Assert (value != null); - return value; - } - - public void ProcessGenericArgumentDataFlow (GenericParameter genericParameter, TypeReference genericArgument) - { - var genericParameterValue = _annotations.GetGenericParameterValue (genericParameter); - Debug.Assert (genericParameterValue.DynamicallyAccessedMemberTypes != DynamicallyAccessedMemberTypes.None); - - MultiValue genericArgumentValue = GetTypeValueNodeFromGenericArgument (genericArgument); - - var diagnosticContext = new DiagnosticContext (_origin, ShouldEnableReflectionPatternReporting (_origin.Provider), _context); - RequireDynamicallyAccessedMembers (diagnosticContext, genericArgumentValue, genericParameterValue); - } - - MultiValue GetTypeValueNodeFromGenericArgument (TypeReference genericArgument) - { - if (genericArgument is GenericParameter inputGenericParameter) { - // Technically this should be a new value node type as it's not a System.Type instance representation, but just the generic parameter - // That said we only use it to perform the dynamically accessed members checks and for that purpose treating it as System.Type is perfectly valid. - return _annotations.GetGenericParameterValue (inputGenericParameter); - } else if (ResolveToTypeDefinition (genericArgument) is TypeDefinition genericArgumentType) { - if (genericArgumentType.IsTypeOf (WellKnownType.System_Nullable_T)) { - var innerGenericArgument = (genericArgument as IGenericInstance)?.GenericArguments.FirstOrDefault (); - switch (innerGenericArgument) { - case GenericParameter gp: - return new NullableValueWithDynamicallyAccessedMembers (genericArgumentType, - new GenericParameterValue (gp, _context.Annotations.FlowAnnotations.GetGenericParameterAnnotation (gp))); - - case TypeReference underlyingType: - if (ResolveToTypeDefinition (underlyingType) is TypeDefinition underlyingTypeDefinition) - return new NullableSystemTypeValue (genericArgumentType, new SystemTypeValue (underlyingTypeDefinition)); - else - return UnknownValue.Instance; - } - } - // All values except for Nullable, including Nullable<> (with no type arguments) - return new SystemTypeValue (genericArgumentType); - } else { - return UnknownValue.Instance; + if (!methodBody.Method.ReturnsVoid ()) { + var method = methodBody.Method; + var methodReturnValue = _annotations.GetMethodReturnValue (method); + if (methodReturnValue.DynamicallyAccessedMemberTypes != 0) + HandleAssignmentPattern (_origin, ReturnValue, methodReturnValue); } } @@ -192,63 +109,98 @@ ValueWithDynamicallyAccessedMembers GetMethodParameterValue (MethodDefinition me return _annotations.GetMethodParameterValue (method, parameterIndex, dynamicallyAccessedMemberTypes); } - protected override MultiValue GetFieldValue (FieldDefinition field) - { - switch (field.Name) { - case "EmptyTypes" when field.DeclaringType.IsTypeOf (WellKnownType.System_Type): { - return ArrayValue.Create (0, field.DeclaringType); - } - case "Empty" when field.DeclaringType.IsTypeOf (WellKnownType.System_String): { - return new KnownStringValue (string.Empty); - } - - default: { - DynamicallyAccessedMemberTypes memberTypes = _context.Annotations.FlowAnnotations.GetFieldAnnotation (field); - return new FieldValue (ResolveToTypeDefinition (field.FieldType), field, memberTypes); - } - } - } + protected override MultiValue GetFieldValue (FieldDefinition field) => _annotations.GetFieldValue (field); - protected override void HandleStoreField (MethodDefinition method, FieldValue field, Instruction operation, MultiValue valueToStore) + private void HandleStoreValueWithDynamicallyAccessedMembers (ValueWithDynamicallyAccessedMembers targetValue, Instruction operation, MultiValue sourceValue) { - if (field.DynamicallyAccessedMemberTypes != 0) { + if (targetValue.DynamicallyAccessedMemberTypes != 0) { _origin = _origin.WithInstructionOffset (operation.Offset); - var diagnosticContext = new DiagnosticContext (_origin, ShouldEnableReflectionPatternReporting (_origin.Provider), _context); - RequireDynamicallyAccessedMembers (diagnosticContext, valueToStore, field); + HandleAssignmentPattern (_origin, sourceValue, targetValue); } } + protected override void HandleStoreField (MethodDefinition method, FieldValue field, Instruction operation, MultiValue valueToStore) + => HandleStoreValueWithDynamicallyAccessedMembers (field, operation, valueToStore); + protected override void HandleStoreParameter (MethodDefinition method, MethodParameterValue parameter, Instruction operation, MultiValue valueToStore) - { - if (parameter.DynamicallyAccessedMemberTypes != 0) { - _origin = _origin.WithInstructionOffset (operation.Offset); - var diagnosticContext = new DiagnosticContext (_origin, ShouldEnableReflectionPatternReporting (_origin.Provider), _context); - RequireDynamicallyAccessedMembers (diagnosticContext, valueToStore, parameter); - } - } + => HandleStoreValueWithDynamicallyAccessedMembers (parameter, operation, valueToStore); + + protected override void HandleStoreMethodThisParameter (MethodDefinition method, MethodThisParameterValue thisParameter, Instruction operation, MultiValue valueToStore) + => HandleStoreValueWithDynamicallyAccessedMembers (thisParameter, operation, valueToStore); + + protected override void HandleStoreMethodReturnValue (MethodDefinition method, MethodReturnValue returnValue, Instruction operation, MultiValue valueToStore) + => HandleStoreValueWithDynamicallyAccessedMembers (returnValue, operation, valueToStore); public override bool HandleCall (MethodBody callingMethodBody, MethodReference calledMethod, Instruction operation, ValueNodeList methodParams, out MultiValue methodReturnValue) { methodReturnValue = new (); - MultiValue? maybeMethodReturnValue = null; var reflectionProcessed = _markStep.ProcessReflectionDependency (callingMethodBody, operation); if (reflectionProcessed) return false; - var callingMethodDefinition = callingMethodBody.Method; + Debug.Assert (callingMethodBody.Method == _origin.Provider); var calledMethodDefinition = _context.TryResolve (calledMethod); if (calledMethodDefinition == null) return false; - bool requiresDataFlowAnalysis = _context.Annotations.FlowAnnotations.RequiresDataFlowAnalysis (calledMethodDefinition); - DynamicallyAccessedMemberTypes returnValueDynamicallyAccessedMemberTypes = requiresDataFlowAnalysis ? - _context.Annotations.FlowAnnotations.GetReturnParameterAnnotation (calledMethodDefinition) : 0; - _origin = _origin.WithInstructionOffset (operation.Offset); - bool diagnosticsEnabled = ShouldEnableReflectionPatternReporting (_origin.Provider); - var diagnosticContext = new DiagnosticContext (_origin, diagnosticsEnabled, _context); - var handleCallAction = new HandleCallAction (_context, _reflectionMarker, diagnosticContext, callingMethodDefinition); + + MultiValue instanceValue; + ImmutableArray arguments; + if (calledMethodDefinition.HasImplicitThis ()) { + instanceValue = methodParams[0]; + arguments = methodParams.Skip (1).ToImmutableArray (); + } else { + instanceValue = MultiValueLattice.Top; + arguments = methodParams.ToImmutableArray (); + } + + TrimAnalysisPatterns.Add (new TrimAnalysisMethodCallPattern ( + operation, + calledMethod, + instanceValue, + arguments, + _origin + )); + + var diagnosticContext = new DiagnosticContext (_origin, diagnosticsEnabled: false, _context); + return HandleCall ( + operation, + calledMethod, + instanceValue, + arguments, + diagnosticContext, + _reflectionMarker, + _context, + _markStep, + out methodReturnValue); + } + + public static bool HandleCall ( + Instruction operation, + MethodReference calledMethod, + MultiValue instanceValue, + ImmutableArray argumentValues, + DiagnosticContext diagnosticContext, + ReflectionMarker reflectionMarker, + LinkContext context, + MarkStep markStep, + out MultiValue methodReturnValue) + { + var origin = diagnosticContext.Origin; + var calledMethodDefinition = context.TryResolve (calledMethod); + Debug.Assert (calledMethodDefinition != null); + var callingMethodDefinition = origin.Provider as MethodDefinition; + Debug.Assert (callingMethodDefinition != null); + + bool requiresDataFlowAnalysis = context.Annotations.FlowAnnotations.RequiresDataFlowAnalysis (calledMethodDefinition); + var annotatedMethodReturnValue = context.Annotations.FlowAnnotations.GetMethodReturnValue (calledMethodDefinition); + Debug.Assert (requiresDataFlowAnalysis || annotatedMethodReturnValue.DynamicallyAccessedMemberTypes == DynamicallyAccessedMemberTypes.None); + + MultiValue? maybeMethodReturnValue = null; + + var handleCallAction = new HandleCallAction (context, reflectionMarker, diagnosticContext, callingMethodDefinition); switch (Intrinsics.GetIntrinsicIdForMethod (calledMethodDefinition)) { case IntrinsicId.IntrospectionExtensions_GetTypeInfo: case IntrinsicId.TypeInfo_AsType: @@ -294,42 +246,31 @@ public override bool HandleCall (MethodBody callingMethodBody, MethodReference c || appDomainCreateInstance == IntrinsicId.AppDomain_CreateInstanceFrom || appDomainCreateInstance == IntrinsicId.AppDomain_CreateInstanceFromAndUnwrap: case IntrinsicId.Assembly_CreateInstance: { - var instanceValue = MultiValueLattice.Top; - IReadOnlyList parameterValues = methodParams; - if (calledMethodDefinition.HasImplicitThis ()) { - instanceValue = methodParams[0]; - parameterValues = parameterValues.Skip (1).ToImmutableList (); - } - return handleCallAction.Invoke (calledMethodDefinition, instanceValue, parameterValues, out methodReturnValue, out _); + return handleCallAction.Invoke (calledMethodDefinition, instanceValue, argumentValues, out methodReturnValue, out _); } case IntrinsicId.None: { if (calledMethodDefinition.IsPInvokeImpl) { // Is the PInvoke dangerous? - bool comDangerousMethod = IsComInterop (calledMethodDefinition.MethodReturnType, calledMethodDefinition.ReturnType); + bool comDangerousMethod = IsComInterop (calledMethodDefinition.MethodReturnType, calledMethodDefinition.ReturnType, context); foreach (ParameterDefinition pd in calledMethodDefinition.Parameters) { - comDangerousMethod |= IsComInterop (pd, pd.ParameterType); + comDangerousMethod |= IsComInterop (pd, pd.ParameterType, context); } if (comDangerousMethod) { diagnosticContext.AddDiagnostic (DiagnosticId.CorrectnessOfCOMCannotBeGuaranteed, calledMethodDefinition.GetDisplayName ()); } } - _markStep.CheckAndReportRequiresUnreferencedCode (calledMethodDefinition, _origin); + if (context.Annotations.DoesMethodRequireUnreferencedCode (calledMethodDefinition, out RequiresUnreferencedCodeAttribute? requiresUnreferencedCode)) + MarkStep.ReportRequiresUnreferencedCode (calledMethodDefinition.GetDisplayName (), requiresUnreferencedCode, diagnosticContext); - var instanceValue = MultiValueLattice.Top; - IReadOnlyList parameterValues = methodParams; - if (calledMethodDefinition.HasImplicitThis ()) { - instanceValue = methodParams[0]; - parameterValues = parameterValues.Skip (1).ToImmutableList (); - } - return handleCallAction.Invoke (calledMethodDefinition, instanceValue, parameterValues, out methodReturnValue, out _); + return handleCallAction.Invoke (calledMethodDefinition, instanceValue, argumentValues, out methodReturnValue, out _); } case IntrinsicId.TypeDelegator_Ctor: { // This is an identity function for analysis purposes if (operation.OpCode == OpCodes.Newobj) - AddReturnValue (methodParams[1]); + AddReturnValue (argumentValues[0]); } break; @@ -338,13 +279,22 @@ public override bool HandleCall (MethodBody callingMethodBody, MethodReference c } break; + case IntrinsicId.Enum_GetValues: + case IntrinsicId.Marshal_SizeOf: + case IntrinsicId.Marshal_OffsetOf: + case IntrinsicId.Marshal_PtrToStructure: + case IntrinsicId.Marshal_DestroyStructure: + case IntrinsicId.Marshal_GetDelegateForFunctionPointer: + // These intrinsics are not interesting for trimmer (they are interesting for AOT and that's why they are recognized) + break; + // // System.Object // // GetType() // case IntrinsicId.Object_GetType: { - foreach (var valueNode in methodParams[0]) { + foreach (var valueNode in instanceValue) { // Note that valueNode can be statically typed in IL as some generic argument type. // For example: // void Method(T instance) { instance.GetType().... } @@ -363,7 +313,7 @@ public override bool HandleCall (MethodBody callingMethodBody, MethodReference c TypeDefinition? staticType = (valueNode as IValueWithStaticType)?.StaticType; if (staticType is null) { // We don't know anything about the type GetType was called on. Track this as a usual result of a method call without any annotations - AddReturnValue (_annotations.GetMethodReturnValue (calledMethodDefinition)); + AddReturnValue (context.Annotations.FlowAnnotations.GetMethodReturnValue (calledMethodDefinition)); } else if (staticType.IsSealed || staticType.IsTypeOf ("System", "Delegate")) { // We can treat this one the same as if it was a typeof() expression @@ -382,15 +332,15 @@ public override bool HandleCall (MethodBody callingMethodBody, MethodReference c } else { // Make sure the type is marked (this will mark it as used via reflection, which is sort of true) // This should already be true for most cases (method params, fields, ...), but just in case - _reflectionMarker.MarkType (_origin, staticType); + reflectionMarker.MarkType (origin, staticType); - var annotation = _markStep.DynamicallyAccessedMembersTypeHierarchy - .ApplyDynamicallyAccessedMembersToTypeHierarchy (_reflectionMarker, staticType); + var annotation = markStep.DynamicallyAccessedMembersTypeHierarchy + .ApplyDynamicallyAccessedMembersToTypeHierarchy (staticType); // Return a value which is "unknown type" with annotation. For now we'll use the return value node // for the method, which means we're loosing the information about which staticType this // started with. For now we don't need it, but we can add it later on. - AddReturnValue (_annotations.GetMethodReturnValue (calledMethodDefinition, annotation)); + AddReturnValue (context.Annotations.FlowAnnotations.GetMethodReturnValue (calledMethodDefinition, annotation)); } } } @@ -414,13 +364,13 @@ public override bool HandleCall (MethodBody callingMethodBody, MethodReference c bool returnsVoid = calledMethod.ReturnsVoid (); methodReturnValue = maybeMethodReturnValue ?? (returnsVoid ? MultiValueLattice.Top : - _annotations.GetMethodReturnValue (calledMethodDefinition, returnValueDynamicallyAccessedMemberTypes)); + annotatedMethodReturnValue); // Validate that the return value has the correct annotations as per the method return value annotations - if (returnValueDynamicallyAccessedMemberTypes != 0) { + if (annotatedMethodReturnValue.DynamicallyAccessedMemberTypes != 0) { foreach (var uniqueValue in methodReturnValue) { if (uniqueValue is ValueWithDynamicallyAccessedMembers methodReturnValueWithMemberTypes) { - if (!methodReturnValueWithMemberTypes.DynamicallyAccessedMemberTypes.HasFlag (returnValueDynamicallyAccessedMemberTypes)) + if (!methodReturnValueWithMemberTypes.DynamicallyAccessedMemberTypes.HasFlag (annotatedMethodReturnValue.DynamicallyAccessedMemberTypes)) throw new InvalidOperationException ($"Internal linker error: processing of call from {callingMethodDefinition.GetDisplayName ()} to {calledMethod.GetDisplayName ()} returned value which is not correctly annotated with the expected dynamic member access kinds."); } else if (uniqueValue is SystemTypeValue) { // SystemTypeValue can fullfill any requirement, so it's always valid @@ -439,7 +389,7 @@ void AddReturnValue (MultiValue value) } } - bool IsComInterop (IMarshalInfoProvider marshalInfoProvider, TypeReference parameterType) + static bool IsComInterop (IMarshalInfoProvider marshalInfoProvider, TypeReference parameterType, LinkContext context) { // This is best effort. One can likely find ways how to get COM without triggering these alarms. // AsAny marshalling of a struct with an object-typed field would be one, for example. @@ -460,7 +410,7 @@ bool IsComInterop (IMarshalInfoProvider marshalInfoProvider, TypeReference param if (nativeType == NativeType.None) { // Resolve will look at the element type - var parameterTypeDef = _context.TryResolve (parameterType); + var parameterTypeDef = context.TryResolve (parameterType); if (parameterTypeDef != null) { if (parameterTypeDef.IsTypeOf (WellKnownType.System_Array)) { @@ -481,10 +431,10 @@ bool IsComInterop (IMarshalInfoProvider marshalInfoProvider, TypeReference param } else if (parameterTypeDef.IsMulticastDelegate ()) { // Delegates are special cased by interop return false; - } else if (parameterTypeDef.IsSubclassOf ("System.Runtime.InteropServices", "CriticalHandle", _context)) { + } else if (parameterTypeDef.IsSubclassOf ("System.Runtime.InteropServices", "CriticalHandle", context)) { // Subclasses of CriticalHandle are special cased by interop return false; - } else if (parameterTypeDef.IsSubclassOf ("System.Runtime.InteropServices", "SafeHandle", _context)) { + } else if (parameterTypeDef.IsSubclassOf ("System.Runtime.InteropServices", "SafeHandle", context)) { // Subclasses of SafeHandle are special cased by interop return false; } else if (!parameterTypeDef.IsSequentialLayout && !parameterTypeDef.IsExplicitLayout) { @@ -497,10 +447,12 @@ bool IsComInterop (IMarshalInfoProvider marshalInfoProvider, TypeReference param return false; } - void RequireDynamicallyAccessedMembers (in DiagnosticContext diagnosticContext, in MultiValue value, ValueWithDynamicallyAccessedMembers targetValue) + void HandleAssignmentPattern ( + in MessageOrigin origin, + in MultiValue value, + ValueWithDynamicallyAccessedMembers targetValue) { - var requireDynamicallyAccessedMembersAction = new RequireDynamicallyAccessedMembersAction (_context, _reflectionMarker, diagnosticContext); - requireDynamicallyAccessedMembersAction.Invoke (value, targetValue); + TrimAnalysisPatterns.Add (new TrimAnalysisAssignmentPattern (value, targetValue, origin)); } } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/RequireDynamicallyAccessedMembersAction.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/RequireDynamicallyAccessedMembersAction.cs index d2fe33783b6ee..5f1f8ee54db7b 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/RequireDynamicallyAccessedMembersAction.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/RequireDynamicallyAccessedMembersAction.cs @@ -11,22 +11,19 @@ namespace ILLink.Shared.TrimAnalysis { partial struct RequireDynamicallyAccessedMembersAction { - readonly LinkContext _context; readonly ReflectionMarker _reflectionMarker; public RequireDynamicallyAccessedMembersAction ( - LinkContext context, ReflectionMarker reflectionMarker, in DiagnosticContext diagnosticContext) { - _context = context; _reflectionMarker = reflectionMarker; _diagnosticContext = diagnosticContext; } public partial bool TryResolveTypeNameAndMark (string typeName, bool needsAssemblyName, out TypeProxy type) { - if (_reflectionMarker.TryResolveTypeNameAndMark (typeName, _diagnosticContext.Origin, needsAssemblyName, out TypeDefinition? foundType)) { + if (_reflectionMarker.TryResolveTypeNameAndMark (typeName, _diagnosticContext, needsAssemblyName, out TypeDefinition? foundType)) { type = new (foundType); return true; } else { diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ScannerExtensions.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ScannerExtensions.cs index 166a968b66aed..c445f7d61c209 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ScannerExtensions.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ScannerExtensions.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using Mono.Cecil; using Mono.Cecil.Cil; namespace Mono.Linker.Dataflow @@ -40,11 +39,6 @@ public static HashSet ComputeBranchTargets (this MethodBody methodBody) } return branchTargets; } - - public static bool IsByRefOrPointer (this TypeReference typeRef) - { - return typeRef.IsByReference || typeRef.IsPointer; - } } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/TrimAnalysisAssignmentPattern.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/TrimAnalysisAssignmentPattern.cs new file mode 100644 index 0000000000000..401330e558a20 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/TrimAnalysisAssignmentPattern.cs @@ -0,0 +1,39 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using ILLink.Shared.TrimAnalysis; +using MultiValue = ILLink.Shared.DataFlow.ValueSet; + +namespace Mono.Linker.Dataflow +{ + public readonly record struct TrimAnalysisAssignmentPattern + { + public MultiValue Source { init; get; } + public MultiValue Target { init; get; } + public MessageOrigin Origin { init; get; } + + public TrimAnalysisAssignmentPattern (MultiValue source, MultiValue target, MessageOrigin origin) + { + Source = source.Clone (); + Target = target.Clone (); + Origin = origin; + } + + public void MarkAndProduceDiagnostics (ReflectionMarker reflectionMarker, LinkContext context) + { + bool diagnosticsEnabled = !context.Annotations.ShouldSuppressAnalysisWarningsForRequiresUnreferencedCode (Origin.Provider); + var diagnosticContext = new DiagnosticContext (Origin, diagnosticsEnabled, context); + + foreach (var sourceValue in Source) { + foreach (var targetValue in Target) { + if (targetValue is not ValueWithDynamicallyAccessedMembers targetWithDynamicallyAccessedMembers) + throw new NotImplementedException (); + + var requireDynamicallyAccessedMembersAction = new RequireDynamicallyAccessedMembersAction (reflectionMarker, diagnosticContext); + requireDynamicallyAccessedMembersAction.Invoke (sourceValue, targetWithDynamicallyAccessedMembers); + } + } + } + } +} \ No newline at end of file diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/TrimAnalysisMethodCallPattern.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/TrimAnalysisMethodCallPattern.cs new file mode 100644 index 0000000000000..1f71d7e4948dd --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/TrimAnalysisMethodCallPattern.cs @@ -0,0 +1,57 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Immutable; +using System.Diagnostics; +using ILLink.Shared.TrimAnalysis; +using Mono.Cecil; +using Mono.Cecil.Cil; +using Mono.Linker.Steps; + +using MultiValue = ILLink.Shared.DataFlow.ValueSet; + +namespace Mono.Linker.Dataflow +{ + public readonly record struct TrimAnalysisMethodCallPattern + { + public readonly Instruction Operation; + public readonly MethodReference CalledMethod; + public readonly MultiValue Instance; + public readonly ImmutableArray Arguments; + public readonly MessageOrigin Origin; + + public TrimAnalysisMethodCallPattern ( + Instruction operation, + MethodReference calledMethod, + MultiValue instance, + ImmutableArray arguments, + MessageOrigin origin) + { + Debug.Assert (origin.Provider is MethodDefinition); + Operation = operation; + CalledMethod = calledMethod; + Instance = instance.Clone (); + if (arguments.IsEmpty) { + Arguments = ImmutableArray.Empty; + } else { + var builder = ImmutableArray.CreateBuilder (); + foreach (var argument in arguments) + builder.Add (argument.Clone ()); + Arguments = builder.ToImmutableArray (); + } + Origin = origin; + } + + public void MarkAndProduceDiagnostics (ReflectionMarker reflectionMarker, MarkStep markStep, LinkContext context) + { + bool diagnosticsEnabled = !context.Annotations.ShouldSuppressAnalysisWarningsForRequiresUnreferencedCode (Origin.Provider); + var diagnosticContext = new DiagnosticContext (Origin, diagnosticsEnabled, context); + ReflectionMethodBodyScanner.HandleCall (Operation, CalledMethod, Instance, Arguments, + diagnosticContext, + reflectionMarker, + context, + markStep, + out MultiValue _); + } + } +} \ No newline at end of file diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/TrimAnalysisPatternStore.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/TrimAnalysisPatternStore.cs new file mode 100644 index 0000000000000..2e7796ef385da --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/TrimAnalysisPatternStore.cs @@ -0,0 +1,47 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using ILLink.Shared.TrimAnalysis; +using Mono.Linker.Steps; + +namespace Mono.Linker.Dataflow +{ + public readonly struct TrimAnalysisPatternStore + { + readonly Dictionary<(MessageOrigin, bool), TrimAnalysisAssignmentPattern> AssignmentPatterns; + readonly Dictionary MethodCallPatterns; + readonly LinkContext _context; + + public TrimAnalysisPatternStore (LinkContext context) + { + AssignmentPatterns = new Dictionary<(MessageOrigin, bool), TrimAnalysisAssignmentPattern> (); + MethodCallPatterns = new Dictionary (); + _context = context; + } + + public void Add (TrimAnalysisAssignmentPattern pattern) + { + // In the linker, each pattern should have a unique origin (which has ILOffset) + // but we don't track the correct ILOffset for return instructions. + // https://github.com/dotnet/linker/issues/2778 + // For now, work around it with a separate bit. + bool isReturnValue = pattern.Target.AsSingleValue () is MethodReturnValue; + AssignmentPatterns.Add ((pattern.Origin, isReturnValue), pattern); + } + + public void Add (TrimAnalysisMethodCallPattern pattern) + { + MethodCallPatterns.Add (pattern.Origin, pattern); + } + + public void MarkAndProduceDiagnostics (ReflectionMarker reflectionMarker, MarkStep markStep) + { + foreach (var pattern in AssignmentPatterns.Values) + pattern.MarkAndProduceDiagnostics (reflectionMarker, _context); + + foreach (var pattern in MethodCallPatterns.Values) + pattern.MarkAndProduceDiagnostics (reflectionMarker, markStep, _context); + } + } +} \ No newline at end of file diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceValue.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceValue.cs new file mode 100644 index 0000000000000..d8bd9b0f6b05b --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceValue.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using ILLink.Shared.DataFlow; + +#nullable enabled + +namespace ILLink.Shared.TrimAnalysis +{ + /// + /// Acts as the base class for all values that represent a reference to another value. These should only be held in a ref type or on the stack as a result of a 'load address' instruction (e.g. ldloca). + /// + public abstract record ReferenceValue : SingleValue { } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMarker.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMarker.cs index 70f3d523ed9e4..a911cfb160e6a 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMarker.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMarker.cs @@ -17,27 +17,32 @@ namespace ILCompiler.Dataflow { - internal class ReflectionMarker + public class ReflectionMarker { private DependencyList _dependencies = new DependencyList(); private readonly Logger _logger; private readonly NodeFactory _factory; private readonly FlowAnnotations _annotations; private bool _typeHierarchyDataFlow; + private bool _enabled; private const string RequiresUnreferencedCodeAttribute = nameof(RequiresUnreferencedCodeAttribute); public DependencyList Dependencies { get => _dependencies; } - public ReflectionMarker(Logger logger, NodeFactory factory, FlowAnnotations annotations, bool typeHierarchyDataFlow) + public ReflectionMarker(Logger logger, NodeFactory factory, FlowAnnotations annotations, bool typeHierarchyDataFlow, bool enabled) { _logger = logger; _factory = factory; _annotations = annotations; _typeHierarchyDataFlow = typeHierarchyDataFlow; + _enabled = enabled; } internal void MarkTypeForDynamicallyAccessedMembers(in MessageOrigin origin, TypeDesc typeDefinition, DynamicallyAccessedMemberTypes requiredMemberTypes, Origin memberWithRequirements, bool declaredOnly = false) { + if (!_enabled) + return; + foreach (var member in typeDefinition.GetDynamicallyAccessedMembers(requiredMemberTypes, declaredOnly)) { switch (member) @@ -63,21 +68,26 @@ internal void MarkTypeForDynamicallyAccessedMembers(in MessageOrigin origin, Typ } } - internal bool TryResolveTypeNameAndMark(string typeName, MessageOrigin origin, bool needsAssemblyName, Origin memberWithRequirements, [NotNullWhen(true)] out TypeDesc? type) + internal bool TryResolveTypeNameAndMark(string typeName, in DiagnosticContext diagnosticContext, bool needsAssemblyName, Origin memberWithRequirements, [NotNullWhen(true)] out TypeDesc? type) { - ModuleDesc? callingModule = ((origin.MemberDefinition as MethodDesc)?.OwningType as MetadataType)?.Module; + ModuleDesc? callingModule = ((diagnosticContext.Origin.MemberDefinition as MethodDesc)?.OwningType as MetadataType)?.Module; - if (!ILCompiler.DependencyAnalysis.ReflectionMethodBodyScanner.ResolveType(typeName, callingModule, origin.MemberDefinition.Context, out TypeDesc foundType, out ModuleDesc referenceModule)) + // NativeAOT doesn't have a fully capable type name resolver yet + // Once this is implemented don't forget to wire up marking of type forwards which are used in generic parameters + if (!ILCompiler.DependencyAnalysis.ReflectionMethodBodyScanner.ResolveType(typeName, callingModule, diagnosticContext.Origin.MemberDefinition.Context, out TypeDesc foundType, out ModuleDesc referenceModule)) { type = default; return false; } - // Also add module metadata in case this reference was through a type forward - if (_factory.MetadataManager.CanGenerateMetadata(referenceModule.GetGlobalModuleType())) - _dependencies.Add(_factory.ModuleMetadata(referenceModule), memberWithRequirements.ToString()); + if (_enabled) + { + // Also add module metadata in case this reference was through a type forward + if (_factory.MetadataManager.CanGenerateMetadata(referenceModule.GetGlobalModuleType())) + _dependencies.Add(_factory.ModuleMetadata(referenceModule), memberWithRequirements.ToString()); - MarkType(origin, foundType, memberWithRequirements); + MarkType(diagnosticContext.Origin, foundType, memberWithRequirements); + } type = foundType; return true; @@ -85,11 +95,17 @@ internal bool TryResolveTypeNameAndMark(string typeName, MessageOrigin origin, b internal void MarkType(in MessageOrigin origin, TypeDesc type, Origin memberWithRequirements) { + if (!_enabled) + return; + RootingHelpers.TryGetDependenciesForReflectedType(ref _dependencies, _factory, type, memberWithRequirements.ToString()); } internal void MarkMethod(in MessageOrigin origin, MethodDesc method, Origin memberWithRequirements) { + if (!_enabled) + return; + if (method.DoesMethodRequire(RequiresUnreferencedCodeAttribute, out _)) { if (_typeHierarchyDataFlow) @@ -109,6 +125,9 @@ internal void MarkMethod(in MessageOrigin origin, MethodDesc method, Origin memb void MarkField(in MessageOrigin origin, FieldDesc field, Origin memberWithRequirements) { + if (!_enabled) + return; + if (_annotations.ShouldWarnWhenAccessedForReflection(field) && !ReflectionMethodBodyScanner.ShouldSuppressAnalysisWarningsForRequires(origin.MemberDefinition, RequiresUnreferencedCodeAttribute)) { WarnOnReflectionAccess(origin, field, memberWithRequirements); @@ -119,6 +138,9 @@ void MarkField(in MessageOrigin origin, FieldDesc field, Origin memberWithRequir internal void MarkProperty(in MessageOrigin origin, PropertyPseudoDesc property, Origin memberWithRequirements) { + if (!_enabled) + return; + if (property.GetMethod != null) MarkMethod(origin, property.GetMethod, memberWithRequirements); if (property.SetMethod != null) @@ -127,6 +149,9 @@ internal void MarkProperty(in MessageOrigin origin, PropertyPseudoDesc property, void MarkEvent(in MessageOrigin origin, EventPseudoDesc @event, Origin memberWithRequirements) { + if (!_enabled) + return; + if (@event.AddMethod != null) MarkMethod(origin, @event.AddMethod, memberWithRequirements); if (@event.RemoveMethod != null) @@ -135,30 +160,45 @@ void MarkEvent(in MessageOrigin origin, EventPseudoDesc @event, Origin memberWit internal void MarkConstructorsOnType(in MessageOrigin origin, TypeDesc type, Func? filter, Origin memberWithRequirements, BindingFlags? bindingFlags = null) { + if (!_enabled) + return; + foreach (var ctor in type.GetConstructorsOnType(filter, bindingFlags)) MarkMethod(origin, ctor, memberWithRequirements); } internal void MarkFieldsOnTypeHierarchy(in MessageOrigin origin, TypeDesc type, Func filter, Origin memberWithRequirements, BindingFlags? bindingFlags = BindingFlags.Default) { + if (!_enabled) + return; + foreach (var field in type.GetFieldsOnTypeHierarchy(filter, bindingFlags)) MarkField(origin, field, memberWithRequirements); } internal void MarkPropertiesOnTypeHierarchy(in MessageOrigin origin, TypeDesc type, Func filter, Origin memberWithRequirements, BindingFlags? bindingFlags = BindingFlags.Default) { + if (!_enabled) + return; + foreach (var property in type.GetPropertiesOnTypeHierarchy(filter, bindingFlags)) MarkProperty(origin, property, memberWithRequirements); } internal void MarkEventsOnTypeHierarchy(in MessageOrigin origin, TypeDesc type, Func filter, Origin memberWithRequirements, BindingFlags? bindingFlags = BindingFlags.Default) { + if (!_enabled) + return; + foreach (var @event in type.GetEventsOnTypeHierarchy(filter, bindingFlags)) MarkEvent(origin, @event, memberWithRequirements); } internal void MarkStaticConstructor(in MessageOrigin origin, TypeDesc type) { + if (!_enabled) + return; + if (!type.IsGenericDefinition && !type.ContainsSignatureVariables(treatGenericParameterLikeSignatureVariable: true) && type.HasStaticConstructor) { // Mark the GC static base - it contains a pointer to the class constructor, but also info diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMethodBodyScanner.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMethodBodyScanner.cs index 18c5b082f60fe..5eb212c64af5f 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMethodBodyScanner.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMethodBodyScanner.cs @@ -30,7 +30,6 @@ namespace ILCompiler.Dataflow { class ReflectionMethodBodyScanner : MethodBodyScanner { - private readonly FlowAnnotations _annotations; private readonly Logger _logger; private readonly NodeFactory _factory; private readonly ReflectionMarker _reflectionMarker; @@ -121,8 +120,8 @@ private enum ScanningPurpose private ScanningPurpose _purpose; private ReflectionMethodBodyScanner(NodeFactory factory, FlowAnnotations annotations, Logger logger, ScanningPurpose purpose = ScanningPurpose.Default) + : base(annotations) { - _annotations = annotations; _logger = logger; _factory = factory; _purpose = purpose; @@ -169,83 +168,6 @@ public static DependencyList ScanAndProcessReturnValue(NodeFactory factory, Flow return scanner._reflectionMarker.Dependencies; } - public static DependencyList? ProcessAttributeDataflow(NodeFactory factory, FlowAnnotations annotations, Logger logger, MethodDesc method, CustomAttributeValue arguments) - { - DependencyList? result = null; - - // First do the dataflow for the constructor parameters if necessary. - if (annotations.RequiresDataflowAnalysis(method)) - { - for (int i = 0; i < method.Signature.Length; i++) - { - var parameterValue = annotations.GetMethodParameterValue(method, i); - if (parameterValue.DynamicallyAccessedMemberTypes != DynamicallyAccessedMemberTypes.None) - { - MultiValue value = GetValueForCustomAttributeArgument(arguments.FixedArguments[i].Value); - var diagnosticContext = new DiagnosticContext(new MessageOrigin(method), diagnosticsEnabled: true, logger); - var scanner = new ReflectionMethodBodyScanner(factory, annotations, logger); - scanner.RequireDynamicallyAccessedMembers(diagnosticContext, value, parameterValue, parameterValue.ParameterOrigin); - AddResults(scanner._reflectionMarker.Dependencies); - } - } - } - - // Named arguments next - TypeDesc attributeType = method.OwningType; - foreach (var namedArgument in arguments.NamedArguments) - { - MultiValue targetValues = new(); - Origin? targetContext = null; - if (namedArgument.Kind == CustomAttributeNamedArgumentKind.Field) - { - FieldDesc field = attributeType.GetField(namedArgument.Name); - if (field != null) - { - targetValues = GetFieldValue(field, annotations); - targetContext = new FieldOrigin(field); - } - } - else - { - Debug.Assert(namedArgument.Kind == CustomAttributeNamedArgumentKind.Property); - PropertyPseudoDesc property = ((MetadataType)attributeType).GetProperty(namedArgument.Name, null); - MethodDesc setter = property.SetMethod; - if (setter != null && setter.Signature.Length > 0 && !setter.Signature.IsStatic) - { - targetValues = annotations.GetMethodParameterValue(setter, 0); - targetContext = new ParameterOrigin(setter, 1); - } - } - - foreach (var targetValueCandidate in targetValues) - { - if (targetValueCandidate is not ValueWithDynamicallyAccessedMembers targetValue || - targetValue.DynamicallyAccessedMemberTypes == DynamicallyAccessedMemberTypes.None) - continue; - - MultiValue valueNode = GetValueForCustomAttributeArgument(namedArgument.Value); - var diagnosticContext = new DiagnosticContext(new MessageOrigin(method), diagnosticsEnabled: true, logger); - var scanner = new ReflectionMethodBodyScanner(factory, annotations, logger); - scanner.RequireDynamicallyAccessedMembers(diagnosticContext, valueNode, targetValue, targetContext!); - AddResults(scanner._reflectionMarker.Dependencies); - } - } - - return result; - - void AddResults(DependencyList dependencies) - { - if (result == null) - { - result = dependencies; - } - else - { - result.AddRange(dependencies); - } - } - } - public static DependencyList ProcessTypeGetTypeDataflow(NodeFactory factory, FlowAnnotations flowAnnotations, Logger logger, MetadataType type) { DynamicallyAccessedMemberTypes annotation = flowAnnotations.GetTypeAnnotation(type); @@ -255,74 +177,6 @@ public static DependencyList ProcessTypeGetTypeDataflow(NodeFactory factory, Flo return reflectionMarker.Dependencies; } - static MultiValue GetValueForCustomAttributeArgument(object? argument) - { - SingleValue? result = null; - if (argument is TypeDesc td) - { - result = new SystemTypeValue(td); - } - else if (argument is string str) - { - result = new KnownStringValue(str); - } - else - { - Debug.Assert(argument is null); - result = NullValue.Instance; - } - - Debug.Assert(result != null); - return result; - } - - public static DependencyList ProcessGenericArgumentDataFlow(NodeFactory factory, FlowAnnotations flowAnnotations, Logger logger, GenericParameterDesc genericParameter, TypeDesc genericArgument, TypeSystemEntity source) - { - var scanner = new ReflectionMethodBodyScanner(factory, flowAnnotations, logger); - - var genericParameterValue = flowAnnotations.GetGenericParameterValue(genericParameter); - Debug.Assert(genericParameterValue.DynamicallyAccessedMemberTypes != DynamicallyAccessedMemberTypes.None); - - MultiValue genericArgumentValue = scanner.GetTypeValueNodeFromGenericArgument(genericArgument); - - bool enableDiagnostics = ShouldSuppressAnalysisWarningsForRequires(source, RequiresUnreferencedCodeAttribute); - var diagnosticContext = new DiagnosticContext(new MessageOrigin(source), diagnosticsEnabled: enableDiagnostics, logger); - var origin = new GenericParameterOrigin(genericParameter); - scanner.RequireDynamicallyAccessedMembers(diagnosticContext, genericArgumentValue, genericParameterValue, origin); - - return scanner._reflectionMarker.Dependencies; - } - - MultiValue GetTypeValueNodeFromGenericArgument(TypeDesc genericArgument) - { - if (genericArgument is GenericParameterDesc inputGenericParameter) - { - return _annotations.GetGenericParameterValue(inputGenericParameter); - } - else if (genericArgument is MetadataType genericArgumentType) - { - if (genericArgumentType.IsTypeOf(WellKnownType.System_Nullable_T)) - { - var innerGenericArgument = genericArgumentType.Instantiation.Length == 1 ? genericArgumentType.Instantiation[0] : null; - switch (innerGenericArgument) - { - case GenericParameterDesc gp: - return new NullableValueWithDynamicallyAccessedMembers(genericArgumentType, - new GenericParameterValue(gp, _annotations.GetGenericParameterAnnotation(gp))); - - case TypeDesc underlyingType: - return new NullableSystemTypeValue(genericArgumentType, new SystemTypeValue(underlyingType)); - } - } - // All values except for Nullable, including Nullable<> (with no type arguments) - return new SystemTypeValue(genericArgumentType); - } - else - { - return UnknownValue.Instance; - } - } - protected override void WarnAboutInvalidILInMethod(MethodIL method, int ilOffset) { // Serves as a debug helper to make sure valid IL is not considered invalid. diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/RequireDynamicallyAccessedMembersAction.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/RequireDynamicallyAccessedMembersAction.cs index 69e03cf2d3945..0b6abdb478fae 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/RequireDynamicallyAccessedMembersAction.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/RequireDynamicallyAccessedMembersAction.cs @@ -27,7 +27,7 @@ public RequireDynamicallyAccessedMembersAction( public partial bool TryResolveTypeNameAndMark(string typeName, bool needsAssemblyName, out TypeProxy type) { - if (_reflectionMarker.TryResolveTypeNameAndMark(typeName, _diagnosticContext.Origin, needsAssemblyName, _memberWithRequirements, out TypeDesc? foundType)) + if (_reflectionMarker.TryResolveTypeNameAndMark(typeName, _diagnosticContext, needsAssemblyName, _memberWithRequirements, out TypeDesc? foundType)) { type = new(foundType); return true; diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ScannerExtensions.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ScannerExtensions.cs index bfb7999e2b47e..4a63effd60802 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ScannerExtensions.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ScannerExtensions.cs @@ -90,10 +90,5 @@ public static HashSet ComputeBranchTargets(this MethodIL methodBody) } return branchTargets; } - - public static bool IsByRefOrPointer(this TypeDesc type) - { - return type.IsByRef || type.IsPointer; - } } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisAssignmentPattern.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisAssignmentPattern.cs new file mode 100644 index 0000000000000..431b6ea33862e --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisAssignmentPattern.cs @@ -0,0 +1,44 @@ +// 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 ILCompiler.Logging; +using ILLink.Shared.TrimAnalysis; +using MultiValue = ILLink.Shared.DataFlow.ValueSet; + +#nullable enable + +namespace ILCompiler.Dataflow +{ + public readonly record struct TrimAnalysisAssignmentPattern + { + public MultiValue Source { init; get; } + public MultiValue Target { init; get; } + public MessageOrigin Origin { init; get; } + + public TrimAnalysisAssignmentPattern(MultiValue source, MultiValue target, MessageOrigin origin) + { + Source = source.Clone(); + Target = target.Clone(); + Origin = origin; + } + + public void MarkAndProduceDiagnostics(ReflectionMarker reflectionMarker, Logger logger) + { + bool diagnosticsEnabled = !context.Annotations.ShouldSuppressAnalysisWarningsForRequiresUnreferencedCode(Origin.Provider); + var diagnosticContext = new DiagnosticContext(Origin, diagnosticsEnabled, logger); + + foreach (var sourceValue in Source) + { + foreach (var targetValue in Target) + { + if (targetValue is not ValueWithDynamicallyAccessedMembers targetWithDynamicallyAccessedMembers) + throw new NotImplementedException(); + + var requireDynamicallyAccessedMembersAction = new RequireDynamicallyAccessedMembersAction(reflectionMarker, diagnosticContext); + requireDynamicallyAccessedMembersAction.Invoke(sourceValue, targetWithDynamicallyAccessedMembers); + } + } + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisMethodCallPattern.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisMethodCallPattern.cs new file mode 100644 index 0000000000000..ba86ba87c1cea --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisMethodCallPattern.cs @@ -0,0 +1,65 @@ +// 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.Immutable; +using System.Diagnostics; +using ILCompiler.Logging; +using ILLink.Shared.TrimAnalysis; +using Internal.IL; +using Internal.TypeSystem; +using MultiValue = ILLink.Shared.DataFlow.ValueSet; + +#nullable enable + +namespace ILCompiler.Dataflow +{ + public readonly record struct TrimAnalysisMethodCallPattern + { + public readonly MethodIL MethodBody; + public readonly ILOpcode Operation; + public readonly int Offset; + public readonly MethodDesc CalledMethod; + public readonly MultiValue Instance; + public readonly ImmutableArray Arguments; + public readonly MessageOrigin Origin; + + public TrimAnalysisMethodCallPattern( + MethodIL methodBody, + ILOpcode operation, + int offset, + MethodDesc calledMethod, + MultiValue instance, + ImmutableArray arguments, + MessageOrigin origin) + { + Debug.Assert(origin.MemberDefinition is MethodDesc); + MethodBody = methodBody; + Operation = operation; + Offset = offset; + CalledMethod = calledMethod; + Instance = instance.Clone(); + if (arguments.IsEmpty) + { + Arguments = ImmutableArray.Empty; + } + else + { + var builder = ImmutableArray.CreateBuilder(); + foreach (var argument in arguments) + builder.Add(argument.Clone()); + Arguments = builder.ToImmutableArray(); + } + Origin = origin; + } + + public void MarkAndProduceDiagnostics(ReflectionMarker reflectionMarker, Logger logger) + { + bool diagnosticsEnabled = !context.Annotations.ShouldSuppressAnalysisWarningsForRequiresUnreferencedCode(Origin.Provider); + var diagnosticContext = new DiagnosticContext(Origin, diagnosticsEnabled, logger); + ReflectionMethodBodyScanner.HandleCall(MethodBody, CalledMethod, Operation, Offset, Instance, Arguments, + diagnosticContext, + reflectionMarker, + out MultiValue _); + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisPatternStore.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisPatternStore.cs new file mode 100644 index 0000000000000..f170acc2495dd --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisPatternStore.cs @@ -0,0 +1,49 @@ +// 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.Logging; +using ILLink.Shared.TrimAnalysis; + +#nullable enable + +namespace ILCompiler.Dataflow +{ + public readonly struct TrimAnalysisPatternStore + { + readonly Dictionary<(MessageOrigin, bool), TrimAnalysisAssignmentPattern> AssignmentPatterns; + readonly Dictionary MethodCallPatterns; + readonly Logger _logger; + + public TrimAnalysisPatternStore(Logger logger) + { + AssignmentPatterns = new Dictionary<(MessageOrigin, bool), TrimAnalysisAssignmentPattern>(); + MethodCallPatterns = new Dictionary(); + _logger = logger; + } + + public void Add(TrimAnalysisAssignmentPattern pattern) + { + // In the linker, each pattern should have a unique origin (which has ILOffset) + // but we don't track the correct ILOffset for return instructions. + // https://github.com/dotnet/linker/issues/2778 + // For now, work around it with a separate bit. + bool isReturnValue = pattern.Target.AsSingleValue() is MethodReturnValue; + AssignmentPatterns.Add((pattern.Origin, isReturnValue), pattern); + } + + public void Add(TrimAnalysisMethodCallPattern pattern) + { + MethodCallPatterns.Add(pattern.Origin, pattern); + } + + public void MarkAndProduceDiagnostics(ReflectionMarker reflectionMarker) + { + foreach (var pattern in AssignmentPatterns.Values) + pattern.MarkAndProduceDiagnostics(reflectionMarker, _logger); + + foreach (var pattern in MethodCallPatterns.Values) + pattern.MarkAndProduceDiagnostics(reflectionMarker, _logger); + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/UsageBasedMetadataManager.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/UsageBasedMetadataManager.cs index 77af00708d864..3ac813a793db6 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/UsageBasedMetadataManager.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/UsageBasedMetadataManager.cs @@ -690,7 +690,7 @@ public override DependencyList GetDependenciesForCustomAttribute(NodeFactory fac bool scanReflection = (_generationOptions & UsageBasedMetadataGenerationOptions.ReflectionILScanning) != 0; if (scanReflection) { - return Dataflow.ReflectionMethodBodyScanner.ProcessAttributeDataflow(factory, FlowAnnotations, Logger, attributeCtor, decodedValue); + (new AttributeDataFlow(Logger, factory, FlowAnnotations, new Logging.MessageOrigin(attributeCtor))).ProcessAttributeDataflow(attributeCtor, decodedValue); } return null; @@ -705,7 +705,7 @@ private void GetFlowDependenciesForInstantiation(ref DependencyList dependencies { try { - var deps = ILCompiler.Dataflow.ReflectionMethodBodyScanner.ProcessGenericArgumentDataFlow(factory, FlowAnnotations, Logger, genericParameter, instantiation[i], source); + var deps = (new ILCompiler.Dataflow.GenericArgumentDataFlow(Logger, factory, FlowAnnotations, new Logging.MessageOrigin(source))).ProcessGenericArgumentDataFlow(genericParameter, instantiation[i]); if (deps.Count > 0) { if (dependencies == null) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj index 12fadf2a915f3..79cb156ac4e0b 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj @@ -1,4 +1,4 @@ - + Library ILCompiler.Compiler @@ -317,26 +317,35 @@ + + + + + - + + + + + diff --git a/src/coreclr/tools/aot/ILLink.Shared/DiagnosticId.cs b/src/coreclr/tools/aot/ILLink.Shared/DiagnosticId.cs index ac417f4ee58e6..8b7f2166becf3 100644 --- a/src/coreclr/tools/aot/ILLink.Shared/DiagnosticId.cs +++ b/src/coreclr/tools/aot/ILLink.Shared/DiagnosticId.cs @@ -179,6 +179,9 @@ public enum DiagnosticId DynamicallyAccessedMembersOnTypeReferencesMemberOnBaseWithDynamicallyAccessedMembers = 2115, RequiresUnreferencedCodeOnStaticConstructor = 2116, MethodsAreAssociatedWithUserMethod = 2117, + CompilerGeneratedMemberAccessedViaReflection = 2118, + DynamicallyAccessedMembersOnTypeReferencesCompilerGeneratedMember = 2119, + DynamicallyAccessedMembersOnTypeReferencesCompilerGeneratedMemberOnBase = 2120, // Single-file diagnostic ids. AvoidAssemblyLocationInSingleFile = 3000, @@ -215,7 +218,7 @@ public static string GetDiagnosticSubcategory (this DiagnosticId diagnosticId) = 2103 => MessageSubCategory.TrimAnalysis, 2106 => MessageSubCategory.TrimAnalysis, 2107 => MessageSubCategory.TrimAnalysis, - >= 2109 and <= 2116 => MessageSubCategory.TrimAnalysis, + >= 2109 and <= 2120 => MessageSubCategory.TrimAnalysis, >= 3050 and <= 3052 => MessageSubCategory.AotAnalysis, >= 3054 and <= 3055 => MessageSubCategory.AotAnalysis, _ => MessageSubCategory.None, diff --git a/src/coreclr/tools/aot/ILLink.Shared/SharedStrings.resx b/src/coreclr/tools/aot/ILLink.Shared/SharedStrings.resx index 61e172189bd5c..29d51ff1e3930 100644 --- a/src/coreclr/tools/aot/ILLink.Shared/SharedStrings.resx +++ b/src/coreclr/tools/aot/ILLink.Shared/SharedStrings.resx @@ -1071,6 +1071,24 @@ Methods '{0}' and '{1}' are both associated with lambda or local function '{2}'. This is currently unsupported and may lead to incorrectly reported warnings. + + Compiler-generated member is accessed via reflection. Trimmer can't guarantee availability of the requirements of the member. + + + Compiler-generated member '{0}' is accessed via reflection. Trimmer can't guarantee availability of the requirements of the member. + + + 'DynamicallyAccessedMemberAttribute' on a type or one of its base types references a compiler-generated member. + + + 'DynamicallyAccessedMemberAttribute' on '{0}' or one of its base types references compiler-generated member '{1}'. + + + 'DynamicallyAccessedMemberAttribute' on a type or one of its base types references a compiler-generated member. + + + 'DynamicallyAccessedMemberAttribute' on '{0}' or one of its base types references compiler-generated member '{1}'. + Avoid accessing Assembly file path when publishing as a single file @@ -1167,4 +1185,4 @@ Unrecognized internal attribute '{0}' - \ No newline at end of file + diff --git a/src/coreclr/tools/aot/ILLink.Shared/TrimAnalysis/HandleCallAction.cs b/src/coreclr/tools/aot/ILLink.Shared/TrimAnalysis/HandleCallAction.cs index 942310a0090a5..ded6275f216fe 100644 --- a/src/coreclr/tools/aot/ILLink.Shared/TrimAnalysis/HandleCallAction.cs +++ b/src/coreclr/tools/aot/ILLink.Shared/TrimAnalysis/HandleCallAction.cs @@ -1154,6 +1154,8 @@ GenericParameterValue genericParam _requireDynamicallyAccessedMembersAction.Invoke (instanceValue, _annotations.GetMethodThisParameterValue (calledMethod)); } for (int argumentIndex = 0; argumentIndex < argumentValues.Count; argumentIndex++) { + if (calledMethod.ParameterReferenceKind (argumentIndex) == ReferenceKind.Out) + continue; _requireDynamicallyAccessedMembersAction.Invoke (argumentValues[argumentIndex], _annotations.GetMethodParameterValue (calledMethod, argumentIndex)); } } @@ -1162,6 +1164,10 @@ GenericParameterValue genericParam // Disable warnings for all unimplemented intrinsics. Some intrinsic methods have annotations, but analyzing them // would produce unnecessary warnings even for cases that are intrinsically handled. So we disable handling these calls // until a proper intrinsic handling is made + // NOTE: Currently this is done "for the analyzer" and it relies on linker/NativeAOT to not call HandleCallAction + // for intrinsics which linker/NativeAOT need special handling for or those which are not implemented here and only there. + // Ideally we would run everything through HandleCallAction and it would return "false" for intrinsics it doesn't handle + // like it already does for Activator.CreateInstance for example. default: methodReturnValue = MultiValueLattice.Top; return true; @@ -1319,7 +1325,7 @@ void ProcessCreateInstanceByName (MethodProxy calledMethod, IReadOnlyList IntrinsicId.Nullable_GetUnderlyingType, + _ => IntrinsicId.None, }; } diff --git a/src/coreclr/tools/aot/ILLink.Shared/TrimAnalysis/ReferenceKind.cs b/src/coreclr/tools/aot/ILLink.Shared/TrimAnalysis/ReferenceKind.cs new file mode 100644 index 0000000000000..1c46b2dbeba0b --- /dev/null +++ b/src/coreclr/tools/aot/ILLink.Shared/TrimAnalysis/ReferenceKind.cs @@ -0,0 +1,14 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + + +namespace ILLink.Shared.TypeSystemProxy +{ + public enum ReferenceKind + { + Ref, + In, + Out, + None + } +} \ No newline at end of file diff --git a/src/coreclr/tools/aot/ILLink.Shared/TypeSystemProxy/WellKnownType.cs b/src/coreclr/tools/aot/ILLink.Shared/TypeSystemProxy/WellKnownType.cs index 9d6f580c06d29..999f8f4dc95f2 100644 --- a/src/coreclr/tools/aot/ILLink.Shared/TypeSystemProxy/WellKnownType.cs +++ b/src/coreclr/tools/aot/ILLink.Shared/TypeSystemProxy/WellKnownType.cs @@ -1,13 +1,14 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -using System; +using StaticCs; // This is needed due to NativeAOT which doesn't enable nullable globally yet #nullable enable namespace ILLink.Shared.TypeSystemProxy { + [Closed] public enum WellKnownType { System_String, @@ -37,7 +38,6 @@ public static (string Namespace, string Name) GetNamespaceAndName (this WellKnow WellKnownType.System_NotSupportedException => ("System", "NotSupportedException"), WellKnownType.System_Runtime_CompilerServices_DisablePrivateReflectionAttribute => ("System.Runtime.CompilerServices", "DisablePrivateReflectionAttribute"), WellKnownType.System_Void => ("System", "Void"), - _ => throw new ArgumentException ($"{nameof (type)} is not a well-known type."), }; } public static string GetNamespace (this WellKnownType type) => GetNamespaceAndName (type).Namespace; From 13e656546dee2d86e99b281faf662b9e42bed4ea Mon Sep 17 00:00:00 2001 From: vitek-karas <10670590+vitek-karas@users.noreply.github.com> Date: Wed, 15 Jun 2022 06:08:28 -0700 Subject: [PATCH 02/18] Progress --- .../Compiler/Dataflow/AttributeDataFlow.cs | 2 +- .../Dataflow/GenericArgumentDataFlow.cs | 2 +- .../Compiler/Dataflow/ReflectionMarker.cs | 25 +- .../Dataflow/ReflectionMethodBodyScanner.cs | 252 ++++++++++-------- .../Dataflow/TrimAnalysisAssignmentPattern.cs | 2 +- .../Dataflow/TrimAnalysisMethodCallPattern.cs | 2 +- .../Compiler/Logging/MessageOrigin.cs | 13 +- 7 files changed, 167 insertions(+), 131 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/AttributeDataFlow.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/AttributeDataFlow.cs index ebfdb8999e44f..19ab1864f905f 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/AttributeDataFlow.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/AttributeDataFlow.cs @@ -128,7 +128,7 @@ void RequireDynamicallyAccessedMembers( Origin memberWithRequirements, ref DependencyList? result) { - var reflectionMarker = new ReflectionMarker(_logger, _factory, _annotations, enabled: true); + var reflectionMarker = new ReflectionMarker(_logger, _factory, _annotations, typeHierarchyDataFlow: false, enabled: true); var requireDynamicallyAccessedMembersAction = new RequireDynamicallyAccessedMembersAction(reflectionMarker, diagnosticContext, memberWithRequirements); requireDynamicallyAccessedMembersAction.Invoke(value, targetValue); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/GenericArgumentDataFlow.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/GenericArgumentDataFlow.cs index 8d246cb0439cd..e429ae22919f5 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/GenericArgumentDataFlow.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/GenericArgumentDataFlow.cs @@ -50,7 +50,7 @@ DependencyList RequireDynamicallyAccessedMembers( ValueWithDynamicallyAccessedMembers targetValue, Origin memberWithRequirements) { - var reflectionMarker = new ReflectionMarker(_logger, _factory, _annotations, enabled: true); + var reflectionMarker = new ReflectionMarker(_logger, _factory, _annotations, typeHierarchyDataFlow:false, enabled: true); var requireDynamicallyAccessedMembersAction = new RequireDynamicallyAccessedMembersAction(reflectionMarker, diagnosticContext, memberWithRequirements); requireDynamicallyAccessedMembersAction.Invoke(value, targetValue); return reflectionMarker.Dependencies; diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMarker.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMarker.cs index a911cfb160e6a..55111bb7c7040 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMarker.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMarker.cs @@ -21,8 +21,8 @@ public class ReflectionMarker { private DependencyList _dependencies = new DependencyList(); private readonly Logger _logger; - private readonly NodeFactory _factory; - private readonly FlowAnnotations _annotations; + public NodeFactory Factory { get; } + public FlowAnnotations Annotations { get; } private bool _typeHierarchyDataFlow; private bool _enabled; private const string RequiresUnreferencedCodeAttribute = nameof(RequiresUnreferencedCodeAttribute); @@ -32,8 +32,8 @@ public class ReflectionMarker public ReflectionMarker(Logger logger, NodeFactory factory, FlowAnnotations annotations, bool typeHierarchyDataFlow, bool enabled) { _logger = logger; - _factory = factory; - _annotations = annotations; + Factory = factory; + Annotations = annotations; _typeHierarchyDataFlow = typeHierarchyDataFlow; _enabled = enabled; } @@ -83,8 +83,8 @@ internal bool TryResolveTypeNameAndMark(string typeName, in DiagnosticContext di if (_enabled) { // Also add module metadata in case this reference was through a type forward - if (_factory.MetadataManager.CanGenerateMetadata(referenceModule.GetGlobalModuleType())) - _dependencies.Add(_factory.ModuleMetadata(referenceModule), memberWithRequirements.ToString()); + if (Factory.MetadataManager.CanGenerateMetadata(referenceModule.GetGlobalModuleType())) + _dependencies.Add(Factory.ModuleMetadata(referenceModule), memberWithRequirements.ToString()); MarkType(diagnosticContext.Origin, foundType, memberWithRequirements); } @@ -98,7 +98,7 @@ internal void MarkType(in MessageOrigin origin, TypeDesc type, Origin memberWith if (!_enabled) return; - RootingHelpers.TryGetDependenciesForReflectedType(ref _dependencies, _factory, type, memberWithRequirements.ToString()); + RootingHelpers.TryGetDependenciesForReflectedType(ref _dependencies, Factory, type, memberWithRequirements.ToString()); } internal void MarkMethod(in MessageOrigin origin, MethodDesc method, Origin memberWithRequirements) @@ -115,12 +115,12 @@ internal void MarkMethod(in MessageOrigin origin, MethodDesc method, Origin memb } } - if (_annotations.ShouldWarnWhenAccessedForReflection(method) && !ReflectionMethodBodyScanner.ShouldSuppressAnalysisWarningsForRequires(method, RequiresUnreferencedCodeAttribute)) + if (Annotations.ShouldWarnWhenAccessedForReflection(method) && !ReflectionMethodBodyScanner.ShouldSuppressAnalysisWarningsForRequires(method, RequiresUnreferencedCodeAttribute)) { WarnOnReflectionAccess(origin, method, memberWithRequirements); } - RootingHelpers.TryGetDependenciesForReflectedMethod(ref _dependencies, _factory, method, memberWithRequirements.ToString()); + RootingHelpers.TryGetDependenciesForReflectedMethod(ref _dependencies, Factory, method, memberWithRequirements.ToString()); } void MarkField(in MessageOrigin origin, FieldDesc field, Origin memberWithRequirements) @@ -128,12 +128,12 @@ void MarkField(in MessageOrigin origin, FieldDesc field, Origin memberWithRequir if (!_enabled) return; - if (_annotations.ShouldWarnWhenAccessedForReflection(field) && !ReflectionMethodBodyScanner.ShouldSuppressAnalysisWarningsForRequires(origin.MemberDefinition, RequiresUnreferencedCodeAttribute)) + if (Annotations.ShouldWarnWhenAccessedForReflection(field) && !ReflectionMethodBodyScanner.ShouldSuppressAnalysisWarningsForRequires(origin.MemberDefinition, RequiresUnreferencedCodeAttribute)) { WarnOnReflectionAccess(origin, field, memberWithRequirements); } - RootingHelpers.TryGetDependenciesForReflectedField(ref _dependencies, _factory, field, memberWithRequirements.ToString()); + RootingHelpers.TryGetDependenciesForReflectedField(ref _dependencies, Factory, field, memberWithRequirements.ToString()); } internal void MarkProperty(in MessageOrigin origin, PropertyPseudoDesc property, Origin memberWithRequirements) @@ -203,10 +203,11 @@ internal void MarkStaticConstructor(in MessageOrigin origin, TypeDesc type) { // Mark the GC static base - it contains a pointer to the class constructor, but also info // about whether the class constructor already executed and it's what is looked at at runtime. - _dependencies.Add(_factory.TypeNonGCStaticsSymbol((MetadataType)type), "RunClassConstructor reference"); + _dependencies.Add(Factory.TypeNonGCStaticsSymbol((MetadataType)type), "RunClassConstructor reference"); } } + // TODO: This should use DiagnosticContext to avoid generating warnings when we run data flow on a method multiple times void WarnOnReflectionAccess(in MessageOrigin origin, TypeSystemEntity entity, Origin memberWithRequirements) { if (_typeHierarchyDataFlow) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMethodBodyScanner.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMethodBodyScanner.cs index 5eb212c64af5f..c273cb28bc2aa 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMethodBodyScanner.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMethodBodyScanner.cs @@ -12,7 +12,7 @@ using ILLink.Shared; using ILLink.Shared.DataFlow; using ILLink.Shared.TrimAnalysis; - +using ILLink.Shared.TypeSystemProxy; using Internal.IL; using Internal.TypeSystem; @@ -33,10 +33,15 @@ class ReflectionMethodBodyScanner : MethodBodyScanner private readonly Logger _logger; private readonly NodeFactory _factory; private readonly ReflectionMarker _reflectionMarker; + private readonly TrimAnalysisPatternStore TrimAnalysisPatterns; + + private MessageOrigin _origin; + private const string RequiresUnreferencedCodeAttribute = nameof(RequiresUnreferencedCodeAttribute); private const string RequiresDynamicCodeAttribute = nameof(RequiresDynamicCodeAttribute); private const string RequiresAssemblyFilesAttribute = nameof(RequiresAssemblyFilesAttribute); + public static bool RequiresReflectionMethodBodyScannerForCallSite(FlowAnnotations flowAnnotations, MethodDesc methodDefinition) { return Intrinsics.GetIntrinsicIdForMethod(methodDefinition) > IntrinsicId.RequiresReflectionBodyScanner_Sentinel || @@ -59,6 +64,8 @@ public static bool RequiresReflectionMethodBodyScannerForAccess(FlowAnnotations fieldDefinition.DoesFieldRequire(RequiresDynamicCodeAttribute, out _); } + // TODO: This should use DiagnosticContext so that it avoids generating warnings in cases we run data flow + // on a method multiple times. void CheckAndReportRequires(TypeSystemEntity calledMember, in MessageOrigin origin, string requiresAttributeName) { // If the caller of a method is already marked with `Requires` a new warning should not @@ -111,26 +118,41 @@ void ReportRequires(string displayName, in MessageOrigin currentOrigin, Diagnost _logger.LogWarning(currentOrigin, diagnosticId, displayName, arg1, arg2); } - private enum ScanningPurpose + private ReflectionMethodBodyScanner(NodeFactory factory, FlowAnnotations annotations, Logger logger, MessageOrigin origin) + : base(annotations) { - Default, - GetTypeDataflow, + _logger = logger; + _factory = factory; + _origin = origin; + _reflectionMarker = new ReflectionMarker(logger, factory, annotations, typeHierarchyDataFlow: false, enabled: false); + TrimAnalysisPatterns = new TrimAnalysisPatternStore(logger); } - private ScanningPurpose _purpose; + public override void InterproceduralScan(MethodIL methodBody) + { + base.InterproceduralScan(methodBody); - private ReflectionMethodBodyScanner(NodeFactory factory, FlowAnnotations annotations, Logger logger, ScanningPurpose purpose = ScanningPurpose.Default) - : base(annotations) + var reflectionMarker = new ReflectionMarker(_logger, _factory, _annotations, typeHierarchyDataFlow: false, enabled: true); + TrimAnalysisPatterns.MarkAndProduceDiagnostics(reflectionMarker); + } + + protected override void Scan(MethodIL methodBody, ref ValueSet methodsInGroup) { - _logger = logger; - _factory = factory; - _purpose = purpose; - _reflectionMarker = new ReflectionMarker(logger, factory, annotations, purpose == ScanningPurpose.GetTypeDataflow); + _origin = new MessageOrigin(methodBody.OwningMethod); + base.Scan(methodBody, ref methodsInGroup); + + if (!methodBody.OwningMethod.Signature.ReturnType.IsVoid) + { + var method = methodBody.OwningMethod; + var methodReturnValue = _annotations.GetMethodReturnValue(method); + if (methodReturnValue.DynamicallyAccessedMemberTypes != 0) + HandleAssignmentPattern(_origin, ReturnValue, methodReturnValue); + } } public static DependencyList ScanAndProcessReturnValue(NodeFactory factory, FlowAnnotations annotations, Logger logger, MethodIL methodBody) { - var scanner = new ReflectionMethodBodyScanner(factory, annotations, logger); + var scanner = new ReflectionMethodBodyScanner(factory, annotations, logger, new MessageOrigin(methodBody.OwningMethod)); Debug.Assert(methodBody.GetMethodILDefinition() == methodBody); if (methodBody.OwningMethod.HasInstantiation || methodBody.OwningMethod.OwningType.HasInstantiation) @@ -152,18 +174,7 @@ public static DependencyList ScanAndProcessReturnValue(NodeFactory factory, Flow methodBody = new InstantiatedMethodIL(methodBody.OwningMethod, methodBody); } - scanner.Scan(methodBody); - - if (!methodBody.OwningMethod.Signature.ReturnType.IsVoid) - { - var method = methodBody.OwningMethod; - var methodReturnValue = scanner._annotations.GetMethodReturnValue(method); - if (methodReturnValue.DynamicallyAccessedMemberTypes != 0) - { - var diagnosticContext = new DiagnosticContext(new MessageOrigin(method), !ShouldSuppressAnalysisWarningsForRequires(method, RequiresUnreferencedCodeAttribute), scanner._logger); - scanner.RequireDynamicallyAccessedMembers(diagnosticContext, scanner.ReturnValue, methodReturnValue, new MethodReturnOrigin(method)); - } - } + scanner.InterproceduralScan(methodBody); return scanner._reflectionMarker.Dependencies; } @@ -172,7 +183,7 @@ public static DependencyList ProcessTypeGetTypeDataflow(NodeFactory factory, Flo { DynamicallyAccessedMemberTypes annotation = flowAnnotations.GetTypeAnnotation(type); Debug.Assert(annotation != DynamicallyAccessedMemberTypes.None); - var reflectionMarker = new ReflectionMarker(logger, factory, flowAnnotations, true); + var reflectionMarker = new ReflectionMarker(logger, factory, flowAnnotations, typeHierarchyDataFlow: true, enabled: true); reflectionMarker.MarkTypeForDynamicallyAccessedMembers(new MessageOrigin(type), type, annotation, new TypeOrigin(type)); return reflectionMarker.Dependencies; } @@ -204,70 +215,100 @@ ValueWithDynamicallyAccessedMembers GetMethodParameterValue(MethodDesc method, i return _annotations.GetMethodParameterValue(method, parameterIndex, dynamicallyAccessedMemberTypes); } - static MultiValue GetFieldValue(FieldDesc field, FlowAnnotations annotations) + protected override MultiValue GetFieldValue(FieldDesc field) => _annotations.GetFieldValue(field); + + private void HandleStoreValueWithDynamicallyAccessedMembers(MethodIL methodBody, int offset, ValueWithDynamicallyAccessedMembers targetValue, MultiValue sourceValue) { - switch (field.Name) + if (targetValue.DynamicallyAccessedMemberTypes != 0) { - case "EmptyTypes" when field.OwningType.IsTypeOf(ILLink.Shared.TypeSystemProxy.WellKnownType.System_Type): - { - return ArrayValue.Create(0, field.OwningType); - } - case "Empty" when field.OwningType.IsTypeOf(ILLink.Shared.TypeSystemProxy.WellKnownType.System_String): - { - return new KnownStringValue(string.Empty); - } - - default: - { - DynamicallyAccessedMemberTypes memberTypes = annotations.GetFieldAnnotation(field); - return new FieldValue(field, memberTypes); - } + _origin = _origin.WithInstructionOffset(methodBody, offset); + HandleAssignmentPattern(_origin, sourceValue, targetValue); } } - protected override MultiValue GetFieldValue(FieldDesc field) - { - return GetFieldValue(field, _annotations); - } - protected override void HandleStoreField(MethodIL methodBody, int offset, FieldValue field, MultiValue valueToStore) - { - if (field.DynamicallyAccessedMemberTypes != 0) - { - var diagnosticContext = new DiagnosticContext(new MessageOrigin(methodBody, offset), !ShouldSuppressAnalysisWarningsForRequires(methodBody.OwningMethod, RequiresUnreferencedCodeAttribute), _logger); - RequireDynamicallyAccessedMembers(diagnosticContext, valueToStore, field, new FieldOrigin(field.Field)); - } + => HandleStoreValueWithDynamicallyAccessedMembers(methodBody, offset, field, valueToStore); + // TODO: The previous HandleStoreField also did this: + // CheckAndReportRequires(field.Field, new MessageOrigin(methodBody.OwningMethod), RequiresUnreferencedCodeAttribute); + // CheckAndReportRequires(field.Field, new MessageOrigin(methodBody.OwningMethod), RequiresDynamicCodeAttribute); - CheckAndReportRequires(field.Field, new MessageOrigin(methodBody.OwningMethod), RequiresUnreferencedCodeAttribute); - CheckAndReportRequires(field.Field, new MessageOrigin(methodBody.OwningMethod), RequiresDynamicCodeAttribute); - } + protected override void HandleStoreParameter(MethodIL methodBody, int offset, MethodParameterValue parameter, MultiValue valueToStore) + => HandleStoreValueWithDynamicallyAccessedMembers(methodBody, offset, parameter, valueToStore); - protected override void HandleStoreParameter(MethodIL method, int offset, MethodParameterValue parameter, MultiValue valueToStore) + protected override void HandleStoreMethodThisParameter(MethodIL methodBody, int offset, MethodThisParameterValue thisParameter, MultiValue valueToStore) + => HandleStoreValueWithDynamicallyAccessedMembers(methodBody, offset, thisParameter, valueToStore); + + protected override void HandleStoreMethodReturnValue(MethodIL methodBody, int offset, MethodReturnValue returnValue, MultiValue valueToStore) + => HandleStoreValueWithDynamicallyAccessedMembers(methodBody, offset, returnValue, valueToStore); + + public override bool HandleCall(MethodIL callingMethodBody, MethodDesc calledMethod, ILOpcode operation, int offset, ValueNodeList methodParams, out MultiValue methodReturnValue) { - if (parameter.DynamicallyAccessedMemberTypes != 0) + methodReturnValue = null; + Debug.Assert(callingMethodBody.OwningMethod == _origin.MemberDefinition); + + _origin = _origin.WithInstructionOffset(callingMethodBody, offset); + + MultiValue instanceValue; + ImmutableArray arguments; + if (!calledMethod.Signature.IsStatic) + { + instanceValue = methodParams[0]; + arguments = methodParams.Skip(1).ToImmutableArray(); + } + else { - var diagnosticContext = new DiagnosticContext(new MessageOrigin(method, offset), !ShouldSuppressAnalysisWarningsForRequires(method.OwningMethod, RequiresUnreferencedCodeAttribute), _logger); - RequireDynamicallyAccessedMembers(diagnosticContext, valueToStore, parameter, parameter.ParameterOrigin); + instanceValue = MultiValueLattice.Top; + arguments = methodParams.ToImmutableArray(); } + + TrimAnalysisPatterns.Add(new TrimAnalysisMethodCallPattern( + callingMethodBody, + operation, + offset, + calledMethod, + instanceValue, + arguments, + _origin + )); + + var diagnosticContext = new DiagnosticContext(_origin, diagnosticsEnabled: false, _logger); + return HandleCall( + callingMethodBody, + calledMethod, + operation, + offset, + instanceValue, + arguments, + diagnosticContext, + _reflectionMarker, + out methodReturnValue); } - public override bool HandleCall(MethodIL callingMethodBody, MethodDesc calledMethod, ILOpcode operation, int offset, ValueNodeList methodParams, out MultiValue methodReturnValue) + public static bool HandleCall( + MethodIL callingMethodBody, + MethodDesc calledMethod, + ILOpcode operation, + int offset, + MultiValue instanceValue, + ImmutableArray argumentValues, + DiagnosticContext diagnosticContext, + ReflectionMarker reflectionMarker, + out MultiValue methodReturnValue) { - methodReturnValue = null; + var origin = diagnosticContext.Origin; + var callingMethodDefinition = callingMethodBody.OwningMethod; + Debug.Assert(callingMethodDefinition != origin.MemberDefinition); + + bool requiresDataFlowAnalysis = reflectionMarker.Annotations.RequiresDataflowAnalysis(calledMethod); + var annotatedMethodReturnValue = reflectionMarker.Annotations.GetMethodReturnValue(calledMethod); + Debug.Assert(requiresDataFlowAnalysis || annotatedMethodReturnValue.DynamicallyAccessedMemberTypes == DynamicallyAccessedMemberTypes.None); + MultiValue? maybeMethodReturnValue = null; - var callingMethodDefinition = callingMethodBody.OwningMethod; bool shouldEnableReflectionWarnings = !ShouldSuppressAnalysisWarningsForRequires(callingMethodDefinition, RequiresUnreferencedCodeAttribute); bool shouldEnableAotWarnings = !ShouldSuppressAnalysisWarningsForRequires(callingMethodDefinition, RequiresDynamicCodeAttribute); - DynamicallyAccessedMemberTypes returnValueDynamicallyAccessedMemberTypes = 0; - - bool requiresDataFlowAnalysis = _annotations.RequiresDataflowAnalysis(calledMethod); - returnValueDynamicallyAccessedMemberTypes = requiresDataFlowAnalysis ? - _annotations.GetReturnParameterAnnotation(calledMethod) : 0; - - var diagnosticContext = new DiagnosticContext(new MessageOrigin(callingMethodBody, offset), shouldEnableReflectionWarnings, _logger); - var handleCallAction = new HandleCallAction(_annotations, _reflectionMarker, diagnosticContext, callingMethodDefinition, new MethodOrigin(calledMethod)); + var handleCallAction = new HandleCallAction(reflectionMarker.Annotations, reflectionMarker, diagnosticContext, callingMethodDefinition, new MethodOrigin(calledMethod)); var intrinsicId = Intrinsics.GetIntrinsicIdForMethod(calledMethod); switch (intrinsicId) @@ -317,14 +358,7 @@ public override bool HandleCall(MethodIL callingMethodBody, MethodDesc calledMet || appDomainCreateInstance == IntrinsicId.AppDomain_CreateInstanceFromAndUnwrap: case IntrinsicId.Assembly_CreateInstance: { - var instanceValue = MultiValueLattice.Top; - IReadOnlyList parameterValues = methodParams; - if (!calledMethod.Signature.IsStatic) - { - instanceValue = methodParams[0]; - parameterValues = parameterValues.Skip(1).ToImmutableList(); - } - bool result = handleCallAction.Invoke(calledMethod, instanceValue, parameterValues, out methodReturnValue, out _); + bool result = handleCallAction.Invoke(calledMethod, instanceValue, argumentValues, out methodReturnValue, out _); // Special case some intrinsics for AOT handling (on top of the trimming handling done in the HandleCallAction) switch (intrinsicId) @@ -366,26 +400,18 @@ public override bool HandleCall(MethodIL callingMethodBody, MethodDesc calledMet } } - var origin = new MessageOrigin(callingMethodBody, offset); CheckAndReportRequires(calledMethod, origin, RequiresUnreferencedCodeAttribute); CheckAndReportRequires(calledMethod, origin, RequiresDynamicCodeAttribute); CheckAndReportRequires(calledMethod, origin, RequiresAssemblyFilesAttribute); - var instanceValue = MultiValueLattice.Top; - IReadOnlyList parameterValues = methodParams; - if (!calledMethod.Signature.IsStatic) - { - instanceValue = methodParams[0]; - parameterValues = parameterValues.Skip(1).ToImmutableList(); - } - return handleCallAction.Invoke(calledMethod, instanceValue, parameterValues, out methodReturnValue, out _); + return handleCallAction.Invoke(calledMethod, instanceValue, argumentValues, out methodReturnValue, out _); } case IntrinsicId.TypeDelegator_Ctor: { // This is an identity function for analysis purposes if (operation == ILOpcode.newobj) - AddReturnValue(methodParams[1]); + AddReturnValue(argumentValues[0]); } break; @@ -407,7 +433,7 @@ public override bool HandleCall(MethodIL callingMethodBody, MethodDesc calledMet // type instead). // // At least until we have shared enum code, this needs extra handling to get it right. - foreach (var value in methodParams[0]) + foreach (var value in argumentValues[0]) { if (value is SystemTypeValue systemTypeValue && !systemTypeValue.RepresentedType.Type.IsGenericDefinition @@ -415,11 +441,11 @@ public override bool HandleCall(MethodIL callingMethodBody, MethodDesc calledMet { if (systemTypeValue.RepresentedType.Type.IsEnum) { - _reflectionMarker.Dependencies.Add(_factory.ConstructedTypeSymbol(systemTypeValue.RepresentedType.Type.MakeArrayType()), "Enum.GetValues"); + reflectionMarker.Dependencies.Add(reflectionMarker.Factory.ConstructedTypeSymbol(systemTypeValue.RepresentedType.Type.MakeArrayType()), "Enum.GetValues"); } } else - CheckAndReportRequires(calledMethod, new MessageOrigin(callingMethodBody, offset), RequiresDynamicCodeAttribute); + CheckAndReportRequires(calledMethod, origin, RequiresDynamicCodeAttribute); } } break; @@ -442,7 +468,7 @@ public override bool HandleCall(MethodIL callingMethodBody, MethodDesc calledMet ? 0 : 1; // We need the data to do struct marshalling. - foreach (var value in methodParams[paramIndex]) + foreach (var value in argumentValues[paramIndex]) { if (value is SystemTypeValue systemTypeValue && !systemTypeValue.RepresentedType.Type.IsGenericDefinition @@ -450,17 +476,17 @@ public override bool HandleCall(MethodIL callingMethodBody, MethodDesc calledMet { if (systemTypeValue.RepresentedType.Type.IsDefType) { - _reflectionMarker.Dependencies.Add(_factory.StructMarshallingData((DefType)systemTypeValue.RepresentedType.Type), "Marshal API"); + reflectionMarker.Dependencies.Add(reflectionMarker.Factory.StructMarshallingData((DefType)systemTypeValue.RepresentedType.Type), "Marshal API"); if (intrinsicId == IntrinsicId.Marshal_PtrToStructure && systemTypeValue.RepresentedType.Type.GetParameterlessConstructor() is MethodDesc ctorMethod - && !_factory.MetadataManager.IsReflectionBlocked(ctorMethod)) + && !reflectionMarker.Factory.MetadataManager.IsReflectionBlocked(ctorMethod)) { - _reflectionMarker.Dependencies.Add(_factory.ReflectableMethod(ctorMethod), "Marshal API"); + reflectionMarker.Dependencies.Add(reflectionMarker.Factory.ReflectableMethod(ctorMethod), "Marshal API"); } } } else - CheckAndReportRequires(calledMethod, new MessageOrigin(callingMethodBody, offset), RequiresDynamicCodeAttribute); + CheckAndReportRequires(calledMethod, origin, RequiresDynamicCodeAttribute); } } break; @@ -473,7 +499,7 @@ public override bool HandleCall(MethodIL callingMethodBody, MethodDesc calledMet case IntrinsicId.Marshal_GetDelegateForFunctionPointer: { // We need the data to do delegate marshalling. - foreach (var value in methodParams[1]) + foreach (var value in argumentValues[1]) { if (value is SystemTypeValue systemTypeValue && !systemTypeValue.RepresentedType.Type.IsGenericDefinition @@ -481,11 +507,11 @@ public override bool HandleCall(MethodIL callingMethodBody, MethodDesc calledMet { if (systemTypeValue.RepresentedType.Type.IsDelegate) { - _reflectionMarker.Dependencies.Add(_factory.DelegateMarshallingData((DefType)systemTypeValue.RepresentedType.Type), "Marshal API"); + reflectionMarker.Dependencies.Add(reflectionMarker.Factory.DelegateMarshallingData((DefType)systemTypeValue.RepresentedType.Type), "Marshal API"); } } else - CheckAndReportRequires(calledMethod, new MessageOrigin(callingMethodBody, offset), RequiresDynamicCodeAttribute); + CheckAndReportRequires(calledMethod, origin, RequiresDynamicCodeAttribute); } } break; @@ -497,7 +523,7 @@ public override bool HandleCall(MethodIL callingMethodBody, MethodDesc calledMet // case IntrinsicId.Object_GetType: { - foreach (var valueNode in methodParams[0]) + foreach (var valueNode in instanceValue) { // Note that valueNode can be statically typed in IL as some generic argument type. // For example: @@ -518,7 +544,7 @@ public override bool HandleCall(MethodIL callingMethodBody, MethodDesc calledMet if (staticType is null || (!staticType.IsDefType && !staticType.IsArray)) { // We don't know anything about the type GetType was called on. Track this as a usual "result of a method call without any annotations" - AddReturnValue(_annotations.GetMethodReturnValue(calledMethod)); + AddReturnValue(reflectionMarker.Annotations.GetMethodReturnValue(calledMethod)); } else if (staticType.IsSealed() || staticType.IsTypeOf("System", "Delegate")) { @@ -541,19 +567,19 @@ public override bool HandleCall(MethodIL callingMethodBody, MethodDesc calledMet { Debug.Assert(staticType is MetadataType || staticType.IsArray); MetadataType closestMetadataType = staticType is MetadataType mdType ? - mdType : (MetadataType)_factory.TypeSystemContext.GetWellKnownType(Internal.TypeSystem.WellKnownType.Array); + mdType : (MetadataType)reflectionMarker.Factory.TypeSystemContext.GetWellKnownType(Internal.TypeSystem.WellKnownType.Array); - var annotation = _annotations.GetTypeAnnotation(staticType); + var annotation = reflectionMarker.Annotations.GetTypeAnnotation(staticType); if (annotation != default) { - _reflectionMarker.Dependencies.Add(_factory.ObjectGetTypeFlowDependencies(closestMetadataType), "GetType called on this type"); + reflectionMarker.Dependencies.Add(reflectionMarker.Factory.ObjectGetTypeFlowDependencies(closestMetadataType), "GetType called on this type"); } // Return a value which is "unknown type" with annotation. For now we'll use the return value node // for the method, which means we're loosing the information about which staticType this // started with. For now we don't need it, but we can add it later on. - AddReturnValue(_annotations.GetMethodReturnValue(calledMethod, annotation)); + AddReturnValue(reflectionMarker.Annotations.GetMethodReturnValue(calledMethod, annotation)); } } } @@ -569,16 +595,16 @@ public override bool HandleCall(MethodIL callingMethodBody, MethodDesc calledMet bool returnsVoid = calledMethod.Signature.ReturnType.IsVoid; methodReturnValue = maybeMethodReturnValue ?? (returnsVoid ? MultiValueLattice.Top : - _annotations.GetMethodReturnValue(calledMethod, returnValueDynamicallyAccessedMemberTypes)!); + annotatedMethodReturnValue); // Validate that the return value has the correct annotations as per the method return value annotations - if (returnValueDynamicallyAccessedMemberTypes != 0) + if (annotatedMethodReturnValue.DynamicallyAccessedMemberTypes != 0) { foreach (var uniqueValue in methodReturnValue) { if (uniqueValue is ValueWithDynamicallyAccessedMembers methodReturnValueWithMemberTypes) { - if (!methodReturnValueWithMemberTypes.DynamicallyAccessedMemberTypes.HasFlag(returnValueDynamicallyAccessedMemberTypes)) + if (!methodReturnValueWithMemberTypes.DynamicallyAccessedMemberTypes.HasFlag(annotatedMethodReturnValue.DynamicallyAccessedMemberTypes)) throw new InvalidOperationException($"Internal linker error: processing of call from {callingMethodDefinition.GetDisplayName()} to {calledMethod.GetDisplayName()} returned value which is not correctly annotated with the expected dynamic member access kinds."); } else if (uniqueValue is SystemTypeValue) @@ -601,7 +627,7 @@ void AddReturnValue(MultiValue value) } } - bool IsComInterop(MarshalAsDescriptor? marshalInfoProvider, TypeDesc parameterType) + static bool IsComInterop(MarshalAsDescriptor? marshalInfoProvider, TypeDesc parameterType) { // This is best effort. One can likely find ways how to get COM without triggering these alarms. // AsAny marshalling of a struct with an object-typed field would be one, for example. @@ -680,10 +706,12 @@ bool IsComInterop(MarshalAsDescriptor? marshalInfoProvider, TypeDesc parameterTy return false; } - void RequireDynamicallyAccessedMembers(in DiagnosticContext diagnosticContext, in MultiValue value, ValueWithDynamicallyAccessedMembers targetValue, Origin memberWithRequirements) + void HandleAssignmentPattern( + in MessageOrigin origin, + in MultiValue value, + ValueWithDynamicallyAccessedMembers targetValue) { - var requireDynamicallyAccessedMembersAction = new RequireDynamicallyAccessedMembersAction(_reflectionMarker, diagnosticContext, memberWithRequirements); - requireDynamicallyAccessedMembersAction.Invoke(value, targetValue); + TrimAnalysisPatterns.Add(new TrimAnalysisAssignmentPattern(value, targetValue, origin)); } } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisAssignmentPattern.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisAssignmentPattern.cs index 431b6ea33862e..f31107cf20789 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisAssignmentPattern.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisAssignmentPattern.cs @@ -25,7 +25,7 @@ public TrimAnalysisAssignmentPattern(MultiValue source, MultiValue target, Messa public void MarkAndProduceDiagnostics(ReflectionMarker reflectionMarker, Logger logger) { - bool diagnosticsEnabled = !context.Annotations.ShouldSuppressAnalysisWarningsForRequiresUnreferencedCode(Origin.Provider); + bool diagnosticsEnabled = !context.Annotations.ShouldSuppressAnalysisWarningsForRequiresUnreferencedCode(Origin.MemberDefinition); var diagnosticContext = new DiagnosticContext(Origin, diagnosticsEnabled, logger); foreach (var sourceValue in Source) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisMethodCallPattern.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisMethodCallPattern.cs index ba86ba87c1cea..b8a1390be06e4 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisMethodCallPattern.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisMethodCallPattern.cs @@ -54,7 +54,7 @@ public TrimAnalysisMethodCallPattern( public void MarkAndProduceDiagnostics(ReflectionMarker reflectionMarker, Logger logger) { - bool diagnosticsEnabled = !context.Annotations.ShouldSuppressAnalysisWarningsForRequiresUnreferencedCode(Origin.Provider); + bool diagnosticsEnabled = !context.Annotations.ShouldSuppressAnalysisWarningsForRequiresUnreferencedCode(Origin.MemberDefinition); var diagnosticContext = new DiagnosticContext(Origin, diagnosticsEnabled, logger); ReflectionMethodBodyScanner.HandleCall(MethodBody, CalledMethod, Operation, Offset, Instance, Arguments, diagnosticContext, diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logging/MessageOrigin.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logging/MessageOrigin.cs index 04ba20872a784..8ace7bd257b6b 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logging/MessageOrigin.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logging/MessageOrigin.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Text; using Internal.IL; using Internal.TypeSystem; @@ -36,12 +37,12 @@ public MessageOrigin(TypeSystemEntity memberDefinition, string fileName = null, SourceColumn = sourceColumn; } - public MessageOrigin(MethodIL origin, int ilOffset) + public MessageOrigin(MethodIL methodBody, int ilOffset) { string document = null; int? lineNumber = null; - IEnumerable sequencePoints = origin.GetDebugInfo()?.GetSequencePoints(); + IEnumerable sequencePoints = methodBody.GetDebugInfo()?.GetSequencePoints(); if (sequencePoints != null) { foreach (var sequencePoint in sequencePoints) @@ -54,11 +55,17 @@ public MessageOrigin(MethodIL origin, int ilOffset) } } FileName = document; - MemberDefinition = origin.OwningMethod; + MemberDefinition = methodBody.OwningMethod; SourceLine = lineNumber; SourceColumn = null; } + public MessageOrigin WithInstructionOffset(MethodIL methodBody, int ilOffset) + { + Debug.Assert(methodBody.OwningMethod == MemberDefinition); + return new MessageOrigin(methodBody, ilOffset); + } + public override string ToString() { if (FileName == null) From 59e5ddd1e602c26bf19ba0c53b9409d45aab36b6 Mon Sep 17 00:00:00 2001 From: vitek-karas <10670590+vitek-karas@users.noreply.github.com> Date: Wed, 15 Jun 2022 11:35:24 -0700 Subject: [PATCH 03/18] Small fix --- .../Compiler/Dataflow/GenericArgumentDataFlow.cs | 2 +- .../aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceValue.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/GenericArgumentDataFlow.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/GenericArgumentDataFlow.cs index e429ae22919f5..9de4cfdf7849c 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/GenericArgumentDataFlow.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/GenericArgumentDataFlow.cs @@ -14,7 +14,7 @@ using DependencyList = ILCompiler.DependencyAnalysisFramework.DependencyNodeCore.DependencyList; using MultiValue = ILLink.Shared.DataFlow.ValueSet; -#nullable enabled +#nullable enable namespace ILCompiler.Dataflow { diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceValue.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceValue.cs index d8bd9b0f6b05b..e08b955df6f44 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceValue.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceValue.cs @@ -3,7 +3,7 @@ using ILLink.Shared.DataFlow; -#nullable enabled +#nullable enable namespace ILLink.Shared.TrimAnalysis { From 67024175e0b6204542ad56bb6c6933f22fc86377 Mon Sep 17 00:00:00 2001 From: vitek-karas <10670590+vitek-karas@users.noreply.github.com> Date: Thu, 16 Jun 2022 13:33:09 -0700 Subject: [PATCH 04/18] Improvements to warning suppression and general cleanup of diagnostics handling --- .../Compiler/Dataflow/DiagnosticContext.cs | 39 ++++++- .../Compiler/Dataflow/DiagnosticUtilities.cs | 29 +++++ .../Compiler/Dataflow/FlowAnnotations.cs | 8 ++ .../Dataflow/GenericArgumentDataFlow.cs | 5 +- .../Compiler/Dataflow/ReflectionMarker.cs | 43 ++++---- .../Dataflow/ReflectionMethodBodyScanner.cs | 102 ++++++------------ .../Dataflow/TrimAnalysisAssignmentPattern.cs | 16 ++- .../Dataflow/TrimAnalysisMethodCallPattern.cs | 8 +- .../tools/aot/ILLink.Shared/DiagnosticId.cs | 21 ++++ .../TrimAnalysis/DiagnosticContext.cs | 11 ++ .../ILLink.Shared/TrimAnalysis/IntrinsicId.cs | 1 - .../TypeSystemProxy/WellKnownType.cs | 4 +- 12 files changed, 178 insertions(+), 109 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/DiagnosticContext.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/DiagnosticContext.cs index fb5551b1c6a00..622c6356b2732 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/DiagnosticContext.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/DiagnosticContext.cs @@ -3,6 +3,7 @@ using ILCompiler; using ILCompiler.Logging; +using Internal.Metadata.NativeFormat; #nullable enable @@ -11,16 +12,46 @@ namespace ILLink.Shared.TrimAnalysis public readonly partial struct DiagnosticContext { public readonly MessageOrigin Origin; - public readonly bool DiagnosticsEnabled; + readonly bool _diagnosticsEnabled; + readonly bool _suppressTrimmerDiagnostics; + readonly bool _suppressAotDiagnostics; + readonly bool _suppressSingleFileDiagnostics; readonly Logger _logger; public DiagnosticContext(in MessageOrigin origin, bool diagnosticsEnabled, Logger logger) - => (Origin, DiagnosticsEnabled, _logger) = (origin, diagnosticsEnabled, logger); + { + Origin = origin; + _diagnosticsEnabled = diagnosticsEnabled; + _suppressTrimmerDiagnostics = false; + _suppressAotDiagnostics = false; + _suppressSingleFileDiagnostics = false; + _logger = logger; + } + + public DiagnosticContext(in MessageOrigin origin, bool suppressTrimmerDiagnostics, bool suppressAotDiagnostics, bool suppressSingleFileDiagnostics, Logger logger) + { + Origin = origin; + _diagnosticsEnabled = true; + _suppressTrimmerDiagnostics = suppressTrimmerDiagnostics; + _suppressAotDiagnostics = suppressAotDiagnostics; + _suppressSingleFileDiagnostics = suppressSingleFileDiagnostics; + _logger = logger; + } public partial void AddDiagnostic(DiagnosticId id, params string[] args) { - if (DiagnosticsEnabled) - _logger.LogWarning(Origin, id, args); + if (!_diagnosticsEnabled) + return; + + string category = id.GetDiagnosticCategory(); + if (_suppressTrimmerDiagnostics && category == DiagnosticCategory.Trimming) + return; + if (_suppressAotDiagnostics && category == DiagnosticCategory.AOT) + return; + if (_suppressSingleFileDiagnostics && category == DiagnosticCategory.SingleFile) + return; + + _logger.LogWarning(Origin, id, args); } } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/DiagnosticUtilities.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/DiagnosticUtilities.cs index 5cf498bcfc69b..17189f421fcb5 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/DiagnosticUtilities.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/DiagnosticUtilities.cs @@ -200,5 +200,34 @@ internal static bool DoesMemberRequire(this TypeSystemEntity member, string requ _ => false }; } + + internal static bool ShouldSuppressAnalysisWarningsForRequires(TypeSystemEntity originMember, string requiresAttribute) + { + // Check if the current scope method has Requires on it + // since that attribute automatically suppresses all trim analysis warnings. + // Check both the immediate origin method as well as suppression context method + // since that will be different for compiler generated code. + if (originMember == null) + return false; + + // TODO - handle fields and other members + // Other types of members still need to go through CompilerGeneratedState check + if (originMember is not MethodDesc method) + return false; + + if (method.IsInRequiresScope(requiresAttribute)) + return true; + + MethodDesc userMethod = ILCompiler.Logging.CompilerGeneratedState.GetUserDefinedMethodForCompilerGeneratedMember(method); + if (userMethod != null && + userMethod.IsInRequiresScope(requiresAttribute)) + return true; + + return false; + } + + internal const string RequiresUnreferencedCodeAttribute = nameof(RequiresUnreferencedCodeAttribute); + internal const string RequiresDynamicCodeAttribute = nameof(RequiresDynamicCodeAttribute); + internal const string RequiresAssemblyFilesAttribute = nameof(RequiresAssemblyFilesAttribute); } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/FlowAnnotations.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/FlowAnnotations.cs index ba3f9e22beda9..a00c084b1111f 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/FlowAnnotations.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/FlowAnnotations.cs @@ -123,6 +123,14 @@ public DynamicallyAccessedMemberTypes GetTypeAnnotation(TypeDesc type) return GetAnnotations(type.GetTypeDefinition()).TypeAnnotation; } + public bool ShouldWarnWhenAccessedForReflection(TypeSystemEntity entity) => + entity switch + { + MethodDesc method => ShouldWarnWhenAccessedForReflection(method), + FieldDesc field => ShouldWarnWhenAccessedForReflection(field), + _ => false + }; + public DynamicallyAccessedMemberTypes GetGenericParameterAnnotation(GenericParameterDesc genericParameter) { if (genericParameter is not EcmaGenericParameter ecmaGenericParameter) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/GenericArgumentDataFlow.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/GenericArgumentDataFlow.cs index 9de4cfdf7849c..388dbb6f700d3 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/GenericArgumentDataFlow.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/GenericArgumentDataFlow.cs @@ -40,7 +40,10 @@ public DependencyList ProcessGenericArgumentDataFlow(GenericParameterDesc generi MultiValue genericArgumentValue = _annotations.GetTypeValueFromGenericArgument(genericArgument); - var diagnosticContext = new DiagnosticContext(_origin, !_context.Annotations.ShouldSuppressAnalysisWarningsForRequiresUnreferencedCode(_origin.Provider), _logger); + var diagnosticContext = new DiagnosticContext( + _origin, + DiagnosticUtilities.ShouldSuppressAnalysisWarningsForRequires(_origin.MemberDefinition, DiagnosticUtilities.RequiresUnreferencedCodeAttribute), + _logger); return RequireDynamicallyAccessedMembers(diagnosticContext, genericArgumentValue, genericParameterValue, new GenericParameterOrigin(genericParameter)); } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMarker.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMarker.cs index 55111bb7c7040..ca018c707039d 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMarker.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMarker.cs @@ -2,9 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.ComponentModel.DataAnnotations; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Reflection; +using System.Reflection.Metadata; using ILCompiler.DependencyAnalysis; using ILCompiler.Logging; using ILLink.Shared; @@ -25,8 +27,6 @@ public class ReflectionMarker public FlowAnnotations Annotations { get; } private bool _typeHierarchyDataFlow; private bool _enabled; - private const string RequiresUnreferencedCodeAttribute = nameof(RequiresUnreferencedCodeAttribute); - public DependencyList Dependencies { get => _dependencies; } public ReflectionMarker(Logger logger, NodeFactory factory, FlowAnnotations annotations, bool typeHierarchyDataFlow, bool enabled) @@ -106,19 +106,7 @@ internal void MarkMethod(in MessageOrigin origin, MethodDesc method, Origin memb if (!_enabled) return; - if (method.DoesMethodRequire(RequiresUnreferencedCodeAttribute, out _)) - { - if (_typeHierarchyDataFlow) - { - _logger.LogWarning(origin, DiagnosticId.DynamicallyAccessedMembersOnTypeReferencesMemberOnBaseWithRequiresUnreferencedCode, - ((TypeOrigin)memberWithRequirements).GetDisplayName(), method.GetDisplayName()); - } - } - - if (Annotations.ShouldWarnWhenAccessedForReflection(method) && !ReflectionMethodBodyScanner.ShouldSuppressAnalysisWarningsForRequires(method, RequiresUnreferencedCodeAttribute)) - { - WarnOnReflectionAccess(origin, method, memberWithRequirements); - } + CheckAndWarnOnReflectionAccess(origin, method, memberWithRequirements); RootingHelpers.TryGetDependenciesForReflectedMethod(ref _dependencies, Factory, method, memberWithRequirements.ToString()); } @@ -128,10 +116,7 @@ void MarkField(in MessageOrigin origin, FieldDesc field, Origin memberWithRequir if (!_enabled) return; - if (Annotations.ShouldWarnWhenAccessedForReflection(field) && !ReflectionMethodBodyScanner.ShouldSuppressAnalysisWarningsForRequires(origin.MemberDefinition, RequiresUnreferencedCodeAttribute)) - { - WarnOnReflectionAccess(origin, field, memberWithRequirements); - } + CheckAndWarnOnReflectionAccess(origin, field, memberWithRequirements); RootingHelpers.TryGetDependenciesForReflectedField(ref _dependencies, Factory, field, memberWithRequirements.ToString()); } @@ -207,9 +192,23 @@ internal void MarkStaticConstructor(in MessageOrigin origin, TypeDesc type) } } - // TODO: This should use DiagnosticContext to avoid generating warnings when we run data flow on a method multiple times - void WarnOnReflectionAccess(in MessageOrigin origin, TypeSystemEntity entity, Origin memberWithRequirements) + void CheckAndWarnOnReflectionAccess(in MessageOrigin origin, TypeSystemEntity entity, Origin memberWithRequirements) { + if (entity.DoesMemberRequire(DiagnosticUtilities.RequiresUnreferencedCodeAttribute, out CustomAttributeValue? requiresAttribute)) + { + if (_typeHierarchyDataFlow) + { + _logger.LogWarning(origin, DiagnosticId.DynamicallyAccessedMembersOnTypeReferencesMemberOnBaseWithRequiresUnreferencedCode, + ((TypeOrigin)memberWithRequirements).GetDisplayName(), + entity.GetDisplayName(), + MessageFormat.FormatRequiresAttributeMessageArg(DiagnosticUtilities.GetRequiresAttributeMessage(requiresAttribute.Value)), + MessageFormat.FormatRequiresAttributeUrlArg(DiagnosticUtilities.GetRequiresAttributeUrl(requiresAttribute.Value))); + } + } + + if (!Annotations.ShouldWarnWhenAccessedForReflection(entity)) + return; + if (_typeHierarchyDataFlow) { // Don't check whether the current scope is a RUC type or RUC method because these warnings @@ -221,7 +220,7 @@ void WarnOnReflectionAccess(in MessageOrigin origin, TypeSystemEntity entity, Or } else { - if (!ReflectionMethodBodyScanner.ShouldSuppressAnalysisWarningsForRequires(origin.MemberDefinition, RequiresUnreferencedCodeAttribute)) + if (!DiagnosticUtilities.ShouldSuppressAnalysisWarningsForRequires(origin.MemberDefinition, DiagnosticUtilities.RequiresUnreferencedCodeAttribute)) { if (entity is FieldDesc) { diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMethodBodyScanner.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMethodBodyScanner.cs index c273cb28bc2aa..a7fe0d173801c 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMethodBodyScanner.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMethodBodyScanner.cs @@ -7,6 +7,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Reflection; using System.Reflection.Metadata; using ILCompiler.Logging; using ILLink.Shared; @@ -37,17 +38,12 @@ class ReflectionMethodBodyScanner : MethodBodyScanner private MessageOrigin _origin; - private const string RequiresUnreferencedCodeAttribute = nameof(RequiresUnreferencedCodeAttribute); - private const string RequiresDynamicCodeAttribute = nameof(RequiresDynamicCodeAttribute); - private const string RequiresAssemblyFilesAttribute = nameof(RequiresAssemblyFilesAttribute); - - public static bool RequiresReflectionMethodBodyScannerForCallSite(FlowAnnotations flowAnnotations, MethodDesc methodDefinition) { return Intrinsics.GetIntrinsicIdForMethod(methodDefinition) > IntrinsicId.RequiresReflectionBodyScanner_Sentinel || flowAnnotations.RequiresDataflowAnalysis(methodDefinition) || - methodDefinition.DoesMethodRequire(RequiresUnreferencedCodeAttribute, out _) || - methodDefinition.DoesMethodRequire(RequiresDynamicCodeAttribute, out _) || + methodDefinition.DoesMethodRequire(DiagnosticUtilities.RequiresUnreferencedCodeAttribute, out _) || + methodDefinition.DoesMethodRequire(DiagnosticUtilities.RequiresDynamicCodeAttribute, out _) || methodDefinition.IsPInvoke; } @@ -60,62 +56,27 @@ public static bool RequiresReflectionMethodBodyScannerForMethodBody(FlowAnnotati public static bool RequiresReflectionMethodBodyScannerForAccess(FlowAnnotations flowAnnotations, FieldDesc fieldDefinition) { return flowAnnotations.RequiresDataflowAnalysis(fieldDefinition) || - fieldDefinition.DoesFieldRequire(RequiresUnreferencedCodeAttribute, out _) || - fieldDefinition.DoesFieldRequire(RequiresDynamicCodeAttribute, out _); + fieldDefinition.DoesFieldRequire(DiagnosticUtilities.RequiresUnreferencedCodeAttribute, out _) || + fieldDefinition.DoesFieldRequire(DiagnosticUtilities.RequiresDynamicCodeAttribute, out _); } - // TODO: This should use DiagnosticContext so that it avoids generating warnings in cases we run data flow - // on a method multiple times. - void CheckAndReportRequires(TypeSystemEntity calledMember, in MessageOrigin origin, string requiresAttributeName) + static void CheckAndReportRequires(in DiagnosticContext diagnosticContext, TypeSystemEntity calledMember, string requiresAttributeName) { - // If the caller of a method is already marked with `Requires` a new warning should not - // be produced for the callee. - if (ShouldSuppressAnalysisWarningsForRequires(origin.MemberDefinition, requiresAttributeName)) - return; - if (!calledMember.DoesMemberRequire(requiresAttributeName, out var requiresAttribute)) return; DiagnosticId diagnosticId = requiresAttributeName switch { - RequiresUnreferencedCodeAttribute => DiagnosticId.RequiresUnreferencedCode, - RequiresDynamicCodeAttribute => DiagnosticId.RequiresDynamicCode, - RequiresAssemblyFilesAttribute => DiagnosticId.RequiresAssemblyFiles, + DiagnosticUtilities.RequiresUnreferencedCodeAttribute => DiagnosticId.RequiresUnreferencedCode, + DiagnosticUtilities.RequiresDynamicCodeAttribute => DiagnosticId.RequiresDynamicCode, + DiagnosticUtilities.RequiresAssemblyFilesAttribute => DiagnosticId.RequiresAssemblyFiles, _ => throw new NotImplementedException($"{requiresAttributeName} is not a valid supported Requires attribute"), }; - ReportRequires(calledMember.GetDisplayName(), origin, diagnosticId, requiresAttribute.Value); - } - - internal static bool ShouldSuppressAnalysisWarningsForRequires(TypeSystemEntity originMember, string requiresAttribute) - { - // Check if the current scope method has Requires on it - // since that attribute automatically suppresses all trim analysis warnings. - // Check both the immediate origin method as well as suppression context method - // since that will be different for compiler generated code. - if (originMember == null) - return false; - - if (originMember is not MethodDesc method) - return false; - - if (method.IsInRequiresScope(requiresAttribute)) - return true; - - MethodDesc userMethod = ILCompiler.Logging.CompilerGeneratedState.GetUserDefinedMethodForCompilerGeneratedMember(method); - if (userMethod != null && - userMethod.IsInRequiresScope(requiresAttribute)) - return true; + string arg1 = MessageFormat.FormatRequiresAttributeMessageArg(DiagnosticUtilities.GetRequiresAttributeMessage(requiresAttribute.Value)); + string arg2 = MessageFormat.FormatRequiresAttributeUrlArg(DiagnosticUtilities.GetRequiresAttributeUrl(requiresAttribute.Value)); - return false; - } - - void ReportRequires(string displayName, in MessageOrigin currentOrigin, DiagnosticId diagnosticId, CustomAttributeValue requiresAttribute) - { - string arg1 = MessageFormat.FormatRequiresAttributeMessageArg(DiagnosticUtilities.GetRequiresAttributeMessage((CustomAttributeValue)requiresAttribute)); - string arg2 = MessageFormat.FormatRequiresAttributeUrlArg(DiagnosticUtilities.GetRequiresAttributeUrl((CustomAttributeValue)requiresAttribute)); - - _logger.LogWarning(currentOrigin, diagnosticId, displayName, arg1, arg2); + diagnosticContext.AddDiagnostic(diagnosticId, calledMember.GetDisplayName(), arg1, arg2); } private ReflectionMethodBodyScanner(NodeFactory factory, FlowAnnotations annotations, Logger logger, MessageOrigin origin) @@ -146,7 +107,7 @@ protected override void Scan(MethodIL methodBody, ref ValueSet meth var method = methodBody.OwningMethod; var methodReturnValue = _annotations.GetMethodReturnValue(method); if (methodReturnValue.DynamicallyAccessedMemberTypes != 0) - HandleAssignmentPattern(_origin, ReturnValue, methodReturnValue); + HandleAssignmentPattern(_origin, ReturnValue, methodReturnValue, new MethodOrigin(method)); } } @@ -217,29 +178,29 @@ ValueWithDynamicallyAccessedMembers GetMethodParameterValue(MethodDesc method, i protected override MultiValue GetFieldValue(FieldDesc field) => _annotations.GetFieldValue(field); - private void HandleStoreValueWithDynamicallyAccessedMembers(MethodIL methodBody, int offset, ValueWithDynamicallyAccessedMembers targetValue, MultiValue sourceValue) + private void HandleStoreValueWithDynamicallyAccessedMembers(MethodIL methodBody, int offset, ValueWithDynamicallyAccessedMembers targetValue, MultiValue sourceValue, Origin memberWithRequirements) { if (targetValue.DynamicallyAccessedMemberTypes != 0) { _origin = _origin.WithInstructionOffset(methodBody, offset); - HandleAssignmentPattern(_origin, sourceValue, targetValue); + HandleAssignmentPattern(_origin, sourceValue, targetValue, memberWithRequirements); } } protected override void HandleStoreField(MethodIL methodBody, int offset, FieldValue field, MultiValue valueToStore) - => HandleStoreValueWithDynamicallyAccessedMembers(methodBody, offset, field, valueToStore); + => HandleStoreValueWithDynamicallyAccessedMembers(methodBody, offset, field, valueToStore, new FieldOrigin(field.Field)); // TODO: The previous HandleStoreField also did this: // CheckAndReportRequires(field.Field, new MessageOrigin(methodBody.OwningMethod), RequiresUnreferencedCodeAttribute); // CheckAndReportRequires(field.Field, new MessageOrigin(methodBody.OwningMethod), RequiresDynamicCodeAttribute); protected override void HandleStoreParameter(MethodIL methodBody, int offset, MethodParameterValue parameter, MultiValue valueToStore) - => HandleStoreValueWithDynamicallyAccessedMembers(methodBody, offset, parameter, valueToStore); + => HandleStoreValueWithDynamicallyAccessedMembers(methodBody, offset, parameter, valueToStore, parameter.ParameterOrigin); protected override void HandleStoreMethodThisParameter(MethodIL methodBody, int offset, MethodThisParameterValue thisParameter, MultiValue valueToStore) - => HandleStoreValueWithDynamicallyAccessedMembers(methodBody, offset, thisParameter, valueToStore); + => HandleStoreValueWithDynamicallyAccessedMembers(methodBody, offset, thisParameter, valueToStore, new ParameterOrigin(thisParameter.Method, 0)); protected override void HandleStoreMethodReturnValue(MethodIL methodBody, int offset, MethodReturnValue returnValue, MultiValue valueToStore) - => HandleStoreValueWithDynamicallyAccessedMembers(methodBody, offset, returnValue, valueToStore); + => HandleStoreValueWithDynamicallyAccessedMembers(methodBody, offset, returnValue, valueToStore, new MethodOrigin(returnValue.Method)); public override bool HandleCall(MethodIL callingMethodBody, MethodDesc calledMethod, ILOpcode operation, int offset, ValueNodeList methodParams, out MultiValue methodReturnValue) { @@ -295,9 +256,8 @@ public static bool HandleCall( ReflectionMarker reflectionMarker, out MultiValue methodReturnValue) { - var origin = diagnosticContext.Origin; var callingMethodDefinition = callingMethodBody.OwningMethod; - Debug.Assert(callingMethodDefinition != origin.MemberDefinition); + Debug.Assert(callingMethodDefinition == diagnosticContext.Origin.MemberDefinition); bool requiresDataFlowAnalysis = reflectionMarker.Annotations.RequiresDataflowAnalysis(calledMethod); var annotatedMethodReturnValue = reflectionMarker.Annotations.GetMethodReturnValue(calledMethod); @@ -305,9 +265,6 @@ public static bool HandleCall( MultiValue? maybeMethodReturnValue = null; - bool shouldEnableReflectionWarnings = !ShouldSuppressAnalysisWarningsForRequires(callingMethodDefinition, RequiresUnreferencedCodeAttribute); - bool shouldEnableAotWarnings = !ShouldSuppressAnalysisWarningsForRequires(callingMethodDefinition, RequiresDynamicCodeAttribute); - var handleCallAction = new HandleCallAction(reflectionMarker.Annotations, reflectionMarker, diagnosticContext, callingMethodDefinition, new MethodOrigin(calledMethod)); var intrinsicId = Intrinsics.GetIntrinsicIdForMethod(calledMethod); @@ -365,7 +322,7 @@ public static bool HandleCall( { case IntrinsicId.Type_MakeGenericType: case IntrinsicId.MethodInfo_MakeGenericMethod: - CheckAndReportRequires(calledMethod, new MessageOrigin(callingMethodBody, offset), RequiresDynamicCodeAttribute); + CheckAndReportRequires(diagnosticContext, calledMethod, DiagnosticUtilities.RequiresDynamicCodeAttribute); break; } @@ -400,9 +357,9 @@ public static bool HandleCall( } } - CheckAndReportRequires(calledMethod, origin, RequiresUnreferencedCodeAttribute); - CheckAndReportRequires(calledMethod, origin, RequiresDynamicCodeAttribute); - CheckAndReportRequires(calledMethod, origin, RequiresAssemblyFilesAttribute); + CheckAndReportRequires(diagnosticContext, calledMethod, DiagnosticUtilities.RequiresUnreferencedCodeAttribute); + CheckAndReportRequires(diagnosticContext, calledMethod, DiagnosticUtilities.RequiresDynamicCodeAttribute); + CheckAndReportRequires(diagnosticContext, calledMethod, DiagnosticUtilities.RequiresAssemblyFilesAttribute); return handleCallAction.Invoke(calledMethod, instanceValue, argumentValues, out methodReturnValue, out _); } @@ -445,7 +402,7 @@ public static bool HandleCall( } } else - CheckAndReportRequires(calledMethod, origin, RequiresDynamicCodeAttribute); + CheckAndReportRequires(diagnosticContext, calledMethod, DiagnosticUtilities.RequiresDynamicCodeAttribute); } } break; @@ -486,7 +443,7 @@ public static bool HandleCall( } } else - CheckAndReportRequires(calledMethod, origin, RequiresDynamicCodeAttribute); + CheckAndReportRequires(diagnosticContext, calledMethod, DiagnosticUtilities.RequiresDynamicCodeAttribute); } } break; @@ -511,7 +468,7 @@ public static bool HandleCall( } } else - CheckAndReportRequires(calledMethod, origin, RequiresDynamicCodeAttribute); + CheckAndReportRequires(diagnosticContext, calledMethod, DiagnosticUtilities.RequiresDynamicCodeAttribute); } } break; @@ -709,9 +666,10 @@ static bool IsComInterop(MarshalAsDescriptor? marshalInfoProvider, TypeDesc para void HandleAssignmentPattern( in MessageOrigin origin, in MultiValue value, - ValueWithDynamicallyAccessedMembers targetValue) + ValueWithDynamicallyAccessedMembers targetValue, + Origin memberWithRequirements) { - TrimAnalysisPatterns.Add(new TrimAnalysisAssignmentPattern(value, targetValue, origin)); + TrimAnalysisPatterns.Add(new TrimAnalysisAssignmentPattern(value, targetValue, origin, memberWithRequirements)); } } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisAssignmentPattern.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisAssignmentPattern.cs index f31107cf20789..803fb3e5d04c3 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisAssignmentPattern.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisAssignmentPattern.cs @@ -2,8 +2,10 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; + using ILCompiler.Logging; using ILLink.Shared.TrimAnalysis; + using MultiValue = ILLink.Shared.DataFlow.ValueSet; #nullable enable @@ -15,18 +17,24 @@ public readonly record struct TrimAnalysisAssignmentPattern public MultiValue Source { init; get; } public MultiValue Target { init; get; } public MessageOrigin Origin { init; get; } + internal Origin MemberWithRequirements { init; get; } - public TrimAnalysisAssignmentPattern(MultiValue source, MultiValue target, MessageOrigin origin) + internal TrimAnalysisAssignmentPattern(MultiValue source, MultiValue target, MessageOrigin origin, Origin memberWithRequirements) { Source = source.Clone(); Target = target.Clone(); Origin = origin; + MemberWithRequirements = memberWithRequirements; } public void MarkAndProduceDiagnostics(ReflectionMarker reflectionMarker, Logger logger) { - bool diagnosticsEnabled = !context.Annotations.ShouldSuppressAnalysisWarningsForRequiresUnreferencedCode(Origin.MemberDefinition); - var diagnosticContext = new DiagnosticContext(Origin, diagnosticsEnabled, logger); + var diagnosticContext = new DiagnosticContext( + Origin, + DiagnosticUtilities.ShouldSuppressAnalysisWarningsForRequires(Origin.MemberDefinition, DiagnosticUtilities.RequiresUnreferencedCodeAttribute), + DiagnosticUtilities.ShouldSuppressAnalysisWarningsForRequires(Origin.MemberDefinition, DiagnosticUtilities.RequiresDynamicCodeAttribute), + DiagnosticUtilities.ShouldSuppressAnalysisWarningsForRequires(Origin.MemberDefinition, DiagnosticUtilities.RequiresAssemblyFilesAttribute), + logger); foreach (var sourceValue in Source) { @@ -35,7 +43,7 @@ public void MarkAndProduceDiagnostics(ReflectionMarker reflectionMarker, Logger if (targetValue is not ValueWithDynamicallyAccessedMembers targetWithDynamicallyAccessedMembers) throw new NotImplementedException(); - var requireDynamicallyAccessedMembersAction = new RequireDynamicallyAccessedMembersAction(reflectionMarker, diagnosticContext); + var requireDynamicallyAccessedMembersAction = new RequireDynamicallyAccessedMembersAction(reflectionMarker, diagnosticContext, MemberWithRequirements); requireDynamicallyAccessedMembersAction.Invoke(sourceValue, targetWithDynamicallyAccessedMembers); } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisMethodCallPattern.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisMethodCallPattern.cs index b8a1390be06e4..a9280b4af7841 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisMethodCallPattern.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisMethodCallPattern.cs @@ -54,8 +54,12 @@ public TrimAnalysisMethodCallPattern( public void MarkAndProduceDiagnostics(ReflectionMarker reflectionMarker, Logger logger) { - bool diagnosticsEnabled = !context.Annotations.ShouldSuppressAnalysisWarningsForRequiresUnreferencedCode(Origin.MemberDefinition); - var diagnosticContext = new DiagnosticContext(Origin, diagnosticsEnabled, logger); + var diagnosticContext = new DiagnosticContext( + Origin, + DiagnosticUtilities.ShouldSuppressAnalysisWarningsForRequires(Origin.MemberDefinition, DiagnosticUtilities.RequiresUnreferencedCodeAttribute), + DiagnosticUtilities.ShouldSuppressAnalysisWarningsForRequires(Origin.MemberDefinition, DiagnosticUtilities.RequiresDynamicCodeAttribute), + DiagnosticUtilities.ShouldSuppressAnalysisWarningsForRequires(Origin.MemberDefinition, DiagnosticUtilities.RequiresAssemblyFilesAttribute), + logger); ReflectionMethodBodyScanner.HandleCall(MethodBody, CalledMethod, Operation, Offset, Instance, Arguments, diagnosticContext, reflectionMarker, diff --git a/src/coreclr/tools/aot/ILLink.Shared/DiagnosticId.cs b/src/coreclr/tools/aot/ILLink.Shared/DiagnosticId.cs index 8b7f2166becf3..e90d73d01c613 100644 --- a/src/coreclr/tools/aot/ILLink.Shared/DiagnosticId.cs +++ b/src/coreclr/tools/aot/ILLink.Shared/DiagnosticId.cs @@ -4,6 +4,8 @@ // This is needed due to NativeAOT which doesn't enable nullable globally yet #nullable enable +using System; + namespace ILLink.Shared { public enum DiagnosticId @@ -223,5 +225,24 @@ public static string GetDiagnosticSubcategory (this DiagnosticId diagnosticId) = >= 3054 and <= 3055 => MessageSubCategory.AotAnalysis, _ => MessageSubCategory.None, }; + + public static string GetDiagnosticCategory (this DiagnosticId diagnosticId) + { + switch ((int) diagnosticId) { + case > 2000 and < 3000: + return DiagnosticCategory.Trimming; + + case >= 3000 and < 3050: + return DiagnosticCategory.SingleFile; + + case >= 3050 and <= 6000: + return DiagnosticCategory.AOT; + + default: + break; + } + + throw new ArgumentException ($"The provided diagnostic id '{diagnosticId}' does not fall into the range of supported warning codes 2001 to 6000 (inclusive)."); + } } } diff --git a/src/coreclr/tools/aot/ILLink.Shared/TrimAnalysis/DiagnosticContext.cs b/src/coreclr/tools/aot/ILLink.Shared/TrimAnalysis/DiagnosticContext.cs index 06353c222852e..95c984d7fda51 100644 --- a/src/coreclr/tools/aot/ILLink.Shared/TrimAnalysis/DiagnosticContext.cs +++ b/src/coreclr/tools/aot/ILLink.Shared/TrimAnalysis/DiagnosticContext.cs @@ -8,6 +8,17 @@ namespace ILLink.Shared.TrimAnalysis { readonly partial struct DiagnosticContext { + /// + /// The diagnostic context may be entirely disabled or some kinds of warnings may be suppressed. + /// The suppressions are determined based on the . + /// Typically the suppressions will be based on diagnostic category : + /// - Trimmer warnings (suppressed by RequiresUnreferencedCodeAttribute) + /// - AOT warnings (suppressed by RequiresDynamicCodeAttribute) + /// - Single-file warnings (suppressed by RequiresAssemblyFilesAttribute) + /// Note that not all categories are used/supported by all tools, for example the ILLink only handles trimmer warnings and ignores the rest. + /// + /// The diagnostic ID, this will be used to determine the category of diagnostic (trimmer, AOT, single-file) + /// The arguments for diagnostic message. public partial void AddDiagnostic (DiagnosticId id, params string[] args); } } diff --git a/src/coreclr/tools/aot/ILLink.Shared/TrimAnalysis/IntrinsicId.cs b/src/coreclr/tools/aot/ILLink.Shared/TrimAnalysis/IntrinsicId.cs index bd9d8df893a2c..7a2a40b068ac9 100644 --- a/src/coreclr/tools/aot/ILLink.Shared/TrimAnalysis/IntrinsicId.cs +++ b/src/coreclr/tools/aot/ILLink.Shared/TrimAnalysis/IntrinsicId.cs @@ -6,7 +6,6 @@ namespace ILLink.Shared.TrimAnalysis { - [StaticCs.Closed] enum IntrinsicId { None = 0, diff --git a/src/coreclr/tools/aot/ILLink.Shared/TypeSystemProxy/WellKnownType.cs b/src/coreclr/tools/aot/ILLink.Shared/TypeSystemProxy/WellKnownType.cs index 999f8f4dc95f2..3503f7b021cd7 100644 --- a/src/coreclr/tools/aot/ILLink.Shared/TypeSystemProxy/WellKnownType.cs +++ b/src/coreclr/tools/aot/ILLink.Shared/TypeSystemProxy/WellKnownType.cs @@ -1,14 +1,11 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. -using StaticCs; - // This is needed due to NativeAOT which doesn't enable nullable globally yet #nullable enable namespace ILLink.Shared.TypeSystemProxy { - [Closed] public enum WellKnownType { System_String, @@ -38,6 +35,7 @@ public static (string Namespace, string Name) GetNamespaceAndName (this WellKnow WellKnownType.System_NotSupportedException => ("System", "NotSupportedException"), WellKnownType.System_Runtime_CompilerServices_DisablePrivateReflectionAttribute => ("System.Runtime.CompilerServices", "DisablePrivateReflectionAttribute"), WellKnownType.System_Void => ("System", "Void"), + _ => throw new System.NotImplementedException() }; } public static string GetNamespace (this WellKnownType type) => GetNamespaceAndName (type).Namespace; From 56db8596ac0d01faf8cf1c61eb7a5b221fdf1d37 Mon Sep 17 00:00:00 2001 From: vitek-karas <10670590+vitek-karas@users.noreply.github.com> Date: Fri, 24 Jun 2022 03:35:50 -0700 Subject: [PATCH 05/18] Progress on CompilerGeneratedState --- .../Dataflow/CompilerGeneratedCallGraph.cs | 67 +++ .../Dataflow/CompilerGeneratedNames.cs | 72 +++ .../Dataflow/CompilerGeneratedState.cs | 524 ++++++++++++++++++ .../Compiler/Dataflow/DiagnosticUtilities.cs | 3 +- .../Compiler/Dataflow/MethodBodyScanner.cs | 54 +- .../Dataflow/ReflectionMethodBodyScanner.cs | 20 - .../ILCompiler.Compiler/Compiler/Logger.cs | 1 + .../Logging/CompilerGeneratedState.cs | 50 -- .../ILCompiler.Compiler.csproj | 6 +- src/coreclr/tools/aot/ILCompiler/Program.cs | 3 + 10 files changed, 717 insertions(+), 83 deletions(-) create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedCallGraph.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedNames.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedState.cs delete mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logging/CompilerGeneratedState.cs diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedCallGraph.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedCallGraph.cs new file mode 100644 index 0000000000000..55d412a1cb638 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedCallGraph.cs @@ -0,0 +1,67 @@ +// 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 System.Diagnostics; + +using Internal.TypeSystem; +using Internal.TypeSystem.Ecma; + +#nullable enable + +namespace ILCompiler.Dataflow +{ + sealed class CompilerGeneratedCallGraph + { + readonly Dictionary> callGraph; + + public CompilerGeneratedCallGraph () => callGraph = new Dictionary> (); + + void TrackCallInternal (TypeSystemEntity fromMember, TypeSystemEntity toMember) + { + if (!callGraph.TryGetValue (fromMember, out HashSet? toMembers)) { + toMembers = new HashSet (); + callGraph.Add (fromMember, toMembers); + } + toMembers.Add (toMember); + } + + public void TrackCall (MethodDesc fromMethod, MethodDesc toMethod) + { + Debug.Assert (CompilerGeneratedNames.IsLambdaOrLocalFunction (toMethod.Name)); + TrackCallInternal (fromMethod, toMethod); + } + + public void TrackCall (MethodDesc fromMethod, DefType toType) + { + Debug.Assert (CompilerGeneratedNames.IsStateMachineType (toType.Name)); + TrackCallInternal (fromMethod, toType); + } + + public void TrackCall (DefType fromType, MethodDesc toMethod) + { + Debug.Assert (CompilerGeneratedNames.IsStateMachineType (fromType.Name)); + Debug.Assert (CompilerGeneratedNames.IsLambdaOrLocalFunction (toMethod.Name)); + TrackCallInternal (fromType, toMethod); + } + + public IEnumerable GetReachableMembers (MethodDesc start) + { + Queue queue = new (); + HashSet visited = new (); + visited.Add (start); + queue.Enqueue (start); + while (queue.TryDequeue (out TypeSystemEntity? method)) { + if (!callGraph.TryGetValue (method, out HashSet? callees)) + continue; + + foreach (var callee in callees) { + if (visited.Add (callee)) { + queue.Enqueue (callee); + yield return callee; + } + } + } + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedNames.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedNames.cs new file mode 100644 index 0000000000000..eeeaedd122b05 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedNames.cs @@ -0,0 +1,72 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#nullable enable + +namespace ILCompiler.Dataflow +{ + sealed class CompilerGeneratedNames + { + internal static bool IsGeneratedMemberName(string memberName) + { + return memberName.Length > 0 && memberName[0] == '<'; + } + + internal static bool IsLambdaDisplayClass(string className) + { + if (!IsGeneratedMemberName(className)) + return false; + + // This is true for static lambdas (which are emitted into a class like <>c) + // and for instance lambdas (which are emitted into a class like <>c__DisplayClass1_0) + return className.StartsWith("<>c"); + } + + internal static bool IsStateMachineType(string typeName) + { + if (!IsGeneratedMemberName(typeName)) + return false; + + int i = typeName.LastIndexOf('>'); + if (i == -1) + return false; + + return typeName.Length > i + 1 && typeName[i + 1] == 'd'; + } + + internal static bool IsGeneratedType(string name) => IsStateMachineType(name) || IsLambdaDisplayClass(name); + + internal static bool IsLambdaOrLocalFunction(string methodName) => IsLambdaMethod(methodName) || IsLocalFunction(methodName); + + // Lambda methods have generated names like "b__0_1" where "UserMethod" is the name + // of the original user code that contains the lambda method declaration. + internal static bool IsLambdaMethod(string methodName) + { + if (!IsGeneratedMemberName(methodName)) + return false; + + int i = methodName.IndexOf('>', 1); + if (i == -1) + return false; + + // Ignore the method ordinal/generation and lambda ordinal/generation. + return methodName.Length > i + 1 && methodName[i + 1] == 'b'; + } + + // Local functions have generated names like "g__LocalFunction|0_1" where "UserMethod" is the name + // of the original user code that contains the lambda method declaration, and "LocalFunction" is the name of + // the local function. + internal static bool IsLocalFunction(string methodName) + { + if (!IsGeneratedMemberName(methodName)) + return false; + + int i = methodName.IndexOf('>', 1); + if (i == -1) + return false; + + // Ignore the method ordinal/generation and local function ordinal/generation. + return methodName.Length > i + 1 && methodName[i + 1] == 'g'; + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedState.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedState.cs new file mode 100644 index 0000000000000..ded9e70b6b9fd --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedState.cs @@ -0,0 +1,524 @@ +// 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 System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Reflection.PortableExecutable; +using ILCompiler.Logging; +using ILLink.Shared; +using Internal.IL; +using Internal.TypeSystem; +using Internal.TypeSystem.Ecma; + +#nullable enable + +namespace ILCompiler.Dataflow +{ + // Currently this is implemented using heuristics + public class CompilerGeneratedState + { + readonly ILProvider _ilProvider; + readonly Dictionary _compilerGeneratedTypeToUserCodeMethod; + readonly Dictionary _generatedTypeToTypeArgumentInfo; + readonly record struct TypeArgumentInfo( + /// The method which calls the ctor for the given type + MethodDesc CreatingMethod, + /// Attributes for the type, pulled from the creators type arguments + IReadOnlyList? OriginalAttributes); + + readonly Dictionary _compilerGeneratedMethodToUserCodeMethod; + + // For each type that has had its cache populated, stores a map of methods which have corresponding + // compiler-generated members (either methods or state machine types) to those compiler-generated members, + // or null if the type has no methods with compiler-generated members. + readonly Dictionary>?> _cachedTypeToCompilerGeneratedMembers; + + public CompilerGeneratedState(ILProvider ilProvider) + { + _ilProvider = ilProvider; + + _compilerGeneratedTypeToUserCodeMethod = new Dictionary(); + _generatedTypeToTypeArgumentInfo = new Dictionary(); + _compilerGeneratedMethodToUserCodeMethod = new Dictionary(); + _cachedTypeToCompilerGeneratedMembers = new Dictionary>?>(); + } + + public Logger? Logger { get; set; } + + static IEnumerable GetCompilerGeneratedNestedTypes(TypeDesc type) + { + if (type is not MetadataType metadataType) + yield break; + + foreach (var nestedType in metadataType.GetNestedTypes()) + { + if (!CompilerGeneratedNames.IsGeneratedMemberName(nestedType.Name)) + continue; + + yield return nestedType; + + foreach (var recursiveNestedType in GetCompilerGeneratedNestedTypes(nestedType)) + yield return recursiveNestedType; + } + } + + // "Nested function" refers to lambdas and local functions. + public static bool IsNestedFunctionOrStateMachineMember(TypeSystemEntity member) + { + if (member is MethodDesc method && CompilerGeneratedNames.IsLambdaOrLocalFunction(method.Name)) + return true; + + if (GetOwningType(member) is not MetadataType declaringType) + return false; + + return CompilerGeneratedNames.IsStateMachineType(declaringType.Name); + } + + public static bool TryGetStateMachineType(MethodDesc method, [NotNullWhen(true)] out MetadataType? stateMachineType) + { + stateMachineType = null; + // Discover state machine methods. + if (!method.HasCustomAttributes) + return false; + + foreach (var attribute in method.CustomAttributes) + { + if (attribute.AttributeType.Namespace != "System.Runtime.CompilerServices") + continue; + + switch (attribute.AttributeType.Name) + { + case "AsyncIteratorStateMachineAttribute": + case "AsyncStateMachineAttribute": + case "IteratorStateMachineAttribute": + stateMachineType = GetFirstConstructorArgumentAsType(attribute); + return stateMachineType != null; + } + } + return false; + } + + /// + /// Walks the type and its descendents to find Roslyn-compiler generated + /// code and gather information to map it back to original user code. If + /// a compiler-generated type is passed in directly, this method will walk + /// up and find the nearest containing user type. Returns the nearest user type, + /// or null if none was found. + /// + MetadataType? PopulateCacheForType(MetadataType? type) + { + Debug.Assert(type == type?.GetTypeDefinition()); + + // Look in the declaring type if this is a compiler-generated type (state machine or display class). + // State machines can be emitted into display classes, so we may also need to go one more level up. + // To avoid depending on implementation details, we go up until we see a non-compiler-generated type. + // This is the counterpart to GetCompilerGeneratedNestedTypes. + while (type != null && CompilerGeneratedNames.IsGeneratedMemberName(type.Name)) + type = type.ContainingType as MetadataType; + + if (type is null) + return null; + + // Avoid repeat scans of the same type + if (_cachedTypeToCompilerGeneratedMembers.ContainsKey(type)) + return type; + + var callGraph = new CompilerGeneratedCallGraph(); + var userDefinedMethods = new HashSet(); + + void ProcessMethod(MethodDesc method) + { + bool isStateMachineMember = CompilerGeneratedNames.IsStateMachineType(((MetadataType)method.OwningType).Name); + if (!CompilerGeneratedNames.IsLambdaOrLocalFunction(method.Name)) + { + if (!isStateMachineMember) + { + // If it's not a nested function, track as an entry point to the call graph. + var added = userDefinedMethods.Add(method); + Debug.Assert(added); + } + } + else + { + // We don't expect lambdas or local functions to be emitted directly into + // state machine types. + Debug.Assert(!isStateMachineMember); + } + + // Discover calls or references to lambdas or local functions. This includes + // calls to local functions, and lambda assignments (which use ldftn). + var methodBody = _ilProvider.GetMethodIL(method); + if (methodBody != null) + { + ILReader reader = new ILReader(methodBody.GetILBytes()); + while (reader.HasNext) + { + ILOpcode opcode = reader.ReadILOpcode(); + MethodDesc? lambdaOrLocalFunction; + switch (opcode) { + case ILOpcode.ldftn: + case ILOpcode.ldtoken: + case ILOpcode.call: + case ILOpcode.callvirt: + case ILOpcode.newobj: + lambdaOrLocalFunction = methodBody.GetObject(reader.ReadILToken()) as MethodDesc; + break; + + default: + lambdaOrLocalFunction = null; + reader.Skip(opcode); + break; + } + + if (lambdaOrLocalFunction == null) + continue; + + if (lambdaOrLocalFunction.IsConstructor && + lambdaOrLocalFunction.OwningType is MetadataType generatedType && + // Don't consider calls in the same type, like inside a static constructor + method.OwningType != generatedType && + CompilerGeneratedNames.IsLambdaDisplayClass(generatedType.Name)) + { + // fill in null for now, attribute providers will be filled in later + if (!_generatedTypeToTypeArgumentInfo.TryAdd(generatedType, new TypeArgumentInfo(method, null))) + { + var alreadyAssociatedMethod = _generatedTypeToTypeArgumentInfo[generatedType].CreatingMethod; + Logger?.LogWarning(new MessageOrigin(method), DiagnosticId.MethodsAreAssociatedWithUserMethod, method.GetDisplayName(), alreadyAssociatedMethod.GetDisplayName(), generatedType.GetDisplayName()); + } + continue; + } + + if (!CompilerGeneratedNames.IsLambdaOrLocalFunction(lambdaOrLocalFunction.Name)) + continue; + + if (isStateMachineMember) + { + if (method.OwningType is MetadataType owningType) + { + callGraph.TrackCall(owningType, lambdaOrLocalFunction); + } + } + else + { + callGraph.TrackCall(method, lambdaOrLocalFunction); + } + } + } + + if (TryGetStateMachineType(method, out MetadataType? stateMachineType)) + { + Debug.Assert(stateMachineType.ContainingType == type || + (CompilerGeneratedNames.IsGeneratedMemberName(stateMachineType.ContainingType.Name) && + stateMachineType.ContainingType.ContainingType == type)); + callGraph.TrackCall(method, stateMachineType); + + if (!_compilerGeneratedTypeToUserCodeMethod.TryAdd(stateMachineType, method)) + { + var alreadyAssociatedMethod = _compilerGeneratedTypeToUserCodeMethod[stateMachineType]; + Logger?.LogWarning(new MessageOrigin(method), DiagnosticId.MethodsAreAssociatedWithStateMachine, method.GetDisplayName(), alreadyAssociatedMethod.GetDisplayName(), stateMachineType.GetDisplayName()); + } + // Already warned above if multiple methods map to the same type + // Fill in null for argument providers now, the real providers will be filled in later + _ = _generatedTypeToTypeArgumentInfo.TryAdd(stateMachineType, new TypeArgumentInfo(method, null)); + } + } + + // Look for state machine methods, and methods which call local functions. + foreach (MethodDesc method in type.GetMethods()) + ProcessMethod(method); + + // Also scan compiler-generated state machine methods (in case they have calls to nested functions), + // and nested functions inside compiler-generated closures (in case they call other nested functions). + + // State machines can be emitted into lambda display classes, so we need to go down at least two + // levels to find calls from iterator nested functions to other nested functions. We just recurse into + // all compiler-generated nested types to avoid depending on implementation details. + + foreach (var nestedType in GetCompilerGeneratedNestedTypes(type)) + { + foreach (var method in nestedType.GetMethods()) + ProcessMethod(method); + } + + // Now we've discovered the call graphs for calls to nested functions. + // Use this to map back from nested functions to the declaring user methods. + + // Note: This maps all nested functions back to the user code, not to the immediately + // declaring local function. The IL doesn't contain enough information in general for + // us to determine the nesting of local functions and lambdas. + + // Note: this only discovers nested functions which are referenced from the user + // code or its referenced nested functions. There is no reliable way to determine from + // IL which user code an unused nested function belongs to. + + Dictionary>? compilerGeneratedCallees = null; + foreach (var userDefinedMethod in userDefinedMethods) + { + var callees = callGraph.GetReachableMembers(userDefinedMethod); + if (!callees.Any()) + continue; + + compilerGeneratedCallees ??= new Dictionary>(); + compilerGeneratedCallees.Add(userDefinedMethod, new List(callees)); + + foreach (var compilerGeneratedMember in callees) + { + switch (compilerGeneratedMember) + { + case MethodDesc nestedFunction: + Debug.Assert(CompilerGeneratedNames.IsLambdaOrLocalFunction(nestedFunction.Name)); + // Nested functions get suppressions from the user method only. + if (!_compilerGeneratedMethodToUserCodeMethod.TryAdd(nestedFunction, userDefinedMethod)) + { + var alreadyAssociatedMethod = _compilerGeneratedMethodToUserCodeMethod[nestedFunction]; + Logger?.LogWarning(new MessageOrigin(userDefinedMethod), DiagnosticId.MethodsAreAssociatedWithUserMethod, userDefinedMethod.GetDisplayName(), alreadyAssociatedMethod.GetDisplayName(), nestedFunction.GetDisplayName()); + } + break; + case MetadataType stateMachineType: + // Types in the call graph are always state machine types + // For those all their methods are not tracked explicitly in the call graph; instead, they + // are represented by the state machine type itself. + // We are already tracking the association of the state machine type to the user code method + // above, so no need to track it here. + Debug.Assert(CompilerGeneratedNames.IsStateMachineType(stateMachineType.Name)); + break; + default: + throw new InvalidOperationException(); + } + } + } + + // Now that we have instantiating methods fully filled out, walk the generated types and fill in the attribute + // providers + foreach (var generatedType in _generatedTypeToTypeArgumentInfo.Keys) + { + if (HasGenericParameters(generatedType)) + MapGeneratedTypeTypeParameters(generatedType); + } + + _cachedTypeToCompilerGeneratedMembers.Add(type, compilerGeneratedCallees); + return type; + + /// + /// Check if the type itself is generic. The only difference is that + /// if the type is a nested type, the generic parameters from its + /// parent type don't count. + /// + static bool HasGenericParameters(MetadataType typeDef) + { + if (typeDef.ContainingType == null) + return typeDef.HasInstantiation; + + return typeDef.Instantiation.Length > typeDef.ContainingType.Instantiation.Length; + } + + void MapGeneratedTypeTypeParameters(MetadataType generatedType) + { + Debug.Assert(CompilerGeneratedNames.IsGeneratedType(generatedType.Name)); + + if (!_generatedTypeToTypeArgumentInfo.TryGetValue(generatedType, out var typeInfo)) + { + // This can happen for static (non-capturing) closure environments, where more than + // nested function can map to the same closure environment. Since the current functionality + // is based on a one-to-one relationship between environments (types) and methods, this is + // not supported. + return; + } + + if (typeInfo.OriginalAttributes is not null) + { + return; + } + var method = typeInfo.CreatingMethod; + var body = _ilProvider.GetMethodIL(method); + if (body is not null) + { + var typeArgs = new ICustomAttributeProvider[generatedType.GenericParameters.Count]; + var typeRef = ScanForInit(generatedType, body); + if (typeRef is null) + { + return; + } + + // The typeRef is going to be a generic instantiation with signature variables + // We need to figure out the actual generic parameters which were used to create these + // so instantiate the typeRef in the context of the method body where it is created + TypeDesc instantiatedType = typeRef.InstantiateSignature(method.OwningType.Instantiation, method.Instantiation); + for (int i = 0; i < instantiatedType.Instantiation.Length; i++) + { + var typeArg = instantiatedType.Instantiation[i]; + // Start with the existing parameters, in case we can't find the mapped one + ICustomAttributeProvider userAttrs = generatedType.GenericParameters[i]; + // The type parameters of the state machine types are alpha renames of the + // the method parameters, so the type ref should always be a GenericParameter. However, + // in the case of nesting, there may be multiple renames, so if the parameter is a method + // we know we're done, but if it's another state machine, we have to keep looking to find + // the original owner of that state machine. + if (typeArg is GenericParameterDesc { Kind: { } kind } param) + { + if (kind == GenericParameterKind.Method) + { + userAttrs = param; + } + else + { + // Must be a type ref + if (method.OwningType is not MetadataType owningType || !CompilerGeneratedNames.IsGeneratedType(owningType.Name)) + { + userAttrs = param; + } + else + { + MapGeneratedTypeTypeParameters(owningType); + if (_generatedTypeToTypeArgumentInfo.TryGetValue(owningType, out var owningInfo) && + owningInfo.OriginalAttributes is { } owningAttrs) + { + userAttrs = owningAttrs[param.Index]; + } + } + } + } + + typeArgs[i] = userAttrs; + } + _generatedTypeToTypeArgumentInfo[generatedType] = typeInfo with { OriginalAttributes = typeArgs }; + } + } + + MetadataType? ScanForInit(MetadataType stateMachineType, MethodIL body) + { + ILReader reader = new ILReader(body.GetILBytes()); + while (reader.HasNext) + { + ILOpcode opcode = reader.ReadILOpcode(); + switch (opcode) + { + case ILOpcode.initobj: + case ILOpcode.newobj: + if (body.GetObject(reader.ReadILToken()) is MethodDesc { OwningType: MetadataType owningType } + && stateMachineType == owningType.GetTypeDefinition()) + { + return owningType; + } + break; + + default: + reader.Skip(opcode); + break; + } + } + return null; + } + } + + static TypeDesc? GetFirstConstructorArgumentAsType(CustomAttribute attribute) + { + if (!attribute.HasConstructorArguments) + return null; + + return attribute.ConstructorArguments[0].Value as TypeDefinition; + } + + public bool TryGetCompilerGeneratedCalleesForUserMethod(MethodDesc method, [NotNullWhen(true)] out List? callees) + { + method = method.GetTypicalMethodDefinition(); + + callees = null; + if (IsNestedFunctionOrStateMachineMember(method)) + return false; + + if (method.OwningType is not MetadataType owningType) + return false; + + var typeToCache = PopulateCacheForType(owningType); + if (typeToCache is null) + return false; + + return _cachedTypeToCompilerGeneratedMembers[typeToCache]?.TryGetValue(method, out callees) == true; + } + + /// + /// Gets the attributes on the "original" method of a generated type, i.e. the + /// attributes on the corresponding type parameters from the owning method. + /// + public IReadOnlyList? GetGeneratedTypeAttributes(MetadataType type) + { + MetadataType? generatedType = type.GetTypeDefinition() as MetadataType; + if (generatedType is null) + return null; + + Debug.Assert(CompilerGeneratedNames.IsGeneratedType(generatedType.Name)); + + var typeToCache = PopulateCacheForType(generatedType); + if (typeToCache is null) + return null; + + if (_generatedTypeToTypeArgumentInfo.TryGetValue(generatedType, out var typeInfo)) + { + return typeInfo.OriginalAttributes; + } + return null; + } + + // For state machine types/members, maps back to the state machine method. + // For local functions and lambdas, maps back to the owning method in user code (not the declaring + // lambda or local function, because the IL doesn't contain enough information to figure this out). + public bool TryGetOwningMethodForCompilerGeneratedMember(TypeSystemEntity sourceMember, [NotNullWhen(true)] out MethodDesc? owningMethod) + { + owningMethod = null; + if (sourceMember == null) + return false; + + MethodDesc? compilerGeneratedMethod = sourceMember as MethodDesc; + if (compilerGeneratedMethod != null) + { + if (_compilerGeneratedMethodToUserCodeMethod.TryGetValue(compilerGeneratedMethod, out owningMethod)) + return true; + } + + MetadataType? sourceType = ((sourceMember as TypeDesc) ?? GetOwningType(sourceMember))?.GetTypeDefinition() as MetadataType; + if (sourceType is null) + return false; + + if (_compilerGeneratedTypeToUserCodeMethod.TryGetValue(sourceType, out owningMethod)) + return true; + + if (!IsNestedFunctionOrStateMachineMember(sourceMember)) + return false; + + // sourceType is a state machine type, or the type containing a lambda or local function. + // Search all methods to find the one which points to the type as its + // state machine implementation. + var typeToCache = PopulateCacheForType(sourceType); + if (typeToCache is null) + return false; + + if (compilerGeneratedMethod != null) + { + if (_compilerGeneratedMethodToUserCodeMethod.TryGetValue(compilerGeneratedMethod, out owningMethod)) + return true; + } + + if (_compilerGeneratedTypeToUserCodeMethod.TryGetValue(sourceType, out owningMethod)) + return true; + + return false; + } + + private static TypeDesc? GetOwningType(TypeSystemEntity entity) + { + return entity switch + { + MethodDesc method => method.OwningType, + FieldDesc field => field.OwningType, + MetadataType type => type.ContainingType, + PropertyPseudoDesc property => property.OwningType, + EventPseudoDesc @event => @event.OwningType, + _ => null + }; + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/DiagnosticUtilities.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/DiagnosticUtilities.cs index 17189f421fcb5..e1f8d9b38f66f 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/DiagnosticUtilities.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/DiagnosticUtilities.cs @@ -5,7 +5,6 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Reflection.Metadata; -using ILCompiler.Logging; using ILLink.Shared; using Internal.TypeSystem; using Internal.TypeSystem.Ecma; @@ -218,7 +217,7 @@ internal static bool ShouldSuppressAnalysisWarningsForRequires(TypeSystemEntity if (method.IsInRequiresScope(requiresAttribute)) return true; - MethodDesc userMethod = ILCompiler.Logging.CompilerGeneratedState.GetUserDefinedMethodForCompilerGeneratedMember(method); + MethodDesc userMethod = CompilerGeneratedState.GetUserDefinedMethodForCompilerGeneratedMember(method); if (userMethod != null && userMethod.IsInRequiresScope(requiresAttribute)) return true; diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/MethodBodyScanner.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/MethodBodyScanner.cs index c15e355950951..503487b9a26fb 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/MethodBodyScanner.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/MethodBodyScanner.cs @@ -290,6 +290,8 @@ private static void StoreMethodLocalValue( // reachable from it. public virtual void InterproceduralScan(MethodIL methodBody) { + Debug.Assert(methodBody.OwningMethod.IsTypicalMethodDefinition); + var methodsInGroup = new ValueSet(methodBody.OwningMethod); // Optimization to prevent multiple scans of a method. @@ -309,12 +311,12 @@ public virtual void InterproceduralScan(MethodIL methodBody) // For state machine methods, also scan the state machine members. // Simplification: assume that all generated methods of the state machine type are // invoked at the point where the state machine method is called. - if (CompilerGeneratedState.TryGetStateMachineType(methodToScan, out TypeDefinition? stateMachineType)) + if (CompilerGeneratedState.TryGetStateMachineType(methodToScan.OwningMethod, out MetadataType? stateMachineType)) { - foreach (var method in stateMachineType.Methods) + foreach (var method in stateMachineType.GetMethods()) { Debug.Assert(!CompilerGeneratedNames.IsLambdaOrLocalFunction(method.Name)); - if (method.Body is MethodBody stateMachineBody) + if (TryGetMethodBody(method, out var stateMachineBody)) Scan(stateMachineBody, ref methodsInGroup); } } @@ -341,24 +343,58 @@ bool TryGetNextMethodToScan([NotNullWhen(true)] out MethodIL? method) foreach (var candidate in methodsInGroup) { var candidateMethod = candidate.Method; - MethodIL methodIL = _annotations.ILProvider.GetMethodIL(candidate.Method); - if (!scannedMethods.Contains(candidateMethod) && methodIL != null) + if (!scannedMethods.Contains(candidateMethod)) { - method = methodIL; - return true; + if (TryGetMethodBody(candidateMethod, out method)) + return true; } } method = null; return false; } + + bool TryGetMethodBody(MethodDesc method, [NotNullWhen(true)] out MethodIL? methodBody) + { + MethodIL methodIL = _annotations.ILProvider.GetMethodIL(method); + if (methodIL == null) + { + methodBody = null; + return false; + } + + Debug.Assert(methodIL.GetMethodILDefinition() == methodIL); + if (methodIL.OwningMethod.HasInstantiation || methodIL.OwningMethod.OwningType.HasInstantiation) + { + // We instantiate the body over the generic parameters. + // + // This will transform references like "call Foo.Method(!0 arg)" into + // "call Foo.Method(T arg)". We do this to avoid getting confused about what + // context the generic variables refer to - in the above example, we would see + // two !0's - one refers to the generic parameter of the type that owns the method with + // the call, but the other one (in the signature of "Method") actually refers to + // the generic parameter of Foo. + // + // If we don't do this translation, retrieving the signature of the called method + // would attempt to do bogus substitutions. + // + // By doing the following transformation, we ensure we don't see the generic variables + // that need to be bound to the context of the currently analyzed method. + methodIL = new InstantiatedMethodIL(methodIL.OwningMethod, methodIL); + } + + methodBody = methodIL; + return true; + } } void TrackNestedFunctionReference(MethodDesc method, ref ValueSet methodsInGroup) { - if (!CompilerGeneratedNames.IsLambdaOrLocalFunction(method.Name)) + MethodDesc methodDefinition = method.GetTypicalMethodDefinition(); + + if (!CompilerGeneratedNames.IsLambdaOrLocalFunction(methodDefinition.Name)) return; - methodsInGroup = MethodLattice.Meet(methodsInGroup, new(method)); + methodsInGroup = MethodLattice.Meet(methodsInGroup, new(methodDefinition)); } protected virtual void Scan(MethodIL methodBody, ref ValueSet methodsInGroup) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMethodBodyScanner.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMethodBodyScanner.cs index a7fe0d173801c..7d93c44e536a2 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMethodBodyScanner.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMethodBodyScanner.cs @@ -115,26 +115,6 @@ public static DependencyList ScanAndProcessReturnValue(NodeFactory factory, Flow { var scanner = new ReflectionMethodBodyScanner(factory, annotations, logger, new MessageOrigin(methodBody.OwningMethod)); - Debug.Assert(methodBody.GetMethodILDefinition() == methodBody); - if (methodBody.OwningMethod.HasInstantiation || methodBody.OwningMethod.OwningType.HasInstantiation) - { - // We instantiate the body over the generic parameters. - // - // This will transform references like "call Foo.Method(!0 arg)" into - // "call Foo.Method(T arg)". We do this to avoid getting confused about what - // context the generic variables refer to - in the above example, we would see - // two !0's - one refers to the generic parameter of the type that owns the method with - // the call, but the other one (in the signature of "Method") actually refers to - // the generic parameter of Foo. - // - // If we don't do this translation, retrieving the signature of the called method - // would attempt to do bogus substitutions. - // - // By doing the following transformation, we ensure we don't see the generic variables - // that need to be bound to the context of the currently analyzed method. - methodBody = new InstantiatedMethodIL(methodBody.OwningMethod, methodBody); - } - scanner.InterproceduralScan(methodBody); return scanner._reflectionMarker.Dependencies; diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logger.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logger.cs index 07a9e0842570b..07542f8c727d8 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logger.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logger.cs @@ -9,6 +9,7 @@ using Internal.TypeSystem; using Internal.TypeSystem.Ecma; +using ILCompiler.Dataflow; using ILCompiler.Logging; using ILLink.Shared; diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logging/CompilerGeneratedState.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logging/CompilerGeneratedState.cs deleted file mode 100644 index c2d861e62b2f6..0000000000000 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logging/CompilerGeneratedState.cs +++ /dev/null @@ -1,50 +0,0 @@ -// 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 Internal.TypeSystem; -using Internal.TypeSystem.Ecma; - -namespace ILCompiler.Logging -{ - // Currently this is implemented using heuristics - public class CompilerGeneratedState - { - private static bool HasRoslynCompilerGeneratedName(DefType type) => - type.Name.Contains('<') || (type.ContainingType != null && HasRoslynCompilerGeneratedName(type.ContainingType)); - - public static MethodDesc GetUserDefinedMethodForCompilerGeneratedMember(MethodDesc sourceMember) - { - var compilerGeneratedType = sourceMember.OwningType.GetTypeDefinition() as EcmaType; - if (compilerGeneratedType == null) - return null; - - // Only handle async or iterator state machine - // So go to the declaring type and check if it's compiler generated (as a perf optimization) - if (!HasRoslynCompilerGeneratedName(compilerGeneratedType) || compilerGeneratedType.ContainingType == null) - return null; - - // Now go to its declaring type and search all methods to find the one which points to the type as its - // state machine implementation. - foreach (EcmaMethod method in compilerGeneratedType.ContainingType.GetMethods()) - { - var decodedAttribute = method.GetDecodedCustomAttribute("System.Runtime.CompilerServices", "AsyncIteratorStateMachineAttribute") - ?? method.GetDecodedCustomAttribute("System.Runtime.CompilerServices", "AsyncStateMachineAttribute") - ?? method.GetDecodedCustomAttribute("System.Runtime.CompilerServices", "IteratorStateMachineAttribute"); - - if (!decodedAttribute.HasValue) - continue; - - if (decodedAttribute.Value.FixedArguments.Length != 1 - || decodedAttribute.Value.FixedArguments[0].Value is not TypeDesc stateMachineType) - continue; - - if (stateMachineType == compilerGeneratedType) - return method; - } - - return null; - } - } -} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj index 79cb156ac4e0b..5c09727d4bd20 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj @@ -1,4 +1,4 @@ - + Library ILCompiler.Compiler @@ -318,6 +318,9 @@ + + + @@ -561,7 +564,6 @@ - diff --git a/src/coreclr/tools/aot/ILCompiler/Program.cs b/src/coreclr/tools/aot/ILCompiler/Program.cs index eb3643851fd50..b3a9ba34d8d30 100644 --- a/src/coreclr/tools/aot/ILCompiler/Program.cs +++ b/src/coreclr/tools/aot/ILCompiler/Program.cs @@ -16,6 +16,7 @@ using Debug = System.Diagnostics.Debug; using InstructionSet = Internal.JitInterface.InstructionSet; +using ILCompiler.Dataflow; namespace ILCompiler { @@ -742,7 +743,9 @@ static string ILLinkify(string rootedAssembly) } ilProvider = new FeatureSwitchManager(ilProvider, featureSwitches); + CompilerGeneratedState compilerGeneratedState = new CompilerGeneratedState(ilProvider); var logger = new Logger(Console.Out, _isVerbose, ProcessWarningCodes(_suppressedWarnings), _singleWarn, _singleWarnEnabledAssemblies, _singleWarnDisabledAssemblies); + compilerGeneratedState.Logger = logger; var stackTracePolicy = _emitStackTraceData ? (StackTraceEmissionPolicy)new EcmaMethodStackTraceEmissionPolicy() : new NoStackTraceEmissionPolicy(); From b9eac18fe220396658b659b1fa8f33de5d16c1a1 Mon Sep 17 00:00:00 2001 From: vitek-karas <10670590+vitek-karas@users.noreply.github.com> Date: Fri, 24 Jun 2022 12:25:45 -0700 Subject: [PATCH 06/18] Finish CompilerGeneratedState port (at least first pass) --- .../Dataflow/CompilerGeneratedState.cs | 60 +++++++++++-------- .../Compiler/Dataflow/FlowAnnotations.cs | 18 +++--- 2 files changed, 44 insertions(+), 34 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedState.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedState.cs index ded9e70b6b9fd..581b9e02258b5 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedState.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedState.cs @@ -6,6 +6,7 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Reflection.Metadata; using System.Reflection.PortableExecutable; using ILCompiler.Logging; using ILLink.Shared; @@ -21,13 +22,14 @@ namespace ILCompiler.Dataflow public class CompilerGeneratedState { readonly ILProvider _ilProvider; + // The MetadataType keys must be type definitions (uninstantiated) readonly Dictionary _compilerGeneratedTypeToUserCodeMethod; readonly Dictionary _generatedTypeToTypeArgumentInfo; readonly record struct TypeArgumentInfo( /// The method which calls the ctor for the given type MethodDesc CreatingMethod, /// Attributes for the type, pulled from the creators type arguments - IReadOnlyList? OriginalAttributes); + IReadOnlyList? OriginalAttributes); readonly Dictionary _compilerGeneratedMethodToUserCodeMethod; @@ -81,24 +83,21 @@ public static bool TryGetStateMachineType(MethodDesc method, [NotNullWhen(true)] { stateMachineType = null; // Discover state machine methods. - if (!method.HasCustomAttributes) + if (method is not EcmaMethod ecmaMethod) return false; - foreach (var attribute in method.CustomAttributes) - { - if (attribute.AttributeType.Namespace != "System.Runtime.CompilerServices") - continue; + CustomAttributeValue? decodedAttribute = null; + decodedAttribute = ecmaMethod.GetDecodedCustomAttribute("System.Runtime.CompilerServices", "AsyncIteratorStateMachineAttribute"); + if (decodedAttribute == null) + decodedAttribute = ecmaMethod.GetDecodedCustomAttribute("System.Runtime.CompilerServices", "AsyncStateMachineAttribute"); + if (decodedAttribute == null) + decodedAttribute = ecmaMethod.GetDecodedCustomAttribute("System.Runtime.CompilerServices", "IteratorStateMachineAttribute"); - switch (attribute.AttributeType.Name) - { - case "AsyncIteratorStateMachineAttribute": - case "AsyncStateMachineAttribute": - case "IteratorStateMachineAttribute": - stateMachineType = GetFirstConstructorArgumentAsType(attribute); - return stateMachineType != null; - } - } - return false; + if (decodedAttribute == null) + return false; + + stateMachineType = GetFirstConstructorArgumentAsType(decodedAttribute.Value) as MetadataType; + return stateMachineType != null; } /// @@ -131,6 +130,8 @@ public static bool TryGetStateMachineType(MethodDesc method, [NotNullWhen(true)] void ProcessMethod(MethodDesc method) { + Debug.Assert(method == method.GetTypicalMethodDefinition()); + bool isStateMachineMember = CompilerGeneratedNames.IsStateMachineType(((MetadataType)method.OwningType).Name); if (!CompilerGeneratedNames.IsLambdaOrLocalFunction(method.Name)) { @@ -176,12 +177,16 @@ void ProcessMethod(MethodDesc method) if (lambdaOrLocalFunction == null) continue; + lambdaOrLocalFunction = lambdaOrLocalFunction.GetTypicalMethodDefinition(); + if (lambdaOrLocalFunction.IsConstructor && lambdaOrLocalFunction.OwningType is MetadataType generatedType && // Don't consider calls in the same type, like inside a static constructor method.OwningType != generatedType && CompilerGeneratedNames.IsLambdaDisplayClass(generatedType.Name)) { + generatedType = (MetadataType)generatedType.GetTypeDefinition(); + // fill in null for now, attribute providers will be filled in later if (!_generatedTypeToTypeArgumentInfo.TryAdd(generatedType, new TypeArgumentInfo(method, null))) { @@ -213,6 +218,8 @@ lambdaOrLocalFunction.OwningType is MetadataType generatedType && Debug.Assert(stateMachineType.ContainingType == type || (CompilerGeneratedNames.IsGeneratedMemberName(stateMachineType.ContainingType.Name) && stateMachineType.ContainingType.ContainingType == type)); + Debug.Assert(stateMachineType == stateMachineType.GetTypeDefinition()); + callGraph.TrackCall(method, stateMachineType); if (!_compilerGeneratedTypeToUserCodeMethod.TryAdd(stateMachineType, method)) @@ -295,6 +302,8 @@ lambdaOrLocalFunction.OwningType is MetadataType generatedType && // providers foreach (var generatedType in _generatedTypeToTypeArgumentInfo.Keys) { + Debug.Assert(generatedType == generatedType.GetTypeDefinition()); + if (HasGenericParameters(generatedType)) MapGeneratedTypeTypeParameters(generatedType); } @@ -318,6 +327,7 @@ static bool HasGenericParameters(MetadataType typeDef) void MapGeneratedTypeTypeParameters(MetadataType generatedType) { Debug.Assert(CompilerGeneratedNames.IsGeneratedType(generatedType.Name)); + Debug.Assert(generatedType == generatedType.GetTypeDefinition()); if (!_generatedTypeToTypeArgumentInfo.TryGetValue(generatedType, out var typeInfo)) { @@ -336,7 +346,7 @@ void MapGeneratedTypeTypeParameters(MetadataType generatedType) var body = _ilProvider.GetMethodIL(method); if (body is not null) { - var typeArgs = new ICustomAttributeProvider[generatedType.GenericParameters.Count]; + var typeArgs = new GenericParameterDesc?[generatedType.Instantiation.Length]; var typeRef = ScanForInit(generatedType, body); if (typeRef is null) { @@ -351,7 +361,7 @@ void MapGeneratedTypeTypeParameters(MetadataType generatedType) { var typeArg = instantiatedType.Instantiation[i]; // Start with the existing parameters, in case we can't find the mapped one - ICustomAttributeProvider userAttrs = generatedType.GenericParameters[i]; + GenericParameterDesc? userAttrs = generatedType.Instantiation[i] as GenericParameterDesc; // The type parameters of the state machine types are alpha renames of the // the method parameters, so the type ref should always be a GenericParameter. However, // in the case of nesting, there may be multiple renames, so if the parameter is a method @@ -372,6 +382,7 @@ void MapGeneratedTypeTypeParameters(MetadataType generatedType) } else { + owningType = (MetadataType)owningType.GetTypeDefinition(); MapGeneratedTypeTypeParameters(owningType); if (_generatedTypeToTypeArgumentInfo.TryGetValue(owningType, out var owningInfo) && owningInfo.OriginalAttributes is { } owningAttrs) @@ -414,12 +425,12 @@ void MapGeneratedTypeTypeParameters(MetadataType generatedType) } } - static TypeDesc? GetFirstConstructorArgumentAsType(CustomAttribute attribute) + static TypeDesc? GetFirstConstructorArgumentAsType(CustomAttributeValue attribute) { - if (!attribute.HasConstructorArguments) + if (attribute.FixedArguments.Length == 0) return null; - return attribute.ConstructorArguments[0].Value as TypeDefinition; + return attribute.FixedArguments[0].Value as TypeDesc; } public bool TryGetCompilerGeneratedCalleesForUserMethod(MethodDesc method, [NotNullWhen(true)] out List? callees) @@ -444,12 +455,9 @@ public bool TryGetCompilerGeneratedCalleesForUserMethod(MethodDesc method, [NotN /// Gets the attributes on the "original" method of a generated type, i.e. the /// attributes on the corresponding type parameters from the owning method. /// - public IReadOnlyList? GetGeneratedTypeAttributes(MetadataType type) + public IReadOnlyList? GetGeneratedTypeAttributes(MetadataType type) { - MetadataType? generatedType = type.GetTypeDefinition() as MetadataType; - if (generatedType is null) - return null; - + MetadataType generatedType = (MetadataType)type.GetTypeDefinition(); Debug.Assert(CompilerGeneratedNames.IsGeneratedType(generatedType.Name)); var typeToCache = PopulateCacheForType(generatedType); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/FlowAnnotations.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/FlowAnnotations.cs index a00c084b1111f..1e4c87a17e28f 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/FlowAnnotations.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/FlowAnnotations.cs @@ -30,14 +30,16 @@ public partial class FlowAnnotations { private readonly TypeAnnotationsHashtable _hashtable; private readonly Logger _logger; + private readonly CompilerGeneratedState _compilerGeneratedState; public ILProvider ILProvider { get; } - public FlowAnnotations(Logger logger, ILProvider ilProvider) + public FlowAnnotations(Logger logger, ILProvider ilProvider, CompilerGeneratedState compilerGeneratedState) { _hashtable = new TypeAnnotationsHashtable(logger, ilProvider); _logger = logger; ILProvider = ilProvider; + _compilerGeneratedState = compilerGeneratedState; } public bool RequiresDataflowAnalysis(MethodDesc method) @@ -258,8 +260,9 @@ private class TypeAnnotationsHashtable : LockFreeReaderHashtable (_logger, _ilProvider) = (logger, ilProvider); + public TypeAnnotationsHashtable(Logger logger, ILProvider ilProvider, CompilerGeneratedState compilerGeneratedState) => (_logger, _ilProvider, _compilerGeneratedState) = (logger, ilProvider, compilerGeneratedState); private static DynamicallyAccessedMemberTypes GetMemberTypesForDynamicallyAccessedMembersAttribute(MetadataReader reader, CustomAttributeHandleCollection customAttributeHandles) { @@ -572,12 +575,11 @@ protected override TypeAnnotations CreateValueFromKey(TypeDesc key) if (ecmaType.Instantiation.Length > 0) { var attrs = GetGeneratedTypeAttributes(ecmaType); - for (int i = 0; i < ecmaType.Instantiation.Length; i++) + for (int genericParameterIndex = 0; genericParameterIndex < ecmaType.Instantiation.Length; genericParameterIndex++) { - EcmaGenericParameter genericParameter = (EcmaGenericParameter)ecmaType.Instantiation[i]; + EcmaGenericParameter genericParameter = (EcmaGenericParameter)ecmaType.Instantiation[genericParameterIndex]; + genericParameter = (attrs?[genericParameterIndex] as EcmaGenericParameter) ?? genericParameter; GenericParameter genericParameterDef = reader.GetGenericParameter(genericParameter.Handle); - var provider = attrs?[genericParameterIndex] ?? genericParameterDef; - var annotation = GetMemberTypesForDynamicallyAccessedMembersAttribute(reader, genericParameterDef.GetCustomAttributes()); if (annotation != DynamicallyAccessedMemberTypes.None) { @@ -591,13 +593,13 @@ protected override TypeAnnotations CreateValueFromKey(TypeDesc key) return new TypeAnnotations(ecmaType, typeAnnotation, annotatedMethods.ToArray(), annotatedFields.ToArray(), typeGenericParameterAnnotations); } - private IReadOnlyList? GetGeneratedTypeAttributes(EcmaType typeDef) + private IReadOnlyList? GetGeneratedTypeAttributes(EcmaType typeDef) { if (!CompilerGeneratedNames.IsGeneratedType(typeDef.Name)) { return null; } - var attrs = _context.CompilerGeneratedState.GetGeneratedTypeAttributes(typeDef); + var attrs = _compilerGeneratedState.GetGeneratedTypeAttributes(typeDef); Debug.Assert(attrs is null || attrs.Count == typeDef.Instantiation.Length); return attrs; } From 177de448595cce8ceb40d011d95ffbb1cf709841 Mon Sep 17 00:00:00 2001 From: vitek-karas <10670590+vitek-karas@users.noreply.github.com> Date: Fri, 24 Jun 2022 15:55:46 -0700 Subject: [PATCH 07/18] Make the simple stuff work --- .../DependencyGraphTests.cs | 5 +- .../Dataflow/CompilerGeneratedState.cs | 17 +-- .../Compiler/Dataflow/DiagnosticUtilities.cs | 25 ----- .../Compiler/Dataflow/EcmaExtensions.cs | 39 +++++-- .../Compiler/Dataflow/FlowAnnotations.cs | 6 +- .../Dataflow/GenericArgumentDataFlow.cs | 2 +- .../Compiler/Dataflow/MethodBodyScanner.cs | 2 +- .../Compiler/Dataflow/ReflectionMarker.cs | 4 +- .../Dataflow/TrimAnalysisAssignmentPattern.cs | 6 +- .../Dataflow/TrimAnalysisMethodCallPattern.cs | 6 +- .../ILCompiler.Compiler/Compiler/Logger.cs | 102 +++++++++++++++--- .../Compiler/Logging/MessageOrigin.cs | 58 ++++++---- src/coreclr/tools/aot/ILCompiler/Program.cs | 4 +- .../TestCasesRunner/ILCompilerDriver.cs | 7 +- 14 files changed, 179 insertions(+), 104 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/DependencyGraphTests.cs b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/DependencyGraphTests.cs index dc1bf6c9cd0c4..0a98bf807d31c 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/DependencyGraphTests.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/DependencyGraphTests.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; - +using ILCompiler.Dataflow; using Internal.IL; using Internal.TypeSystem; using Internal.TypeSystem.Ecma; @@ -67,11 +67,12 @@ public void TestDependencyGraphInvariants(EcmaMethod method) CompilationModuleGroup compilationGroup = new SingleFileCompilationModuleGroup(); NativeAotILProvider ilProvider = new NativeAotILProvider(); + CompilerGeneratedState compilerGeneratedState = new CompilerGeneratedState(ilProvider); UsageBasedMetadataManager metadataManager = new UsageBasedMetadataManager(compilationGroup, context, new FullyBlockedMetadataBlockingPolicy(), new FullyBlockedManifestResourceBlockingPolicy(), null, new NoStackTraceEmissionPolicy(), new NoDynamicInvokeThunkGenerationPolicy(), - new ILLink.Shared.TrimAnalysis.FlowAnnotations(Logger.Null, ilProvider), UsageBasedMetadataGenerationOptions.None, + new ILLink.Shared.TrimAnalysis.FlowAnnotations(Logger.Null, ilProvider, compilerGeneratedState), UsageBasedMetadataGenerationOptions.None, Logger.Null, Array.Empty>(), Array.Empty(), Array.Empty()); CompilationBuilder builder = new RyuJitCompilationBuilder(context, compilationGroup) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedState.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedState.cs index 581b9e02258b5..38a9f3595e431 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedState.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedState.cs @@ -73,7 +73,7 @@ public static bool IsNestedFunctionOrStateMachineMember(TypeSystemEntity member) if (member is MethodDesc method && CompilerGeneratedNames.IsLambdaOrLocalFunction(method.Name)) return true; - if (GetOwningType(member) is not MetadataType declaringType) + if (member.GetOwningType() is not MetadataType declaringType) return false; return CompilerGeneratedNames.IsStateMachineType(declaringType.Name); @@ -487,7 +487,7 @@ public bool TryGetOwningMethodForCompilerGeneratedMember(TypeSystemEntity source return true; } - MetadataType? sourceType = ((sourceMember as TypeDesc) ?? GetOwningType(sourceMember))?.GetTypeDefinition() as MetadataType; + MetadataType? sourceType = ((sourceMember as TypeDesc) ?? sourceMember.GetOwningType())?.GetTypeDefinition() as MetadataType; if (sourceType is null) return false; @@ -515,18 +515,5 @@ public bool TryGetOwningMethodForCompilerGeneratedMember(TypeSystemEntity source return false; } - - private static TypeDesc? GetOwningType(TypeSystemEntity entity) - { - return entity switch - { - MethodDesc method => method.OwningType, - FieldDesc field => field.OwningType, - MetadataType type => type.ContainingType, - PropertyPseudoDesc property => property.OwningType, - EventPseudoDesc @event => @event.OwningType, - _ => null - }; - } } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/DiagnosticUtilities.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/DiagnosticUtilities.cs index e1f8d9b38f66f..09879c2df2b55 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/DiagnosticUtilities.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/DiagnosticUtilities.cs @@ -200,31 +200,6 @@ internal static bool DoesMemberRequire(this TypeSystemEntity member, string requ }; } - internal static bool ShouldSuppressAnalysisWarningsForRequires(TypeSystemEntity originMember, string requiresAttribute) - { - // Check if the current scope method has Requires on it - // since that attribute automatically suppresses all trim analysis warnings. - // Check both the immediate origin method as well as suppression context method - // since that will be different for compiler generated code. - if (originMember == null) - return false; - - // TODO - handle fields and other members - // Other types of members still need to go through CompilerGeneratedState check - if (originMember is not MethodDesc method) - return false; - - if (method.IsInRequiresScope(requiresAttribute)) - return true; - - MethodDesc userMethod = CompilerGeneratedState.GetUserDefinedMethodForCompilerGeneratedMember(method); - if (userMethod != null && - userMethod.IsInRequiresScope(requiresAttribute)) - return true; - - return false; - } - internal const string RequiresUnreferencedCodeAttribute = nameof(RequiresUnreferencedCodeAttribute); internal const string RequiresDynamicCodeAttribute = nameof(RequiresDynamicCodeAttribute); internal const string RequiresAssemblyFilesAttribute = nameof(RequiresAssemblyFilesAttribute); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/EcmaExtensions.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/EcmaExtensions.cs index 870d13b92a5b0..85f74baf51d78 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/EcmaExtensions.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/EcmaExtensions.cs @@ -78,14 +78,37 @@ public static ReferenceKind ParameterReferenceKind(this MethodDesc method, int i } // Parameter metadata index 0 is for return parameter - var parameterMetadata = method.GetParameterMetadata()[index + 1]; - if (!method.Signature[index].IsByRef) - return ReferenceKind.None; - if (parameterMetadata.In) - return ReferenceKind.In; - if (parameterMetadata.Out) - return ReferenceKind.Out; - return ReferenceKind.Ref; + foreach (var parameterMetadata in method.GetParameterMetadata()) + { + if (parameterMetadata.Index != index + 1) + continue; + + if (!method.Signature[index].IsByRef) + return ReferenceKind.None; + if (parameterMetadata.In) + return ReferenceKind.In; + if (parameterMetadata.Out) + return ReferenceKind.Out; + return ReferenceKind.Ref; + } + + return ReferenceKind.None; + } + + public static bool IsByRefOrPointer(this TypeDesc type) + => type.IsByRef || type.IsPointer; + + public static TypeDesc GetOwningType(this TypeSystemEntity entity) + { + return entity switch + { + MethodDesc method => method.OwningType, + FieldDesc field => field.OwningType, + MetadataType type => type.ContainingType, + PropertyPseudoDesc property => property.OwningType, + EventPseudoDesc @event => @event.OwningType, + _ => null + }; } } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/FlowAnnotations.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/FlowAnnotations.cs index 1e4c87a17e28f..d3c0c51fa7fa8 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/FlowAnnotations.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/FlowAnnotations.cs @@ -30,16 +30,16 @@ public partial class FlowAnnotations { private readonly TypeAnnotationsHashtable _hashtable; private readonly Logger _logger; - private readonly CompilerGeneratedState _compilerGeneratedState; public ILProvider ILProvider { get; } + public CompilerGeneratedState CompilerGeneratedState { get; } public FlowAnnotations(Logger logger, ILProvider ilProvider, CompilerGeneratedState compilerGeneratedState) { - _hashtable = new TypeAnnotationsHashtable(logger, ilProvider); + _hashtable = new TypeAnnotationsHashtable(logger, ilProvider, compilerGeneratedState); _logger = logger; ILProvider = ilProvider; - _compilerGeneratedState = compilerGeneratedState; + CompilerGeneratedState = compilerGeneratedState; } public bool RequiresDataflowAnalysis(MethodDesc method) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/GenericArgumentDataFlow.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/GenericArgumentDataFlow.cs index 388dbb6f700d3..01f3d0363e273 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/GenericArgumentDataFlow.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/GenericArgumentDataFlow.cs @@ -42,7 +42,7 @@ public DependencyList ProcessGenericArgumentDataFlow(GenericParameterDesc generi var diagnosticContext = new DiagnosticContext( _origin, - DiagnosticUtilities.ShouldSuppressAnalysisWarningsForRequires(_origin.MemberDefinition, DiagnosticUtilities.RequiresUnreferencedCodeAttribute), + _logger.ShouldSuppressAnalysisWarningsForRequires(_origin.MemberDefinition, DiagnosticUtilities.RequiresUnreferencedCodeAttribute), _logger); return RequireDynamicallyAccessedMembers(diagnosticContext, genericArgumentValue, genericParameterValue, new GenericParameterOrigin(genericParameter)); } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/MethodBodyScanner.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/MethodBodyScanner.cs index 503487b9a26fb..da04fcd37e359 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/MethodBodyScanner.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/MethodBodyScanner.cs @@ -325,7 +325,7 @@ public virtual void InterproceduralScan(MethodIL methodBody) #if DEBUG // Validate that the compiler-generated callees tracked by the compiler-generated state // are the same set of methods that we discovered and scanned above. - if (_context.CompilerGeneratedState.TryGetCompilerGeneratedCalleesForUserMethod(methodBody.Method, out List? compilerGeneratedCallees)) + if (_annotations.CompilerGeneratedState.TryGetCompilerGeneratedCalleesForUserMethod(methodBody.OwningMethod, out List? compilerGeneratedCallees)) { var calleeMethods = compilerGeneratedCallees.OfType(); Debug.Assert(methodsInGroup.Count() == 1 + calleeMethods.Count()); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMarker.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMarker.cs index ca018c707039d..69c2cf98245cc 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMarker.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMarker.cs @@ -74,7 +74,7 @@ internal bool TryResolveTypeNameAndMark(string typeName, in DiagnosticContext di // NativeAOT doesn't have a fully capable type name resolver yet // Once this is implemented don't forget to wire up marking of type forwards which are used in generic parameters - if (!ILCompiler.DependencyAnalysis.ReflectionMethodBodyScanner.ResolveType(typeName, callingModule, diagnosticContext.Origin.MemberDefinition.Context, out TypeDesc foundType, out ModuleDesc referenceModule)) + if (!ILCompiler.DependencyAnalysis.ReflectionMethodBodyScanner.ResolveType(typeName, callingModule, diagnosticContext.Origin.MemberDefinition!.Context, out TypeDesc foundType, out ModuleDesc referenceModule)) { type = default; return false; @@ -220,7 +220,7 @@ void CheckAndWarnOnReflectionAccess(in MessageOrigin origin, TypeSystemEntity en } else { - if (!DiagnosticUtilities.ShouldSuppressAnalysisWarningsForRequires(origin.MemberDefinition, DiagnosticUtilities.RequiresUnreferencedCodeAttribute)) + if (!_logger.ShouldSuppressAnalysisWarningsForRequires(origin.MemberDefinition, DiagnosticUtilities.RequiresUnreferencedCodeAttribute)) { if (entity is FieldDesc) { diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisAssignmentPattern.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisAssignmentPattern.cs index 803fb3e5d04c3..c61f9305f0c53 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisAssignmentPattern.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisAssignmentPattern.cs @@ -31,9 +31,9 @@ public void MarkAndProduceDiagnostics(ReflectionMarker reflectionMarker, Logger { var diagnosticContext = new DiagnosticContext( Origin, - DiagnosticUtilities.ShouldSuppressAnalysisWarningsForRequires(Origin.MemberDefinition, DiagnosticUtilities.RequiresUnreferencedCodeAttribute), - DiagnosticUtilities.ShouldSuppressAnalysisWarningsForRequires(Origin.MemberDefinition, DiagnosticUtilities.RequiresDynamicCodeAttribute), - DiagnosticUtilities.ShouldSuppressAnalysisWarningsForRequires(Origin.MemberDefinition, DiagnosticUtilities.RequiresAssemblyFilesAttribute), + logger.ShouldSuppressAnalysisWarningsForRequires(Origin.MemberDefinition, DiagnosticUtilities.RequiresUnreferencedCodeAttribute), + logger.ShouldSuppressAnalysisWarningsForRequires(Origin.MemberDefinition, DiagnosticUtilities.RequiresDynamicCodeAttribute), + logger.ShouldSuppressAnalysisWarningsForRequires(Origin.MemberDefinition, DiagnosticUtilities.RequiresAssemblyFilesAttribute), logger); foreach (var sourceValue in Source) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisMethodCallPattern.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisMethodCallPattern.cs index a9280b4af7841..f24953f293b43 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisMethodCallPattern.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisMethodCallPattern.cs @@ -56,9 +56,9 @@ public void MarkAndProduceDiagnostics(ReflectionMarker reflectionMarker, Logger { var diagnosticContext = new DiagnosticContext( Origin, - DiagnosticUtilities.ShouldSuppressAnalysisWarningsForRequires(Origin.MemberDefinition, DiagnosticUtilities.RequiresUnreferencedCodeAttribute), - DiagnosticUtilities.ShouldSuppressAnalysisWarningsForRequires(Origin.MemberDefinition, DiagnosticUtilities.RequiresDynamicCodeAttribute), - DiagnosticUtilities.ShouldSuppressAnalysisWarningsForRequires(Origin.MemberDefinition, DiagnosticUtilities.RequiresAssemblyFilesAttribute), + logger.ShouldSuppressAnalysisWarningsForRequires(Origin.MemberDefinition, DiagnosticUtilities.RequiresUnreferencedCodeAttribute), + logger.ShouldSuppressAnalysisWarningsForRequires(Origin.MemberDefinition, DiagnosticUtilities.RequiresDynamicCodeAttribute), + logger.ShouldSuppressAnalysisWarningsForRequires(Origin.MemberDefinition, DiagnosticUtilities.RequiresAssemblyFilesAttribute), logger); ReflectionMethodBodyScanner.HandleCall(MethodBody, CalledMethod, Operation, Offset, Instance, Arguments, diagnosticContext, diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logger.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logger.cs index 07542f8c727d8..784f1920feac3 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logger.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logger.cs @@ -15,12 +15,17 @@ using ILSequencePoint = Internal.IL.ILSequencePoint; using MethodIL = Internal.IL.MethodIL; +using System.Diagnostics.Metrics; +using System.Diagnostics; +using System.Reflection; +using Internal.IL; namespace ILCompiler { public class Logger { private readonly ILogWriter _logWriter; + private readonly CompilerGeneratedState _compilerGeneratedState; private readonly HashSet _suppressedWarnings; @@ -30,13 +35,21 @@ public class Logger private readonly HashSet _trimWarnedAssemblies = new HashSet(StringComparer.OrdinalIgnoreCase); private readonly HashSet _aotWarnedAssemblies = new HashSet(StringComparer.OrdinalIgnoreCase); - public static Logger Null = new Logger(new TextLogWriter(TextWriter.Null), false); + public static Logger Null = new Logger(new TextLogWriter(TextWriter.Null), null, false); public bool IsVerbose { get; } - public Logger(ILogWriter writer, bool isVerbose, IEnumerable suppressedWarnings, bool singleWarn, IEnumerable singleWarnEnabledModules, IEnumerable singleWarnDisabledModules) + public Logger( + ILogWriter writer, + CompilerGeneratedState compilerGeneratedState, + bool isVerbose, + IEnumerable suppressedWarnings, + bool singleWarn, + IEnumerable singleWarnEnabledModules, + IEnumerable singleWarnDisabledModules) { _logWriter = writer; + _compilerGeneratedState = compilerGeneratedState; IsVerbose = isVerbose; _suppressedWarnings = new HashSet(suppressedWarnings); _isSingleWarn = singleWarn; @@ -44,18 +57,18 @@ public Logger(ILogWriter writer, bool isVerbose, IEnumerable suppressedWarn _singleWarnDisabledAssemblies = new HashSet(singleWarnDisabledModules, StringComparer.OrdinalIgnoreCase); } - public Logger(TextWriter writer, bool isVerbose, IEnumerable suppressedWarnings, bool singleWarn, IEnumerable singleWarnEnabledModules, IEnumerable singleWarnDisabledModules) - : this(new TextLogWriter(writer), isVerbose, suppressedWarnings, singleWarn, singleWarnEnabledModules, singleWarnDisabledModules) + public Logger(TextWriter writer, CompilerGeneratedState compilerGeneratedState, bool isVerbose, IEnumerable suppressedWarnings, bool singleWarn, IEnumerable singleWarnEnabledModules, IEnumerable singleWarnDisabledModules) + : this(new TextLogWriter(writer), compilerGeneratedState, isVerbose, suppressedWarnings, singleWarn, singleWarnEnabledModules, singleWarnDisabledModules) { } - public Logger(ILogWriter writer, bool isVerbose) - : this(writer, isVerbose, Array.Empty(), singleWarn: false, Array.Empty(), Array.Empty()) + public Logger(ILogWriter writer, CompilerGeneratedState compilerGeneratedState, bool isVerbose) + : this(writer, compilerGeneratedState, isVerbose, Array.Empty(), singleWarn: false, Array.Empty(), Array.Empty()) { } - public Logger(TextWriter writer, bool isVerbose) - : this(new TextLogWriter(writer), isVerbose) + public Logger(TextWriter writer, CompilerGeneratedState compilerGeneratedState, bool isVerbose) + : this(new TextLogWriter(writer), compilerGeneratedState, isVerbose) { } @@ -140,20 +153,57 @@ internal bool IsWarningSuppressed(int code, MessageOrigin origin) if (_suppressedWarnings.Contains(code)) return true; - IEnumerable> suppressions = null; - // TODO: Suppressions with different scopes - if (origin.MemberDefinition is TypeDesc type) + TypeSystemEntity member = origin.MemberDefinition; + if (IsSuppressed(code, member)) + return true; + + MethodDesc owningMethod; + while (_compilerGeneratedState?.TryGetOwningMethodForCompilerGeneratedMember(member, out owningMethod) == true) + { + Debug.Assert(owningMethod != member); + if (IsSuppressed(code, owningMethod)) + return true; + member = owningMethod; + } + + return false; + } + + bool IsSuppressed(int id, TypeSystemEntity warningOrigin) + { + TypeSystemEntity warningOriginMember = warningOrigin; + while (warningOriginMember != null) + { + if (IsSuppressedOnElement(id, warningOriginMember)) + return true; + + warningOriginMember = warningOriginMember.GetOwningType(); + } + + // TODO: Assembly-level suppressions + + return false; + } + + bool IsSuppressedOnElement(int id, TypeSystemEntity provider) + { + if (provider == null) + return false; + + // TODO: Assembly-level suppressions + + IEnumerable> suppressions = null; + + if (provider is TypeDesc type) { var ecmaType = type.GetTypeDefinition() as EcmaType; suppressions = ecmaType?.GetDecodedCustomAttributes("System.Diagnostics.CodeAnalysis", "UnconditionalSuppressMessageAttribute"); } - if (origin.MemberDefinition is MethodDesc method) + if (provider is MethodDesc method) { - method = CompilerGeneratedState.GetUserDefinedMethodForCompilerGeneratedMember(method) ?? method; - var ecmaMethod = method.GetTypicalMethodDefinition() as EcmaMethod; suppressions = ecmaMethod?.GetDecodedCustomAttributes("System.Diagnostics.CodeAnalysis", "UnconditionalSuppressMessageAttribute"); } @@ -172,7 +222,7 @@ internal bool IsWarningSuppressed(int code, MessageOrigin origin) continue; } - if (code == suppressedCode) + if (id == suppressedCode) { return true; } @@ -235,5 +285,27 @@ private static string GetModuleFileName(ModuleDesc module) } return assemblyName; } + + internal bool ShouldSuppressAnalysisWarningsForRequires(TypeSystemEntity originMember, string requiresAttribute) + { + // Check if the current scope method has Requires on it + // since that attribute automatically suppresses all trim analysis warnings. + // Check both the immediate origin method as well as suppression context method + // since that will be different for compiler generated code. + if (originMember is MethodDesc method && + method.IsInRequiresScope(requiresAttribute)) + return true; + + MethodDesc owningMethod; + while (_compilerGeneratedState.TryGetOwningMethodForCompilerGeneratedMember(originMember, out owningMethod)) + { + Debug.Assert(owningMethod != originMember); + if (owningMethod.IsInRequiresScope(requiresAttribute)) + return true; + originMember = owningMethod; + } + + return false; + } } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logging/MessageOrigin.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logging/MessageOrigin.cs index 8ace7bd257b6b..b2d002cca090f 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logging/MessageOrigin.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logging/MessageOrigin.cs @@ -4,22 +4,27 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Reflection.Metadata; using System.Text; using Internal.IL; using Internal.TypeSystem; +#nullable enable + namespace ILCompiler.Logging { - public struct MessageOrigin + public struct MessageOrigin : #if false - : IComparable, IEquatable + IComparable, #endif + IEquatable { - public string FileName { get; } - public TypeSystemEntity MemberDefinition { get; } + public string? FileName { get; } + public TypeSystemEntity? MemberDefinition { get; } public int? SourceLine { get; } public int? SourceColumn { get; } + public int? ILOffset { get; } public MessageOrigin(string fileName, int? sourceLine = null, int? sourceColumn = null) { @@ -27,22 +32,24 @@ public MessageOrigin(string fileName, int? sourceLine = null, int? sourceColumn SourceLine = sourceLine; SourceColumn = sourceColumn; MemberDefinition = null; + ILOffset = null; } - public MessageOrigin(TypeSystemEntity memberDefinition, string fileName = null, int? sourceLine = 0, int? sourceColumn = 0) + public MessageOrigin(TypeSystemEntity memberDefinition, string? fileName = null, int? sourceLine = 0, int? sourceColumn = 0) { FileName = fileName; MemberDefinition = memberDefinition; SourceLine = sourceLine; SourceColumn = sourceColumn; + ILOffset = null; } public MessageOrigin(MethodIL methodBody, int ilOffset) { - string document = null; + string? document = null; int? lineNumber = null; - IEnumerable sequencePoints = methodBody.GetDebugInfo()?.GetSequencePoints(); + IEnumerable? sequencePoints = methodBody.GetDebugInfo()?.GetSequencePoints(); if (sequencePoints != null) { foreach (var sequencePoint in sequencePoints) @@ -58,6 +65,7 @@ public MessageOrigin(MethodIL methodBody, int ilOffset) MemberDefinition = methodBody.OwningMethod; SourceLine = lineNumber; SourceColumn = null; + ILOffset = ilOffset; } public MessageOrigin WithInstructionOffset(MethodIL methodBody, int ilOffset) @@ -66,7 +74,7 @@ public MessageOrigin WithInstructionOffset(MethodIL methodBody, int ilOffset) return new MessageOrigin(methodBody, ilOffset); } - public override string ToString() + public override string? ToString() { if (FileName == null) return null; @@ -84,30 +92,36 @@ public override string ToString() return sb.ToString(); } -#if false public bool Equals(MessageOrigin other) => (FileName, MemberDefinition, SourceLine, SourceColumn, ILOffset) == (other.FileName, other.MemberDefinition, other.SourceLine, other.SourceColumn, other.ILOffset); - public override bool Equals(object obj) => obj is MessageOrigin messageOrigin && Equals(messageOrigin); - public override int GetHashCode() => (FileName, MemberDefinition, SourceLine, SourceColumn).GetHashCode(); + public override bool Equals(object? obj) => obj is MessageOrigin messageOrigin && Equals(messageOrigin); + public override int GetHashCode() => (FileName, MemberDefinition, SourceLine, SourceColumn, ILOffset).GetHashCode(); public static bool operator ==(MessageOrigin lhs, MessageOrigin rhs) => lhs.Equals(rhs); public static bool operator !=(MessageOrigin lhs, MessageOrigin rhs) => !lhs.Equals(rhs); +#if false public int CompareTo(MessageOrigin other) { if (MemberDefinition != null && other.MemberDefinition != null) { - TypeDefinition thisTypeDef = (MemberDefinition as TypeDefinition) ?? MemberDefinition.DeclaringType; - TypeDefinition otherTypeDef = (other.MemberDefinition as TypeDefinition) ?? other.MemberDefinition.DeclaringType; - int result = (thisTypeDef?.Module?.Assembly?.Name?.Name, thisTypeDef?.Name, MemberDefinition?.Name).CompareTo - ((otherTypeDef?.Module?.Assembly?.Name?.Name, otherTypeDef?.Name, other.MemberDefinition?.Name)); - if (result != 0) - return result; - if (ILOffset != null && other.ILOffset != null) - return ILOffset.Value.CompareTo (other.ILOffset); - return ILOffset == null ? (other.ILOffset == null ? 0 : 1) : -1; + var thisMember = Provider as IMemberDefinition; + var otherMember = other.Provider as IMemberDefinition; + TypeDefinition? thisTypeDef = (Provider as TypeDefinition) ?? (Provider as IMemberDefinition)?.DeclaringType; + TypeDefinition? otherTypeDef = (other.Provider as TypeDefinition) ?? (other.Provider as IMemberDefinition)?.DeclaringType; + var thisAssembly = thisTypeDef?.Module.Assembly ?? Provider as AssemblyDefinition; + var otherAssembly = otherTypeDef?.Module.Assembly ?? other.Provider as AssemblyDefinition; + int result = (thisAssembly?.Name.Name, thisTypeDef?.Name, thisMember?.Name).CompareTo + ((otherAssembly?.Name.Name, otherTypeDef?.Name, otherMember?.Name)); + if (result != 0) + return result; + + if (ILOffset != null && other.ILOffset != null) + return ILOffset.Value.CompareTo(other.ILOffset); + + return ILOffset == null ? (other.ILOffset == null ? 0 : 1) : -1; } - else if (MemberDefinition == null && other.MemberDefinition == null) + else if (Provider == null && other.Provider == null) { if (FileName != null && other.FileName != null) { @@ -121,7 +135,7 @@ public int CompareTo(MessageOrigin other) return (FileName == null) ? 1 : -1; } - return (MemberDefinition == null) ? 1 : -1; + return (Provider == null) ? 1 : -1; } #endif } diff --git a/src/coreclr/tools/aot/ILCompiler/Program.cs b/src/coreclr/tools/aot/ILCompiler/Program.cs index b3a9ba34d8d30..1df78af1e32d6 100644 --- a/src/coreclr/tools/aot/ILCompiler/Program.cs +++ b/src/coreclr/tools/aot/ILCompiler/Program.cs @@ -744,7 +744,7 @@ static string ILLinkify(string rootedAssembly) ilProvider = new FeatureSwitchManager(ilProvider, featureSwitches); CompilerGeneratedState compilerGeneratedState = new CompilerGeneratedState(ilProvider); - var logger = new Logger(Console.Out, _isVerbose, ProcessWarningCodes(_suppressedWarnings), _singleWarn, _singleWarnEnabledAssemblies, _singleWarnDisabledAssemblies); + var logger = new Logger(Console.Out, compilerGeneratedState, _isVerbose, ProcessWarningCodes(_suppressedWarnings), _singleWarn, _singleWarnEnabledAssemblies, _singleWarnDisabledAssemblies); compilerGeneratedState.Logger = logger; var stackTracePolicy = _emitStackTraceData ? @@ -779,7 +779,7 @@ static string ILLinkify(string rootedAssembly) DynamicInvokeThunkGenerationPolicy invokeThunkGenerationPolicy = new DefaultDynamicInvokeThunkGenerationPolicy(); - var flowAnnotations = new ILLink.Shared.TrimAnalysis.FlowAnnotations(logger, ilProvider); + var flowAnnotations = new ILLink.Shared.TrimAnalysis.FlowAnnotations(logger, ilProvider, compilerGeneratedState); MetadataManager metadataManager = new UsageBasedMetadataManager( compilationGroup, diff --git a/src/coreclr/tools/aot/Mono.Linker.Tests/TestCasesRunner/ILCompilerDriver.cs b/src/coreclr/tools/aot/Mono.Linker.Tests/TestCasesRunner/ILCompilerDriver.cs index a0eff369ee98a..6a8457e433675 100644 --- a/src/coreclr/tools/aot/Mono.Linker.Tests/TestCasesRunner/ILCompilerDriver.cs +++ b/src/coreclr/tools/aot/Mono.Linker.Tests/TestCasesRunner/ILCompilerDriver.cs @@ -6,6 +6,7 @@ using System.Reflection; using System.Runtime.InteropServices; using ILCompiler; +using ILCompiler.Dataflow; using ILLink.Shared.TrimAnalysis; using Internal.IL; using Internal.TypeSystem; @@ -56,7 +57,9 @@ public void Trim (ILCompilerOptions options, ILogWriter logWriter) ilProvider = new FeatureSwitchManager (ilProvider, options.FeatureSwitches); - Logger logger = new Logger (logWriter, isVerbose: true); + CompilerGeneratedState compilerGeneratedState = new CompilerGeneratedState (ilProvider); + + Logger logger = new Logger (logWriter, compilerGeneratedState, isVerbose: true); UsageBasedMetadataManager metadataManager = new UsageBasedMetadataManager ( compilationGroup, @@ -66,7 +69,7 @@ public void Trim (ILCompilerOptions options, ILogWriter logWriter) logFile: null, new NoStackTraceEmissionPolicy (), new NoDynamicInvokeThunkGenerationPolicy (), - new FlowAnnotations (logger, ilProvider), + new FlowAnnotations (logger, ilProvider, compilerGeneratedState), UsageBasedMetadataGenerationOptions.ReflectionILScanning, logger, Array.Empty> (), From 222acda5ec5d82a14cd45b2e7dd131a6740f6c5a Mon Sep 17 00:00:00 2001 From: vitek-karas <10670590+vitek-karas@users.noreply.github.com> Date: Mon, 27 Jun 2022 03:17:21 -0700 Subject: [PATCH 08/18] Fixes --- .../Compiler/Dataflow/AttributeDataFlow.cs | 13 +++++++------ .../Compiler/Dataflow/MethodBodyScanner.cs | 3 +-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/AttributeDataFlow.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/AttributeDataFlow.cs index 19ab1864f905f..0260c80dbe4ff 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/AttributeDataFlow.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/AttributeDataFlow.cs @@ -96,13 +96,14 @@ void ProcessAttributeDataflow(MethodDesc method, ImmutableArray argumen public void ProcessAttributeDataflow(FieldDesc field, object? value, ref DependencyList? result) { - MultiValue valueNode = GetValueForCustomAttributeArgument(value); var fieldValueCandidate = _annotations.GetFieldValue(field); - if (fieldValueCandidate is not ValueWithDynamicallyAccessedMembers fieldValue) - return; - - var diagnosticContext = new DiagnosticContext(_origin, diagnosticsEnabled: true, _logger); - RequireDynamicallyAccessedMembers(diagnosticContext, valueNode, fieldValue, new FieldOrigin(field), ref result); + if (fieldValueCandidate is ValueWithDynamicallyAccessedMembers fieldValue + && fieldValue.DynamicallyAccessedMemberTypes != DynamicallyAccessedMemberTypes.None) + { + MultiValue valueNode = GetValueForCustomAttributeArgument(value); + var diagnosticContext = new DiagnosticContext(_origin, diagnosticsEnabled: true, _logger); + RequireDynamicallyAccessedMembers(diagnosticContext, valueNode, fieldValue, new FieldOrigin(field), ref result); + } } MultiValue GetValueForCustomAttributeArgument(object? argument) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/MethodBodyScanner.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/MethodBodyScanner.cs index da04fcd37e359..b6b885ab03d45 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/MethodBodyScanner.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/MethodBodyScanner.cs @@ -957,8 +957,7 @@ private void ScanLdloc( Stack currentStack, ValueBasicBlockPair?[] locals) { - bool isByRef = operation == ILOpcode.ldloca || operation == ILOpcode.ldloca_s - || methodBody.GetLocals()[index].Type.IsByRefOrPointer(); + bool isByRef = operation == ILOpcode.ldloca || operation == ILOpcode.ldloca_s; ValueBasicBlockPair? localValue = locals[index]; StackSlot newSlot; From d77232267a4856cbb5a5d13bc58f96a85e42ea65 Mon Sep 17 00:00:00 2001 From: vitek-karas <10670590+vitek-karas@users.noreply.github.com> Date: Mon, 27 Jun 2022 12:17:56 -0700 Subject: [PATCH 09/18] Formatting --- .../Dataflow/CompilerGeneratedCallGraph.cs | 96 ++++++++++--------- .../Dataflow/CompilerGeneratedState.cs | 3 +- .../Compiler/Dataflow/EcmaExtensions.cs | 3 +- .../Compiler/Dataflow/FieldReferenceValue.cs | 8 +- .../Dataflow/GenericArgumentDataFlow.cs | 2 +- .../Compiler/Dataflow/MethodBodyScanner.cs | 3 +- .../Dataflow/ParameterReferenceValue.cs | 14 +-- 7 files changed, 67 insertions(+), 62 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedCallGraph.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedCallGraph.cs index 55d412a1cb638..4512f0829204a 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedCallGraph.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedCallGraph.cs @@ -11,57 +11,61 @@ namespace ILCompiler.Dataflow { - sealed class CompilerGeneratedCallGraph - { - readonly Dictionary> callGraph; + sealed class CompilerGeneratedCallGraph + { + readonly Dictionary> callGraph; - public CompilerGeneratedCallGraph () => callGraph = new Dictionary> (); + public CompilerGeneratedCallGraph() => callGraph = new Dictionary>(); - void TrackCallInternal (TypeSystemEntity fromMember, TypeSystemEntity toMember) - { - if (!callGraph.TryGetValue (fromMember, out HashSet? toMembers)) { - toMembers = new HashSet (); - callGraph.Add (fromMember, toMembers); - } - toMembers.Add (toMember); - } + void TrackCallInternal(TypeSystemEntity fromMember, TypeSystemEntity toMember) + { + if (!callGraph.TryGetValue(fromMember, out HashSet? toMembers)) + { + toMembers = new HashSet(); + callGraph.Add(fromMember, toMembers); + } + toMembers.Add(toMember); + } - public void TrackCall (MethodDesc fromMethod, MethodDesc toMethod) - { - Debug.Assert (CompilerGeneratedNames.IsLambdaOrLocalFunction (toMethod.Name)); - TrackCallInternal (fromMethod, toMethod); - } + public void TrackCall(MethodDesc fromMethod, MethodDesc toMethod) + { + Debug.Assert(CompilerGeneratedNames.IsLambdaOrLocalFunction(toMethod.Name)); + TrackCallInternal(fromMethod, toMethod); + } - public void TrackCall (MethodDesc fromMethod, DefType toType) - { - Debug.Assert (CompilerGeneratedNames.IsStateMachineType (toType.Name)); - TrackCallInternal (fromMethod, toType); - } + public void TrackCall(MethodDesc fromMethod, DefType toType) + { + Debug.Assert(CompilerGeneratedNames.IsStateMachineType(toType.Name)); + TrackCallInternal(fromMethod, toType); + } - public void TrackCall (DefType fromType, MethodDesc toMethod) - { - Debug.Assert (CompilerGeneratedNames.IsStateMachineType (fromType.Name)); - Debug.Assert (CompilerGeneratedNames.IsLambdaOrLocalFunction (toMethod.Name)); - TrackCallInternal (fromType, toMethod); - } + public void TrackCall(DefType fromType, MethodDesc toMethod) + { + Debug.Assert(CompilerGeneratedNames.IsStateMachineType(fromType.Name)); + Debug.Assert(CompilerGeneratedNames.IsLambdaOrLocalFunction(toMethod.Name)); + TrackCallInternal(fromType, toMethod); + } - public IEnumerable GetReachableMembers (MethodDesc start) - { - Queue queue = new (); - HashSet visited = new (); - visited.Add (start); - queue.Enqueue (start); - while (queue.TryDequeue (out TypeSystemEntity? method)) { - if (!callGraph.TryGetValue (method, out HashSet? callees)) - continue; + public IEnumerable GetReachableMembers(MethodDesc start) + { + Queue queue = new(); + HashSet visited = new(); + visited.Add(start); + queue.Enqueue(start); + while (queue.TryDequeue(out TypeSystemEntity? method)) + { + if (!callGraph.TryGetValue(method, out HashSet? callees)) + continue; - foreach (var callee in callees) { - if (visited.Add (callee)) { - queue.Enqueue (callee); - yield return callee; - } - } - } - } - } + foreach (var callee in callees) + { + if (visited.Add(callee)) + { + queue.Enqueue(callee); + yield return callee; + } + } + } + } + } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedState.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedState.cs index 38a9f3595e431..d2977a6021421 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedState.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedState.cs @@ -159,7 +159,8 @@ void ProcessMethod(MethodDesc method) { ILOpcode opcode = reader.ReadILOpcode(); MethodDesc? lambdaOrLocalFunction; - switch (opcode) { + switch (opcode) + { case ILOpcode.ldftn: case ILOpcode.ldtoken: case ILOpcode.call: diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/EcmaExtensions.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/EcmaExtensions.cs index 85f74baf51d78..e992e2e7cc980 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/EcmaExtensions.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/EcmaExtensions.cs @@ -4,11 +4,12 @@ using Internal.TypeSystem; using Internal.TypeSystem.Ecma; +using ILLink.Shared.TypeSystemProxy; + using MethodAttributes = System.Reflection.MethodAttributes; using FieldAttributes = System.Reflection.FieldAttributes; using TypeAttributes = System.Reflection.TypeAttributes; using Debug = System.Diagnostics.Debug; -using ILLink.Shared.TypeSystemProxy; namespace ILCompiler.Dataflow { diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/FieldReferenceValue.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/FieldReferenceValue.cs index 14f8998575f33..30e5c53156fd3 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/FieldReferenceValue.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/FieldReferenceValue.cs @@ -9,8 +9,8 @@ namespace ILLink.Shared.TrimAnalysis { - public partial record FieldReferenceValue (FieldDesc FieldDefinition) : ReferenceValue - { - public override SingleValue DeepCopy () => this; - } + public partial record FieldReferenceValue(FieldDesc FieldDefinition) : ReferenceValue + { + public override SingleValue DeepCopy() => this; + } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/GenericArgumentDataFlow.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/GenericArgumentDataFlow.cs index 01f3d0363e273..1bdc58eee2922 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/GenericArgumentDataFlow.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/GenericArgumentDataFlow.cs @@ -53,7 +53,7 @@ DependencyList RequireDynamicallyAccessedMembers( ValueWithDynamicallyAccessedMembers targetValue, Origin memberWithRequirements) { - var reflectionMarker = new ReflectionMarker(_logger, _factory, _annotations, typeHierarchyDataFlow:false, enabled: true); + var reflectionMarker = new ReflectionMarker(_logger, _factory, _annotations, typeHierarchyDataFlow: false, enabled: true); var requireDynamicallyAccessedMembersAction = new RequireDynamicallyAccessedMembersAction(reflectionMarker, diagnosticContext, memberWithRequirements); requireDynamicallyAccessedMembersAction.Invoke(value, targetValue); return reflectionMarker.Dependencies; diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/MethodBodyScanner.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/MethodBodyScanner.cs index b6b885ab03d45..abb8e8055cda9 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/MethodBodyScanner.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/MethodBodyScanner.cs @@ -4,10 +4,10 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using ILCompiler.Logging; using ILLink.Shared; -using System.Reflection.Metadata; using ILLink.Shared.DataFlow; using ILLink.Shared.TrimAnalysis; using ILLink.Shared.TypeSystemProxy; @@ -15,7 +15,6 @@ using Internal.TypeSystem; using MultiValue = ILLink.Shared.DataFlow.ValueSet; -using System.Diagnostics.CodeAnalysis; #nullable enable diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ParameterReferenceValue.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ParameterReferenceValue.cs index 6b4f1270c4781..2ffd7e5ac3bc4 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ParameterReferenceValue.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ParameterReferenceValue.cs @@ -9,12 +9,12 @@ namespace ILLink.Shared.TrimAnalysis { - public partial record ParameterReferenceValue (MethodDesc MethodDefinition, int ParameterIndex) + public partial record ParameterReferenceValue(MethodDesc MethodDefinition, int ParameterIndex) : ReferenceValue - { - public override SingleValue DeepCopy () - { - return this; - } - } + { + public override SingleValue DeepCopy() + { + return this; + } + } } From 1cd000fb7125aeee36a0f83789f45604d8030a1e Mon Sep 17 00:00:00 2001 From: vitek-karas <10670590+vitek-karas@users.noreply.github.com> Date: Mon, 27 Jun 2022 12:24:35 -0700 Subject: [PATCH 10/18] Formatting --- .../Compiler/Dataflow/ReflectionMethodBodyScanner.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMethodBodyScanner.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMethodBodyScanner.cs index 7d93c44e536a2..705163eb3d672 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMethodBodyScanner.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMethodBodyScanner.cs @@ -170,8 +170,8 @@ private void HandleStoreValueWithDynamicallyAccessedMembers(MethodIL methodBody, protected override void HandleStoreField(MethodIL methodBody, int offset, FieldValue field, MultiValue valueToStore) => HandleStoreValueWithDynamicallyAccessedMembers(methodBody, offset, field, valueToStore, new FieldOrigin(field.Field)); // TODO: The previous HandleStoreField also did this: - // CheckAndReportRequires(field.Field, new MessageOrigin(methodBody.OwningMethod), RequiresUnreferencedCodeAttribute); - // CheckAndReportRequires(field.Field, new MessageOrigin(methodBody.OwningMethod), RequiresDynamicCodeAttribute); + // CheckAndReportRequires(field.Field, new MessageOrigin(methodBody.OwningMethod), RequiresUnreferencedCodeAttribute); + // CheckAndReportRequires(field.Field, new MessageOrigin(methodBody.OwningMethod), RequiresDynamicCodeAttribute); protected override void HandleStoreParameter(MethodIL methodBody, int offset, MethodParameterValue parameter, MultiValue valueToStore) => HandleStoreValueWithDynamicallyAccessedMembers(methodBody, offset, parameter, valueToStore, parameter.ParameterOrigin); From c34901f53f32a380f18e5c9a37b5fcc6c38f6d20 Mon Sep 17 00:00:00 2001 From: vitek-karas <10670590+vitek-karas@users.noreply.github.com> Date: Tue, 28 Jun 2022 05:02:42 -0700 Subject: [PATCH 11/18] Fixes - rewrite compiler generated state ILC is multi-threaded and CompilerGeneratedState stores a global cache. So the cache has to be synchronized. This rewrites it to use LockFreeReaderHashtable. --- .../Dataflow/CompilerGeneratedState.cs | 720 ++++++++++-------- .../Compiler/Dataflow/ReflectionMarker.cs | 2 +- .../Dataflow/ReflectionMethodBodyScanner.cs | 7 +- 3 files changed, 397 insertions(+), 332 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedState.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedState.cs index d2977a6021421..7fa413659f73f 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedState.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedState.cs @@ -22,408 +22,485 @@ namespace ILCompiler.Dataflow public class CompilerGeneratedState { readonly ILProvider _ilProvider; - // The MetadataType keys must be type definitions (uninstantiated) - readonly Dictionary _compilerGeneratedTypeToUserCodeMethod; - readonly Dictionary _generatedTypeToTypeArgumentInfo; readonly record struct TypeArgumentInfo( /// The method which calls the ctor for the given type MethodDesc CreatingMethod, /// Attributes for the type, pulled from the creators type arguments IReadOnlyList? OriginalAttributes); - readonly Dictionary _compilerGeneratedMethodToUserCodeMethod; + TypeCacheHashtable _typeCacheHashtable; - // For each type that has had its cache populated, stores a map of methods which have corresponding - // compiler-generated members (either methods or state machine types) to those compiler-generated members, - // or null if the type has no methods with compiler-generated members. - readonly Dictionary>?> _cachedTypeToCompilerGeneratedMembers; - - public CompilerGeneratedState(ILProvider ilProvider) - { - _ilProvider = ilProvider; - - _compilerGeneratedTypeToUserCodeMethod = new Dictionary(); - _generatedTypeToTypeArgumentInfo = new Dictionary(); - _compilerGeneratedMethodToUserCodeMethod = new Dictionary(); - _cachedTypeToCompilerGeneratedMembers = new Dictionary>?>(); - } - - public Logger? Logger { get; set; } - - static IEnumerable GetCompilerGeneratedNestedTypes(TypeDesc type) + public Logger? Logger { - if (type is not MetadataType metadataType) - yield break; - - foreach (var nestedType in metadataType.GetNestedTypes()) - { - if (!CompilerGeneratedNames.IsGeneratedMemberName(nestedType.Name)) - continue; - - yield return nestedType; - - foreach (var recursiveNestedType in GetCompilerGeneratedNestedTypes(nestedType)) - yield return recursiveNestedType; - } + get => _typeCacheHashtable.Logger; + set => _typeCacheHashtable.Logger = value; } - // "Nested function" refers to lambdas and local functions. - public static bool IsNestedFunctionOrStateMachineMember(TypeSystemEntity member) + public CompilerGeneratedState(ILProvider ilProvider) { - if (member is MethodDesc method && CompilerGeneratedNames.IsLambdaOrLocalFunction(method.Name)) - return true; - - if (member.GetOwningType() is not MetadataType declaringType) - return false; + _ilProvider = ilProvider; - return CompilerGeneratedNames.IsStateMachineType(declaringType.Name); + _typeCacheHashtable = new TypeCacheHashtable(ilProvider); } - public static bool TryGetStateMachineType(MethodDesc method, [NotNullWhen(true)] out MetadataType? stateMachineType) + class TypeCacheHashtable : LockFreeReaderHashtable { - stateMachineType = null; - // Discover state machine methods. - if (method is not EcmaMethod ecmaMethod) - return false; + private ILProvider _ilProvider; + internal Logger? Logger; - CustomAttributeValue? decodedAttribute = null; - decodedAttribute = ecmaMethod.GetDecodedCustomAttribute("System.Runtime.CompilerServices", "AsyncIteratorStateMachineAttribute"); - if (decodedAttribute == null) - decodedAttribute = ecmaMethod.GetDecodedCustomAttribute("System.Runtime.CompilerServices", "AsyncStateMachineAttribute"); - if (decodedAttribute == null) - decodedAttribute = ecmaMethod.GetDecodedCustomAttribute("System.Runtime.CompilerServices", "IteratorStateMachineAttribute"); + public TypeCacheHashtable(ILProvider ilProvider) => _ilProvider = ilProvider; - if (decodedAttribute == null) - return false; + protected override bool CompareKeyToValue(MetadataType key, TypeCache value) => key == value.Type; + protected override bool CompareValueToValue(TypeCache value1, TypeCache value2) => value1.Type == value2.Type; + protected override int GetKeyHashCode(MetadataType key) => key.GetHashCode(); + protected override int GetValueHashCode(TypeCache value) => value.Type.GetHashCode(); - stateMachineType = GetFirstConstructorArgumentAsType(decodedAttribute.Value) as MetadataType; - return stateMachineType != null; + protected override TypeCache CreateValueFromKey(MetadataType key) + => new TypeCache(key, Logger, _ilProvider); } - /// - /// Walks the type and its descendents to find Roslyn-compiler generated - /// code and gather information to map it back to original user code. If - /// a compiler-generated type is passed in directly, this method will walk - /// up and find the nearest containing user type. Returns the nearest user type, - /// or null if none was found. - /// - MetadataType? PopulateCacheForType(MetadataType? type) + class TypeCache { - Debug.Assert(type == type?.GetTypeDefinition()); + public readonly MetadataType Type; - // Look in the declaring type if this is a compiler-generated type (state machine or display class). - // State machines can be emitted into display classes, so we may also need to go one more level up. - // To avoid depending on implementation details, we go up until we see a non-compiler-generated type. - // This is the counterpart to GetCompilerGeneratedNestedTypes. - while (type != null && CompilerGeneratedNames.IsGeneratedMemberName(type.Name)) - type = type.ContainingType as MetadataType; + // The MetadataType keys must be type definitions (uninstantiated) + private Dictionary? _compilerGeneratedTypeToUserCodeMethod; + private Dictionary? _generatedTypeToTypeArgumentInfo; + private Dictionary? _compilerGeneratedMethodToUserCodeMethod; - if (type is null) - return null; + // Stores a map of methods which have corresponding compiler-generated members + // (either methods or state machine types) to those compiler-generated members, + // or null if the type has no methods with compiler-generated members. + private Dictionary>? _compilerGeneratedMembers; - // Avoid repeat scans of the same type - if (_cachedTypeToCompilerGeneratedMembers.ContainsKey(type)) - return type; + /// + /// Walks the type and its descendents to find Roslyn-compiler generated + /// code and gather information to map it back to original user code. If + /// a compiler-generated type is passed in directly, this method will walk + /// up and find the nearest containing user type. Returns the nearest user type, + /// or null if none was found. + /// + internal TypeCache(MetadataType type, Logger? logger, ILProvider ilProvider) + { + Debug.Assert(type == type.GetTypeDefinition()); + Debug.Assert(!CompilerGeneratedNames.IsGeneratedMemberName(type.Name)); - var callGraph = new CompilerGeneratedCallGraph(); - var userDefinedMethods = new HashSet(); + Type = type; - void ProcessMethod(MethodDesc method) - { - Debug.Assert(method == method.GetTypicalMethodDefinition()); + var callGraph = new CompilerGeneratedCallGraph(); + var userDefinedMethods = new HashSet(); - bool isStateMachineMember = CompilerGeneratedNames.IsStateMachineType(((MetadataType)method.OwningType).Name); - if (!CompilerGeneratedNames.IsLambdaOrLocalFunction(method.Name)) + void ProcessMethod(MethodDesc method) { - if (!isStateMachineMember) + Debug.Assert(method == method.GetTypicalMethodDefinition()); + + bool isStateMachineMember = CompilerGeneratedNames.IsStateMachineType(((MetadataType)method.OwningType).Name); + if (!CompilerGeneratedNames.IsLambdaOrLocalFunction(method.Name)) { - // If it's not a nested function, track as an entry point to the call graph. - var added = userDefinedMethods.Add(method); - Debug.Assert(added); + if (!isStateMachineMember) + { + // If it's not a nested function, track as an entry point to the call graph. + var added = userDefinedMethods.Add(method); + Debug.Assert(added); + } + } + else + { + // We don't expect lambdas or local functions to be emitted directly into + // state machine types. + Debug.Assert(!isStateMachineMember); } - } - else - { - // We don't expect lambdas or local functions to be emitted directly into - // state machine types. - Debug.Assert(!isStateMachineMember); - } - // Discover calls or references to lambdas or local functions. This includes - // calls to local functions, and lambda assignments (which use ldftn). - var methodBody = _ilProvider.GetMethodIL(method); - if (methodBody != null) - { - ILReader reader = new ILReader(methodBody.GetILBytes()); - while (reader.HasNext) + // Discover calls or references to lambdas or local functions. This includes + // calls to local functions, and lambda assignments (which use ldftn). + var methodBody = ilProvider.GetMethodIL(method); + if (methodBody != null) { - ILOpcode opcode = reader.ReadILOpcode(); - MethodDesc? lambdaOrLocalFunction; - switch (opcode) + ILReader reader = new ILReader(methodBody.GetILBytes()); + while (reader.HasNext) { - case ILOpcode.ldftn: - case ILOpcode.ldtoken: - case ILOpcode.call: - case ILOpcode.callvirt: - case ILOpcode.newobj: - lambdaOrLocalFunction = methodBody.GetObject(reader.ReadILToken()) as MethodDesc; - break; + ILOpcode opcode = reader.ReadILOpcode(); + MethodDesc? lambdaOrLocalFunction; + switch (opcode) + { + case ILOpcode.ldftn: + case ILOpcode.ldtoken: + case ILOpcode.call: + case ILOpcode.callvirt: + case ILOpcode.newobj: + lambdaOrLocalFunction = methodBody.GetObject(reader.ReadILToken()) as MethodDesc; + break; + + default: + lambdaOrLocalFunction = null; + reader.Skip(opcode); + break; + } - default: - lambdaOrLocalFunction = null; - reader.Skip(opcode); - break; - } + if (lambdaOrLocalFunction == null) + continue; - if (lambdaOrLocalFunction == null) - continue; + lambdaOrLocalFunction = lambdaOrLocalFunction.GetTypicalMethodDefinition(); - lambdaOrLocalFunction = lambdaOrLocalFunction.GetTypicalMethodDefinition(); + if (lambdaOrLocalFunction.IsConstructor && + lambdaOrLocalFunction.OwningType is MetadataType generatedType && + // Don't consider calls in the same type, like inside a static constructor + method.OwningType != generatedType && + CompilerGeneratedNames.IsLambdaDisplayClass(generatedType.Name)) + { + generatedType = (MetadataType)generatedType.GetTypeDefinition(); - if (lambdaOrLocalFunction.IsConstructor && - lambdaOrLocalFunction.OwningType is MetadataType generatedType && - // Don't consider calls in the same type, like inside a static constructor - method.OwningType != generatedType && - CompilerGeneratedNames.IsLambdaDisplayClass(generatedType.Name)) - { - generatedType = (MetadataType)generatedType.GetTypeDefinition(); + // fill in null for now, attribute providers will be filled in later + _generatedTypeToTypeArgumentInfo ??= new Dictionary(); - // fill in null for now, attribute providers will be filled in later - if (!_generatedTypeToTypeArgumentInfo.TryAdd(generatedType, new TypeArgumentInfo(method, null))) - { - var alreadyAssociatedMethod = _generatedTypeToTypeArgumentInfo[generatedType].CreatingMethod; - Logger?.LogWarning(new MessageOrigin(method), DiagnosticId.MethodsAreAssociatedWithUserMethod, method.GetDisplayName(), alreadyAssociatedMethod.GetDisplayName(), generatedType.GetDisplayName()); + if (!_generatedTypeToTypeArgumentInfo.TryAdd(generatedType, new TypeArgumentInfo(method, null))) + { + var alreadyAssociatedMethod = _generatedTypeToTypeArgumentInfo[generatedType].CreatingMethod; + logger?.LogWarning(new MessageOrigin(method), DiagnosticId.MethodsAreAssociatedWithUserMethod, method.GetDisplayName(), alreadyAssociatedMethod.GetDisplayName(), generatedType.GetDisplayName()); + } + continue; } - continue; - } - if (!CompilerGeneratedNames.IsLambdaOrLocalFunction(lambdaOrLocalFunction.Name)) - continue; + if (!CompilerGeneratedNames.IsLambdaOrLocalFunction(lambdaOrLocalFunction.Name)) + continue; - if (isStateMachineMember) - { - if (method.OwningType is MetadataType owningType) + if (isStateMachineMember) { - callGraph.TrackCall(owningType, lambdaOrLocalFunction); + if (method.OwningType is MetadataType owningType) + { + callGraph.TrackCall(owningType, lambdaOrLocalFunction); + } + } + else + { + callGraph.TrackCall(method, lambdaOrLocalFunction); } - } - else - { - callGraph.TrackCall(method, lambdaOrLocalFunction); } } - } - if (TryGetStateMachineType(method, out MetadataType? stateMachineType)) - { - Debug.Assert(stateMachineType.ContainingType == type || - (CompilerGeneratedNames.IsGeneratedMemberName(stateMachineType.ContainingType.Name) && - stateMachineType.ContainingType.ContainingType == type)); - Debug.Assert(stateMachineType == stateMachineType.GetTypeDefinition()); + if (TryGetStateMachineType(method, out MetadataType? stateMachineType)) + { + Debug.Assert(stateMachineType.ContainingType == type || + (CompilerGeneratedNames.IsGeneratedMemberName(stateMachineType.ContainingType.Name) && + stateMachineType.ContainingType.ContainingType == type)); + Debug.Assert(stateMachineType == stateMachineType.GetTypeDefinition()); - callGraph.TrackCall(method, stateMachineType); + callGraph.TrackCall(method, stateMachineType); - if (!_compilerGeneratedTypeToUserCodeMethod.TryAdd(stateMachineType, method)) - { - var alreadyAssociatedMethod = _compilerGeneratedTypeToUserCodeMethod[stateMachineType]; - Logger?.LogWarning(new MessageOrigin(method), DiagnosticId.MethodsAreAssociatedWithStateMachine, method.GetDisplayName(), alreadyAssociatedMethod.GetDisplayName(), stateMachineType.GetDisplayName()); + _compilerGeneratedTypeToUserCodeMethod ??= new Dictionary(); + if (!_compilerGeneratedTypeToUserCodeMethod.TryAdd(stateMachineType, method)) + { + var alreadyAssociatedMethod = _compilerGeneratedTypeToUserCodeMethod[stateMachineType]; + logger?.LogWarning(new MessageOrigin(method), DiagnosticId.MethodsAreAssociatedWithStateMachine, method.GetDisplayName(), alreadyAssociatedMethod.GetDisplayName(), stateMachineType.GetDisplayName()); + } + // Already warned above if multiple methods map to the same type + // Fill in null for argument providers now, the real providers will be filled in later + _generatedTypeToTypeArgumentInfo ??= new Dictionary(); + _ = _generatedTypeToTypeArgumentInfo.TryAdd(stateMachineType, new TypeArgumentInfo(method, null)); } - // Already warned above if multiple methods map to the same type - // Fill in null for argument providers now, the real providers will be filled in later - _ = _generatedTypeToTypeArgumentInfo.TryAdd(stateMachineType, new TypeArgumentInfo(method, null)); } - } - - // Look for state machine methods, and methods which call local functions. - foreach (MethodDesc method in type.GetMethods()) - ProcessMethod(method); - - // Also scan compiler-generated state machine methods (in case they have calls to nested functions), - // and nested functions inside compiler-generated closures (in case they call other nested functions). - // State machines can be emitted into lambda display classes, so we need to go down at least two - // levels to find calls from iterator nested functions to other nested functions. We just recurse into - // all compiler-generated nested types to avoid depending on implementation details. - - foreach (var nestedType in GetCompilerGeneratedNestedTypes(type)) - { - foreach (var method in nestedType.GetMethods()) + // Look for state machine methods, and methods which call local functions. + foreach (MethodDesc method in type.GetMethods()) ProcessMethod(method); - } - - // Now we've discovered the call graphs for calls to nested functions. - // Use this to map back from nested functions to the declaring user methods. - // Note: This maps all nested functions back to the user code, not to the immediately - // declaring local function. The IL doesn't contain enough information in general for - // us to determine the nesting of local functions and lambdas. + // Also scan compiler-generated state machine methods (in case they have calls to nested functions), + // and nested functions inside compiler-generated closures (in case they call other nested functions). - // Note: this only discovers nested functions which are referenced from the user - // code or its referenced nested functions. There is no reliable way to determine from - // IL which user code an unused nested function belongs to. + // State machines can be emitted into lambda display classes, so we need to go down at least two + // levels to find calls from iterator nested functions to other nested functions. We just recurse into + // all compiler-generated nested types to avoid depending on implementation details. - Dictionary>? compilerGeneratedCallees = null; - foreach (var userDefinedMethod in userDefinedMethods) - { - var callees = callGraph.GetReachableMembers(userDefinedMethod); - if (!callees.Any()) - continue; - - compilerGeneratedCallees ??= new Dictionary>(); - compilerGeneratedCallees.Add(userDefinedMethod, new List(callees)); - - foreach (var compilerGeneratedMember in callees) + foreach (var nestedType in GetCompilerGeneratedNestedTypes(type)) { - switch (compilerGeneratedMember) - { - case MethodDesc nestedFunction: - Debug.Assert(CompilerGeneratedNames.IsLambdaOrLocalFunction(nestedFunction.Name)); - // Nested functions get suppressions from the user method only. - if (!_compilerGeneratedMethodToUserCodeMethod.TryAdd(nestedFunction, userDefinedMethod)) - { - var alreadyAssociatedMethod = _compilerGeneratedMethodToUserCodeMethod[nestedFunction]; - Logger?.LogWarning(new MessageOrigin(userDefinedMethod), DiagnosticId.MethodsAreAssociatedWithUserMethod, userDefinedMethod.GetDisplayName(), alreadyAssociatedMethod.GetDisplayName(), nestedFunction.GetDisplayName()); - } - break; - case MetadataType stateMachineType: - // Types in the call graph are always state machine types - // For those all their methods are not tracked explicitly in the call graph; instead, they - // are represented by the state machine type itself. - // We are already tracking the association of the state machine type to the user code method - // above, so no need to track it here. - Debug.Assert(CompilerGeneratedNames.IsStateMachineType(stateMachineType.Name)); - break; - default: - throw new InvalidOperationException(); - } + foreach (var method in nestedType.GetMethods()) + ProcessMethod(method); } - } - // Now that we have instantiating methods fully filled out, walk the generated types and fill in the attribute - // providers - foreach (var generatedType in _generatedTypeToTypeArgumentInfo.Keys) - { - Debug.Assert(generatedType == generatedType.GetTypeDefinition()); + // Now we've discovered the call graphs for calls to nested functions. + // Use this to map back from nested functions to the declaring user methods. - if (HasGenericParameters(generatedType)) - MapGeneratedTypeTypeParameters(generatedType); - } + // Note: This maps all nested functions back to the user code, not to the immediately + // declaring local function. The IL doesn't contain enough information in general for + // us to determine the nesting of local functions and lambdas. - _cachedTypeToCompilerGeneratedMembers.Add(type, compilerGeneratedCallees); - return type; + // Note: this only discovers nested functions which are referenced from the user + // code or its referenced nested functions. There is no reliable way to determine from + // IL which user code an unused nested function belongs to. - /// - /// Check if the type itself is generic. The only difference is that - /// if the type is a nested type, the generic parameters from its - /// parent type don't count. - /// - static bool HasGenericParameters(MetadataType typeDef) - { - if (typeDef.ContainingType == null) - return typeDef.HasInstantiation; + foreach (var userDefinedMethod in userDefinedMethods) + { + var callees = callGraph.GetReachableMembers(userDefinedMethod); + if (!callees.Any()) + continue; - return typeDef.Instantiation.Length > typeDef.ContainingType.Instantiation.Length; - } + _compilerGeneratedMembers ??= new Dictionary>(); + _compilerGeneratedMembers.Add(userDefinedMethod, new List(callees)); - void MapGeneratedTypeTypeParameters(MetadataType generatedType) - { - Debug.Assert(CompilerGeneratedNames.IsGeneratedType(generatedType.Name)); - Debug.Assert(generatedType == generatedType.GetTypeDefinition()); + foreach (var compilerGeneratedMember in callees) + { + switch (compilerGeneratedMember) + { + case MethodDesc nestedFunction: + Debug.Assert(CompilerGeneratedNames.IsLambdaOrLocalFunction(nestedFunction.Name)); + // Nested functions get suppressions from the user method only. + _compilerGeneratedMethodToUserCodeMethod ??= new Dictionary(); + if (!_compilerGeneratedMethodToUserCodeMethod.TryAdd(nestedFunction, userDefinedMethod)) + { + var alreadyAssociatedMethod = _compilerGeneratedMethodToUserCodeMethod[nestedFunction]; + logger?.LogWarning(new MessageOrigin(userDefinedMethod), DiagnosticId.MethodsAreAssociatedWithUserMethod, userDefinedMethod.GetDisplayName(), alreadyAssociatedMethod.GetDisplayName(), nestedFunction.GetDisplayName()); + } + break; + case MetadataType stateMachineType: + // Types in the call graph are always state machine types + // For those all their methods are not tracked explicitly in the call graph; instead, they + // are represented by the state machine type itself. + // We are already tracking the association of the state machine type to the user code method + // above, so no need to track it here. + Debug.Assert(CompilerGeneratedNames.IsStateMachineType(stateMachineType.Name)); + break; + default: + throw new InvalidOperationException(); + } + } + } - if (!_generatedTypeToTypeArgumentInfo.TryGetValue(generatedType, out var typeInfo)) + // Now that we have instantiating methods fully filled out, walk the generated types and fill in the attribute + // providers + if (_generatedTypeToTypeArgumentInfo != null) { - // This can happen for static (non-capturing) closure environments, where more than - // nested function can map to the same closure environment. Since the current functionality - // is based on a one-to-one relationship between environments (types) and methods, this is - // not supported. - return; + foreach (var generatedType in _generatedTypeToTypeArgumentInfo.Keys) + { + Debug.Assert(generatedType == generatedType.GetTypeDefinition()); + + if (HasGenericParameters(generatedType)) + MapGeneratedTypeTypeParameters(generatedType); + } } - if (typeInfo.OriginalAttributes is not null) + /// + /// Check if the type itself is generic. The only difference is that + /// if the type is a nested type, the generic parameters from its + /// parent type don't count. + /// + static bool HasGenericParameters(MetadataType typeDef) { - return; + if (typeDef.ContainingType == null) + return typeDef.HasInstantiation; + + return typeDef.Instantiation.Length > typeDef.ContainingType.Instantiation.Length; } - var method = typeInfo.CreatingMethod; - var body = _ilProvider.GetMethodIL(method); - if (body is not null) + + void MapGeneratedTypeTypeParameters(MetadataType generatedType) { - var typeArgs = new GenericParameterDesc?[generatedType.Instantiation.Length]; - var typeRef = ScanForInit(generatedType, body); - if (typeRef is null) + Debug.Assert(CompilerGeneratedNames.IsGeneratedType(generatedType.Name)); + Debug.Assert(generatedType == generatedType.GetTypeDefinition()); + + if (_generatedTypeToTypeArgumentInfo?.TryGetValue(generatedType, out var typeInfo) != true) { + // This can happen for static (non-capturing) closure environments, where more than + // nested function can map to the same closure environment. Since the current functionality + // is based on a one-to-one relationship between environments (types) and methods, this is + // not supported. return; } - // The typeRef is going to be a generic instantiation with signature variables - // We need to figure out the actual generic parameters which were used to create these - // so instantiate the typeRef in the context of the method body where it is created - TypeDesc instantiatedType = typeRef.InstantiateSignature(method.OwningType.Instantiation, method.Instantiation); - for (int i = 0; i < instantiatedType.Instantiation.Length; i++) + if (typeInfo.OriginalAttributes is not null) { - var typeArg = instantiatedType.Instantiation[i]; - // Start with the existing parameters, in case we can't find the mapped one - GenericParameterDesc? userAttrs = generatedType.Instantiation[i] as GenericParameterDesc; - // The type parameters of the state machine types are alpha renames of the - // the method parameters, so the type ref should always be a GenericParameter. However, - // in the case of nesting, there may be multiple renames, so if the parameter is a method - // we know we're done, but if it's another state machine, we have to keep looking to find - // the original owner of that state machine. - if (typeArg is GenericParameterDesc { Kind: { } kind } param) + return; + } + var method = typeInfo.CreatingMethod; + var body = ilProvider.GetMethodIL(method); + if (body is not null) + { + var typeArgs = new GenericParameterDesc?[generatedType.Instantiation.Length]; + var typeRef = ScanForInit(generatedType, body); + if (typeRef is null) { - if (kind == GenericParameterKind.Method) - { - userAttrs = param; - } - else + return; + } + + // The typeRef is going to be a generic instantiation with signature variables + // We need to figure out the actual generic parameters which were used to create these + // so instantiate the typeRef in the context of the method body where it is created + TypeDesc instantiatedType = typeRef.InstantiateSignature(method.OwningType.Instantiation, method.Instantiation); + for (int i = 0; i < instantiatedType.Instantiation.Length; i++) + { + var typeArg = instantiatedType.Instantiation[i]; + // Start with the existing parameters, in case we can't find the mapped one + GenericParameterDesc? userAttrs = generatedType.Instantiation[i] as GenericParameterDesc; + // The type parameters of the state machine types are alpha renames of the + // the method parameters, so the type ref should always be a GenericParameter. However, + // in the case of nesting, there may be multiple renames, so if the parameter is a method + // we know we're done, but if it's another state machine, we have to keep looking to find + // the original owner of that state machine. + if (typeArg is GenericParameterDesc { Kind: { } kind } param) { - // Must be a type ref - if (method.OwningType is not MetadataType owningType || !CompilerGeneratedNames.IsGeneratedType(owningType.Name)) + if (kind == GenericParameterKind.Method) { userAttrs = param; } else { - owningType = (MetadataType)owningType.GetTypeDefinition(); - MapGeneratedTypeTypeParameters(owningType); - if (_generatedTypeToTypeArgumentInfo.TryGetValue(owningType, out var owningInfo) && - owningInfo.OriginalAttributes is { } owningAttrs) + // Must be a type ref + if (method.OwningType is not MetadataType owningType || !CompilerGeneratedNames.IsGeneratedType(owningType.Name)) + { + userAttrs = param; + } + else { - userAttrs = owningAttrs[param.Index]; + owningType = (MetadataType)owningType.GetTypeDefinition(); + MapGeneratedTypeTypeParameters(owningType); + if (_generatedTypeToTypeArgumentInfo.TryGetValue(owningType, out var owningInfo) && + owningInfo.OriginalAttributes is { } owningAttrs) + { + userAttrs = owningAttrs[param.Index]; + } } } } + + typeArgs[i] = userAttrs; } + _generatedTypeToTypeArgumentInfo[generatedType] = typeInfo with { OriginalAttributes = typeArgs }; + } + } - typeArgs[i] = userAttrs; + MetadataType? ScanForInit(MetadataType stateMachineType, MethodIL body) + { + ILReader reader = new ILReader(body.GetILBytes()); + while (reader.HasNext) + { + ILOpcode opcode = reader.ReadILOpcode(); + switch (opcode) + { + case ILOpcode.initobj: + case ILOpcode.newobj: + if (body.GetObject(reader.ReadILToken()) is MethodDesc { OwningType: MetadataType owningType } + && stateMachineType == owningType.GetTypeDefinition()) + { + return owningType; + } + break; + + default: + reader.Skip(opcode); + break; + } } - _generatedTypeToTypeArgumentInfo[generatedType] = typeInfo with { OriginalAttributes = typeArgs }; + return null; } } - MetadataType? ScanForInit(MetadataType stateMachineType, MethodIL body) + public bool TryGetCompilerGeneratedCalleesForUserMethod(MethodDesc method, [NotNullWhen(true)] out List? callees) { - ILReader reader = new ILReader(body.GetILBytes()); - while (reader.HasNext) + if (_compilerGeneratedMembers == null) { - ILOpcode opcode = reader.ReadILOpcode(); - switch (opcode) - { - case ILOpcode.initobj: - case ILOpcode.newobj: - if (body.GetObject(reader.ReadILToken()) is MethodDesc { OwningType: MetadataType owningType } - && stateMachineType == owningType.GetTypeDefinition()) - { - return owningType; - } - break; + callees = null; + return false; + } - default: - reader.Skip(opcode); - break; - } + return _compilerGeneratedMembers.TryGetValue(method, out callees); + } + + public IReadOnlyList? GetGeneratedTypeAttributes(MetadataType type) + { + if (_generatedTypeToTypeArgumentInfo?.TryGetValue(type, out var typeInfo) == true) + { + return typeInfo.OriginalAttributes; } + return null; } + + public bool TryGetOwningMethodForCompilerGeneratedMethod(MethodDesc compilerGeneratedMethod, [NotNullWhen(true)] out MethodDesc? owningMethod) + { + if (_compilerGeneratedMethodToUserCodeMethod == null) + { + owningMethod = null; + return false; + } + + return _compilerGeneratedMethodToUserCodeMethod.TryGetValue(compilerGeneratedMethod, out owningMethod); + } + + public bool TryGetOwningMethodForCompilerGeneratedType(MetadataType compilerGeneratedType, [NotNullWhen(true)] out MethodDesc? owningMethod) + { + if (_compilerGeneratedTypeToUserCodeMethod == null) + { + owningMethod = null; + return false; + } + + return _compilerGeneratedTypeToUserCodeMethod.TryGetValue(compilerGeneratedType, out owningMethod); + } + } + + static IEnumerable GetCompilerGeneratedNestedTypes(TypeDesc type) + { + if (type is not MetadataType metadataType) + yield break; + + foreach (var nestedType in metadataType.GetNestedTypes()) + { + if (!CompilerGeneratedNames.IsGeneratedMemberName(nestedType.Name)) + continue; + + yield return nestedType; + + foreach (var recursiveNestedType in GetCompilerGeneratedNestedTypes(nestedType)) + yield return recursiveNestedType; + } + } + + // "Nested function" refers to lambdas and local functions. + public static bool IsNestedFunctionOrStateMachineMember(TypeSystemEntity member) + { + if (member is MethodDesc method && CompilerGeneratedNames.IsLambdaOrLocalFunction(method.Name)) + return true; + + if (member.GetOwningType() is not MetadataType declaringType) + return false; + + return CompilerGeneratedNames.IsStateMachineType(declaringType.Name); + } + + public static bool TryGetStateMachineType(MethodDesc method, [NotNullWhen(true)] out MetadataType? stateMachineType) + { + stateMachineType = null; + // Discover state machine methods. + if (method is not EcmaMethod ecmaMethod) + return false; + + CustomAttributeValue? decodedAttribute = null; + decodedAttribute = ecmaMethod.GetDecodedCustomAttribute("System.Runtime.CompilerServices", "AsyncIteratorStateMachineAttribute"); + if (decodedAttribute == null) + decodedAttribute = ecmaMethod.GetDecodedCustomAttribute("System.Runtime.CompilerServices", "AsyncStateMachineAttribute"); + if (decodedAttribute == null) + decodedAttribute = ecmaMethod.GetDecodedCustomAttribute("System.Runtime.CompilerServices", "IteratorStateMachineAttribute"); + + if (decodedAttribute == null) + return false; + + stateMachineType = GetFirstConstructorArgumentAsType(decodedAttribute.Value) as MetadataType; + return stateMachineType != null; + } + + private TypeCache? PopulateCacheForType(MetadataType? type) + { + Debug.Assert(type == type?.GetTypeDefinition()); + + // Look in the declaring type if this is a compiler-generated type (state machine or display class). + // State machines can be emitted into display classes, so we may also need to go one more level up. + // To avoid depending on implementation details, we go up until we see a non-compiler-generated type. + // This is the counterpart to GetCompilerGeneratedNestedTypes. + while (type != null && CompilerGeneratedNames.IsGeneratedMemberName(type.Name)) + type = type.ContainingType as MetadataType; + + if (type is null) + return null; + + return _typeCacheHashtable.GetOrCreateValue(type); } static TypeDesc? GetFirstConstructorArgumentAsType(CustomAttributeValue attribute) @@ -445,11 +522,11 @@ public bool TryGetCompilerGeneratedCalleesForUserMethod(MethodDesc method, [NotN if (method.OwningType is not MetadataType owningType) return false; - var typeToCache = PopulateCacheForType(owningType); - if (typeToCache is null) + var typeCache = PopulateCacheForType(owningType); + if (typeCache is null) return false; - return _cachedTypeToCompilerGeneratedMembers[typeToCache]?.TryGetValue(method, out callees) == true; + return typeCache.TryGetCompilerGeneratedCalleesForUserMethod(method, out callees); } /// @@ -461,15 +538,11 @@ public bool TryGetCompilerGeneratedCalleesForUserMethod(MethodDesc method, [NotN MetadataType generatedType = (MetadataType)type.GetTypeDefinition(); Debug.Assert(CompilerGeneratedNames.IsGeneratedType(generatedType.Name)); - var typeToCache = PopulateCacheForType(generatedType); - if (typeToCache is null) + var typeCache = PopulateCacheForType(generatedType); + if (typeCache is null) return null; - if (_generatedTypeToTypeArgumentInfo.TryGetValue(generatedType, out var typeInfo)) - { - return typeInfo.OriginalAttributes; - } - return null; + return typeCache.GetGeneratedTypeAttributes(type); } // For state machine types/members, maps back to the state machine method. @@ -481,37 +554,28 @@ public bool TryGetOwningMethodForCompilerGeneratedMember(TypeSystemEntity source if (sourceMember == null) return false; - MethodDesc? compilerGeneratedMethod = sourceMember as MethodDesc; - if (compilerGeneratedMethod != null) - { - if (_compilerGeneratedMethodToUserCodeMethod.TryGetValue(compilerGeneratedMethod, out owningMethod)) - return true; - } - MetadataType? sourceType = ((sourceMember as TypeDesc) ?? sourceMember.GetOwningType())?.GetTypeDefinition() as MetadataType; if (sourceType is null) return false; - if (_compilerGeneratedTypeToUserCodeMethod.TryGetValue(sourceType, out owningMethod)) - return true; - if (!IsNestedFunctionOrStateMachineMember(sourceMember)) return false; // sourceType is a state machine type, or the type containing a lambda or local function. // Search all methods to find the one which points to the type as its // state machine implementation. - var typeToCache = PopulateCacheForType(sourceType); - if (typeToCache is null) + var typeCache = PopulateCacheForType(sourceType); + if (typeCache is null) return false; + MethodDesc? compilerGeneratedMethod = sourceMember as MethodDesc; if (compilerGeneratedMethod != null) { - if (_compilerGeneratedMethodToUserCodeMethod.TryGetValue(compilerGeneratedMethod, out owningMethod)) + if (typeCache.TryGetOwningMethodForCompilerGeneratedMethod(compilerGeneratedMethod, out owningMethod)) return true; } - if (_compilerGeneratedTypeToUserCodeMethod.TryGetValue(sourceType, out owningMethod)) + if (typeCache.TryGetOwningMethodForCompilerGeneratedType(sourceType, out owningMethod)) return true; return false; diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMarker.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMarker.cs index 69c2cf98245cc..7a396cfe0e18b 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMarker.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMarker.cs @@ -184,7 +184,7 @@ internal void MarkStaticConstructor(in MessageOrigin origin, TypeDesc type) if (!_enabled) return; - if (!type.IsGenericDefinition && !type.ContainsSignatureVariables(treatGenericParameterLikeSignatureVariable: true) && type.HasStaticConstructor) + if (!type.IsGenericDefinition && !type.ContainsSignatureVariables(treatGenericParameterLikeSignatureVariable: true) && Factory.PreinitializationManager.HasLazyStaticConstructor(type)) { // Mark the GC static base - it contains a pointer to the class constructor, but also info // about whether the class constructor already executed and it's what is looked at at runtime. diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMethodBodyScanner.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMethodBodyScanner.cs index 705163eb3d672..a3466002e092b 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMethodBodyScanner.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMethodBodyScanner.cs @@ -33,7 +33,7 @@ class ReflectionMethodBodyScanner : MethodBodyScanner { private readonly Logger _logger; private readonly NodeFactory _factory; - private readonly ReflectionMarker _reflectionMarker; + private ReflectionMarker _reflectionMarker; private readonly TrimAnalysisPatternStore TrimAnalysisPatterns; private MessageOrigin _origin; @@ -93,8 +93,9 @@ public override void InterproceduralScan(MethodIL methodBody) { base.InterproceduralScan(methodBody); - var reflectionMarker = new ReflectionMarker(_logger, _factory, _annotations, typeHierarchyDataFlow: false, enabled: true); - TrimAnalysisPatterns.MarkAndProduceDiagnostics(reflectionMarker); + // Replace the reflection marker with one which actually marks + _reflectionMarker = new ReflectionMarker(_logger, _factory, _annotations, typeHierarchyDataFlow: false, enabled: true); + TrimAnalysisPatterns.MarkAndProduceDiagnostics(_reflectionMarker); } protected override void Scan(MethodIL methodBody, ref ValueSet methodsInGroup) From 38520cec777b4a8c37aece53df1ddf7d7949cfd9 Mon Sep 17 00:00:00 2001 From: vitek-karas <10670590+vitek-karas@users.noreply.github.com> Date: Wed, 29 Jun 2022 08:37:50 -0700 Subject: [PATCH 12/18] Fixes With this it passes smoketests --- .../ILCompiler.Compiler/Compiler/Dataflow/AttributeDataFlow.cs | 2 +- .../ILCompiler.Compiler/Compiler/UsageBasedMetadataManager.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/AttributeDataFlow.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/AttributeDataFlow.cs index 0260c80dbe4ff..5cced116a7231 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/AttributeDataFlow.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/AttributeDataFlow.cs @@ -72,7 +72,7 @@ public AttributeDataFlow(Logger logger, NodeFactory factory, FlowAnnotations ann MethodDesc setter = property.SetMethod; if (setter != null && setter.Signature.Length > 0 && !setter.Signature.IsStatic) { - ProcessAttributeDataflow(method, ImmutableArray.Create(namedArgument.Value), ref result); + ProcessAttributeDataflow(setter, ImmutableArray.Create(namedArgument.Value), ref result); } } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/UsageBasedMetadataManager.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/UsageBasedMetadataManager.cs index 3ac813a793db6..dc444aba181ac 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/UsageBasedMetadataManager.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/UsageBasedMetadataManager.cs @@ -690,7 +690,7 @@ public override DependencyList GetDependenciesForCustomAttribute(NodeFactory fac bool scanReflection = (_generationOptions & UsageBasedMetadataGenerationOptions.ReflectionILScanning) != 0; if (scanReflection) { - (new AttributeDataFlow(Logger, factory, FlowAnnotations, new Logging.MessageOrigin(attributeCtor))).ProcessAttributeDataflow(attributeCtor, decodedValue); + return (new AttributeDataFlow(Logger, factory, FlowAnnotations, new Logging.MessageOrigin(attributeCtor))).ProcessAttributeDataflow(attributeCtor, decodedValue); } return null; From f29afe0d164f04f16fefae8d91ad65a3ea8f16d3 Mon Sep 17 00:00:00 2001 From: vitek-karas <10670590+vitek-karas@users.noreply.github.com> Date: Thu, 30 Jun 2022 04:14:04 -0700 Subject: [PATCH 13/18] Cleanup and fixes --- .../Compiler/Dataflow/AttributeDataFlow.cs | 2 +- .../Compiler/Dataflow/CompilerGeneratedState.cs | 7 ++----- .../Compiler/Dataflow/ReflectionMethodBodyScanner.cs | 8 +++----- .../Compiler/Dataflow/TrimAnalysisAssignmentPattern.cs | 10 ++++++++++ 4 files changed, 16 insertions(+), 11 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/AttributeDataFlow.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/AttributeDataFlow.cs index 5cced116a7231..658bc535c7880 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/AttributeDataFlow.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/AttributeDataFlow.cs @@ -118,7 +118,7 @@ MultiValue GetValueForCustomAttributeArgument(object? argument) return new KnownStringValue(str); } - // We shouldn't have gotten a non-null annotation for this from GetParameterAnnotation + // We shouldn't have gotten a None annotation from flow annotations since only string/Type can have annotations throw new InvalidOperationException(); } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedState.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedState.cs index 7fa413659f73f..e41e95c620996 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedState.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedState.cs @@ -21,14 +21,13 @@ namespace ILCompiler.Dataflow // Currently this is implemented using heuristics public class CompilerGeneratedState { - readonly ILProvider _ilProvider; readonly record struct TypeArgumentInfo( /// The method which calls the ctor for the given type MethodDesc CreatingMethod, /// Attributes for the type, pulled from the creators type arguments IReadOnlyList? OriginalAttributes); - TypeCacheHashtable _typeCacheHashtable; + readonly TypeCacheHashtable _typeCacheHashtable; public Logger? Logger { @@ -38,8 +37,6 @@ public Logger? Logger public CompilerGeneratedState(ILProvider ilProvider) { - _ilProvider = ilProvider; - _typeCacheHashtable = new TypeCacheHashtable(ilProvider); } @@ -63,7 +60,7 @@ class TypeCache { public readonly MetadataType Type; - // The MetadataType keys must be type definitions (uninstantiated) + // The MetadataType keys must be type definitions (uninstantiated) same goes for MethodDesc must be method definition private Dictionary? _compilerGeneratedTypeToUserCodeMethod; private Dictionary? _generatedTypeToTypeArgumentInfo; private Dictionary? _compilerGeneratedMethodToUserCodeMethod; diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMethodBodyScanner.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMethodBodyScanner.cs index a3466002e092b..394bfe273b38d 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMethodBodyScanner.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMethodBodyScanner.cs @@ -60,7 +60,7 @@ public static bool RequiresReflectionMethodBodyScannerForAccess(FlowAnnotations fieldDefinition.DoesFieldRequire(DiagnosticUtilities.RequiresDynamicCodeAttribute, out _); } - static void CheckAndReportRequires(in DiagnosticContext diagnosticContext, TypeSystemEntity calledMember, string requiresAttributeName) + internal static void CheckAndReportRequires(in DiagnosticContext diagnosticContext, TypeSystemEntity calledMember, string requiresAttributeName) { if (!calledMember.DoesMemberRequire(requiresAttributeName, out var requiresAttribute)) return; @@ -161,7 +161,8 @@ ValueWithDynamicallyAccessedMembers GetMethodParameterValue(MethodDesc method, i private void HandleStoreValueWithDynamicallyAccessedMembers(MethodIL methodBody, int offset, ValueWithDynamicallyAccessedMembers targetValue, MultiValue sourceValue, Origin memberWithRequirements) { - if (targetValue.DynamicallyAccessedMemberTypes != 0) + // We must record all field accesses since we need to check RUC/RDC/RAF attributes on them regardless of annotations + if (targetValue.DynamicallyAccessedMemberTypes != 0 || targetValue is FieldValue) { _origin = _origin.WithInstructionOffset(methodBody, offset); HandleAssignmentPattern(_origin, sourceValue, targetValue, memberWithRequirements); @@ -170,9 +171,6 @@ private void HandleStoreValueWithDynamicallyAccessedMembers(MethodIL methodBody, protected override void HandleStoreField(MethodIL methodBody, int offset, FieldValue field, MultiValue valueToStore) => HandleStoreValueWithDynamicallyAccessedMembers(methodBody, offset, field, valueToStore, new FieldOrigin(field.Field)); - // TODO: The previous HandleStoreField also did this: - // CheckAndReportRequires(field.Field, new MessageOrigin(methodBody.OwningMethod), RequiresUnreferencedCodeAttribute); - // CheckAndReportRequires(field.Field, new MessageOrigin(methodBody.OwningMethod), RequiresDynamicCodeAttribute); protected override void HandleStoreParameter(MethodIL methodBody, int offset, MethodParameterValue parameter, MultiValue valueToStore) => HandleStoreValueWithDynamicallyAccessedMembers(methodBody, offset, parameter, valueToStore, parameter.ParameterOrigin); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisAssignmentPattern.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisAssignmentPattern.cs index c61f9305f0c53..714b5380fb88e 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisAssignmentPattern.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisAssignmentPattern.cs @@ -40,6 +40,16 @@ public void MarkAndProduceDiagnostics(ReflectionMarker reflectionMarker, Logger { foreach (var targetValue in Target) { + if (targetValue is FieldValue fieldValue) + { + // Once this is removed, please also cleanup ReflectionMethodBodyScanner.HandleStoreValueWithDynamicallyAccessedMembers + // which has to special case FieldValue right now, should not be needed after removal of this + ReflectionMethodBodyScanner.CheckAndReportRequires(diagnosticContext, fieldValue.Field, DiagnosticUtilities.RequiresUnreferencedCodeAttribute); + ReflectionMethodBodyScanner.CheckAndReportRequires(diagnosticContext, fieldValue.Field, DiagnosticUtilities.RequiresDynamicCodeAttribute); + // ?? Should this be enabled (was not so far) + //ReflectionMethodBodyScanner.CheckAndReportRequires(diagnosticContext, fieldValue.Field, DiagnosticUtilities.RequiresAssemblyFilesAttribute); + } + if (targetValue is not ValueWithDynamicallyAccessedMembers targetWithDynamicallyAccessedMembers) throw new NotImplementedException(); From 10bbc6d894a562b8cfed79fdd4335870f882052b Mon Sep 17 00:00:00 2001 From: vitek-karas <10670590+vitek-karas@users.noreply.github.com> Date: Mon, 4 Jul 2022 13:13:12 -0700 Subject: [PATCH 14/18] PR Feedback - just minor tweaks --- .../Compiler/Dataflow/AttributeDataFlow.cs | 19 ++-- .../Dataflow/CompilerGeneratedNames.cs | 2 + .../Compiler/Dataflow/DiagnosticContext.cs | 1 - .../Compiler/Dataflow/FlowAnnotations.cs | 1 - .../ILCompiler.Compiler/Compiler/Logger.cs | 7 +- .../ReferenceSource/CompilerGeneratedState.cs | 90 ------------------- .../tools/aot/ILLink.Shared/DiagnosticId.cs | 25 ++---- 7 files changed, 17 insertions(+), 128 deletions(-) delete mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logging/ReferenceSource/CompilerGeneratedState.cs diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/AttributeDataFlow.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/AttributeDataFlow.cs index 658bc535c7880..57ec8ad24b082 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/AttributeDataFlow.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/AttributeDataFlow.cs @@ -107,20 +107,13 @@ public void ProcessAttributeDataflow(FieldDesc field, object? value, ref Depende } MultiValue GetValueForCustomAttributeArgument(object? argument) - { - if (argument is TypeDesc td) - { - return new SystemTypeValue(td); - } - - if (argument is string str) + => argument switch { - return new KnownStringValue(str); - } - - // We shouldn't have gotten a None annotation from flow annotations since only string/Type can have annotations - throw new InvalidOperationException(); - } + TypeDesc td => new SystemTypeValue(td), + string str => new KnownStringValue(str), + // We shouldn't have gotten a None annotation from flow annotations since only string/Type can have annotations + _ => throw new InvalidOperationException() + }; void RequireDynamicallyAccessedMembers( in DiagnosticContext diagnosticContext, diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedNames.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedNames.cs index eeeaedd122b05..c41d5fa11d7a3 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedNames.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedNames.cs @@ -27,6 +27,8 @@ internal static bool IsStateMachineType(string typeName) if (!IsGeneratedMemberName(typeName)) return false; + // State machines are generated into types with names like d__0 + // Or if its nested in a local function the name will look like <g__Local>d and so on int i = typeName.LastIndexOf('>'); if (i == -1) return false; diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/DiagnosticContext.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/DiagnosticContext.cs index 622c6356b2732..85dbe6df08497 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/DiagnosticContext.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/DiagnosticContext.cs @@ -3,7 +3,6 @@ using ILCompiler; using ILCompiler.Logging; -using Internal.Metadata.NativeFormat; #nullable enable diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/FlowAnnotations.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/FlowAnnotations.cs index d3c0c51fa7fa8..8965f9f694737 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/FlowAnnotations.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/FlowAnnotations.cs @@ -924,7 +924,6 @@ internal partial MethodParameterValue GetMethodParameterValue(MethodProxy method internal partial MethodParameterValue GetMethodParameterValue(MethodProxy method, int parameterIndex) => GetMethodParameterValue(method, parameterIndex, GetParameterAnnotation(method.Method, parameterIndex + (method.IsStatic() ? 0 : 1))); - // Linker-specific dataflow value creation. Eventually more of these should be shared. internal SingleValue GetFieldValue(FieldDesc field) => field.Name switch { diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logger.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logger.cs index 784f1920feac3..acd726e314329 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logger.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logger.cs @@ -3,8 +3,9 @@ using System; using System.Collections.Generic; -using System.Reflection.Metadata; +using System.Diagnostics; using System.IO; +using System.Reflection.Metadata; using Internal.TypeSystem; using Internal.TypeSystem.Ecma; @@ -15,10 +16,6 @@ using ILSequencePoint = Internal.IL.ILSequencePoint; using MethodIL = Internal.IL.MethodIL; -using System.Diagnostics.Metrics; -using System.Diagnostics; -using System.Reflection; -using Internal.IL; namespace ILCompiler { diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logging/ReferenceSource/CompilerGeneratedState.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logging/ReferenceSource/CompilerGeneratedState.cs deleted file mode 100644 index 3c22da1f5478b..0000000000000 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logging/ReferenceSource/CompilerGeneratedState.cs +++ /dev/null @@ -1,90 +0,0 @@ -// 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 ILLink.Shared; -using Mono.Cecil; - -namespace Mono.Linker -{ - // Currently this is implemented using heuristics - public class CompilerGeneratedState - { - readonly LinkContext _context; - readonly Dictionary _compilerGeneratedTypeToUserCodeMethod; - readonly HashSet _typesWithPopulatedCache; - - public CompilerGeneratedState (LinkContext context) - { - _context = context; - _compilerGeneratedTypeToUserCodeMethod = new Dictionary (); - _typesWithPopulatedCache = new HashSet (); - } - - static bool HasRoslynCompilerGeneratedName (TypeDefinition type) => - type.Name.Contains ('<') || (type.DeclaringType != null && HasRoslynCompilerGeneratedName (type.DeclaringType)); - - void PopulateCacheForType (TypeDefinition type) - { - // Avoid repeat scans of the same type - if (!_typesWithPopulatedCache.Add (type)) - return; - - foreach (MethodDefinition method in type.Methods) { - if (!method.HasCustomAttributes) - continue; - - foreach (var attribute in method.CustomAttributes) { - if (attribute.AttributeType.Namespace != "System.Runtime.CompilerServices") - continue; - - switch (attribute.AttributeType.Name) { - case "AsyncIteratorStateMachineAttribute": - case "AsyncStateMachineAttribute": - case "IteratorStateMachineAttribute": - TypeDefinition? stateMachineType = GetFirstConstructorArgumentAsType (attribute); - if (stateMachineType != null) { - if (!_compilerGeneratedTypeToUserCodeMethod.TryAdd (stateMachineType, method)) { - var alreadyAssociatedMethod = _compilerGeneratedTypeToUserCodeMethod[stateMachineType]; - _context.LogWarning (new MessageOrigin (method), DiagnosticId.MethodsAreAssociatedWithStateMachine, method.GetDisplayName (), alreadyAssociatedMethod.GetDisplayName (), stateMachineType.GetDisplayName ()); - } - } - - break; - } - } - } - } - - static TypeDefinition? GetFirstConstructorArgumentAsType (CustomAttribute attribute) - { - if (!attribute.HasConstructorArguments) - return null; - - return attribute.ConstructorArguments[0].Value as TypeDefinition; - } - - public MethodDefinition? GetUserDefinedMethodForCompilerGeneratedMember (IMemberDefinition sourceMember) - { - if (sourceMember == null) - return null; - - TypeDefinition compilerGeneratedType = (sourceMember as TypeDefinition) ?? sourceMember.DeclaringType; - if (_compilerGeneratedTypeToUserCodeMethod.TryGetValue (compilerGeneratedType, out MethodDefinition? userDefinedMethod)) - return userDefinedMethod; - - // Only handle async or iterator state machine - // So go to the declaring type and check if it's compiler generated (as a perf optimization) - if (!HasRoslynCompilerGeneratedName (compilerGeneratedType) || compilerGeneratedType.DeclaringType == null) - return null; - - // Now go to its declaring type and search all methods to find the one which points to the type as its - // state machine implementation. - PopulateCacheForType (compilerGeneratedType.DeclaringType); - if (_compilerGeneratedTypeToUserCodeMethod.TryGetValue (compilerGeneratedType, out userDefinedMethod)) - return userDefinedMethod; - - return null; - } - } -} diff --git a/src/coreclr/tools/aot/ILLink.Shared/DiagnosticId.cs b/src/coreclr/tools/aot/ILLink.Shared/DiagnosticId.cs index e90d73d01c613..8534c4960d81e 100644 --- a/src/coreclr/tools/aot/ILLink.Shared/DiagnosticId.cs +++ b/src/coreclr/tools/aot/ILLink.Shared/DiagnosticId.cs @@ -226,23 +226,12 @@ public static string GetDiagnosticSubcategory (this DiagnosticId diagnosticId) = _ => MessageSubCategory.None, }; - public static string GetDiagnosticCategory (this DiagnosticId diagnosticId) - { - switch ((int) diagnosticId) { - case > 2000 and < 3000: - return DiagnosticCategory.Trimming; - - case >= 3000 and < 3050: - return DiagnosticCategory.SingleFile; - - case >= 3050 and <= 6000: - return DiagnosticCategory.AOT; - - default: - break; - } - - throw new ArgumentException ($"The provided diagnostic id '{diagnosticId}' does not fall into the range of supported warning codes 2001 to 6000 (inclusive)."); - } + public static string GetDiagnosticCategory (this DiagnosticId diagnosticId) => + (int) diagnosticId switch { + > 2000 and < 3000 => DiagnosticCategory.Trimming, + >= 3000 and < 3050 => DiagnosticCategory.SingleFile, + >= 3050 and <= 6000 => DiagnosticCategory.AOT, + _ => throw new ArgumentException ($"The provided diagnostic id '{diagnosticId}' does not fall into the range of supported warning codes 2001 to 6000 (inclusive).") + }; } } From 53800b7af8be348b767e1ea32b6dfa5761f72eae Mon Sep 17 00:00:00 2001 From: vitek-karas <10670590+vitek-karas@users.noreply.github.com> Date: Wed, 6 Jul 2022 12:29:24 -0700 Subject: [PATCH 15/18] Port almost latest linker changes This was needed to fix failures in libraries build with AOT. The underlying problem is not fully fixed: https://github.com/dotnet/linker/issues/2874 But with these changes it doesn't crash the compiler, it just leads to sometimes imprecise warnings. --- eng/Versions.props | 1 + .../Dataflow/CompilerGeneratedState.cs | 8 + .../Compiler/Dataflow/FlowAnnotations.cs | 2 +- .../Compiler/Dataflow/HoistedLocalKey.cs | 32 ++ .../Compiler/Dataflow/InterproceduralState.cs | 159 +++++++ .../Compiler/Dataflow/MethodBodyScanner.cs | 182 ++++---- .../CompilerGeneratedCallGraph.cs | 63 +++ .../ReferenceSource/CompilerGeneratedNames.cs | 72 +++ .../ReferenceSource/CompilerGeneratedState.cs | 425 ++++++++++++++++++ ...DynamicallyAccessedMembersTypeHierarchy.cs | 2 +- .../ReferenceSource/FlowAnnotations.cs | 2 +- .../ReferenceSource/HoistedLocalKey.cs | 30 ++ .../ReferenceSource/InterproceduralState.cs | 92 ++++ .../ReferenceSource/MethodBodyScanner.cs | 129 +++--- .../Dataflow/ReferenceSource/MethodProxy.cs | 2 +- .../ReflectionMethodBodyScanner.cs | 13 +- .../TrimAnalysisAssignmentPattern.cs | 12 + .../TrimAnalysisMethodCallPattern.cs | 21 +- .../TrimAnalysisPatternStore.cs | 20 +- .../Dataflow/ReferenceSource/ValueNode.cs | 9 +- .../Dataflow/ReflectionMethodBodyScanner.cs | 8 +- .../Dataflow/TrimAnalysisAssignmentPattern.cs | 14 +- .../Dataflow/TrimAnalysisMethodCallPattern.cs | 24 + .../Dataflow/TrimAnalysisPatternStore.cs | 22 +- .../Compiler/Dataflow/ValueNode.cs | 10 +- .../DataflowAnalyzedMethodNode.cs | 2 + .../Compiler/UsageBasedMetadataManager.cs | 50 ++- .../ILCompiler.Compiler.csproj | 7 + .../DataFlow/DefaultValueDictionary.cs | 25 ++ .../tools/aot/ILLink.Shared/DiagnosticId.cs | 1 + .../aot/ILLink.Shared/ILLink.Shared.projitems | 2 +- .../aot/ILLink.Shared/SharedStrings.resx | 10 +- .../ILLink.Shared/TrimAnalysis/IntrinsicId.cs | 1 + .../TypeSystemProxy/WellKnownType.cs | 4 +- 34 files changed, 1235 insertions(+), 221 deletions(-) create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/HoistedLocalKey.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/InterproceduralState.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/CompilerGeneratedCallGraph.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/CompilerGeneratedNames.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/CompilerGeneratedState.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/HoistedLocalKey.cs create mode 100644 src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/InterproceduralState.cs diff --git a/eng/Versions.props b/eng/Versions.props index b1d2cf6d02147..60029a5544b3c 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -50,6 +50,7 @@ TODO: Remove pinned version once arcade supplies a compiler that enables the repo to compile. --> 4.4.0-1.22315.13 + 0.1.0 2.0.0-preview.4.22252.4 diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedState.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedState.cs index e41e95c620996..32bdcbe66b4bd 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedState.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedState.cs @@ -450,6 +450,14 @@ static IEnumerable GetCompilerGeneratedNestedTypes(TypeDesc type) } } + public static bool IsHoistedLocal(FieldDesc field) + { + // Treat all fields on compiler-generated types as hoisted locals. + // This avoids depending on the name mangling scheme for hoisted locals. + var declaringTypeName = field.OwningType.Name; + return CompilerGeneratedNames.IsLambdaDisplayClass(declaringTypeName) || CompilerGeneratedNames.IsStateMachineType(declaringTypeName); + } + // "Nested function" refers to lambdas and local functions. public static bool IsNestedFunctionOrStateMachineMember(TypeSystemEntity member) { diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/FlowAnnotations.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/FlowAnnotations.cs index 8965f9f694737..82922c9795008 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/FlowAnnotations.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/FlowAnnotations.cs @@ -26,7 +26,7 @@ namespace ILLink.Shared.TrimAnalysis /// /// Caches dataflow annotations for type members. /// - public partial class FlowAnnotations + sealed public partial class FlowAnnotations { private readonly TypeAnnotationsHashtable _hashtable; private readonly Logger _logger; diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/HoistedLocalKey.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/HoistedLocalKey.cs new file mode 100644 index 0000000000000..11be949bed019 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/HoistedLocalKey.cs @@ -0,0 +1,32 @@ +// 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; +using Internal.TypeSystem; + +#nullable enable + +namespace ILCompiler.Dataflow +{ + // This represents a field which has been generated by the compiler as the + // storage location for a hoisted local (a local variable which is lifted to a + // field on a state machine type, or to a field on a closure accessed by lambdas + // or local functions). + public readonly struct HoistedLocalKey : IEquatable + { + readonly FieldDesc Field; + + public HoistedLocalKey(FieldDesc field) + { + Debug.Assert(CompilerGeneratedState.IsHoistedLocal(field)); + Field = field; + } + + public bool Equals(HoistedLocalKey other) => Field.Equals(other.Field); + + public override bool Equals(object? obj) => obj is HoistedLocalKey other && Equals(other); + + public override int GetHashCode() => Field.GetHashCode(); + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/InterproceduralState.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/InterproceduralState.cs new file mode 100644 index 0000000000000..304fa353d69e8 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/InterproceduralState.cs @@ -0,0 +1,159 @@ +// 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 System.Diagnostics; +using System.Diagnostics.CodeAnalysis; + +using ILLink.Shared.DataFlow; +using Internal.IL; +using Internal.TypeSystem; + +using HoistedLocalState = ILLink.Shared.DataFlow.DefaultValueDictionary< + ILCompiler.Dataflow.HoistedLocalKey, + ILLink.Shared.DataFlow.ValueSet>; +using MultiValue = ILLink.Shared.DataFlow.ValueSet; + +#nullable enable + +namespace ILCompiler.Dataflow +{ + // Wrapper that implements IEquatable for MethodBody. + readonly record struct MethodBodyValue(MethodIL MethodBody) : IEquatable + { + bool IEquatable.Equals(ILCompiler.Dataflow.MethodBodyValue other) + => other.MethodBody.OwningMethod == MethodBody.OwningMethod; + + public override int GetHashCode() => MethodBody.OwningMethod.GetHashCode(); + } + + // Tracks the set of methods which get analyzer together during interprocedural analysis, + // and the possible states of hoisted locals in state machine methods and lambdas/local functions. + struct InterproceduralState : IEquatable + { + readonly ILProvider _ilProvider; + public ValueSet MethodBodies; + public HoistedLocalState HoistedLocals; + readonly InterproceduralStateLattice lattice; + + public InterproceduralState(ILProvider ilProvider, ValueSet methodBodies, HoistedLocalState hoistedLocals, InterproceduralStateLattice lattice) + => (_ilProvider, MethodBodies, HoistedLocals, this.lattice) = (ilProvider, methodBodies, hoistedLocals, lattice); + + public bool Equals(InterproceduralState other) + => MethodBodies.Equals(other.MethodBodies) && HoistedLocals.Equals(other.HoistedLocals); + + public InterproceduralState Clone() + => new(_ilProvider, MethodBodies.Clone(), HoistedLocals.Clone(), lattice); + + public void TrackMethod(MethodDesc method) + { + if (!TryGetMethodBody(method, out MethodIL? methodBody)) + return; + + TrackMethod(methodBody); + } + + public void TrackMethod(MethodIL methodBody) + { + Debug.Assert(methodBody.GetMethodILDefinition() == methodBody); + methodBody = GetInstantiatedMethodIL(methodBody); + + // Work around the fact that ValueSet is readonly + var methodsList = new List(MethodBodies); + methodsList.Add(new MethodBodyValue(methodBody)); + + // For state machine methods, also scan the state machine members. + // Simplification: assume that all generated methods of the state machine type are + // reached at the point where the state machine method is reached. + if (CompilerGeneratedState.TryGetStateMachineType(methodBody.OwningMethod, out MetadataType? stateMachineType)) + { + foreach (var stateMachineMethod in stateMachineType.GetMethods()) + { + Debug.Assert(!CompilerGeneratedNames.IsLambdaOrLocalFunction(stateMachineMethod.Name)); + if (TryGetMethodBody(stateMachineMethod, out MethodIL? stateMachineMethodBody)) + { + stateMachineMethodBody = GetInstantiatedMethodIL(stateMachineMethodBody); + methodsList.Add(new MethodBodyValue(stateMachineMethodBody)); + } + } + } + + MethodBodies = new ValueSet(methodsList); + + static MethodIL GetInstantiatedMethodIL(MethodIL methodIL) + { + if (methodIL.OwningMethod.HasInstantiation || methodIL.OwningMethod.OwningType.HasInstantiation) + { + // We instantiate the body over the generic parameters. + // + // This will transform references like "call Foo.Method(!0 arg)" into + // "call Foo.Method(T arg)". We do this to avoid getting confused about what + // context the generic variables refer to - in the above example, we would see + // two !0's - one refers to the generic parameter of the type that owns the method with + // the call, but the other one (in the signature of "Method") actually refers to + // the generic parameter of Foo. + // + // If we don't do this translation, retrieving the signature of the called method + // would attempt to do bogus substitutions. + // + // By doing the following transformation, we ensure we don't see the generic variables + // that need to be bound to the context of the currently analyzed method. + methodIL = new InstantiatedMethodIL(methodIL.OwningMethod, methodIL); + } + + return methodIL; + } + } + + public void SetHoistedLocal(HoistedLocalKey key, MultiValue value) + { + // For hoisted locals, we track the entire set of assigned values seen + // in the closure of a method, so setting a hoisted local value meets + // it with any existing value. + HoistedLocals.Set(key, + lattice.HoistedLocalsLattice.ValueLattice.Meet( + HoistedLocals.Get(key), value)); + } + + public MultiValue GetHoistedLocal(HoistedLocalKey key) + => HoistedLocals.Get(key); + + bool TryGetMethodBody(MethodDesc method, [NotNullWhen(true)] out MethodIL? methodBody) + { + methodBody = null; + + if (method.IsPInvoke) + return false; + + MethodIL methodIL = _ilProvider.GetMethodIL(method); + if (methodIL == null) + return false; + + methodBody = methodIL; + return true; + } + } + + struct InterproceduralStateLattice : ILattice + { + private readonly ILProvider _ilProvider; + public readonly ValueSetLattice MethodBodyLattice; + public readonly DictionaryLattice> HoistedLocalsLattice; + + public InterproceduralStateLattice( + ILProvider ilProvider, + ValueSetLattice methodBodyLattice, + DictionaryLattice> hoistedLocalsLattice) + => (_ilProvider, MethodBodyLattice, HoistedLocalsLattice) = (ilProvider, methodBodyLattice, hoistedLocalsLattice); + + public InterproceduralState Top => new InterproceduralState(_ilProvider, MethodBodyLattice.Top, HoistedLocalsLattice.Top, this); + + public InterproceduralState Meet(InterproceduralState left, InterproceduralState right) + => new( + _ilProvider, + MethodBodyLattice.Meet(left.MethodBodies, right.MethodBodies), + HoistedLocalsLattice.Meet(left.HoistedLocals, right.HoistedLocals), + this); + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/MethodBodyScanner.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/MethodBodyScanner.cs index abb8e8055cda9..4fde62216e253 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/MethodBodyScanner.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/MethodBodyScanner.cs @@ -4,13 +4,16 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.Linq; +using System.Reflection.Metadata; + using ILCompiler.Logging; + using ILLink.Shared; using ILLink.Shared.DataFlow; using ILLink.Shared.TrimAnalysis; using ILLink.Shared.TypeSystemProxy; + using Internal.IL; using Internal.TypeSystem; @@ -45,8 +48,8 @@ public StackSlot(MultiValue value) abstract partial class MethodBodyScanner { + protected readonly InterproceduralStateLattice InterproceduralStateLattice; protected static ValueSetLattice MultiValueLattice => default; - protected static ValueSetLattice MethodLattice => default; protected readonly FlowAnnotations _annotations; @@ -55,6 +58,7 @@ abstract partial class MethodBodyScanner protected MethodBodyScanner(FlowAnnotations annotations) { _annotations = annotations; + InterproceduralStateLattice = new InterproceduralStateLattice(annotations.ILProvider, default, default); } protected virtual void WarnAboutInvalidILInMethod(MethodIL method, int ilOffset) @@ -287,116 +291,63 @@ private static void StoreMethodLocalValue( // Scans the method as well as any nested functions (local functions or lambdas) and state machines // reachable from it. - public virtual void InterproceduralScan(MethodIL methodBody) + public virtual void InterproceduralScan(MethodIL startingMethodBody) { - Debug.Assert(methodBody.OwningMethod.IsTypicalMethodDefinition); + MethodDesc startingMethod = startingMethodBody.OwningMethod; + Debug.Assert(startingMethod.IsTypicalMethodDefinition); - var methodsInGroup = new ValueSet(methodBody.OwningMethod); + // We should never have created a DataFlowAnalyzedMethodNode for compiler generated methods + // since their data flow analysis is handled as part of their parent method analysis. + Debug.Assert(!CompilerGeneratedState.IsNestedFunctionOrStateMachineMember(startingMethod)); - // Optimization to prevent multiple scans of a method. - // Eventually we will need to allow re-scanning in some cases, for example - // when we discover new inputs to a method. But we aren't doing dataflow across - // lambdas and local functions yet, so no need for now. - HashSet scannedMethods = new HashSet(); + // Note that the default value of a hoisted local will be MultiValueLattice.Top, not UnknownValue.Instance. + // This ensures that there are no warnings for the "unassigned state" of a parameter. + // Definite assignment should ensure that there is no way for this to be an analysis hole. + var interproceduralState = InterproceduralStateLattice.Top; - while (true) - { - if (!TryGetNextMethodToScan(out MethodIL? methodToScan)) - break; + var oldInterproceduralState = interproceduralState.Clone(); + interproceduralState.TrackMethod(startingMethodBody); - scannedMethods.Add(methodToScan.OwningMethod); - Scan(methodToScan, ref methodsInGroup); + while (!interproceduralState.Equals(oldInterproceduralState)) + { + oldInterproceduralState = interproceduralState.Clone(); - // For state machine methods, also scan the state machine members. - // Simplification: assume that all generated methods of the state machine type are - // invoked at the point where the state machine method is called. - if (CompilerGeneratedState.TryGetStateMachineType(methodToScan.OwningMethod, out MetadataType? stateMachineType)) - { - foreach (var method in stateMachineType.GetMethods()) - { - Debug.Assert(!CompilerGeneratedNames.IsLambdaOrLocalFunction(method.Name)); - if (TryGetMethodBody(method, out var stateMachineBody)) - Scan(stateMachineBody, ref methodsInGroup); - } - } + // Flow state through all methods encountered so far, as long as there + // are changes discovered in the hoisted local state on entry to any method. + foreach (var methodBodyValue in oldInterproceduralState.MethodBodies) + Scan(methodBodyValue.MethodBody, ref interproceduralState); } #if DEBUG // Validate that the compiler-generated callees tracked by the compiler-generated state // are the same set of methods that we discovered and scanned above. - if (_annotations.CompilerGeneratedState.TryGetCompilerGeneratedCalleesForUserMethod(methodBody.OwningMethod, out List? compilerGeneratedCallees)) + if (_annotations.CompilerGeneratedState.TryGetCompilerGeneratedCalleesForUserMethod(startingMethod, out List? compilerGeneratedCallees)) { - var calleeMethods = compilerGeneratedCallees.OfType(); - Debug.Assert(methodsInGroup.Count() == 1 + calleeMethods.Count()); - foreach (var method in calleeMethods) - Debug.Assert(methodsInGroup.Contains(method)); + var calleeMethods = compilerGeneratedCallees.OfType(); + // https://github.com/dotnet/linker/issues/2845 + // Disabled asserts due to a bug + // Debug.Assert (interproceduralState.Count == 1 + calleeMethods.Count ()); + // foreach (var method in calleeMethods) + // Debug.Assert (interproceduralState.Any (kvp => kvp.Key.Method == method)); } else { - Debug.Assert(methodsInGroup.Count() == 1); + Debug.Assert(interproceduralState.MethodBodies.Count() == 1); } #endif - - bool TryGetNextMethodToScan([NotNullWhen(true)] out MethodIL? method) - { - foreach (var candidate in methodsInGroup) - { - var candidateMethod = candidate.Method; - if (!scannedMethods.Contains(candidateMethod)) - { - if (TryGetMethodBody(candidateMethod, out method)) - return true; - } - } - method = null; - return false; - } - - bool TryGetMethodBody(MethodDesc method, [NotNullWhen(true)] out MethodIL? methodBody) - { - MethodIL methodIL = _annotations.ILProvider.GetMethodIL(method); - if (methodIL == null) - { - methodBody = null; - return false; - } - - Debug.Assert(methodIL.GetMethodILDefinition() == methodIL); - if (methodIL.OwningMethod.HasInstantiation || methodIL.OwningMethod.OwningType.HasInstantiation) - { - // We instantiate the body over the generic parameters. - // - // This will transform references like "call Foo.Method(!0 arg)" into - // "call Foo.Method(T arg)". We do this to avoid getting confused about what - // context the generic variables refer to - in the above example, we would see - // two !0's - one refers to the generic parameter of the type that owns the method with - // the call, but the other one (in the signature of "Method") actually refers to - // the generic parameter of Foo. - // - // If we don't do this translation, retrieving the signature of the called method - // would attempt to do bogus substitutions. - // - // By doing the following transformation, we ensure we don't see the generic variables - // that need to be bound to the context of the currently analyzed method. - methodIL = new InstantiatedMethodIL(methodIL.OwningMethod, methodIL); - } - - methodBody = methodIL; - return true; - } } - void TrackNestedFunctionReference(MethodDesc method, ref ValueSet methodsInGroup) + void TrackNestedFunctionReference(MethodDesc referencedMethod, ref InterproceduralState interproceduralState) { - MethodDesc methodDefinition = method.GetTypicalMethodDefinition(); + MethodDesc method = referencedMethod.GetTypicalMethodDefinition(); - if (!CompilerGeneratedNames.IsLambdaOrLocalFunction(methodDefinition.Name)) + if (!CompilerGeneratedNames.IsLambdaOrLocalFunction(method.Name)) return; - methodsInGroup = MethodLattice.Meet(methodsInGroup, new(methodDefinition)); + interproceduralState.TrackMethod(method); } - protected virtual void Scan(MethodIL methodBody, ref ValueSet methodsInGroup) + protected virtual void Scan(MethodIL methodBody, ref InterproceduralState interproceduralState) { MethodDesc thisMethod = methodBody.OwningMethod; @@ -533,7 +484,7 @@ protected virtual void Scan(MethodIL methodBody, ref ValueSet metho { if (methodBody.GetObject(reader.ReadILToken()) is MethodDesc methodOperand) { - TrackNestedFunctionReference(methodOperand, ref methodsInGroup); + TrackNestedFunctionReference(methodOperand, ref interproceduralState); } PushUnknown(currentStack); @@ -661,7 +612,7 @@ protected virtual void Scan(MethodIL methodBody, ref ValueSet metho case ILOpcode.ldsfld: case ILOpcode.ldflda: case ILOpcode.ldsflda: - ScanLdfld(methodBody, offset, opcode, (FieldDesc)methodBody.GetObject(reader.ReadILToken()), currentStack); + ScanLdfld(methodBody, offset, opcode, (FieldDesc)methodBody.GetObject(reader.ReadILToken()), currentStack, ref interproceduralState); break; case ILOpcode.newarr: @@ -710,7 +661,7 @@ protected virtual void Scan(MethodIL methodBody, ref ValueSet metho case ILOpcode.stfld: case ILOpcode.stsfld: - ScanStfld(methodBody, offset, opcode, (FieldDesc)methodBody.GetObject(reader.ReadILToken()), currentStack); + ScanStfld(methodBody, offset, opcode, (FieldDesc)methodBody.GetObject(reader.ReadILToken()), currentStack, ref interproceduralState); break; case ILOpcode.cpobj: @@ -799,8 +750,8 @@ protected virtual void Scan(MethodIL methodBody, ref ValueSet metho case ILOpcode.newobj: { MethodDesc methodOperand = (MethodDesc)methodBody.GetObject(reader.ReadILToken()); - TrackNestedFunctionReference(methodOperand, ref methodsInGroup); - HandleCall(methodBody, opcode, offset, methodOperand, currentStack, locals, curBasicBlock); + TrackNestedFunctionReference(methodOperand, ref interproceduralState); + HandleCall(methodBody, opcode, offset, methodOperand, currentStack, locals, ref interproceduralState, curBasicBlock); } break; @@ -840,7 +791,7 @@ protected virtual void Scan(MethodIL methodBody, ref ValueSet metho StackSlot retValue = PopUnknown(currentStack, 1, methodBody, offset); // If the return value is a reference, treat it as the value itself for now // We can handle ref return values better later - ReturnValue = MultiValueLattice.Meet(ReturnValue, DereferenceValue(retValue.Value, locals)); + ReturnValue = MultiValueLattice.Meet(ReturnValue, DereferenceValue(retValue.Value, locals, ref interproceduralState)); } ClearStack(ref currentStack); break; @@ -1113,19 +1064,28 @@ private void ScanLdfld( int offset, ILOpcode opcode, FieldDesc field, - Stack currentStack - ) + Stack currentStack, + ref InterproceduralState interproceduralState) { if (opcode == ILOpcode.ldfld || opcode == ILOpcode.ldflda) PopUnknown(currentStack, 1, methodBody, offset); bool isByRef = opcode == ILOpcode.ldflda || opcode == ILOpcode.ldsflda; - MultiValue newValue = isByRef ? - new FieldReferenceValue(field) - : GetFieldValue(field); - StackSlot slot = new(newValue); - currentStack.Push(slot); + MultiValue value; + if (isByRef) + { + value = new FieldReferenceValue(field); + } + else if (CompilerGeneratedState.IsHoistedLocal(field)) + { + value = interproceduralState.GetHoistedLocal(new HoistedLocalKey(field)); + } + else + { + value = GetFieldValue(field); + } + currentStack.Push(new StackSlot(value)); } protected virtual void HandleStoreField(MethodIL method, int offset, FieldValue field, MultiValue valueToStore) @@ -1149,12 +1109,19 @@ private void ScanStfld( int offset, ILOpcode opcode, FieldDesc field, - Stack currentStack) + Stack currentStack, + ref InterproceduralState interproceduralState) { StackSlot valueToStoreSlot = PopUnknown(currentStack, 1, methodBody, offset); if (opcode == ILOpcode.stfld) PopUnknown(currentStack, 1, methodBody, offset); + if (CompilerGeneratedState.IsHoistedLocal(field)) + { + interproceduralState.SetHoistedLocal(new HoistedLocalKey(field), valueToStoreSlot.Value); + return; + } + foreach (var value in GetFieldValue(field)) { // GetFieldValue may return different node types, in which case they can't be stored to. @@ -1196,7 +1163,7 @@ private ValueNodeList PopCallArguments( return methodParams; } - internal MultiValue DereferenceValue(MultiValue maybeReferenceValue, ValueBasicBlockPair?[] locals) + internal MultiValue DereferenceValue(MultiValue maybeReferenceValue, ValueBasicBlockPair?[] locals, ref InterproceduralState interproceduralState) { MultiValue dereferencedValue = MultiValueLattice.Top; foreach (var value in maybeReferenceValue) @@ -1206,7 +1173,9 @@ internal MultiValue DereferenceValue(MultiValue maybeReferenceValue, ValueBasicB case FieldReferenceValue fieldReferenceValue: dereferencedValue = MultiValue.Meet( dereferencedValue, - GetFieldValue(fieldReferenceValue.FieldDefinition)); + CompilerGeneratedState.IsHoistedLocal(fieldReferenceValue.FieldDefinition) + ? interproceduralState.GetHoistedLocal(new HoistedLocalKey(fieldReferenceValue.FieldDefinition)) + : GetFieldValue(fieldReferenceValue.FieldDefinition)); break; case ParameterReferenceValue parameterReferenceValue: dereferencedValue = MultiValue.Meet( @@ -1259,6 +1228,7 @@ private void HandleCall( MethodDesc calledMethod, Stack currentStack, ValueBasicBlockPair?[] locals, + ref InterproceduralState interproceduralState, int curBasicBlock) { bool isNewObj = opcode == ILOpcode.newobj; @@ -1266,15 +1236,17 @@ private void HandleCall( SingleValue? newObjValue; ValueNodeList methodArguments = PopCallArguments(currentStack, calledMethod, callingMethodBody, isNewObj, offset, out newObjValue); - ValueNodeList dereferencedMethodArguments = new(methodArguments.Select(argument => DereferenceValue(argument, locals)).ToList()); + var dereferencedMethodParams = new List(); + foreach (var argument in methodArguments) + dereferencedMethodParams.Add(DereferenceValue(argument, locals, ref interproceduralState)); MultiValue methodReturnValue; bool handledFunction = HandleCall( callingMethodBody, calledMethod, opcode, offset, - dereferencedMethodArguments, + new ValueNodeList(dereferencedMethodParams), out methodReturnValue); // Handle the return value or newobj result diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/CompilerGeneratedCallGraph.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/CompilerGeneratedCallGraph.cs new file mode 100644 index 0000000000000..551ff51e5c434 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/CompilerGeneratedCallGraph.cs @@ -0,0 +1,63 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.Diagnostics; +using Mono.Cecil; + +namespace Mono.Linker.Dataflow +{ + sealed class CompilerGeneratedCallGraph + { + readonly Dictionary> callGraph; + + public CompilerGeneratedCallGraph () => callGraph = new Dictionary> (); + + void TrackCallInternal (IMemberDefinition fromMember, IMemberDefinition toMember) + { + if (!callGraph.TryGetValue (fromMember, out HashSet? toMembers)) { + toMembers = new HashSet (); + callGraph.Add (fromMember, toMembers); + } + toMembers.Add (toMember); + } + + public void TrackCall (MethodDefinition fromMethod, MethodDefinition toMethod) + { + Debug.Assert (CompilerGeneratedNames.IsLambdaOrLocalFunction (toMethod.Name)); + TrackCallInternal (fromMethod, toMethod); + } + + public void TrackCall (MethodDefinition fromMethod, TypeDefinition toType) + { + Debug.Assert (CompilerGeneratedNames.IsStateMachineType (toType.Name)); + TrackCallInternal (fromMethod, toType); + } + + public void TrackCall (TypeDefinition fromType, MethodDefinition toMethod) + { + Debug.Assert (CompilerGeneratedNames.IsStateMachineType (fromType.Name)); + Debug.Assert (CompilerGeneratedNames.IsLambdaOrLocalFunction (toMethod.Name)); + TrackCallInternal (fromType, toMethod); + } + + public IEnumerable GetReachableMembers (MethodDefinition start) + { + Queue queue = new (); + HashSet visited = new (); + visited.Add (start); + queue.Enqueue (start); + while (queue.TryDequeue (out IMemberDefinition? method)) { + if (!callGraph.TryGetValue (method, out HashSet? callees)) + continue; + + foreach (var callee in callees) { + if (visited.Add (callee)) { + queue.Enqueue (callee); + yield return callee; + } + } + } + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/CompilerGeneratedNames.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/CompilerGeneratedNames.cs new file mode 100644 index 0000000000000..46e6c9764782a --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/CompilerGeneratedNames.cs @@ -0,0 +1,72 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Mono.Linker.Dataflow +{ + sealed class CompilerGeneratedNames + { + internal static bool IsGeneratedMemberName (string memberName) + { + return memberName.Length > 0 && memberName[0] == '<'; + } + + internal static bool IsLambdaDisplayClass (string className) + { + if (!IsGeneratedMemberName (className)) + return false; + + // This is true for static lambdas (which are emitted into a class like <>c) + // and for instance lambdas (which are emitted into a class like <>c__DisplayClass1_0) + return className.StartsWith ("<>c"); + } + + internal static bool IsStateMachineType (string typeName) + { + if (!IsGeneratedMemberName (typeName)) + return false; + + // State machines are generated into types with names like d__0 + // Or if its nested in a local function the name will look like <g__Local>d and so on + int i = typeName.LastIndexOf ('>'); + if (i == -1) + return false; + + return typeName.Length > i + 1 && typeName[i + 1] == 'd'; + } + + internal static bool IsGeneratedType (string name) => IsStateMachineType (name) || IsLambdaDisplayClass (name); + + internal static bool IsLambdaOrLocalFunction (string methodName) => IsLambdaMethod (methodName) || IsLocalFunction (methodName); + + // Lambda methods have generated names like "b__0_1" where "UserMethod" is the name + // of the original user code that contains the lambda method declaration. + internal static bool IsLambdaMethod (string methodName) + { + if (!IsGeneratedMemberName (methodName)) + return false; + + int i = methodName.IndexOf ('>', 1); + if (i == -1) + return false; + + // Ignore the method ordinal/generation and lambda ordinal/generation. + return methodName.Length > i + 1 && methodName[i + 1] == 'b'; + } + + // Local functions have generated names like "g__LocalFunction|0_1" where "UserMethod" is the name + // of the original user code that contains the lambda method declaration, and "LocalFunction" is the name of + // the local function. + internal static bool IsLocalFunction (string methodName) + { + if (!IsGeneratedMemberName (methodName)) + return false; + + int i = methodName.IndexOf ('>', 1); + if (i == -1) + return false; + + // Ignore the method ordinal/generation and local function ordinal/generation. + return methodName.Length > i + 1 && methodName[i + 1] == 'g'; + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/CompilerGeneratedState.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/CompilerGeneratedState.cs new file mode 100644 index 0000000000000..2802e6703df11 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/CompilerGeneratedState.cs @@ -0,0 +1,425 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using ILLink.Shared; +using Mono.Cecil; +using Mono.Cecil.Cil; + +namespace Mono.Linker.Dataflow +{ + // Currently this is implemented using heuristics + public class CompilerGeneratedState + { + readonly LinkContext _context; + readonly Dictionary _compilerGeneratedTypeToUserCodeMethod; + readonly Dictionary _generatedTypeToTypeArgumentInfo; + readonly record struct TypeArgumentInfo ( + /// The method which calls the ctor for the given type + MethodDefinition CreatingMethod, + /// Attributes for the type, pulled from the creators type arguments + IReadOnlyList? OriginalAttributes); + + readonly Dictionary _compilerGeneratedMethodToUserCodeMethod; + + // For each type that has had its cache populated, stores a map of methods which have corresponding + // compiler-generated members (either methods or state machine types) to those compiler-generated members, + // or null if the type has no methods with compiler-generated members. + readonly Dictionary>?> _cachedTypeToCompilerGeneratedMembers; + + public CompilerGeneratedState (LinkContext context) + { + _context = context; + _compilerGeneratedTypeToUserCodeMethod = new Dictionary (); + _generatedTypeToTypeArgumentInfo = new Dictionary (); + _compilerGeneratedMethodToUserCodeMethod = new Dictionary (); + _cachedTypeToCompilerGeneratedMembers = new Dictionary>?> (); + } + + static IEnumerable GetCompilerGeneratedNestedTypes (TypeDefinition type) + { + foreach (var nestedType in type.NestedTypes) { + if (!CompilerGeneratedNames.IsGeneratedMemberName (nestedType.Name)) + continue; + + yield return nestedType; + + foreach (var recursiveNestedType in GetCompilerGeneratedNestedTypes (nestedType)) + yield return recursiveNestedType; + } + } + + public static bool IsHoistedLocal (FieldDefinition field) + { + // Treat all fields on compiler-generated types as hoisted locals. + // This avoids depending on the name mangling scheme for hoisted locals. + var declaringTypeName = field.DeclaringType.Name; + return CompilerGeneratedNames.IsLambdaDisplayClass (declaringTypeName) || CompilerGeneratedNames.IsStateMachineType (declaringTypeName); + } + + // "Nested function" refers to lambdas and local functions. + public static bool IsNestedFunctionOrStateMachineMember (IMemberDefinition member) + { + if (member is MethodDefinition method && CompilerGeneratedNames.IsLambdaOrLocalFunction (method.Name)) + return true; + + if (member.DeclaringType is not TypeDefinition declaringType) + return false; + + return CompilerGeneratedNames.IsStateMachineType (declaringType.Name); + } + + public static bool TryGetStateMachineType (MethodDefinition method, [NotNullWhen (true)] out TypeDefinition? stateMachineType) + { + stateMachineType = null; + // Discover state machine methods. + if (!method.HasCustomAttributes) + return false; + + foreach (var attribute in method.CustomAttributes) { + if (attribute.AttributeType.Namespace != "System.Runtime.CompilerServices") + continue; + + switch (attribute.AttributeType.Name) { + case "AsyncIteratorStateMachineAttribute": + case "AsyncStateMachineAttribute": + case "IteratorStateMachineAttribute": + stateMachineType = GetFirstConstructorArgumentAsType (attribute); + return stateMachineType != null; + } + } + return false; + } + + /// + /// Walks the type and its descendents to find Roslyn-compiler generated + /// code and gather information to map it back to original user code. If + /// a compiler-generated type is passed in directly, this method will walk + /// up and find the nearest containing user type. Returns the nearest user type, + /// or null if none was found. + /// + TypeDefinition? PopulateCacheForType (TypeDefinition type) + { + // Look in the declaring type if this is a compiler-generated type (state machine or display class). + // State machines can be emitted into display classes, so we may also need to go one more level up. + // To avoid depending on implementation details, we go up until we see a non-compiler-generated type. + // This is the counterpart to GetCompilerGeneratedNestedTypes. + while (type != null && CompilerGeneratedNames.IsGeneratedMemberName (type.Name)) + type = type.DeclaringType; + + if (type is null) + return null; + + // Avoid repeat scans of the same type + if (_cachedTypeToCompilerGeneratedMembers.ContainsKey (type)) + return type; + + var callGraph = new CompilerGeneratedCallGraph (); + var userDefinedMethods = new HashSet (); + + void ProcessMethod (MethodDefinition method) + { + bool isStateMachineMember = CompilerGeneratedNames.IsStateMachineType (method.DeclaringType.Name); + if (!CompilerGeneratedNames.IsLambdaOrLocalFunction (method.Name)) { + if (!isStateMachineMember) { + // If it's not a nested function, track as an entry point to the call graph. + var added = userDefinedMethods.Add (method); + Debug.Assert (added); + } + } else { + // We don't expect lambdas or local functions to be emitted directly into + // state machine types. + Debug.Assert (!isStateMachineMember); + } + + // Discover calls or references to lambdas or local functions. This includes + // calls to local functions, and lambda assignments (which use ldftn). + if (method.Body != null) { + foreach (var instruction in method.Body.Instructions) { + if (instruction.OpCode.OperandType != OperandType.InlineMethod) + continue; + + MethodDefinition? lambdaOrLocalFunction = _context.TryResolve ((MethodReference) instruction.Operand); + if (lambdaOrLocalFunction == null) + continue; + + if (lambdaOrLocalFunction.IsConstructor && + lambdaOrLocalFunction.DeclaringType is var generatedType && + // Don't consider calls in the same type, like inside a static constructor + method.DeclaringType != generatedType && + CompilerGeneratedNames.IsLambdaDisplayClass (generatedType.Name)) { + // fill in null for now, attribute providers will be filled in later + if (!_generatedTypeToTypeArgumentInfo.TryAdd (generatedType, new TypeArgumentInfo (method, null))) { + var alreadyAssociatedMethod = _generatedTypeToTypeArgumentInfo[generatedType].CreatingMethod; + _context.LogWarning (new MessageOrigin (method), DiagnosticId.MethodsAreAssociatedWithUserMethod, method.GetDisplayName (), alreadyAssociatedMethod.GetDisplayName (), generatedType.GetDisplayName ()); + } + continue; + } + + if (!CompilerGeneratedNames.IsLambdaOrLocalFunction (lambdaOrLocalFunction.Name)) + continue; + + if (isStateMachineMember) { + callGraph.TrackCall (method.DeclaringType, lambdaOrLocalFunction); + } else { + callGraph.TrackCall (method, lambdaOrLocalFunction); + } + } + } + + if (TryGetStateMachineType (method, out TypeDefinition? stateMachineType)) { + Debug.Assert (stateMachineType.DeclaringType == type || + CompilerGeneratedNames.IsGeneratedMemberName (stateMachineType.DeclaringType.Name) && + stateMachineType.DeclaringType.DeclaringType == type); + callGraph.TrackCall (method, stateMachineType); + + if (!_compilerGeneratedTypeToUserCodeMethod.TryAdd (stateMachineType, method)) { + var alreadyAssociatedMethod = _compilerGeneratedTypeToUserCodeMethod[stateMachineType]; + _context.LogWarning (new MessageOrigin (method), DiagnosticId.MethodsAreAssociatedWithStateMachine, method.GetDisplayName (), alreadyAssociatedMethod.GetDisplayName (), stateMachineType.GetDisplayName ()); + } + // Already warned above if multiple methods map to the same type + // Fill in null for argument providers now, the real providers will be filled in later + _ = _generatedTypeToTypeArgumentInfo.TryAdd (stateMachineType, new TypeArgumentInfo (method, null)); + } + } + + // Look for state machine methods, and methods which call local functions. + foreach (MethodDefinition method in type.Methods) + ProcessMethod (method); + + // Also scan compiler-generated state machine methods (in case they have calls to nested functions), + // and nested functions inside compiler-generated closures (in case they call other nested functions). + + // State machines can be emitted into lambda display classes, so we need to go down at least two + // levels to find calls from iterator nested functions to other nested functions. We just recurse into + // all compiler-generated nested types to avoid depending on implementation details. + + foreach (var nestedType in GetCompilerGeneratedNestedTypes (type)) { + foreach (var method in nestedType.Methods) + ProcessMethod (method); + } + + // Now we've discovered the call graphs for calls to nested functions. + // Use this to map back from nested functions to the declaring user methods. + + // Note: This maps all nested functions back to the user code, not to the immediately + // declaring local function. The IL doesn't contain enough information in general for + // us to determine the nesting of local functions and lambdas. + + // Note: this only discovers nested functions which are referenced from the user + // code or its referenced nested functions. There is no reliable way to determine from + // IL which user code an unused nested function belongs to. + + Dictionary>? compilerGeneratedCallees = null; + foreach (var userDefinedMethod in userDefinedMethods) { + var callees = callGraph.GetReachableMembers (userDefinedMethod); + if (!callees.Any ()) + continue; + + compilerGeneratedCallees ??= new Dictionary> (); + compilerGeneratedCallees.Add (userDefinedMethod, new List (callees)); + + foreach (var compilerGeneratedMember in callees) { + switch (compilerGeneratedMember) { + case MethodDefinition nestedFunction: + Debug.Assert (CompilerGeneratedNames.IsLambdaOrLocalFunction (nestedFunction.Name)); + // Nested functions get suppressions from the user method only. + if (!_compilerGeneratedMethodToUserCodeMethod.TryAdd (nestedFunction, userDefinedMethod)) { + var alreadyAssociatedMethod = _compilerGeneratedMethodToUserCodeMethod[nestedFunction]; + _context.LogWarning (new MessageOrigin (userDefinedMethod), DiagnosticId.MethodsAreAssociatedWithUserMethod, userDefinedMethod.GetDisplayName (), alreadyAssociatedMethod.GetDisplayName (), nestedFunction.GetDisplayName ()); + } + break; + case TypeDefinition stateMachineType: + // Types in the call graph are always state machine types + // For those all their methods are not tracked explicitly in the call graph; instead, they + // are represented by the state machine type itself. + // We are already tracking the association of the state machine type to the user code method + // above, so no need to track it here. + Debug.Assert (CompilerGeneratedNames.IsStateMachineType (stateMachineType.Name)); + break; + default: + throw new InvalidOperationException (); + } + } + } + + // Now that we have instantiating methods fully filled out, walk the generated types and fill in the attribute + // providers + foreach (var generatedType in _generatedTypeToTypeArgumentInfo.Keys) { + if (HasGenericParameters (generatedType)) + MapGeneratedTypeTypeParameters (generatedType); + } + + _cachedTypeToCompilerGeneratedMembers.Add (type, compilerGeneratedCallees); + return type; + + /// + /// Check if the type itself is generic. The only difference is that + /// if the type is a nested type, the generic parameters from its + /// parent type don't count. + /// + static bool HasGenericParameters (TypeDefinition typeDef) + { + if (!typeDef.IsNested) + return typeDef.HasGenericParameters; + + return typeDef.GenericParameters.Count > typeDef.DeclaringType.GenericParameters.Count; + } + + void MapGeneratedTypeTypeParameters (TypeDefinition generatedType) + { + Debug.Assert (CompilerGeneratedNames.IsGeneratedType (generatedType.Name)); + + if (!_generatedTypeToTypeArgumentInfo.TryGetValue (generatedType, out var typeInfo)) { + // This can happen for static (non-capturing) closure environments, where more than + // nested function can map to the same closure environment. Since the current functionality + // is based on a one-to-one relationship between environments (types) and methods, this is + // not supported. + return; + } + + if (typeInfo.OriginalAttributes is not null) { + return; + } + var method = typeInfo.CreatingMethod; + if (method.Body is { } body) { + var typeArgs = new ICustomAttributeProvider[generatedType.GenericParameters.Count]; + var typeRef = ScanForInit (generatedType, body); + if (typeRef is null) { + return; + } + for (int i = 0; i < typeRef.GenericArguments.Count; i++) { + var typeArg = typeRef.GenericArguments[i]; + // Start with the existing parameters, in case we can't find the mapped one + ICustomAttributeProvider userAttrs = generatedType.GenericParameters[i]; + // The type parameters of the state machine types are alpha renames of the + // the method parameters, so the type ref should always be a GenericParameter. However, + // in the case of nesting, there may be multiple renames, so if the parameter is a method + // we know we're done, but if it's another state machine, we have to keep looking to find + // the original owner of that state machine. + if (typeArg is GenericParameter { Owner: { } owner } param) { + if (owner is MethodReference) { + userAttrs = param; + } else { + // Must be a type ref + var owningRef = (TypeReference) owner; + if (!CompilerGeneratedNames.IsGeneratedType (owningRef.Name)) { + userAttrs = param; + } else if (_context.TryResolve ((TypeReference) param.Owner) is { } owningType) { + MapGeneratedTypeTypeParameters (owningType); + if (_generatedTypeToTypeArgumentInfo.TryGetValue (owningType, out var owningInfo) && + owningInfo.OriginalAttributes is { } owningAttrs) { + userAttrs = owningAttrs[param.Position]; + } + } + } + } + + typeArgs[i] = userAttrs; + } + _generatedTypeToTypeArgumentInfo[generatedType] = typeInfo with { OriginalAttributes = typeArgs }; + } + } + + GenericInstanceType? ScanForInit (TypeDefinition stateMachineType, MethodBody body) + { + foreach (var instr in body.Instructions) { + switch (instr.OpCode.Code) { + case Code.Initobj: + case Code.Newobj: + if (instr.Operand is MethodReference { DeclaringType: GenericInstanceType typeRef } + && stateMachineType == _context.TryResolve (typeRef)) { + return typeRef; + } + break; + } + } + return null; + } + } + + static TypeDefinition? GetFirstConstructorArgumentAsType (CustomAttribute attribute) + { + if (!attribute.HasConstructorArguments) + return null; + + return attribute.ConstructorArguments[0].Value as TypeDefinition; + } + + public bool TryGetCompilerGeneratedCalleesForUserMethod (MethodDefinition method, [NotNullWhen (true)] out List? callees) + { + callees = null; + if (IsNestedFunctionOrStateMachineMember (method)) + return false; + + var typeToCache = PopulateCacheForType (method.DeclaringType); + if (typeToCache is null) + return false; + + return _cachedTypeToCompilerGeneratedMembers[typeToCache]?.TryGetValue (method, out callees) == true; + } + + /// + /// Gets the attributes on the "original" method of a generated type, i.e. the + /// attributes on the corresponding type parameters from the owning method. + /// + public IReadOnlyList? GetGeneratedTypeAttributes (TypeDefinition generatedType) + { + Debug.Assert (CompilerGeneratedNames.IsGeneratedType (generatedType.Name)); + + var typeToCache = PopulateCacheForType (generatedType); + if (typeToCache is null) + return null; + + if (_generatedTypeToTypeArgumentInfo.TryGetValue (generatedType, out var typeInfo)) { + return typeInfo.OriginalAttributes; + } + return null; + } + + // For state machine types/members, maps back to the state machine method. + // For local functions and lambdas, maps back to the owning method in user code (not the declaring + // lambda or local function, because the IL doesn't contain enough information to figure this out). + public bool TryGetOwningMethodForCompilerGeneratedMember (IMemberDefinition sourceMember, [NotNullWhen (true)] out MethodDefinition? owningMethod) + { + owningMethod = null; + if (sourceMember == null) + return false; + + MethodDefinition? compilerGeneratedMethod = sourceMember as MethodDefinition; + if (compilerGeneratedMethod != null) { + if (_compilerGeneratedMethodToUserCodeMethod.TryGetValue (compilerGeneratedMethod, out owningMethod)) + return true; + } + + TypeDefinition sourceType = sourceMember as TypeDefinition ?? sourceMember.DeclaringType; + + if (_compilerGeneratedTypeToUserCodeMethod.TryGetValue (sourceType, out owningMethod)) + return true; + + if (!IsNestedFunctionOrStateMachineMember (sourceMember)) + return false; + + // sourceType is a state machine type, or the type containing a lambda or local function. + // Search all methods to find the one which points to the type as its + // state machine implementation. + var typeToCache = PopulateCacheForType (sourceType); + if (typeToCache is null) + return false; + + if (compilerGeneratedMethod != null) { + if (_compilerGeneratedMethodToUserCodeMethod.TryGetValue (compilerGeneratedMethod, out owningMethod)) + return true; + } + + if (_compilerGeneratedTypeToUserCodeMethod.TryGetValue (sourceType, out owningMethod)) + return true; + + return false; + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/DynamicallyAccessedMembersTypeHierarchy.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/DynamicallyAccessedMembersTypeHierarchy.cs index 103745ac54fc8..caa1be80e7260 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/DynamicallyAccessedMembersTypeHierarchy.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/DynamicallyAccessedMembersTypeHierarchy.cs @@ -10,7 +10,7 @@ namespace Mono.Linker.Dataflow { - class DynamicallyAccessedMembersTypeHierarchy + sealed class DynamicallyAccessedMembersTypeHierarchy { readonly LinkContext _context; readonly MarkStep _markStep; diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/FlowAnnotations.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/FlowAnnotations.cs index 32e148acd97ae..610cd1f59d5a9 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/FlowAnnotations.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/FlowAnnotations.cs @@ -15,7 +15,7 @@ namespace ILLink.Shared.TrimAnalysis { - partial class FlowAnnotations + sealed partial class FlowAnnotations { readonly LinkContext _context; readonly Dictionary _annotations = new Dictionary (); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/HoistedLocalKey.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/HoistedLocalKey.cs new file mode 100644 index 0000000000000..b721df288b408 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/HoistedLocalKey.cs @@ -0,0 +1,30 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Diagnostics; +using Mono.Cecil; + +namespace Mono.Linker.Dataflow +{ + // This represents a field which has been generated by the compiler as the + // storage location for a hoisted local (a local variable which is lifted to a + // field on a state machine type, or to a field on a closure accessed by lambdas + // or local functions). + public readonly struct HoistedLocalKey : IEquatable + { + readonly FieldDefinition Field; + + public HoistedLocalKey (FieldDefinition field) + { + Debug.Assert (CompilerGeneratedState.IsHoistedLocal (field)); + Field = field; + } + + public bool Equals (HoistedLocalKey other) => Field.Equals (other.Field); + + public override bool Equals (object? obj) => obj is HoistedLocalKey other && Equals (other); + + public override int GetHashCode () => Field.GetHashCode (); + } +} \ No newline at end of file diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/InterproceduralState.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/InterproceduralState.cs new file mode 100644 index 0000000000000..0c5eec7f78c67 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/InterproceduralState.cs @@ -0,0 +1,92 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using ILLink.Shared.DataFlow; +using Mono.Cecil; +using Mono.Cecil.Cil; +using HoistedLocalState = ILLink.Shared.DataFlow.DefaultValueDictionary< + Mono.Linker.Dataflow.HoistedLocalKey, + ILLink.Shared.DataFlow.ValueSet>; +using MultiValue = ILLink.Shared.DataFlow.ValueSet; + +namespace Mono.Linker.Dataflow +{ + // Wrapper that implements IEquatable for MethodBody. + readonly record struct MethodBodyValue (MethodBody MethodBody); + + // Tracks the set of methods which get analyzer together during interprocedural analysis, + // and the possible states of hoisted locals in state machine methods and lambdas/local functions. + struct InterproceduralState : IEquatable + { + public ValueSet MethodBodies; + public HoistedLocalState HoistedLocals; + readonly InterproceduralStateLattice lattice; + + public InterproceduralState (ValueSet methodBodies, HoistedLocalState hoistedLocals, InterproceduralStateLattice lattice) + => (MethodBodies, HoistedLocals, this.lattice) = (methodBodies, hoistedLocals, lattice); + + public bool Equals (InterproceduralState other) + => MethodBodies.Equals (other.MethodBodies) && HoistedLocals.Equals (other.HoistedLocals); + + public InterproceduralState Clone () + => new (MethodBodies.Clone (), HoistedLocals.Clone (), lattice); + + public void TrackMethod (MethodDefinition method) + { + if (method.Body is not MethodBody methodBody) + return; + + // Work around the fact that ValueSet is readonly + var methodsList = new List (MethodBodies); + methodsList.Add (new MethodBodyValue (methodBody)); + + // For state machine methods, also scan the state machine members. + // Simplification: assume that all generated methods of the state machine type are + // reached at the point where the state machine method is reached. + if (CompilerGeneratedState.TryGetStateMachineType (method, out TypeDefinition? stateMachineType)) { + foreach (var stateMachineMethod in stateMachineType.Methods) { + Debug.Assert (!CompilerGeneratedNames.IsLambdaOrLocalFunction (stateMachineMethod.Name)); + if (stateMachineMethod.Body is MethodBody stateMachineMethodBody) + methodsList.Add (new MethodBodyValue (stateMachineMethodBody)); + } + } + + MethodBodies = new ValueSet (methodsList); + } + + public void SetHoistedLocal (HoistedLocalKey key, MultiValue value) + { + // For hoisted locals, we track the entire set of assigned values seen + // in the closure of a method, so setting a hoisted local value meets + // it with any existing value. + HoistedLocals.Set (key, + lattice.HoistedLocalsLattice.ValueLattice.Meet ( + HoistedLocals.Get (key), value)); + } + + public MultiValue GetHoistedLocal (HoistedLocalKey key) + => HoistedLocals.Get (key); + } + + struct InterproceduralStateLattice : ILattice + { + public readonly ValueSetLattice MethodBodyLattice; + public readonly DictionaryLattice> HoistedLocalsLattice; + + public InterproceduralStateLattice ( + ValueSetLattice methodBodyLattice, + DictionaryLattice> hoistedLocalsLattice) + => (MethodBodyLattice, HoistedLocalsLattice) = (methodBodyLattice, hoistedLocalsLattice); + + public InterproceduralState Top => new InterproceduralState (MethodBodyLattice.Top, HoistedLocalsLattice.Top, this); + + public InterproceduralState Meet (InterproceduralState left, InterproceduralState right) + => new ( + MethodBodyLattice.Meet (left.MethodBodies, right.MethodBodies), + HoistedLocalsLattice.Meet (left.HoistedLocals, right.HoistedLocals), + this); + } +} \ No newline at end of file diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/MethodBodyScanner.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/MethodBodyScanner.cs index 0562acee139f3..9bb41e9e35fc1 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/MethodBodyScanner.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/MethodBodyScanner.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; using System.Linq; using ILLink.Shared; using ILLink.Shared.DataFlow; @@ -13,7 +12,9 @@ using Mono.Cecil; using Mono.Cecil.Cil; using Mono.Collections.Generic; -using LocalVariableStore = System.Collections.Generic.Dictionary; +using LocalVariableStore = System.Collections.Generic.Dictionary< + Mono.Cecil.Cil.VariableDefinition, + Mono.Linker.Dataflow.ValueBasicBlockPair>; using MultiValue = ILLink.Shared.DataFlow.ValueSet; namespace Mono.Linker.Dataflow @@ -44,12 +45,13 @@ public StackSlot (MultiValue value) abstract partial class MethodBodyScanner { protected readonly LinkContext _context; + protected readonly InterproceduralStateLattice InterproceduralStateLattice; protected static ValueSetLattice MultiValueLattice => default; - protected static ValueSetLattice MethodLattice => default; protected MethodBodyScanner (LinkContext context) { this._context = context; + this.InterproceduralStateLattice = default; } internal MultiValue ReturnValue { private set; get; } @@ -224,63 +226,42 @@ protected static void StoreMethodLocalValue ( // Scans the method as well as any nested functions (local functions or lambdas) and state machines // reachable from it. - public virtual void InterproceduralScan (MethodBody methodBody) + public virtual void InterproceduralScan (MethodDefinition startingMethod) { - var methodsInGroup = new ValueSet (methodBody.Method); + // Note that the default value of a hoisted local will be MultiValueLattice.Top, not UnknownValue.Instance. + // This ensures that there are no warnings for the "unassigned state" of a parameter. + // Definite assignment should ensure that there is no way for this to be an analysis hole. + var interproceduralState = InterproceduralStateLattice.Top; - // Optimization to prevent multiple scans of a method. - // Eventually we will need to allow re-scanning in some cases, for example - // when we discover new inputs to a method. But we aren't doing dataflow across - // lambdas and local functions yet, so no need for now. - HashSet scannedMethods = new HashSet (); + var oldInterproceduralState = interproceduralState.Clone (); + interproceduralState.TrackMethod (startingMethod); - while (true) { - if (!TryGetNextMethodToScan (out MethodDefinition? methodToScan)) - break; + while (!interproceduralState.Equals (oldInterproceduralState)) { + oldInterproceduralState = interproceduralState.Clone (); - scannedMethods.Add (methodToScan); - Scan (methodToScan.Body, ref methodsInGroup); - - // For state machine methods, also scan the state machine members. - // Simplification: assume that all generated methods of the state machine type are - // invoked at the point where the state machine method is called. - if (CompilerGeneratedState.TryGetStateMachineType (methodToScan, out TypeDefinition? stateMachineType)) { - foreach (var method in stateMachineType.Methods) { - Debug.Assert (!CompilerGeneratedNames.IsLambdaOrLocalFunction (method.Name)); - if (method.Body is MethodBody stateMachineBody) - Scan (stateMachineBody, ref methodsInGroup); - } - } + // Flow state through all methods encountered so far, as long as there + // are changes discovered in the hoisted local state on entry to any method. + foreach (var methodBodyValue in oldInterproceduralState.MethodBodies) + Scan (methodBodyValue.MethodBody, ref interproceduralState); } #if DEBUG // Validate that the compiler-generated callees tracked by the compiler-generated state // are the same set of methods that we discovered and scanned above. - if (_context.CompilerGeneratedState.TryGetCompilerGeneratedCalleesForUserMethod (methodBody.Method, out List? compilerGeneratedCallees)) { + if (_context.CompilerGeneratedState.TryGetCompilerGeneratedCalleesForUserMethod (startingMethod, out List? compilerGeneratedCallees)) { var calleeMethods = compilerGeneratedCallees.OfType (); - Debug.Assert (methodsInGroup.Count () == 1 + calleeMethods.Count ()); - foreach (var method in calleeMethods) - Debug.Assert (methodsInGroup.Contains (method)); + // https://github.com/dotnet/linker/issues/2845 + // Disabled asserts due to a bug + // Debug.Assert (interproceduralState.Count == 1 + calleeMethods.Count ()); + // foreach (var method in calleeMethods) + // Debug.Assert (interproceduralState.Any (kvp => kvp.Key.Method == method)); } else { - Debug.Assert (methodsInGroup.Count () == 1); + Debug.Assert (interproceduralState.MethodBodies.Count () == 1); } #endif - - bool TryGetNextMethodToScan ([NotNullWhen (true)] out MethodDefinition? method) - { - foreach (var candidate in methodsInGroup) { - var candidateMethod = candidate.Method; - if (!scannedMethods.Contains (candidateMethod) && candidateMethod.HasBody) { - method = candidateMethod; - return true; - } - } - method = null; - return false; - } } - void TrackNestedFunctionReference (MethodReference referencedMethod, ref ValueSet methodsInGroup) + void TrackNestedFunctionReference (MethodReference referencedMethod, ref InterproceduralState interproceduralState) { if (_context.TryResolve (referencedMethod) is not MethodDefinition method) return; @@ -288,10 +269,10 @@ void TrackNestedFunctionReference (MethodReference referencedMethod, ref ValueSe if (!CompilerGeneratedNames.IsLambdaOrLocalFunction (method.Name)) return; - methodsInGroup = MethodLattice.Meet (methodsInGroup, new (method)); + interproceduralState.TrackMethod (method); } - protected virtual void Scan (MethodBody methodBody, ref ValueSet methodsInGroup) + protected virtual void Scan (MethodBody methodBody, ref InterproceduralState interproceduralState) { MethodDefinition thisMethod = methodBody.Method; @@ -408,7 +389,7 @@ protected virtual void Scan (MethodBody methodBody, ref ValueSet me break; case Code.Ldftn: - TrackNestedFunctionReference ((MethodReference) operation.Operand, ref methodsInGroup); + TrackNestedFunctionReference ((MethodReference) operation.Operand, ref interproceduralState); PushUnknown (currentStack); break; @@ -515,7 +496,7 @@ protected virtual void Scan (MethodBody methodBody, ref ValueSet me case Code.Ldsfld: case Code.Ldflda: case Code.Ldsflda: - ScanLdfld (operation, currentStack, methodBody); + ScanLdfld (operation, currentStack, methodBody, ref interproceduralState); break; case Code.Newarr: { @@ -559,7 +540,7 @@ protected virtual void Scan (MethodBody methodBody, ref ValueSet me case Code.Stfld: case Code.Stsfld: - ScanStfld (operation, currentStack, thisMethod, methodBody); + ScanStfld (operation, currentStack, thisMethod, methodBody, ref interproceduralState); break; case Code.Cpobj: @@ -634,8 +615,8 @@ protected virtual void Scan (MethodBody methodBody, ref ValueSet me case Code.Call: case Code.Callvirt: case Code.Newobj: - TrackNestedFunctionReference ((MethodReference) operation.Operand, ref methodsInGroup); - HandleCall (methodBody, operation, currentStack, locals, curBasicBlock); + TrackNestedFunctionReference ((MethodReference) operation.Operand, ref interproceduralState); + HandleCall (methodBody, operation, currentStack, locals, ref interproceduralState, curBasicBlock); break; case Code.Jmp: @@ -672,7 +653,7 @@ protected virtual void Scan (MethodBody methodBody, ref ValueSet me StackSlot retValue = PopUnknown (currentStack, 1, methodBody, operation.Offset); // If the return value is a reference, treat it as the value itself for now // We can handle ref return values better later - ReturnValue = MultiValueLattice.Meet (ReturnValue, DereferenceValue (retValue.Value, locals)); + ReturnValue = MultiValueLattice.Meet (ReturnValue, DereferenceValue (retValue.Value, locals, ref interproceduralState)); } ClearStack (ref currentStack); break; @@ -941,7 +922,8 @@ when GetMethodParameterValue (parameterReference.MethodDefinition, parameterRefe private void ScanLdfld ( Instruction operation, Stack currentStack, - MethodBody methodBody) + MethodBody methodBody, + ref InterproceduralState interproceduralState) { Code code = operation.OpCode.Code; if (code == Code.Ldfld || code == Code.Ldflda) @@ -950,16 +932,20 @@ private void ScanLdfld ( bool isByRef = code == Code.Ldflda || code == Code.Ldsflda; FieldDefinition? field = _context.TryResolve ((FieldReference) operation.Operand); - if (field != null) { - MultiValue newValue = isByRef ? - new FieldReferenceValue (field) - : GetFieldValue (field); - StackSlot slot = new (newValue); - currentStack.Push (slot); + if (field == null) { + PushUnknown (currentStack); return; } - PushUnknown (currentStack); + MultiValue value; + if (isByRef) { + value = new FieldReferenceValue (field); + } else if (CompilerGeneratedState.IsHoistedLocal (field)) { + value = interproceduralState.GetHoistedLocal (new HoistedLocalKey (field)); + } else { + value = GetFieldValue (field); + } + currentStack.Push (new StackSlot (value)); } protected virtual void HandleStoreField (MethodDefinition method, FieldValue field, Instruction operation, MultiValue valueToStore) @@ -982,7 +968,8 @@ private void ScanStfld ( Instruction operation, Stack currentStack, MethodDefinition thisMethod, - MethodBody methodBody) + MethodBody methodBody, + ref InterproceduralState interproceduralState) { StackSlot valueToStoreSlot = PopUnknown (currentStack, 1, methodBody, operation.Offset); if (operation.OpCode.Code == Code.Stfld) @@ -990,6 +977,11 @@ private void ScanStfld ( FieldDefinition? field = _context.TryResolve ((FieldReference) operation.Operand); if (field != null) { + if (CompilerGeneratedState.IsHoistedLocal (field)) { + interproceduralState.SetHoistedLocal (new HoistedLocalKey (field), valueToStoreSlot.Value); + return; + } + foreach (var value in GetFieldValue (field)) { // GetFieldValue may return different node types, in which case they can't be stored to. // At least not yet. @@ -1040,7 +1032,7 @@ private ValueNodeList PopCallArguments ( return methodParams; } - internal MultiValue DereferenceValue (MultiValue maybeReferenceValue, Dictionary locals) + internal MultiValue DereferenceValue (MultiValue maybeReferenceValue, Dictionary locals, ref InterproceduralState interproceduralState) { MultiValue dereferencedValue = MultiValueLattice.Top; foreach (var value in maybeReferenceValue) { @@ -1048,7 +1040,9 @@ internal MultiValue DereferenceValue (MultiValue maybeReferenceValue, Dictionary case FieldReferenceValue fieldReferenceValue: dereferencedValue = MultiValue.Meet ( dereferencedValue, - GetFieldValue (fieldReferenceValue.FieldDefinition)); + CompilerGeneratedState.IsHoistedLocal (fieldReferenceValue.FieldDefinition) + ? interproceduralState.GetHoistedLocal (new HoistedLocalKey (fieldReferenceValue.FieldDefinition)) + : GetFieldValue (fieldReferenceValue.FieldDefinition)); break; case ParameterReferenceValue parameterReferenceValue: dereferencedValue = MultiValue.Meet ( @@ -1101,6 +1095,7 @@ private void HandleCall ( Instruction operation, Stack currentStack, LocalVariableStore locals, + ref InterproceduralState interproceduralState, int curBasicBlock) { MethodReference calledMethod = (MethodReference) operation.Operand; @@ -1110,13 +1105,15 @@ private void HandleCall ( SingleValue? newObjValue; ValueNodeList methodArguments = PopCallArguments (currentStack, calledMethod, callingMethodBody, isNewObj, operation.Offset, out newObjValue); - ValueNodeList dereferencedMethodParams = new (methodArguments.Select (param => DereferenceValue (param, locals)).ToList ()); + var dereferencedMethodParams = new List (); + foreach (var argument in methodArguments) + dereferencedMethodParams.Add (DereferenceValue (argument, locals, ref interproceduralState)); MultiValue methodReturnValue; bool handledFunction = HandleCall ( callingMethodBody, calledMethod, operation, - dereferencedMethodParams, + new ValueNodeList (dereferencedMethodParams), out methodReturnValue); // Handle the return value or newobj result diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/MethodProxy.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/MethodProxy.cs index 2b22095662cb7..6821613c44f50 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/MethodProxy.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/MethodProxy.cs @@ -53,7 +53,7 @@ internal partial ImmutableArray GetGenericParameters () public override string ToString () => Method.ToString (); - public ReferenceKind ParameterReferenceKind (int index) => Method.ParameterReferenceKind (index); + public ReferenceKind ParameterReferenceKind (int index) => Method.HasImplicitThis () ? Method.ParameterReferenceKind (index + 1) : Method.ParameterReferenceKind (index); public bool Equals (MethodProxy other) => Method.Equals (other.Method); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ReflectionMethodBodyScanner.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ReflectionMethodBodyScanner.cs index 7138685726dbf..6b07bc53cdd61 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ReflectionMethodBodyScanner.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ReflectionMethodBodyScanner.cs @@ -7,7 +7,6 @@ using System.Diagnostics.CodeAnalysis; using System.Linq; using ILLink.Shared; -using ILLink.Shared.DataFlow; using ILLink.Shared.TrimAnalysis; using ILLink.Shared.TypeSystemProxy; using Mono.Cecil; @@ -17,7 +16,7 @@ namespace Mono.Linker.Dataflow { - class ReflectionMethodBodyScanner : MethodBodyScanner + sealed class ReflectionMethodBodyScanner : MethodBodyScanner { readonly MarkStep _markStep; MessageOrigin _origin; @@ -59,21 +58,21 @@ public ReflectionMethodBodyScanner (LinkContext context, MarkStep parent, Messag _origin = origin; _annotations = context.Annotations.FlowAnnotations; _reflectionMarker = new ReflectionMarker (context, parent, enabled: false); - TrimAnalysisPatterns = new TrimAnalysisPatternStore (context); + TrimAnalysisPatterns = new TrimAnalysisPatternStore (MultiValueLattice, context); } - public override void InterproceduralScan (MethodBody methodBody) + public override void InterproceduralScan (MethodDefinition method) { - base.InterproceduralScan (methodBody); + base.InterproceduralScan (method); var reflectionMarker = new ReflectionMarker (_context, _markStep, enabled: true); TrimAnalysisPatterns.MarkAndProduceDiagnostics (reflectionMarker, _markStep); } - protected override void Scan (MethodBody methodBody, ref ValueSet methodsInGroup) + protected override void Scan (MethodBody methodBody, ref InterproceduralState interproceduralState) { _origin = new MessageOrigin (methodBody.Method); - base.Scan (methodBody, ref methodsInGroup); + base.Scan (methodBody, ref interproceduralState); if (!methodBody.Method.ReturnsVoid ()) { var method = methodBody.Method; diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/TrimAnalysisAssignmentPattern.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/TrimAnalysisAssignmentPattern.cs index 401330e558a20..81010a3a62579 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/TrimAnalysisAssignmentPattern.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/TrimAnalysisAssignmentPattern.cs @@ -2,6 +2,8 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; +using System.Diagnostics; +using ILLink.Shared.DataFlow; using ILLink.Shared.TrimAnalysis; using MultiValue = ILLink.Shared.DataFlow.ValueSet; @@ -20,6 +22,16 @@ public TrimAnalysisAssignmentPattern (MultiValue source, MultiValue target, Mess Origin = origin; } + public TrimAnalysisAssignmentPattern Merge (ValueSetLattice lattice, TrimAnalysisAssignmentPattern other) + { + Debug.Assert (Origin == other.Origin); + + return new TrimAnalysisAssignmentPattern ( + lattice.Meet (Source, other.Source), + lattice.Meet (Target, other.Target), + Origin); + } + public void MarkAndProduceDiagnostics (ReflectionMarker reflectionMarker, LinkContext context) { bool diagnosticsEnabled = !context.Annotations.ShouldSuppressAnalysisWarningsForRequiresUnreferencedCode (Origin.Provider); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/TrimAnalysisMethodCallPattern.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/TrimAnalysisMethodCallPattern.cs index 1f71d7e4948dd..be4219ae6d698 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/TrimAnalysisMethodCallPattern.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/TrimAnalysisMethodCallPattern.cs @@ -3,11 +3,11 @@ using System.Collections.Immutable; using System.Diagnostics; +using ILLink.Shared.DataFlow; using ILLink.Shared.TrimAnalysis; using Mono.Cecil; using Mono.Cecil.Cil; using Mono.Linker.Steps; - using MultiValue = ILLink.Shared.DataFlow.ValueSet; namespace Mono.Linker.Dataflow @@ -42,6 +42,25 @@ public TrimAnalysisMethodCallPattern ( Origin = origin; } + public TrimAnalysisMethodCallPattern Merge (ValueSetLattice lattice, TrimAnalysisMethodCallPattern other) + { + Debug.Assert (Operation == other.Operation); + Debug.Assert (Origin == other.Origin); + Debug.Assert (CalledMethod == other.CalledMethod); + Debug.Assert (Arguments.Length == other.Arguments.Length); + + var argumentsBuilder = ImmutableArray.CreateBuilder (); + for (int i = 0; i < Arguments.Length; i++) + argumentsBuilder.Add (lattice.Meet (Arguments[i], other.Arguments[i])); + + return new TrimAnalysisMethodCallPattern ( + Operation, + CalledMethod, + lattice.Meet (Instance, other.Instance), + argumentsBuilder.ToImmutable (), + Origin); + } + public void MarkAndProduceDiagnostics (ReflectionMarker reflectionMarker, MarkStep markStep, LinkContext context) { bool diagnosticsEnabled = !context.Annotations.ShouldSuppressAnalysisWarningsForRequiresUnreferencedCode (Origin.Provider); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/TrimAnalysisPatternStore.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/TrimAnalysisPatternStore.cs index 2e7796ef385da..ee88e9d55da04 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/TrimAnalysisPatternStore.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/TrimAnalysisPatternStore.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System.Collections.Generic; +using ILLink.Shared.DataFlow; using ILLink.Shared.TrimAnalysis; using Mono.Linker.Steps; @@ -11,12 +12,14 @@ public readonly struct TrimAnalysisPatternStore { readonly Dictionary<(MessageOrigin, bool), TrimAnalysisAssignmentPattern> AssignmentPatterns; readonly Dictionary MethodCallPatterns; + readonly ValueSetLattice Lattice; readonly LinkContext _context; - public TrimAnalysisPatternStore (LinkContext context) + public TrimAnalysisPatternStore (ValueSetLattice lattice, LinkContext context) { AssignmentPatterns = new Dictionary<(MessageOrigin, bool), TrimAnalysisAssignmentPattern> (); MethodCallPatterns = new Dictionary (); + Lattice = lattice; _context = context; } @@ -27,12 +30,23 @@ public void Add (TrimAnalysisAssignmentPattern pattern) // https://github.com/dotnet/linker/issues/2778 // For now, work around it with a separate bit. bool isReturnValue = pattern.Target.AsSingleValue () is MethodReturnValue; - AssignmentPatterns.Add ((pattern.Origin, isReturnValue), pattern); + + if (!AssignmentPatterns.TryGetValue ((pattern.Origin, isReturnValue), out var existingPattern)) { + AssignmentPatterns.Add ((pattern.Origin, isReturnValue), pattern); + return; + } + + AssignmentPatterns[(pattern.Origin, isReturnValue)] = pattern.Merge (Lattice, existingPattern); } public void Add (TrimAnalysisMethodCallPattern pattern) { - MethodCallPatterns.Add (pattern.Origin, pattern); + if (!MethodCallPatterns.TryGetValue (pattern.Origin, out var existingPattern)) { + MethodCallPatterns.Add (pattern.Origin, pattern); + return; + } + + MethodCallPatterns[pattern.Origin] = pattern.Merge (Lattice, existingPattern); } public void MarkAndProduceDiagnostics (ReflectionMarker reflectionMarker, MarkStep markStep) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ValueNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ValueNode.cs index 5a0df8f347652..81d5e0581c2f9 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ValueNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ValueNode.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using ILLink.Shared; using MultiValue = ILLink.Shared.DataFlow.ValueSet; namespace Mono.Linker.Dataflow @@ -47,7 +48,7 @@ public override bool Equals (object? other) } } - public struct ValueBasicBlockPair + public struct ValueBasicBlockPair : IEquatable { public ValueBasicBlockPair (MultiValue value, int basicBlockIndex) { @@ -57,5 +58,11 @@ public ValueBasicBlockPair (MultiValue value, int basicBlockIndex) public MultiValue Value { get; } public int BasicBlockIndex { get; } + + public bool Equals (ValueBasicBlockPair other) => Value.Equals (other.Value) && BasicBlockIndex.Equals (other.BasicBlockIndex); + + public override bool Equals (object? obj) => obj is ValueBasicBlockPair other && Equals (other); + + public override int GetHashCode () => HashUtils.Combine (Value.GetHashCode (), BasicBlockIndex); } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMethodBodyScanner.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMethodBodyScanner.cs index 394bfe273b38d..6eecae4df64b8 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMethodBodyScanner.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReflectionMethodBodyScanner.cs @@ -29,7 +29,7 @@ namespace ILCompiler.Dataflow { - class ReflectionMethodBodyScanner : MethodBodyScanner + sealed class ReflectionMethodBodyScanner : MethodBodyScanner { private readonly Logger _logger; private readonly NodeFactory _factory; @@ -86,7 +86,7 @@ private ReflectionMethodBodyScanner(NodeFactory factory, FlowAnnotations annotat _factory = factory; _origin = origin; _reflectionMarker = new ReflectionMarker(logger, factory, annotations, typeHierarchyDataFlow: false, enabled: false); - TrimAnalysisPatterns = new TrimAnalysisPatternStore(logger); + TrimAnalysisPatterns = new TrimAnalysisPatternStore(MultiValueLattice, logger); } public override void InterproceduralScan(MethodIL methodBody) @@ -98,10 +98,10 @@ public override void InterproceduralScan(MethodIL methodBody) TrimAnalysisPatterns.MarkAndProduceDiagnostics(_reflectionMarker); } - protected override void Scan(MethodIL methodBody, ref ValueSet methodsInGroup) + protected override void Scan(MethodIL methodBody, ref InterproceduralState interproceduralState) { _origin = new MessageOrigin(methodBody.OwningMethod); - base.Scan(methodBody, ref methodsInGroup); + base.Scan(methodBody, ref interproceduralState); if (!methodBody.OwningMethod.Signature.ReturnType.IsVoid) { diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisAssignmentPattern.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisAssignmentPattern.cs index 714b5380fb88e..0ceead7dcb856 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisAssignmentPattern.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisAssignmentPattern.cs @@ -2,8 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; - +using System.Diagnostics; using ILCompiler.Logging; +using ILLink.Shared.DataFlow; using ILLink.Shared.TrimAnalysis; using MultiValue = ILLink.Shared.DataFlow.ValueSet; @@ -27,6 +28,17 @@ internal TrimAnalysisAssignmentPattern(MultiValue source, MultiValue target, Mes MemberWithRequirements = memberWithRequirements; } + public TrimAnalysisAssignmentPattern Merge(ValueSetLattice lattice, TrimAnalysisAssignmentPattern other) + { + Debug.Assert(Origin == other.Origin); + + return new TrimAnalysisAssignmentPattern( + lattice.Meet(Source, other.Source), + lattice.Meet(Target, other.Target), + Origin, + MemberWithRequirements); + } + public void MarkAndProduceDiagnostics(ReflectionMarker reflectionMarker, Logger logger) { var diagnosticContext = new DiagnosticContext( diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisMethodCallPattern.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisMethodCallPattern.cs index f24953f293b43..d58b9684280fe 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisMethodCallPattern.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisMethodCallPattern.cs @@ -4,6 +4,7 @@ using System.Collections.Immutable; using System.Diagnostics; using ILCompiler.Logging; +using ILLink.Shared.DataFlow; using ILLink.Shared.TrimAnalysis; using Internal.IL; using Internal.TypeSystem; @@ -52,6 +53,29 @@ public TrimAnalysisMethodCallPattern( Origin = origin; } + public TrimAnalysisMethodCallPattern Merge(ValueSetLattice lattice, TrimAnalysisMethodCallPattern other) + { + Debug.Assert(MethodBody.OwningMethod == other.MethodBody.OwningMethod); + Debug.Assert(Operation == other.Operation); + Debug.Assert(Offset == other.Offset); + Debug.Assert(Origin == other.Origin); + Debug.Assert(CalledMethod == other.CalledMethod); + Debug.Assert(Arguments.Length == other.Arguments.Length); + + var argumentsBuilder = ImmutableArray.CreateBuilder(); + for (int i = 0; i < Arguments.Length; i++) + argumentsBuilder.Add(lattice.Meet(Arguments[i], other.Arguments[i])); + + return new TrimAnalysisMethodCallPattern( + MethodBody, + Operation, + Offset, + CalledMethod, + lattice.Meet(Instance, other.Instance), + argumentsBuilder.ToImmutable(), + Origin); + } + public void MarkAndProduceDiagnostics(ReflectionMarker reflectionMarker, Logger logger) { var diagnosticContext = new DiagnosticContext( diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisPatternStore.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisPatternStore.cs index f170acc2495dd..e728a5599ff85 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisPatternStore.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/TrimAnalysisPatternStore.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using ILCompiler.Logging; +using ILLink.Shared.DataFlow; using ILLink.Shared.TrimAnalysis; #nullable enable @@ -13,12 +14,14 @@ public readonly struct TrimAnalysisPatternStore { readonly Dictionary<(MessageOrigin, bool), TrimAnalysisAssignmentPattern> AssignmentPatterns; readonly Dictionary MethodCallPatterns; + readonly ValueSetLattice Lattice; readonly Logger _logger; - public TrimAnalysisPatternStore(Logger logger) + public TrimAnalysisPatternStore(ValueSetLattice lattice, Logger logger) { AssignmentPatterns = new Dictionary<(MessageOrigin, bool), TrimAnalysisAssignmentPattern>(); MethodCallPatterns = new Dictionary(); + Lattice = lattice; _logger = logger; } @@ -29,12 +32,25 @@ public void Add(TrimAnalysisAssignmentPattern pattern) // https://github.com/dotnet/linker/issues/2778 // For now, work around it with a separate bit. bool isReturnValue = pattern.Target.AsSingleValue() is MethodReturnValue; - AssignmentPatterns.Add((pattern.Origin, isReturnValue), pattern); + + if (!AssignmentPatterns.TryGetValue((pattern.Origin, isReturnValue), out var existingPattern)) + { + AssignmentPatterns.Add((pattern.Origin, isReturnValue), pattern); + return; + } + + AssignmentPatterns[(pattern.Origin, isReturnValue)] = pattern.Merge(Lattice, existingPattern); } public void Add(TrimAnalysisMethodCallPattern pattern) { - MethodCallPatterns.Add(pattern.Origin, pattern); + if (!MethodCallPatterns.TryGetValue(pattern.Origin, out var existingPattern)) + { + MethodCallPatterns.Add(pattern.Origin, pattern); + return; + } + + MethodCallPatterns[pattern.Origin] = pattern.Merge(Lattice, existingPattern); } public void MarkAndProduceDiagnostics(ReflectionMarker reflectionMarker) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ValueNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ValueNode.cs index 9a8254de64c3d..8e22a0c9fc96f 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ValueNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ValueNode.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; - +using ILLink.Shared; using MultiValue = ILLink.Shared.DataFlow.ValueSet; #nullable enable @@ -51,7 +51,7 @@ public override bool Equals(object? other) } } - public struct ValueBasicBlockPair + public struct ValueBasicBlockPair : IEquatable { public ValueBasicBlockPair(MultiValue value, int basicBlockIndex) { @@ -61,5 +61,11 @@ public ValueBasicBlockPair(MultiValue value, int basicBlockIndex) public MultiValue Value { get; } public int BasicBlockIndex { get; } + + public bool Equals(ValueBasicBlockPair other) => Value.Equals(other.Value) && BasicBlockIndex.Equals(other.BasicBlockIndex); + + public override bool Equals(object? obj) => obj is ValueBasicBlockPair other && Equals(other); + + public override int GetHashCode() => HashUtils.Combine(Value.GetHashCode(), BasicBlockIndex); } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/DataflowAnalyzedMethodNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/DataflowAnalyzedMethodNode.cs index bde3bc13eabb4..2935553dba001 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/DataflowAnalyzedMethodNode.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/DataflowAnalyzedMethodNode.cs @@ -9,6 +9,7 @@ using Internal.Runtime; using Internal.IL; +using ILCompiler.Dataflow; using ILCompiler.DependencyAnalysisFramework; namespace ILCompiler.DependencyAnalysis @@ -23,6 +24,7 @@ public class DataflowAnalyzedMethodNode : DependencyNodeCore public DataflowAnalyzedMethodNode(MethodIL methodIL) { Debug.Assert(methodIL.OwningMethod.IsTypicalMethodDefinition); + Debug.Assert(!CompilerGeneratedState.IsNestedFunctionOrStateMachineMember(methodIL.OwningMethod)); _methodIL = methodIL; } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/UsageBasedMetadataManager.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/UsageBasedMetadataManager.cs index dc444aba181ac..602255af6a4bb 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/UsageBasedMetadataManager.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/UsageBasedMetadataManager.cs @@ -3,31 +3,29 @@ using System; using System.Collections.Generic; -using System.Reflection.PortableExecutable; -using System.Reflection.Metadata; using System.IO; +using System.Reflection.Metadata; +using System.Reflection.PortableExecutable; using System.Xml; -using Internal.IL; -using Internal.TypeSystem; -using Internal.TypeSystem.Ecma; - -using ILCompiler.Metadata; +using ILCompiler.Dataflow; using ILCompiler.DependencyAnalysis; using ILCompiler.DependencyAnalysisFramework; +using ILCompiler.Metadata; using ILLink.Shared; -using FlowAnnotations = ILLink.Shared.TrimAnalysis.FlowAnnotations; -using DependencyList = ILCompiler.DependencyAnalysisFramework.DependencyNodeCore.DependencyList; +using Internal.IL; +using Internal.TypeSystem; +using Internal.TypeSystem.Ecma; + using CombinedDependencyList = System.Collections.Generic.List.CombinedDependencyListEntry>; -using Debug = System.Diagnostics.Debug; +using CustomAttributeHandle = System.Reflection.Metadata.CustomAttributeHandle; using CustomAttributeValue = System.Reflection.Metadata.CustomAttributeValue; +using Debug = System.Diagnostics.Debug; +using DependencyList = ILCompiler.DependencyAnalysisFramework.DependencyNodeCore.DependencyList; using EcmaModule = Internal.TypeSystem.Ecma.EcmaModule; using EcmaType = Internal.TypeSystem.Ecma.EcmaType; -using CustomAttributeHandle = System.Reflection.Metadata.CustomAttributeHandle; -using CustomAttributeTypeProvider = Internal.TypeSystem.Ecma.CustomAttributeTypeProvider; -using MetadataExtensions = Internal.TypeSystem.Ecma.MetadataExtensions; -using ILCompiler.Dataflow; +using FlowAnnotations = ILLink.Shared.TrimAnalysis.FlowAnnotations; namespace ILCompiler { @@ -499,8 +497,12 @@ protected override void GetDependenciesDueToMethodCodePresenceInternal(ref Depen { if (FlowAnnotations.RequiresDataflowAnalysis(method)) { - dependencies = dependencies ?? new DependencyList(); - dependencies.Add(factory.DataflowAnalyzedMethod(methodIL.GetMethodILDefinition()), "Method has annotated parameters"); + MethodIL methodILDefinition = methodIL.GetMethodILDefinition(); + if (!CompilerGeneratedState.IsNestedFunctionOrStateMachineMember(methodILDefinition.OwningMethod)) + { + dependencies = dependencies ?? new DependencyList(); + dependencies.Add(factory.DataflowAnalyzedMethod(methodILDefinition), "Method has annotated parameters"); + } } if ((method.HasInstantiation && !method.IsCanonicalMethod(CanonicalFormKind.Any))) @@ -616,8 +618,12 @@ public override void GetDependenciesDueToAccess(ref DependencyList dependencies, bool scanReflection = (_generationOptions & UsageBasedMetadataGenerationOptions.ReflectionILScanning) != 0; if (scanReflection && Dataflow.ReflectionMethodBodyScanner.RequiresReflectionMethodBodyScannerForAccess(FlowAnnotations, writtenField)) { - dependencies = dependencies ?? new DependencyList(); - dependencies.Add(factory.DataflowAnalyzedMethod(methodIL.GetMethodILDefinition()), "Access to interesting field"); + MethodIL methodILDefinition = methodIL.GetMethodILDefinition(); + if (!CompilerGeneratedState.IsNestedFunctionOrStateMachineMember(methodILDefinition.OwningMethod)) + { + dependencies = dependencies ?? new DependencyList(); + dependencies.Add(factory.DataflowAnalyzedMethod(methodILDefinition), "Access to interesting field"); + } } string reason = "Use of a field"; @@ -680,8 +686,12 @@ public override void GetDependenciesDueToAccess(ref DependencyList dependencies, bool scanReflection = (_generationOptions & UsageBasedMetadataGenerationOptions.ReflectionILScanning) != 0; if (scanReflection && Dataflow.ReflectionMethodBodyScanner.RequiresReflectionMethodBodyScannerForCallSite(FlowAnnotations, calledMethod)) { - dependencies = dependencies ?? new DependencyList(); - dependencies.Add(factory.DataflowAnalyzedMethod(methodIL.GetMethodILDefinition()), "Call to interesting method"); + MethodIL methodILDefinition = methodIL.GetMethodILDefinition(); + if (!CompilerGeneratedState.IsNestedFunctionOrStateMachineMember(methodILDefinition.OwningMethod)) + { + dependencies = dependencies ?? new DependencyList(); + dependencies.Add(factory.DataflowAnalyzedMethod(methodILDefinition), "Call to interesting method"); + } } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj index 5c09727d4bd20..5dcc449c55db4 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj @@ -20,6 +20,11 @@ + + + all + runtime + @@ -332,6 +337,8 @@ + + diff --git a/src/coreclr/tools/aot/ILLink.Shared/DataFlow/DefaultValueDictionary.cs b/src/coreclr/tools/aot/ILLink.Shared/DataFlow/DefaultValueDictionary.cs index 679f70ca621ea..1d8221463ace3 100644 --- a/src/coreclr/tools/aot/ILLink.Shared/DataFlow/DefaultValueDictionary.cs +++ b/src/coreclr/tools/aot/ILLink.Shared/DataFlow/DefaultValueDictionary.cs @@ -27,6 +27,8 @@ public struct DefaultValueDictionary : IEquatable (Dictionary, DefaultValue) = (null, defaultValue); + private DefaultValueDictionary (TValue defaultValue, Dictionary dictionary) => (Dictionary, DefaultValue) = (dictionary, defaultValue); + public DefaultValueDictionary (DefaultValueDictionary other) { Dictionary = other.Dictionary == null ? null : new Dictionary (other.Dictionary); @@ -65,6 +67,10 @@ public bool Equals (DefaultValueDictionary other) return true; } + public override bool Equals (object? obj) => obj is DefaultValueDictionary other && Equals (other); + + public int Count => Dictionary?.Count ?? 0; + public IEnumerator> GetEnumerator () { return Dictionary?.GetEnumerator () ?? Enumerable.Empty> ().GetEnumerator (); @@ -84,5 +90,24 @@ public override string ToString () sb.Append (Environment.NewLine).Append ("}"); return sb.ToString (); } + + public DefaultValueDictionary Clone () + { + var defaultValue = DefaultValue is IDeepCopyValue copyDefaultValue ? copyDefaultValue.DeepCopy () : DefaultValue; + if (Dictionary == null) + return new DefaultValueDictionary (defaultValue); + + var dict = new Dictionary (); + foreach (var kvp in Dictionary) { + var key = kvp.Key; + var value = kvp.Value; + dict.Add (key, value is IDeepCopyValue copyValue ? copyValue.DeepCopy () : value); + } + return new DefaultValueDictionary (defaultValue, dict); + } + + // Prevent warning CS0659 https://docs.microsoft.com/en-us/dotnet/csharp/misc/cs0659. + // This type should never be used as a dictionary key. + public override int GetHashCode () => throw new NotImplementedException (); } } diff --git a/src/coreclr/tools/aot/ILLink.Shared/DiagnosticId.cs b/src/coreclr/tools/aot/ILLink.Shared/DiagnosticId.cs index 8534c4960d81e..c7f5028f2d9d7 100644 --- a/src/coreclr/tools/aot/ILLink.Shared/DiagnosticId.cs +++ b/src/coreclr/tools/aot/ILLink.Shared/DiagnosticId.cs @@ -57,6 +57,7 @@ public enum DiagnosticId CouldNotResolveCustomAttributeTypeValue = 1044, UnexpectedAttributeArgumentType = 1045, InvalidMetadataOption = 1046, + InvalidDependenciesFileFormat = 1047, // Linker diagnostic ids. TypeHasNoFieldsToPreserve = 2001, diff --git a/src/coreclr/tools/aot/ILLink.Shared/ILLink.Shared.projitems b/src/coreclr/tools/aot/ILLink.Shared/ILLink.Shared.projitems index 66dd85465ebfd..8b16e377892a2 100644 --- a/src/coreclr/tools/aot/ILLink.Shared/ILLink.Shared.projitems +++ b/src/coreclr/tools/aot/ILLink.Shared/ILLink.Shared.projitems @@ -23,4 +23,4 @@ Designer - \ No newline at end of file + diff --git a/src/coreclr/tools/aot/ILLink.Shared/SharedStrings.resx b/src/coreclr/tools/aot/ILLink.Shared/SharedStrings.resx index 29d51ff1e3930..5f85cc3f450f2 100644 --- a/src/coreclr/tools/aot/ILLink.Shared/SharedStrings.resx +++ b/src/coreclr/tools/aot/ILLink.Shared/SharedStrings.resx @@ -38,7 +38,7 @@ The mimetype is used for serialized objects, and tells the ResXResourceReader how to depersist the object. This is currently not - extensible. For a given mimetype the value must be set accordingly: + extensible. For a given mimetype the value must be set accordingly Note - application/x-microsoft.net.object.binary.base64 is the format that the ResXResourceWriter will generate, however the reader can @@ -1185,4 +1185,10 @@ Unrecognized internal attribute '{0}' - + + The only allowed file types are Xml or Dgml. + + + Unrecognized dependencies file type. + + \ No newline at end of file diff --git a/src/coreclr/tools/aot/ILLink.Shared/TrimAnalysis/IntrinsicId.cs b/src/coreclr/tools/aot/ILLink.Shared/TrimAnalysis/IntrinsicId.cs index 7a2a40b068ac9..bd9d8df893a2c 100644 --- a/src/coreclr/tools/aot/ILLink.Shared/TrimAnalysis/IntrinsicId.cs +++ b/src/coreclr/tools/aot/ILLink.Shared/TrimAnalysis/IntrinsicId.cs @@ -6,6 +6,7 @@ namespace ILLink.Shared.TrimAnalysis { + [StaticCs.Closed] enum IntrinsicId { None = 0, diff --git a/src/coreclr/tools/aot/ILLink.Shared/TypeSystemProxy/WellKnownType.cs b/src/coreclr/tools/aot/ILLink.Shared/TypeSystemProxy/WellKnownType.cs index 3503f7b021cd7..999f8f4dc95f2 100644 --- a/src/coreclr/tools/aot/ILLink.Shared/TypeSystemProxy/WellKnownType.cs +++ b/src/coreclr/tools/aot/ILLink.Shared/TypeSystemProxy/WellKnownType.cs @@ -1,11 +1,14 @@ // Copyright (c) .NET Foundation and contributors. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. +using StaticCs; + // This is needed due to NativeAOT which doesn't enable nullable globally yet #nullable enable namespace ILLink.Shared.TypeSystemProxy { + [Closed] public enum WellKnownType { System_String, @@ -35,7 +38,6 @@ public static (string Namespace, string Name) GetNamespaceAndName (this WellKnow WellKnownType.System_NotSupportedException => ("System", "NotSupportedException"), WellKnownType.System_Runtime_CompilerServices_DisablePrivateReflectionAttribute => ("System.Runtime.CompilerServices", "DisablePrivateReflectionAttribute"), WellKnownType.System_Void => ("System", "Void"), - _ => throw new System.NotImplementedException() }; } public static string GetNamespace (this WellKnownType type) => GetNamespaceAndName (type).Namespace; From af6d00bb8296193c985f45f3f3fe3550cc2f668f Mon Sep 17 00:00:00 2001 From: vitek-karas <10670590+vitek-karas@users.noreply.github.com> Date: Thu, 14 Jul 2022 12:47:26 -0700 Subject: [PATCH 16/18] Mostly PR feedback and small cleanup --- .../DependencyGraphTests.cs | 2 +- .../Compiler/Dataflow/AttributeDataFlow.cs | 1 + .../Dataflow/CompilerGeneratedCallGraph.cs | 16 ++- .../Dataflow/CompilerGeneratedState.cs | 128 ++++++++---------- .../Compiler/Dataflow/EcmaExtensions.cs | 9 +- .../Compiler/Dataflow/FlowAnnotations.cs | 11 +- .../ReferenceSource/AttributeDataFlow.cs | 5 +- .../CompilerGeneratedCallGraph.cs | 10 +- .../ReferenceSource/CompilerGeneratedState.cs | 12 +- .../ReferenceSource/InterproceduralState.cs | 7 +- .../ReferenceSource/MethodBodyScanner.cs | 6 +- .../ReflectionMethodBodyScanner.cs | 4 +- .../ILCompiler.Compiler/Compiler/Logger.cs | 43 +++--- .../Compiler/UsageBasedMetadataManager.cs | 31 ++--- src/coreclr/tools/aot/ILCompiler/Program.cs | 5 +- .../TestCasesRunner/ILCompilerDriver.cs | 5 +- 16 files changed, 152 insertions(+), 143 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/DependencyGraphTests.cs b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/DependencyGraphTests.cs index 0a98bf807d31c..5d7a4ab2eac3c 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/DependencyGraphTests.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/DependencyGraphTests.cs @@ -67,7 +67,7 @@ public void TestDependencyGraphInvariants(EcmaMethod method) CompilationModuleGroup compilationGroup = new SingleFileCompilationModuleGroup(); NativeAotILProvider ilProvider = new NativeAotILProvider(); - CompilerGeneratedState compilerGeneratedState = new CompilerGeneratedState(ilProvider); + CompilerGeneratedState compilerGeneratedState = new CompilerGeneratedState(ilProvider, Logger.Null); UsageBasedMetadataManager metadataManager = new UsageBasedMetadataManager(compilationGroup, context, new FullyBlockedMetadataBlockingPolicy(), new FullyBlockedManifestResourceBlockingPolicy(), diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/AttributeDataFlow.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/AttributeDataFlow.cs index 57ec8ad24b082..afdc85ab9e06e 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/AttributeDataFlow.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/AttributeDataFlow.cs @@ -111,6 +111,7 @@ MultiValue GetValueForCustomAttributeArgument(object? argument) { TypeDesc td => new SystemTypeValue(td), string str => new KnownStringValue(str), + null => NullValue.Instance, // We shouldn't have gotten a None annotation from flow annotations since only string/Type can have annotations _ => throw new InvalidOperationException() }; diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedCallGraph.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedCallGraph.cs index 4512f0829204a..643e0a6a4bf13 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedCallGraph.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedCallGraph.cs @@ -13,34 +13,40 @@ namespace ILCompiler.Dataflow { sealed class CompilerGeneratedCallGraph { - readonly Dictionary> callGraph; + readonly Dictionary> _callGraph; - public CompilerGeneratedCallGraph() => callGraph = new Dictionary>(); + public CompilerGeneratedCallGraph() => _callGraph = new Dictionary>(); void TrackCallInternal(TypeSystemEntity fromMember, TypeSystemEntity toMember) { - if (!callGraph.TryGetValue(fromMember, out HashSet? toMembers)) + if (!_callGraph.TryGetValue(fromMember, out HashSet? toMembers)) { toMembers = new HashSet(); - callGraph.Add(fromMember, toMembers); + _callGraph.Add(fromMember, toMembers); } toMembers.Add(toMember); } public void TrackCall(MethodDesc fromMethod, MethodDesc toMethod) { + Debug.Assert(fromMethod.IsTypicalMethodDefinition); + Debug.Assert(toMethod.IsTypicalMethodDefinition); Debug.Assert(CompilerGeneratedNames.IsLambdaOrLocalFunction(toMethod.Name)); TrackCallInternal(fromMethod, toMethod); } public void TrackCall(MethodDesc fromMethod, DefType toType) { + Debug.Assert(fromMethod.IsTypicalMethodDefinition); + Debug.Assert(toType.IsTypeDefinition); Debug.Assert(CompilerGeneratedNames.IsStateMachineType(toType.Name)); TrackCallInternal(fromMethod, toType); } public void TrackCall(DefType fromType, MethodDesc toMethod) { + Debug.Assert(fromType.IsTypeDefinition); + Debug.Assert(toMethod.IsTypicalMethodDefinition); Debug.Assert(CompilerGeneratedNames.IsStateMachineType(fromType.Name)); Debug.Assert(CompilerGeneratedNames.IsLambdaOrLocalFunction(toMethod.Name)); TrackCallInternal(fromType, toMethod); @@ -54,7 +60,7 @@ public IEnumerable GetReachableMembers(MethodDesc start) queue.Enqueue(start); while (queue.TryDequeue(out TypeSystemEntity? method)) { - if (!callGraph.TryGetValue(method, out HashSet? callees)) + if (!_callGraph.TryGetValue(method, out HashSet? callees)) continue; foreach (var callee in callees) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedState.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedState.cs index 32bdcbe66b4bd..19e3e7ecb622b 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedState.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedState.cs @@ -29,23 +29,17 @@ readonly record struct TypeArgumentInfo( readonly TypeCacheHashtable _typeCacheHashtable; - public Logger? Logger + public CompilerGeneratedState(ILProvider ilProvider, Logger logger) { - get => _typeCacheHashtable.Logger; - set => _typeCacheHashtable.Logger = value; - } - - public CompilerGeneratedState(ILProvider ilProvider) - { - _typeCacheHashtable = new TypeCacheHashtable(ilProvider); + _typeCacheHashtable = new TypeCacheHashtable(ilProvider, logger); } class TypeCacheHashtable : LockFreeReaderHashtable { private ILProvider _ilProvider; - internal Logger? Logger; + private Logger? _logger; - public TypeCacheHashtable(ILProvider ilProvider) => _ilProvider = ilProvider; + public TypeCacheHashtable(ILProvider ilProvider, Logger logger) => (_ilProvider, _logger) = (ilProvider, logger); protected override bool CompareKeyToValue(MetadataType key, TypeCache value) => key == value.Type; protected override bool CompareValueToValue(TypeCache value1, TypeCache value2) => value1.Type == value2.Type; @@ -53,7 +47,7 @@ class TypeCacheHashtable : LockFreeReaderHashtable protected override int GetValueHashCode(TypeCache value) => value.Type.GetHashCode(); protected override TypeCache CreateValueFromKey(MetadataType key) - => new TypeCache(key, Logger, _ilProvider); + => new TypeCache(key, _logger, _ilProvider); } class TypeCache @@ -125,7 +119,7 @@ void ProcessMethod(MethodDesc method) case ILOpcode.call: case ILOpcode.callvirt: case ILOpcode.newobj: - lambdaOrLocalFunction = methodBody.GetObject(reader.ReadILToken()) as MethodDesc; + lambdaOrLocalFunction = methodBody.GetObject(reader.ReadILToken(), NotFoundBehavior.ReturnNull) as MethodDesc; break; default: @@ -145,7 +139,7 @@ lambdaOrLocalFunction.OwningType is MetadataType generatedType && method.OwningType != generatedType && CompilerGeneratedNames.IsLambdaDisplayClass(generatedType.Name)) { - generatedType = (MetadataType)generatedType.GetTypeDefinition(); + Debug.Assert(generatedType.IsTypeDefinition); // fill in null for now, attribute providers will be filled in later _generatedTypeToTypeArgumentInfo ??= new Dictionary(); @@ -163,10 +157,7 @@ lambdaOrLocalFunction.OwningType is MetadataType generatedType && if (isStateMachineMember) { - if (method.OwningType is MetadataType owningType) - { - callGraph.TrackCall(owningType, lambdaOrLocalFunction); - } + callGraph.TrackCall((MetadataType)method.OwningType, lambdaOrLocalFunction); } else { @@ -193,7 +184,7 @@ lambdaOrLocalFunction.OwningType is MetadataType generatedType && // Already warned above if multiple methods map to the same type // Fill in null for argument providers now, the real providers will be filled in later _generatedTypeToTypeArgumentInfo ??= new Dictionary(); - _ = _generatedTypeToTypeArgumentInfo.TryAdd(stateMachineType, new TypeArgumentInfo(method, null)); + _generatedTypeToTypeArgumentInfo[stateMachineType] = new TypeArgumentInfo(method, null); } } @@ -308,59 +299,57 @@ void MapGeneratedTypeTypeParameters(MetadataType generatedType) } var method = typeInfo.CreatingMethod; var body = ilProvider.GetMethodIL(method); - if (body is not null) + var typeArgs = new GenericParameterDesc?[generatedType.Instantiation.Length]; + var typeRef = ScanForInit(generatedType, body); + if (typeRef is null) { - var typeArgs = new GenericParameterDesc?[generatedType.Instantiation.Length]; - var typeRef = ScanForInit(generatedType, body); - if (typeRef is null) - { - return; - } + return; + } - // The typeRef is going to be a generic instantiation with signature variables - // We need to figure out the actual generic parameters which were used to create these - // so instantiate the typeRef in the context of the method body where it is created - TypeDesc instantiatedType = typeRef.InstantiateSignature(method.OwningType.Instantiation, method.Instantiation); - for (int i = 0; i < instantiatedType.Instantiation.Length; i++) + // The typeRef is going to be a generic instantiation with signature variables + // We need to figure out the actual generic parameters which were used to create these + // so instantiate the typeRef in the context of the method body where it is created + TypeDesc instantiatedType = typeRef.InstantiateSignature(method.OwningType.Instantiation, method.Instantiation); + for (int i = 0; i < instantiatedType.Instantiation.Length; i++) + { + var typeArg = instantiatedType.Instantiation[i]; + // Start with the existing parameters, in case we can't find the mapped one + GenericParameterDesc? userAttrs = generatedType.Instantiation[i] as GenericParameterDesc; + // The type parameters of the state machine types are alpha renames of the + // the method parameters, so the type ref should always be a GenericParameter. However, + // in the case of nesting, there may be multiple renames, so if the parameter is a method + // we know we're done, but if it's another state machine, we have to keep looking to find + // the original owner of that state machine. + if (typeArg is GenericParameterDesc { Kind: { } kind } param) { - var typeArg = instantiatedType.Instantiation[i]; - // Start with the existing parameters, in case we can't find the mapped one - GenericParameterDesc? userAttrs = generatedType.Instantiation[i] as GenericParameterDesc; - // The type parameters of the state machine types are alpha renames of the - // the method parameters, so the type ref should always be a GenericParameter. However, - // in the case of nesting, there may be multiple renames, so if the parameter is a method - // we know we're done, but if it's another state machine, we have to keep looking to find - // the original owner of that state machine. - if (typeArg is GenericParameterDesc { Kind: { } kind } param) + if (kind == GenericParameterKind.Method) { - if (kind == GenericParameterKind.Method) + userAttrs = param; + } + else + { + // Must be a type ref + if (method.OwningType is not MetadataType owningType || !CompilerGeneratedNames.IsGeneratedType(owningType.Name)) { userAttrs = param; } else { - // Must be a type ref - if (method.OwningType is not MetadataType owningType || !CompilerGeneratedNames.IsGeneratedType(owningType.Name)) - { - userAttrs = param; - } - else + owningType = (MetadataType)owningType.GetTypeDefinition(); + MapGeneratedTypeTypeParameters(owningType); + if (_generatedTypeToTypeArgumentInfo.TryGetValue(owningType, out var owningInfo) && + owningInfo.OriginalAttributes is { } owningAttrs) { - owningType = (MetadataType)owningType.GetTypeDefinition(); - MapGeneratedTypeTypeParameters(owningType); - if (_generatedTypeToTypeArgumentInfo.TryGetValue(owningType, out var owningInfo) && - owningInfo.OriginalAttributes is { } owningAttrs) - { - userAttrs = owningAttrs[param.Index]; - } + userAttrs = owningAttrs[param.Index]; } } } - - typeArgs[i] = userAttrs; } - _generatedTypeToTypeArgumentInfo[generatedType] = typeInfo with { OriginalAttributes = typeArgs }; + + typeArgs[i] = userAttrs; } + + _generatedTypeToTypeArgumentInfo[generatedType] = typeInfo with { OriginalAttributes = typeArgs }; } MetadataType? ScanForInit(MetadataType stateMachineType, MethodIL body) @@ -433,12 +422,9 @@ public bool TryGetOwningMethodForCompilerGeneratedType(MetadataType compilerGene } } - static IEnumerable GetCompilerGeneratedNestedTypes(TypeDesc type) + static IEnumerable GetCompilerGeneratedNestedTypes(MetadataType type) { - if (type is not MetadataType metadataType) - yield break; - - foreach (var nestedType in metadataType.GetNestedTypes()) + foreach (var nestedType in type.GetNestedTypes()) { if (!CompilerGeneratedNames.IsGeneratedMemberName(nestedType.Name)) continue; @@ -491,21 +477,23 @@ public static bool TryGetStateMachineType(MethodDesc method, [NotNullWhen(true)] return stateMachineType != null; } - private TypeCache? PopulateCacheForType(MetadataType? type) + private TypeCache? GetCompilerGeneratedStateForType(MetadataType type) { - Debug.Assert(type == type?.GetTypeDefinition()); + Debug.Assert(type.IsTypeDefinition); + + MetadataType? userType = type; // Look in the declaring type if this is a compiler-generated type (state machine or display class). // State machines can be emitted into display classes, so we may also need to go one more level up. // To avoid depending on implementation details, we go up until we see a non-compiler-generated type. // This is the counterpart to GetCompilerGeneratedNestedTypes. - while (type != null && CompilerGeneratedNames.IsGeneratedMemberName(type.Name)) - type = type.ContainingType as MetadataType; + while (userType != null && CompilerGeneratedNames.IsGeneratedMemberName(userType.Name)) + userType = userType.ContainingType as MetadataType; - if (type is null) + if (userType is null) return null; - return _typeCacheHashtable.GetOrCreateValue(type); + return _typeCacheHashtable.GetOrCreateValue(userType); } static TypeDesc? GetFirstConstructorArgumentAsType(CustomAttributeValue attribute) @@ -527,7 +515,7 @@ public bool TryGetCompilerGeneratedCalleesForUserMethod(MethodDesc method, [NotN if (method.OwningType is not MetadataType owningType) return false; - var typeCache = PopulateCacheForType(owningType); + var typeCache = GetCompilerGeneratedStateForType(owningType); if (typeCache is null) return false; @@ -543,7 +531,7 @@ public bool TryGetCompilerGeneratedCalleesForUserMethod(MethodDesc method, [NotN MetadataType generatedType = (MetadataType)type.GetTypeDefinition(); Debug.Assert(CompilerGeneratedNames.IsGeneratedType(generatedType.Name)); - var typeCache = PopulateCacheForType(generatedType); + var typeCache = GetCompilerGeneratedStateForType(generatedType); if (typeCache is null) return null; @@ -569,7 +557,7 @@ public bool TryGetOwningMethodForCompilerGeneratedMember(TypeSystemEntity source // sourceType is a state machine type, or the type containing a lambda or local function. // Search all methods to find the one which points to the type as its // state machine implementation. - var typeCache = PopulateCacheForType(sourceType); + var typeCache = GetCompilerGeneratedStateForType(sourceType); if (typeCache is null) return false; diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/EcmaExtensions.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/EcmaExtensions.cs index e992e2e7cc980..5d91895e35711 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/EcmaExtensions.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/EcmaExtensions.cs @@ -1,6 +1,8 @@ // 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 Internal.TypeSystem; using Internal.TypeSystem.Ecma; @@ -78,14 +80,15 @@ public static ReferenceKind ParameterReferenceKind(this MethodDesc method, int i index--; } + if (!method.Signature[index].IsByRef) + return ReferenceKind.None; + // Parameter metadata index 0 is for return parameter foreach (var parameterMetadata in method.GetParameterMetadata()) { if (parameterMetadata.Index != index + 1) continue; - if (!method.Signature[index].IsByRef) - return ReferenceKind.None; if (parameterMetadata.In) return ReferenceKind.In; if (parameterMetadata.Out) @@ -108,7 +111,7 @@ public static TypeDesc GetOwningType(this TypeSystemEntity entity) MetadataType type => type.ContainingType, PropertyPseudoDesc property => property.OwningType, EventPseudoDesc @event => @event.OwningType, - _ => null + _ => throw new NotImplementedException("Unexpected type system entity") }; } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/FlowAnnotations.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/FlowAnnotations.cs index 82922c9795008..4ed23ceb4bbfc 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/FlowAnnotations.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/FlowAnnotations.cs @@ -6,18 +6,17 @@ using System.Diagnostics.CodeAnalysis; using System.Reflection.Metadata; -using Internal.IL; -using Internal.TypeSystem; -using Internal.TypeSystem.Ecma; using ILCompiler; using ILCompiler.Dataflow; -using ILLink.Shared; +using ILLink.Shared.DataFlow; using ILLink.Shared.TypeSystemProxy; +using Internal.IL; +using Internal.TypeSystem; +using Internal.TypeSystem.Ecma; + using Debug = System.Diagnostics.Debug; using WellKnownType = Internal.TypeSystem.WellKnownType; -using System.Reflection; -using ILLink.Shared.DataFlow; #nullable enable diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/AttributeDataFlow.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/AttributeDataFlow.cs index 357803a768d13..3ed11d392487a 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/AttributeDataFlow.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/AttributeDataFlow.cs @@ -50,6 +50,9 @@ public void ProcessAttributeDataflow (FieldDefinition field, CustomAttributeArgu MultiValue GetValueForCustomAttributeArgument (CustomAttributeArgument argument) { if (argument.Type.Name == "Type") { + if (argument.Value is null) + return NullValue.Instance; + TypeDefinition? referencedType = ((TypeReference) argument.Value).ResolveToTypeDefinition (_context); return referencedType == null ? UnknownValue.Instance @@ -57,7 +60,7 @@ MultiValue GetValueForCustomAttributeArgument (CustomAttributeArgument argument) } if (argument.Type.MetadataType == MetadataType.String) - return new KnownStringValue ((string) argument.Value); + return argument.Value is null ? NullValue.Instance : new KnownStringValue ((string) argument.Value); // We shouldn't have gotten a non-null annotation for this from GetParameterAnnotation throw new InvalidOperationException (); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/CompilerGeneratedCallGraph.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/CompilerGeneratedCallGraph.cs index 551ff51e5c434..1cfbc8770e62f 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/CompilerGeneratedCallGraph.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/CompilerGeneratedCallGraph.cs @@ -9,15 +9,15 @@ namespace Mono.Linker.Dataflow { sealed class CompilerGeneratedCallGraph { - readonly Dictionary> callGraph; + readonly Dictionary> _callGraph; - public CompilerGeneratedCallGraph () => callGraph = new Dictionary> (); + public CompilerGeneratedCallGraph () => _callGraph = new Dictionary> (); void TrackCallInternal (IMemberDefinition fromMember, IMemberDefinition toMember) { - if (!callGraph.TryGetValue (fromMember, out HashSet? toMembers)) { + if (!_callGraph.TryGetValue (fromMember, out HashSet? toMembers)) { toMembers = new HashSet (); - callGraph.Add (fromMember, toMembers); + _callGraph.Add (fromMember, toMembers); } toMembers.Add (toMember); } @@ -48,7 +48,7 @@ public IEnumerable GetReachableMembers (MethodDefinition star visited.Add (start); queue.Enqueue (start); while (queue.TryDequeue (out IMemberDefinition? method)) { - if (!callGraph.TryGetValue (method, out HashSet? callees)) + if (!_callGraph.TryGetValue (method, out HashSet? callees)) continue; foreach (var callee in callees) { diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/CompilerGeneratedState.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/CompilerGeneratedState.cs index 2802e6703df11..c99968f1d0cc3 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/CompilerGeneratedState.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/CompilerGeneratedState.cs @@ -102,7 +102,7 @@ public static bool TryGetStateMachineType (MethodDefinition method, [NotNullWhen /// up and find the nearest containing user type. Returns the nearest user type, /// or null if none was found. /// - TypeDefinition? PopulateCacheForType (TypeDefinition type) + TypeDefinition? GetCompilerGeneratedStateForType (TypeDefinition type) { // Look in the declaring type if this is a compiler-generated type (state machine or display class). // State machines can be emitted into display classes, so we may also need to go one more level up. @@ -183,7 +183,7 @@ lambdaOrLocalFunction.DeclaringType is var generatedType && } // Already warned above if multiple methods map to the same type // Fill in null for argument providers now, the real providers will be filled in later - _ = _generatedTypeToTypeArgumentInfo.TryAdd (stateMachineType, new TypeArgumentInfo (method, null)); + _generatedTypeToTypeArgumentInfo[stateMachineType] = new TypeArgumentInfo (method, null); } } @@ -292,6 +292,7 @@ void MapGeneratedTypeTypeParameters (TypeDefinition generatedType) if (typeRef is null) { return; } + for (int i = 0; i < typeRef.GenericArguments.Count; i++) { var typeArg = typeRef.GenericArguments[i]; // Start with the existing parameters, in case we can't find the mapped one @@ -321,6 +322,7 @@ void MapGeneratedTypeTypeParameters (TypeDefinition generatedType) typeArgs[i] = userAttrs; } + _generatedTypeToTypeArgumentInfo[generatedType] = typeInfo with { OriginalAttributes = typeArgs }; } } @@ -356,7 +358,7 @@ public bool TryGetCompilerGeneratedCalleesForUserMethod (MethodDefinition method if (IsNestedFunctionOrStateMachineMember (method)) return false; - var typeToCache = PopulateCacheForType (method.DeclaringType); + var typeToCache = GetCompilerGeneratedStateForType (method.DeclaringType); if (typeToCache is null) return false; @@ -371,7 +373,7 @@ public bool TryGetCompilerGeneratedCalleesForUserMethod (MethodDefinition method { Debug.Assert (CompilerGeneratedNames.IsGeneratedType (generatedType.Name)); - var typeToCache = PopulateCacheForType (generatedType); + var typeToCache = GetCompilerGeneratedStateForType (generatedType); if (typeToCache is null) return null; @@ -407,7 +409,7 @@ public bool TryGetOwningMethodForCompilerGeneratedMember (IMemberDefinition sour // sourceType is a state machine type, or the type containing a lambda or local function. // Search all methods to find the one which points to the type as its // state machine implementation. - var typeToCache = PopulateCacheForType (sourceType); + var typeToCache = GetCompilerGeneratedStateForType (sourceType); if (typeToCache is null) return false; diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/InterproceduralState.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/InterproceduralState.cs index 0c5eec7f78c67..09bc3be226300 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/InterproceduralState.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/InterproceduralState.cs @@ -39,6 +39,11 @@ public void TrackMethod (MethodDefinition method) if (method.Body is not MethodBody methodBody) return; + TrackMethod (methodBody); + } + + public void TrackMethod (MethodBody methodBody) + { // Work around the fact that ValueSet is readonly var methodsList = new List (MethodBodies); methodsList.Add (new MethodBodyValue (methodBody)); @@ -46,7 +51,7 @@ public void TrackMethod (MethodDefinition method) // For state machine methods, also scan the state machine members. // Simplification: assume that all generated methods of the state machine type are // reached at the point where the state machine method is reached. - if (CompilerGeneratedState.TryGetStateMachineType (method, out TypeDefinition? stateMachineType)) { + if (CompilerGeneratedState.TryGetStateMachineType (methodBody.Method, out TypeDefinition? stateMachineType)) { foreach (var stateMachineMethod in stateMachineType.Methods) { Debug.Assert (!CompilerGeneratedNames.IsLambdaOrLocalFunction (stateMachineMethod.Name)); if (stateMachineMethod.Body is MethodBody stateMachineMethodBody) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/MethodBodyScanner.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/MethodBodyScanner.cs index 9bb41e9e35fc1..1c056663ebc54 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/MethodBodyScanner.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/MethodBodyScanner.cs @@ -226,15 +226,17 @@ protected static void StoreMethodLocalValue ( // Scans the method as well as any nested functions (local functions or lambdas) and state machines // reachable from it. - public virtual void InterproceduralScan (MethodDefinition startingMethod) + public virtual void InterproceduralScan (MethodBody startingMethodBody) { + MethodDefinition startingMethod = startingMethodBody.Method; + // Note that the default value of a hoisted local will be MultiValueLattice.Top, not UnknownValue.Instance. // This ensures that there are no warnings for the "unassigned state" of a parameter. // Definite assignment should ensure that there is no way for this to be an analysis hole. var interproceduralState = InterproceduralStateLattice.Top; var oldInterproceduralState = interproceduralState.Clone (); - interproceduralState.TrackMethod (startingMethod); + interproceduralState.TrackMethod (startingMethodBody); while (!interproceduralState.Equals (oldInterproceduralState)) { oldInterproceduralState = interproceduralState.Clone (); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ReflectionMethodBodyScanner.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ReflectionMethodBodyScanner.cs index 6b07bc53cdd61..02a512806bfe6 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ReflectionMethodBodyScanner.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/ReferenceSource/ReflectionMethodBodyScanner.cs @@ -61,9 +61,9 @@ public ReflectionMethodBodyScanner (LinkContext context, MarkStep parent, Messag TrimAnalysisPatterns = new TrimAnalysisPatternStore (MultiValueLattice, context); } - public override void InterproceduralScan (MethodDefinition method) + public override void InterproceduralScan (MethodBody methodBody) { - base.InterproceduralScan (method); + base.InterproceduralScan (methodBody); var reflectionMarker = new ReflectionMarker (_context, _markStep, enabled: true); TrimAnalysisPatterns.MarkAndProduceDiagnostics (reflectionMarker, _markStep); diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logger.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logger.cs index acd726e314329..0482731b0432b 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logger.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Logger.cs @@ -16,6 +16,7 @@ using ILSequencePoint = Internal.IL.ILSequencePoint; using MethodIL = Internal.IL.MethodIL; +using Internal.IL; namespace ILCompiler { @@ -38,7 +39,7 @@ public class Logger public Logger( ILogWriter writer, - CompilerGeneratedState compilerGeneratedState, + ILProvider ilProvider, bool isVerbose, IEnumerable suppressedWarnings, bool singleWarn, @@ -46,7 +47,7 @@ public Logger( IEnumerable singleWarnDisabledModules) { _logWriter = writer; - _compilerGeneratedState = compilerGeneratedState; + _compilerGeneratedState = ilProvider == null ? null : new CompilerGeneratedState(ilProvider, this); IsVerbose = isVerbose; _suppressedWarnings = new HashSet(suppressedWarnings); _isSingleWarn = singleWarn; @@ -54,18 +55,18 @@ public Logger( _singleWarnDisabledAssemblies = new HashSet(singleWarnDisabledModules, StringComparer.OrdinalIgnoreCase); } - public Logger(TextWriter writer, CompilerGeneratedState compilerGeneratedState, bool isVerbose, IEnumerable suppressedWarnings, bool singleWarn, IEnumerable singleWarnEnabledModules, IEnumerable singleWarnDisabledModules) - : this(new TextLogWriter(writer), compilerGeneratedState, isVerbose, suppressedWarnings, singleWarn, singleWarnEnabledModules, singleWarnDisabledModules) + public Logger(TextWriter writer, ILProvider ilProvider, bool isVerbose, IEnumerable suppressedWarnings, bool singleWarn, IEnumerable singleWarnEnabledModules, IEnumerable singleWarnDisabledModules) + : this(new TextLogWriter(writer), ilProvider, isVerbose, suppressedWarnings, singleWarn, singleWarnEnabledModules, singleWarnDisabledModules) { } - public Logger(ILogWriter writer, CompilerGeneratedState compilerGeneratedState, bool isVerbose) - : this(writer, compilerGeneratedState, isVerbose, Array.Empty(), singleWarn: false, Array.Empty(), Array.Empty()) + public Logger(ILogWriter writer, ILProvider ilProvider, bool isVerbose) + : this(writer, ilProvider, isVerbose, Array.Empty(), singleWarn: false, Array.Empty(), Array.Empty()) { } - public Logger(TextWriter writer, CompilerGeneratedState compilerGeneratedState, bool isVerbose) - : this(new TextLogWriter(writer), compilerGeneratedState, isVerbose) + public Logger(TextWriter writer, ILProvider ilProvider, bool isVerbose) + : this(new TextLogWriter(writer), ilProvider, isVerbose) { } @@ -157,12 +158,15 @@ internal bool IsWarningSuppressed(int code, MessageOrigin origin) return true; MethodDesc owningMethod; - while (_compilerGeneratedState?.TryGetOwningMethodForCompilerGeneratedMember(member, out owningMethod) == true) + if (_compilerGeneratedState != null) { - Debug.Assert(owningMethod != member); - if (IsSuppressed(code, owningMethod)) - return true; - member = owningMethod; + while (_compilerGeneratedState?.TryGetOwningMethodForCompilerGeneratedMember(member, out owningMethod) == true) + { + Debug.Assert(owningMethod != member); + if (IsSuppressed(code, owningMethod)) + return true; + member = owningMethod; + } } return false; @@ -294,12 +298,15 @@ internal bool ShouldSuppressAnalysisWarningsForRequires(TypeSystemEntity originM return true; MethodDesc owningMethod; - while (_compilerGeneratedState.TryGetOwningMethodForCompilerGeneratedMember(originMember, out owningMethod)) + if (_compilerGeneratedState != null) { - Debug.Assert(owningMethod != originMember); - if (owningMethod.IsInRequiresScope(requiresAttribute)) - return true; - originMember = owningMethod; + while (_compilerGeneratedState.TryGetOwningMethodForCompilerGeneratedMember(originMember, out owningMethod)) + { + Debug.Assert(owningMethod != originMember); + if (owningMethod.IsInRequiresScope(requiresAttribute)) + return true; + originMember = owningMethod; + } } return false; diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/UsageBasedMetadataManager.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/UsageBasedMetadataManager.cs index 602255af6a4bb..11650f3e34057 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/UsageBasedMetadataManager.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/UsageBasedMetadataManager.cs @@ -497,12 +497,7 @@ protected override void GetDependenciesDueToMethodCodePresenceInternal(ref Depen { if (FlowAnnotations.RequiresDataflowAnalysis(method)) { - MethodIL methodILDefinition = methodIL.GetMethodILDefinition(); - if (!CompilerGeneratedState.IsNestedFunctionOrStateMachineMember(methodILDefinition.OwningMethod)) - { - dependencies = dependencies ?? new DependencyList(); - dependencies.Add(factory.DataflowAnalyzedMethod(methodILDefinition), "Method has annotated parameters"); - } + AddDataflowDependencyIfNeeded(ref dependencies, factory, methodIL, "Method has annotated parameters"); } if ((method.HasInstantiation && !method.IsCanonicalMethod(CanonicalFormKind.Any))) @@ -618,12 +613,7 @@ public override void GetDependenciesDueToAccess(ref DependencyList dependencies, bool scanReflection = (_generationOptions & UsageBasedMetadataGenerationOptions.ReflectionILScanning) != 0; if (scanReflection && Dataflow.ReflectionMethodBodyScanner.RequiresReflectionMethodBodyScannerForAccess(FlowAnnotations, writtenField)) { - MethodIL methodILDefinition = methodIL.GetMethodILDefinition(); - if (!CompilerGeneratedState.IsNestedFunctionOrStateMachineMember(methodILDefinition.OwningMethod)) - { - dependencies = dependencies ?? new DependencyList(); - dependencies.Add(factory.DataflowAnalyzedMethod(methodILDefinition), "Access to interesting field"); - } + AddDataflowDependencyIfNeeded(ref dependencies, factory, methodIL, "Access to interesting field"); } string reason = "Use of a field"; @@ -686,12 +676,7 @@ public override void GetDependenciesDueToAccess(ref DependencyList dependencies, bool scanReflection = (_generationOptions & UsageBasedMetadataGenerationOptions.ReflectionILScanning) != 0; if (scanReflection && Dataflow.ReflectionMethodBodyScanner.RequiresReflectionMethodBodyScannerForCallSite(FlowAnnotations, calledMethod)) { - MethodIL methodILDefinition = methodIL.GetMethodILDefinition(); - if (!CompilerGeneratedState.IsNestedFunctionOrStateMachineMember(methodILDefinition.OwningMethod)) - { - dependencies = dependencies ?? new DependencyList(); - dependencies.Add(factory.DataflowAnalyzedMethod(methodILDefinition), "Call to interesting method"); - } + AddDataflowDependencyIfNeeded(ref dependencies, factory, methodIL, "Call to interesting method"); } } @@ -904,6 +889,16 @@ public MetadataManager ToAnalysisBasedMetadataManager() reflectableFields.ToEnumerable(), _customAttributesWithMetadata, rootedCctorContexts); } + private void AddDataflowDependencyIfNeeded(ref DependencyList dependencies, NodeFactory factory, MethodIL methodIL, string reason) + { + MethodIL methodILDefinition = methodIL.GetMethodILDefinition(); + if (!CompilerGeneratedState.IsNestedFunctionOrStateMachineMember(methodILDefinition.OwningMethod)) + { + dependencies = dependencies ?? new DependencyList(); + dependencies.Add(factory.DataflowAnalyzedMethod(methodILDefinition), reason); + } + } + private struct ReflectableEntityBuilder { private Dictionary _dictionary; diff --git a/src/coreclr/tools/aot/ILCompiler/Program.cs b/src/coreclr/tools/aot/ILCompiler/Program.cs index 1df78af1e32d6..de93f9e65ed55 100644 --- a/src/coreclr/tools/aot/ILCompiler/Program.cs +++ b/src/coreclr/tools/aot/ILCompiler/Program.cs @@ -743,9 +743,8 @@ static string ILLinkify(string rootedAssembly) } ilProvider = new FeatureSwitchManager(ilProvider, featureSwitches); - CompilerGeneratedState compilerGeneratedState = new CompilerGeneratedState(ilProvider); - var logger = new Logger(Console.Out, compilerGeneratedState, _isVerbose, ProcessWarningCodes(_suppressedWarnings), _singleWarn, _singleWarnEnabledAssemblies, _singleWarnDisabledAssemblies); - compilerGeneratedState.Logger = logger; + var logger = new Logger(Console.Out, ilProvider, _isVerbose, ProcessWarningCodes(_suppressedWarnings), _singleWarn, _singleWarnEnabledAssemblies, _singleWarnDisabledAssemblies); + CompilerGeneratedState compilerGeneratedState = new CompilerGeneratedState(ilProvider, logger); var stackTracePolicy = _emitStackTraceData ? (StackTraceEmissionPolicy)new EcmaMethodStackTraceEmissionPolicy() : new NoStackTraceEmissionPolicy(); diff --git a/src/coreclr/tools/aot/Mono.Linker.Tests/TestCasesRunner/ILCompilerDriver.cs b/src/coreclr/tools/aot/Mono.Linker.Tests/TestCasesRunner/ILCompilerDriver.cs index 6a8457e433675..b970f5ac587f8 100644 --- a/src/coreclr/tools/aot/Mono.Linker.Tests/TestCasesRunner/ILCompilerDriver.cs +++ b/src/coreclr/tools/aot/Mono.Linker.Tests/TestCasesRunner/ILCompilerDriver.cs @@ -57,9 +57,8 @@ public void Trim (ILCompilerOptions options, ILogWriter logWriter) ilProvider = new FeatureSwitchManager (ilProvider, options.FeatureSwitches); - CompilerGeneratedState compilerGeneratedState = new CompilerGeneratedState (ilProvider); - - Logger logger = new Logger (logWriter, compilerGeneratedState, isVerbose: true); + Logger logger = new Logger (logWriter, ilProvider, isVerbose: true); + CompilerGeneratedState compilerGeneratedState = new CompilerGeneratedState (ilProvider, logger); UsageBasedMetadataManager metadataManager = new UsageBasedMetadataManager ( compilationGroup, From d39a7f78c24124322c24020ad35cbbbb9497ee2a Mon Sep 17 00:00:00 2001 From: vitek-karas <10670590+vitek-karas@users.noreply.github.com> Date: Fri, 15 Jul 2022 12:23:29 -0700 Subject: [PATCH 17/18] Ignore single-file warnings in AOT tests --- eng/testing/tests.singlefile.targets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eng/testing/tests.singlefile.targets b/eng/testing/tests.singlefile.targets index f95e1957de028..18b342db71ac0 100644 --- a/eng/testing/tests.singlefile.targets +++ b/eng/testing/tests.singlefile.targets @@ -26,7 +26,7 @@ $(CoreCLRILCompilerDir)netstandard/ILCompiler.Build.Tasks.dll $(CoreCLRAotSdkDir) $(NetCoreAppCurrentTestHostSharedFrameworkPath) - $(NoWarn);IL3050;IL3051;IL3052;IL3055;IL1005 + $(NoWarn);IL3050;IL3051;IL3052;IL3055;IL1005;IL3002 false true From 739a5c16e4d2c20b26c9022712e5019a5f9f9721 Mon Sep 17 00:00:00 2001 From: vitek-karas <10670590+vitek-karas@users.noreply.github.com> Date: Mon, 18 Jul 2022 12:31:54 -0700 Subject: [PATCH 18/18] Fixes a bug when compiler generated code would not run DataFlow. If the user method has no intersting code in it, but it has a lambda (for example) which does have an interesting code which needs data flow - we now need to add the user method for data flow analysis (since the user method and all of its compiler generated methods will be analyzed together). This also adds a few smoke tests covering these scenarios. --- .../Dataflow/CompilerGeneratedState.cs | 24 +++++++++ .../Compiler/UsageBasedMetadataManager.cs | 20 ++++--- .../nativeaot/SmokeTests/Dataflow/Dataflow.cs | 52 +++++++++++++++++++ 3 files changed, 89 insertions(+), 7 deletions(-) diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedState.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedState.cs index 19e3e7ecb622b..9cf7ee200ffde 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedState.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedState.cs @@ -573,5 +573,29 @@ public bool TryGetOwningMethodForCompilerGeneratedMember(TypeSystemEntity source return false; } + + public bool TryGetUserMethodForCompilerGeneratedMember(TypeSystemEntity sourceMember, [NotNullWhen(true)] out MethodDesc? userMethod) + { + userMethod = null; + if (sourceMember == null) + return false; + + TypeSystemEntity member = sourceMember; + MethodDesc? userMethodCandidate = null; + while (TryGetOwningMethodForCompilerGeneratedMember(member, out userMethodCandidate)) + { + Debug.Assert(userMethodCandidate != member); + member = userMethodCandidate; + userMethod = userMethodCandidate; + } + + if (userMethod != null) + { + Debug.Assert(!CompilerGeneratedState.IsNestedFunctionOrStateMachineMember(userMethod)); + return true; + } + + return false; + } } } diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/UsageBasedMetadataManager.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/UsageBasedMetadataManager.cs index 7b55b7ae1582b..e366e80708f4b 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/UsageBasedMetadataManager.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/UsageBasedMetadataManager.cs @@ -512,7 +512,7 @@ protected override void GetDependenciesDueToMethodCodePresenceInternal(ref Depen { if (FlowAnnotations.RequiresDataflowAnalysis(method)) { - AddDataflowDependencyIfNeeded(ref dependencies, factory, methodIL, "Method has annotated parameters"); + AddDataflowDependency(ref dependencies, factory, methodIL, "Method has annotated parameters"); } if ((method.HasInstantiation && !method.IsCanonicalMethod(CanonicalFormKind.Any))) @@ -628,7 +628,7 @@ public override void GetDependenciesDueToAccess(ref DependencyList dependencies, bool scanReflection = (_generationOptions & UsageBasedMetadataGenerationOptions.ReflectionILScanning) != 0; if (scanReflection && Dataflow.ReflectionMethodBodyScanner.RequiresReflectionMethodBodyScannerForAccess(FlowAnnotations, writtenField)) { - AddDataflowDependencyIfNeeded(ref dependencies, factory, methodIL, "Access to interesting field"); + AddDataflowDependency(ref dependencies, factory, methodIL, "Access to interesting field"); } string reason = "Use of a field"; @@ -691,7 +691,7 @@ public override void GetDependenciesDueToAccess(ref DependencyList dependencies, bool scanReflection = (_generationOptions & UsageBasedMetadataGenerationOptions.ReflectionILScanning) != 0; if (scanReflection && Dataflow.ReflectionMethodBodyScanner.RequiresReflectionMethodBodyScannerForCallSite(FlowAnnotations, calledMethod)) { - AddDataflowDependencyIfNeeded(ref dependencies, factory, methodIL, "Call to interesting method"); + AddDataflowDependency(ref dependencies, factory, methodIL, "Call to interesting method"); } } @@ -904,14 +904,20 @@ public MetadataManager ToAnalysisBasedMetadataManager() reflectableFields.ToEnumerable(), _customAttributesWithMetadata, rootedCctorContexts); } - private void AddDataflowDependencyIfNeeded(ref DependencyList dependencies, NodeFactory factory, MethodIL methodIL, string reason) + private void AddDataflowDependency(ref DependencyList dependencies, NodeFactory factory, MethodIL methodIL, string reason) { MethodIL methodILDefinition = methodIL.GetMethodILDefinition(); - if (!CompilerGeneratedState.IsNestedFunctionOrStateMachineMember(methodILDefinition.OwningMethod)) + if (FlowAnnotations.CompilerGeneratedState.TryGetUserMethodForCompilerGeneratedMember(methodILDefinition.OwningMethod, out var userMethod)) { - dependencies = dependencies ?? new DependencyList(); - dependencies.Add(factory.DataflowAnalyzedMethod(methodILDefinition), reason); + Debug.Assert(userMethod != methodILDefinition.OwningMethod); + + // It is possible that this will try to add the DatadlowAnalyzedMethod node multiple times for the same method + // but that's OK since the node factory will only add actually one node. + methodILDefinition = FlowAnnotations.ILProvider.GetMethodIL(userMethod); } + + dependencies = dependencies ?? new DependencyList(); + dependencies.Add(factory.DataflowAnalyzedMethod(methodILDefinition), reason); } private struct ReflectableEntityBuilder diff --git a/src/tests/nativeaot/SmokeTests/Dataflow/Dataflow.cs b/src/tests/nativeaot/SmokeTests/Dataflow/Dataflow.cs index 0ebfcb06546f2..5f27838b5cd07 100644 --- a/src/tests/nativeaot/SmokeTests/Dataflow/Dataflow.cs +++ b/src/tests/nativeaot/SmokeTests/Dataflow/Dataflow.cs @@ -27,6 +27,7 @@ static int Main() TestDynamicDependencyWithGenerics.Run(); TestObjectGetTypeDataflow.Run(); TestMarshalIntrinsics.Run(); + TestCompilerGeneratedCode.Run(); return 100; } @@ -606,6 +607,57 @@ static void SanityTest() } } } + + class TestCompilerGeneratedCode + { + private static void ReflectionInLambda() + { + var func = () => { + Type helpersType = Type.GetType(nameof(Helpers)); + Assert.NotNull(helpersType); + }; + + func(); + } + + private static void ReflectionInLocalFunction() + { + func(); + + void func() + { + Type helpersType = Type.GetType(nameof(Helpers)); + Assert.NotNull(helpersType); + }; + } + + private static async void ReflectionInAsync() + { + await System.Threading.Tasks.Task.Delay(100); + Type helpersType = Type.GetType(nameof(Helpers)); + Assert.NotNull(helpersType); + } + + private static async void ReflectionInLambdaAsync() + { + await System.Threading.Tasks.Task.Delay(100); + + var func = () => { + Type helpersType = Type.GetType(nameof(Helpers)); + Assert.NotNull(helpersType); + }; + + func(); + } + + public static void Run() + { + ReflectionInLambda(); + ReflectionInLocalFunction(); + ReflectionInAsync(); + ReflectionInLambdaAsync(); + } + } } static class Assert