Inline resource strings in the compiler (#80896)
authorMichal Strehovský <MichalStrehovsky@users.noreply.github.com>
Mon, 30 Jan 2023 23:53:42 +0000 (08:53 +0900)
committerGitHub <noreply@github.com>
Mon, 30 Jan 2023 23:53:42 +0000 (08:53 +0900)
On a high level:
* When we're looking at IL to do substitutions, we additionally look for calls to `SR.SomeResourceName`. These are generated properties (generated by a piece of code in Arcade) that basically just do `GetResourceString(nameof(SomeResourceName))`. We look up what the resource string is (in the manifest resource) and replace the call with the looked up string literal.
* We also keep track of calls to `SR.GetResourceString`. Seeing this in the graph means that the optimization was defeated - someone bypassed the generated accessors. If we see one, we add dependency graph node to the graph that represent the manifest resource that has the string.
* When generating managed resources we skip over the one that has the strings unless the above dependency node is in the graph. This allows optimizing away the resource blobs if all accesses were inlined.

src/coreclr/nativeaot/System.Private.CoreLib/src/System/Diagnostics/StackFrame.NativeAot.cs
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/InlineableStringsResourceNode.cs [new file with mode: 0644]
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/NodeFactory.cs
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/ResourceDataNode.cs
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/FeatureSwitchManager.cs
src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/MetadataManager.cs
src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj
src/libraries/Common/src/System/SR.cs
src/libraries/System.Private.CoreLib/src/System/Text/EncodingData.cs
src/libraries/System.Private.CoreLib/src/System/Text/EncodingTable.cs
src/tests/nativeaot/SmokeTests/FrameworkStrings/Program.cs

index 540f549..e920b34 100644 (file)
@@ -163,15 +163,16 @@ namespace System.Diagnostics
             {
                 // Passing a default string for "at" in case SR.UsingResourceKeys() is true
                 // as this is a special case and we don't want to have "Word_At" on stack traces.
-                string word_At = SR.GetResourceString(nameof(SR.Word_At), defaultString: "at");
+                string word_At = SR.UsingResourceKeys() ? "at" : SR.Word_At;
                 builder.Append("   ").Append(word_At).Append(' ');
                 builder.AppendLine(DeveloperExperience.Default.CreateStackTraceString(_ipAddress, _needFileInfo));
             }
             if (_isLastFrameFromForeignExceptionStackTrace)
             {
                 // Passing default for Exception_EndStackTraceFromPreviousThrow in case SR.UsingResourceKeys is set.
-                builder.AppendLine(SR.GetResourceString(nameof(SR.Exception_EndStackTraceFromPreviousThrow),
-                    defaultString: "--- End of stack trace from previous location ---"));
+                builder.AppendLine(SR.UsingResourceKeys() ?
+                    "--- End of stack trace from previous location ---" :
+                    SR.Exception_EndStackTraceFromPreviousThrow);
             }
         }
     }
diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/InlineableStringsResourceNode.cs b/src/coreclr/tools/aot/ILCompiler.Compiler/Compiler/DependencyAnalysis/InlineableStringsResourceNode.cs
new file mode 100644 (file)
index 0000000..b696088
--- /dev/null
@@ -0,0 +1,77 @@
+// 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 ILCompiler.DependencyAnalysisFramework;
+using Internal.TypeSystem;
+using Internal.TypeSystem.Ecma;
+
+namespace ILCompiler.DependencyAnalysis
+{
+    /// <summary>
+    /// Represents a resource blob used by the SR class in the BCL.
+    /// If this node is present in the graph, it means we were not able to optimize its use away
+    /// and the blob has to be generated.
+    /// </summary>
+    internal sealed class InlineableStringsResourceNode : DependencyNodeCore<NodeFactory>
+    {
+        private readonly EcmaModule _module;
+
+        public const string ResourceAccessorTypeName = "SR";
+        public const string ResourceAccessorTypeNamespace = "System";
+        public const string ResourceAccessorGetStringMethodName = "GetResourceString";
+
+        public InlineableStringsResourceNode(EcmaModule module)
+        {
+            _module = module;
+        }
+
+        public override bool InterestingForDynamicDependencyAnalysis => false;
+
+        public override bool HasDynamicDependencies => false;
+
+        public override bool HasConditionalStaticDependencies => false;
+
+        public override bool StaticDependenciesAreComputed => true;
+
+        public static bool IsInlineableStringsResource(EcmaModule module, string resourceName)
+        {
+            if (!resourceName.EndsWith(".resources", StringComparison.Ordinal))
+                return false;
+
+            // Make a guess at the name of the resource Arcade tooling generated for the resource
+            // strings.
+            // https://github.com/dotnet/runtime/issues/81385 tracks not having to guess this.
+            string simpleName = module.Assembly.GetName().Name;
+            string resourceName1 = $"{simpleName}.Strings.resources";
+            string resourceName2 = $"FxResources.{simpleName}.SR.resources";
+
+            if (resourceName != resourceName1 && resourceName != resourceName2)
+                return false;
+
+            MetadataType srType = module.GetType(ResourceAccessorTypeNamespace, ResourceAccessorTypeName, throwIfNotFound: false);
+            if (srType == null)
+                return false;
+
+            return srType.GetMethod(ResourceAccessorGetStringMethodName, null) != null;
+        }
+
+        public static void AddDependenciesDueToResourceStringUse(ref DependencyList dependencies, NodeFactory factory, MethodDesc method)
+        {
+            if (method.Name == ResourceAccessorGetStringMethodName && method.OwningType is MetadataType mdType
+                && mdType.Name == ResourceAccessorTypeName && mdType.Namespace == ResourceAccessorTypeNamespace)
+            {
+                dependencies ??= new DependencyList();
+                dependencies.Add(factory.InlineableStringResource((EcmaModule)mdType.Module), "Using the System.SR class");
+            }
+        }
+
+        public override IEnumerable<CombinedDependencyListEntry> GetConditionalStaticDependencies(NodeFactory context) => null;
+        public override IEnumerable<DependencyListEntry> GetStaticDependencies(NodeFactory context) => null;
+        public override IEnumerable<CombinedDependencyListEntry> SearchDynamicDependencies(List<DependencyNodeCore<NodeFactory>> markedNodes, int firstNode, NodeFactory context) => null;
+        protected override string GetName(NodeFactory context)
+            => $"String resources for {_module.Assembly.GetName().Name}";
+    }
+}
index 196a51e..ba0f5be 100644 (file)
@@ -462,6 +462,11 @@ namespace ILCompiler.DependencyAnalysis
                 return new ModuleMetadataNode(module);
             });
 
+            _inlineableStringResources = new NodeCache<EcmaModule, InlineableStringsResourceNode>(module =>
+            {
+                return new InlineableStringsResourceNode(module);
+            });
+
             _customAttributesWithMetadata = new NodeCache<ReflectableCustomAttribute, CustomAttributeMetadataNode>(ca =>
             {
                 return new CustomAttributeMetadataNode(ca);
@@ -1139,6 +1144,12 @@ namespace ILCompiler.DependencyAnalysis
             return _modulesWithMetadata.GetOrAdd(module);
         }
 
+        private NodeCache<EcmaModule, InlineableStringsResourceNode> _inlineableStringResources;
+        internal InlineableStringsResourceNode InlineableStringResource(EcmaModule module)
+        {
+            return _inlineableStringResources.GetOrAdd(module);
+        }
+
         private NodeCache<ReflectableCustomAttribute, CustomAttributeMetadataNode> _customAttributesWithMetadata;
 
         internal CustomAttributeMetadataNode CustomAttributeMetadata(ReflectableCustomAttribute ca)
index 19fe325..903d4c2 100644 (file)
@@ -98,7 +98,7 @@ namespace ILCompiler.DependencyAnalysis
                             string resourceName = module.MetadataReader.GetString(resource.Name);
 
                             // Check if emitting the manifest resource is blocked by policy.
-                            if (factory.MetadataManager.IsManifestResourceBlocked(module, resourceName))
+                            if (factory.MetadataManager.IsManifestResourceBlocked(factory, module, resourceName))
                                 continue;
 
                             string assemblyName = module.GetName().FullName;
index cbe2c0a..0f94936 100644 (file)
@@ -5,7 +5,11 @@ using System;
 using System.Collections.Generic;
 using System.IO;
 using System.Reflection.Metadata;
+using System.Reflection.Metadata.Ecma335;
 using System.Reflection.PortableExecutable;
+using System.Resources;
+
+using ILCompiler.DependencyAnalysis;
 
 using Internal.IL;
 using Internal.TypeSystem;
@@ -114,6 +118,9 @@ namespace ILCompiler
             // (Lets us avoid seeing lots of small basic blocks within eliminated chunks.)
             VisibleBasicBlockStart = 0x10,
 
+            // This is a potential SR.get_SomeResourceString call.
+            GetResourceStringCall = 0x20,
+
             // The instruction at this offset is reachable
             Mark = 0x80,
         }
@@ -139,9 +146,17 @@ namespace ILCompiler
             // Last step is a sweep - we replace the tail of all unreachable blocks with "br $-2"
             // and nop out the rest. If the basic block is smaller than 2 bytes, we don't touch it.
             // We also eliminate any EH records that correspond to the stubbed out basic block.
+            //
+            // We also attempt to rewrite calls to SR.SomeResourceString accessors with string
+            // literals looked up from the managed resources.
 
             Debug.Assert(method.GetMethodILDefinition() == method);
 
+            // Do not attempt to inline resource strings if we only want to use resource keys.
+            // The optimizations are not compatible.
+            bool shouldInlineResourceStrings =
+                !_hashtable._switchValues.TryGetValue("System.Resources.UseSystemResourceKeys", out bool useResourceKeys) || !useResourceKeys;
+
             ILExceptionRegion[] ehRegions = method.GetExceptionRegions();
             byte[] methodBytes = method.GetILBytes();
             OpcodeFlags[] flags = new OpcodeFlags[methodBytes.Length];
@@ -243,6 +258,8 @@ namespace ILCompiler
                 }
             }
 
+            bool hasGetResourceStringCall = false;
+
             // Mark all reachable basic blocks
             //
             // We also do another round of basic block marking to mark beginning of visible basic blocks
@@ -384,6 +401,19 @@ namespace ILCompiler
                         if (reader.HasNext)
                             flags[reader.Offset] |= OpcodeFlags.VisibleBasicBlockStart;
                     }
+                    else if (shouldInlineResourceStrings && opcode == ILOpcode.call)
+                    {
+                        var callee = method.GetObject(reader.ReadILToken(), NotFoundBehavior.ReturnNull) as EcmaMethod;
+                        if (callee != null && callee.IsSpecialName && callee.OwningType is EcmaType calleeType
+                            && calleeType.Name == InlineableStringsResourceNode.ResourceAccessorTypeName
+                            && calleeType.Namespace == InlineableStringsResourceNode.ResourceAccessorTypeNamespace
+                            && callee.Signature is { Length: 0, IsStatic: true }
+                            && callee.Name.StartsWith("get_", StringComparison.Ordinal))
+                        {
+                            flags[offset] |= OpcodeFlags.GetResourceStringCall;
+                            hasGetResourceStringCall = true;
+                        }
+                    }
                     else
                     {
                         reader.Skip(opcode);
@@ -405,7 +435,7 @@ namespace ILCompiler
                 }
             }
 
-            if (!hasUnmarkedInstruction)
+            if (!hasUnmarkedInstruction && !hasGetResourceStringCall)
                 return method;
 
             byte[] newBody = (byte[])methodBytes.Clone();
@@ -472,7 +502,47 @@ namespace ILCompiler
                 debugInfo = new SubstitutedDebugInformation(debugInfo, sequencePoints.ToArray());
             }
 
-            return new SubstitutedMethodIL(method, newBody, newEHRegions.ToArray(), debugInfo);
+            // We only optimize EcmaMethods because there we can find out the highest string token RID
+            // in use.
+            ArrayBuilder<string> newStrings = default;
+            if (hasGetResourceStringCall && method.GetMethodILDefinition() is EcmaMethodIL ecmaMethodIL)
+            {
+                // We're going to inject new string tokens. Start where the last token of the module left off.
+                // We don't need this token to be globally unique because all token resolution happens in the context
+                // of a MethodIL and we're making a new one here. It just has to be unique to the MethodIL.
+                int tokenRid = ecmaMethodIL.Module.MetadataReader.GetHeapSize(HeapIndex.UserString);
+
+                for (int offset = 0; offset < flags.Length; offset++)
+                {
+                    if ((flags[offset] & OpcodeFlags.GetResourceStringCall) == 0)
+                        continue;
+
+                    Debug.Assert(newBody[offset] == (byte)ILOpcode.call);
+                    var getter = (EcmaMethod)method.GetObject(new ILReader(newBody, offset + 1).ReadILToken());
+
+                    // If we can't get the string, this might be something else.
+                    string resourceString = GetResourceStringForAccessor(getter);
+                    if (resourceString == null)
+                        continue;
+
+                    // If we ran out of tokens, we can't optimize anymore.
+                    if (tokenRid > 0xFFFFFF)
+                        continue;
+
+                    newStrings.Add(resourceString);
+
+                    // call and ldstr are both 5-byte instructions: opcode followed by a token.
+                    newBody[offset] = (byte)ILOpcode.ldstr;
+                    newBody[offset + 1] = (byte)tokenRid;
+                    newBody[offset + 2] = (byte)(tokenRid >> 8);
+                    newBody[offset + 3] = (byte)(tokenRid >> 16);
+                    newBody[offset + 4] = TokenTypeString;
+
+                    tokenRid++;
+                }
+            }
+
+            return new SubstitutedMethodIL(method, newBody, newEHRegions.ToArray(), debugInfo, newStrings.ToArray());
         }
 
         private bool TryGetConstantArgument(MethodIL methodIL, byte[] body, OpcodeFlags[] flags, int offset, int argIndex, out int constant)
@@ -641,19 +711,36 @@ namespace ILCompiler
             return false;
         }
 
+        private string GetResourceStringForAccessor(EcmaMethod method)
+        {
+            Debug.Assert(method.Name.StartsWith("get_", StringComparison.Ordinal));
+            string resourceStringName = method.Name.Substring(4);
+
+            Dictionary<string, string> dict = _hashtable.GetOrCreateValue(method.Module).InlineableResourceStrings;
+            if (dict != null
+                && dict.TryGetValue(resourceStringName, out string result))
+            {
+                return result;
+            }
+
+            return null;
+        }
+
         private sealed class SubstitutedMethodIL : MethodIL
         {
             private readonly byte[] _body;
             private readonly ILExceptionRegion[] _ehRegions;
             private readonly MethodIL _wrappedMethodIL;
             private readonly MethodDebugInformation _debugInfo;
+            private readonly string[] _newStrings;
 
-            public SubstitutedMethodIL(MethodIL wrapped, byte[] body, ILExceptionRegion[] ehRegions, MethodDebugInformation debugInfo)
+            public SubstitutedMethodIL(MethodIL wrapped, byte[] body, ILExceptionRegion[] ehRegions, MethodDebugInformation debugInfo, string[] newStrings)
             {
                 _wrappedMethodIL = wrapped;
                 _body = body;
                 _ehRegions = ehRegions;
                 _debugInfo = debugInfo;
+                _newStrings = newStrings;
             }
 
             public override MethodDesc OwningMethod => _wrappedMethodIL.OwningMethod;
@@ -662,7 +749,23 @@ namespace ILCompiler
             public override ILExceptionRegion[] GetExceptionRegions() => _ehRegions;
             public override byte[] GetILBytes() => _body;
             public override LocalVariableDefinition[] GetLocals() => _wrappedMethodIL.GetLocals();
-            public override object GetObject(int token, NotFoundBehavior notFoundBehavior) => _wrappedMethodIL.GetObject(token, notFoundBehavior);
+            public override object GetObject(int token, NotFoundBehavior notFoundBehavior)
+            {
+                // If this is a string token, it could be one of the new string tokens we injected.
+                if ((token >>> 24) == TokenTypeString
+                    && _wrappedMethodIL.GetMethodILDefinition() is EcmaMethodIL ecmaMethodIL)
+                {
+                    int rid = token & 0xFFFFFF;
+                    int maxRealTokenRid = ecmaMethodIL.Module.MetadataReader.GetHeapSize(HeapIndex.UserString);
+                    if (rid >= maxRealTokenRid)
+                    {
+                        // Yep, string injected by us.
+                        return _newStrings[rid - maxRealTokenRid];
+                    }
+                }
+
+                return _wrappedMethodIL.GetObject(token, notFoundBehavior);
+            }
             public override MethodDebugInformation GetDebugInfo() => _debugInfo;
         }
 
@@ -682,9 +785,11 @@ namespace ILCompiler
             public override IEnumerable<ILSequencePoint> GetSequencePoints() => _sequencePoints;
         }
 
+        private const int TokenTypeString = 0x70; // CorTokenType for strings
+
         private sealed class FeatureSwitchHashtable : LockFreeReaderHashtable<EcmaModule, AssemblyFeatureInfo>
         {
-            private readonly Dictionary<string, bool> _switchValues;
+            internal readonly Dictionary<string, bool> _switchValues;
             private readonly Logger _logger;
 
             public FeatureSwitchHashtable(Logger logger, Dictionary<string, bool> switchValues)
@@ -710,6 +815,7 @@ namespace ILCompiler
 
             public Dictionary<MethodDesc, BodySubstitution> BodySubstitutions { get; }
             public Dictionary<FieldDesc, object> FieldSubstitutions { get; }
+            public Dictionary<string, string> InlineableResourceStrings { get; }
 
             public AssemblyFeatureInfo(EcmaModule module, Logger logger, IReadOnlyDictionary<string, bool> featureSwitchValues)
             {
@@ -741,6 +847,27 @@ namespace ILCompiler
 
                         (BodySubstitutions, FieldSubstitutions) = BodySubstitutionsParser.GetSubstitutions(logger, module.Context, ms, resource, module, "name", featureSwitchValues);
                     }
+                    else if (InlineableStringsResourceNode.IsInlineableStringsResource(module, resourceName))
+                    {
+                        BlobReader reader = resourceDirectory.GetReader((int)resource.Offset, resourceDirectory.Length - (int)resource.Offset);
+                        int length = (int)reader.ReadUInt32();
+
+                        UnmanagedMemoryStream ms;
+                        unsafe
+                        {
+                            ms = new UnmanagedMemoryStream(reader.CurrentPointer, length);
+                        }
+
+                        InlineableResourceStrings = new Dictionary<string, string>();
+
+                        using var resReader = new ResourceReader(ms);
+                        var enumerator = resReader.GetEnumerator();
+                        while (enumerator.MoveNext())
+                        {
+                            if (enumerator.Key is string key && enumerator.Value is string value)
+                                InlineableResourceStrings[key] = value;
+                        }
+                    }
                 }
             }
         }
index d5d2d7b..19d8a8a 100644 (file)
@@ -526,6 +526,8 @@ namespace ILCompiler
                 ExactMethodInstantiationsNode.GetExactMethodInstantiationDependenciesForMethod(ref dependencies, factory, method);
             }
 
+            InlineableStringsResourceNode.AddDependenciesDueToResourceStringUse(ref dependencies, factory, method);
+
             GetDependenciesDueToMethodCodePresenceInternal(ref dependencies, factory, method, methodIL);
         }
 
@@ -839,9 +841,17 @@ namespace ILCompiler
             return _blockingPolicy.IsBlocked(typicalMethodDefinition);
         }
 
-        public bool IsManifestResourceBlocked(ModuleDesc module, string resourceName)
+        public bool IsManifestResourceBlocked(NodeFactory factory, Internal.TypeSystem.Ecma.EcmaModule module, string resourceName)
         {
-            return _resourceBlockingPolicy.IsManifestResourceBlocked(module, resourceName);
+            if (_resourceBlockingPolicy.IsManifestResourceBlocked(module, resourceName))
+                return true;
+
+            // If this is a resource strings resource but we don't actually need it, block it.
+            if (InlineableStringsResourceNode.IsInlineableStringsResource(module, resourceName)
+                && !factory.InlineableStringResource(module).Marked)
+                return true;
+
+            return false;
         }
 
         public bool CanGenerateMetadata(MetadataType type)
index 33c4e33..db2e930 100644 (file)
     <Compile Include="Compiler\DependencyAnalysis\FieldRvaDataNode.cs" />
     <Compile Include="Compiler\DependencyAnalysis\GenericStaticBaseInfoNode.cs" />
     <Compile Include="Compiler\DependencyAnalysis\IndirectionExtensions.cs" />
+    <Compile Include="Compiler\DependencyAnalysis\InlineableStringsResourceNode.cs" />
     <Compile Include="Compiler\DependencyAnalysis\InterfaceDispatchCellSectionNode.cs" />
     <Compile Include="Compiler\DependencyAnalysis\MethodExceptionHandlingInfoNode.cs" />
     <Compile Include="Compiler\DependencyAnalysis\ModuleInitializerListNode.cs" />
index ec25620..96fcada 100644 (file)
@@ -13,7 +13,7 @@ namespace System
         // by default it returns the value of System.Resources.UseSystemResourceKeys AppContext switch or false if not specified.
         // Native code generators can replace the value this returns based on user input at the time of native code generation.
         // The Linker is also capable of replacing the value of this method when the application is being trimmed.
-        private static bool UsingResourceKeys() => s_usingResourceKeys;
+        internal static bool UsingResourceKeys() => s_usingResourceKeys;
 
         internal static string GetResourceString(string resourceKey)
         {
index 2b621d6..88689f5 100644 (file)
@@ -237,41 +237,6 @@ namespace System.Text
             56
         };
 
-        //
-        // EnglishNames is the concatenation of the English names for each codepage.
-        // It is used in retrieving the value for System.Text.Encoding.EncodingName
-        // given System.Text.Encoding.CodePage.
-        // This is done rather than using a large readonly array of strings to avoid
-        // generating a large amount of code in the static constructor.
-        //
-        private const string EnglishNames =
-            "Unicode" + // 1200
-            "Unicode (Big-Endian)" + // 1201
-            "Unicode (UTF-32)" + // 12000
-            "Unicode (UTF-32 Big-Endian)" + // 12001
-            "US-ASCII" + // 20127
-            "Western European (ISO)" + // 28591
-            "Unicode (UTF-7)" + // 65000
-            "Unicode (UTF-8)"; // 65001
-
-        //
-        // EnglishNameIndices contains the start index of each code page's English
-        // name in the string EnglishNames. It is indexed by an index into
-        // MappedCodePages.
-        //
-        private static ReadOnlySpan<int> EnglishNameIndices => new int[]
-        {
-            0, // Unicode (1200)
-            7, // Unicode (Big-Endian) (1201)
-            27, // Unicode (UTF-32) (12000)
-            43, // Unicode (UTF-32 Big-Endian) (12001)
-            70, // US-ASCII (20127)
-            78, // Western European (ISO) (28591)
-            100, // Unicode (UTF-7) (65000)
-            115, // Unicode (UTF-8) (65001)
-            130
-        };
-
         // redeclaring these constants here for readability below
         private const uint MIMECONTF_MAILNEWS = Encoding.MIMECONTF_MAILNEWS;
         private const uint MIMECONTF_BROWSER = Encoding.MIMECONTF_BROWSER;
index e404065..c3b043a 100644 (file)
@@ -124,7 +124,7 @@ namespace System.Text
                 arrayEncodingInfo[arrayEncodingInfoIdx++] = new EncodingInfo(
                     codePage,
                     webNames[webNameIndices[i]..webNameIndices[i + 1]],
-                    GetDisplayName(codePage, i)
+                    GetDisplayName(codePage)
                     );
             }
 
@@ -151,7 +151,7 @@ namespace System.Text
                     if (codePage != Encoding.CodePageUTF7 || LocalAppContextSwitches.EnableUnsafeUTF7Encoding)
                     {
                         encodingInfoList[codePage] = new EncodingInfo(codePage, webNames[webNameIndices[i]..webNameIndices[i + 1]],
-                                                                                GetDisplayName(codePage, i));
+                                                                                GetDisplayName(codePage));
                     }
                 }
             }
@@ -229,19 +229,28 @@ namespace System.Text
             // All supported code pages have identical header names, and body names.
             string headerName = webName;
             string bodyName = webName;
-            string displayName = GetDisplayName(codePage, index);
+            string displayName = GetDisplayName(codePage);
             uint flags = Flags[index];
 
             return new CodePageDataItem(uiFamilyCodePage, webName, headerName, bodyName, displayName, flags);
         }
 
-        private static string GetDisplayName(int codePage, int englishNameIndex)
+        private static string GetDisplayName(int codePage)
         {
-            string? displayName = SR.GetResourceString("Globalization_cp_" + codePage.ToString());
-            if (string.IsNullOrEmpty(displayName))
-                displayName = EnglishNames[EnglishNameIndices[englishNameIndex]..EnglishNameIndices[englishNameIndex + 1]];
-
-            return displayName;
+            switch (codePage)
+            {
+                case 1200: return SR.Globalization_cp_1200;
+                case 1201: return SR.Globalization_cp_1201;
+                case 12000: return SR.Globalization_cp_12000;
+                case 12001: return SR.Globalization_cp_12001;
+                case 20127: return SR.Globalization_cp_20127;
+                case 28591: return SR.Globalization_cp_28591;
+                case 65000: return SR.Globalization_cp_65000;
+                case 65001: return SR.Globalization_cp_65001;
+            };
+
+            Debug.Fail("Unexpected code page");
+            return "";
         }
     }
 }
index 6f4cb0f..73b0b99 100644 (file)
@@ -56,7 +56,6 @@ if (coreLibNames.Length != 0)
 Console.WriteLine("Resources in reflection library:");
 string[] refNames;
 const string reflectionAssembly = "System.Private.Reflection.Execution";
-#if RESOURCE_KEYS
 try
 {
     refNames = System.Reflection.Assembly.Load(reflectionAssembly).GetManifestResourceNames();
@@ -65,9 +64,6 @@ catch (System.IO.FileNotFoundException)
 {
     refNames = Array.Empty<string>();
 }
-#else
-refNames = System.Reflection.Assembly.Load(reflectionAssembly).GetManifestResourceNames();
-#endif
 foreach (var name in refNames)
     Console.WriteLine(name);