| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517 |
- using Ryujinx.Graphics.Device;
- using System;
- using System.Collections.Generic;
- using System.Reflection.Emit;
- namespace Ryujinx.Graphics.Gpu.Engine.MME
- {
- /// <summary>
- /// Represents a Macro Just-in-Time compiler.
- /// </summary>R
- class MacroJitCompiler
- {
- private readonly DynamicMethod _meth;
- private readonly ILGenerator _ilGen;
- private readonly LocalBuilder[] _gprs;
- private readonly LocalBuilder _carry;
- private readonly LocalBuilder _methAddr;
- private readonly LocalBuilder _methIncr;
- /// <summary>
- /// Creates a new instance of the Macro Just-in-Time compiler.
- /// </summary>
- public MacroJitCompiler()
- {
- _meth = new DynamicMethod("Macro", typeof(void), new Type[] { typeof(MacroJitContext), typeof(IDeviceState), typeof(int) });
- _ilGen = _meth.GetILGenerator();
- _gprs = new LocalBuilder[8];
- for (int i = 1; i < 8; i++)
- {
- _gprs[i] = _ilGen.DeclareLocal(typeof(int));
- }
- _carry = _ilGen.DeclareLocal(typeof(int));
- _methAddr = _ilGen.DeclareLocal(typeof(int));
- _methIncr = _ilGen.DeclareLocal(typeof(int));
- _ilGen.Emit(OpCodes.Ldarg_2);
- _ilGen.Emit(OpCodes.Stloc, _gprs[1]);
- }
- public delegate void MacroExecute(MacroJitContext context, IDeviceState state, int arg0);
- /// <summary>
- /// Translates a new piece of GPU Macro code into host executable code.
- /// </summary>
- /// <param name="code">Code to be translated</param>
- /// <returns>Delegate of the host compiled code</returns>
- public MacroExecute Compile(ReadOnlySpan<int> code)
- {
- Dictionary<int, Label> labels = new Dictionary<int, Label>();
- int lastTarget = 0;
- int i;
- // Collect all branch targets.
- for (i = 0; i < code.Length; i++)
- {
- int opCode = code[i];
- if ((opCode & 7) == 7)
- {
- int target = i + (opCode >> 14);
- if (!labels.ContainsKey(target))
- {
- labels.Add(target, _ilGen.DefineLabel());
- }
- if (lastTarget < target)
- {
- lastTarget = target;
- }
- }
- bool exit = (opCode & 0x80) != 0;
- if (exit && i >= lastTarget)
- {
- break;
- }
- }
- // Code generation.
- for (i = 0; i < code.Length; i++)
- {
- if (labels.TryGetValue(i, out Label label))
- {
- _ilGen.MarkLabel(label);
- }
- Emit(code, i, labels);
- int opCode = code[i];
- bool exit = (opCode & 0x80) != 0;
- if (exit)
- {
- Emit(code, i + 1, labels);
- _ilGen.Emit(OpCodes.Ret);
- if (i >= lastTarget)
- {
- break;
- }
- }
- }
- if (i == code.Length)
- {
- _ilGen.Emit(OpCodes.Ret);
- }
- return _meth.CreateDelegate<MacroExecute>();
- }
- /// <summary>
- /// Emits IL equivalent to the Macro instruction at a given offset.
- /// </summary>
- /// <param name="code">GPU Macro code</param>
- /// <param name="offset">Offset, in words, where the instruction is located</param>
- /// <param name="labels">Labels for Macro branch targets, used by branch instructions</param>
- private void Emit(ReadOnlySpan<int> code, int offset, Dictionary<int, Label> labels)
- {
- int opCode = code[offset];
- if ((opCode & 7) < 7)
- {
- // Operation produces a value.
- AssignmentOperation asgOp = (AssignmentOperation)((opCode >> 4) & 7);
- EmitAluOp(opCode);
- switch (asgOp)
- {
- // Fetch parameter and ignore result.
- case AssignmentOperation.IgnoreAndFetch:
- _ilGen.Emit(OpCodes.Pop);
- EmitFetchParam();
- EmitStoreDstGpr(opCode);
- break;
- // Move result.
- case AssignmentOperation.Move:
- EmitStoreDstGpr(opCode);
- break;
- // Move result and use as Method Address.
- case AssignmentOperation.MoveAndSetMaddr:
- _ilGen.Emit(OpCodes.Dup);
- EmitStoreDstGpr(opCode);
- EmitStoreMethAddr();
- break;
- // Fetch parameter and send result.
- case AssignmentOperation.FetchAndSend:
- EmitFetchParam();
- EmitStoreDstGpr(opCode);
- EmitSend();
- break;
- // Move and send result.
- case AssignmentOperation.MoveAndSend:
- _ilGen.Emit(OpCodes.Dup);
- EmitStoreDstGpr(opCode);
- EmitSend();
- break;
- // Fetch parameter and use result as Method Address.
- case AssignmentOperation.FetchAndSetMaddr:
- EmitFetchParam();
- EmitStoreDstGpr(opCode);
- EmitStoreMethAddr();
- break;
- // Move result and use as Method Address, then fetch and send parameter.
- case AssignmentOperation.MoveAndSetMaddrThenFetchAndSend:
- _ilGen.Emit(OpCodes.Dup);
- EmitStoreDstGpr(opCode);
- EmitStoreMethAddr();
- EmitFetchParam();
- EmitSend();
- break;
- // Move result and use as Method Address, then send bits 17:12 of result.
- case AssignmentOperation.MoveAndSetMaddrThenSendHigh:
- _ilGen.Emit(OpCodes.Dup);
- _ilGen.Emit(OpCodes.Dup);
- EmitStoreDstGpr(opCode);
- EmitStoreMethAddr();
- _ilGen.Emit(OpCodes.Ldc_I4, 12);
- _ilGen.Emit(OpCodes.Shr_Un);
- _ilGen.Emit(OpCodes.Ldc_I4, 0x3f);
- _ilGen.Emit(OpCodes.And);
- EmitSend();
- break;
- }
- }
- else
- {
- // Branch.
- bool onNotZero = ((opCode >> 4) & 1) != 0;
- EmitLoadGprA(opCode);
- Label lblSkip = _ilGen.DefineLabel();
- if (onNotZero)
- {
- _ilGen.Emit(OpCodes.Brfalse, lblSkip);
- }
- else
- {
- _ilGen.Emit(OpCodes.Brtrue, lblSkip);
- }
- bool noDelays = (opCode & 0x20) != 0;
- if (!noDelays)
- {
- Emit(code, offset + 1, labels);
- }
- int target = offset + (opCode >> 14);
- _ilGen.Emit(OpCodes.Br, labels[target]);
- _ilGen.MarkLabel(lblSkip);
- }
- }
- /// <summary>
- /// Emits IL for a Arithmetic and Logic Unit instruction.
- /// </summary>
- /// <param name="opCode">Instruction to be translated</param>
- /// <exception cref="InvalidOperationException">Throw when the instruction encoding is invalid</exception>
- private void EmitAluOp(int opCode)
- {
- AluOperation op = (AluOperation)(opCode & 7);
- switch (op)
- {
- case AluOperation.AluReg:
- EmitAluOp((AluRegOperation)((opCode >> 17) & 0x1f), opCode);
- break;
- case AluOperation.AddImmediate:
- EmitLoadGprA(opCode);
- EmitLoadImm(opCode);
- _ilGen.Emit(OpCodes.Add);
- break;
- case AluOperation.BitfieldReplace:
- case AluOperation.BitfieldExtractLslImm:
- case AluOperation.BitfieldExtractLslReg:
- int bfSrcBit = (opCode >> 17) & 0x1f;
- int bfSize = (opCode >> 22) & 0x1f;
- int bfDstBit = (opCode >> 27) & 0x1f;
- int bfMask = (1 << bfSize) - 1;
- switch (op)
- {
- case AluOperation.BitfieldReplace:
- EmitLoadGprB(opCode);
- _ilGen.Emit(OpCodes.Ldc_I4, bfSrcBit);
- _ilGen.Emit(OpCodes.Shr_Un);
- _ilGen.Emit(OpCodes.Ldc_I4, bfMask);
- _ilGen.Emit(OpCodes.And);
- _ilGen.Emit(OpCodes.Ldc_I4, bfDstBit);
- _ilGen.Emit(OpCodes.Shl);
- EmitLoadGprA(opCode);
- _ilGen.Emit(OpCodes.Ldc_I4, ~(bfMask << bfDstBit));
- _ilGen.Emit(OpCodes.And);
- _ilGen.Emit(OpCodes.Or);
- break;
- case AluOperation.BitfieldExtractLslImm:
- EmitLoadGprB(opCode);
- EmitLoadGprA(opCode);
- _ilGen.Emit(OpCodes.Shr_Un);
- _ilGen.Emit(OpCodes.Ldc_I4, bfMask);
- _ilGen.Emit(OpCodes.And);
- _ilGen.Emit(OpCodes.Ldc_I4, bfDstBit);
- _ilGen.Emit(OpCodes.Shl);
- break;
- case AluOperation.BitfieldExtractLslReg:
- EmitLoadGprB(opCode);
- _ilGen.Emit(OpCodes.Ldc_I4, bfSrcBit);
- _ilGen.Emit(OpCodes.Shr_Un);
- _ilGen.Emit(OpCodes.Ldc_I4, bfMask);
- _ilGen.Emit(OpCodes.And);
- EmitLoadGprA(opCode);
- _ilGen.Emit(OpCodes.Shl);
- break;
- }
- break;
- case AluOperation.ReadImmediate:
- _ilGen.Emit(OpCodes.Ldarg_1);
- EmitLoadGprA(opCode);
- EmitLoadImm(opCode);
- _ilGen.Emit(OpCodes.Add);
- _ilGen.Emit(OpCodes.Call, typeof(MacroJitContext).GetMethod(nameof(MacroJitContext.Read)));
- break;
- default:
- throw new InvalidOperationException($"Invalid operation \"{op}\" on instruction 0x{opCode:X8}.");
- }
- }
- /// <summary>
- /// Emits IL for a binary Arithmetic and Logic Unit instruction.
- /// </summary>
- /// <param name="aluOp">Arithmetic and Logic Unit instruction</param>
- /// <param name="opCode">Raw instruction</param>
- /// <exception cref="InvalidOperationException">Throw when the instruction encoding is invalid</exception>
- private void EmitAluOp(AluRegOperation aluOp, int opCode)
- {
- switch (aluOp)
- {
- case AluRegOperation.Add:
- EmitLoadGprA(opCode);
- _ilGen.Emit(OpCodes.Conv_U8);
- EmitLoadGprB(opCode);
- _ilGen.Emit(OpCodes.Conv_U8);
- _ilGen.Emit(OpCodes.Add);
- _ilGen.Emit(OpCodes.Dup);
- _ilGen.Emit(OpCodes.Ldc_I8, 0xffffffffL);
- _ilGen.Emit(OpCodes.Cgt_Un);
- _ilGen.Emit(OpCodes.Stloc, _carry);
- _ilGen.Emit(OpCodes.Conv_U4);
- break;
- case AluRegOperation.AddWithCarry:
- EmitLoadGprA(opCode);
- _ilGen.Emit(OpCodes.Conv_U8);
- EmitLoadGprB(opCode);
- _ilGen.Emit(OpCodes.Conv_U8);
- _ilGen.Emit(OpCodes.Ldloc_S, _carry);
- _ilGen.Emit(OpCodes.Conv_U8);
- _ilGen.Emit(OpCodes.Add);
- _ilGen.Emit(OpCodes.Add);
- _ilGen.Emit(OpCodes.Dup);
- _ilGen.Emit(OpCodes.Ldc_I8, 0xffffffffL);
- _ilGen.Emit(OpCodes.Cgt_Un);
- _ilGen.Emit(OpCodes.Stloc, _carry);
- _ilGen.Emit(OpCodes.Conv_U4);
- break;
- case AluRegOperation.Subtract:
- EmitLoadGprA(opCode);
- _ilGen.Emit(OpCodes.Conv_U8);
- EmitLoadGprB(opCode);
- _ilGen.Emit(OpCodes.Conv_U8);
- _ilGen.Emit(OpCodes.Sub);
- _ilGen.Emit(OpCodes.Dup);
- _ilGen.Emit(OpCodes.Ldc_I8, 0x100000000L);
- _ilGen.Emit(OpCodes.Clt_Un);
- _ilGen.Emit(OpCodes.Stloc, _carry);
- _ilGen.Emit(OpCodes.Conv_U4);
- break;
- case AluRegOperation.SubtractWithBorrow:
- EmitLoadGprA(opCode);
- _ilGen.Emit(OpCodes.Conv_U8);
- EmitLoadGprB(opCode);
- _ilGen.Emit(OpCodes.Conv_U8);
- _ilGen.Emit(OpCodes.Ldc_I4_1);
- _ilGen.Emit(OpCodes.Ldloc_S, _carry);
- _ilGen.Emit(OpCodes.Sub);
- _ilGen.Emit(OpCodes.Conv_U8);
- _ilGen.Emit(OpCodes.Sub);
- _ilGen.Emit(OpCodes.Sub);
- _ilGen.Emit(OpCodes.Dup);
- _ilGen.Emit(OpCodes.Ldc_I8, 0x100000000L);
- _ilGen.Emit(OpCodes.Clt_Un);
- _ilGen.Emit(OpCodes.Stloc, _carry);
- _ilGen.Emit(OpCodes.Conv_U4);
- break;
- case AluRegOperation.BitwiseExclusiveOr:
- EmitLoadGprA(opCode);
- EmitLoadGprB(opCode);
- _ilGen.Emit(OpCodes.Xor);
- break;
- case AluRegOperation.BitwiseOr:
- EmitLoadGprA(opCode);
- EmitLoadGprB(opCode);
- _ilGen.Emit(OpCodes.Or);
- break;
- case AluRegOperation.BitwiseAnd:
- EmitLoadGprA(opCode);
- EmitLoadGprB(opCode);
- _ilGen.Emit(OpCodes.And);
- break;
- case AluRegOperation.BitwiseAndNot:
- EmitLoadGprA(opCode);
- EmitLoadGprB(opCode);
- _ilGen.Emit(OpCodes.Not);
- _ilGen.Emit(OpCodes.And);
- break;
- case AluRegOperation.BitwiseNotAnd:
- EmitLoadGprA(opCode);
- EmitLoadGprB(opCode);
- _ilGen.Emit(OpCodes.And);
- _ilGen.Emit(OpCodes.Not);
- break;
- default:
- throw new InvalidOperationException($"Invalid operation \"{aluOp}\" on instruction 0x{opCode:X8}.");
- }
- }
- /// <summary>
- /// Loads a immediate value on the IL evaluation stack.
- /// </summary>
- /// <param name="opCode">Instruction from where the immediate should be extracted</param>
- private void EmitLoadImm(int opCode)
- {
- // Note: The immediate is signed, the sign-extension is intended here.
- _ilGen.Emit(OpCodes.Ldc_I4, opCode >> 14);
- }
- /// <summary>
- /// Loads a value from the General Purpose register specified as first operand on the IL evaluation stack.
- /// </summary>
- /// <param name="opCode">Instruction from where the register number should be extracted</param>
- private void EmitLoadGprA(int opCode)
- {
- EmitLoadGpr((opCode >> 11) & 7);
- }
- /// <summary>
- /// Loads a value from the General Purpose register specified as second operand on the IL evaluation stack.
- /// </summary>
- /// <param name="opCode">Instruction from where the register number should be extracted</param>
- private void EmitLoadGprB(int opCode)
- {
- EmitLoadGpr((opCode >> 14) & 7);
- }
- /// <summary>
- /// Loads a value a General Purpose register on the IL evaluation stack.
- /// </summary>
- /// <remarks>
- /// Register number 0 has a hardcoded value of 0.
- /// </remarks>
- /// <param name="index">Register number</param>
- private void EmitLoadGpr(int index)
- {
- if (index == 0)
- {
- _ilGen.Emit(OpCodes.Ldc_I4_0);
- }
- else
- {
- _ilGen.Emit(OpCodes.Ldloc_S, _gprs[index]);
- }
- }
- /// <summary>
- /// Emits a call to the method that fetches an argument from the arguments FIFO.
- /// The argument is pushed into the IL evaluation stack.
- /// </summary>
- private void EmitFetchParam()
- {
- _ilGen.Emit(OpCodes.Ldarg_0);
- _ilGen.Emit(OpCodes.Call, typeof(MacroJitContext).GetMethod(nameof(MacroJitContext.FetchParam)));
- }
- /// <summary>
- /// Stores the value on the top of the IL evaluation stack into a General Purpose register.
- /// </summary>
- /// <remarks>
- /// Register number 0 does not exist, reads are hardcoded to 0, and writes are simply discarded.
- /// </remarks>
- /// <param name="opCode">Instruction from where the register number should be extracted</param>
- private void EmitStoreDstGpr(int opCode)
- {
- int index = (opCode >> 8) & 7;
- if (index == 0)
- {
- _ilGen.Emit(OpCodes.Pop);
- }
- else
- {
- _ilGen.Emit(OpCodes.Stloc_S, _gprs[index]);
- }
- }
- /// <summary>
- /// Stores the value on the top of the IL evaluation stack as method address.
- /// This will be used on subsequent send calls as the destination method address.
- /// Additionally, the 6 bits starting at bit 12 will be used as increment value,
- /// added to the method address after each sent value.
- /// </summary>
- private void EmitStoreMethAddr()
- {
- _ilGen.Emit(OpCodes.Dup);
- _ilGen.Emit(OpCodes.Ldc_I4, 0xfff);
- _ilGen.Emit(OpCodes.And);
- _ilGen.Emit(OpCodes.Stloc_S, _methAddr);
- _ilGen.Emit(OpCodes.Ldc_I4, 12);
- _ilGen.Emit(OpCodes.Shr_Un);
- _ilGen.Emit(OpCodes.Ldc_I4, 0x3f);
- _ilGen.Emit(OpCodes.And);
- _ilGen.Emit(OpCodes.Stloc_S, _methIncr);
- }
- /// <summary>
- /// Sends the value on the top of the IL evaluation stack to the GPU,
- /// using the current method address.
- /// </summary>
- private void EmitSend()
- {
- _ilGen.Emit(OpCodes.Ldarg_1);
- _ilGen.Emit(OpCodes.Ldloc_S, _methAddr);
- _ilGen.Emit(OpCodes.Call, typeof(MacroJitContext).GetMethod(nameof(MacroJitContext.Send)));
- _ilGen.Emit(OpCodes.Ldloc_S, _methAddr);
- _ilGen.Emit(OpCodes.Ldloc_S, _methIncr);
- _ilGen.Emit(OpCodes.Add);
- _ilGen.Emit(OpCodes.Stloc_S, _methAddr);
- }
- }
- }
|