Skip to content

Commit

Permalink
Integrate changes from NativeAOT branch
Browse files Browse the repository at this point in the history
  • Loading branch information
MichalStrehovsky committed Sep 27, 2021
1 parent c1767af commit c7901ed
Show file tree
Hide file tree
Showing 10 changed files with 755 additions and 341 deletions.
73 changes: 70 additions & 3 deletions src/coreclr/tools/Common/Compiler/Logger.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,30 @@ public class Logger
{
private readonly HashSet<int> _suppressedWarnings;

private readonly bool _isSingleWarn;
private readonly HashSet<string> _singleWarnEnabledAssemblies;
private readonly HashSet<string> _singleWarnDisabledAssemblies;
private readonly HashSet<string> _trimWarnedAssemblies = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
private readonly HashSet<string> _aotWarnedAssemblies = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

public static Logger Null = new Logger(TextWriter.Null, false);

public TextWriter Writer { get; }

public bool IsVerbose { get; }

public Logger(TextWriter writer, bool isVerbose, IEnumerable<int> suppressedWarnings)
public Logger(TextWriter writer, bool isVerbose, IEnumerable<int> suppressedWarnings, bool singleWarn, IEnumerable<string> singleWarnEnabledModules, IEnumerable<string> singleWarnDisabledModules)
{
Writer = TextWriter.Synchronized(writer);
IsVerbose = isVerbose;
_suppressedWarnings = new HashSet<int>(suppressedWarnings);
_isSingleWarn = singleWarn;
_singleWarnEnabledAssemblies = new HashSet<string>(singleWarnEnabledModules, StringComparer.OrdinalIgnoreCase);
_singleWarnDisabledAssemblies = new HashSet<string>(singleWarnDisabledModules, StringComparer.OrdinalIgnoreCase);
}

public Logger(TextWriter writer, bool isVerbose)
: this(writer, isVerbose, Array.Empty<int>())
: this(writer, isVerbose, Array.Empty<int>(), singleWarn: false, Array.Empty<string>(), Array.Empty<string>())
{
}

Expand Down Expand Up @@ -71,10 +80,18 @@ public void LogWarning(string text, int code, MethodIL origin, int ilOffset, str
}
}

MessageOrigin messageOrigin = new MessageOrigin(origin.OwningMethod, document, lineNumber, null);
MethodDesc warnedMethod = CompilerGeneratedState.GetUserDefinedMethodForCompilerGeneratedMember(origin.OwningMethod) ?? origin.OwningMethod;

MessageOrigin messageOrigin = new MessageOrigin(warnedMethod, document, lineNumber, null);
LogWarning(text, code, messageOrigin, subcategory);
}

public void LogWarning(string text, int code, string origin, string subcategory = MessageSubCategory.None)
{
MessageOrigin _origin = new MessageOrigin(origin);
LogWarning(text, code, _origin, subcategory);
}

internal bool IsWarningSuppressed(int code, MessageOrigin origin)
{
if (_suppressedWarnings.Contains(code))
Expand All @@ -87,6 +104,8 @@ internal bool IsWarningSuppressed(int code, MessageOrigin origin)

if (origin.MemberDefinition is MethodDesc method)
{
method = CompilerGeneratedState.GetUserDefinedMethodForCompilerGeneratedMember(method) ?? method;

var ecmaMethod = method.GetTypicalMethodDefinition() as EcmaMethod;
suppressions = ecmaMethod?.GetDecodedCustomAttributes("System.Diagnostics.CodeAnalysis", "UnconditionalSuppressMessageAttribute");
}
Expand Down Expand Up @@ -120,6 +139,54 @@ internal bool IsWarningAsError(int code)
// TODO: warnaserror
return false;
}

internal bool IsSingleWarn(ModuleDesc owningModule, string messageSubcategory)
{
string assemblyName = owningModule.Assembly.GetName().Name;

bool result = false;

if ((_isSingleWarn || _singleWarnEnabledAssemblies.Contains(assemblyName))
&& !_singleWarnDisabledAssemblies.Contains(assemblyName))
{
result = true;

if (messageSubcategory == MessageSubCategory.TrimAnalysis)
{
lock (_trimWarnedAssemblies)
{
if (_trimWarnedAssemblies.Add(assemblyName))
{
LogWarning($"Assembly '{assemblyName}' produced trim warnings. For more information see https://aka.ms/dotnet-illink/libraries", 2104, GetModuleFileName(owningModule));
}
}
}
else if (messageSubcategory == MessageSubCategory.AotAnalysis)
{
lock (_aotWarnedAssemblies)
{
if (_aotWarnedAssemblies.Add(assemblyName))
{
LogWarning($"Assembly '{assemblyName}' produced AOT analysis warnings.", 9702, GetModuleFileName(owningModule));
}
}
}
}

return result;
}

private static string GetModuleFileName(ModuleDesc module)
{
string assemblyName = module.Assembly.GetName().Name;
var context = (CompilerTypeSystemContext)module.Context;
if (context.ReferenceFilePaths.TryGetValue(assemblyName, out string result)
|| context.InputFilePaths.TryGetValue(assemblyName, out result))
{
return result;
}
return assemblyName;
}
}

public static class MessageSubCategory
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

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;
}
}
}
71 changes: 71 additions & 0 deletions src/coreclr/tools/Common/Compiler/Logging/MessageContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
using System;
using System.Text;
using Internal.TypeSystem;
using Internal.TypeSystem.Ecma;

using Debug = System.Diagnostics.Debug;

namespace ILCompiler.Logging
{
Expand Down Expand Up @@ -87,12 +90,80 @@ internal static MessageContainer CreateErrorMessage(string text, int code, strin
if (context.IsWarningSuppressed(code, origin))
return null;

if (TryLogSingleWarning(context, code, origin, subcategory))
return null;

if (context.IsWarningAsError(code))
return new MessageContainer(MessageCategory.WarningAsError, text, code, subcategory, origin);

return new MessageContainer(MessageCategory.Warning, text, code, subcategory, origin);
}

private static bool TryLogSingleWarning(Logger context, int code, MessageOrigin origin, string subcategory)
{
if (subcategory != MessageSubCategory.AotAnalysis && subcategory != MessageSubCategory.TrimAnalysis)
return false;

var declaringType = origin.MemberDefinition switch
{
TypeDesc type => type,
MethodDesc method => method.OwningType,
FieldDesc field => field.OwningType,
#if !READYTORUN
PropertyPseudoDesc property => property.OwningType,
EventPseudoDesc @event => @event.OwningType,
#endif
_ => null,
};

ModuleDesc declaringAssembly = (declaringType as MetadataType)?.Module;
Debug.Assert(declaringAssembly != null);
if (declaringAssembly == null)
return false;

// Any IL2026 warnings left in an assembly with an IsTrimmable attribute are considered intentional
// and should not be collapsed, so that the user-visible RUC message gets printed.
if (code == 2026 && IsTrimmableAssembly(declaringAssembly))
return false;

if (context.IsSingleWarn(declaringAssembly, subcategory))
return true;

return false;
}

private static bool IsTrimmableAssembly(ModuleDesc assembly)
{
if (assembly is EcmaAssembly ecmaAssembly)
{
foreach (var attribute in ecmaAssembly.GetDecodedCustomAttributes("System.Reflection", "AssemblyMetadataAttribute"))
{
if (attribute.FixedArguments.Length != 2)
continue;

if (!attribute.FixedArguments[0].Type.IsString
|| ((string)(attribute.FixedArguments[0].Value)).Equals("IsTrimmable", StringComparison.Ordinal))
continue;

if (!attribute.FixedArguments[1].Type.IsString)
continue;

string value = (string)attribute.FixedArguments[1].Value;

if (value.Equals("True", StringComparison.OrdinalIgnoreCase))
{
return true;
}
else
{
//LogWarning($"Invalid AssemblyMetadata(\"IsTrimmable\", \"{args[1].Value}\") attribute in assembly '{assembly.Name.Name}'. Value must be \"True\"", 2102, GetAssemblyLocation(assembly));
}
}
}

return false;
}

/// <summary>
/// Create a info message.
/// </summary>
Expand Down
15 changes: 11 additions & 4 deletions src/coreclr/tools/Common/Compiler/Logging/MessageOrigin.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public struct MessageOrigin
public int? SourceLine { get; }
public int? SourceColumn { get; }

public MessageOrigin(string fileName, int sourceLine = 0, int sourceColumn = 0)
public MessageOrigin(string fileName, int? sourceLine = null, int? sourceColumn = null)
{
FileName = fileName;
SourceLine = sourceLine;
Expand Down Expand Up @@ -55,7 +55,7 @@ public override string ToString()

#if false
public bool Equals(MessageOrigin other) =>
(FileName, MemberDefinition, SourceLine, SourceColumn) == (other.FileName, other.MemberDefinition, other.SourceLine, other.SourceColumn);
(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();
Expand All @@ -66,8 +66,15 @@ public int CompareTo(MessageOrigin other)
{
if (MemberDefinition != null && other.MemberDefinition != null)
{
return (MemberDefinition.DeclaringType?.Module?.Assembly?.Name?.Name, MemberDefinition.DeclaringType?.Name, MemberDefinition?.Name).CompareTo
((other.MemberDefinition.DeclaringType?.Module?.Assembly?.Name?.Name, other.MemberDefinition.DeclaringType?.Name, other.MemberDefinition?.Name));
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;
}
else if (MemberDefinition == null && other.MemberDefinition == null)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Collections.Generic;
using Mono.Cecil;

namespace Mono.Linker
{
// Currently this is implemented using heuristics
public class CompilerGeneratedState
{
readonly LinkContext _context;
readonly Dictionary<TypeDefinition, MethodDefinition> _compilerGeneratedTypeToUserCodeMethod;
readonly HashSet<TypeDefinition> _typesWithPopulatedCache;

public CompilerGeneratedState (LinkContext context)
{
_context = context;
_compilerGeneratedTypeToUserCodeMethod = new Dictionary<TypeDefinition, MethodDefinition> ();
_typesWithPopulatedCache = new HashSet<TypeDefinition> ();
}

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 (
$"Methods '{method.GetDisplayName ()}' and '{alreadyAssociatedMethod.GetDisplayName ()}' are both associated with state machine type '{stateMachineType.GetDisplayName ()}'. This is currently unsupported and may lead to incorrectly reported warnings.",
2107,
new MessageOrigin (method),
MessageSubCategory.TrimAnalysis);
}
}

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;
}
}
}
Loading

0 comments on commit c7901ed

Please sign in to comment.