Skip to content

Commit

Permalink
ELF/LibObjectFile experiment: Add support for building optimized stri…
Browse files Browse the repository at this point in the history
…ng table
  • Loading branch information
filipnavara committed Oct 2, 2023
1 parent 0b30437 commit 1c0d928
Show file tree
Hide file tree
Showing 2 changed files with 146 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ namespace LibObjectFile.Elf
public class ElfStringTable : ElfSection
{
private readonly MemoryStream _table;
private readonly List<string> _reservedStrings;
private readonly Dictionary<string, uint> _mapStringToIndex;
private readonly Dictionary<uint, string> _mapIndexToString;

Expand All @@ -35,8 +36,9 @@ public ElfStringTable(int capacityInBytes) : base(ElfSectionType.StringTable)
_table = new MemoryStream(capacityInBytes);
_mapStringToIndex = new Dictionary<string, uint>();
_mapIndexToString = new Dictionary<uint, string>();
_reservedStrings = new List<string>();
// Always create an empty string
GetOrCreateIndex(string.Empty);
CreateIndex(string.Empty);
}

public override ElfSectionType Type
Expand All @@ -55,6 +57,7 @@ public override ElfSectionType Type
public override void UpdateLayout(DiagnosticBag diagnostics)
{
if (diagnostics == null) throw new ArgumentNullException(nameof(diagnostics));
if (_reservedStrings.Count > 0) FlushReservedStrings();
Size = (ulong)_table.Length;
}

Expand All @@ -73,21 +76,136 @@ protected override void Write(ElfWriter writer)
writer.Stream.Write(_table.GetBuffer(), 0, (int)_table.Length);
}

public uint GetOrCreateIndex(string text)
internal void ReserveString(string text)
{
// Same as empty string
if (text == null) return 0;
if (text is object && !_mapStringToIndex.ContainsKey(text))
{
_reservedStrings.Add(text);
}
}

if (_mapStringToIndex.TryGetValue(text, out uint index))
internal void FlushReservedStrings()
{
// TODO: Use CollectionsMarshal.AsSpan
string[] reservedStrings = _reservedStrings.ToArray();

// Pre-sort the string based on their matching suffix
MultiKeySort(reservedStrings, 0);

// Add the strings to string table
string lastText = null;
for (int i = 0; i < reservedStrings.Length; i++)
{
return index;
var text = reservedStrings[i];
uint index;
if (lastText != null && lastText.EndsWith(text, StringComparison.Ordinal))
{
// Suffix matches the last symbol
index = (uint)(_table.Length - Encoding.UTF8.GetByteCount(text) - 1);
_mapIndexToString.Add(index, text);
_mapStringToIndex.Add(text, index);
}
else
{
lastText = text;
CreateIndex(text);
}
}

_reservedStrings.Clear();

static char TailCharacter(string str, int pos)
{
int index = str.Length - pos - 1;
if ((uint)index < str.Length)
return str[index];
return '\0';
}

static void MultiKeySort(Span<string> input, int pos)
{
if (!MultiKeySortSmallInput(input, pos))
{
MultiKeySortLargeInput(input, pos);
}
}

static void MultiKeySortLargeInput(Span<string> input, int pos)
{
tailcall:
char pivot = TailCharacter(input[0], pos);
int l = 0, h = input.Length;
for (int i = 1; i < h;)
{
char c = TailCharacter(input[i], pos);
if (c > pivot)
{
(input[l], input[i]) = (input[i], input[l]);
l++; i++;
}
else if (c < pivot)
{
h--;
(input[h], input[i]) = (input[i], input[h]);
}
else
{
i++;
}
}

MultiKeySort(input.Slice(0, l), pos);
MultiKeySort(input.Slice(h), pos);
if (pivot != '\0')
{
// Use a loop as a poor man's tailcall
// MultiKeySort(input.Slice(l, h - l), pos + 1);
pos++;
input = input.Slice(l, h - l);
if (!MultiKeySortSmallInput(input, pos))
{
goto tailcall;
}
}
}

static bool MultiKeySortSmallInput(Span<string> input, int pos)
{
if (input.Length <= 1)
return true;

// Optimize comparing two strings
if (input.Length == 2)
{
while (true)
{
char c0 = TailCharacter(input[0], pos);
char c1 = TailCharacter(input[1], pos);
if (c0 < c1)
{
(input[0], input[1]) = (input[1], input[0]);
break;
}
else if (c0 > c1 || c0 == (char)0)
{
break;
}
pos++;
}
return true;
}

return false;
}
}

index = (uint) _table.Length;
private uint CreateIndex(string text)
{
uint index = (uint) _table.Length;
_mapIndexToString.Add(index, text);
_mapStringToIndex.Add(text, index);

if (text.Length == 0)
if (index == 0)
{
Debug.Assert(index == 0);
_table.WriteByte(0);
Expand All @@ -105,25 +223,26 @@ public uint GetOrCreateIndex(string text)
}
_table.Write(span);
ArrayPool<byte>.Shared.Return(buffer);

// Register all subsequent strings
while (text.Length > 0)
{
text = text.Substring(1);
if (_mapStringToIndex.ContainsKey(text))
{
break;
}
var offset = reservedBytes - Encoding.UTF8.GetByteCount(text) - 1;
var subIndex = index + (uint) offset;
_mapStringToIndex.Add(text, subIndex);
_mapIndexToString.Add(subIndex, text);
}
}

return index;
}

public uint GetOrCreateIndex(string text)
{
// Same as empty string
if (text == null) return 0;

if (_reservedStrings.Count > 0) FlushReservedStrings();

if (_mapStringToIndex.TryGetValue(text, out uint index))
{
return index;
}

return CreateIndex(text);
}

public bool TryResolve(ElfString inStr, out ElfString outStr)
{
outStr = inStr;
Expand Down Expand Up @@ -153,6 +272,8 @@ public bool TryFind(uint index, out string text)
return true;
}

if (_reservedStrings.Count > 0) FlushReservedStrings();

if (_mapIndexToString.TryGetValue(index, out text))
{
return true;
Expand Down Expand Up @@ -191,9 +312,10 @@ public void Reset()
_table.SetLength(0);
_mapStringToIndex.Clear();
_mapIndexToString.Clear();
_reservedStrings.Clear();

// Always create an empty string
GetOrCreateIndex(string.Empty);
CreateIndex(string.Empty);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ public override void Verify(DiagnosticBag diagnostics)
diagnostics.Error(DiagnosticId.ELF_ERR_InvalidSymbolEntrySectionParent, $"Invalid section for the symbol entry #{i} in the {nameof(ElfSymbolTable)} section [{Index}]. The section of the entry `{entry}` must the same than this symbol table section");
}

stringTable.GetOrCreateIndex(entry.Name);
stringTable.ReserveString(entry.Name);

// Update the last local index
if (entry.Bind == ElfSymbolBind.Local)
Expand Down

0 comments on commit 1c0d928

Please sign in to comment.