diff --git a/src/coreclr/tools/Common/Compiler/NativeAotNameMangler.cs b/src/coreclr/tools/Common/Compiler/NativeAotNameMangler.cs
index 59c82c03f110e3..1cae9d6f046431 100644
--- a/src/coreclr/tools/Common/Compiler/NativeAotNameMangler.cs
+++ b/src/coreclr/tools/Common/Compiler/NativeAotNameMangler.cs
@@ -129,6 +129,7 @@ private static Utf8String SanitizeNameWithHash(Utf8String literal, byte[] hash =
/// Dictionary given a mangled name for a given
///
private Dictionary _mangledTypeNames = new Dictionary();
+ private Dictionary _mangledAssemblyNames = new Dictionary(StringComparer.OrdinalIgnoreCase);
///
/// Given a set of names check if
@@ -195,6 +196,84 @@ protected Utf8String NestMangledName(Utf8String name)
return Utf8String.Concat(EnterNameScopeSequence, name, ExitNameScopeSequence);
}
+ private Utf8String GetMangledAssemblyName(EcmaAssembly assembly)
+ {
+ string assemblyName = assembly.GetName().Name;
+ lock (this)
+ {
+ if (_mangledAssemblyNames.TryGetValue(assemblyName, out Utf8String mangledName))
+ return mangledName;
+
+ return ComputeMangledAssemblyName(assemblyName, (CompilerTypeSystemContext)assembly.Context);
+ }
+ }
+
+ private Utf8String ComputeMangledAssemblyName(string assemblyName, CompilerTypeSystemContext context)
+ {
+ lock (this)
+ {
+ if (!_mangledAssemblyNames.TryGetValue(assemblyName, out Utf8String name))
+ {
+ var assemblies = new List(context.InputFilePaths.Count + context.ReferenceFilePaths.Count + 1);
+ var assemblySet = new HashSet(StringComparer.OrdinalIgnoreCase);
+ if (assemblySet.Add(assemblyName))
+ assemblies.Add(assemblyName);
+ foreach (string candidateAssemblyName in context.InputFilePaths.Keys)
+ {
+ if (assemblySet.Add(candidateAssemblyName))
+ assemblies.Add(candidateAssemblyName);
+ }
+ foreach (string candidateAssemblyName in context.ReferenceFilePaths.Keys)
+ {
+ if (assemblySet.Add(candidateAssemblyName))
+ assemblies.Add(candidateAssemblyName);
+ }
+ assemblies.Sort(CompareAssembliesForMangling);
+
+ var deduplicator = new HashSet();
+ foreach (string candidateAssemblyName in assemblies)
+ {
+ if (_mangledAssemblyNames.TryGetValue(candidateAssemblyName, out Utf8String existingMangledName))
+ {
+ deduplicator.Add(existingMangledName);
+ continue;
+ }
+
+ bool isSystemPrivate = IsSystemPrivateAssemblyName(candidateAssemblyName);
+ string prefixAssemblyName = isSystemPrivate
+ ? string.Concat("S.P.", candidateAssemblyName.AsSpan(15))
+ : candidateAssemblyName;
+
+ name = SanitizeName(new Utf8String(prefixAssemblyName));
+
+ if (!isSystemPrivate)
+ name = DisambiguateName(name, deduplicator);
+
+ deduplicator.Add(name);
+ _mangledAssemblyNames.Add(candidateAssemblyName, name);
+ }
+
+ name = _mangledAssemblyNames[assemblyName];
+ }
+ return name;
+ }
+
+ static int CompareAssembliesForMangling(string leftName, string rightName)
+ {
+ bool leftSystemPrivate = IsSystemPrivateAssemblyName(leftName);
+ bool rightSystemPrivate = IsSystemPrivateAssemblyName(rightName);
+ if (leftSystemPrivate != rightSystemPrivate)
+ return leftSystemPrivate ? -1 : 1;
+
+ return string.CompareOrdinal(leftName, rightName);
+ }
+
+ static bool IsSystemPrivateAssemblyName(string assemblyName)
+ {
+ return assemblyName.StartsWith("System.Private.", StringComparison.Ordinal);
+ }
+ }
+
///
/// If given is an precompute its mangled type name
/// along with all the other types from the same module as .
@@ -215,15 +294,7 @@ private Utf8String ComputeMangledTypeName(TypeDesc type)
{
bool isSystemModule = ecmaType.Module == ecmaType.Context.SystemModule;
- string assemblyName = ((EcmaAssembly)ecmaType.Module).GetName().Name;
- bool isSystemPrivate = assemblyName.StartsWith("System.Private.");
-
- // Abbreviate System.Private to S.P. This might conflict with user defined assembly names,
- // but we already have a problem due to running SanitizeName without disambiguating the result
- // This problem needs a better fix.
- if (isSystemPrivate)
- assemblyName = string.Concat("S.P.", assemblyName.AsSpan(15));
- Utf8String prependAssemblyName = SanitizeName(new Utf8String(assemblyName));
+ Utf8String prependAssemblyName = GetMangledAssemblyName((EcmaAssembly)ecmaType.Module);
var deduplicator = new HashSet();
diff --git a/src/tests/nativeaot/SmokeTests/AssemblyNameCollision/AssemblyNameCollision.cs b/src/tests/nativeaot/SmokeTests/AssemblyNameCollision/AssemblyNameCollision.cs
new file mode 100644
index 00000000000000..36a86955b5f6a8
--- /dev/null
+++ b/src/tests/nativeaot/SmokeTests/AssemblyNameCollision/AssemblyNameCollision.cs
@@ -0,0 +1,19 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+extern alias AssemblyWithDot;
+extern alias AssemblyWithUnderscore;
+
+class Program
+{
+ public static int Main()
+ {
+ if (new AssemblyWithDot::TestNamespace.TestType().Value != "A.B")
+ return 101;
+
+ if (new AssemblyWithUnderscore::TestNamespace.TestType().Value != "A_B")
+ return 102;
+
+ return 100;
+ }
+}
diff --git a/src/tests/nativeaot/SmokeTests/AssemblyNameCollision/AssemblyNameCollision.csproj b/src/tests/nativeaot/SmokeTests/AssemblyNameCollision/AssemblyNameCollision.csproj
new file mode 100644
index 00000000000000..a3e0126a377a18
--- /dev/null
+++ b/src/tests/nativeaot/SmokeTests/AssemblyNameCollision/AssemblyNameCollision.csproj
@@ -0,0 +1,10 @@
+
+
+ Exe
+ 0
+
+
+
+
+
+
diff --git a/src/tests/nativeaot/SmokeTests/AssemblyNameCollision/AssemblyWithDot/AssemblyWithDot.csproj b/src/tests/nativeaot/SmokeTests/AssemblyNameCollision/AssemblyWithDot/AssemblyWithDot.csproj
new file mode 100644
index 00000000000000..b333d242ead865
--- /dev/null
+++ b/src/tests/nativeaot/SmokeTests/AssemblyNameCollision/AssemblyWithDot/AssemblyWithDot.csproj
@@ -0,0 +1,5 @@
+
+
+ A.B
+
+
diff --git a/src/tests/nativeaot/SmokeTests/AssemblyNameCollision/AssemblyWithDot/TestType.cs b/src/tests/nativeaot/SmokeTests/AssemblyNameCollision/AssemblyWithDot/TestType.cs
new file mode 100644
index 00000000000000..7f8a99eec02e5e
--- /dev/null
+++ b/src/tests/nativeaot/SmokeTests/AssemblyNameCollision/AssemblyWithDot/TestType.cs
@@ -0,0 +1,9 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace TestNamespace;
+
+public sealed class TestType
+{
+ public string Value => "A.B";
+}
diff --git a/src/tests/nativeaot/SmokeTests/AssemblyNameCollision/AssemblyWithUnderscore/AssemblyWithUnderscore.csproj b/src/tests/nativeaot/SmokeTests/AssemblyNameCollision/AssemblyWithUnderscore/AssemblyWithUnderscore.csproj
new file mode 100644
index 00000000000000..1ea149d380a71a
--- /dev/null
+++ b/src/tests/nativeaot/SmokeTests/AssemblyNameCollision/AssemblyWithUnderscore/AssemblyWithUnderscore.csproj
@@ -0,0 +1,5 @@
+
+
+ A_B
+
+
diff --git a/src/tests/nativeaot/SmokeTests/AssemblyNameCollision/AssemblyWithUnderscore/TestType.cs b/src/tests/nativeaot/SmokeTests/AssemblyNameCollision/AssemblyWithUnderscore/TestType.cs
new file mode 100644
index 00000000000000..f7f27d1f6d766a
--- /dev/null
+++ b/src/tests/nativeaot/SmokeTests/AssemblyNameCollision/AssemblyWithUnderscore/TestType.cs
@@ -0,0 +1,9 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+namespace TestNamespace;
+
+public sealed class TestType
+{
+ public string Value => "A_B";
+}