Files
ppsspp/Core/MIPS/MIPS.h
T
Henrik RydgårdandClaude Opus 5 bd505b7f1c Interpreter: implement the FPU divide-by-zero exception
div.s now maintains fcr31's Cause.Z and (when the trap is masked) sticky
Flag.Z bits, in the standard MIPS bit positions. Only a finite non-zero
dividend counts, so 0/0, inf/0 and NaN operands are excluded per IEEE 754.

When the guest has the trap unmasked, the new Core_FPUException() reports it
with the usual module suffix and MIPS call stack, and fd is left unwritten as
hardware would. That's gated behind a new developer setting, off by default:
PSP threads start with fcr31 = 0x00000e00, i.e. three of the traps already
enabled, and games divide by zero without meaning anything by it. The fcr31
bits are updated either way, so what the game reads back doesn't depend on
the setting.

Interpreter only - the JITs are unchanged, and none of this is reachable
under them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R9fKXvYBrnqtp1QQGaGWVv
2026-08-29 23:40:48 +02:00

316 lines
8.6 KiB
C++

// Copyright (c) 2012- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#pragma once
#include "ppsspp_config.h"
#include <cstddef>
#include <vector>
#include "Common/CommonTypes.h"
#include "Core/Opcode.h"
class PointerWrap;
typedef Memory::Opcode MIPSOpcode;
// Unlike on the PPC, opcode 0 is not unused and thus we have to choose another fake
// opcode to represent JIT blocks and other emu hacks.
// I've chosen 0x68000000.
#define MIPS_EMUHACK_OPCODE 0x68000000
#define MIPS_EMUHACK_MASK 0xFC000000
#define MIPS_JITBLOCK_MASK 0xFF000000
#define MIPS_EMUHACK_VALUE_MASK 0x00FFFFFF
// There are 2 bits available for sub-opcodes, 0x03000000.
#define EMUOP_RUNBLOCK 0 // Runs a JIT block
#define EMUOP_RETKERNEL 1 // Returns to the simulated PSP kernel from a thread
#define EMUOP_CALL_REPLACEMENT 2
#define MIPS_IS_EMUHACK(op) (((op) & 0xFC000000) == MIPS_EMUHACK_OPCODE) // masks away the subop
#define MIPS_IS_RUNBLOCK(op) (((op) & 0xFF000000) == MIPS_EMUHACK_OPCODE) // masks away the subop
#define MIPS_IS_REPLACEMENT(op) (((op) & 0xFF000000) == (MIPS_EMUHACK_OPCODE | (EMUOP_CALL_REPLACEMENT << 24))) // masks away the subop
#define MIPS_EMUHACK_CALL_REPLACEMENT (MIPS_EMUHACK_OPCODE | (EMUOP_CALL_REPLACEMENT << 24))
enum MIPSGPReg {
MIPS_REG_ZERO=0,
MIPS_REG_COMPILER_SCRATCH=1,
MIPS_REG_V0=2,
MIPS_REG_V1=3,
MIPS_REG_A0=4,
MIPS_REG_A1=5,
MIPS_REG_A2=6,
MIPS_REG_A3=7,
MIPS_REG_A4=8,
MIPS_REG_A5=9,
MIPS_REG_T0=8, //alternate names for A4/A5
MIPS_REG_T1=9,
MIPS_REG_T2=10,
MIPS_REG_T3=11,
MIPS_REG_T4=12,
MIPS_REG_T5=13,
MIPS_REG_T6=14,
MIPS_REG_T7=15,
MIPS_REG_S0=16,
MIPS_REG_S1=17,
MIPS_REG_S2=18,
MIPS_REG_S3=19,
MIPS_REG_S4=20,
MIPS_REG_S5=21,
MIPS_REG_S6=22,
MIPS_REG_S7=23,
MIPS_REG_T8=24,
MIPS_REG_T9=25,
MIPS_REG_K0=26,
MIPS_REG_K1=27,
MIPS_REG_GP=28,
MIPS_REG_SP=29,
MIPS_REG_FP=30,
MIPS_REG_RA=31,
// Not real regs, just for convenience/jit mapping.
// NOTE: These are not the same as the offsets the IR has to use!
MIPS_REG_HI = 32,
MIPS_REG_LO = 33,
MIPS_REG_FPCOND = 34,
MIPS_REG_VFPUCC = 35,
MIPS_REG_INVALID=-1,
};
enum {
VFPU_CTRL_SPREFIX,
VFPU_CTRL_TPREFIX,
VFPU_CTRL_DPREFIX,
VFPU_CTRL_CC,
VFPU_CTRL_INF4,
VFPU_CTRL_RSV5,
VFPU_CTRL_RSV6,
VFPU_CTRL_REV,
VFPU_CTRL_RCX0,
VFPU_CTRL_RCX1,
VFPU_CTRL_RCX2,
VFPU_CTRL_RCX3,
VFPU_CTRL_RCX4,
VFPU_CTRL_RCX5,
VFPU_CTRL_RCX6,
VFPU_CTRL_RCX7,
VFPU_CTRL_MAX,
//unknown....
};
enum VCondition
{
VC_FL,
VC_EQ,
VC_LT,
VC_LE,
VC_TR,
VC_NE,
VC_GE,
VC_GT,
VC_EZ,
VC_EN,
VC_EI,
VC_ES,
VC_NZ,
VC_NN,
VC_NI,
VC_NS
};
// fcr31, the FPU control/status register, in the standard MIPS layout: bits 0-1 are the
// rounding mode, 2-6 the sticky Flags, 7-11 the trap Enables, 12-16 the per-instruction Cause,
// 23 the condition flag (mirrored in fpcond) and 24 flush-to-zero. Each of the three 5-bit
// fields holds the IEEE exceptions in the same order (inexact, underflow, overflow, divide by
// zero, invalid operation); only divide by zero is implemented so far, and only in the interpreter.
enum {
FCR31_FLAG_DIV_BY_ZERO = 1 << 5,
FCR31_ENABLE_DIV_BY_ZERO = 1 << 10,
FCR31_CAUSE_DIV_BY_ZERO = 1 << 15,
};
// In memory, we order the VFPU registers differently.
// Games use columns a whole lot more than rows, and it would thus be good if columns
// were contiguous in memory. Also, matrices aren't but should be.
extern u8 voffset[128];
extern u8 fromvoffset[128];
enum class CPUCore;
#if PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)
// Note that CTXREG is offset to point at the first floating point register, intentionally. This is so that a byte offset
// can reach both GPR and FPR regs.
#define MIPSSTATE_VAR(x) MDisp(X64JitConstants::CTXREG, \
(int)(offsetof(MIPSState, x) - offsetof(MIPSState, f[0])))
// Workaround for compilers that don't like dynamic indexing in offsetof
#define MIPSSTATE_VAR_ELEM32(x, i) MDisp(X64JitConstants::CTXREG, \
(int)(offsetof(MIPSState, x) - offsetof(MIPSState, f[0]) + (i) * 4))
// To get RIP/relative addressing (requires tight memory control so generated code isn't too far from the binary, and a reachable variable called mips):
// #define MIPSSTATE_VAR(x) M(&mips_->x)
#endif
// Only icache invalidations currently, so no type field.
struct PendingCacheOperation {
u32 addr;
u32 size;
};
class MIPSState {
public:
MIPSState();
~MIPSState();
void Init();
void Shutdown();
void Reset();
void UpdateCore(CPUCore desired);
void DoState(PointerWrap &p);
// MUST start with r and be followed by f, v, and t!
u32 r[32];
union {
float f[32];
u32 fi[32];
int fs[32];
};
union {
float v[128];
u32 vi[128];
};
// Register-allocated JIT Temps don't get flushed so we don't reserve space for them.
// However, the IR interpreter needs some temps that can stick around between ops.
// Can be indexed through r[] using indices 192+, thanks to predictable struct layout.
// Unfortunately, UBSAN isn't too happy about these.
u32 t[16]; //192
// If vfpuCtrl (prefixes) get mysterious values, check the VFPU regcache code.
u32 vfpuCtrl[16]; // 208
float vt[16]; //224 TODO: VFPU temp
// ARM64 wants lo/hi to be aligned to 64 bits from the base of this struct.
u32 padLoHi; // 240
union {
struct {
u32 pc; //241
u32 lo; //242
u32 hi; //243
u32 fcr31; //244 fpu control register
u32 fpcond; //245 cache the cond flag of fcr31 (& 1 << 23)
};
u32 other[6];
};
u32 nextPC;
int downcount; // This really doesn't belong here, it belongs in CoreTiming. But you gotta do what you gotta do, this needs to be reachable in the JITs without additional pointers.
bool inDelaySlot;
int llBit; // ll/sc
u32 temp; // can be used to save temporaries during calculations when we need more than R0 and R1
u32 mxcsrTemp;
// Temporary used around delay slots and similar.
u64 saved_flags;
// Debug stuff
u32 debugCount; // can be used to count basic blocks before crashes, etc.
// Temps needed for JitBranch.cpp experiments
u32 intBranchExit;
u32 jitBranchExit;
u32 savedPC;
alignas(16) u32 vcmpResult[4];
float sincostemp[2];
static const u32 FCR0_VALUE = 0x00003351;
#if PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)
enum {
NUM_X86_FPU_TEMPS = 16,
};
// FPU TEMP0, etc. are swapped in here if necessary (e.g. on x86.)
float tempValues[NUM_X86_FPU_TEMPS];
#endif
u8 VfpuWriteMask() const {
return (vfpuCtrl[VFPU_CTRL_DPREFIX] >> 8) & 0xF;
}
bool VfpuWriteMask(int i) const {
return (vfpuCtrl[VFPU_CTRL_DPREFIX] >> (8 + i)) & 1;
}
bool HasDefaultPrefix() const;
void SingleStep();
int RunLoopUntil(u64 globalTicks);
// To clear jit caches, etc.
// The immediate functions have the risk of invalidating the currently running block -
// might not behave correctly in all cases. Use Deferred when possible.
void InvalidateICacheRangeImmediate(u32 address, u32 length);
// Actual clearing is deferred until execution begins again.
void InvalidateICacheRangeDeferred(u32 address, u32 length);
void ClearJitCacheDeferred();
void ProcessPendingInvalidates();
private:
// Doesn't need save stating.
std::vector<PendingCacheOperation> pendingInvalidates_;
bool invalidateAll_ = false;
};
class MIPSDebugInterface;
// The one we are compiling or running currently
// TODO: These globals should be refactored away.
extern MIPSState *currentMIPS;
extern MIPSDebugInterface *currentDebugMIPS;
extern MIPSState mipsr4k;
extern const float cst_constants[32];
// The guest's rounding mode and flush-to-zero flag (fcr31 bits 0-1 and 24) are emulated by putting
// the *host* FPU into the matching mode, since we do the arithmetic with plain host float ops.
// That mode must not be left on while running anything that isn't emulating a guest instruction -
// HLE syscalls, replacement functions, the GPU - so an emulation loop applies it on entry and
// restores it before calling out, the way the JITs do (see Jit::ApplyRoundingMode).
// Apply is cheap when the guest is in the default mode (by far the common case): it does nothing.
void ApplyHostRoundingMode(const MIPSState *mips);
void RestoreHostRoundingMode();