mirror of
https://github.com/ARMSX2/ARMSX2.git
synced 2026-08-24 16:50:16 -07:00
Eleven commented-out defines in iR5900-arm64.h, each selecting the interpreter for one opcode category (branch, jump, move, shift, ALU, arith-imm, mult/div, memory, COP0, FPU, COP2), consumed by twelve #ifdef/#else/#endif pairs across eleven files. Using one meant editing the header and rebuilding. pcsx2-eerunner --rec-fallback <groups> does the same bisect at runtime with no rebuild and no source edit, over a full VM boot, so it stays game-faithful. The transform keeps every #else body byte for byte - the diff is deletions only, no added or reindented lines. Also drops two comments that referenced the defines. recompiler_tests: 1513 passed, 2 skipped, 0 failed.
606 lines
16 KiB
C++
606 lines
16 KiB
C++
// SPDX-FileCopyrightText: 2026 yaps2 Dev Team
|
|
// SPDX-License-Identifier: GPL-3.0+
|
|
|
|
// ARM64 EE Multiply/Divide Instruction Codegen — memory-based
|
|
// MULT/DIV write to HI:LO registers, optionally Rd.
|
|
// ARM64 has native SMULL/UMULL and SDIV/UDIV.
|
|
// All operands via cpuRegs memory.
|
|
|
|
#include "arm64/iR5900-arm64.h"
|
|
|
|
namespace a64 = vixl::aarch64;
|
|
|
|
namespace R5900 {
|
|
namespace Dynarec {
|
|
namespace OpcodeImpl {
|
|
|
|
namespace Interp = R5900::Interpreter::OpcodeImpl;
|
|
|
|
|
|
// Fetch Rs/Rt lower 32 bits for the mul/div ops. Substitution-aware
|
|
// (EE-SRA 2 WS-C6): returns the pin / MODE_READ allocator reg directly (zero
|
|
// insns) or materializes into w1/w9. Every caller sits right after
|
|
// _eeFlushAllDirty (the post-flush coherence contract), and every consumer
|
|
// below is read-only on the sources — EXCEPT the DIVU remainder Msub, which
|
|
// therefore targets w10 rather than writing a source in place.
|
|
//
|
|
// ⚠️ Scratch choice: w9/w10 (non-allocatable), NOT w2/w3. A resident FCR31
|
|
// (ARM64TYPE_FPRC, GE-12) lives in the {x2-x7, x14, x15} pool ACROSS ops —
|
|
// _eeFlushAllDirty writes it back but keeps residency, so a raw w2/w3
|
|
// clobber here poisons the flag a later BC1x/CFC1/DIV-flag-RMW reads (same
|
|
// class as the fpuEmitGuardedAddSub SotC bug; pinned by
|
|
// EeRecFpu.CompareSurvivesInterposed{Mult,Div}).
|
|
static a64::Register loadRs32()
|
|
{
|
|
if (GPR_IS_CONST1(_Rs_))
|
|
{
|
|
armAsm->Mov(a64::w1, g_cpuConstRegs[_Rs_].UL[0]);
|
|
return a64::w1;
|
|
}
|
|
return _eeGetGPRSourceReg(a64::w1, _Rs_);
|
|
}
|
|
|
|
static a64::Register loadRt32()
|
|
{
|
|
if (GPR_IS_CONST1(_Rt_))
|
|
{
|
|
armAsm->Mov(a64::w9, g_cpuConstRegs[_Rt_].UL[0]);
|
|
return a64::w9;
|
|
}
|
|
return _eeGetGPRSourceReg(a64::w9, _Rt_);
|
|
}
|
|
|
|
// Write LO and HI from 64-bit result in x0 (clobbers x0)
|
|
// lo = lower 32, hi = upper 32, both sign-extended to 64 bits
|
|
static void recWritebackHILO(bool upper)
|
|
{
|
|
armAsm->Sxtw(RXSCRATCH, a64::w0);
|
|
armAsm->Str(RXSCRATCH, armCpuRegMem(upper ? &cpuRegs.LO.UD[1] : &cpuRegs.LO.UD[0]));
|
|
|
|
// Asr(x0, #32) already yields the sign-extended upper half — store it directly (GE-01).
|
|
armAsm->Asr(a64::x0, a64::x0, 32);
|
|
armAsm->Str(a64::x0, armCpuRegMem(upper ? &cpuRegs.HI.UD[1] : &cpuRegs.HI.UD[0]));
|
|
}
|
|
|
|
// Write Rd from the sign-extended LO value recWritebackHILO leaves in
|
|
// RXSCRATCH (GE-09). Contract: every caller invokes this IMMEDIATELY after
|
|
// recWritebackHILO — the old Ldr from LO.UD[half] was a store→load
|
|
// round-trip on the value computed one instruction earlier. Same helper
|
|
// serves both pipelines (the RXSCRATCH value is the rd value either way).
|
|
// _deleteEEreg's NEON writeback emits only Str q/pin Movs — x8 untouched.
|
|
static void recWritebackRd()
|
|
{
|
|
if (!_Rd_) return;
|
|
|
|
_deleteEEreg(_Rd_, 0);
|
|
GPR_DEL_CONST(_Rd_);
|
|
_eeStoreGPRDestReg(_Rd_, RXSCRATCH);
|
|
}
|
|
|
|
//// MULT — signed 32-bit multiply, result in HI:LO, optionally Rd
|
|
void recMULT()
|
|
{
|
|
if (GPR_IS_CONST2(_Rs_, _Rt_))
|
|
{
|
|
s64 result = (s64)(s32)g_cpuConstRegs[_Rs_].UL[0] * (s64)(s32)g_cpuConstRegs[_Rt_].UL[0];
|
|
|
|
armAsm->Mov(RXSCRATCH, (s64)(s32)(u32)result);
|
|
armAsm->Str(RXSCRATCH, armCpuRegMem(&cpuRegs.LO.UD[0]));
|
|
|
|
armAsm->Mov(RXSCRATCH, (s64)(s32)(u32)(result >> 32));
|
|
armAsm->Str(RXSCRATCH, armCpuRegMem(&cpuRegs.HI.UD[0]));
|
|
|
|
if (_Rd_)
|
|
{
|
|
_deleteEEreg(_Rd_, 0);
|
|
g_cpuConstRegs[_Rd_].SD[0] = (s32)(u32)result;
|
|
GPR_SET_CONST(_Rd_);
|
|
}
|
|
return;
|
|
}
|
|
|
|
_eeFlushAllDirty();
|
|
const a64::Register rs32 = loadRs32();
|
|
const a64::Register rt32 = loadRt32();
|
|
|
|
armAsm->Smull(a64::x0, rs32, rt32);
|
|
|
|
recWritebackHILO(false);
|
|
recWritebackRd();
|
|
}
|
|
|
|
//// MULTU — unsigned 32-bit multiply
|
|
void recMULTU()
|
|
{
|
|
if (GPR_IS_CONST2(_Rs_, _Rt_))
|
|
{
|
|
u64 result = (u64)g_cpuConstRegs[_Rs_].UL[0] * (u64)g_cpuConstRegs[_Rt_].UL[0];
|
|
|
|
armAsm->Mov(RXSCRATCH, (s64)(s32)(u32)result);
|
|
armAsm->Str(RXSCRATCH, armCpuRegMem(&cpuRegs.LO.UD[0]));
|
|
|
|
armAsm->Mov(RXSCRATCH, (s64)(s32)(u32)(result >> 32));
|
|
armAsm->Str(RXSCRATCH, armCpuRegMem(&cpuRegs.HI.UD[0]));
|
|
|
|
if (_Rd_)
|
|
{
|
|
_deleteEEreg(_Rd_, 0);
|
|
g_cpuConstRegs[_Rd_].SD[0] = (s32)(u32)result;
|
|
GPR_SET_CONST(_Rd_);
|
|
}
|
|
return;
|
|
}
|
|
|
|
_eeFlushAllDirty();
|
|
const a64::Register rs32 = loadRs32();
|
|
const a64::Register rt32 = loadRt32();
|
|
|
|
armAsm->Umull(a64::x0, rs32, rt32);
|
|
|
|
recWritebackHILO(false);
|
|
recWritebackRd();
|
|
}
|
|
|
|
//// DIV — signed 32-bit divide. LO = quotient, HI = remainder.
|
|
// PS2 div-by-zero: LO = (rs >= 0 ? -1 : 1), HI = rs (sign-extended into 64-bit
|
|
// HI/LO). Matches the interpreter and the PS2 hardware spec.
|
|
void recDIV()
|
|
{
|
|
if (GPR_IS_CONST2(_Rs_, _Rt_))
|
|
{
|
|
s32 rs = g_cpuConstRegs[_Rs_].SL[0];
|
|
s32 rt = g_cpuConstRegs[_Rt_].SL[0];
|
|
|
|
s32 lo, hi;
|
|
if (rt == 0)
|
|
{
|
|
lo = (rs >= 0) ? -1 : 1;
|
|
hi = rs;
|
|
}
|
|
else if (rs == (s32)0x80000000 && rt == -1)
|
|
{
|
|
lo = (s32)0x80000000;
|
|
hi = 0;
|
|
}
|
|
else
|
|
{
|
|
lo = rs / rt;
|
|
hi = rs % rt;
|
|
}
|
|
|
|
armAsm->Mov(RXSCRATCH, (s64)lo);
|
|
armAsm->Str(RXSCRATCH, armCpuRegMem(&cpuRegs.LO.UD[0]));
|
|
|
|
armAsm->Mov(RXSCRATCH, (s64)hi);
|
|
armAsm->Str(RXSCRATCH, armCpuRegMem(&cpuRegs.HI.UD[0]));
|
|
return;
|
|
}
|
|
|
|
_eeFlushAllDirty();
|
|
const a64::Register rs32 = loadRs32();
|
|
const a64::Register rt32 = loadRt32();
|
|
|
|
// Branch on rt == 0 → div-by-zero handler.
|
|
a64::Label divByZero;
|
|
a64::Label done;
|
|
armAsm->Cbz(rt32, &divByZero);
|
|
|
|
// Normal path: SDIV w0 = rs/rt; MSUB w10 = rs - w0*rt (remainder).
|
|
armAsm->Sdiv(a64::w0, rs32, rt32);
|
|
armAsm->Msub(a64::w10, a64::w0, rt32, rs32);
|
|
armAsm->B(&done);
|
|
|
|
// Div-by-zero: w0 = (rs >= 0 ? -1 : 1), w10 = rs.
|
|
// Cneg w0, w0, lt: if rs < 0, w0 = -(-1) = 1; else w0 = -1.
|
|
armAsm->Bind(&divByZero);
|
|
armAsm->Mov(a64::w0, -1);
|
|
armAsm->Cmp(rs32, 0);
|
|
armAsm->Cneg(a64::w0, a64::w0, a64::lt);
|
|
armAsm->Mov(a64::w10, rs32); // HI = rs
|
|
|
|
armAsm->Bind(&done);
|
|
|
|
// Store LO = sign_extend(quotient or -1/1)
|
|
armAsm->Sxtw(RXSCRATCH, a64::w0);
|
|
armAsm->Str(RXSCRATCH, armCpuRegMem(&cpuRegs.LO.UD[0]));
|
|
|
|
// Store HI = sign_extend(remainder or rs)
|
|
armAsm->Sxtw(RXSCRATCH, a64::w10);
|
|
armAsm->Str(RXSCRATCH, armCpuRegMem(&cpuRegs.HI.UD[0]));
|
|
}
|
|
|
|
//// DIVU — unsigned 32-bit divide. PS2 div-by-zero: LO = -1 (0xffffffff
|
|
//// sign-extended), HI = rs.
|
|
void recDIVU()
|
|
{
|
|
if (GPR_IS_CONST2(_Rs_, _Rt_))
|
|
{
|
|
u32 rs = g_cpuConstRegs[_Rs_].UL[0];
|
|
u32 rt = g_cpuConstRegs[_Rt_].UL[0];
|
|
|
|
s32 lo, hi;
|
|
if (rt == 0)
|
|
{
|
|
lo = -1;
|
|
hi = (s32)rs;
|
|
}
|
|
else
|
|
{
|
|
lo = (s32)(rs / rt);
|
|
hi = (s32)(rs % rt);
|
|
}
|
|
|
|
armAsm->Mov(RXSCRATCH, (s64)lo);
|
|
armAsm->Str(RXSCRATCH, armCpuRegMem(&cpuRegs.LO.UD[0]));
|
|
|
|
armAsm->Mov(RXSCRATCH, (s64)hi);
|
|
armAsm->Str(RXSCRATCH, armCpuRegMem(&cpuRegs.HI.UD[0]));
|
|
return;
|
|
}
|
|
|
|
_eeFlushAllDirty();
|
|
const a64::Register rs32 = loadRs32();
|
|
const a64::Register rt32 = loadRt32();
|
|
|
|
a64::Label divByZero;
|
|
a64::Label done;
|
|
armAsm->Cbz(rt32, &divByZero);
|
|
|
|
// Normal path: UDIV w0; MSUB remainder into w10 — NOT in place over the Rs
|
|
// source, which may be a pin (WS-C6 pin-safety; the old in-place form
|
|
// predates operand substitution).
|
|
armAsm->Udiv(a64::w0, rs32, rt32);
|
|
armAsm->Msub(a64::w10, a64::w0, rt32, rs32);
|
|
armAsm->B(&done);
|
|
|
|
// Div-by-zero: w0 = -1; HI = rs.
|
|
armAsm->Bind(&divByZero);
|
|
armAsm->Mov(a64::w0, -1);
|
|
armAsm->Mov(a64::w10, rs32);
|
|
|
|
armAsm->Bind(&done);
|
|
|
|
// Store LO = sign_extend(quotient or -1)
|
|
armAsm->Sxtw(RXSCRATCH, a64::w0);
|
|
armAsm->Str(RXSCRATCH, armCpuRegMem(&cpuRegs.LO.UD[0]));
|
|
|
|
// Store HI = sign_extend(remainder or rs)
|
|
armAsm->Sxtw(RXSCRATCH, a64::w10);
|
|
armAsm->Str(RXSCRATCH, armCpuRegMem(&cpuRegs.HI.UD[0]));
|
|
}
|
|
|
|
|
|
// Pipeline-1 Rd writeback — identical to recWritebackRd since GE-09 (the
|
|
// rd value rides RXSCRATCH out of recWritebackHILO for either pipeline).
|
|
static void recWritebackRd1()
|
|
{
|
|
recWritebackRd();
|
|
}
|
|
|
|
//// MULT1 — signed 32-bit multiply, pipeline 1 (HI1:LO1)
|
|
void recMULT1()
|
|
{
|
|
if (GPR_IS_CONST2(_Rs_, _Rt_))
|
|
{
|
|
s64 result = (s64)(s32)g_cpuConstRegs[_Rs_].UL[0] * (s64)(s32)g_cpuConstRegs[_Rt_].UL[0];
|
|
|
|
armAsm->Mov(RXSCRATCH, (s64)(s32)(u32)result);
|
|
armAsm->Str(RXSCRATCH, armCpuRegMem(&cpuRegs.LO.UD[1]));
|
|
|
|
armAsm->Mov(RXSCRATCH, (s64)(s32)(u32)(result >> 32));
|
|
armAsm->Str(RXSCRATCH, armCpuRegMem(&cpuRegs.HI.UD[1]));
|
|
|
|
if (_Rd_)
|
|
{
|
|
_deleteEEreg(_Rd_, 0);
|
|
g_cpuConstRegs[_Rd_].SD[0] = (s32)(u32)result;
|
|
GPR_SET_CONST(_Rd_);
|
|
}
|
|
return;
|
|
}
|
|
|
|
_eeFlushAllDirty();
|
|
const a64::Register rs32 = loadRs32();
|
|
const a64::Register rt32 = loadRt32();
|
|
|
|
armAsm->Smull(a64::x0, rs32, rt32);
|
|
|
|
recWritebackHILO(true);
|
|
recWritebackRd1();
|
|
}
|
|
|
|
//// MULTU1 — unsigned 32-bit multiply, pipeline 1
|
|
void recMULTU1()
|
|
{
|
|
if (GPR_IS_CONST2(_Rs_, _Rt_))
|
|
{
|
|
u64 result = (u64)g_cpuConstRegs[_Rs_].UL[0] * (u64)g_cpuConstRegs[_Rt_].UL[0];
|
|
|
|
armAsm->Mov(RXSCRATCH, (s64)(s32)(u32)result);
|
|
armAsm->Str(RXSCRATCH, armCpuRegMem(&cpuRegs.LO.UD[1]));
|
|
|
|
armAsm->Mov(RXSCRATCH, (s64)(s32)(u32)(result >> 32));
|
|
armAsm->Str(RXSCRATCH, armCpuRegMem(&cpuRegs.HI.UD[1]));
|
|
|
|
if (_Rd_)
|
|
{
|
|
_deleteEEreg(_Rd_, 0);
|
|
g_cpuConstRegs[_Rd_].SD[0] = (s32)(u32)result;
|
|
GPR_SET_CONST(_Rd_);
|
|
}
|
|
return;
|
|
}
|
|
|
|
_eeFlushAllDirty();
|
|
const a64::Register rs32 = loadRs32();
|
|
const a64::Register rt32 = loadRt32();
|
|
|
|
armAsm->Umull(a64::x0, rs32, rt32);
|
|
|
|
recWritebackHILO(true);
|
|
recWritebackRd1();
|
|
}
|
|
|
|
//// DIV1 — signed 32-bit divide, pipeline 1. Same div-by-zero spec as DIV.
|
|
void recDIV1()
|
|
{
|
|
if (GPR_IS_CONST2(_Rs_, _Rt_))
|
|
{
|
|
s32 rs = g_cpuConstRegs[_Rs_].SL[0];
|
|
s32 rt = g_cpuConstRegs[_Rt_].SL[0];
|
|
|
|
s32 lo, hi;
|
|
if (rt == 0)
|
|
{
|
|
lo = (rs >= 0) ? -1 : 1;
|
|
hi = rs;
|
|
}
|
|
else if (rs == (s32)0x80000000 && rt == -1)
|
|
{
|
|
lo = (s32)0x80000000;
|
|
hi = 0;
|
|
}
|
|
else
|
|
{
|
|
lo = rs / rt;
|
|
hi = rs % rt;
|
|
}
|
|
|
|
armAsm->Mov(RXSCRATCH, (s64)lo);
|
|
armAsm->Str(RXSCRATCH, armCpuRegMem(&cpuRegs.LO.UD[1]));
|
|
|
|
armAsm->Mov(RXSCRATCH, (s64)hi);
|
|
armAsm->Str(RXSCRATCH, armCpuRegMem(&cpuRegs.HI.UD[1]));
|
|
return;
|
|
}
|
|
|
|
_eeFlushAllDirty();
|
|
const a64::Register rs32 = loadRs32();
|
|
const a64::Register rt32 = loadRt32();
|
|
|
|
a64::Label divByZero;
|
|
a64::Label done;
|
|
armAsm->Cbz(rt32, &divByZero);
|
|
|
|
armAsm->Sdiv(a64::w0, rs32, rt32);
|
|
armAsm->Msub(a64::w10, a64::w0, rt32, rs32);
|
|
armAsm->B(&done);
|
|
|
|
// Div-by-zero: w0 = (rs >= 0 ? -1 : 1), w10 = rs. See recDIV for Cneg rationale.
|
|
armAsm->Bind(&divByZero);
|
|
armAsm->Mov(a64::w0, -1);
|
|
armAsm->Cmp(rs32, 0);
|
|
armAsm->Cneg(a64::w0, a64::w0, a64::lt);
|
|
armAsm->Mov(a64::w10, rs32); // HI = rs
|
|
|
|
armAsm->Bind(&done);
|
|
|
|
armAsm->Sxtw(RXSCRATCH, a64::w0);
|
|
armAsm->Str(RXSCRATCH, armCpuRegMem(&cpuRegs.LO.UD[1]));
|
|
|
|
armAsm->Sxtw(RXSCRATCH, a64::w10);
|
|
armAsm->Str(RXSCRATCH, armCpuRegMem(&cpuRegs.HI.UD[1]));
|
|
}
|
|
|
|
//// DIVU1 — unsigned 32-bit divide, pipeline 1. Same div-by-zero spec as DIVU.
|
|
void recDIVU1()
|
|
{
|
|
if (GPR_IS_CONST2(_Rs_, _Rt_))
|
|
{
|
|
u32 rs = g_cpuConstRegs[_Rs_].UL[0];
|
|
u32 rt = g_cpuConstRegs[_Rt_].UL[0];
|
|
|
|
s32 lo, hi;
|
|
if (rt == 0)
|
|
{
|
|
lo = -1;
|
|
hi = (s32)rs;
|
|
}
|
|
else
|
|
{
|
|
lo = (s32)(rs / rt);
|
|
hi = (s32)(rs % rt);
|
|
}
|
|
|
|
armAsm->Mov(RXSCRATCH, (s64)lo);
|
|
armAsm->Str(RXSCRATCH, armCpuRegMem(&cpuRegs.LO.UD[1]));
|
|
|
|
armAsm->Mov(RXSCRATCH, (s64)hi);
|
|
armAsm->Str(RXSCRATCH, armCpuRegMem(&cpuRegs.HI.UD[1]));
|
|
return;
|
|
}
|
|
|
|
_eeFlushAllDirty();
|
|
const a64::Register rs32 = loadRs32();
|
|
const a64::Register rt32 = loadRt32();
|
|
|
|
a64::Label divByZero;
|
|
a64::Label done;
|
|
armAsm->Cbz(rt32, &divByZero);
|
|
|
|
// Remainder into w10, not in place — see recDIVU (WS-C6 pin-safety).
|
|
armAsm->Udiv(a64::w0, rs32, rt32);
|
|
armAsm->Msub(a64::w10, a64::w0, rt32, rs32);
|
|
armAsm->B(&done);
|
|
|
|
armAsm->Bind(&divByZero);
|
|
armAsm->Mov(a64::w0, -1);
|
|
armAsm->Mov(a64::w10, rs32);
|
|
|
|
armAsm->Bind(&done);
|
|
|
|
armAsm->Sxtw(RXSCRATCH, a64::w0);
|
|
armAsm->Str(RXSCRATCH, armCpuRegMem(&cpuRegs.LO.UD[1]));
|
|
|
|
armAsm->Sxtw(RXSCRATCH, a64::w10);
|
|
armAsm->Str(RXSCRATCH, armCpuRegMem(&cpuRegs.HI.UD[1]));
|
|
}
|
|
|
|
//// MADD — signed multiply-add: HI:LO += Rs * Rt, Rd = LO
|
|
void recMADD()
|
|
{
|
|
if (GPR_IS_CONST2(_Rs_, _Rt_))
|
|
{
|
|
s64 result = (s64)(s32)g_cpuConstRegs[_Rs_].UL[0] * (s64)(s32)g_cpuConstRegs[_Rt_].UL[0];
|
|
|
|
// Add to existing HI:LO — load, add, store
|
|
_eeFlushAllDirty();
|
|
armLoadEERegPtr(a64::w1, &cpuRegs.LO.UL[0]);
|
|
armLoadEERegPtr(a64::w9, &cpuRegs.HI.UL[0]);
|
|
armAsm->Orr(a64::x1, a64::x1, a64::Operand(a64::x9, a64::LSL, 32));
|
|
armAsm->Mov(RXSCRATCH, result);
|
|
armAsm->Add(a64::x0, a64::x1, RXSCRATCH);
|
|
|
|
recWritebackHILO(false);
|
|
recWritebackRd();
|
|
return;
|
|
}
|
|
|
|
_eeFlushAllDirty();
|
|
const a64::Register rs32 = loadRs32();
|
|
const a64::Register rt32 = loadRt32();
|
|
|
|
// x0 = Rs * Rt (signed 32x32→64)
|
|
armAsm->Smull(a64::x0, rs32, rt32);
|
|
|
|
// Load existing HI:LO into x1
|
|
armLoadEERegPtr(a64::w10, &cpuRegs.LO.UL[0]);
|
|
armLoadEERegPtr(RWSCRATCH, &cpuRegs.HI.UL[0]); // w8: reserved scratch (w4 is allocatable)
|
|
armAsm->Orr(a64::x10, a64::x10, a64::Operand(RXSCRATCH, a64::LSL, 32));
|
|
|
|
// Add
|
|
armAsm->Add(a64::x0, a64::x0, a64::x10);
|
|
|
|
recWritebackHILO(false);
|
|
recWritebackRd();
|
|
}
|
|
|
|
//// MADDU — unsigned multiply-add: HI:LO += Rs * Rt, Rd = LO
|
|
void recMADDU()
|
|
{
|
|
if (GPR_IS_CONST2(_Rs_, _Rt_))
|
|
{
|
|
u64 result = (u64)g_cpuConstRegs[_Rs_].UL[0] * (u64)g_cpuConstRegs[_Rt_].UL[0];
|
|
|
|
_eeFlushAllDirty();
|
|
armLoadEERegPtr(a64::w1, &cpuRegs.LO.UL[0]);
|
|
armLoadEERegPtr(a64::w9, &cpuRegs.HI.UL[0]);
|
|
armAsm->Orr(a64::x1, a64::x1, a64::Operand(a64::x9, a64::LSL, 32));
|
|
armAsm->Mov(RXSCRATCH, result);
|
|
armAsm->Add(a64::x0, a64::x1, RXSCRATCH);
|
|
|
|
recWritebackHILO(false);
|
|
recWritebackRd();
|
|
return;
|
|
}
|
|
|
|
_eeFlushAllDirty();
|
|
const a64::Register rs32 = loadRs32();
|
|
const a64::Register rt32 = loadRt32();
|
|
|
|
armAsm->Umull(a64::x0, rs32, rt32);
|
|
|
|
armLoadEERegPtr(a64::w10, &cpuRegs.LO.UL[0]);
|
|
armLoadEERegPtr(RWSCRATCH, &cpuRegs.HI.UL[0]); // w8: reserved scratch (w4 is allocatable)
|
|
armAsm->Orr(a64::x10, a64::x10, a64::Operand(RXSCRATCH, a64::LSL, 32));
|
|
|
|
armAsm->Add(a64::x0, a64::x0, a64::x10);
|
|
|
|
recWritebackHILO(false);
|
|
recWritebackRd();
|
|
}
|
|
|
|
//// MADD1 — signed multiply-add, pipeline 1: HI1:LO1 += Rs * Rt, Rd = LO1
|
|
void recMADD1()
|
|
{
|
|
if (GPR_IS_CONST2(_Rs_, _Rt_))
|
|
{
|
|
s64 result = (s64)(s32)g_cpuConstRegs[_Rs_].UL[0] * (s64)(s32)g_cpuConstRegs[_Rt_].UL[0];
|
|
|
|
_eeFlushAllDirty();
|
|
armLoadEERegPtr(a64::w1, &cpuRegs.LO.UL[2]);
|
|
armLoadEERegPtr(a64::w9, &cpuRegs.HI.UL[2]);
|
|
armAsm->Orr(a64::x1, a64::x1, a64::Operand(a64::x9, a64::LSL, 32));
|
|
armAsm->Mov(RXSCRATCH, result);
|
|
armAsm->Add(a64::x0, a64::x1, RXSCRATCH);
|
|
|
|
recWritebackHILO(true);
|
|
recWritebackRd1();
|
|
return;
|
|
}
|
|
|
|
_eeFlushAllDirty();
|
|
const a64::Register rs32 = loadRs32();
|
|
const a64::Register rt32 = loadRt32();
|
|
|
|
armAsm->Smull(a64::x0, rs32, rt32);
|
|
|
|
armLoadEERegPtr(a64::w10, &cpuRegs.LO.UL[2]); // LO1 = LO.UL[2] (upper 64 bits)
|
|
armLoadEERegPtr(RWSCRATCH, &cpuRegs.HI.UL[2]); // HI1 = HI.UL[2] (w8: reserved scratch — w4 is allocatable)
|
|
armAsm->Orr(a64::x10, a64::x10, a64::Operand(RXSCRATCH, a64::LSL, 32));
|
|
|
|
armAsm->Add(a64::x0, a64::x0, a64::x10);
|
|
|
|
recWritebackHILO(true);
|
|
recWritebackRd1();
|
|
}
|
|
|
|
//// MADDU1 — unsigned multiply-add, pipeline 1
|
|
void recMADDU1()
|
|
{
|
|
if (GPR_IS_CONST2(_Rs_, _Rt_))
|
|
{
|
|
u64 result = (u64)g_cpuConstRegs[_Rs_].UL[0] * (u64)g_cpuConstRegs[_Rt_].UL[0];
|
|
|
|
_eeFlushAllDirty();
|
|
armLoadEERegPtr(a64::w1, &cpuRegs.LO.UL[2]);
|
|
armLoadEERegPtr(a64::w9, &cpuRegs.HI.UL[2]);
|
|
armAsm->Orr(a64::x1, a64::x1, a64::Operand(a64::x9, a64::LSL, 32));
|
|
armAsm->Mov(RXSCRATCH, result);
|
|
armAsm->Add(a64::x0, a64::x1, RXSCRATCH);
|
|
|
|
recWritebackHILO(true);
|
|
recWritebackRd1();
|
|
return;
|
|
}
|
|
|
|
_eeFlushAllDirty();
|
|
const a64::Register rs32 = loadRs32();
|
|
const a64::Register rt32 = loadRt32();
|
|
|
|
armAsm->Umull(a64::x0, rs32, rt32);
|
|
|
|
armLoadEERegPtr(a64::w10, &cpuRegs.LO.UL[2]);
|
|
armLoadEERegPtr(RWSCRATCH, &cpuRegs.HI.UL[2]); // w8: reserved scratch (w4 is allocatable)
|
|
armAsm->Orr(a64::x10, a64::x10, a64::Operand(RXSCRATCH, a64::LSL, 32));
|
|
|
|
armAsm->Add(a64::x0, a64::x0, a64::x10);
|
|
|
|
recWritebackHILO(true);
|
|
recWritebackRd1();
|
|
}
|
|
|
|
} // namespace OpcodeImpl
|
|
} // namespace Dynarec
|
|
} // namespace R5900
|