diff options
| author | FICTURE7 <FICTURE7@gmail.com> | 2021-10-09 01:15:44 +0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-10-08 18:15:44 -0300 |
| commit | 69093cf2d69490862aff974f170cee63a0016fd0 (patch) | |
| tree | 24507a2d3da862416d3c2d3ca228c89cb40d5437 /ARMeilleure/CodeGen/RegisterAllocators/LiveInterval.cs | |
| parent | c54a14d0b8d445d9d0074861dca816cc801e4008 (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.cs | 410 |
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 |
