Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ELF refactoring with breaking changes #42

Merged
merged 17 commits into from
Oct 15, 2024
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Optimize Elf read/write
xoofx committed Oct 15, 2024
commit e712f3cbac5289289e3b955f0630731b9776ad9f
1 change: 1 addition & 0 deletions src/Directory.Packages.props
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@
<ItemGroup>
<PackageVersion Include="MinVer" Version="6.0.0" />
<PackageVersion Include="MSTest" Version="3.6.0" />
<PackageVersion Include="SuperluminalPerf" Version="1.3.0" />
<PackageVersion Include="System.Text.Encoding.CodePages" Version="8.0.0" />
<PackageVersion Include="Verify.DiffPlex" Version="3.1.0" />
<PackageVersion Include="Verify.MSTest" Version="26.6.0" />
4 changes: 4 additions & 0 deletions src/LibObjectFile.Bench/LibObjectFile.Bench.csproj
Original file line number Diff line number Diff line change
@@ -8,6 +8,10 @@
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="SuperluminalPerf" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\LibObjectFile\LibObjectFile.csproj" />
</ItemGroup>
38 changes: 33 additions & 5 deletions src/LibObjectFile.Bench/Program.cs
Original file line number Diff line number Diff line change
@@ -11,18 +11,46 @@ internal class Program
{
static void Main(string[] args)
{
Console.WriteLine("Loading files into memory");
var clock = Stopwatch.StartNew();
//var memoryStream = new MemoryStream();
var streams = new List<MemoryStream>();
int biggestCapacity = 0;
foreach (var file in GetLinuxBins())
{
//memoryStream.SetLength(0);
using var stream = File.OpenRead((string)file[0]);
//stream.CopyTo(memoryStream);

if (ElfFile.IsElf(stream))
{
ElfFile.Read(stream);
stream.Position = 0;
var localStream = new MemoryStream((int)stream.Length);
stream.CopyTo(localStream);
localStream.Position = 0;
streams.Add(localStream);
if (localStream.Capacity > biggestCapacity)
{
biggestCapacity = localStream.Capacity;
}
}
}

clock.Stop();
Console.WriteLine($"End reading in {clock.Elapsed.TotalMilliseconds}ms");
Console.ReadLine();

Console.WriteLine("Processing");
var memoryStream = new MemoryStream(biggestCapacity);
clock.Restart();
//SuperluminalPerf.Initialize();
for (int i = 0; i < 10; i++)
{
//SuperluminalPerf.BeginEvent($"Round{i}");
foreach (var stream in streams)
{
stream.Position = 0;
var elf = ElfFile.Read(stream);
memoryStream.SetLength(0);
elf.Write(memoryStream);
}
//SuperluminalPerf.EndEvent();
}
clock.Stop();
Console.WriteLine($"{clock.Elapsed.TotalMilliseconds}ms");
75 changes: 75 additions & 0 deletions src/LibObjectFile.Tests/IO/TestBatchDataReaderWriter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.

using System;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using LibObjectFile.IO;

namespace LibObjectFile.Tests.IO;

/// <summary>
/// Tests for <see cref="BatchDataReader{T}"/> and <see cref="BatchDataWriter{T}"/>.
/// </summary>
[TestClass]
public class TestBatchDataReaderWriter
{
[DataTestMethod]
[DataRow(0)]
[DataRow(1)]
[DataRow(100)]
[DataRow(1000)]
[DataRow(1024)]
[DataRow(1025)]
public void TestRead(int count)
{
var stream = new MemoryStream();
stream.Write(MemoryMarshal.AsBytes(Enumerable.Range(0, count).ToArray().AsSpan()));
stream.Position = 0;

using var reader = new BatchDataReader<int>(stream, count);
int i = 0;
while (reader.HasNext())
{
Assert.AreEqual(i, reader.Read(), $"Invalid value at index {i}");
i++;
}
Assert.AreEqual(count, i);
}

[DataTestMethod]
[DataRow(0)]
[DataRow(1)]
[DataRow(100)]
[DataRow(1000)]
[DataRow(1024)]
[DataRow(1025)]
public void TestWrite(int count)
{
var stream = new MemoryStream();
int i = 0;
{
using var writer = new BatchDataWriter<int>(stream, count);
{
while (writer.HasNext())
{
writer.Write(i);
i++;
}
}
}
Assert.AreEqual(count * sizeof(int), stream.Length);

stream.Position = 0;
using var reader = new BatchDataReader<int>(stream, count);
i = 0;
while (reader.HasNext())
{
Assert.AreEqual(i, reader.Read(), $"Invalid value at index {i}");
i++;
}
Assert.AreEqual(count, i);
}
}
20 changes: 18 additions & 2 deletions src/LibObjectFile/Elf/ElfFile.Read.cs
Original file line number Diff line number Diff line change
@@ -84,12 +84,28 @@ private unsafe void VerifyAndFixProgramHeadersAndSections(ElfReader reader)
}

// Connect section Link instance
section.Link = reader.ResolveLink(section.Link, $"Invalid section Link [{{0}}] for section [{i}]");
var link = section.Link;
if (!reader.TryResolveLink(ref link))
{
reader.Diagnostics.Error(DiagnosticId.ELF_ERR_InvalidResolvedLink, $"Invalid section Link [{link.SpecialIndex}] for section [{i}]");
}
else
{
section.Link = link;
}

// Connect section Info instance
if (section.Type != ElfSectionType.DynamicLinkerSymbolTable && section.Type != ElfSectionType.SymbolTable && (section.Flags & ElfSectionFlags.InfoLink) != 0)
{
section.Info = reader.ResolveLink(section.Info, $"Invalid section Info [{{0}}] for section [{i}]");
link = section.Info;
if (!reader.TryResolveLink(ref link))
{
reader.Diagnostics.Error(DiagnosticId.ELF_ERR_InvalidResolvedLink, $"Invalid section Info [{link.SpecialIndex}] for section [{i}]");
}
else
{
section.Info = link;
}
}

if (section != SectionHeaderStringTable && section.HasContent)
16 changes: 5 additions & 11 deletions src/LibObjectFile/Elf/ElfReader.cs
Original file line number Diff line number Diff line change
@@ -30,24 +30,18 @@ private protected ElfReader(ElfFile file, Stream stream, ElfReaderOptions reader

public override bool KeepOriginalStreamForSubStreams => Options.UseSubStream;

public ElfSectionLink ResolveLink(ElfSectionLink link, string errorMessageFormat)
public bool TryResolveLink(ref ElfSectionLink link)
{
ArgumentNullException.ThrowIfNull(errorMessageFormat);

// Connect section Link instance
if (!link.IsEmpty)
{
if (link.SpecialIndex >= File.Sections.Count)
{
Diagnostics.Error(DiagnosticId.ELF_ERR_InvalidResolvedLink, string.Format(errorMessageFormat, link.SpecialIndex));
}
else
{
link = new ElfSectionLink(File.Sections[link.SpecialIndex]);
return false;
}
}

return link;
link = new ElfSectionLink(File.Sections[link.SpecialIndex]);
}
return true;
}

internal static ElfReader Create(ElfFile file, Stream stream, ElfReaderOptions options)
1 change: 1 addition & 0 deletions src/LibObjectFile/Elf/ElfSectionLink.cs
Original file line number Diff line number Diff line change
@@ -21,6 +21,7 @@ namespace LibObjectFile.Elf;

public ElfSectionLink(int index)
{
ArgumentOutOfRangeException.ThrowIfNegative(index);
Section = null;
SpecialIndex = index;
}
50 changes: 30 additions & 20 deletions src/LibObjectFile/Elf/Sections/ElfRelocationTable.cs
Original file line number Diff line number Diff line change
@@ -76,7 +76,7 @@ private unsafe void Read32(ElfReader reader)
ref var entry = ref MemoryMarshal.GetReference(span);
while (batch.HasNext())
{
ref var rel = ref batch.ReadNext();
ref var rel = ref batch.Read();
entry.Offset = reader.Decode(rel.r_offset);
var r_info = reader.Decode(rel.r_info);
entry.Type = new ElfRelocationType(Parent!.Arch, r_info & 0xFF);
@@ -91,7 +91,7 @@ private unsafe void Read32(ElfReader reader)
ref var entry = ref MemoryMarshal.GetReference(span);
while (batch.HasNext())
{
ref var rel = ref batch.ReadNext();
ref var rel = ref batch.Read();
entry.Offset = reader.Decode(rel.r_offset);
var r_info = reader.Decode(rel.r_info);
entry.Type = new ElfRelocationType(Parent!.Arch, r_info & 0xFF);
@@ -115,7 +115,7 @@ private unsafe void Read64(ElfReader reader)
ref var entry = ref MemoryMarshal.GetReference(span);
while (batch.HasNext())
{
ref var rel = ref batch.ReadNext();
ref var rel = ref batch.Read();
entry.Offset = reader.Decode(rel.r_offset);
var r_info = reader.Decode(rel.r_info);
entry.Type = new ElfRelocationType(Parent!.Arch, (uint)(r_info & 0xFFFFFFFF));
@@ -130,7 +130,7 @@ private unsafe void Read64(ElfReader reader)
ref var entry = ref MemoryMarshal.GetReference(span);
while (batch.HasNext())
{
ref var rel = ref batch.ReadNext();
ref var rel = ref batch.Read();
entry.Offset = reader.Decode(rel.r_offset);
var r_info = reader.Decode(rel.r_info);
entry.Type = new ElfRelocationType(Parent!.Arch, (uint)(r_info & 0xFFFFFFFF));
@@ -143,66 +143,76 @@ private unsafe void Read64(ElfReader reader)

private void Write32(ElfWriter writer)
{
var entries = CollectionsMarshal.AsSpan(_entries);
if (IsRelocationWithAddends)
{
using var batch = new BatchDataWriter<ElfNative.Elf32_Rela>(writer.Stream, entries.Length);
// Write all entries
for (int i = 0; i < Entries.Count; i++)
var rel = new ElfNative.Elf32_Rela();
for (int i = 0; i < entries.Length; i++)
{
var entry = Entries[i];
ref var entry = ref entries[i];

var rel = new ElfNative.Elf32_Rela();
writer.Encode(out rel.r_offset, (uint)entry.Offset);
uint r_info = entry.Info32;
writer.Encode(out rel.r_info, r_info);
writer.Encode(out rel.r_addend, (int)entry.Addend);
writer.Write(rel);

batch.Write(rel);
}
}
else
{
using var batch = new BatchDataWriter<ElfNative.Elf32_Rel>(writer.Stream, entries.Length);
// Write all entries
for (int i = 0; i < Entries.Count; i++)
var rel = new ElfNative.Elf32_Rel();
for (int i = 0; i < entries.Length; i++)
{
var entry = Entries[i];
ref var entry = ref entries[i];

var rel = new ElfNative.Elf32_Rel();
writer.Encode(out rel.r_offset, (uint)entry.Offset);
uint r_info = entry.Info32;
writer.Encode(out rel.r_info, r_info);
writer.Write(rel);

batch.Write(rel);
}
}
}

private void Write64(ElfWriter writer)
{
var entries = CollectionsMarshal.AsSpan(_entries);
if (IsRelocationWithAddends)
{
using var batch = new BatchDataWriter<ElfNative.Elf64_Rela>(writer.Stream, entries.Length);
var rel = new ElfNative.Elf64_Rela();
// Write all entries
for (int i = 0; i < Entries.Count; i++)
for (int i = 0; i < entries.Length; i++)
{
var entry = Entries[i];
ref var entry = ref entries[i];

var rel = new ElfNative.Elf64_Rela();
writer.Encode(out rel.r_offset, entry.Offset);
ulong r_info = entry.Info64;
writer.Encode(out rel.r_info, r_info);
writer.Encode(out rel.r_addend, entry.Addend);
writer.Write(rel);

batch.Write(rel);
}
}
else
{
using var batch = new BatchDataWriter<ElfNative.Elf64_Rel>(writer.Stream, entries.Length);
var rel = new ElfNative.Elf64_Rel();
// Write all entries
for (int i = 0; i < Entries.Count; i++)
for (int i = 0; i < entries.Length; i++)
{
var entry = Entries[i];
ref var entry = ref entries[i];

var rel = new ElfNative.Elf64_Rel();
writer.Encode(out rel.r_offset, (uint)entry.Offset);
ulong r_info = entry.Info64;
writer.Encode(out rel.r_info, r_info);
writer.Write(rel);

batch.Write(rel);
}
}
}
53 changes: 33 additions & 20 deletions src/LibObjectFile/Elf/Sections/ElfSymbolTable.cs
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@

using System;
using System.Collections.Generic;
using System.Reflection.PortableExecutable;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using LibObjectFile.Diagnostics;
@@ -72,7 +73,7 @@ private void Read32(ElfReader reader, int numberOfEntries)
ref var entry = ref MemoryMarshal.GetReference(span);
while (batch.HasNext())
{
ref var sym = ref batch.ReadNext();
ref var sym = ref batch.Read();

entry.Name = new ElfString(reader.Decode(sym.st_name));
entry.Value = reader.Decode(sym.st_value);
@@ -94,7 +95,7 @@ private void Read64(ElfReader reader, int numberOfEntries)
ref var entry = ref MemoryMarshal.GetReference(span);
while (batch.HasNext())
{
ref var sym = ref batch.ReadNext();
ref var sym = ref batch.Read();

entry.Name = new ElfString(reader.Decode(sym.st_name));
entry.Value = reader.Decode(sym.st_value);
@@ -114,11 +115,13 @@ private void Write32(ElfWriter writer)
var stringTable = (ElfStringTable)Link.Section!;

// Write all entries
for (int i = 0; i < Entries.Count; i++)
var entries = CollectionsMarshal.AsSpan(Entries);
using var batch = new BatchDataWriter<ElfNative.Elf32_Sym>(writer.Stream, entries.Length);
var sym = new ElfNative.Elf32_Sym();
for (int i = 0; i < entries.Length; i++)
{
var entry = Entries[i];
ref var entry = ref entries[i];

var sym = new ElfNative.Elf32_Sym();
writer.Encode(out sym.st_name, (ushort)stringTable.Resolve(entry.Name!).Index);
writer.Encode(out sym.st_value, (uint)entry.Value);
writer.Encode(out sym.st_size, (uint)entry.Size);
@@ -127,19 +130,22 @@ private void Write32(ElfWriter writer)
var sectionIndex = entry.SectionLink.GetIndex();
writer.Encode(out sym.st_shndx, sectionIndex < ElfNative.SHN_LORESERVE || entry.SectionLink.IsSpecial ? (ElfNative.Elf32_Half)sectionIndex : (ElfNative.Elf32_Half)ElfNative.SHN_XINDEX);

writer.Write(sym);
batch.Write(sym);
}
}

private void Write64(ElfWriter writer)
{
var stringTable = (ElfStringTable)Link.Section!;

for (int i = 0; i < Entries.Count; i++)
// Write all entries
var entries = CollectionsMarshal.AsSpan(Entries);
using var batch = new BatchDataWriter<ElfNative.Elf64_Sym>(writer.Stream, entries.Length);
var sym = new ElfNative.Elf64_Sym();
for (int i = 0; i < entries.Length; i++)
{
var entry = Entries[i];
ref var entry = ref entries[i];

var sym = new ElfNative.Elf64_Sym();
writer.Encode(out sym.st_name, stringTable.Resolve(entry.Name!).Index);
writer.Encode(out sym.st_value, entry.Value);
writer.Encode(out sym.st_size, entry.Size);
@@ -148,7 +154,7 @@ private void Write64(ElfWriter writer)
var sectionIndex = entry.SectionLink.GetIndex();
writer.Encode(out sym.st_shndx, sectionIndex < ElfNative.SHN_LORESERVE || entry.SectionLink.IsSpecial ? (ElfNative.Elf64_Half)sectionIndex : (ElfNative.Elf64_Half)ElfNative.SHN_XINDEX);

writer.Write(sym);
batch.Write(sym);
}
}

@@ -157,9 +163,10 @@ protected override void AfterRead(ElfReader reader)
// Verify that the link is safe and configured as expected
Link.TryGetSectionSafe<ElfStringTable>(nameof(ElfSymbolTable), nameof(Link), this, reader.Diagnostics, out var stringTable, ElfSectionType.StringTable);

for (int i = 0; i < Entries.Count; i++)
var entries = CollectionsMarshal.AsSpan(Entries);
for (int i = 0; i < entries.Length; i++)
{
var entry = Entries[i];
ref var entry = ref entries[i];

if (stringTable != null)
{
@@ -175,10 +182,16 @@ protected override void AfterRead(ElfReader reader)

if (entry.SectionLink.SpecialIndex < ElfNative.SHN_LORESERVE)
{
entry.SectionLink = reader.ResolveLink(entry.SectionLink, $"Invalid link section index {{0}} for symbol table entry [{i}] from symbol table section [{this}]");
var link = entry.SectionLink;
if (!reader.TryResolveLink(ref link))
{
reader.Diagnostics.Error(DiagnosticId.ELF_ERR_InvalidResolvedLink, $"Invalid link section index [{entry.SectionLink.SpecialIndex}] for symbol table entry [{i}] from symbol table section [{this}]");
}
else
{
entry.SectionLink = link;
}
}

Entries[i] = entry;
}
}

@@ -239,12 +252,12 @@ protected override unsafe void UpdateLayoutCore(ElfVisitorContext context)

bool isAllowingLocal = true;

for (int i = 0; i < Entries.Count; i++)
var entries = CollectionsMarshal.AsSpan(Entries);
for (int i = 0; i < entries.Length; i++)
{
var entry = Entries[i];
ref var entry = ref entries[i];
entry.Name = stringTable.Resolve(entry.Name);



// Update the last local index
if (entry.Bind == ElfSymbolBind.Local)
{
@@ -263,7 +276,7 @@ protected override unsafe void UpdateLayoutCore(ElfVisitorContext context)
}


Size = (uint)Entries.Count * TableEntrySize;
Size = (uint)entries.Length * TableEntrySize;
}

protected override unsafe void ValidateParent(ObjectElement parent)
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using LibObjectFile.Diagnostics;

namespace LibObjectFile.Elf;
@@ -55,17 +56,20 @@ protected override void AfterRead(ElfReader reader)
return;
}

var symbolEntries = CollectionsMarshal.AsSpan(symbolTable.Entries);
for (int i = 0; i < _entries.Count; i++)
{
var entry = _entries[i];
if (entry != 0)
{
var resolvedLink = reader.ResolveLink(new ElfSectionLink((int)entry), $"Invalid link section index {{0}} for symbol table entry [{i}] from symbol table section .symtab_shndx");
var resolvedLink = new ElfSectionLink((int)entry);
if (!reader.TryResolveLink(ref resolvedLink))
{
reader.Diagnostics.Error(DiagnosticId.ELF_ERR_InvalidResolvedLink, $"Invalid link section index {entry} for symbol table entry [{i}] from symbol table section .symtab_shndx");
}

// Update the link in symbol table
var symbolTableEntry = symbolTable.Entries[i];
symbolTableEntry.SectionLink = resolvedLink;
symbolTable.Entries[i] = symbolTableEntry;
symbolEntries[i].SectionLink = resolvedLink;
}
}
}
16 changes: 9 additions & 7 deletions src/LibObjectFile/IO/BatchDataReader.cs
Original file line number Diff line number Diff line change
@@ -32,7 +32,7 @@ public BatchDataReader(Stream stream, int count)
{
_stream = stream;
_count = count;
var size = sizeof(TData) * BatchSize;
var size = sizeof(TData) * Math.Min(count, BatchSize);
var buffer = ArrayPool<byte>.Shared.Rent(size);
_firstValue = ref Unsafe.As<byte, TData>(ref MemoryMarshal.GetArrayDataReference(buffer));
_buffer = buffer;
@@ -49,26 +49,28 @@ public BatchDataReader(Stream stream, int count)
/// </summary>
/// <returns>A reference to the next element.</returns>
/// <exception cref="InvalidOperationException">Thrown when there are no more elements to read.</exception>
public ref TData ReadNext()
public ref TData Read()
{
if (_index >= _count)
var index = _index;
var count = _count;
if (index >= count)
{
throw new InvalidOperationException("No more elements to read");
}

var remaining = _index & (BatchSize - 1);
var remaining = index & (BatchSize - 1);
_index = index + 1;
if (remaining == 0)
{
var sizeToRead = Math.Min(_count - _index, BatchSize) * sizeof(TData);
var sizeToRead = Math.Min(count - index, BatchSize) * sizeof(TData);
int read = _stream.Read(_buffer, 0, sizeToRead);
if (read != sizeToRead)
{
throw new InvalidOperationException($"Not enough data to read at position {_stream.Position}");
throw new EndOfStreamException($"Not enough data to read at position {_stream.Position}");
}
}

ref var value = ref Unsafe.Add(ref _firstValue, remaining);
_index++;
return ref value;
}

92 changes: 92 additions & 0 deletions src/LibObjectFile/IO/BatchDataWriter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// Copyright (c) Alexandre Mutel. All rights reserved.
// This file is licensed under the BSD-Clause 2 license.
// See the license.txt file in the project root for more information.

using System;
using System.Buffers;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace LibObjectFile.IO;

/// <summary>
/// Represents a batch data writer for writing elements of type <typeparamref name="TData"/> to a stream.
/// </summary>
/// <typeparam name="TData">The type of the elements to write.</typeparam>
public unsafe ref struct BatchDataWriter<TData> where TData : unmanaged
{
private readonly Stream _stream;
private readonly int _count;
private readonly byte[] _buffer;
private readonly ref TData _firstValue;
private int _index;
private const int BatchSize = 1024; // TODO: could be made configurable

/// <summary>
/// Initializes a new instance of the <see cref="BatchDataWriter{TData}"/> struct.
/// </summary>
/// <param name="stream">The stream to write the data to.</param>
/// <param name="count">The total number of elements to write.</param>
public BatchDataWriter(Stream stream, int count)
{
_stream = stream;
_count = count;
var size = sizeof(TData) * Math.Min(count, BatchSize);
var buffer = ArrayPool<byte>.Shared.Rent(size);
_firstValue = ref Unsafe.As<byte, TData>(ref MemoryMarshal.GetArrayDataReference(buffer));
_buffer = buffer;
}

/// <summary>
/// Gets a value indicating whether there are more elements to write.
/// </summary>
/// <returns><c>true</c> if there are more elements to write; otherwise, <c>false</c>.</returns>
public bool HasNext() => _index < _count;

/// <summary>
/// Writes the specified value to the stream.
/// </summary>
/// <param name="value">The value to write.</param>
public void Write(in TData value)
{
var index = _index;
var count = _count;
if (index >= count)
{
throw new InvalidOperationException("No more elements to write");
}

var remaining = index & (BatchSize - 1);
if (remaining == 0 && index > 0)
{
_stream.Write(_buffer, 0, BatchSize * sizeof(TData));
}

Unsafe.Add(ref _firstValue, remaining) = value;
_index = index + 1;
}

/// <summary>
/// Releases the resources used by the <see cref="BatchDataWriter{TData}"/>.
/// </summary>
public void Dispose()
{
var buffer = _buffer;
if (buffer != null)
{
var remaining = _count & (BatchSize - 1);
if (remaining != 0)
{
var sizeToWrite = remaining * sizeof(TData);
_stream.Write(buffer, 0, sizeToWrite);
}
else if (_count > 0)
{
_stream.Write(buffer, 0, BatchSize * sizeof(TData));
}

ArrayPool<byte>.Shared.Return(buffer);
}
}
}
29 changes: 15 additions & 14 deletions src/LibObjectFile/IO/StreamExtensions.cs
Original file line number Diff line number Diff line change
@@ -71,27 +71,28 @@ public static string ReadStringUTF8NullTerminated(this Stream stream)
{
while (true)
{
// TODO: Optimize this by reading a block of bytes
int nextByte = stream.ReadByte();
if (nextByte < 0)
{
throw new EndOfStreamException("Unexpected end of stream while trying to read a null terminated UTF8 string");
}

if (nextByte == 0)
var span = new Span<byte>(buffer, textLength, buffer.Length - textLength);
int read = stream.Read(span);
if (read <= 0)
{
break;
}

if (textLength >= buffer.Length)
span = span.Slice(0, read);
var nullIndex = span.IndexOf((byte)0);
if (nullIndex >= 0)
{
var newBuffer = ArrayPool<byte>.Shared.Rent((int)textLength * 2);
Array.Copy(buffer, 0, newBuffer, 0, buffer.Length);
ArrayPool<byte>.Shared.Return(buffer);
buffer = newBuffer;
textLength += nullIndex;
// Seek back to after the null character
stream.Position = stream.Position - read + nullIndex + 1;
break;
}
textLength += read;

buffer[textLength++] = (byte)nextByte;
var newBuffer = ArrayPool<byte>.Shared.Rent(buffer.Length + 128);
Array.Copy(buffer, 0, newBuffer, 0, textLength);
ArrayPool<byte>.Shared.Return(buffer);
buffer = newBuffer;
}

return Encoding.UTF8.GetString(buffer, 0, textLength);