Jit64: Dynamic length of regular jump instruction (for known addresses)

Conditional jumps already do that, so let's be consistent.
This commit is contained in:
Martino Fontana 2025-10-05 21:46:41 +02:00
commit f6e5448b43
9 changed files with 55 additions and 40 deletions

View file

@ -412,26 +412,30 @@ void XEmitter::Rex(int w, int r, int x, int b)
Write8(rx); Write8(rx);
} }
void XEmitter::JMP(const u8* addr, const Jump jump) void XEmitter::JMP(const u8* addr, bool force_near_padding)
{ {
u64 fn = (u64)addr; u64 fn = (u64)addr;
if (jump == Jump::Short) s64 distance = (s64)(fn - ((u64)code + SHORT_JMP_LEN));
if (distance < -0x80 || distance >= 0x80)
{ {
s64 distance = (s64)(fn - ((u64)code + 2)); distance = (s64)(fn - ((u64)code + NEAR_JMP_LEN));
ASSERT_MSG(DYNA_REC, distance >= -0x80 && distance < 0x80, ASSERT_MSG(DYNA_REC, distance >= -0x80000000LL && distance < 0x80000000LL,
"Jump::Short target too far away ({}), needs Jump::Near", distance); "Jump target too far away ({}), needs indirect register", distance);
// 8 bits will do Write8(0xE9);
Write8(0xEB); Write32((u32)(s32)distance);
Write8((u8)(s8)distance);
} }
else else
{ {
s64 distance = (s64)(fn - ((u64)code + 5)); Write8(0xEB);
Write8((u8)(s8)distance);
ASSERT_MSG(DYNA_REC, distance >= -0x80000000LL && distance < 0x80000000LL, if (force_near_padding)
"Jump::Near target too far away ({}), needs indirect register", distance); {
Write8(0xE9); for (int i = 0; i < NEAR_JMP_LEN - SHORT_JMP_LEN; i++)
Write32((u32)(s32)distance); {
// INT3 is more efficient than NOP if never executed, as it stops CPU speculation.
INT3();
}
}
} }
} }

View file

@ -444,6 +444,8 @@ public:
Short, Short,
Near, Near,
}; };
static const int SHORT_JMP_LEN = 2;
static const int NEAR_JMP_LEN = 5;
// Flow control // Flow control
void RET(); void RET();
@ -451,7 +453,7 @@ public:
void UD2(); void UD2();
[[nodiscard]] FixupBranch J(Jump jump = Jump::Short); [[nodiscard]] FixupBranch J(Jump jump = Jump::Short);
void JMP(const u8* addr, Jump jump = Jump::Short); void JMP(const u8* addr, bool force_near_padding = false);
void JMPptr(const OpArg& arg); void JMPptr(const OpArg& arg);
void JMPself(); // infinite loop! void JMPself(); // infinite loop!
#ifdef CALL #ifdef CALL

View file

@ -119,7 +119,7 @@ void DSPEmitter::checkExceptions(u32 retval)
TEST(32, R(ABI_RETURN), R(ABI_RETURN)); TEST(32, R(ABI_RETURN), R(ABI_RETURN));
FixupBranch skip_return = J_CC(CC_Z, Jump::Short); FixupBranch skip_return = J_CC(CC_Z, Jump::Short);
MOV(32, R(EAX), Imm32(retval)); MOV(32, R(EAX), Imm32(retval));
JMP(m_return_dispatcher, Jump::Near); JMP(m_return_dispatcher);
SetJumpTarget(skip_return); SetJumpTarget(skip_return);
m_gpr.LoadRegs(false); m_gpr.LoadRegs(false);
m_gpr.FlushRegs(c, false); m_gpr.FlushRegs(c, false);
@ -293,7 +293,7 @@ void DSPEmitter::Compile(u16 start_addr)
{ {
MOV(16, R(EAX), Imm16(m_block_size[start_addr])); MOV(16, R(EAX), Imm16(m_block_size[start_addr]));
} }
JMP(m_return_dispatcher, Jump::Near); JMP(m_return_dispatcher);
m_gpr.LoadRegs(false); m_gpr.LoadRegs(false);
m_gpr.FlushRegs(c, false); m_gpr.FlushRegs(c, false);
@ -329,7 +329,7 @@ void DSPEmitter::Compile(u16 start_addr)
{ {
MOV(16, R(EAX), Imm16(m_block_size[start_addr])); MOV(16, R(EAX), Imm16(m_block_size[start_addr]));
} }
JMP(m_return_dispatcher, Jump::Near); JMP(m_return_dispatcher);
m_gpr.LoadRegs(false); m_gpr.LoadRegs(false);
m_gpr.FlushRegs(c, false); m_gpr.FlushRegs(c, false);
@ -392,7 +392,7 @@ void DSPEmitter::Compile(u16 start_addr)
{ {
MOV(16, R(EAX), Imm16(m_block_size[start_addr])); MOV(16, R(EAX), Imm16(m_block_size[start_addr]));
} }
JMP(m_return_dispatcher, Jump::Near); JMP(m_return_dispatcher);
} }
void DSPEmitter::CompileCurrent(DSPEmitter& emitter) void DSPEmitter::CompileCurrent(DSPEmitter& emitter)

View file

@ -109,7 +109,7 @@ void DSPEmitter::WriteBranchExit()
{ {
MOV(16, R(EAX), Imm16(m_block_size[m_start_address])); MOV(16, R(EAX), Imm16(m_block_size[m_start_address]));
} }
JMP(m_return_dispatcher, Jump::Near); JMP(m_return_dispatcher);
m_gpr.LoadRegs(false); m_gpr.LoadRegs(false);
m_gpr.FlushRegs(c, false); m_gpr.FlushRegs(c, false);
} }
@ -130,7 +130,7 @@ void DSPEmitter::WriteBlockLink(u16 dest)
SUB(16, R(ECX), Imm16(m_block_size[m_start_address])); SUB(16, R(ECX), Imm16(m_block_size[m_start_address]));
MOV(16, MatR(RAX), R(ECX)); MOV(16, MatR(RAX), R(ECX));
JMP(m_block_links[dest], Jump::Near); JMP(m_block_links[dest]);
SetJumpTarget(notEnoughCycles); SetJumpTarget(notEnoughCycles);
} }
else else

View file

@ -207,7 +207,7 @@ bool Jit64::BackPatch(SContext* ctx)
// Patch the original memory operation. // Patch the original memory operation.
XEmitter emitter(start, start + info.len); XEmitter emitter(start, start + info.len);
emitter.JMP(trampoline, Jump::Near); emitter.JMP(trampoline);
// NOPs become dead code // NOPs become dead code
const u8* end = info.start + info.len; const u8* end = info.start + info.len;
for (const u8* i = emitter.GetCodePtr(); i < end; ++i) for (const u8* i = emitter.GetCodePtr(); i < end; ++i)
@ -594,7 +594,10 @@ void Jit64::JustWriteExit(u32 destination, bool bl, u32 after)
J_CC(CC_LE, asm_routines.do_timing); J_CC(CC_LE, asm_routines.do_timing);
linkData.exitPtrs = GetWritableCodePtr(); linkData.exitPtrs = GetWritableCodePtr();
JMP(asm_routines.dispatcher_no_timing_check, Jump::Near); // Padding required for correctness, as the JMP length might differ between dispatcher and
// linked block: if this wrote a Short JMP but then JitBlockCache::WriteLinkBlock wrote a Near
// JMP, the latter would overwrite other instructions.
JMP(asm_routines.dispatcher_no_timing_check, true);
} }
b->linkData.push_back(linkData); b->linkData.push_back(linkData);
@ -622,7 +625,7 @@ void Jit64::WriteExitDestInRSCRATCH(bool bl, u32 after)
} }
else else
{ {
JMP(asm_routines.dispatcher, Jump::Near); JMP(asm_routines.dispatcher);
} }
} }
@ -660,7 +663,7 @@ void Jit64::WriteRfiExitDestInRSCRATCH()
ABI_PopRegistersAndAdjustStack({}, 0); ABI_PopRegistersAndAdjustStack({}, 0);
EmitUpdateMembase(); EmitUpdateMembase();
SUB(32, PPCSTATE(downcount), Imm32(js.downcountAmount)); SUB(32, PPCSTATE(downcount), Imm32(js.downcountAmount));
JMP(asm_routines.dispatcher, Jump::Near); JMP(asm_routines.dispatcher);
} }
void Jit64::WriteIdleExit(u32 destination) void Jit64::WriteIdleExit(u32 destination)
@ -682,7 +685,7 @@ void Jit64::WriteExceptionExit()
ABI_PopRegistersAndAdjustStack({}, 0); ABI_PopRegistersAndAdjustStack({}, 0);
EmitUpdateMembase(); EmitUpdateMembase();
SUB(32, PPCSTATE(downcount), Imm32(js.downcountAmount)); SUB(32, PPCSTATE(downcount), Imm32(js.downcountAmount));
JMP(asm_routines.dispatcher, Jump::Near); JMP(asm_routines.dispatcher);
} }
void Jit64::WriteExternalExceptionExit() void Jit64::WriteExternalExceptionExit()
@ -695,7 +698,7 @@ void Jit64::WriteExternalExceptionExit()
ABI_PopRegistersAndAdjustStack({}, 0); ABI_PopRegistersAndAdjustStack({}, 0);
EmitUpdateMembase(); EmitUpdateMembase();
SUB(32, PPCSTATE(downcount), Imm32(js.downcountAmount)); SUB(32, PPCSTATE(downcount), Imm32(js.downcountAmount));
JMP(asm_routines.dispatcher, Jump::Near); JMP(asm_routines.dispatcher);
} }
void Jit64::Run() void Jit64::Run()
@ -936,7 +939,7 @@ bool Jit64::DoJit(u32 em_address, JitBlock* b, u32 nextPC)
ABI_CallFunctionPC(JitInterface::CompileExceptionCheckFromJIT, &m_system.GetJitInterface(), ABI_CallFunctionPC(JitInterface::CompileExceptionCheckFromJIT, &m_system.GetJitInterface(),
static_cast<u32>(JitInterface::ExceptionType::PairedQuantize)); static_cast<u32>(JitInterface::ExceptionType::PairedQuantize));
ABI_PopRegistersAndAdjustStack({}, 0); ABI_PopRegistersAndAdjustStack({}, 0);
JMP(asm_routines.dispatcher_no_check, Jump::Near); JMP(asm_routines.dispatcher_no_check);
SwitchToNearCode(); SwitchToNearCode();
// Insert a check that the GQRs are still the value we expect at // Insert a check that the GQRs are still the value we expect at
@ -1064,7 +1067,7 @@ bool Jit64::DoJit(u32 em_address, JitBlock* b, u32 nextPC)
Cleanup(); Cleanup();
MOV(32, PPCSTATE(npc), Imm32(op.address)); MOV(32, PPCSTATE(npc), Imm32(op.address));
SUB(32, PPCSTATE(downcount), Imm32(js.downcountAmount)); SUB(32, PPCSTATE(downcount), Imm32(js.downcountAmount));
JMP(asm_routines.dispatcher_exit, Jump::Near); JMP(asm_routines.dispatcher_exit);
SetJumpTarget(noBreakpoint); SetJumpTarget(noBreakpoint);
} }
@ -1284,7 +1287,7 @@ void Jit64::IntializeSpeculativeConstants()
ABI_CallFunctionPC(JitInterface::CompileExceptionCheckFromJIT, &m_system.GetJitInterface(), ABI_CallFunctionPC(JitInterface::CompileExceptionCheckFromJIT, &m_system.GetJitInterface(),
static_cast<u32>(JitInterface::ExceptionType::SpeculativeConstants)); static_cast<u32>(JitInterface::ExceptionType::SpeculativeConstants));
ABI_PopRegistersAndAdjustStack({}, 0); ABI_PopRegistersAndAdjustStack({}, 0);
JMP(asm_routines.dispatcher_no_check, Jump::Near); JMP(asm_routines.dispatcher_no_check);
SwitchToNearCode(); SwitchToNearCode();
} }
CMP(32, PPCSTATE_GPR(i), Imm32(compileTimeValue)); CMP(32, PPCSTATE_GPR(i), Imm32(compileTimeValue));

View file

@ -218,7 +218,7 @@ void Jit64AsmRoutineManager::Generate()
// If jitting triggered an ISI exception, MSR.DR may have changed // If jitting triggered an ISI exception, MSR.DR may have changed
MOV(64, R(RMEM), PPCSTATE(mem_ptr)); MOV(64, R(RMEM), PPCSTATE(mem_ptr));
JMP(dispatcher_no_check, Jump::Near); JMP(dispatcher_no_check);
SetJumpTarget(bail); SetJumpTarget(bail);
do_timing = GetCodePtr(); do_timing = GetCodePtr();

View file

@ -26,15 +26,19 @@ void JitBlockCache::WriteLinkBlock(const JitBlock::LinkData& source, const JitBl
// to emit JMP. So just NOP out the gap to the next block. // to emit JMP. So just NOP out the gap to the next block.
// Support up to 3 additional bytes because of alignment. // Support up to 3 additional bytes because of alignment.
s64 offset = address - location; s64 offset = address - location;
if (offset > 0 && offset <= 5 + 3) if (offset > 0 && offset <= Gen::XEmitter::NEAR_JMP_LEN + 3)
{ {
Gen::XEmitter emit(location, location + offset); Gen::XEmitter emit(location, location + offset);
emit.NOP(offset); emit.NOP(offset);
} }
else else
{ {
Gen::XEmitter emit(location, location + 5); // Length forced to Near because JMP length might differ between dispatcher and linked block.
emit.JMP(address, Gen::XEmitter::Jump::Near); // Technically this isn't necessary, as this is executed after Jit64::JustWriteExit (which
// also pads), and a Short JMP written on top of a Near JMP isn't incorrect since the garbage
// bytes are skipped. But they confuse the disassembler, and probably the CPU speculation too.
Gen::XEmitter emit(location, location + Gen::XEmitter::NEAR_JMP_LEN);
emit.JMP(address, true);
} }
} }
} }

View file

@ -46,7 +46,7 @@ const u8* TrampolineCache::GenerateReadTrampoline(const TrampolineInfo& info)
SafeLoadToReg(info.op_reg, info.op_arg, info.accessSize << 3, info.offset, info.registersInUse, SafeLoadToReg(info.op_reg, info.op_arg, info.accessSize << 3, info.offset, info.registersInUse,
info.signExtend, info.flags | SAFE_LOADSTORE_FORCE_SLOW_ACCESS); info.signExtend, info.flags | SAFE_LOADSTORE_FORCE_SLOW_ACCESS);
JMP(info.start + info.len, Jump::Near); JMP(info.start + info.len);
Common::JitRegister::Register(trampoline, GetCodePtr(), "JIT_ReadTrampoline_{:x}", info.pc); Common::JitRegister::Register(trampoline, GetCodePtr(), "JIT_ReadTrampoline_{:x}", info.pc);
return trampoline; return trampoline;
@ -65,7 +65,7 @@ const u8* TrampolineCache::GenerateWriteTrampoline(const TrampolineInfo& info)
SafeWriteRegToReg(info.op_arg, info.op_reg, info.accessSize << 3, info.offset, SafeWriteRegToReg(info.op_arg, info.op_reg, info.accessSize << 3, info.offset,
info.registersInUse, info.flags | SAFE_LOADSTORE_FORCE_SLOW_ACCESS); info.registersInUse, info.flags | SAFE_LOADSTORE_FORCE_SLOW_ACCESS);
JMP(info.start + info.len, Jump::Near); JMP(info.start + info.len);
Common::JitRegister::Register(trampoline, GetCodePtr(), "JIT_WriteTrampoline_{:x}", info.pc); Common::JitRegister::Register(trampoline, GetCodePtr(), "JIT_WriteTrampoline_{:x}", info.pc);
return trampoline; return trampoline;

View file

@ -298,12 +298,14 @@ TEST_F(x64EmitterTest, POP_Register)
TEST_F(x64EmitterTest, JMP) TEST_F(x64EmitterTest, JMP)
{ {
emitter->NOP(1); emitter->NOP(1);
emitter->JMP(code_buffer, XEmitter::Jump::Short); emitter->JMP(code_buffer);
ExpectBytes({/* nop */ 0x90, /* short jmp */ 0xeb, /* offset -3 */ 0xfd}); ExpectBytes({/* nop */ 0x90, /* short jmp */ 0xeb, /* offset -3 */ 0xfd});
emitter->NOP(1); emitter->NOP(0x90);
emitter->JMP(code_buffer, XEmitter::Jump::Near); const u8* after_nops = emitter->GetCodePtr();
ExpectBytes({/* nop */ 0x90, /* near jmp */ 0xe9, /* offset -6 */ 0xfa, 0xff, 0xff, 0xff}); ResetCodeBuffer();
emitter->JMP(after_nops);
ExpectBytes({/* near jmp */ 0xe9, /* offset */ 0x8B, 0, 0, 0});
} }
TEST_F(x64EmitterTest, JMPptr_Register) TEST_F(x64EmitterTest, JMPptr_Register)