Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 35 additions & 48 deletions src/coreclr/tools/Common/Compiler/NativeAotNameMangler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ private static Utf8String DisambiguateName(Utf8String origName, HashSet<Utf8Stri
byte[] buffer = null;
for (uint iter = 0; set.Contains(result); iter++)
{
int neededLength = origName.Length + 1 + CountDigits(iter);
int neededLength = origName.Length + 1 + FormattingHelpers.CountDigits(iter);

if (buffer == null || buffer.Length != neededLength)
{
Expand All @@ -160,22 +160,6 @@ private static Utf8String DisambiguateName(Utf8String origName, HashSet<Utf8Stri
return result;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int CountDigits(uint value)
{
// Algorithm based on https://lemire.me/blog/2021/06/03/computing-the-number-of-digits-of-an-integer-even-faster.
ReadOnlySpan<long> table =
[
4294967296, 8589934582, 8589934582, 8589934582, 12884901788, 12884901788, 12884901788, 17179868184,
17179868184, 17179868184, 21474826480, 21474826480, 21474826480, 21474826480, 25769703776, 25769703776,
25769703776, 30063771072, 30063771072, 30063771072, 34349738368, 34349738368, 34349738368, 34349738368,
38554705664, 38554705664, 38554705664, 41949672960, 41949672960, 41949672960, 42949672960, 42949672960,
];
long tableValue = table[(int)uint.Log2(value)];
return (int)((value + tableValue) >> 32);
}


public override Utf8String GetMangledTypeName(TypeDesc type)
{
lock (this)
Expand Down Expand Up @@ -415,42 +399,45 @@ static void AppendTypeName(Utf8StringBuilder sb, MetadataType t)
// Case of a generic type. If `type' is a type definition we use the type name
// for mangling, otherwise we use the mangling of the type and its generic type
// parameters, e.g. A <B> becomes A_<___B_>_.
var typeDefinition = type.GetTypeDefinition();
if (typeDefinition != type)
if (type is IPrefixMangledType prefixMangledType)
{
var sb = new Utf8StringBuilder();
sb.Append(GetMangledTypeName(typeDefinition));

var inst = type.Instantiation;
sb.Append(EnterNameScopeSequence);
for (int i = 0; i < inst.Length; i++)
{
if (i > 0)
sb.Append("__"u8);
sb.Append(GetMangledTypeName(inst[i]));
}
sb.Append(ExitNameScopeSequence);
mangledName = sb.ToUtf8String();
}
else if (type is IPrefixMangledMethod)
{
mangledName = GetPrefixMangledMethodName((IPrefixMangledMethod)type);
}
else if (type is IPrefixMangledType)
{
mangledName = GetPrefixMangledTypeName((IPrefixMangledType)type);
mangledName = GetPrefixMangledTypeName(prefixMangledType);
}
else
{
// This is a type definition. Since we didn't fall in the `is EcmaType` case above,
// it's likely a compiler-generated type.
var defType = (DefType)type;
var sb = new Utf8StringBuilder();
if (defType.Namespace.Length > 0)
sb.Append(SanitizeName(defType.Namespace)).Append('_');
var typeDefinition = type.GetTypeDefinition();
if (typeDefinition != type)
{
var sb = new Utf8StringBuilder();
sb.Append(GetMangledTypeName(typeDefinition));

sb.Append(SanitizeName(defType.Name));
mangledName = sb.ToUtf8String();
var inst = type.Instantiation;
sb.Append(EnterNameScopeSequence);
for (int i = 0; i < inst.Length; i++)
{
if (i > 0)
sb.Append("__"u8);
sb.Append(GetMangledTypeName(inst[i]));
}
sb.Append(ExitNameScopeSequence);
mangledName = sb.ToUtf8String();
}
else if (type is IPrefixMangledMethod)
{
mangledName = GetPrefixMangledMethodName((IPrefixMangledMethod)type);
}
else
{
// This is a type definition. Since we didn't fall in the `is EcmaType` case above,
// it's likely a compiler-generated type.
var defType = (DefType)type;
var sb = new Utf8StringBuilder();
if (defType.Namespace.Length > 0)
sb.Append(SanitizeName(defType.Namespace)).Append('_');

sb.Append(SanitizeName(defType.Name));
mangledName = sb.ToUtf8String();
}
}
break;
}
Expand Down
2 changes: 2 additions & 0 deletions src/coreclr/tools/Common/System/FormattingHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,7 @@ public static string ToStringInvariant<T>(this T value, string format) where T :
{
return value.ToString(format, CultureInfo.InvariantCulture);
}

public static int CountDigits(uint value) => 1 + (int)uint.Log10(value);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,27 @@ namespace Internal.TypeSystem
/// Runtime determined types also behave like signature variables in the sense that they allow being
/// substituted during signature instantiation.
/// </remarks>
public sealed partial class RuntimeDeterminedType : DefType
public sealed partial class RuntimeDeterminedType : DefType, IPrefixMangledType
{
private DefType _rawCanonType;
private GenericParameterDesc _runtimeDeterminedDetailsType;
private readonly byte[] _utf8Prefix;

public RuntimeDeterminedType(DefType rawCanonType, GenericParameterDesc runtimeDeterminedDetailsType)
{
_rawCanonType = rawCanonType;
_runtimeDeterminedDetailsType = runtimeDeterminedDetailsType;
int index = _runtimeDeterminedDetailsType.Index;
_utf8Prefix = new byte[1 + FormattingHelpers.CountDigits((uint)index)];
_utf8Prefix[0] = (byte)(_runtimeDeterminedDetailsType.Kind == GenericParameterKind.Type ? 'T' : 'M');
bool formatted = index.TryFormat(_utf8Prefix.AsSpan(1), out int bytesWritten);
Comment on lines +40 to +43

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will come up with the same prefix for e.g. List<__Canon> and Dictionary<__Canon, __Canon>. The RuntimeDeterminedType for the first argument is rawCanonType == __Canon for both, but detailsType is T (of List<>) in one case and TKey (of Dictionary<,> in the other).

This would need the extension I mentioned to do properly.

Based on what Copilot says, this is not a problem for ReadyToRun because it has a custom comparer for RuntimeDeterminedTypes that ignores where the generic parameter comes from and ends up comparing these two equal even though they're not equal in type system sense.

It is also not a problem for native AOT because it doesn't use mangled names for these.

However, the implementation is still wrong.

We should either make it correct, or continue doing weird things on the ReadyToRun side and e.g. also do custom mangling for these that takes into consideration the equivalency rules.

Debug.Assert(formatted && bytesWritten == _utf8Prefix.Length - 1);
}

TypeDesc IPrefixMangledType.BaseType => _rawCanonType;

ReadOnlySpan<byte> IPrefixMangledType.Prefix => _utf8Prefix;

/// <summary>
/// Gets the generic parameter this runtime determined type represents.
/// </summary>
Expand Down Expand Up @@ -104,8 +114,7 @@ public override Utf8Span Namespace
{
get
{
return new Utf8Span(System.Text.Encoding.UTF8.GetBytes(_runtimeDeterminedDetailsType.Name))
.Append("_"u8, _rawCanonType.Namespace);
return _rawCanonType.Namespace;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -537,6 +537,22 @@ static void Validate(ReadyToRunReader reader)
}
}

[Fact]
public void GenericParameterNamesDoNotCollide()
{
var assembly = new CompiledAssembly
{
AssemblyName = nameof(GenericParameterNamesDoNotCollide),
SourceResourceNames = ["VirtualMethodGenerics/GenericParameterNames.cs"],
};

new R2RTestRunner(_output).Run(new R2RTestCase(
nameof(GenericParameterNamesDoNotCollide),
[
new(nameof(GenericParameterNamesDoNotCollide), [new CrossgenAssembly(assembly)])
]));
}

[Fact]
public void GenericTypeConstraintsAllowVariantParameters()
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// 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.Runtime.CompilerServices;

public static class GenericParameterNames
{
public static bool CompareUnicodeProperties()
{
var value = new { \u3042 = "", \u3044 = "" };
return value.Equals(new { \u3042 = "a", \u3044 = "b" });
}

public static bool CompareUnderscoreProperties()
{
var value = new { a_b = "", a\uFF3Fb = "" };
return value.Equals(new { a_b = "a", a\uFF3Fb = "b" });
}

public static object[] GetComparers() =>
GenericParameters<string, object>.GetComparers<string, object>();
}

public static class GenericParameters<\u3042, \u3044>
{
[MethodImpl(MethodImplOptions.NoInlining)]
public static object[] GetComparers<\u3046, \u3048>() =>
[
EqualityComparer<\u3042>.Default,
EqualityComparer<\u3044>.Default,
EqualityComparer<\u3046>.Default,
EqualityComparer<\u3048>.Default,
];
}
Loading