mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-09-11 15:13:37 +02:00
Every JIT backend puts the host FPU into the mode fcr31 asks for (bits 0-1 and 24) before running emulated code, and takes it back out before calling any host code. The plain interpreter did none of that, so all its float math rounded to nearest with denormals intact no matter what the game had set - cpu/fpu/fpu fails under -i and passes under the JIT on exactly this. Move the helpers the IR interpreter already had for this out of IRInterpreter and into MIPS.cpp as ApplyHostRoundingMode/RestoreHostRoundingMode, and use them around the interpreter's run loop and single step, restoring around syscalls and replacement functions, which are host code. ctc1 re-applies immediately, since the interpreter has no block boundary to defer it to. round.w.s changes with it: it was floorf(x + 0.5f), which is half-away-from-zero rather than the half-to-even every JIT produces, and the add would now pick up the guest's rounding mode on top of that. round_ieee_754 is both correct and mode-independent, and is what cvt.w.s already used for the same rounding. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SfY7iFJEjmRXf1XGrTs4MF
305 lines
8.0 KiB
C++
305 lines
8.0 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
|
|
};
|
|
|
|
// 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();
|