Implement a new JIT for Arm devices (#6057)

* Implement a new JIT for Arm devices

* Auto-format

* Make a lot of Assembler members read-only

* More read-only

* Fix more warnings

* ObjectDisposedException.ThrowIf

* New JIT cache for platforms that enforce W^X, currently unused

* Remove unused using

* Fix assert

* Pass memory manager type around

* Safe memory manager mode support + other improvements

* Actual safe memory manager mode masking support

* PR feedback
This commit is contained in:
gdkchan
2024-01-20 11:11:28 -03:00
committed by GitHub
parent 331c07807f
commit 427b7d06b5
135 changed files with 43322 additions and 24 deletions

View File

@@ -0,0 +1,171 @@
namespace Ryujinx.Cpu.LightningJit.Graph
{
static class DataFlow
{
public static (RegisterMask[], RegisterMask[]) GetGlobalUses(IBlockList blocks)
{
// Compute local register inputs and outputs used inside blocks.
RegisterMask[] localInputs = new RegisterMask[blocks.Count];
RegisterMask[] localOutputs = new RegisterMask[blocks.Count];
for (int index = 0; index < blocks.Count; index++)
{
IBlock block = blocks[index];
RegisterUse use = block.ComputeUseMasks();
localInputs[block.Index] = use.Read;
localOutputs[block.Index] = use.Write;
}
// Compute global register inputs and outputs used across blocks.
RegisterMask[] globalInputs = new RegisterMask[blocks.Count];
RegisterMask[] globalOutputs = new RegisterMask[blocks.Count];
bool modified;
// Compute register outputs.
do
{
modified = false;
for (int index = 0; index < blocks.Count; index++)
{
IBlock block = blocks[index];
int firstPIndex = GetFirstPredecessorIndex(block);
if (firstPIndex >= 0)
{
IBlock predecessor = block.GetPredecessor(firstPIndex);
RegisterMask outputs = globalOutputs[predecessor.Index];
for (int pIndex = firstPIndex + 1; pIndex < block.PredecessorsCount; pIndex++)
{
predecessor = block.GetPredecessor(pIndex);
if (predecessor.EndsWithContextStore())
{
// If a block ended with a context store, then we don't need to care
// about any of it's outputs, as they have already been saved to the context.
// Common outputs must be reset as doing a context stores indicates we will
// do a function call and wipe all register values.
continue;
}
outputs |= globalOutputs[predecessor.Index];
}
outputs |= localOutputs[block.Index];
modified |= Exchange(globalOutputs, block.Index, globalOutputs[block.Index] | outputs);
}
else
{
modified |= Exchange(globalOutputs, block.Index, localOutputs[block.Index]);
}
}
}
while (modified);
// Compute register inputs.
do
{
modified = false;
for (int index = blocks.Count - 1; index >= 0; index--)
{
IBlock block = blocks[index];
RegisterMask cmnOutputs = RegisterMask.Zero;
RegisterMask allOutputs = RegisterMask.Zero;
int firstPIndex = GetFirstPredecessorIndex(block);
if (firstPIndex == 0)
{
IBlock predecessor = block.GetPredecessor(0);
// Assumes that block index 0 is the entry block.
cmnOutputs = block.Index != 0 ? globalOutputs[predecessor.Index] : RegisterMask.Zero;
allOutputs = globalOutputs[predecessor.Index];
for (int pIndex = 1; pIndex < block.PredecessorsCount; pIndex++)
{
predecessor = block.GetPredecessor(pIndex);
if (!predecessor.EndsWithContextStore())
{
RegisterMask outputs = globalOutputs[predecessor.Index];
cmnOutputs &= outputs;
allOutputs |= outputs;
}
else
{
cmnOutputs = RegisterMask.Zero;
}
}
}
else if (firstPIndex > 0)
{
IBlock predecessor = block.GetPredecessor(firstPIndex);
allOutputs = globalOutputs[predecessor.Index];
for (int pIndex = firstPIndex + 1; pIndex < block.PredecessorsCount; pIndex++)
{
predecessor = block.GetPredecessor(pIndex);
if (!predecessor.EndsWithContextStore())
{
allOutputs |= globalOutputs[predecessor.Index];
}
}
}
RegisterMask inputs = localInputs[block.Index];
// If this block will load from context at the end,
// we don't need to care about what comes next.
if (!block.EndsWithContextLoad())
{
for (int sIndex = 0; sIndex < block.SuccessorsCount; sIndex++)
{
inputs |= globalInputs[block.GetSuccessor(sIndex).Index] & ~localOutputs[block.Index];
}
}
inputs |= allOutputs & ~localOutputs[block.Index];
inputs &= ~cmnOutputs;
modified |= Exchange(globalInputs, block.Index, globalInputs[block.Index] | inputs);
}
}
while (modified);
return (globalInputs, globalOutputs);
}
private static bool Exchange(RegisterMask[] masks, int blkIndex, RegisterMask value)
{
ref RegisterMask curValue = ref masks[blkIndex];
bool changed = curValue != value;
curValue = value;
return changed;
}
private static int GetFirstPredecessorIndex(IBlock block)
{
for (int pIndex = 0; pIndex < block.PredecessorsCount; pIndex++)
{
if (!block.GetPredecessor(pIndex).EndsWithContextStore())
{
return pIndex;
}
}
return -1;
}
}
}

View File

@@ -0,0 +1,17 @@
namespace Ryujinx.Cpu.LightningJit.Graph
{
interface IBlock
{
int Index { get; }
int PredecessorsCount { get; }
int SuccessorsCount { get; }
IBlock GetSuccessor(int index);
IBlock GetPredecessor(int index);
RegisterUse ComputeUseMasks();
bool EndsWithContextLoad();
bool EndsWithContextStore();
}
}

View File

@@ -0,0 +1,9 @@
namespace Ryujinx.Cpu.LightningJit.Graph
{
interface IBlockList
{
int Count { get; }
IBlock this[int index] { get; }
}
}

View File

@@ -0,0 +1,60 @@
using System;
namespace Ryujinx.Cpu.LightningJit.Graph
{
readonly struct RegisterMask : IEquatable<RegisterMask>
{
public readonly uint GprMask;
public readonly uint FpSimdMask;
public readonly uint PStateMask;
public static RegisterMask Zero => new(0u, 0u, 0u);
public RegisterMask(uint gprMask, uint fpSimdMask, uint pStateMask)
{
GprMask = gprMask;
FpSimdMask = fpSimdMask;
PStateMask = pStateMask;
}
public static RegisterMask operator &(RegisterMask x, RegisterMask y)
{
return new(x.GprMask & y.GprMask, x.FpSimdMask & y.FpSimdMask, x.PStateMask & y.PStateMask);
}
public static RegisterMask operator |(RegisterMask x, RegisterMask y)
{
return new(x.GprMask | y.GprMask, x.FpSimdMask | y.FpSimdMask, x.PStateMask | y.PStateMask);
}
public static RegisterMask operator ~(RegisterMask x)
{
return new(~x.GprMask, ~x.FpSimdMask, ~x.PStateMask);
}
public static bool operator ==(RegisterMask x, RegisterMask y)
{
return x.Equals(y);
}
public static bool operator !=(RegisterMask x, RegisterMask y)
{
return !x.Equals(y);
}
public override bool Equals(object obj)
{
return obj is RegisterMask regMask && Equals(regMask);
}
public bool Equals(RegisterMask other)
{
return GprMask == other.GprMask && FpSimdMask == other.FpSimdMask && PStateMask == other.PStateMask;
}
public override int GetHashCode()
{
return HashCode.Combine(GprMask, FpSimdMask, PStateMask);
}
}
}

View File

@@ -0,0 +1,24 @@
namespace Ryujinx.Cpu.LightningJit.Graph
{
readonly struct RegisterUse
{
public readonly RegisterMask Read;
public readonly RegisterMask Write;
public RegisterUse(RegisterMask read, RegisterMask write)
{
Read = read;
Write = write;
}
public RegisterUse(
uint gprReadMask,
uint gprWriteMask,
uint fpSimdReadMask,
uint fpSimdWriteMask,
uint pStateReadMask,
uint pStateWriteMask) : this(new(gprReadMask, fpSimdReadMask, pStateReadMask), new(gprWriteMask, fpSimdWriteMask, pStateWriteMask))
{
}
}
}