Ryujinx/ARMeilleure/CodeGen/RegisterAllocators/CopyResolver.cs
riperiperi 8226997bc7
CodeGen Optimisations (LSRA and Translator) (#978)
* Start of JIT garbage collection improvements

- thread static pool for Operand, MemoryOperand, Operation
- Operands and Operations are always to be constructed via their static
helper classes, so they can be pooled.
- removing LinkedList from Node for sources/destinations (replaced with
List<>s for now, but probably could do arrays since size is bounded)
- removing params constructors from Node
- LinkedList<> to List<> with Clear() for Operand assignments/uses
- ThreadStaticPool is very simple and basically just exists for the
purpose of our specific translation allocation problem. Right now it
will stay at the worst case allocation count for that thread (so far) -
the pool can never shrink.

- Still some cases of Operand[] that haven't been removed yet. Will need
to evaluate them (eg. is there a reasonable max number of params for
Calls?)

* ConcurrentStack instead of ConcurrentQueue for Rejit

* Optimize some parts of LSRA

- BitMap now operates on 64-bit int rather than 32-bit
- BitMap is now pooled in a ThreadStatic pool (within lrsa)
- BitMap now is now its own iterator. Marginally speeds up iterating
through the bits.
- A few cases where enumerators were generated have been converted to
forms that generate less garbage.
- New data structure for sorting _usePositions in LiveIntervals. Much
faster split, NextUseAfter, initial insertion. Random insertion is
slightly slower.
- That last one is WIP since you need to insert the values backwards. It
would be ideal if it just flipped it for you, uncomplicating things on
the caller side.

* Use a static pool of thread static pools. (yes.)

Prevents each execution thread creating its own lowCq pool and making me cry.

* Move constant value to top, change naming convention.

* Fix iteration of memory operands.

* Increase max thread count.

* Address Feedback
2020-03-18 22:44:32 +11:00

249 lines
8.6 KiB
C#

using ARMeilleure.IntermediateRepresentation;
using System;
using System.Collections.Generic;
using static ARMeilleure.IntermediateRepresentation.OperandHelper;
using static ARMeilleure.IntermediateRepresentation.OperationHelper;
namespace ARMeilleure.CodeGen.RegisterAllocators
{
class CopyResolver
{
private class ParallelCopy
{
private struct Copy
{
public Register Dest { get; }
public Register Source { get; }
public OperandType Type { get; }
public Copy(Register dest, Register source, OperandType type)
{
Dest = dest;
Source = source;
Type = type;
}
}
private List<Copy> _copies;
public int Count => _copies.Count;
public ParallelCopy()
{
_copies = new List<Copy>();
}
public void AddCopy(Register dest, Register source, OperandType type)
{
_copies.Add(new Copy(dest, source, type));
}
public void Sequence(List<Operation> sequence)
{
Dictionary<Register, Register> locations = new Dictionary<Register, Register>();
Dictionary<Register, Register> sources = new Dictionary<Register, Register>();
Dictionary<Register, OperandType> types = new Dictionary<Register, OperandType>();
Queue<Register> pendingQueue = new Queue<Register>();
Queue<Register> readyQueue = new Queue<Register>();
foreach (Copy copy in _copies)
{
locations[copy.Source] = copy.Source;
sources[copy.Dest] = copy.Source;
types[copy.Dest] = copy.Type;
pendingQueue.Enqueue(copy.Dest);
}
foreach (Copy copy in _copies)
{
// If the destination is not used anywhere, we can assign it immediately.
if (!locations.ContainsKey(copy.Dest))
{
readyQueue.Enqueue(copy.Dest);
}
}
while (pendingQueue.TryDequeue(out Register current))
{
Register copyDest;
Register origSource;
Register copySource;
while (readyQueue.TryDequeue(out copyDest))
{
origSource = sources[copyDest];
copySource = locations[origSource];
OperandType type = types[copyDest];
EmitCopy(sequence, GetRegister(copyDest, type), GetRegister(copySource, type));
locations[origSource] = copyDest;
if (origSource == copySource && sources.ContainsKey(origSource))
{
readyQueue.Enqueue(origSource);
}
}
copyDest = current;
origSource = sources[copyDest];
copySource = locations[origSource];
if (copyDest != copySource)
{
OperandType type = types[copyDest];
type = type.IsInteger() ? OperandType.I64 : OperandType.V128;
EmitXorSwap(sequence, GetRegister(copyDest, type), GetRegister(copySource, type));
locations[origSource] = copyDest;
Register swapOther = copySource;
if (copyDest != locations[sources[copySource]])
{
// Find the other swap destination register.
// To do that, we search all the pending registers, and pick
// the one where the copy source register is equal to the
// current destination register being processed (copyDest).
foreach (Register pending in pendingQueue)
{
// Is this a copy of pending <- copyDest?
if (copyDest == locations[sources[pending]])
{
swapOther = pending;
break;
}
}
}
// The value that was previously at "copyDest" now lives on
// "copySource" thanks to the swap, now we need to update the
// location for the next copy that is supposed to copy the value
// that used to live on "copyDest".
locations[sources[swapOther]] = copySource;
}
}
}
private static void EmitCopy(List<Operation> sequence, Operand x, Operand y)
{
sequence.Add(Operation(Instruction.Copy, x, y));
}
private static void EmitXorSwap(List<Operation> sequence, Operand x, Operand y)
{
sequence.Add(Operation(Instruction.BitwiseExclusiveOr, x, x, y));
sequence.Add(Operation(Instruction.BitwiseExclusiveOr, y, y, x));
sequence.Add(Operation(Instruction.BitwiseExclusiveOr, x, x, y));
}
}
private Queue<Operation> _fillQueue = new Queue<Operation>();
private Queue<Operation> _spillQueue = new Queue<Operation>();
private ParallelCopy _parallelCopy;
public bool HasCopy { get; private set; }
public CopyResolver()
{
_fillQueue = new Queue<Operation>();
_spillQueue = new Queue<Operation>();
_parallelCopy = new ParallelCopy();
}
public void AddSplit(LiveInterval left, LiveInterval right)
{
if (left.Local != right.Local)
{
throw new ArgumentException("Intervals of different variables are not allowed.");
}
OperandType type = left.Local.Type;
if (left.IsSpilled && !right.IsSpilled)
{
// Move from the stack to a register.
AddSplitFill(left, right, type);
}
else if (!left.IsSpilled && right.IsSpilled)
{
// Move from a register to the stack.
AddSplitSpill(left, right, type);
}
else if (!left.IsSpilled && !right.IsSpilled && left.Register != right.Register)
{
// Move from one register to another.
AddSplitCopy(left, right, type);
}
else if (left.SpillOffset != right.SpillOffset)
{
// This would be the stack-to-stack move case, but this is not supported.
throw new ArgumentException("Both intervals were spilled.");
}
}
private void AddSplitFill(LiveInterval left, LiveInterval right, OperandType type)
{
Operand register = GetRegister(right.Register, type);
Operand offset = Const(left.SpillOffset);
_fillQueue.Enqueue(Operation(Instruction.Fill, register, offset));
HasCopy = true;
}
private void AddSplitSpill(LiveInterval left, LiveInterval right, OperandType type)
{
Operand offset = Const(right.SpillOffset);
Operand register = GetRegister(left.Register, type);
_spillQueue.Enqueue(Operation(Instruction.Spill, null, offset, register));
HasCopy = true;
}
private void AddSplitCopy(LiveInterval left, LiveInterval right, OperandType type)
{
_parallelCopy.AddCopy(right.Register, left.Register, type);
HasCopy = true;
}
public Operation[] Sequence()
{
List<Operation> sequence = new List<Operation>();
while (_spillQueue.TryDequeue(out Operation spillOp))
{
sequence.Add(spillOp);
}
_parallelCopy.Sequence(sequence);
while (_fillQueue.TryDequeue(out Operation fillOp))
{
sequence.Add(fillOp);
}
return sequence.ToArray();
}
private static Operand GetRegister(Register reg, OperandType type)
{
return Register(reg.Index, reg.Type, type);
}
}
}