Resource writing for crossgen2 (dotnet/coreclr#26639)
authorDavid Wrighton <davidwr@microsoft.com>
Tue, 10 Sep 2019 23:18:02 +0000 (16:18 -0700)
committerGitHub <noreply@github.com>
Tue, 10 Sep 2019 23:18:02 +0000 (16:18 -0700)
- Refactor the win32 resource reading code to use more natural managed data structures
- Build a Win32 resource emitter on top of refactored data structures
- Replace resource section copy logic with new node to generate win32 resources

Commit migrated from https://github.com/dotnet/coreclr/commit/17e4bdb9dab45240c85815edd6bf656bf36b1e3d

13 files changed:
src/coreclr/src/tools/crossgen2/Common/Compiler/DependencyAnalysis/ObjectDataBuilder.cs
src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/CodeGen/ReadyToRunObjectWriter.cs
src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/Win32ResourcesNode.cs [new file with mode: 0644]
src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRunCodegenNodeFactory.cs
src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Compiler/ReadyToRunCodegenCompilationBuilder.cs
src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj
src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/ObjectWriter/R2RPEBuilder.cs
src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/ObjectWriter/SectionBuilder.cs
src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Win32Resources/ResourceData.Reader.cs
src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Win32Resources/ResourceData.ResourcesDataModel.cs
src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Win32Resources/ResourceData.UpdateResourceDataModel.cs
src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Win32Resources/ResourceData.Win32Structs.cs
src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Win32Resources/ResourceData.cs

index d56b5dd..6699d06 100644 (file)
@@ -256,6 +256,15 @@ namespace ILCompiler.DependencyAnalysis
             _data[offset + 3] = (byte)((emit >> 24) & 0xFF);
         }
 
+        public void EmitUInt(Reservation reservation, uint emit)
+        {
+            int offset = ReturnReservationTicket(reservation);
+            _data[offset] = (byte)(emit & 0xFF);
+            _data[offset + 1] = (byte)((emit >> 8) & 0xFF);
+            _data[offset + 2] = (byte)((emit >> 16) & 0xFF);
+            _data[offset + 3] = (byte)((emit >> 24) & 0xFF);
+        }
+
         public void EmitReloc(ISymbolNode symbol, RelocType relocType, int delta = 0)
         {
 #if DEBUG
@@ -321,5 +330,15 @@ namespace ILCompiler.DependencyAnalysis
         {
             _definedSymbols.Add(node);
         }
+
+        public void PadAlignment(int align)
+        {
+            Debug.Assert((align == 2) || (align == 4) || (align == 8) || (align == 16));
+            int misalignment = _data.Count & (align - 1);
+            if (misalignment != 0)
+            {
+                EmitZeros(align - misalignment);
+            }
+        }
     }
 }
index 8fc5442..0474411 100644 (file)
@@ -114,6 +114,12 @@ namespace ILCompiler.DependencyAnalysis
 
                 r2rPeBuilder.SetCorHeader(_nodeFactory.CopiedCorHeaderNode, _nodeFactory.CopiedCorHeaderNode.Size);
 
+                if (_nodeFactory.Win32ResourcesNode != null)
+                {
+                    Debug.Assert(_nodeFactory.Win32ResourcesNode.Size != 0);
+                    r2rPeBuilder.SetWin32Resources(_nodeFactory.Win32ResourcesNode, _nodeFactory.Win32ResourcesNode.Size);
+                }
+
                 using (var peStream = File.Create(_objectFilePath))
                 {
                     r2rPeBuilder.Write(peStream);
diff --git a/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/Win32ResourcesNode.cs b/src/coreclr/src/tools/crossgen2/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/Win32ResourcesNode.cs
new file mode 100644 (file)
index 0000000..7536f59
--- /dev/null
@@ -0,0 +1,61 @@
+// 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;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Reflection.Metadata;
+using System.Reflection.Metadata.Ecma335;
+
+using Internal.NativeFormat;
+using Internal.Text;
+using ILCompiler.Win32Resources;
+using Internal.TypeSystem;
+using Internal.TypeSystem.Ecma;
+
+namespace ILCompiler.DependencyAnalysis.ReadyToRun
+{
+    public class Win32ResourcesNode : ObjectNode, ISymbolDefinitionNode
+    {
+        private ResourceData _resourceData;
+        private int _size;
+
+        public Win32ResourcesNode(ResourceData resourceData)
+        {
+            _resourceData = resourceData;
+        }
+
+        public override ObjectNodeSection Section => ObjectNodeSection.TextSection;
+
+        public override bool IsShareable => false;
+
+        public override int ClassCode => 315358339;
+
+        public override bool StaticDependenciesAreComputed => true;
+
+        public int Offset => 0;
+
+        public void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb)
+        {
+            sb.Append("____Win32Resources");
+        }
+
+        public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false)
+        {
+            ObjectDataBuilder builder = new ObjectDataBuilder();
+            builder.AddSymbol(this);
+            _resourceData.WriteResources(this, ref builder);
+            _size = builder.CountBytes;
+            return builder.ToObjectData();
+        }
+
+        protected override string GetName(NodeFactory context)
+        {
+            return "____Win32Resources";
+        }
+
+        public int Size => _size;
+    }
+}
index 8c0cc4f..f0c048c 100644 (file)
@@ -15,6 +15,7 @@ using Internal.JitInterface;
 using Internal.TypeSystem;
 using Internal.Text;
 using Internal.TypeSystem.Ecma;
+using ILCompiler.Win32Resources;
 
 namespace ILCompiler.DependencyAnalysis
 {
@@ -214,7 +215,8 @@ namespace ILCompiler.DependencyAnalysis
             NameMangler nameMangler,
             ModuleTokenResolver moduleTokenResolver,
             SignatureContext signatureContext,
-            CopiedCorHeaderNode corHeaderNode)
+            CopiedCorHeaderNode corHeaderNode,
+            ResourceData win32Resources)
             : base(context,
                   compilationModuleGroup,
                   nameMangler,
@@ -225,6 +227,8 @@ namespace ILCompiler.DependencyAnalysis
             Resolver = moduleTokenResolver;
             InputModuleContext = signatureContext;
             CopiedCorHeaderNode = corHeaderNode;
+            if (!win32Resources.IsEmpty)
+                Win32ResourcesNode = new Win32ResourcesNode(win32Resources);
         }
 
         public SignatureContext InputModuleContext;
@@ -233,6 +237,8 @@ namespace ILCompiler.DependencyAnalysis
 
         public CopiedCorHeaderNode CopiedCorHeaderNode;
 
+        public Win32ResourcesNode Win32ResourcesNode;
+
         public HeaderNode Header;
 
         public RuntimeFunctionsTableNode RuntimeFunctionsTable;
@@ -557,6 +563,9 @@ namespace ILCompiler.DependencyAnalysis
             graph.AddRoot(Header, "ReadyToRunHeader is always generated");
             graph.AddRoot(CopiedCorHeaderNode, "MSIL COR header is always generated");
 
+            if (Win32ResourcesNode != null)
+                graph.AddRoot(Win32ResourcesNode, "Win32 Resources are placed if not empty");
+
             MetadataManager.AttachToDependencyGraph(graph);
         }
 
index ff61006..89c86d1 100644 (file)
@@ -8,7 +8,7 @@ using System.Collections.Generic;
 using ILCompiler.DependencyAnalysis;
 using ILCompiler.DependencyAnalysis.ReadyToRun;
 using ILCompiler.DependencyAnalysisFramework;
-
+using ILCompiler.Win32Resources;
 using Internal.IL;
 using Internal.JitInterface;
 using Internal.TypeSystem;
@@ -81,13 +81,31 @@ namespace ILCompiler
             SignatureContext signatureContext = new SignatureContext(_inputModule, moduleTokenResolver);
             CopiedCorHeaderNode corHeaderNode = new CopiedCorHeaderNode(_inputModule);
 
+            // Produce a ResourceData where the IBC PROFILE_DATA entry has been filtered out
+            ResourceData win32Resources = new ResourceData(_inputModule, (object type, object name, ushort language) =>
+            {
+                if (!(type is string) || !(name is string))
+                    return true;
+                if (language != 0)
+                    return true;
+
+                string typeString = (string)type;
+                string nameString = (string)name;
+
+                if ((typeString == "IBC") && (nameString == "PROFILE_DATA"))
+                    return false;
+
+                return true;
+            });
+
             ReadyToRunCodegenNodeFactory factory = new ReadyToRunCodegenNodeFactory(
                 _context,
                 _compilationGroup,
                 _nameMangler,
                 moduleTokenResolver,
                 signatureContext,
-                corHeaderNode);
+                corHeaderNode,
+                win32Resources);
 
             DependencyAnalyzerBase<NodeFactory> graph = CreateDependencyGraph(factory);
 
index a04a1d6..73baa78 100644 (file)
     <Compile Include="Compiler\DependencyAnalysis\ReadyToRun\TransitionBlock.cs" />
     <Compile Include="Compiler\DependencyAnalysis\ReadyToRun\TypeFixupSignature.cs" />
     <Compile Include="Compiler\DependencyAnalysis\ReadyToRun\TypesTableNode.cs" />
+    <Compile Include="Compiler\DependencyAnalysis\ReadyToRun\Win32ResourcesNode.cs" />
     <Compile Include="Compiler\DependencyAnalysis\ReadyToRunSymbolNodeFactory.cs" />
     <Compile Include="Compiler\DependencyAnalysis\TypeAndMethod.cs" />
     <Compile Include="Compiler\IRootingServiceProvider.cs" />
index 5eaa351..394d877 100644 (file)
@@ -7,10 +7,7 @@ using System.Collections.Generic;
 using System.Collections.Immutable;
 using System.Diagnostics;
 using System.IO;
-using System.Linq;
-using System.Reflection;
 using System.Reflection.Metadata;
-using System.Reflection.Metadata.Ecma335;
 using System.Reflection.PortableExecutable;
 
 using ILCompiler.DependencyAnalysis;
@@ -77,11 +74,6 @@ namespace ILCompiler.PEWriter
         /// Name of the initialized data section.
         /// </summary>
         public const string SDataSectionName = ".sdata";
-
-        /// <summary>
-        /// Name of the resource section.
-        /// </summary>
-        public const string RsrcSectionName = ".rsrc";
         
         /// <summary>
         /// Name of the relocation section.
@@ -196,14 +188,6 @@ namespace ILCompiler.PEWriter
                 _customSections.Add(section.SectionName);
             }
 
-            foreach (SectionHeader sectionHeader in peReader.PEHeaders.SectionHeaders)
-            {
-                if (_sectionBuilder.FindSection(sectionHeader.Name) == null)
-                {
-                    _sectionBuilder.AddSection(sectionHeader.Name, sectionHeader.SectionCharacteristics, peReader.PEHeaders.PEHeader.SectionAlignment);
-                }
-            }
-
             if (_sectionBuilder.FindSection(R2RPEBuilder.RelocSectionName) == null)
             {
                 // Always inject the relocation section to the end of section list
@@ -233,6 +217,11 @@ namespace ILCompiler.PEWriter
             _sectionBuilder.SetCorHeader(symbol, headerSize);
         }
 
+        public void SetWin32Resources(ISymbolNode symbol, int resourcesSize)
+        {
+            _sectionBuilder.SetWin32Resources(symbol, resourcesSize);
+        }
+
         /// <summary>
         /// Emit a single object data item into the output R2R PE file using the section builder.
         /// </summary>
@@ -407,8 +396,6 @@ namespace ILCompiler.PEWriter
         protected override PEDirectoriesBuilder GetDirectories()
         {
             PEDirectoriesBuilder builder = new PEDirectoriesBuilder();
-            builder.CorHeaderTable = RelocateDirectoryEntry(_peReader.PEHeaders.PEHeader.CorHeaderTableDirectory);
-            builder.ResourceTable = RelocateDirectoryEntry(_peReader.PEHeaders.PEHeader.ResourceTableDirectory);
 
             _sectionBuilder.UpdateDirectories(builder);
 
@@ -502,57 +489,6 @@ namespace ILCompiler.PEWriter
                 _sectionRVAs[outputSectionIndex] = sectionStartRva;
             }
 
-            int inputSectionIndex = _peReader.PEHeaders.SectionHeaders.Count() - 1;
-            while (inputSectionIndex >= 0 && _peReader.PEHeaders.SectionHeaders[inputSectionIndex].Name != name)
-            {
-                inputSectionIndex--;
-            }
-            if (inputSectionIndex >= 0)
-            {
-                SectionHeader sectionHeader = _peReader.PEHeaders.SectionHeaders[inputSectionIndex];
-                int sectionOffset = (_peReader.IsLoadedImage ? sectionHeader.VirtualAddress : sectionHeader.PointerToRawData);
-                int rvaDelta = location.RelativeVirtualAddress - sectionHeader.VirtualAddress;
-                
-                _sectionRvaDeltas.Add(new SectionRVADelta(
-                    startRVA: sectionHeader.VirtualAddress,
-                    endRVA: sectionHeader.VirtualAddress + Math.Max(sectionHeader.VirtualSize, sectionHeader.SizeOfRawData),
-                    deltaRVA: rvaDelta));
-                
-                
-                int bytesToRead = Math.Min(sectionHeader.SizeOfRawData, sectionHeader.VirtualSize);
-                BlobReader inputSectionReader = _peReader.GetEntireImage().GetReader(sectionOffset, bytesToRead);
-                        
-                if (name == RsrcSectionName)
-                {
-                    // There seems to be a bug in BlobBuilder - when we LinkSuffix to an empty blob builder,
-                    // the blob data goes out of sync and WriteContentTo outputs garbage.
-                    sectionDataBuilder = PEResourceHelper.Relocate(inputSectionReader, rvaDelta);
-                }
-
-                int alignedSize = sectionHeader.VirtualSize;
-                    
-                // When custom section data is present, align the section size to 4K to prevent
-                // pre-generated MSIL relocations from tampering with native relocations.
-                if (_customSections.Contains(name))
-                {
-                    alignedSize = (alignedSize + 0xFFF) & ~0xFFF;
-                }
-
-                if (sectionDataBuilder != null)
-                {
-                    if (alignedSize > bytesToRead)
-                    {
-                        // If the number of bytes read from the source PE file is less than the virtual size,
-                        // zero pad to the end of virtual size before emitting extra section data
-                        sectionDataBuilder.WriteBytes(0, alignedSize - bytesToRead);
-                    }
-
-                    location = new SectionLocation(
-                        location.RelativeVirtualAddress + sectionDataBuilder.Count,
-                        location.PointerToRawData + sectionDataBuilder.Count);
-                }
-            }
-
             BlobBuilder extraData = _sectionBuilder.SerializeSection(name, location);
             if (extraData != null)
             {
@@ -587,170 +523,6 @@ namespace ILCompiler.PEWriter
             return sectionDataBuilder;
         }
     }
-
-    /// <summary>
-    /// When copying PE contents we may need to move the resource section, however its internal
-    /// ResourceDataEntry records hold RVA's so they need to be relocated. Thankfully the resource
-    /// data model is very simple so that we just traverse the structure using offset constants.
-    /// </summary>
-    unsafe sealed class PEResourceHelper
-    {
-        /// <summary>
-        /// Field offsets in the resource directory table.
-        /// </summary>
-        private static class DirectoryTable
-        {
-            public const int Characteristics = 0x0;
-            public const int TimeDateStamp = 0x04;
-            public const int MajorVersion = 0x08;
-            public const int MinorVersion = 0x0A;
-            public const int NumberOfNameEntries = 0x0C;
-            public const int NumberOfIDEntries = 0x0E;
-            public const int Size = 0x10;
-        }
-        
-        /// <summary>
-        /// Field offsets in the resource directory entry.
-        /// </summary>
-        private static class DirectoryEntry
-        {
-            public const int NameOffsetOrID = 0x0;
-            public const int DataOrSubdirectoryOffset = 0x4;
-            public const int Size = 0x8;
-        }
-
-        /// <summary>
-        /// When the 4-byte value at the offset DirectoryEntry.DataOrSubdirectoryOffset
-        /// has 31-st bit set, it's a subdirectory table entry; when it's clear, it's a
-        /// resource data entry.
-        /// </summary>
-        private const int EntryOffsetIsSubdirectory = unchecked((int)0x80000000u);
-        
-        /// <summary>
-        /// Field offsets in the resource data entry.
-        /// </summary>
-        private static class DataEntry
-        {
-            public const int RVA = 0x0;
-            public const int Size = 0x4;
-            public const int Codepage = 0x8;
-            public const int Reserved = 0xC;
-        }
-        
-        /// <summary>
-        /// Blob reader representing the input resource section.
-        /// </summary>
-        private BlobReader _reader;
-
-        /// <summary>
-        /// This BlobBuilder holds the relocated resource section after the ctor finishes.
-        /// </summary>
-        private BlobBuilder _builder;
-
-        /// <summary>
-        /// Relocation delta (the difference between input and output RVA of the resource section).
-        /// </summary>
-        private int _delta;
-
-        /// <summary>
-        /// Offsets within the resource section representing RVA's in the resource data entries
-        /// that need relocating.
-        /// </summary>
-        private List<int> _offsetsOfRvasToRelocate;
-        
-        /// <summary>
-        /// Public API receives the input resource section reader and the relocation delta
-        /// and returns a blob builder representing the relocated resource section.
-        /// </summary>
-        /// <param name="reader">Blob reader representing the input resource section</param>
-        /// <param name="delta">Relocation delta to apply (value to add to RVA's)</param>
-        public static BlobBuilder Relocate(BlobReader reader, int delta)
-        {
-            return new PEResourceHelper(reader, delta)._builder;
-        }
-        
-        /// <summary>
-        /// Private constructor first traverses the internal graph of resource tables
-        /// and collects offsets to RVA's that need relocation; after that we sort the list of
-        /// offsets and do a linear copying pass patching the RVA cells with the updated values.
-        /// </summary>
-        /// <param name="reader">Blob reader representing the input resource section</param>
-        /// <param name="delta">Relocation delta to apply (value to add to RVA's)</param>
-        private PEResourceHelper(BlobReader reader, int delta)
-        {
-            _reader = reader;
-            _builder = new BlobBuilder();
-            _delta = delta;
-            
-            _offsetsOfRvasToRelocate = new List<int>();
-            
-            TraverseDirectoryTable(tableOffset: 0);
-
-            _offsetsOfRvasToRelocate.Sort();
-            int currentOffset = 0;
-            
-            _reader.Reset();
-            foreach (int offsetOfRvaToRelocate in _offsetsOfRvasToRelocate)
-            {
-                int bytesToCopy = offsetOfRvaToRelocate - currentOffset;
-                Debug.Assert(bytesToCopy >= 0);
-                if (bytesToCopy > 0)
-                {
-                    _builder.WriteBytes(_reader.CurrentPointer, bytesToCopy);
-                    _reader.Offset += bytesToCopy;
-                    currentOffset += bytesToCopy;
-                }
-                int rva = _reader.ReadInt32();
-                _builder.WriteInt32(rva + delta);
-                currentOffset += sizeof(int);
-            }
-            if (_reader.RemainingBytes > 0)
-            {
-                _builder.WriteBytes(_reader.CurrentPointer, _reader.RemainingBytes);
-            }
-        }
-        
-        /// <summary>
-        /// Traverse a single directory table at a given offset within the resource section.
-        /// Please note the method might end up calling itself recursively through the call graph
-        /// TraverseDirectoryTable -&gt; TraverseDirectoryEntry -&gt; TraverseDirectoryTable.
-        /// Maximum depth is equal to depth of the table graph - today resources use 3.
-        /// </summary>
-        /// <param name="tableOffset">Offset of the resource directory table within the resource section</param>
-        private void TraverseDirectoryTable(int tableOffset)
-        {
-            _reader.Offset = tableOffset + DirectoryTable.NumberOfNameEntries;
-            int numberOfNameEntries = _reader.ReadInt16();
-            int numberOfIDEntries = _reader.ReadInt16();
-            int totalEntries = numberOfNameEntries + numberOfIDEntries;
-            for (int entryIndex = 0; entryIndex < totalEntries; entryIndex++)
-            {
-                TraverseDirectoryEntry(tableOffset + DirectoryTable.Size + entryIndex * DirectoryEntry.Size);
-            }
-        }
-        
-        /// <summary>
-        /// Traverse a single directory entry (name- and ID-based directory entries are processed
-        /// the same way as we're not really interested in the entry identifier, just in the
-        /// data / table pointers.
-        /// </summary>
-        /// <param name="entryOffset">Offset of the resource directory entry within the resource section</param>
-        private void TraverseDirectoryEntry(int entryOffset)
-        {
-            _reader.Offset = entryOffset + DirectoryEntry.DataOrSubdirectoryOffset;
-            int dataOrSubdirectoryOffset = _reader.ReadInt32();
-            if ((dataOrSubdirectoryOffset & EntryOffsetIsSubdirectory) != 0)
-            {
-                // subdirectory offset
-                TraverseDirectoryTable(dataOrSubdirectoryOffset & ~EntryOffsetIsSubdirectory);
-            }
-            else
-            {
-                // data entry offset
-                _offsetsOfRvasToRelocate.Add(dataOrSubdirectoryOffset + DataEntry.RVA);
-            }
-        }
-    }
     
     /// <summary>
     /// Simple helper for copying the various global values in the PE header.
index 92ed56f..c2c4fbe 100644 (file)
@@ -252,6 +252,16 @@ namespace ILCompiler.PEWriter
         int _corHeaderSize;
 
         /// <summary>
+        /// Symbol representing the start of the win32 resources
+        /// </summary>
+        ISymbolNode _win32ResourcesSymbol;
+
+        /// <summary>
+        /// Size of the win32 resources
+        /// </summary>
+        int _win32ResourcesSize;
+
+        /// <summary>
         /// Padding 4-byte sequence to use in code section. Typically corresponds
         /// to some interrupt to be thrown at "invalid" IP addresses.
         /// </summary>
@@ -372,6 +382,12 @@ namespace ILCompiler.PEWriter
             _corHeaderSize = headerSize;
         }
 
+        public void SetWin32Resources(ISymbolNode symbol, int resourcesSize)
+        {
+            _win32ResourcesSymbol = symbol;
+            _win32ResourcesSize = resourcesSize;
+        }
+
         private CoreRTNameMangler _nameMangler;
         
         private NameMangler GetNameMangler()
@@ -739,6 +755,14 @@ namespace ILCompiler.PEWriter
                 directoriesBuilder.CorHeaderTable = new DirectoryEntry(section.RVAWhenPlaced + symbolTarget.Offset, _corHeaderSize);
             }
 
+            if (_win32ResourcesSymbol != null)
+            {
+                SymbolTarget symbolTarget = _symbolMap[_win32ResourcesSymbol];
+                Section section = _sections[symbolTarget.SectionIndex];
+                Debug.Assert(section.RVAWhenPlaced != 0);
+                directoriesBuilder.ResourceTable = new DirectoryEntry(section.RVAWhenPlaced + symbolTarget.Offset, _win32ResourcesSize);
+            }
+
             if (_exportDirectoryEntry.Size != 0)
             {
                 directoriesBuilder.ExportTable = _exportDirectoryEntry;
index e6cf0c3..b9b3353 100644 (file)
@@ -4,14 +4,14 @@
 
 using System;
 using System.Reflection.Metadata;
+using System.Reflection.PortableExecutable;
 
 namespace ILCompiler.Win32Resources
 {
     public unsafe partial class ResourceData
     {
-        private void ReadResourceData()
+        private void ReadResourceData(BlobReader resourceReader, PEReader peFile, Func<object, object, ushort, bool> resourceFilter)
         {
-            BlobReader resourceReader = _resourceDataBlob;
             DoResourceDirectoryRead(resourceReader, 0, ProcessOuterResource);
             return;
 
@@ -43,9 +43,15 @@ namespace ILCompiler.Win32Resources
                         IMAGE_RESOURCE_DATA_ENTRY resourceData = new IMAGE_RESOURCE_DATA_ENTRY(ref resourceReader);
 
                         // The actual resource data offset is relative to the start address of the file
-                        BlobReader resourceDataBlob = _peFile.GetSectionData(checked((int)resourceData.OffsetToData)).GetReader(0, checked((int)resourceData.Size));
+                        BlobReader resourceDataBlob = peFile.GetSectionData(checked((int)resourceData.OffsetToData)).GetReader(0, checked((int)resourceData.Size));
                         byte[] data = resourceDataBlob.ReadBytes((int)resourceData.Size);
 
+                        if (resourceFilter != null)
+                        {
+                            // If the filter returns false, don't add this resource to the model
+                            if (!resourceFilter(typeName, name, (ushort)languageName))
+                                return;
+                        }
                         AddResource(typeName, name, (ushort)languageName, data);
                     }
                 }
index f842a66..266e4e2 100644 (file)
@@ -2,81 +2,37 @@
 // 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;
 using System.Collections.Generic;
 
 namespace ILCompiler.Win32Resources
 {
     public unsafe partial class ResourceData
     {
-        private readonly List<ResType_Ordinal> _resTypeHeadID = new List<ResType_Ordinal>();
-        private readonly List<ResType_Name> _resTypeHeadName = new List<ResType_Name>();
+        private readonly SortedDictionary<ushort, ResType> _resTypeHeadID = new SortedDictionary<ushort, ResType>();
+        private readonly SortedDictionary<string, ResType> _resTypeHeadName = new SortedDictionary<string, ResType>(StringComparer.Ordinal);
 
-        private class OrdinalName
+        private class ResLanguage
         {
-            public OrdinalName(ushort ordinal) { Ordinal = ordinal; }
-            public readonly ushort Ordinal;
-        }
-
-        private interface IUnderlyingName<T>
-        {
-            T Name { get; }
-        }
+            public ResLanguage(byte[] data)
+            {
+                DataEntry = data;
+            }
 
-        private class ResName
-        {
             public uint DataSize => (uint)DataEntry.Length;
             public byte[] DataEntry;
-            public ushort NumberOfLanguages;
-            public ushort LanguageId;
         }
 
-        private class ResName_Name : ResName, IUnderlyingName<string>
+        private class ResName
         {
-            public ResName_Name(string name)
-            {
-                Name = name;
-            }
-
-            public string Name { get; }
-        }
-
-        private class ResName_Ordinal : ResName, IUnderlyingName<ushort>
-        {
-            public ResName_Ordinal(ushort name)
-            {
-                Name = new OrdinalName(name);
-            }
-
-            public OrdinalName Name;
-            ushort IUnderlyingName<ushort>.Name => Name.Ordinal;
+            public SortedDictionary<ushort, ResLanguage> Languages = new SortedDictionary<ushort, ResLanguage>();
         }
 
         private class ResType
         {
-            public List<ResName_Name> NameHeadName = new List<ResName_Name>();
-            public List<ResName_Ordinal> NameHeadID = new List<ResName_Ordinal>();
-        }
-
-        private class ResType_Ordinal : ResType, IUnderlyingName<ushort>
-        {
-            public ResType_Ordinal(ushort type)
-            {
-                Type = new OrdinalName(type);
-            }
-
-            public OrdinalName Type;
-            ushort IUnderlyingName<ushort>.Name => Type.Ordinal;
+            public SortedDictionary<string, ResName> NameHeadName = new SortedDictionary<string, ResName>(StringComparer.Ordinal);
+            public SortedDictionary<ushort, ResName> NameHeadID = new SortedDictionary<ushort, ResName>();
         }
 
-        private class ResType_Name : ResType, IUnderlyingName<string>
-        {
-            public ResType_Name(string type)
-            {
-                Type = type;
-            }
-
-            public string Type { get; set; }
-            string IUnderlyingName<string>.Name => Type;
-        }
     }
 }
index 8272811..37f80ab 100644 (file)
@@ -11,161 +11,45 @@ namespace ILCompiler.Win32Resources
     {
         private void AddResource(object type, object name, ushort language, byte[] data)
         {
-            ResType resType = null;
-            // Allocate new object in case it is needed.
-            ResType newResType;
-            int newIndex;
+            ResType resType;
 
-            IList typeList;
-            bool updateExisting;
             if (type is ushort)
             {
-                ResType_Ordinal newOrdinalType = new ResType_Ordinal((ushort)type);
-                newResType = newOrdinalType;
-                typeList = _resTypeHeadID;
-
-                newIndex = GetIndexOfFirstItemMatchingInListOrInsertionPoint(typeList, (ushort left, ushort right) => (int)left - (int)right, (ushort)type, out updateExisting);
-            }
-            else
-            {
-                ResType_Name newStringType = new ResType_Name((string)type);
-                newResType = newStringType;
-                typeList = _resTypeHeadName;
-
-                newIndex = GetIndexOfFirstItemMatchingInListOrInsertionPoint(typeList, string.CompareOrdinal, (string)type, out updateExisting);
-            }
-
-            if (updateExisting)
-            {
-                resType = (ResType)typeList[newIndex];
-            }
-            else
-            {
-                // This is a new type
-                if (newIndex == -1)
-                    typeList.Add(newResType);
-                else
-                    typeList.Insert(newIndex, newResType);
-
-                resType = newResType;
-            }
-
-            Type resNameType;
-            IList nameList;
-            int nameIndex;
-
-            if (name is ushort)
-            {
-                nameList = resType.NameHeadID;
-                resNameType = typeof(ResName_Ordinal);
-                nameIndex = GetIndexOfFirstItemMatchingInListOrInsertionPoint(nameList, (ushort left, ushort right) => (int)left - (int)right, (ushort)name, out updateExisting);
-            }
-            else
-            {
-                nameList = resType.NameHeadName;
-                resNameType = typeof(ResName_Name);
-                nameIndex = GetIndexOfFirstItemMatchingInListOrInsertionPoint(nameList, string.CompareOrdinal, (string)name, out updateExisting);
-            }
-
-            if (updateExisting)
-            {
-                // We have at least 1 language with the same type/name. Insert/delete from language list
-                ResName resName = (ResName)nameList[nameIndex];
-                int newNumberOfLanguages = (int)resName.NumberOfLanguages + (data != null ? 1 : -1);
-
-                int newIndexForNewOrUpdatedNameWithMatchingLanguage = GetIndexOfFirstItemMatchingInListOrInsertionPoint(nameList, nameIndex,
-                    resName.NumberOfLanguages, (object o) => ((ResName)o).LanguageId, (ushort left, ushort right) => (int)left - (int)right, language, out bool exactLanguageExists);
-
-                if (exactLanguageExists)
+                if (!_resTypeHeadID.TryGetValue((ushort)type, out resType))
                 {
-                    if (data == null)
-                    {
-                        // delete item
-                        nameList.RemoveAt(newIndexForNewOrUpdatedNameWithMatchingLanguage);
-
-                        if (newNumberOfLanguages > 0)
-                        {
-                            // if another name is still present, update the number of languages counter
-                            resName = (ResName)nameList[nameIndex];
-                            resName.NumberOfLanguages = (ushort)newNumberOfLanguages;
-                        }
-
-                        if ((resType.NameHeadID.Count == 0) && (resType.NameHeadName.Count == 0))
-                        {
-                            /* type list completely empty? */
-                            typeList.Remove(resType);
-                        }
-                    }
-                    else
-                    {
-                        // Resource file has two copies of same resource... ignore second copy
-                        return;
-                    }
-                }
-                else
-                {
-                    // Insert a new name at the new spot
-                    AddNewName(nameList, resNameType, newIndexForNewOrUpdatedNameWithMatchingLanguage, name, language, data);
-                    // Update the NumberOfLanguages for the language list
-                    resName = (ResName)nameList[nameIndex];
-                    resName.NumberOfLanguages = (ushort)newNumberOfLanguages;
+                    resType = new ResType();
+                    _resTypeHeadID[(ushort)type] = resType;
                 }
             }
             else
             {
-                // This is a new name in a new language list
-                if (data == null)
+                if (!_resTypeHeadName.TryGetValue((string)type, out resType))
                 {
-                    // Can't delete new name
-                    throw new ArgumentException();
+                    resType = new ResType();
+                    _resTypeHeadName[(string)type] = resType;
                 }
-
-                AddNewName(nameList, resNameType, nameIndex, name, language, data);
             }
-        }
 
-        private static int GetIndexOfFirstItemMatchingInListOrInsertionPoint<T>(IList list, Func<T, T, int> compareFunction, T comparand, out bool exists)
-        {
-            return GetIndexOfFirstItemMatchingInListOrInsertionPoint(list, 0, list.Count, (object o) => ((IUnderlyingName<T>)o).Name, compareFunction, comparand, out exists);
-        }
+            ResName resName;
 
-        private static int GetIndexOfFirstItemMatchingInListOrInsertionPoint<T>(IList list, int start, int count, Func<object, T> getComparandFromListElem, Func<T, T, int> compareFunction, T comparand, out bool exists)
-        {
-            int i = start;
-            for (; i < (start + count); i++)
+            if (name is ushort)
             {
-                int iCompare = compareFunction(comparand, getComparandFromListElem(list[i]));
-                if (iCompare == 0)
-                {
-                    exists = true;
-                    return i;
-                }
-                else if (iCompare < 0)
+                if (!resType.NameHeadID.TryGetValue((ushort)name, out resName))
                 {
-                    exists = false;
-                    return i;
+                    resName = new ResName();
+                    resType.NameHeadID[(ushort)name] = resName;
                 }
             }
-
-            exists = false;
-            if ((start + count) < list.Count)
+            else
             {
-                return start + count;
+                if (!resType.NameHeadName.TryGetValue((string)name, out resName))
+                {
+                    resName = new ResName();
+                    resType.NameHeadName[(string)name] = resName;
+                }
             }
-            return -1;
-        }
 
-        private void AddNewName(IList list, Type resNameType, int insertPoint, object name, ushort language, byte[] data)
-        {
-            ResName newResName = (ResName)Activator.CreateInstance(resNameType, name);
-            newResName.LanguageId = language;
-            newResName.NumberOfLanguages = 1;
-            newResName.DataEntry = data;
-
-            if (insertPoint == -1)
-                list.Add(newResName);
-            else
-                list.Insert(insertPoint, newResName);
+            resName.Languages[language] = new ResLanguage(data);
         }
 
         private byte[] FindResourceInternal(object name, object type, ushort language)
@@ -174,58 +58,34 @@ namespace ILCompiler.Win32Resources
 
             if (type is ushort)
             {
-                foreach (ResType_Ordinal candidate in _resTypeHeadID)
-                {
-                    if (candidate.Type.Ordinal == (ushort)type)
-                    {
-                        resType = candidate;
-                        break;
-                    }
-                }
+                _resTypeHeadID.TryGetValue((ushort)type, out resType);
             }
             if (type is string)
             {
-                foreach (ResType_Name candidate in _resTypeHeadName)
-                {
-                    if (candidate.Type == (string)type)
-                    {
-                        resType = candidate;
-                        break;
-                    }
-                }
+                _resTypeHeadName.TryGetValue((string)type, out resType);
             }
 
             if (resType == null)
                 return null;
 
+            ResName resName = null;
+
             if (name is ushort)
             {
-                foreach (ResName_Ordinal candidate in resType.NameHeadID)
-                {
-                    if (candidate.Name.Ordinal != (ushort)type)
-                        continue;
-
-                    if (candidate.LanguageId != language)
-                        continue;
-
-                    return (byte[])candidate.DataEntry.Clone();
-                }
+                resType.NameHeadID.TryGetValue((ushort)name, out resName);
             }
             if (name is string)
             {
-                foreach (ResName_Name candidate in resType.NameHeadName)
-                {
-                    if (candidate.Name != (string)name)
-                        continue;
+                resType.NameHeadName.TryGetValue((string)name, out resName);
+            }
 
-                    if (candidate.LanguageId != language)
-                        continue;
+            if (resName == null)
+                return null;
 
-                    return (byte[])candidate.DataEntry.Clone();
-                }
-            }
+            if (!resName.Languages.TryGetValue(language, out ResLanguage resLanguage))
+                return null;
 
-            return null;
+            return (byte[])resLanguage.DataEntry.Clone();
         }
     }
 }
index a5421f1..74aa6d1 100644 (file)
@@ -2,9 +2,12 @@
 // 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 System.Runtime.InteropServices;
 using System.Reflection.Metadata;
 
+using ILCompiler.DependencyAnalysis;
+
 namespace ILCompiler.Win32Resources
 {
     public unsafe partial class ResourceData
@@ -21,6 +24,16 @@ namespace ILCompiler.Win32Resources
                 NumberOfIdEntries = blobReader.ReadUInt16();
             }
 
+            public static void Write(ref ObjectDataBuilder builder, ushort namedEntries, ushort idEntries)
+            {
+                builder.EmitUInt(0); // Characteristics
+                builder.EmitUInt(0); // TimeDateStamp
+                builder.EmitUShort(4); // MajorVersion
+                builder.EmitUShort(0); // MinorVersion
+                builder.EmitUShort(namedEntries);
+                builder.EmitUShort(idEntries);
+            }
+
             public readonly uint Characteristics;
             public readonly uint TimeDateStamp;
             public readonly ushort MajorVersion;
@@ -37,6 +50,24 @@ namespace ILCompiler.Win32Resources
                 OffsetToData = blobReader.ReadUInt32();
             }
 
+            public static ObjectDataBuilder.Reservation Write(ref ObjectDataBuilder dataBuilder, string name, IDictionary<string, List<ObjectDataBuilder.Reservation>> nameTable)
+            {
+                List<ObjectDataBuilder.Reservation> relatedNameReferences;
+                if (!nameTable.TryGetValue(name, out relatedNameReferences))
+                {
+                    relatedNameReferences = new List<ObjectDataBuilder.Reservation>();
+                    nameTable[name] = relatedNameReferences;
+                }
+                relatedNameReferences.Add(dataBuilder.ReserveInt());
+                return dataBuilder.ReserveInt();
+            }
+
+            public static ObjectDataBuilder.Reservation Write(ref ObjectDataBuilder dataBuilder, ushort id)
+            {
+                dataBuilder.EmitInt(id);
+                return dataBuilder.ReserveInt();
+            }
+
             public readonly uint Name;
             public readonly uint OffsetToData;
         }
@@ -52,6 +83,14 @@ namespace ILCompiler.Win32Resources
                 Reserved = blobReader.ReadUInt32();
             }
 
+            public static void Write(ref ObjectDataBuilder dataBuilder, ISymbolNode node, int offsetFromSymbol, int sizeOfData)
+            {
+                dataBuilder.EmitReloc(node, RelocType.IMAGE_REL_BASED_ADDR32NB, offsetFromSymbol);
+                dataBuilder.EmitInt(sizeOfData);
+                dataBuilder.EmitInt(1252);  // CODEPAGE = DEFAULT_CODEPAGE
+                dataBuilder.EmitInt(0); // RESERVED
+            }
+
             public uint OffsetToData;
             public uint Size;
             private uint CodePage;
index 9c6d342..daa10dd 100644 (file)
@@ -2,10 +2,15 @@
 // The .NET Foundation licenses this file to you under the MIT license.
 // See the LICENSE file in the project root for more information.
 
-using Internal.TypeSystem.Ecma;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
 using System.Reflection.Metadata;
 using System.Reflection.PortableExecutable;
 
+using ILCompiler.DependencyAnalysis;
+using Internal.TypeSystem.Ecma;
+
 namespace ILCompiler.Win32Resources
 {
     /// <summary>
@@ -14,23 +19,20 @@ namespace ILCompiler.Win32Resources
     /// </summary>
     public unsafe partial class ResourceData
     {
-        BlobReader _resourceDataBlob;
-        PEReader _peFile;
-
         /// <summary>
         /// Initialize a ResourceData instance from a PE file
         /// </summary>
         /// <param name="ecmaModule"></param>
-        public ResourceData(EcmaModule ecmaModule)
+        public ResourceData(EcmaModule ecmaModule, Func<object, object, ushort, bool> resourceFilter = null)
         {
-            var ecmaData = ecmaModule.PEReader.GetEntireImage().GetContent();
-            _peFile = ecmaModule.PEReader;
+            System.Collections.Immutable.ImmutableArray<byte> ecmaData = ecmaModule.PEReader.GetEntireImage().GetContent();
+            PEReader peFile = ecmaModule.PEReader;
 
-            DirectoryEntry resourceDirectory = _peFile.PEHeaders.PEHeader.ResourceTableDirectory;
+            DirectoryEntry resourceDirectory = peFile.PEHeaders.PEHeader.ResourceTableDirectory;
             if (resourceDirectory.Size != 0)
             {
-                _resourceDataBlob = ecmaModule.PEReader.GetSectionData(resourceDirectory.RelativeVirtualAddress).GetReader(0, resourceDirectory.Size);
-                ReadResourceData();
+                BlobReader resourceDataBlob = ecmaModule.PEReader.GetSectionData(resourceDirectory.RelativeVirtualAddress).GetReader(0, resourceDirectory.Size);
+                ReadResourceData(resourceDataBlob, peFile, resourceFilter);
             }
         }
 
@@ -65,5 +67,97 @@ namespace ILCompiler.Win32Resources
         {
             return FindResourceInternal(name, type, language);
         }
+
+        public bool IsEmpty
+        {
+            get
+            {
+                if (_resTypeHeadID.Count > 0)
+                    return false;
+
+                if (_resTypeHeadName.Count > 0)
+                    return false;
+
+                return true;
+            }
+        }
+
+        public void WriteResources(ISymbolNode nodeAssociatedWithDataBuilder, ref ObjectDataBuilder dataBuilder)
+        {
+            Debug.Assert(dataBuilder.CountBytes == 0);
+
+            SortedDictionary<string, List<ObjectDataBuilder.Reservation>> nameTable = new SortedDictionary<string, List<ObjectDataBuilder.Reservation>>();
+            Dictionary<ResLanguage, int> dataEntryTable = new Dictionary<ResLanguage, int>();
+            List<Tuple<ResType, ObjectDataBuilder.Reservation>> resTypes = new List<Tuple<ResType, ObjectDataBuilder.Reservation>>();
+            List<Tuple<ResName, ObjectDataBuilder.Reservation>> resNames = new List<Tuple<ResName, ObjectDataBuilder.Reservation>>();
+            List<Tuple<ResLanguage, ObjectDataBuilder.Reservation>> resLanguages = new List<Tuple<ResLanguage, ObjectDataBuilder.Reservation>>();
+
+            IMAGE_RESOURCE_DIRECTORY.Write(ref dataBuilder, checked((ushort)_resTypeHeadName.Count), checked((ushort)_resTypeHeadID.Count));
+            foreach (KeyValuePair<string, ResType> res in _resTypeHeadName)
+            {
+                resTypes.Add(new Tuple<ResType, ObjectDataBuilder.Reservation>(res.Value, IMAGE_RESOURCE_DIRECTORY_ENTRY.Write(ref dataBuilder, res.Key, nameTable)));
+            }
+            foreach (KeyValuePair<ushort, ResType> res in _resTypeHeadID)
+            {
+                resTypes.Add(new Tuple<ResType, ObjectDataBuilder.Reservation>(res.Value, IMAGE_RESOURCE_DIRECTORY_ENTRY.Write(ref dataBuilder, res.Key)));
+            }
+
+            foreach (Tuple<ResType, ObjectDataBuilder.Reservation> type in resTypes)
+            {
+                dataBuilder.EmitUInt(type.Item2, (uint)dataBuilder.CountBytes | 0x80000000);
+                IMAGE_RESOURCE_DIRECTORY.Write(ref dataBuilder, checked((ushort)type.Item1.NameHeadName.Count), checked((ushort)type.Item1.NameHeadID.Count));
+
+                foreach (KeyValuePair<string, ResName> res in type.Item1.NameHeadName)
+                {
+                    resNames.Add(new Tuple<ResName, ObjectDataBuilder.Reservation>(res.Value, IMAGE_RESOURCE_DIRECTORY_ENTRY.Write(ref dataBuilder, res.Key, nameTable)));
+                }
+                foreach (KeyValuePair<ushort, ResName> res in type.Item1.NameHeadID)
+                {
+                    resNames.Add(new Tuple<ResName, ObjectDataBuilder.Reservation>(res.Value, IMAGE_RESOURCE_DIRECTORY_ENTRY.Write(ref dataBuilder, res.Key)));
+                }
+            }
+
+            foreach (Tuple<ResName, ObjectDataBuilder.Reservation> type in resNames)
+            {
+                dataBuilder.EmitUInt(type.Item2, (uint)dataBuilder.CountBytes | 0x80000000);
+                IMAGE_RESOURCE_DIRECTORY.Write(ref dataBuilder, 0, checked((ushort)type.Item1.Languages.Count));
+                foreach (KeyValuePair<ushort, ResLanguage> res in type.Item1.Languages)
+                {
+                    resLanguages.Add(new Tuple<ResLanguage, ObjectDataBuilder.Reservation>(res.Value, IMAGE_RESOURCE_DIRECTORY_ENTRY.Write(ref dataBuilder, res.Key)));
+                }
+            }
+
+            // Emit name table
+            dataBuilder.PadAlignment(2); // name table is 2 byte aligned
+            foreach (KeyValuePair<string, List<ObjectDataBuilder.Reservation>> name in nameTable)
+            {
+                foreach (ObjectDataBuilder.Reservation reservation in name.Value)
+                {
+                    dataBuilder.EmitUInt(reservation, (uint)dataBuilder.CountBytes | 0x80000000);
+                }
+
+                dataBuilder.EmitUShort(checked((ushort)name.Key.Length));
+                foreach (char c in name.Key)
+                {
+                    dataBuilder.EmitUShort((ushort)c);
+                }
+            }
+
+            // Emit byte arrays of resource data, capture the offsets
+            foreach (Tuple<ResLanguage, ObjectDataBuilder.Reservation> language in resLanguages)
+            {
+                dataBuilder.PadAlignment(4); // Data in resource files is 4 byte aligned
+                dataEntryTable.Add(language.Item1, dataBuilder.CountBytes);
+                dataBuilder.EmitBytes(language.Item1.DataEntry);
+            }
+
+            dataBuilder.PadAlignment(4); // resource data entries are 4 byte aligned
+            foreach (Tuple<ResLanguage, ObjectDataBuilder.Reservation> language in resLanguages)
+            {
+                dataBuilder.EmitInt(language.Item2, dataBuilder.CountBytes);
+                IMAGE_RESOURCE_DATA_ENTRY.Write(ref dataBuilder, nodeAssociatedWithDataBuilder, dataEntryTable[language.Item1], language.Item1.DataEntry.Length);
+            }
+            dataBuilder.PadAlignment(4); // resource data entries are 4 byte aligned
+        }
     }
 }