aboutsummaryrefslogtreecommitdiff
path: root/ARMeilleure/CodeGen/RegisterAllocators/LiveInterval.cs
diff options
context:
space:
mode:
authorFICTURE7 <FICTURE7@gmail.com>2021-10-09 01:15:44 +0400
committerGitHub <noreply@github.com>2021-10-08 18:15:44 -0300
commit69093cf2d69490862aff974f170cee63a0016fd0 (patch)
tree24507a2d3da862416d3c2d3ca228c89cb40d5437 /ARMeilleure/CodeGen/RegisterAllocators/LiveInterval.cs
parentc54a14d0b8d445d9d0074861dca816cc801e4008 (diff)
Optimize LSRA (#2563)
* Optimize `TryAllocateRegWithtoutSpill` a bit * Add a fast path for when all registers are live. * Do not query `GetOverlapPosition` if the register is already in use (i.e: free position is 0). * Do not allocate child split list if not parent * Turn `LiveRange` into a reference struct `LiveRange` is now a reference wrapping struct like `Operand` and `Operation`. It has also been changed into a singly linked-list. In micro-benchmarks traversing the linked-list was faster than binary search on `List<T>`. Even for quite large input sizes (e.g: 1,000,000), surprisingly. Could be because the code gen for traversing the linked-list is much much cleaner and there is no virtual dispatch happening when checking if intervals overlaps. * Turn `LiveInterval` into an iterator The LSRA allocates in forward order and never inspect previous `LiveInterval` once they are expired. Something similar can be done for the `LiveRange`s within the `LiveInterval`s themselves. The `LiveInterval` is turned into a iterator which expires `LiveRange` within it. The iterator is moved forward along with interval walking code, i.e: AllocateInterval(context, interval, cIndex). * Remove `LinearScanAllocator.Sources` Local methods are less susceptible to do allocations than lambdas. * Optimize `GetOverlapPosition(interval)` a bit Time complexity should be in O(n+m) instead of O(nm) now. * Optimize `NumberLocals` a bit Use the same idea as in `HybridAllocator` to store the visited state in the MSB of the Operand's value instead of using a `HashSet<T>`. * Optimize `InsertSplitCopies` a bit Avoid allocating a redundant `CopyResolver`. * Optimize `InsertSplitCopiesAtEdges` a bit Avoid redundant allocations of `CopyResolver`. * Use stack allocation for `freePositions` Avoid redundant computations. * Add `UseList` Replace `SortedIntegerList` with an even more specialized data structure. It allocates memory on the arena allocators and does not require copying use positions when splitting it. * Turn `LiveInterval` into a reference struct `LiveInterval` is now a reference wrapping struct like `Operand` and `Operation`. The rationale behind turning this in a reference wrapping struct is because a `LiveInterval` is associated with each local variable, and these intervals may themselves be split further. I've seen translations having up to 8000 local variables. To make the `LiveInterval` unmanaged, a new data structure called `LiveIntervalList` was added to store child splits. This differs from `SortedList<,>` because it can contain intervals with the same start position. Really wished we got some more of C++ template in C#. :^( * Optimize `GetChildSplit` a bit No need to inspect the remaining ranges if we've reached a range which starts after position, since the split list is ordered. * Optimize `CopyResolver` a bit Lazily allocate the fill, spill and parallel copy structures since most of the time only one of them is needed. * Optimize `BitMap.Enumerator` a bit Marking `MoveNext` as `AggressiveInlining` allows RyuJIT to promote the `Enumerator` struct into registers completely, reducing load/store code a lot since it does not have to store the struct on the stack for ABI purposes. * Use stack allocation for `use/blockedPositions` * Optimize `AllocateWithSpill` a bit * Address feedback * Make `LiveInterval.AddRange(,)` more conservative Produces no diff against master, but just for good measure.
Diffstat (limited to 'ARMeilleure/CodeGen/RegisterAllocators/LiveInterval.cs')
-rw-r--r--ARMeilleure/CodeGen/RegisterAllocators/LiveInterval.cs410
1 files changed, 205 insertions, 205 deletions
diff --git a/ARMeilleure/CodeGen/RegisterAllocators/LiveInterval.cs b/ARMeilleure/CodeGen/RegisterAllocators/LiveInterval.cs
index be587652..77ad9541 100644
--- a/ARMeilleure/CodeGen/RegisterAllocators/LiveInterval.cs
+++ b/ARMeilleure/CodeGen/RegisterAllocators/LiveInterval.cs
@@ -1,341 +1,291 @@
-using ARMeilleure.Common;
using ARMeilleure.IntermediateRepresentation;
using System;
using System.Collections.Generic;
using System.Diagnostics;
-using System.Linq;
namespace ARMeilleure.CodeGen.RegisterAllocators
{
- class LiveInterval : IComparable<LiveInterval>
+ unsafe readonly struct LiveInterval : IComparable<LiveInterval>
{
public const int NotFound = -1;
- private LiveInterval _parent;
+ private struct Data
+ {
+ public int End;
+ public int SpillOffset;
- private SortedIntegerList _usePositions;
+ public LiveRange FirstRange;
+ public LiveRange PrevRange;
+ public LiveRange CurrRange;
- public int UsesCount => _usePositions.Count;
+ public LiveInterval Parent;
- private List<LiveRange> _ranges;
+ public UseList Uses;
+ public LiveIntervalList Children;
- private SortedList<int, LiveInterval> _childs;
+ public Operand Local;
+ public Register Register;
- public bool IsSplit => _childs.Count != 0;
+ public bool IsFixed;
+ }
- public Operand Local { get; }
+ private readonly Data* _data;
- public Register Register { get; set; }
+ private ref int End => ref _data->End;
+ private ref LiveRange FirstRange => ref _data->FirstRange;
+ private ref LiveRange CurrRange => ref _data->CurrRange;
+ private ref LiveRange PrevRange => ref _data->PrevRange;
+ private ref LiveInterval Parent => ref _data->Parent;
+ private ref UseList Uses => ref _data->Uses;
+ private ref LiveIntervalList Children => ref _data->Children;
- public int SpillOffset { get; private set; }
+ public Operand Local => _data->Local;
+ public ref Register Register => ref _data->Register;
+ public ref int SpillOffset => ref _data->SpillOffset;
+ public bool IsFixed => _data->IsFixed;
+ public bool IsEmpty => FirstRange == default;
+ public bool IsSplit => Children.Count != 0;
public bool IsSpilled => SpillOffset != -1;
- public bool IsFixed { get; }
- public bool IsEmpty => _ranges.Count == 0;
+ public int UsesCount => Uses.Count;
- public LiveInterval(Operand local = default, LiveInterval parent = null)
+ public LiveInterval(Operand local = default, LiveInterval parent = default)
{
- Local = local;
- _parent = parent ?? this;
+ _data = Allocators.LiveIntervals.Allocate<Data>();
+ *_data = default;
- _usePositions = new SortedIntegerList();
+ _data->IsFixed = false;
+ _data->Local = local;
- _ranges = new List<LiveRange>();
+ Parent = parent == default ? this : parent;
+ Uses = new UseList();
+ Children = new LiveIntervalList();
- _childs = new SortedList<int, LiveInterval>();
+ FirstRange = default;
+ CurrRange = default;
+ PrevRange = default;
SpillOffset = -1;
}
- public LiveInterval(Register register) : this()
+ public LiveInterval(Register register) : this(local: default, parent: default)
{
- IsFixed = true;
+ _data->IsFixed = true;
+
Register = register;
}
- public void SetStart(int position)
+ public void Reset()
{
- if (_ranges.Count != 0)
- {
- Debug.Assert(position != _ranges[0].End);
+ PrevRange = default;
+ CurrRange = FirstRange;
+ }
- _ranges[0] = new LiveRange(position, _ranges[0].End);
- }
- else
+ public void Forward(int position)
+ {
+ LiveRange prev = PrevRange;
+ LiveRange curr = CurrRange;
+
+ while (curr != default && curr.Start < position && !curr.Overlaps(position))
{
- _ranges.Add(new LiveRange(position, position + 1));
+ prev = curr;
+ curr = curr.Next;
}
+
+ PrevRange = prev;
+ CurrRange = curr;
}
public int GetStart()
{
- if (_ranges.Count == 0)
- {
- throw new InvalidOperationException("Empty interval.");
- }
+ Debug.Assert(!IsEmpty, "Empty LiveInterval cannot have a start position.");
- return _ranges[0].Start;
+ return FirstRange.Start;
}
- public void SetEnd(int position)
+ public void SetStart(int position)
{
- if (_ranges.Count != 0)
+ if (FirstRange != default)
{
- int lastIdx = _ranges.Count - 1;
-
- Debug.Assert(position != _ranges[lastIdx].Start);
+ Debug.Assert(position != FirstRange.End);
- _ranges[lastIdx] = new LiveRange(_ranges[lastIdx].Start, position);
+ FirstRange.Start = position;
}
else
{
- _ranges.Add(new LiveRange(position, position + 1));
+ FirstRange = new LiveRange(position, position + 1);
+ End = position + 1;
}
}
public int GetEnd()
{
- if (_ranges.Count == 0)
- {
- throw new InvalidOperationException("Empty interval.");
- }
+ Debug.Assert(!IsEmpty, "Empty LiveInterval cannot have an end position.");
- return _ranges[_ranges.Count - 1].End;
+ return End;
}
public void AddRange(int start, int end)
{
- if (start >= end)
- {
- throw new ArgumentException("Invalid range start position " + start + ", " + end);
- }
+ Debug.Assert(start < end, $"Invalid range start position {start}, {end}");
- int index = _ranges.BinarySearch(new LiveRange(start, end));
-
- if (index >= 0)
+ if (FirstRange != default)
{
- // New range insersects with an existing range, we need to remove
- // all the intersecting ranges before adding the new one.
- // We also extend the new range as needed, based on the values of
- // the existing ranges being removed.
- int lIndex = index;
- int rIndex = index;
-
- while (lIndex > 0 && _ranges[lIndex - 1].End >= start)
+ // If the new range ends exactly where the first range start, then coalesce together.
+ if (end == FirstRange.Start)
{
- lIndex--;
- }
+ FirstRange.Start = start;
- while (rIndex + 1 < _ranges.Count && _ranges[rIndex + 1].Start <= end)
- {
- rIndex++;
+ return;
}
-
- if (start > _ranges[lIndex].Start)
+ // If the new range is already contained, then coalesce together.
+ else if (FirstRange.Overlaps(start, end))
{
- start = _ranges[lIndex].Start;
- }
+ FirstRange.Start = Math.Min(FirstRange.Start, start);
+ FirstRange.End = Math.Max(FirstRange.End, end);
+ End = Math.Max(End, end);
- if (end < _ranges[rIndex].End)
- {
- end = _ranges[rIndex].End;
+ Debug.Assert(FirstRange.Next == default || !FirstRange.Overlaps(FirstRange.Next));
+ return;
}
-
- _ranges.RemoveRange(lIndex, (rIndex - lIndex) + 1);
-
- InsertRange(lIndex, start, end);
- }
- else
- {
- InsertRange(~index, start, end);
- }
- }
-
- private void InsertRange(int index, int start, int end)
- {
- // Here we insert a new range on the ranges list.
- // If possible, we extend an existing range rather than inserting a new one.
- // We can extend an existing range if any of the following conditions are true:
- // - The new range starts right after the end of the previous range on the list.
- // - The new range ends right before the start of the next range on the list.
- // If both cases are true, we can extend either one. We prefer to extend the
- // previous range, and then remove the next one, but theres no specific reason
- // for that, extending either one will do.
- int? extIndex = null;
-
- if (index > 0 && _ranges[index - 1].End == start)
- {
- start = _ranges[index - 1].Start;
-
- extIndex = index - 1;
}
- if (index < _ranges.Count && _ranges[index].Start == end)
- {
- end = _ranges[index].End;
+ FirstRange = new LiveRange(start, end, FirstRange);
+ End = Math.Max(End, end);
- if (extIndex.HasValue)
- {
- _ranges.RemoveAt(index);
- }
- else
- {
- extIndex = index;
- }
- }
-
- if (extIndex.HasValue)
- {
- _ranges[extIndex.Value] = new LiveRange(start, end);
- }
- else
- {
- _ranges.Insert(index, new LiveRange(start, end));
- }
+ Debug.Assert(FirstRange.Next == default || !FirstRange.Overlaps(FirstRange.Next));
}
public void AddUsePosition(int position)
{
- // Inserts are in descending order, but ascending is faster for SortedIntegerList<>.
- // We flip the ordering, then iterate backwards when using the final list.
- _usePositions.Add(-position);
+ Uses.Add(position);
}
public bool Overlaps(int position)
{
- return _ranges.BinarySearch(new LiveRange(position, position + 1)) >= 0;
- }
+ LiveRange curr = CurrRange;
- public bool Overlaps(LiveInterval other)
- {
- foreach (LiveRange range in other._ranges)
+ while (curr != default && curr.Start <= position)
{
- if (_ranges.BinarySearch(range) >= 0)
+ if (curr.Overlaps(position))
{
return true;
}
+
+ curr = curr.Next;
}
return false;
}
+ public bool Overlaps(LiveInterval other)
+ {
+ return GetOverlapPosition(other) != NotFound;
+ }
+
public int GetOverlapPosition(LiveInterval other)
{
- foreach (LiveRange range in other._ranges)
- {
- int overlapIndex = _ranges.BinarySearch(range);
+ LiveRange a = CurrRange;
+ LiveRange b = other.CurrRange;
- if (overlapIndex >= 0)
+ while (a != default)
+ {
+ while (b != default && b.Start < a.Start)
{
- // It's possible that we have multiple overlaps within a single interval,
- // in this case, we pick the one with the lowest start position, since
- // we return the first overlap position.
- while (overlapIndex > 0 && _ranges[overlapIndex - 1].End > range.Start)
+ if (a.Overlaps(b))
{
- overlapIndex--;
+ return a.Start;
}
- LiveRange overlappingRange = _ranges[overlapIndex];
+ b = b.Next;
+ }
- return overlappingRange.Start;
+ if (b == default)
+ {
+ break;
+ }
+ else if (a.Overlaps(b))
+ {
+ return a.Start;
}
+
+ a = a.Next;
}
return NotFound;
}
- public IEnumerable<LiveInterval> SplitChilds()
+ public ReadOnlySpan<LiveInterval> SplitChildren()
{
- return _childs.Values;
+ return Parent.Children.Span;
}
- public IList<int> UsePositions()
+ public ReadOnlySpan<int> UsePositions()
{
- return _usePositions.GetList();
+ return Uses.Span;
}
public int FirstUse()
{
- if (_usePositions.Count == 0)
- {
- return NotFound;
- }
-
- return -_usePositions.Last();
+ return Uses.FirstUse;
}
public int NextUseAfter(int position)
{
- int index = _usePositions.FindLessEqualIndex(-position);
- return (index >= 0) ? -_usePositions[index] : NotFound;
- }
-
- public void RemoveAfter(int position)
- {
- int index = _usePositions.FindLessEqualIndex(-position);
- _usePositions.RemoveRange(0, index + 1);
+ return Uses.NextUse(position);
}
public LiveInterval Split(int position)
{
- LiveInterval right = new LiveInterval(Local, _parent);
+ LiveInterval result = new(Local, Parent);
+ result.End = End;
- int splitIndex = 0;
+ LiveRange prev = PrevRange;
+ LiveRange curr = CurrRange;
- for (; splitIndex < _ranges.Count; splitIndex++)
+ while (curr != default && curr.Start < position && !curr.Overlaps(position))
{
- LiveRange range = _ranges[splitIndex];
-
- if (position > range.Start && position < range.End)
- {
- right._ranges.Add(new LiveRange(position, range.End));
-
- range = new LiveRange(range.Start, position);
-
- _ranges[splitIndex++] = range;
-
- break;
- }
-
- if (range.Start >= position)
- {
- break;
- }
+ prev = curr;
+ curr = curr.Next;
}
- if (splitIndex < _ranges.Count)
+ if (curr.Start >= position)
{
- int count = _ranges.Count - splitIndex;
+ prev.Next = default;
- right._ranges.AddRange(_ranges.GetRange(splitIndex, count));
+ result.FirstRange = curr;
- _ranges.RemoveRange(splitIndex, count);
+ End = prev.End;
}
-
- int addAfter = _usePositions.FindLessEqualIndex(-position);
- for (int index = addAfter; index >= 0; index--)
+ else
{
- int usePosition = _usePositions[index];
- right._usePositions.Add(usePosition);
+ result.FirstRange = new LiveRange(position, curr.End, curr.Next);
+
+ curr.End = position;
+ curr.Next = default;
+
+ End = curr.End;
}
- RemoveAfter(position);
+ result.Uses = Uses.Split(position);
- Debug.Assert(_ranges.Count != 0, "Left interval is empty after split.");
+ AddSplitChild(result);
- Debug.Assert(right._ranges.Count != 0, "Right interval is empty after split.");
+ Debug.Assert(!IsEmpty, "Left interval is empty after split.");
+ Debug.Assert(!result.IsEmpty, "Right interval is empty after split.");
- AddSplitChild(right);
+ // Make sure the iterator in the new split is pointing to the start.
+ result.Reset();
- return right;
+ return result;
}
private void AddSplitChild(LiveInterval child)
{
- Debug.Assert(!child.IsEmpty, "Trying to insert a empty interval.");
+ Debug.Assert(!child.IsEmpty, "Trying to insert an empty interval.");
- _parent._childs.Add(child.GetStart(), child);
+ Parent.Children.Add(child);
}
public LiveInterval GetSplitChild(int position)
@@ -345,20 +295,24 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
return this;
}
- foreach (LiveInterval splitChild in _childs.Values)
+ foreach (LiveInterval splitChild in SplitChildren())
{
if (splitChild.Overlaps(position))
{
return splitChild;
}
+ else if (splitChild.GetStart() > position)
+ {
+ break;
+ }
}
- return null;
+ return default;
}
public bool TrySpillWithSiblingOffset()
{
- foreach (LiveInterval splitChild in _parent._childs.Values)
+ foreach (LiveInterval splitChild in SplitChildren())
{
if (splitChild.IsSpilled)
{
@@ -376,19 +330,65 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
SpillOffset = offset;
}
- public int CompareTo(LiveInterval other)
+ public int CompareTo(LiveInterval interval)
{
- if (_ranges.Count == 0 || other._ranges.Count == 0)
+ if (FirstRange == default || interval.FirstRange == default)
{
- return _ranges.Count.CompareTo(other._ranges.Count);
+ return 0;
}
- return _ranges[0].Start.CompareTo(other._ranges[0].Start);
+ return GetStart().CompareTo(interval.GetStart());
+ }
+
+ public bool Equals(LiveInterval interval)
+ {
+ return interval._data == _data;
+ }
+
+ public override bool Equals(object obj)
+ {
+ return obj is LiveInterval interval && Equals(interval);
+ }
+
+ public static bool operator ==(LiveInterval a, LiveInterval b)
+ {
+ return a.Equals(b);
+ }
+
+ public static bool operator !=(LiveInterval a, LiveInterval b)
+ {
+ return !a.Equals(b);
+ }
+
+ public override int GetHashCode()
+ {
+ return HashCode.Combine((IntPtr)_data);
}
public override string ToString()
{
- return string.Join("; ", _ranges);
+ LiveInterval self = this;
+
+ IEnumerable<string> GetRanges()
+ {
+ LiveRange curr = self.CurrRange;
+
+ while (curr != default)
+ {
+ if (curr == self.CurrRange)
+ {
+ yield return "*" + curr;
+ }
+ else
+ {
+ yield return curr.ToString();
+ }
+
+ curr = curr.Next;
+ }
+ }
+
+ return string.Join(", ", GetRanges());
}
}
} \ No newline at end of file