Ryujinx/ChocolArm64/Translation/ILEmitterCtx.cs

555 lines
15 KiB
C#
Raw Normal View History

using ChocolArm64.Decoders;
using ChocolArm64.Instructions;
using ChocolArm64.State;
using System;
using System.Collections.Generic;
using System.Reflection;
using System.Reflection.Emit;
namespace ChocolArm64.Translation
{
class ILEmitterCtx
{
private TranslatorCache _cache;
private Dictionary<long, ILLabel> _labels;
private int _blkIndex;
private int _opcIndex;
private Block[] _graph;
private Block _root;
public Block CurrBlock => _graph[_blkIndex];
public OpCode64 CurrOp => _graph[_blkIndex].OpCodes[_opcIndex];
private ILEmitter _emitter;
private ILBlock _ilBlock;
private OpCode64 _optOpLastCompare;
private OpCode64 _optOpLastFlagSet;
//This is the index of the temporary register, used to store temporary
//values needed by some functions, since IL doesn't have a swap instruction.
//You can use any value here as long it doesn't conflict with the indices
//for the other registers. Any value >= 64 or < 0 will do.
private const int Tmp1Index = -1;
private const int Tmp2Index = -2;
private const int Tmp3Index = -3;
private const int Tmp4Index = -4;
private const int Tmp5Index = -5;
private const int Tmp6Index = -6;
public ILEmitterCtx(
TranslatorCache cache,
Block[] graph,
Block root,
string subName)
{
_cache = cache ?? throw new ArgumentNullException(nameof(cache));
_graph = graph ?? throw new ArgumentNullException(nameof(graph));
_root = root ?? throw new ArgumentNullException(nameof(root));
_labels = new Dictionary<long, ILLabel>();
_emitter = new ILEmitter(graph, root, subName);
_ilBlock = _emitter.GetIlBlock(0);
_opcIndex = -1;
if (graph.Length == 0 || !AdvanceOpCode())
{
throw new ArgumentException(nameof(graph));
}
}
public TranslatedSub GetSubroutine()
{
return _emitter.GetSubroutine();
}
public bool AdvanceOpCode()
{
if (_opcIndex + 1 == CurrBlock.OpCodes.Count &&
_blkIndex + 1 == _graph.Length)
{
return false;
}
while (++_opcIndex >= (CurrBlock?.OpCodes.Count ?? 0))
{
_blkIndex++;
_opcIndex = -1;
_optOpLastFlagSet = null;
_optOpLastCompare = null;
_ilBlock = _emitter.GetIlBlock(_blkIndex);
}
return true;
}
public void EmitOpCode()
{
if (_opcIndex == 0)
{
MarkLabel(GetLabel(CurrBlock.Position));
EmitSynchronization();
}
CurrOp.Emitter(this);
_ilBlock.Add(new ILBarrier());
}
private void EmitSynchronization()
{
EmitLdarg(TranslatedSub.StateArgIdx);
EmitLdc_I4(CurrBlock.OpCodes.Count);
EmitPrivateCall(typeof(CpuThreadState), nameof(CpuThreadState.Synchronize));
EmitLdc_I4(0);
ILLabel lblContinue = new ILLabel();
Emit(OpCodes.Bne_Un_S, lblContinue);
EmitLdc_I8(0);
Emit(OpCodes.Ret);
MarkLabel(lblContinue);
}
public bool TryOptEmitSubroutineCall()
{
if (CurrBlock.Next == null)
{
return false;
}
if (CurrOp.Emitter != InstEmit.Bl)
{
return false;
}
if (!_cache.TryGetSubroutine(((OpCodeBImmAl64)CurrOp).Imm, out TranslatedSub subroutine))
{
return false;
}
for (int index = 0; index < TranslatedSub.FixedArgTypes.Length; index++)
{
EmitLdarg(index);
}
foreach (Register reg in subroutine.Params)
{
switch (reg.Type)
{
case RegisterType.Flag: Ldloc(reg.Index, IoType.Flag); break;
case RegisterType.Int: Ldloc(reg.Index, IoType.Int); break;
case RegisterType.Vector: Ldloc(reg.Index, IoType.Vector); break;
}
}
EmitCall(subroutine.Method);
subroutine.AddCaller(_root.Position);
return true;
}
public void TryOptMarkCondWithoutCmp()
{
_optOpLastCompare = CurrOp;
InstEmitAluHelper.EmitDataLoadOpers(this);
Stloc(Tmp4Index, IoType.Int);
Stloc(Tmp3Index, IoType.Int);
}
private Dictionary<Cond, System.Reflection.Emit.OpCode> _branchOps = new Dictionary<Cond, System.Reflection.Emit.OpCode>()
{
{ Cond.Eq, OpCodes.Beq },
{ Cond.Ne, OpCodes.Bne_Un },
{ Cond.GeUn, OpCodes.Bge_Un },
{ Cond.LtUn, OpCodes.Blt_Un },
{ Cond.GtUn, OpCodes.Bgt_Un },
{ Cond.LeUn, OpCodes.Ble_Un },
{ Cond.Ge, OpCodes.Bge },
{ Cond.Lt, OpCodes.Blt },
{ Cond.Gt, OpCodes.Bgt },
{ Cond.Le, OpCodes.Ble }
};
public void EmitCondBranch(ILLabel target, Cond cond)
{
System.Reflection.Emit.OpCode ilOp;
int intCond = (int)cond;
if (_optOpLastCompare != null &&
_optOpLastCompare == _optOpLastFlagSet && _branchOps.ContainsKey(cond))
{
Ldloc(Tmp3Index, IoType.Int, _optOpLastCompare.RegisterSize);
Ldloc(Tmp4Index, IoType.Int, _optOpLastCompare.RegisterSize);
ilOp = _branchOps[cond];
}
else if (intCond < 14)
{
int condTrue = intCond >> 1;
switch (condTrue)
{
case 0: EmitLdflg((int)PState.ZBit); break;
case 1: EmitLdflg((int)PState.CBit); break;
case 2: EmitLdflg((int)PState.NBit); break;
case 3: EmitLdflg((int)PState.VBit); break;
case 4:
EmitLdflg((int)PState.CBit);
EmitLdflg((int)PState.ZBit);
Emit(OpCodes.Not);
Emit(OpCodes.And);
break;
case 5:
case 6:
EmitLdflg((int)PState.NBit);
EmitLdflg((int)PState.VBit);
Emit(OpCodes.Ceq);
if (condTrue == 6)
{
EmitLdflg((int)PState.ZBit);
Emit(OpCodes.Not);
Emit(OpCodes.And);
}
break;
}
ilOp = (intCond & 1) != 0
? OpCodes.Brfalse
: OpCodes.Brtrue;
}
else
{
ilOp = OpCodes.Br;
}
Emit(ilOp, target);
}
public void EmitCast(IntType intType)
{
switch (intType)
{
case IntType.UInt8: Emit(OpCodes.Conv_U1); break;
case IntType.UInt16: Emit(OpCodes.Conv_U2); break;
case IntType.UInt32: Emit(OpCodes.Conv_U4); break;
case IntType.UInt64: Emit(OpCodes.Conv_U8); break;
case IntType.Int8: Emit(OpCodes.Conv_I1); break;
case IntType.Int16: Emit(OpCodes.Conv_I2); break;
case IntType.Int32: Emit(OpCodes.Conv_I4); break;
case IntType.Int64: Emit(OpCodes.Conv_I8); break;
}
bool sz64 = CurrOp.RegisterSize != RegisterSize.Int32;
if (sz64 == (intType == IntType.UInt64 ||
intType == IntType.Int64))
{
return;
}
if (sz64)
{
Emit(intType >= IntType.Int8
? OpCodes.Conv_I8
: OpCodes.Conv_U8);
}
else
{
Emit(OpCodes.Conv_U4);
}
}
public void EmitLsl(int amount) => EmitIlShift(amount, OpCodes.Shl);
public void EmitLsr(int amount) => EmitIlShift(amount, OpCodes.Shr_Un);
public void EmitAsr(int amount) => EmitIlShift(amount, OpCodes.Shr);
private void EmitIlShift(int amount, System.Reflection.Emit.OpCode ilOp)
{
if (amount > 0)
{
EmitLdc_I4(amount);
Emit(ilOp);
}
}
public void EmitRor(int amount)
{
if (amount > 0)
{
Stloc(Tmp2Index, IoType.Int);
Ldloc(Tmp2Index, IoType.Int);
EmitLdc_I4(amount);
Emit(OpCodes.Shr_Un);
Ldloc(Tmp2Index, IoType.Int);
EmitLdc_I4(CurrOp.GetBitsCount() - amount);
Emit(OpCodes.Shl);
Emit(OpCodes.Or);
}
}
public ILLabel GetLabel(long position)
{
if (!_labels.TryGetValue(position, out ILLabel output))
{
output = new ILLabel();
_labels.Add(position, output);
}
return output;
}
public void MarkLabel(ILLabel label)
{
_ilBlock.Add(label);
}
public void Emit(System.Reflection.Emit.OpCode ilOp)
{
_ilBlock.Add(new ILOpCode(ilOp));
}
public void Emit(System.Reflection.Emit.OpCode ilOp, ILLabel label)
{
_ilBlock.Add(new ILOpCodeBranch(ilOp, label));
}
public void Emit(string text)
{
_ilBlock.Add(new ILOpCodeLog(text));
}
public void EmitLdarg(int index)
{
_ilBlock.Add(new IlOpCodeLoad(index, IoType.Arg));
}
public void EmitLdintzr(int index)
{
if (index != CpuThreadState.ZrIndex)
{
EmitLdint(index);
}
else
{
EmitLdc_I(0);
}
}
public void EmitStintzr(int index)
{
if (index != CpuThreadState.ZrIndex)
{
EmitStint(index);
}
else
{
Emit(OpCodes.Pop);
}
}
public void EmitLoadState(Block retBlk)
{
_ilBlock.Add(new IlOpCodeLoad(Array.IndexOf(_graph, retBlk), IoType.Fields));
}
public void EmitStoreState()
{
_ilBlock.Add(new IlOpCodeStore(Array.IndexOf(_graph, CurrBlock), IoType.Fields));
}
public void EmitLdtmp() => EmitLdint(Tmp1Index);
public void EmitSttmp() => EmitStint(Tmp1Index);
public void EmitLdvectmp() => EmitLdvec(Tmp5Index);
public void EmitStvectmp() => EmitStvec(Tmp5Index);
public void EmitLdvectmp2() => EmitLdvec(Tmp6Index);
public void EmitStvectmp2() => EmitStvec(Tmp6Index);
public void EmitLdint(int index) => Ldloc(index, IoType.Int);
public void EmitStint(int index) => Stloc(index, IoType.Int);
public void EmitLdvec(int index) => Ldloc(index, IoType.Vector);
public void EmitStvec(int index) => Stloc(index, IoType.Vector);
public void EmitLdflg(int index) => Ldloc(index, IoType.Flag);
public void EmitStflg(int index)
{
_optOpLastFlagSet = CurrOp;
Stloc(index, IoType.Flag);
}
private void Ldloc(int index, IoType ioType)
{
_ilBlock.Add(new IlOpCodeLoad(index, ioType, CurrOp.RegisterSize));
}
private void Ldloc(int index, IoType ioType, RegisterSize registerSize)
{
_ilBlock.Add(new IlOpCodeLoad(index, ioType, registerSize));
}
private void Stloc(int index, IoType ioType)
{
_ilBlock.Add(new IlOpCodeStore(index, ioType, CurrOp.RegisterSize));
}
public void EmitCallPropGet(Type objType, string propName)
{
if (objType == null)
{
throw new ArgumentNullException(nameof(objType));
}
if (propName == null)
{
throw new ArgumentNullException(nameof(propName));
}
EmitCall(objType.GetMethod($"get_{propName}"));
}
public void EmitCallPropSet(Type objType, string propName)
{
if (objType == null)
{
throw new ArgumentNullException(nameof(objType));
}
if (propName == null)
{
throw new ArgumentNullException(nameof(propName));
}
EmitCall(objType.GetMethod($"set_{propName}"));
}
public void EmitCall(Type objType, string mthdName)
{
if (objType == null)
{
throw new ArgumentNullException(nameof(objType));
}
if (mthdName == null)
{
throw new ArgumentNullException(nameof(mthdName));
}
EmitCall(objType.GetMethod(mthdName));
}
public void EmitPrivateCall(Type objType, string mthdName)
{
if (objType == null)
{
throw new ArgumentNullException(nameof(objType));
}
if (mthdName == null)
{
throw new ArgumentNullException(nameof(mthdName));
}
EmitCall(objType.GetMethod(mthdName, BindingFlags.Instance | BindingFlags.NonPublic));
}
public void EmitCall(MethodInfo mthdInfo)
{
if (mthdInfo == null)
{
throw new ArgumentNullException(nameof(mthdInfo));
}
_ilBlock.Add(new ILOpCodeCall(mthdInfo));
}
public void EmitLdc_I(long value)
{
if (CurrOp.RegisterSize == RegisterSize.Int32)
{
EmitLdc_I4((int)value);
}
else
{
EmitLdc_I8(value);
}
}
public void EmitLdc_I4(int value)
{
_ilBlock.Add(new ILOpCodeConst(value));
}
public void EmitLdc_I8(long value)
{
_ilBlock.Add(new ILOpCodeConst(value));
}
public void EmitLdc_R4(float value)
{
_ilBlock.Add(new ILOpCodeConst(value));
}
public void EmitLdc_R8(double value)
{
_ilBlock.Add(new ILOpCodeConst(value));
}
public void EmitZnFlagCheck()
{
EmitZnCheck(OpCodes.Ceq, (int)PState.ZBit);
EmitZnCheck(OpCodes.Clt, (int)PState.NBit);
}
private void EmitZnCheck(System.Reflection.Emit.OpCode ilCmpOp, int flag)
{
Emit(OpCodes.Dup);
Emit(OpCodes.Ldc_I4_0);
if (CurrOp.RegisterSize != RegisterSize.Int32)
{
Emit(OpCodes.Conv_I8);
}
Emit(ilCmpOp);
EmitStflg(flag);
}
}
}