using Ryujinx.Common.Logging; using Ryujinx.Graphics.Gpu.State; using System; using System.Collections.Generic; namespace Ryujinx.Graphics.Gpu { /// /// Macro code interpreter. /// class MacroInterpreter { private enum AssignmentOperation { IgnoreAndFetch = 0, Move = 1, MoveAndSetMaddr = 2, FetchAndSend = 3, MoveAndSend = 4, FetchAndSetMaddr = 5, MoveAndSetMaddrThenFetchAndSend = 6, MoveAndSetMaddrThenSendHigh = 7 } private enum AluOperation { AluReg = 0, AddImmediate = 1, BitfieldReplace = 2, BitfieldExtractLslImm = 3, BitfieldExtractLslReg = 4, ReadImmediate = 5 } private enum AluRegOperation { Add = 0, AddWithCarry = 1, Subtract = 2, SubtractWithBorrow = 3, BitwiseExclusiveOr = 8, BitwiseOr = 9, BitwiseAnd = 10, BitwiseAndNot = 11, BitwiseNotAnd = 12 } public Queue Fifo { get; } private int[] _gprs; private int _methAddr; private int _methIncr; private bool _carry; private int _opCode; private int _pipeOp; private bool _ignoreExitFlag; private int _pc; private ShadowRamControl _shadowCtrl; /// /// Creates a new instance of the macro code interpreter. /// public MacroInterpreter() { Fifo = new Queue(); _gprs = new int[8]; } /// /// Executes a macro program until it exits. /// /// Code of the program to execute /// Start position to execute /// Optional argument passed to the program, 0 if not used /// Shadow RAM control register value /// Current GPU state public void Execute(int[] mme, int position, int param, ShadowRamControl shadowCtrl, GpuState state) { Reset(); _gprs[1] = param; _pc = position; _shadowCtrl = shadowCtrl; FetchOpCode(mme); while (Step(mme, state)); // Due to the delay slot, we still need to execute // one more instruction before we actually exit. Step(mme, state); } /// /// Resets the internal interpreter state. /// Call each time you run a new program. /// private void Reset() { for (int index = 0; index < _gprs.Length; index++) { _gprs[index] = 0; } _methAddr = 0; _methIncr = 0; _carry = false; } /// /// Executes a single instruction of the program. /// /// Program code to execute /// Current GPU state /// True to continue execution, false if the program exited private bool Step(int[] mme, GpuState state) { int baseAddr = _pc - 1; FetchOpCode(mme); if ((_opCode & 7) < 7) { // Operation produces a value. AssignmentOperation asgOp = (AssignmentOperation)((_opCode >> 4) & 7); int result = GetAluResult(state); switch (asgOp) { // Fetch parameter and ignore result. case AssignmentOperation.IgnoreAndFetch: { SetDstGpr(FetchParam()); break; } // Move result. case AssignmentOperation.Move: { SetDstGpr(result); break; } // Move result and use as Method Address. case AssignmentOperation.MoveAndSetMaddr: { SetDstGpr(result); SetMethAddr(result); break; } // Fetch parameter and send result. case AssignmentOperation.FetchAndSend: { SetDstGpr(FetchParam()); Send(state, result); break; } // Move and send result. case AssignmentOperation.MoveAndSend: { SetDstGpr(result); Send(state, result); break; } // Fetch parameter and use result as Method Address. case AssignmentOperation.FetchAndSetMaddr: { SetDstGpr(FetchParam()); SetMethAddr(result); break; } // Move result and use as Method Address, then fetch and send parameter. case AssignmentOperation.MoveAndSetMaddrThenFetchAndSend: { SetDstGpr(result); SetMethAddr(result); Send(state, FetchParam()); break; } // Move result and use as Method Address, then send bits 17:12 of result. case AssignmentOperation.MoveAndSetMaddrThenSendHigh: { SetDstGpr(result); SetMethAddr(result); Send(state, (result >> 12) & 0x3f); break; } } } else { // Branch. bool onNotZero = ((_opCode >> 4) & 1) != 0; bool taken = onNotZero ? GetGprA() != 0 : GetGprA() == 0; if (taken) { _pc = baseAddr + GetImm(); bool noDelays = (_opCode & 0x20) != 0; if (noDelays) { FetchOpCode(mme); } else { // The delay slot instruction exit flag should be ignored. _ignoreExitFlag = true; } return true; } } bool exit = (_opCode & 0x80) != 0 && !_ignoreExitFlag; _ignoreExitFlag = false; return !exit; } /// /// Fetches a single operation code from the program code. /// /// Program code private void FetchOpCode(int[] mme) { _opCode = _pipeOp; _pipeOp = mme[_pc++]; } /// /// Gets the result of the current Arithmetic and Logic unit operation. /// /// Current GPU state /// Operation result private int GetAluResult(GpuState state) { AluOperation op = (AluOperation)(_opCode & 7); switch (op) { case AluOperation.AluReg: { AluRegOperation aluOp = (AluRegOperation)((_opCode >> 17) & 0x1f); return GetAluResult(aluOp, GetGprA(), GetGprB()); } case AluOperation.AddImmediate: { return GetGprA() + GetImm(); } 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; int dst = GetGprA(); int src = GetGprB(); switch (op) { case AluOperation.BitfieldReplace: { src = (int)((uint)src >> bfSrcBit) & bfMask; dst &= ~(bfMask << bfDstBit); dst |= src << bfDstBit; return dst; } case AluOperation.BitfieldExtractLslImm: { src = (int)((uint)src >> dst) & bfMask; return src << bfDstBit; } case AluOperation.BitfieldExtractLslReg: { src = (int)((uint)src >> bfSrcBit) & bfMask; return src << dst; } } break; } case AluOperation.ReadImmediate: { return Read(state, GetGprA() + GetImm()); } } throw new ArgumentException(nameof(_opCode)); } /// /// Gets the result of an Arithmetic and Logic operation using registers. /// /// Arithmetic and Logic unit operation with registers /// First operand value /// Second operand value /// Operation result private int GetAluResult(AluRegOperation aluOp, int a, int b) { switch (aluOp) { case AluRegOperation.Add: { ulong result = (ulong)a + (ulong)b; _carry = result > 0xffffffff; return (int)result; } case AluRegOperation.AddWithCarry: { ulong result = (ulong)a + (ulong)b + (_carry ? 1UL : 0UL); _carry = result > 0xffffffff; return (int)result; } case AluRegOperation.Subtract: { ulong result = (ulong)a - (ulong)b; _carry = result < 0x100000000; return (int)result; } case AluRegOperation.SubtractWithBorrow: { ulong result = (ulong)a - (ulong)b - (_carry ? 0UL : 1UL); _carry = result < 0x100000000; return (int)result; } case AluRegOperation.BitwiseExclusiveOr: return a ^ b; case AluRegOperation.BitwiseOr: return a | b; case AluRegOperation.BitwiseAnd: return a & b; case AluRegOperation.BitwiseAndNot: return a & ~b; case AluRegOperation.BitwiseNotAnd: return ~(a & b); } throw new ArgumentOutOfRangeException(nameof(aluOp)); } /// /// Extracts a 32-bits signed integer constant from the current operation code. /// /// The 32-bits immediate value encoded at the current operation code private int GetImm() { // Note: The immediate is signed, the sign-extension is intended here. return _opCode >> 14; } /// /// Sets the current method address, for method calls. /// /// Packed address and increment value private void SetMethAddr(int value) { _methAddr = (value >> 0) & 0xfff; _methIncr = (value >> 12) & 0x3f; } /// /// Sets the destination register value. /// /// Value to set (usually the operation result) private void SetDstGpr(int value) { _gprs[(_opCode >> 8) & 7] = value; } /// /// Gets first operand value from the respective register. /// /// Operand value private int GetGprA() { return GetGprValue((_opCode >> 11) & 7); } /// /// Gets second operand value from the respective register. /// /// Operand value private int GetGprB() { return GetGprValue((_opCode >> 14) & 7); } /// /// Gets the value from a register, or 0 if the R0 register is specified. /// /// Index of the register /// Register value private int GetGprValue(int index) { return index != 0 ? _gprs[index] : 0; } /// /// Fetches a call argument from the call argument FIFO. /// /// The call argument, or 0 if the FIFO is empty private int FetchParam() { if (!Fifo.TryDequeue(out int value)) { Logger.PrintWarning(LogClass.Gpu, "Macro attempted to fetch an inexistent argument."); return 0; } return value; } /// /// Reads data from a GPU register. /// /// Current GPU state /// Register offset to read /// GPU register value private int Read(GpuState state, int reg) { return state.Read(reg); } /// /// Performs a GPU method call. /// /// Current GPU state /// Call argument private void Send(GpuState state, int value) { MethodParams meth = new MethodParams(_methAddr, value); state.CallMethod(meth, _shadowCtrl); _methAddr += _methIncr; } } }