diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs index 54ff003ae967de..ab9cf4f3eda81a 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/CoffObjectWriter.cs @@ -47,7 +47,22 @@ namespace ILCompiler.ObjectWriter /// internal partial class CoffObjectWriter : ObjectWriter { - protected sealed record SectionDefinition(CoffSectionHeader Header, Stream Stream, List Relocations, Utf8String ComdatName, Utf8String SymbolName); + protected sealed class SectionDefinition + { + public SectionDefinition(CoffSectionHeader header, Stream stream, Utf8String comdatName, Utf8String symbolName) + { + Header = header; + Stream = stream; + ComdatName = comdatName; + SymbolName = symbolName; + } + + public CoffSectionHeader Header { get; } + public Stream Stream { get; } + public List Relocations { get; set; } + public Utf8String ComdatName { get; } + public Utf8String SymbolName { get; } + } protected readonly Machine _machine; protected readonly List _sections = new(); @@ -156,7 +171,7 @@ private protected override void CreateSection(ObjectNodeSection section, Utf8Str } } - _sections.Add(new SectionDefinition(sectionHeader, sectionStream, new List(), comdatName, symbolName)); + _sections.Add(new SectionDefinition(sectionHeader, sectionStream, comdatName, symbolName)); } protected internal override void UpdateSectionAlignment(int sectionIndex, int alignment) @@ -278,10 +293,17 @@ private protected override void EmitSymbolTable( private protected override void EmitRelocations(int sectionIndex, List relocationList) { - CoffSectionHeader sectionHeader = _sections[sectionIndex].Header; - List coffRelocations = _sections[sectionIndex].Relocations; + SectionDefinition section = _sections[sectionIndex]; + CoffSectionHeader sectionHeader = section.Header; if (relocationList.Count > 0) { + List coffRelocations = section.Relocations; + if (coffRelocations is null) + { + coffRelocations = new List(); + section.Relocations = coffRelocations; + } + if (relocationList.Count <= ushort.MaxValue) { sectionHeader.NumberOfRelocations = (ushort)relocationList.Count; @@ -405,8 +427,9 @@ private protected override void EmitObjectFile(Stream outputFileStream) } // Section relocations - section.Header.PointerToRelocations = section.Relocations.Count > 0 ? dataOffset : 0; - dataOffset += (uint)(section.Relocations.Count * CoffRelocation.Size); + int relocationCount = section.Relocations?.Count ?? 0; + section.Header.PointerToRelocations = relocationCount > 0 ? dataOffset : 0; + dataOffset += (uint)(relocationCount * CoffRelocation.Size); // Record the section layout _outputSectionLayout.Add(new OutputSection(section.Header.Name, section.Header.PointerToRawData, section.Header.VirtualAddress, section.Header.SizeOfRawData)); @@ -452,9 +475,9 @@ private protected override void EmitObjectFile(Stream outputFileStream) section.Stream.CopyTo(outputFileStream); } - if (section.Relocations.Count > 0) + if (section.Relocations is List relocations) { - foreach (var relocation in section.Relocations) + foreach (CoffRelocation relocation in relocations) { relocation.Write(outputFileStream); } diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs index 21b1802a49536b..88000a6f4e8195 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/ObjectWriter.cs @@ -111,7 +111,7 @@ private protected SectionWriter GetOrCreateSection(ObjectNodeSection section, Ut sectionIndex = _sectionIndexToData.Count; CreateSection(section, comdatName, symbolName, sectionIndex, sectionData.GetReadStream()); _sectionIndexToData.Add(sectionData); - _sectionIndexToRelocations.Add(new()); + _sectionIndexToRelocations.Add(null); if (comdatName.IsNull) { _sectionNameToSectionIndex.Add(section.Name, sectionIndex); @@ -234,12 +234,19 @@ protected internal virtual void EmitRelocation( Utf8String symbolName, long addend) { - _sectionIndexToRelocations[sectionIndex].Add(new SymbolicRelocation(offset, relocType, symbolName, addend)); + List relocations = _sectionIndexToRelocations[sectionIndex]; + if (relocations is null) + { + relocations = new List(); + _sectionIndexToRelocations[sectionIndex] = relocations; + } + + relocations.Add(new SymbolicRelocation(offset, relocType, symbolName, addend)); } private protected bool SectionHasRelocations(int sectionIndex) { - return _sectionIndexToRelocations[sectionIndex].Count > 0; + return _sectionIndexToRelocations[sectionIndex]?.Count > 0; } private protected virtual void EmitReferencedMethod(Utf8String symbolName) { } @@ -310,6 +317,11 @@ private SortedSet GetUndefinedSymbols() SortedSet undefinedSymbolSet = new SortedSet(); foreach (var relocationList in _sectionIndexToRelocations) { + if (relocationList is null) + { + continue; + } + foreach (var symbolicRelocation in relocationList) { if (!_definedSymbols.ContainsKey(symbolicRelocation.SymbolName)) @@ -597,7 +609,11 @@ public virtual void EmitObject(Stream outputFileStream, IReadOnlyCollection relocationList in _sectionIndexToRelocations) { - EmitRelocations(relocSectionIndex, relocationList); + if (relocationList is not null) + { + EmitRelocations(relocSectionIndex, relocationList); + } + relocSectionIndex++; } diff --git a/src/coreclr/tools/Common/Compiler/ObjectWriter/SectionData.cs b/src/coreclr/tools/Common/Compiler/ObjectWriter/SectionData.cs index 9ae566487981a7..8e9b3c386ea3c8 100644 --- a/src/coreclr/tools/Common/Compiler/ObjectWriter/SectionData.cs +++ b/src/coreclr/tools/Common/Compiler/ObjectWriter/SectionData.cs @@ -16,36 +16,57 @@ namespace ILCompiler.ObjectWriter /// Optimized append-only structure for writing sections. /// /// - /// The section data are kept in memory as a list of buffers. It supports - /// appending existing read-only buffers without copying (such as buffer - /// from ObjectNode.ObjectData). + /// The section data are kept in memory as a sequence of buffers, with the + /// first buffer stored directly and a list allocated only when needed. It + /// supports appending existing read-only buffers without copying (such as + /// buffer from ObjectNode.ObjectData). /// internal sealed class SectionData { - private readonly ArrayBufferWriter _appendBuffer = new(); - private readonly List> _buffers = new(); + private const int PaddingBufferSize = 16; + private const byte NopPaddingByte = 0x90; + + private static readonly byte[] s_zeroPadding = new byte[PaddingBufferSize]; + private static readonly byte[] s_nopPadding = CreatePadding(NopPaddingByte); + + private ArrayBufferWriter _appendBuffer; + private List> _buffers; + private ReadOnlyMemory _firstBuffer; + private int _bufferCount; private long _length; - private readonly byte[] _padding = new byte[16]; + private readonly byte[] _padding; public SectionData(byte paddingByte = 0) { - _padding.AsSpan().Fill(paddingByte); + _padding = paddingByte switch + { + 0 => s_zeroPadding, + NopPaddingByte => s_nopPadding, + _ => CreatePadding(paddingByte), + }; + } + + private static byte[] CreatePadding(byte value) + { + byte[] result = new byte[PaddingBufferSize]; + result.AsSpan().Fill(value); + return result; } private void FlushAppendBuffer() { - if (_appendBuffer.WrittenCount > 0) + if (_appendBuffer is { WrittenCount: > 0 } appendBuffer) { - _buffers.Add(_appendBuffer.WrittenSpan.ToArray()); - _length += _appendBuffer.WrittenCount; - _appendBuffer.Clear(); + AddBuffer(appendBuffer.WrittenSpan.ToArray()); + _length += appendBuffer.WrittenCount; + appendBuffer.Clear(); } } public void AppendData(ReadOnlyMemory data) { FlushAppendBuffer(); - _buffers.Add(data); + AddBuffer(data); _length += data.Length; } @@ -53,10 +74,11 @@ public void AppendPadding(int paddingLength) { if (paddingLength > 0) { - if (_appendBuffer.WrittenCount > 0 || paddingLength > _padding.Length) + if ((_appendBuffer?.WrittenCount ?? 0) > 0 || paddingLength > _padding.Length) { - _appendBuffer.GetSpan(paddingLength).Slice(0, paddingLength).Fill(_padding[0]); - _appendBuffer.Advance(paddingLength); + ArrayBufferWriter appendBuffer = GetAppendBuffer(); + appendBuffer.GetSpan(paddingLength).Slice(0, paddingLength).Fill(_padding[0]); + appendBuffer.Advance(paddingLength); } else { @@ -65,15 +87,48 @@ public void AppendPadding(int paddingLength) } } - public IBufferWriter BufferWriter => _appendBuffer; + public IBufferWriter BufferWriter => GetAppendBuffer(); - public long Length => _length + _appendBuffer.WrittenCount; + public long Length => _length + (_appendBuffer?.WrittenCount ?? 0); /// /// Gets a read-only stream accessing the section data. /// public Stream GetReadStream() => new ReadStream(this); + private ArrayBufferWriter GetAppendBuffer() + { + return _appendBuffer ??= new ArrayBufferWriter(); + } + + private void AddBuffer(ReadOnlyMemory data) + { + if (_bufferCount == 0) + { + _firstBuffer = data; + } + else + { + if (_buffers is null) + { + _buffers = new List>(); + _buffers.Add(_firstBuffer); + } + + _buffers.Add(data); + } + + _bufferCount++; + } + + private int BufferCount => _bufferCount; + + private ReadOnlyMemory GetBuffer(int index) + { + Debug.Assert((uint)index < (uint)_bufferCount); + return _buffers is null ? _firstBuffer : _buffers[index]; + } + private sealed class ReadStream : Stream { private readonly SectionData _sectionData; @@ -98,11 +153,12 @@ public override long Position _position = 0; _bufferIndex = 0; _bufferPosition = 0; - while (_position < value && _bufferIndex < _sectionData._buffers.Count) + while (_position < value && _bufferIndex < _sectionData.BufferCount) { - if (_sectionData._buffers[_bufferIndex].Length < value - _position) + ReadOnlyMemory currentBuffer = _sectionData.GetBuffer(_bufferIndex); + if (currentBuffer.Length < value - _position) { - _position += _sectionData._buffers[_bufferIndex].Length; + _position += currentBuffer.Length; _bufferIndex++; } else @@ -136,9 +192,9 @@ public override int Read(Span buffer) // _bufferIndex and _bufferPosition is only valid after seeking when // _position < _length - while (_position < _sectionData._length && _bufferIndex < _sectionData._buffers.Count) + while (_position < _sectionData._length && _bufferIndex < _sectionData.BufferCount) { - ReadOnlySpan currentBuffer = _sectionData._buffers[_bufferIndex].Span.Slice(_bufferPosition); + ReadOnlySpan currentBuffer = _sectionData.GetBuffer(_bufferIndex).Span.Slice(_bufferPosition); if (currentBuffer.Length >= buffer.Length) { diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.csproj b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.csproj index 69e9d87637f92d..fa76ee999a4510 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.csproj +++ b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ILCompiler.Compiler.Tests.csproj @@ -42,6 +42,7 @@ + diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ObjectWriterTests.cs b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ObjectWriterTests.cs new file mode 100644 index 00000000000000..ebce062e8523c4 --- /dev/null +++ b/src/coreclr/tools/aot/ILCompiler.Compiler.Tests/ObjectWriterTests.cs @@ -0,0 +1,350 @@ +// 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.Buffers; +using System.Buffers.Binary; +using System.Collections.Generic; +using System.IO; +using System.Reflection.PortableExecutable; +using System.Text; + +using ILCompiler.DependencyAnalysis; +using ILCompiler.DependencyAnalysisFramework; +using ILCompiler.ObjectWriter; + +using Internal.IL; +using Internal.Text; +using Internal.TypeSystem; + +using Xunit; + +namespace ILCompiler.Compiler.Tests +{ + public class ObjectWriterTests + { + [Fact] + public void SectionDataSupportsEmptyData() + { + var sectionData = new SectionData(); + sectionData.AppendData(ReadOnlyMemory.Empty); + + Assert.Equal(0, sectionData.Length); + Assert.Empty(ReadAll(sectionData)); + } + + [Fact] + public void SectionDataTransitionsFromInlineToOverflowWithoutCopying() + { + byte[] first = [1, 2]; + byte[] second = [3]; + byte[] third = [4, 5]; + var sectionData = new SectionData(); + + sectionData.AppendData(first); + sectionData.AppendData(second); + sectionData.AppendData(third); + + first[0] = 9; + second[0] = 8; + third[1] = 7; + + Assert.Equal([9, 2, 8, 4, 7], ReadAll(sectionData)); + + using Stream stream = sectionData.GetReadStream(); + stream.Position = 1; + Span remaining = stackalloc byte[4]; + Assert.Equal(4, stream.Read(remaining)); + Assert.Equal([2, 8, 4, 7], remaining.ToArray()); + } + + [Theory] + [InlineData((byte)0)] + [InlineData((byte)0x5A)] + [InlineData((byte)0x90)] + public void SectionDataPreservesBufferedWritesAndPadding(byte paddingByte) + { + var sectionData = new SectionData(paddingByte); + + sectionData.AppendPadding(3); + Write(sectionData.BufferWriter, [1, 2]); + sectionData.AppendPadding(2); + sectionData.AppendData(new byte[] { 3 }); + sectionData.AppendPadding(20); + sectionData.AppendData(new byte[] { 4 }); + + byte[] expected = new byte[29]; + expected.AsSpan(0, 3).Fill(paddingByte); + expected[3] = 1; + expected[4] = 2; + expected.AsSpan(5, 2).Fill(paddingByte); + expected[7] = 3; + expected.AsSpan(8, 20).Fill(paddingByte); + expected[28] = 4; + + Assert.Equal(expected.Length, sectionData.Length); + Assert.Equal(expected, ReadAll(sectionData)); + } + + [Fact] + public void CoffObjectWriterPreservesSectionAndRelocationSemantics() + { + NodeFactory factory = CreateNodeFactory(); + byte[] objectBytes = EmitObject(factory, out int allocatedRelocationLists); + using var objectStream = new MemoryStream(objectBytes); + var headers = new PEHeaders(objectStream); + + Assert.True(headers.IsCoffOnly); + Assert.Equal(2, allocatedRelocationLists); + + SectionHeader empty = GetSection(headers, ".empty"); + Assert.Equal(0, empty.SizeOfRawData); + Assert.Equal(0, empty.PointerToRelocations); + Assert.Equal(0, empty.NumberOfRelocations); + + SectionHeader aligned = GetSection(headers, ".align"); + Assert.Equal([1, 2, 3, 0, 0, 0, 0, 0, 4, 5], GetSectionData(objectBytes, aligned).ToArray()); + + SectionHeader code = GetSection(headers, ".code"); + Assert.Equal([0xCC, 0xCC, 0xCC, 0x90, 0x90, 0x90, 0x90, 0x90, 0xC3], GetSectionData(objectBytes, code).ToArray()); + + SectionHeader single = GetSection(headers, ".single"); + Assert.Equal(1, single.NumberOfRelocations); + Assert.Equal(7u, BinaryPrimitives.ReadUInt32LittleEndian(GetSectionData(objectBytes, single))); + AssertRelocation(objectBytes, headers, single, 0, 0, "targetA"); + + SectionHeader multiple = GetSection(headers, ".multi"); + Assert.Equal(2, multiple.NumberOfRelocations); + ReadOnlySpan multipleData = GetSectionData(objectBytes, multiple); + Assert.Equal(7u, BinaryPrimitives.ReadUInt32LittleEndian(multipleData)); + Assert.Equal(11u, BinaryPrimitives.ReadUInt32LittleEndian(multipleData.Slice(4))); + AssertRelocation(objectBytes, headers, multiple, 0, 4, "targetB"); + AssertRelocation(objectBytes, headers, multiple, 1, 0, "targetA"); + + Assert.True(GetSectionIndex(headers, ".empty") < GetSectionIndex(headers, ".align")); + Assert.True(GetSectionIndex(headers, ".align") < GetSectionIndex(headers, ".single")); + Assert.True(GetSectionIndex(headers, ".single") < GetSectionIndex(headers, ".multi")); + } + + [Fact] + public void CoffObjectWriterIsDeterministic() + { + NodeFactory factory = CreateNodeFactory(); + + Assert.Equal(EmitObject(factory, out _), EmitObject(factory, out _)); + } + + private static NodeFactory CreateNodeFactory() + { + var target = new TargetDetails(TargetArchitecture.X64, TargetOS.Windows, TargetAbi.NativeAot); + var context = new CompilerTypeSystemContext(target, SharedGenericsMode.CanonicalReferenceTypes, DelegateFeature.All) + { + InputFilePaths = new Dictionary + { + { "Test.CoreLib", @"Test.CoreLib.dll" }, + }, + ReferenceFilePaths = new Dictionary(), + }; + context.SetSystemModule(context.GetModuleForSimpleName("Test.CoreLib")); + + var builder = new RyuJitCompilationBuilder(context, new SingleFileCompilationModuleGroup()); + IILScanner scanner = builder.GetILScannerBuilder().ToILScanner(); + NodeFactory factory = ((Compilation)scanner).NodeFactory; + factory.SetMarkingComplete(); + return factory; + } + + private static byte[] EmitObject(NodeFactory factory, out int allocatedRelocationLists) + { + TestObjectNode targetA = new("targetA", new ObjectNodeSection("ta", SectionType.ReadOnly), [0xAA], referenceOffset: 7); + TestObjectNode targetB = new("targetB", new ObjectNodeSection("tb", SectionType.ReadOnly), [0xBB], referenceOffset: 11); + ObjectNodeSection alignedSection = new("align", SectionType.ReadOnly); + ObjectNodeSection codeSection = new("code", SectionType.Executable); + + DependencyNodeCore[] nodes = + [ + new TestObjectNode("empty", new ObjectNodeSection("empty", SectionType.ReadOnly), []), + new TestObjectNode("align1", alignedSection, [1, 2, 3]), + new TestObjectNode("align2", alignedSection, [4, 5], alignment: 8), + new TestObjectNode("code1", codeSection, [0xCC, 0xCC, 0xCC]), + new TestObjectNode("code2", codeSection, [0xC3], alignment: 8), + new TestObjectNode( + "single", + new ObjectNodeSection("single", SectionType.ReadOnly), + new byte[4], + [new Relocation(RelocType.IMAGE_REL_BASED_ADDR32NB, 0, targetA)], + alignment: 4), + new TestObjectNode( + "multiple", + new ObjectNodeSection("multi", SectionType.ReadOnly), + new byte[8], + [ + new Relocation(RelocType.IMAGE_REL_BASED_ADDR32NB, 4, targetB), + new Relocation(RelocType.IMAGE_REL_BASED_ADDR32NB, 0, targetA), + ], + alignment: 4), + targetA, + targetB, + ]; + + var writer = new InspectableCoffObjectWriter(factory); + using var output = new MemoryStream(); + writer.EmitObject(output, nodes, dumper: null, Logger.Null); + allocatedRelocationLists = writer.AllocatedRelocationListCount; + return output.ToArray(); + } + + private static byte[] ReadAll(SectionData sectionData) + { + using Stream stream = sectionData.GetReadStream(); + using var output = new MemoryStream(); + stream.CopyTo(output); + return output.ToArray(); + } + + private static void Write(IBufferWriter writer, ReadOnlySpan data) + { + data.CopyTo(writer.GetSpan(data.Length)); + writer.Advance(data.Length); + } + + private static SectionHeader GetSection(PEHeaders headers, string name) + { + foreach (SectionHeader section in headers.SectionHeaders) + { + if (section.Name == name) + { + return section; + } + } + + throw new InvalidOperationException($"Section '{name}' was not found."); + } + + private static int GetSectionIndex(PEHeaders headers, string name) + { + for (int i = 0; i < headers.SectionHeaders.Length; i++) + { + if (headers.SectionHeaders[i].Name == name) + { + return i; + } + } + + throw new InvalidOperationException($"Section '{name}' was not found."); + } + + private static ReadOnlySpan GetSectionData(byte[] objectBytes, SectionHeader section) + { + return objectBytes.AsSpan(section.PointerToRawData, section.SizeOfRawData); + } + + private static void AssertRelocation( + byte[] objectBytes, + PEHeaders headers, + SectionHeader section, + int relocationIndex, + uint expectedOffset, + string expectedSymbol) + { + const int CoffRelocationSize = 10; + const ushort ImageRelAmd64Addr32Nb = 3; + + int relocationOffset = section.PointerToRelocations + relocationIndex * CoffRelocationSize; + ReadOnlySpan relocation = objectBytes.AsSpan(relocationOffset, CoffRelocationSize); + Assert.Equal(expectedOffset, BinaryPrimitives.ReadUInt32LittleEndian(relocation)); + uint symbolIndex = BinaryPrimitives.ReadUInt32LittleEndian(relocation.Slice(4)); + Assert.Equal(ImageRelAmd64Addr32Nb, BinaryPrimitives.ReadUInt16LittleEndian(relocation.Slice(8))); + Assert.Equal(expectedSymbol, GetSymbolName(objectBytes, headers.CoffHeader.PointerToSymbolTable, symbolIndex)); + } + + private static string GetSymbolName(byte[] objectBytes, int symbolTableOffset, uint symbolIndex) + { + const int CoffSymbolSize = 18; + ReadOnlySpan name = objectBytes.AsSpan(symbolTableOffset + checked((int)symbolIndex) * CoffSymbolSize, 8); + int length = name.IndexOf((byte)0); + if (length < 0) + { + length = name.Length; + } + + return Encoding.UTF8.GetString(name.Slice(0, length)); + } + + private sealed class InspectableCoffObjectWriter : CoffObjectWriter + { + public InspectableCoffObjectWriter(NodeFactory factory) + : base(factory, ObjectWritingOptions.None) + { + } + + public int AllocatedRelocationListCount + { + get + { + int count = 0; + foreach (SectionDefinition section in _sections) + { + if (section.Relocations is not null) + { + count++; + } + } + + return count; + } + } + } + + private sealed class TestObjectNode : ObjectNode, ISymbolDefinitionNode + { + private readonly string _name; + private readonly ObjectNodeSection _section; + private readonly byte[] _data; + private readonly Relocation[] _relocations; + private readonly int _alignment; + private readonly int _referenceOffset; + + public TestObjectNode( + string name, + ObjectNodeSection section, + byte[] data, + Relocation[] relocations = null, + int alignment = 1, + int referenceOffset = 0) + { + _name = name; + _section = section; + _data = data; + _relocations = relocations ?? Array.Empty(); + _alignment = alignment; + _referenceOffset = referenceOffset; + } + + public int Offset => 0; + int ISymbolNode.Offset => _referenceOffset; + public override bool IsShareable => false; + public override int ClassCode => -1737417254; + public override bool StaticDependenciesAreComputed => true; + + public void AppendMangledName(NameMangler nameMangler, Utf8StringBuilder sb) + { + sb.Append(new Utf8String(_name)); + } + + public override ObjectData GetData(NodeFactory factory, bool relocsOnly = false) + { + return new ObjectData(_data, _relocations, _alignment, [this]); + } + + public override ObjectNodeSection GetSection(NodeFactory factory) => _section; + + public override int CompareToImpl(ISortableNode other, CompilerComparer comparer) + { + return StringComparer.Ordinal.Compare(_name, ((TestObjectNode)other)._name); + } + + protected override string GetName(NodeFactory factory) => _name; + } + } +} diff --git a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj index 0964719be88f3d..ef6f55a5ffb9b7 100644 --- a/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj +++ b/src/coreclr/tools/aot/ILCompiler.Compiler/ILCompiler.Compiler.csproj @@ -21,6 +21,8 @@ + +