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 def7db6138c99c..24707c75f8c293 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedState.cs +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/Dataflow/CompilerGeneratedState.cs @@ -29,17 +29,19 @@ private readonly record struct TypeArgumentInfo( private readonly TypeCacheHashtable _typeCacheHashtable; + private readonly Logger _logger; + public CompilerGeneratedState(ILProvider ilProvider, Logger logger) { - _typeCacheHashtable = new TypeCacheHashtable(ilProvider, logger); + _typeCacheHashtable = new TypeCacheHashtable(ilProvider); + _logger = logger; } private sealed class TypeCacheHashtable : LockFreeReaderHashtable { private ILProvider _ilProvider; - private Logger? _logger; - public TypeCacheHashtable(ILProvider ilProvider, Logger logger) => (_ilProvider, _logger) = (ilProvider, logger); + public TypeCacheHashtable(ILProvider ilProvider) => _ilProvider = ilProvider; protected override bool CompareKeyToValue(MetadataType key, TypeCache value) => key == value.Type; protected override bool CompareValueToValue(TypeCache value1, TypeCache value2) => value1.Type == value2.Type; @@ -47,7 +49,27 @@ private sealed class TypeCacheHashtable : LockFreeReaderHashtable value.Type.GetHashCode(); protected override TypeCache CreateValueFromKey(MetadataType key) - => new TypeCache(key, _logger, _ilProvider); + => new TypeCache(key, _ilProvider); + + public TypeCache GetOrCreateValue(MetadataType key, out bool created) + { + TypeCache existingValue; + created = false; + if (TryGetValue(key, out existingValue)) + return existingValue; + + var newValue = CreateValueFromKey(key); + if (TryAdd(newValue)) + { + created = true; + return newValue; + } + + if (!TryGetValue(key, out existingValue)) + throw new InvalidOperationException(); + + return existingValue; + } } private sealed class TypeCache @@ -64,6 +86,18 @@ private sealed class TypeCache // or null if the type has no methods with compiler-generated members. private Dictionary>? _compilerGeneratedMembers; + // Stores a list of warnings to be emitted at the end of the cache construction + private List<(MessageOrigin, DiagnosticId, string[])>? _warnings; + + internal void LogWarnings(Logger? logger) + { + if (_warnings == null || logger == null) + return; + + foreach (var (origin, id, messageArgs) in _warnings) + logger.LogWarning(origin, id, messageArgs); + } + /// /// Walks the type and its descendents to find Roslyn-compiler generated /// code and gather information to map it back to original user code. If @@ -71,7 +105,7 @@ private sealed class TypeCache /// 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) + internal TypeCache(MetadataType type, ILProvider ilProvider) { Debug.Assert(type == type.GetTypeDefinition()); Debug.Assert(!CompilerGeneratedNames.IsStateMachineOrDisplayClass(type.Name)); @@ -82,6 +116,15 @@ internal TypeCache(MetadataType type, Logger? logger, ILProvider ilProvider) var userDefinedMethods = new HashSet(); var generatedTypeToTypeArgs = new Dictionary(); + // We delay actually logging the warnings until the compiler-generated type info is + // populated for this type, because the type info is needed to determine whether a warning + // is suppressed. + void AddWarning(MessageOrigin origin, DiagnosticId id, params string[] messageArgs) + { + _warnings ??= new List<(MessageOrigin, DiagnosticId, string[])>(); + _warnings.Add((origin, id, messageArgs)); + } + void ProcessMethod(MethodDesc method) { Debug.Assert(method == method.GetTypicalMethodDefinition()); @@ -139,7 +182,7 @@ referencedMethod.OwningType is MetadataType generatedType && if (!generatedTypeToTypeArgs.TryAdd(generatedType, new TypeArgumentInfo(method, null))) { var alreadyAssociatedMethod = generatedTypeToTypeArgs[generatedType].CreatingMethod; - logger?.LogWarning(new MessageOrigin(method), DiagnosticId.MethodsAreAssociatedWithUserMethod, method.GetDisplayName(), alreadyAssociatedMethod.GetDisplayName(), generatedType.GetDisplayName()); + AddWarning(new MessageOrigin(method), DiagnosticId.MethodsAreAssociatedWithUserMethod, method.GetDisplayName(), alreadyAssociatedMethod.GetDisplayName(), generatedType.GetDisplayName()); } continue; } @@ -207,7 +250,7 @@ referencedMethod.OwningType is MetadataType generatedType && if (!_compilerGeneratedTypeToUserCodeMethod.TryAdd(stateMachineType, method)) { var alreadyAssociatedMethod = _compilerGeneratedTypeToUserCodeMethod[stateMachineType]; - logger?.LogWarning(new MessageOrigin(method), DiagnosticId.MethodsAreAssociatedWithStateMachine, method.GetDisplayName(), alreadyAssociatedMethod.GetDisplayName(), stateMachineType.GetDisplayName()); + AddWarning(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 @@ -263,7 +306,7 @@ referencedMethod.OwningType is MetadataType generatedType && if (!_compilerGeneratedMethodToUserCodeMethod.TryAdd(nestedFunction, userDefinedMethod)) { var alreadyAssociatedMethod = _compilerGeneratedMethodToUserCodeMethod[nestedFunction]; - logger?.LogWarning(new MessageOrigin(userDefinedMethod), DiagnosticId.MethodsAreAssociatedWithUserMethod, userDefinedMethod.GetDisplayName(), alreadyAssociatedMethod.GetDisplayName(), nestedFunction.GetDisplayName()); + AddWarning(new MessageOrigin(userDefinedMethod), DiagnosticId.MethodsAreAssociatedWithUserMethod, userDefinedMethod.GetDisplayName(), alreadyAssociatedMethod.GetDisplayName(), nestedFunction.GetDisplayName()); } break; case MetadataType stateMachineType: @@ -295,7 +338,7 @@ referencedMethod.OwningType is MetadataType generatedType && { var method = info.CreatingMethod; var alreadyAssociatedMethod = _generatedTypeToTypeArgumentInfo[generatedType].CreatingMethod; - logger?.LogWarning(new MessageOrigin(method), DiagnosticId.MethodsAreAssociatedWithUserMethod, method.GetDisplayName(), alreadyAssociatedMethod.GetDisplayName(), generatedType.GetDisplayName()); + AddWarning(new MessageOrigin(method), DiagnosticId.MethodsAreAssociatedWithUserMethod, method.GetDisplayName(), alreadyAssociatedMethod.GetDisplayName(), generatedType.GetDisplayName()); } } } @@ -575,7 +618,10 @@ public static bool TryGetStateMachineType(MethodDesc method, [NotNullWhen(true)] if (userType is null) return null; - return _typeCacheHashtable.GetOrCreateValue(userType); + var typeCache = _typeCacheHashtable.GetOrCreateValue(userType, out bool created); + if (created) + typeCache.LogWarnings(_logger); + return typeCache; } private static TypeDesc? GetFirstConstructorArgumentAsType(CustomAttributeValue attribute) diff --git a/src/tools/illink/src/linker/Linker.Dataflow/CompilerGeneratedState.cs b/src/tools/illink/src/linker/Linker.Dataflow/CompilerGeneratedState.cs index d246e4a6c54e1d..8f6ca7356dcf4f 100644 --- a/src/tools/illink/src/linker/Linker.Dataflow/CompilerGeneratedState.cs +++ b/src/tools/illink/src/linker/Linker.Dataflow/CompilerGeneratedState.cs @@ -129,6 +129,17 @@ public static bool TryGetStateMachineType (MethodDefinition method, [NotNullWhen var userDefinedMethods = new HashSet (); var generatedTypeToTypeArgs = new Dictionary (); + List<(MessageOrigin, DiagnosticId, string[])>? _warnings = null; + + // We delay actually logging the warnings until the compiler-generated type info is + // populated for this type, because the type info is needed to determine whether a warning + // is suppressed. + void AddWarning (MessageOrigin origin, DiagnosticId id, params string[] messageArgs) + { + _warnings ??= new List<(MessageOrigin, DiagnosticId, string[])> (); + _warnings.Add ((origin, id, messageArgs)); + } + void ProcessMethod (MethodDefinition method) { bool isStateMachineMember = CompilerGeneratedNames.IsStateMachineType (method.DeclaringType.Name); @@ -163,7 +174,7 @@ referencedMethod.DeclaringType is var generatedType && // fill in null for now, attribute providers will be filled in later if (!generatedTypeToTypeArgs.TryAdd (generatedType, new TypeArgumentInfo (method, null))) { var alreadyAssociatedMethod = generatedTypeToTypeArgs[generatedType].CreatingMethod; - _context.LogWarning (new MessageOrigin (method), DiagnosticId.MethodsAreAssociatedWithUserMethod, method.GetDisplayName (), alreadyAssociatedMethod.GetDisplayName (), generatedType.GetDisplayName ()); + AddWarning (new MessageOrigin (method), DiagnosticId.MethodsAreAssociatedWithUserMethod, method.GetDisplayName (), alreadyAssociatedMethod.GetDisplayName (), generatedType.GetDisplayName ()); } continue; } @@ -214,7 +225,7 @@ referencedMethod.DeclaringType is var generatedType && if (!_compilerGeneratedTypeToUserCodeMethod.TryAdd (stateMachineType, method)) { var alreadyAssociatedMethod = _compilerGeneratedTypeToUserCodeMethod[stateMachineType]; - _context.LogWarning (new MessageOrigin (method), DiagnosticId.MethodsAreAssociatedWithStateMachine, method.GetDisplayName (), alreadyAssociatedMethod.GetDisplayName (), stateMachineType.GetDisplayName ()); + AddWarning (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 @@ -265,7 +276,7 @@ referencedMethod.DeclaringType is var generatedType && // 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 ()); + AddWarning (new MessageOrigin (userDefinedMethod), DiagnosticId.MethodsAreAssociatedWithUserMethod, userDefinedMethod.GetDisplayName (), alreadyAssociatedMethod.GetDisplayName (), nestedFunction.GetDisplayName ()); } break; case TypeDefinition stateMachineType: @@ -292,12 +303,19 @@ referencedMethod.DeclaringType is var generatedType && if (!_generatedTypeToTypeArgumentInfo.TryAdd (generatedType, info)) { var method = info.CreatingMethod; var alreadyAssociatedMethod = _generatedTypeToTypeArgumentInfo[generatedType].CreatingMethod; - _context.LogWarning (new MessageOrigin (method), DiagnosticId.MethodsAreAssociatedWithUserMethod, method.GetDisplayName (), alreadyAssociatedMethod.GetDisplayName (), generatedType.GetDisplayName ()); + AddWarning (new MessageOrigin (method), DiagnosticId.MethodsAreAssociatedWithUserMethod, method.GetDisplayName (), alreadyAssociatedMethod.GetDisplayName (), generatedType.GetDisplayName ()); } } } _cachedTypeToCompilerGeneratedMembers.Add (type, compilerGeneratedCallees); + + if (_warnings != null) { + foreach (var (origin, id, messageArgs) in _warnings) { + _context.LogWarning (origin, id, messageArgs); + } + } + return type; /// diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Warnings/Dependencies/MultipleMethodsUseSameAsyncStateMachine.il b/src/tools/illink/test/Mono.Linker.Tests.Cases/Warnings/Dependencies/MultipleMethodsUseSameAsyncStateMachine.il new file mode 100644 index 00000000000000..087e3e42890396 --- /dev/null +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Warnings/Dependencies/MultipleMethodsUseSameAsyncStateMachine.il @@ -0,0 +1,137 @@ +.assembly extern System.Runtime +{ + .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A ) +} + +.assembly 'library' +{ + .hash algorithm 0x00008004 + .ver 1:0:0:0 +} + +.namespace Mono.Linker.Tests.Cases.Warnings.Dependencies +{ + .class public auto ansi beforefieldinit MultipleMethodsUseSameAsyncStateMachine + extends [System.Runtime]System.Object + { + // Nested Types + .class nested public sequential ansi sealed beforefieldinit 'd' + extends [System.Runtime]System.ValueType + implements [System.Runtime]System.Runtime.CompilerServices.IAsyncStateMachine + { + // Fields + .field public static int32 'field' + + // Methods + .method public final hidebysig newslot virtual + instance void MoveNext () cil managed + { + // Method begins at RVA 0x207a + // Code size 9 (0x9) + .maxstack 8 + + IL_0000: nop + IL_0001: ldarg.0 + IL_0002: ldc.i4.1 + IL_0003: stfld int32 Mono.Linker.Tests.Cases.Warnings.Dependencies.MultipleMethodsUseSameAsyncStateMachine/'d'::'field' + IL_0008: ret + } // end of method 'd'::MoveNext + + .method private final hidebysig newslot virtual + instance void System.Runtime.CompilerServices.IAsyncStateMachine.SetStateMachine ( + class [System.Runtime]System.Runtime.CompilerServices.IAsyncStateMachine stateMachine + ) cil managed + { + .custom instance void [System.Runtime]System.Runtime.CompilerServices.NullableContextAttribute::.ctor(uint8) = ( + 01 00 01 00 00 + ) + .override method instance void [System.Runtime]System.Runtime.CompilerServices.IAsyncStateMachine::SetStateMachine(class [System.Runtime]System.Runtime.CompilerServices.IAsyncStateMachine) + // Method begins at RVA 0x2050 + // Code size 2 (0x2) + .maxstack 8 + + IL_0000: nop + IL_0001: ret + } // end of method 'd'::System.Runtime.CompilerServices.IAsyncStateMachine.SetStateMachine + + } // end of class 'd' + + + // Methods + .method public hidebysig static + void M () cil managed + { + .custom instance void [System.Runtime]System.Runtime.CompilerServices.AsyncStateMachineAttribute::.ctor(class [System.Runtime]System.Type) = ( + 01 00 65 4D 6F 6E 6F 2E 4C 69 6E 6B 65 72 2E 54 + 65 73 74 73 2E 43 61 73 65 73 2E 57 61 72 6E 69 + 6E 67 73 2E 44 65 70 65 6E 64 65 6E 63 69 65 73 + 2E 4D 75 6C 74 69 70 6C 65 4D 65 74 68 6F 64 73 + 55 73 65 53 61 6D 65 41 73 79 6E 63 53 74 61 74 + 65 4D 61 63 68 69 6E 65 2B 3C 53 74 61 74 65 4D + 61 63 68 69 6E 65 3E 64 00 00 + ) + .maxstack 8 + + IL_0000: call void Mono.Linker.Tests.Cases.Warnings.Dependencies.MultipleMethodsUseSameAsyncStateMachine::RUC(); + IL_0005: call void Mono.Linker.Tests.Cases.Warnings.Dependencies.MultipleMethodsUseSameAsyncStateMachine::'g__LocalFunction'() + IL_000a: ret + } // end of method MultipleMethodsUseSameAsyncStateMachine::M + + .method private hidebysig static + void RUC () cil managed + { + .custom instance void [System.Runtime]System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute::.ctor(string) = ( + 01 00 03 52 55 43 00 00 + ) + // Method begins at RVA 0x205c + // Code size 1 (0x1) + .maxstack 8 + + IL_0000: ret + } // end of method MultipleMethodsUseSameAsyncStateMachine::RUC + + .method public hidebysig static + void 'g__LocalFunction' () cil managed + { + .custom instance void [System.Runtime]System.Runtime.CompilerServices.AsyncStateMachineAttribute::.ctor(class [System.Runtime]System.Type) = ( + 01 00 65 4D 6F 6E 6F 2E 4C 69 6E 6B 65 72 2E 54 + 65 73 74 73 2E 43 61 73 65 73 2E 57 61 72 6E 69 + 6E 67 73 2E 44 65 70 65 6E 64 65 6E 63 69 65 73 + 2E 4D 75 6C 74 69 70 6C 65 4D 65 74 68 6F 64 73 + 55 73 65 53 61 6D 65 41 73 79 6E 63 53 74 61 74 + 65 4D 61 63 68 69 6E 65 2B 3C 53 74 61 74 65 4D + 61 63 68 69 6E 65 3E 64 00 00 + ) + .maxstack 2 + .locals init ( + [0] valuetype Mono.Linker.Tests.Cases.Warnings.Dependencies.MultipleMethodsUseSameAsyncStateMachine/'d' s + ) + + IL_0000: nop + IL_0001: ldloca.s 0 + IL_0003: initobj Mono.Linker.Tests.Cases.Warnings.Dependencies.MultipleMethodsUseSameAsyncStateMachine/'d' + IL_0009: ldloca.s 0 + IL_000b: ldc.i4.0 + IL_000c: stfld int32 Mono.Linker.Tests.Cases.Warnings.Dependencies.MultipleMethodsUseSameAsyncStateMachine/'d'::'field' + IL_0011: ldloca.s 0 + IL_0013: call instance void Mono.Linker.Tests.Cases.Warnings.Dependencies.MultipleMethodsUseSameAsyncStateMachine/'d'::MoveNext() + IL_0018: nop + IL_0019: ret + } // end of method MultipleMethodsUseSameAsyncStateMachine::'g__LocalFunction' + + .method public hidebysig specialname rtspecialname + instance void .ctor () cil managed + { + // Method begins at RVA 0x2053 + // Code size 8 (0x8) + .maxstack 8 + + IL_0000: ldarg.0 + IL_0001: call instance void [System.Runtime]System.Object::.ctor() + IL_0006: nop + IL_0007: ret + } // end of method MultipleMethodsUseSameAsyncStateMachine::.ctor + + } // end of class Mono.Linker.Tests.Cases.Warnings.Dependencies.MultipleMethodsUseSameAsyncStateMachine + +} diff --git a/src/tools/illink/test/Mono.Linker.Tests.Cases/Warnings/MultipleMethodsUseSameAsyncStateMachine.cs b/src/tools/illink/test/Mono.Linker.Tests.Cases/Warnings/MultipleMethodsUseSameAsyncStateMachine.cs new file mode 100644 index 00000000000000..a8767d6cfba3de --- /dev/null +++ b/src/tools/illink/test/Mono.Linker.Tests.Cases/Warnings/MultipleMethodsUseSameAsyncStateMachine.cs @@ -0,0 +1,27 @@ +// 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.Linq; +using System.Text; +using System.Threading.Tasks; +using Mono.Linker.Tests.Cases.Expectations.Assertions; +using Mono.Linker.Tests.Cases.Expectations.Metadata; + +namespace Mono.Linker.Tests.Cases.Warnings +{ + [Define ("IL_ASSEMBLY_AVAILABLE")] + [SetupCompileBefore ("library.dll", new[] { "Dependencies/MultipleMethodsUseSameAsyncStateMachine.il" })] + [ExpectedNoWarnings] + [LogContains ("IL2107.*g__LocalFunction().*M().*both associated with state machine type.*d", regexMatch: true)] + class MultipleMethodsUseSameAsyncStateMachine + { + public static void Main () + { +#if IL_ASSEMBLY_AVAILABLE + Dependencies.MultipleMethodsUseSameAsyncStateMachine.M(); +#endif + } + } +}