2012-11-01 15:19:01 +00:00
// 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
2012-11-04 22:01:49 +00:00
// the Free Software Foundation, version 2.0 or later versions.
2012-11-01 15:19:01 +00:00
// 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/.
2013-05-27 03:30:14 +00:00
# include "Core/Reporting.h"
2013-11-08 11:30:31 +00:00
# include "Core/Config.h"
2013-08-15 08:35:17 +00:00
# include "Core/HLE/HLE.h"
# include "Core/HLE/HLETables.h"
# include "Core/Host.h"
2014-03-15 18:22:19 +00:00
# include "Core/MemMap.h"
2012-11-01 15:19:01 +00:00
2013-08-15 08:35:17 +00:00
# include "Core/MIPS/MIPS.h"
# include "Core/MIPS/MIPSCodeUtils.h"
# include "Core/MIPS/MIPSAnalyst.h"
# include "Core/MIPS/MIPSTables.h"
2012-11-01 15:19:01 +00:00
2013-08-15 08:35:17 +00:00
# include "Core/MIPS/x86/Jit.h"
# include "Core/MIPS/x86/RegCache.h"
2013-04-26 21:58:20 +00:00
# include "Core/MIPS/JitCommon/JitBlockCache.h"
2012-11-01 15:19:01 +00:00
2013-08-25 02:31:12 +00:00
# define _RS MIPS_GET_RS(op)
# define _RT MIPS_GET_RT(op)
# define _RD MIPS_GET_RD(op)
# define _FS MIPS_GET_FS(op)
# define _FT MIPS_GET_FT(op)
# define _FD MIPS_GET_FD(op)
# define _SA MIPS_GET_SA(op)
# define _POS ((op>> 6) & 0x1F)
# define _SIZE ((op>>11) & 0x1F)
# define _IMM16 (signed short)(op & 0xFFFF)
2013-08-16 09:02:56 +00:00
# define _IMM26 (op & 0x03FFFFFF)
2012-11-01 15:19:01 +00:00
# define LOOPOPTIMIZATION 0
using namespace MIPSAnalyst ;
2012-11-12 13:35:10 +00:00
// NOTE: Can't use CONDITIONAL_DISABLE in this file, branches are so special
// that they cannot be interpreted in the context of the Jit.
2013-01-21 02:48:54 +00:00
// But we can at least log and compare.
// #define DO_CONDITIONAL_LOG 1
# define DO_CONDITIONAL_LOG 0
2013-01-25 03:11:03 +00:00
// We can also disable nice delay slots.
// #define CONDITIONAL_NICE_DELAYSLOT delaySlotIsNice = false;
# define CONDITIONAL_NICE_DELAYSLOT ;
2013-01-21 02:48:54 +00:00
# if DO_CONDITIONAL_LOG
# define CONDITIONAL_LOG BranchLog(op);
# define CONDITIONAL_LOG_EXIT(addr) BranchLogExit(op, addr, false);
# define CONDITIONAL_LOG_EXIT_EAX() BranchLogExit(op, 0, true);
# else
# define CONDITIONAL_LOG ;
# define CONDITIONAL_LOG_EXIT(addr) ;
# define CONDITIONAL_LOG_EXIT_EAX() ;
# endif
2012-11-01 15:19:01 +00:00
namespace MIPSComp
{
2013-01-21 02:48:54 +00:00
static u32 intBranchExit ;
static u32 jitBranchExit ;
2013-08-24 21:43:49 +00:00
static void JitBranchLog ( MIPSOpcode op , u32 pc )
2013-01-21 02:48:54 +00:00
{
currentMIPS - > pc = pc ;
currentMIPS - > inDelaySlot = false ;
MIPSInterpretFunc func = MIPSGetInterpretFunc ( op ) ;
2013-08-24 20:22:10 +00:00
MIPSInfo info = MIPSGetInfo ( op ) ;
2013-01-21 02:48:54 +00:00
func ( op ) ;
// Branch taken, use nextPC.
if ( currentMIPS - > inDelaySlot )
intBranchExit = currentMIPS - > nextPC ;
else
{
// Branch not taken, likely delay slot skipped.
if ( info & LIKELY )
intBranchExit = currentMIPS - > pc ;
// Branch not taken, so increment over delay slot.
else
intBranchExit = currentMIPS - > pc + 4 ;
}
currentMIPS - > pc = pc ;
currentMIPS - > inDelaySlot = false ;
}
2013-08-24 21:43:49 +00:00
static void JitBranchLogMismatch ( MIPSOpcode op , u32 pc )
2013-01-21 02:48:54 +00:00
{
char temp [ 256 ] ;
MIPSDisAsm ( op , pc , temp , true ) ;
ERROR_LOG ( JIT , " Bad jump: %s - int:%08x jit:%08x " , temp , intBranchExit , jitBranchExit ) ;
host - > SetDebugMode ( true ) ;
}
2013-08-24 21:43:49 +00:00
void Jit : : BranchLog ( MIPSOpcode op )
2013-01-21 02:48:54 +00:00
{
FlushAll ( ) ;
2014-01-18 17:57:13 +00:00
ABI_CallFunctionCC ( thunks . ProtectFunction ( & JitBranchLog ) , op . encoding , js . compilerPC ) ;
2013-01-21 02:48:54 +00:00
}
2013-08-24 21:43:49 +00:00
void Jit : : BranchLogExit ( MIPSOpcode op , u32 dest , bool useEAX )
2013-01-21 02:48:54 +00:00
{
2013-01-21 03:29:06 +00:00
OpArg destArg = useEAX ? R ( EAX ) : Imm32 ( dest ) ;
2013-01-21 02:48:54 +00:00
2014-01-18 17:57:13 +00:00
CMP ( 32 , M ( & intBranchExit ) , destArg ) ;
2013-01-21 03:29:06 +00:00
FixupBranch skip = J_CC ( CC_E ) ;
2013-01-21 02:48:54 +00:00
2014-01-18 17:57:13 +00:00
MOV ( 32 , M ( & jitBranchExit ) , destArg ) ;
ABI_CallFunctionCC ( thunks . ProtectFunction ( & JitBranchLogMismatch ) , op . encoding , js . compilerPC ) ;
2013-01-21 03:29:06 +00:00
// Restore EAX, we probably ruined it.
2013-01-21 02:48:54 +00:00
if ( useEAX )
2014-01-18 17:57:13 +00:00
MOV ( 32 , R ( EAX ) , M ( & jitBranchExit ) ) ;
2013-01-21 03:29:06 +00:00
SetJumpTarget ( skip ) ;
2013-01-21 02:48:54 +00:00
}
2013-11-11 06:50:23 +00:00
static CCFlags FlipCCFlag ( CCFlags flag )
{
switch ( flag )
{
case CC_O : return CC_NO ;
case CC_NO : return CC_O ;
case CC_B : return CC_NB ;
case CC_NB : return CC_B ;
case CC_Z : return CC_NZ ;
case CC_NZ : return CC_Z ;
case CC_BE : return CC_NBE ;
case CC_NBE : return CC_BE ;
case CC_S : return CC_NS ;
case CC_NS : return CC_S ;
case CC_P : return CC_NP ;
case CC_NP : return CC_P ;
case CC_L : return CC_NL ;
case CC_NL : return CC_L ;
case CC_LE : return CC_NLE ;
case CC_NLE : return CC_LE ;
}
2013-12-09 15:48:11 +00:00
ERROR_LOG_REPORT ( JIT , " FlipCCFlag: Unexpected CC flag: %d " , flag ) ;
2013-11-11 06:50:23 +00:00
return CC_O ;
}
bool Jit : : PredictTakeBranch ( u32 targetAddr , bool likely ) {
// If it's likely, it's... probably likely, right?
if ( likely )
return true ;
// TODO: Normal branch prediction would be to take branches going upward to lower addresses.
// However, this results in worse performance as of this comment's writing.
// The reverse check generally gives better or same performance.
return targetAddr > js . compilerPC ;
}
2013-11-12 08:45:28 +00:00
void Jit : : CompBranchExits ( CCFlags cc , u32 targetAddr , u32 notTakenAddr , bool delaySlotIsNice , bool likely , bool andLink ) {
// We may want to try to continue along this branch a little while, to reduce reg flushing.
if ( CanContinueBranch ( ) )
{
bool predictTakeBranch = PredictTakeBranch ( targetAddr , likely ) ;
if ( predictTakeBranch )
cc = FlipCCFlag ( cc ) ;
Gen : : FixupBranch ptr ;
RegCacheState state ;
if ( ! likely )
{
if ( ! delaySlotIsNice )
CompileDelaySlot ( DELAYSLOT_SAFE ) ;
ptr = J_CC ( cc , true ) ;
GetStateAndFlushAll ( state ) ;
}
else
{
ptr = J_CC ( cc , true ) ;
if ( predictTakeBranch )
GetStateAndFlushAll ( state ) ;
else
2014-10-12 19:44:51 +00:00
{
// We need to get the state BEFORE the delay slot is compiled.
gpr . GetState ( state . gpr ) ;
fpr . GetState ( state . fpr ) ;
2013-11-12 08:45:28 +00:00
CompileDelaySlot ( DELAYSLOT_FLUSH ) ;
2014-10-12 19:44:51 +00:00
}
2013-11-12 08:45:28 +00:00
}
if ( predictTakeBranch )
{
// We flipped the cc, the not taken case is first.
CONDITIONAL_LOG_EXIT ( notTakenAddr ) ;
WriteExit ( notTakenAddr , js . nextExit + + ) ;
// Now our taken path. Bring the regs back, we didn't flush 'em after all.
SetJumpTarget ( ptr ) ;
RestoreState ( state ) ;
CONDITIONAL_LOG_EXIT ( targetAddr ) ;
if ( andLink )
gpr . SetImm ( MIPS_REG_RA , js . compilerPC + 8 ) ;
// Don't forget to run the delay slot if likely.
if ( likely )
CompileDelaySlot ( DELAYSLOT_NICE ) ;
// Account for the increment in the loop.
js . compilerPC = targetAddr - 4 ;
// In case the delay slot was a break or something.
js . compiling = true ;
}
else
{
// Take the branch
if ( andLink )
MOV ( 32 , M ( & mips_ - > r [ MIPS_REG_RA ] ) , Imm32 ( js . compilerPC + 8 ) ) ;
CONDITIONAL_LOG_EXIT ( targetAddr ) ;
WriteExit ( targetAddr , js . nextExit + + ) ;
// Not taken
SetJumpTarget ( ptr ) ;
RestoreState ( state ) ;
CONDITIONAL_LOG_EXIT ( notTakenAddr ) ;
// Account for the delay slot.
js . compilerPC + = 4 ;
// In case the delay slot was a break or something.
js . compiling = true ;
}
}
else
{
Gen : : FixupBranch ptr ;
if ( ! likely )
{
if ( ! delaySlotIsNice )
CompileDelaySlot ( DELAYSLOT_SAFE_FLUSH ) ;
else
FlushAll ( ) ;
ptr = J_CC ( cc , true ) ;
}
else
{
FlushAll ( ) ;
ptr = J_CC ( cc , true ) ;
CompileDelaySlot ( DELAYSLOT_FLUSH ) ;
}
// Take the branch
if ( andLink )
MOV ( 32 , M ( & mips_ - > r [ MIPS_REG_RA ] ) , Imm32 ( js . compilerPC + 8 ) ) ;
CONDITIONAL_LOG_EXIT ( targetAddr ) ;
WriteExit ( targetAddr , js . nextExit + + ) ;
// Not taken
SetJumpTarget ( ptr ) ;
CONDITIONAL_LOG_EXIT ( notTakenAddr ) ;
WriteExit ( notTakenAddr , js . nextExit + + ) ;
js . compiling = false ;
}
}
2014-10-12 19:37:54 +00:00
void Jit : : CompBranchExit ( bool taken , u32 targetAddr , u32 notTakenAddr , bool delaySlotIsNice , bool likely , bool andLink ) {
// Continuing is handled in the imm branch case... TODO: move it here?
if ( taken & & andLink )
gpr . SetImm ( MIPS_REG_RA , js . compilerPC + 8 ) ;
if ( taken | | ! likely )
CompileDelaySlot ( DELAYSLOT_FLUSH ) ;
else
FlushAll ( ) ;
const u32 destAddr = taken ? targetAddr : notTakenAddr ;
CONDITIONAL_LOG_EXIT ( destAddr ) ;
WriteExit ( destAddr , js . nextExit + + ) ;
js . compiling = false ;
}
2013-08-24 21:43:49 +00:00
void Jit : : BranchRSRTComp ( MIPSOpcode op , Gen : : CCFlags cc , bool likely )
2012-11-01 15:19:01 +00:00
{
2013-01-21 02:48:54 +00:00
CONDITIONAL_LOG ;
2012-12-26 07:37:53 +00:00
if ( js . inDelaySlot ) {
2013-06-30 20:19:27 +00:00
ERROR_LOG_REPORT ( JIT , " Branch in RSRTComp delay slot at %08x in block starting at %08x " , js . compilerPC , js . blockStart ) ;
2012-12-26 07:37:53 +00:00
return ;
}
2013-08-16 07:44:23 +00:00
int offset = _IMM16 < < 2 ;
2013-08-25 02:31:12 +00:00
MIPSGPReg rt = _RT ;
MIPSGPReg rs = _RS ;
2012-11-01 15:19:01 +00:00
u32 targetAddr = js . compilerPC + offset + 4 ;
2014-10-12 19:00:59 +00:00
bool immBranch = false ;
2014-10-12 19:37:54 +00:00
bool immBranchTaken = false ;
2014-10-12 19:00:59 +00:00
if ( gpr . IsImm ( rs ) & & gpr . IsImm ( rt ) ) {
2013-08-16 08:05:52 +00:00
// The cc flags are opposites: when NOT to take the branch.
2014-10-12 19:37:54 +00:00
bool immBranchNotTaken ;
2013-11-11 04:55:20 +00:00
s32 rsImm = ( s32 ) gpr . GetImm ( rs ) ;
s32 rtImm = ( s32 ) gpr . GetImm ( rt ) ;
2013-08-16 08:05:52 +00:00
switch ( cc )
{
2014-10-12 19:00:59 +00:00
case CC_E : immBranchNotTaken = rsImm = = rtImm ; break ;
case CC_NE : immBranchNotTaken = rsImm ! = rtImm ; break ;
default : immBranchNotTaken = false ; _dbg_assert_msg_ ( JIT , false , " Bad cc flag in BranchRSRTComp(). " ) ;
2013-08-16 08:05:52 +00:00
}
2014-10-12 19:00:59 +00:00
immBranch = true ;
2014-10-12 19:37:54 +00:00
immBranchTaken = ! immBranchNotTaken ;
2014-10-12 19:00:59 +00:00
}
2013-08-16 08:05:52 +00:00
2014-10-12 19:00:59 +00:00
if ( jo . immBranches & & immBranch & & js . numInstructions < jo . continueMaxInstructions )
{
2014-10-12 19:37:54 +00:00
if ( ! immBranchTaken )
2013-08-16 08:05:52 +00:00
{
// Skip the delay slot if likely, otherwise it'll be the next instruction.
if ( likely )
js . compilerPC + = 4 ;
return ;
}
// Branch taken. Always compile the delay slot, and then go to dest.
CompileDelaySlot ( DELAYSLOT_NICE ) ;
// Account for the increment in the loop.
js . compilerPC = targetAddr - 4 ;
2013-08-25 00:25:50 +00:00
// In case the delay slot was a break or something.
js . compiling = true ;
2013-08-16 08:05:52 +00:00
return ;
}
2013-11-11 03:38:42 +00:00
MIPSOpcode delaySlotOp = Memory : : Read_Instruction ( js . compilerPC + 4 ) ;
bool delaySlotIsNice = IsDelaySlotNiceReg ( op , delaySlotOp , rt , rs ) ;
CONDITIONAL_NICE_DELAYSLOT ;
2013-01-24 09:56:47 +00:00
2014-10-12 19:37:54 +00:00
if ( immBranch )
CompBranchExit ( immBranchTaken , targetAddr , js . compilerPC + 8 , delaySlotIsNice , likely , false ) ;
2012-11-01 15:19:01 +00:00
else
{
2014-10-12 19:37:54 +00:00
if ( ! likely & & delaySlotIsNice )
CompileDelaySlot ( DELAYSLOT_NICE ) ;
if ( gpr . IsImm ( rt ) & & gpr . GetImm ( rt ) = = 0 )
{
gpr . KillImmediate ( rs , true , false ) ;
CMP ( 32 , gpr . R ( rs ) , Imm32 ( 0 ) ) ;
}
else
{
gpr . MapReg ( rs , true , false ) ;
CMP ( 32 , gpr . R ( rs ) , gpr . R ( rt ) ) ;
}
2012-11-01 15:19:01 +00:00
2014-10-12 19:37:54 +00:00
CompBranchExits ( cc , targetAddr , js . compilerPC + 8 , delaySlotIsNice , likely , false ) ;
}
2012-11-01 15:19:01 +00:00
}
2013-08-24 21:43:49 +00:00
void Jit : : BranchRSZeroComp ( MIPSOpcode op , Gen : : CCFlags cc , bool andLink , bool likely )
2012-11-01 15:19:01 +00:00
{
2013-01-21 02:48:54 +00:00
CONDITIONAL_LOG ;
2012-12-26 07:37:53 +00:00
if ( js . inDelaySlot ) {
2013-06-30 20:19:27 +00:00
ERROR_LOG_REPORT ( JIT , " Branch in RSZeroComp delay slot at %08x in block starting at %08x " , js . compilerPC , js . blockStart ) ;
2012-12-26 07:37:53 +00:00
return ;
}
2013-08-16 07:44:23 +00:00
int offset = _IMM16 < < 2 ;
2013-08-25 02:31:12 +00:00
MIPSGPReg rs = _RS ;
2012-11-01 15:19:01 +00:00
u32 targetAddr = js . compilerPC + offset + 4 ;
2014-10-12 19:00:59 +00:00
bool immBranch = false ;
2014-10-12 19:37:54 +00:00
bool immBranchTaken = false ;
2014-10-12 19:00:59 +00:00
if ( gpr . IsImm ( rs ) ) {
2013-08-16 08:05:52 +00:00
// The cc flags are opposites: when NOT to take the branch.
2014-10-12 19:37:54 +00:00
bool immBranchNotTaken ;
2013-11-11 04:55:20 +00:00
s32 imm = ( s32 ) gpr . GetImm ( rs ) ;
2013-08-16 08:05:52 +00:00
switch ( cc )
{
2014-10-12 19:00:59 +00:00
case CC_G : immBranchNotTaken = imm > 0 ; break ;
case CC_GE : immBranchNotTaken = imm > = 0 ; break ;
case CC_L : immBranchNotTaken = imm < 0 ; break ;
case CC_LE : immBranchNotTaken = imm < = 0 ; break ;
default : immBranchNotTaken = false ; _dbg_assert_msg_ ( JIT , false , " Bad cc flag in BranchRSZeroComp(). " ) ;
2013-08-16 08:05:52 +00:00
}
2014-10-12 19:00:59 +00:00
immBranch = true ;
2014-10-12 19:37:54 +00:00
immBranchTaken = ! immBranchNotTaken ;
2014-10-12 19:00:59 +00:00
}
2013-08-16 08:05:52 +00:00
2014-10-12 19:00:59 +00:00
if ( jo . immBranches & & immBranch & & js . numInstructions < jo . continueMaxInstructions )
{
2014-10-12 19:37:54 +00:00
if ( ! immBranchTaken )
2013-08-16 08:05:52 +00:00
{
// Skip the delay slot if likely, otherwise it'll be the next instruction.
if ( likely )
js . compilerPC + = 4 ;
return ;
}
// Branch taken. Always compile the delay slot, and then go to dest.
CompileDelaySlot ( DELAYSLOT_NICE ) ;
if ( andLink )
2013-11-11 04:55:20 +00:00
gpr . SetImm ( MIPS_REG_RA , js . compilerPC + 8 ) ;
2013-11-11 03:38:42 +00:00
2013-08-16 08:05:52 +00:00
// Account for the increment in the loop.
js . compilerPC = targetAddr - 4 ;
2013-08-25 00:25:50 +00:00
// In case the delay slot was a break or something.
js . compiling = true ;
2013-08-16 08:05:52 +00:00
return ;
}
2013-11-11 03:38:42 +00:00
MIPSOpcode delaySlotOp = Memory : : Read_Instruction ( js . compilerPC + 4 ) ;
bool delaySlotIsNice = IsDelaySlotNiceReg ( op , delaySlotOp , rs ) ;
CONDITIONAL_NICE_DELAYSLOT ;
2013-01-24 09:56:47 +00:00
2014-10-12 19:37:54 +00:00
if ( immBranch )
CompBranchExit ( immBranchTaken , targetAddr , js . compilerPC + 8 , delaySlotIsNice , likely , false ) ;
else
{
if ( ! likely & & delaySlotIsNice )
CompileDelaySlot ( DELAYSLOT_NICE ) ;
gpr . MapReg ( rs , true , false ) ;
CMP ( 32 , gpr . R ( rs ) , Imm32 ( 0 ) ) ;
2012-11-01 15:19:01 +00:00
2014-10-12 19:37:54 +00:00
CompBranchExits ( cc , targetAddr , js . compilerPC + 8 , delaySlotIsNice , likely , andLink ) ;
}
2012-11-01 15:19:01 +00:00
}
2013-08-24 21:43:49 +00:00
void Jit : : Comp_RelBranch ( MIPSOpcode op )
2012-11-01 15:19:01 +00:00
{
switch ( op > > 26 )
{
case 4 : BranchRSRTComp ( op , CC_NZ , false ) ; break ; //beq
2013-01-22 16:04:01 +00:00
case 5 : BranchRSRTComp ( op , CC_Z , false ) ; break ; //bne
2012-11-01 15:19:01 +00:00
2013-01-22 16:04:01 +00:00
case 6 : BranchRSZeroComp ( op , CC_G , false , false ) ; break ; //blez
case 7 : BranchRSZeroComp ( op , CC_LE , false , false ) ; break ; //bgtz
2012-11-01 15:19:01 +00:00
case 20 : BranchRSRTComp ( op , CC_NZ , true ) ; break ; //beql
2013-01-22 16:04:01 +00:00
case 21 : BranchRSRTComp ( op , CC_Z , true ) ; break ; //bnel
2012-11-01 15:19:01 +00:00
2013-01-22 16:04:01 +00:00
case 22 : BranchRSZeroComp ( op , CC_G , false , true ) ; break ; //blezl
case 23 : BranchRSZeroComp ( op , CC_LE , false , true ) ; break ; //bgtzl
2012-11-01 15:19:01 +00:00
default :
_dbg_assert_msg_ ( CPU , 0 , " Trying to compile instruction that can't be compiled " ) ;
break ;
}
}
2013-08-24 21:43:49 +00:00
void Jit : : Comp_RelBranchRI ( MIPSOpcode op )
2012-11-01 15:19:01 +00:00
{
switch ( ( op > > 16 ) & 0x1F )
{
2013-01-22 16:04:01 +00:00
case 0 : BranchRSZeroComp ( op , CC_GE , false , false ) ; break ; //if ((s32)R(rs) < 0) DelayBranchTo(addr); else PC += 4; break;//bltz
case 1 : BranchRSZeroComp ( op , CC_L , false , false ) ; break ; //if ((s32)R(rs) >= 0) DelayBranchTo(addr); else PC += 4; break;//bgez
case 2 : BranchRSZeroComp ( op , CC_GE , false , true ) ; break ; //if ((s32)R(rs) < 0) DelayBranchTo(addr); else PC += 8; break;//bltzl
case 3 : BranchRSZeroComp ( op , CC_L , false , true ) ; break ; //if ((s32)R(rs) >= 0) DelayBranchTo(addr); else PC += 8; break;//bgezl
case 16 : BranchRSZeroComp ( op , CC_GE , true , false ) ; break ; //R(MIPS_REG_RA) = PC + 8; if ((s32)R(rs) < 0) DelayBranchTo(addr); else PC += 4; break;//bltzal
case 17 : BranchRSZeroComp ( op , CC_L , true , false ) ; break ; //R(MIPS_REG_RA) = PC + 8; if ((s32)R(rs) >= 0) DelayBranchTo(addr); else PC += 4; break;//bgezal
case 18 : BranchRSZeroComp ( op , CC_GE , true , true ) ; break ; //R(MIPS_REG_RA) = PC + 8; if ((s32)R(rs) < 0) DelayBranchTo(addr); else SkipLikely(); break;//bltzall
case 19 : BranchRSZeroComp ( op , CC_L , true , true ) ; break ; //R(MIPS_REG_RA) = PC + 8; if ((s32)R(rs) >= 0) DelayBranchTo(addr); else SkipLikely(); break;//bgezall
2012-11-01 15:19:01 +00:00
default :
_dbg_assert_msg_ ( CPU , 0 , " Trying to compile instruction that can't be compiled " ) ;
break ;
}
}
// If likely is set, discard the branch slot if NOT taken.
2013-08-24 21:43:49 +00:00
void Jit : : BranchFPFlag ( MIPSOpcode op , Gen : : CCFlags cc , bool likely )
2012-11-01 15:19:01 +00:00
{
2013-01-21 02:48:54 +00:00
CONDITIONAL_LOG ;
2012-12-26 07:37:53 +00:00
if ( js . inDelaySlot ) {
2013-06-30 20:19:27 +00:00
ERROR_LOG_REPORT ( JIT , " Branch in FPFlag delay slot at %08x in block starting at %08x " , js . compilerPC , js . blockStart ) ;
2012-12-26 07:37:53 +00:00
return ;
}
2013-08-16 07:44:23 +00:00
int offset = _IMM16 < < 2 ;
2012-11-01 15:19:01 +00:00
u32 targetAddr = js . compilerPC + offset + 4 ;
2013-08-24 21:43:49 +00:00
MIPSOpcode delaySlotOp = Memory : : Read_Instruction ( js . compilerPC + 4 ) ;
2013-01-24 16:29:32 +00:00
bool delaySlotIsNice = IsDelaySlotNiceFPU ( op , delaySlotOp ) ;
2013-01-25 03:11:03 +00:00
CONDITIONAL_NICE_DELAYSLOT ;
2013-01-24 09:56:47 +00:00
if ( ! likely & & delaySlotIsNice )
CompileDelaySlot ( DELAYSLOT_NICE ) ;
2014-06-28 05:03:00 +00:00
gpr . KillImmediate ( MIPS_REG_FPCOND , true , false ) ;
TEST ( 32 , gpr . R ( MIPS_REG_FPCOND ) , Imm32 ( 1 ) ) ;
2012-11-01 15:19:01 +00:00
2013-11-12 08:45:28 +00:00
CompBranchExits ( cc , targetAddr , js . compilerPC + 8 , delaySlotIsNice , likely , false ) ;
2012-11-01 15:19:01 +00:00
}
2013-08-24 21:43:49 +00:00
void Jit : : Comp_FPUBranch ( MIPSOpcode op )
2012-11-01 15:19:01 +00:00
{
switch ( ( op > > 16 ) & 0x1f )
{
case 0 : BranchFPFlag ( op , CC_NZ , false ) ; break ; //bc1f
case 1 : BranchFPFlag ( op , CC_Z , false ) ; break ; //bc1t
case 2 : BranchFPFlag ( op , CC_NZ , true ) ; break ; //bc1fl
case 3 : BranchFPFlag ( op , CC_Z , true ) ; break ; //bc1tl
default :
_dbg_assert_msg_ ( CPU , 0 , " Trying to interpret instruction that can't be interpreted " ) ;
break ;
}
}
// If likely is set, discard the branch slot if NOT taken.
2013-08-24 21:43:49 +00:00
void Jit : : BranchVFPUFlag ( MIPSOpcode op , Gen : : CCFlags cc , bool likely )
2012-11-01 15:19:01 +00:00
{
2013-01-21 02:48:54 +00:00
CONDITIONAL_LOG ;
2012-12-26 07:37:53 +00:00
if ( js . inDelaySlot ) {
2013-06-30 20:19:27 +00:00
ERROR_LOG_REPORT ( JIT , " Branch in VFPU delay slot at %08x in block starting at %08x " , js . compilerPC , js . blockStart ) ;
2012-12-26 07:37:53 +00:00
return ;
}
2013-08-16 07:44:23 +00:00
int offset = _IMM16 < < 2 ;
2012-11-01 15:19:01 +00:00
u32 targetAddr = js . compilerPC + offset + 4 ;
2013-08-24 21:43:49 +00:00
MIPSOpcode delaySlotOp = Memory : : Read_Instruction ( js . compilerPC + 4 ) ;
2013-08-15 05:34:32 +00:00
// Sometimes there's a VFPU branch in a delay slot (Disgaea 2: Dark Hero Days, Zettai Hero Project, La Pucelle)
// The behavior is undefined - the CPU may take the second branch even if the first one passes.
// However, it does consistently try each branch, which these games seem to expect.
bool delaySlotIsBranch = MIPSCodeUtils : : IsVFPUBranch ( delaySlotOp ) ;
bool delaySlotIsNice = ! delaySlotIsBranch & & IsDelaySlotNiceVFPU ( op , delaySlotOp ) ;
2013-01-25 03:11:03 +00:00
CONDITIONAL_NICE_DELAYSLOT ;
2013-01-24 09:56:47 +00:00
if ( ! likely & & delaySlotIsNice )
CompileDelaySlot ( DELAYSLOT_NICE ) ;
2013-08-15 06:14:25 +00:00
if ( delaySlotIsBranch & & ( signed short ) ( delaySlotOp & 0xFFFF ) ! = ( signed short ) ( op & 0xFFFF ) - 1 )
2013-09-06 06:27:51 +00:00
ERROR_LOG_REPORT ( JIT , " VFPU branch in VFPU delay slot at %08x with different target %d / %d " , js . compilerPC , ( signed short ) ( delaySlotOp & 0xFFFF ) , ( signed short ) ( op & 0xFFFF ) - 1 ) ;
2013-01-24 09:56:47 +00:00
2012-11-01 15:19:01 +00:00
// THE CONDITION
int imm3 = ( op > > 18 ) & 7 ;
2014-06-28 05:30:45 +00:00
gpr . KillImmediate ( MIPS_REG_VFPUCC , true , false ) ;
TEST ( 32 , gpr . R ( MIPS_REG_VFPUCC ) , Imm32 ( 1 < < imm3 ) ) ;
2012-11-01 15:19:01 +00:00
2013-11-11 06:50:23 +00:00
u32 notTakenTarget = js . compilerPC + ( delaySlotIsBranch ? 4 : 8 ) ;
2013-11-12 08:45:28 +00:00
CompBranchExits ( cc , targetAddr , notTakenTarget , delaySlotIsNice , likely , false ) ;
2012-11-01 15:19:01 +00:00
}
2013-08-24 21:43:49 +00:00
void Jit : : Comp_VBranch ( MIPSOpcode op )
2012-11-01 15:19:01 +00:00
{
switch ( ( op > > 16 ) & 3 )
{
case 0 : BranchVFPUFlag ( op , CC_NZ , false ) ; break ; //bvf
case 1 : BranchVFPUFlag ( op , CC_Z , false ) ; break ; //bvt
case 2 : BranchVFPUFlag ( op , CC_NZ , true ) ; break ; //bvfl
case 3 : BranchVFPUFlag ( op , CC_Z , true ) ; break ; //bvtl
default :
_dbg_assert_msg_ ( CPU , 0 , " Comp_VBranch: Invalid instruction " ) ;
break ;
}
}
2014-06-30 02:02:41 +00:00
void Jit : : Comp_Jump ( MIPSOpcode op ) {
2013-01-21 02:48:54 +00:00
CONDITIONAL_LOG ;
2012-12-26 07:37:53 +00:00
if ( js . inDelaySlot ) {
2013-06-30 20:19:27 +00:00
ERROR_LOG_REPORT ( JIT , " Branch in Jump delay slot at %08x in block starting at %08x " , js . compilerPC , js . blockStart ) ;
2012-12-26 07:37:53 +00:00
return ;
}
2013-08-16 07:44:23 +00:00
u32 off = _IMM26 < < 2 ;
2012-11-01 15:19:01 +00:00
u32 targetAddr = ( js . compilerPC & 0xF0000000 ) | off ;
2013-03-11 09:04:15 +00:00
2013-11-11 05:46:42 +00:00
// Might be a stubbed address or something?
2014-06-30 02:02:41 +00:00
if ( ! Memory : : IsValidAddress ( targetAddr ) ) {
if ( js . nextExit = = 0 ) {
2014-07-05 20:19:53 +00:00
ERROR_LOG_REPORT ( JIT , " Jump to invalid address: %08x PC %08x LR %08x " , targetAddr , js . compilerPC , currentMIPS - > r [ MIPS_REG_RA ] ) ;
2014-06-30 02:02:41 +00:00
} else {
2013-11-11 05:46:42 +00:00
js . compiling = false ;
2014-06-30 02:02:41 +00:00
}
2013-11-11 05:46:42 +00:00
// TODO: Mark this block dirty or something? May be indication it will be changed by imports.
return ;
}
2014-06-30 02:02:41 +00:00
switch ( op > > 26 ) {
2012-11-01 15:19:01 +00:00
case 2 : //j
2013-03-11 09:18:27 +00:00
CompileDelaySlot ( DELAYSLOT_NICE ) ;
2013-11-11 04:29:30 +00:00
if ( jo . continueJumps & & js . numInstructions < jo . continueMaxInstructions )
{
// Account for the increment in the loop.
js . compilerPC = targetAddr - 4 ;
// In case the delay slot was a break or something.
js . compiling = true ;
return ;
}
2013-03-11 09:18:27 +00:00
FlushAll ( ) ;
2013-01-21 02:48:54 +00:00
CONDITIONAL_LOG_EXIT ( targetAddr ) ;
2013-08-16 07:44:23 +00:00
WriteExit ( targetAddr , js . nextExit + + ) ;
2013-03-11 09:04:15 +00:00
break ;
2012-11-01 15:19:01 +00:00
case 3 : //jal
2013-12-18 15:27:23 +00:00
// Special case for branches to "replace functions":
2013-12-18 23:39:49 +00:00
if ( ReplaceJalTo ( targetAddr ) )
return ;
2013-12-18 15:27:23 +00:00
2013-12-20 14:37:37 +00:00
// Check for small function inlining (future)
2013-11-11 04:29:30 +00:00
// Save return address - might be overwritten by delay slot.
2013-11-11 04:55:20 +00:00
gpr . SetImm ( MIPS_REG_RA , js . compilerPC + 8 ) ;
2013-03-11 09:18:27 +00:00
CompileDelaySlot ( DELAYSLOT_NICE ) ;
2013-11-11 04:29:30 +00:00
if ( jo . continueJumps & & js . numInstructions < jo . continueMaxInstructions )
{
// Account for the increment in the loop.
js . compilerPC = targetAddr - 4 ;
// In case the delay slot was a break or something.
js . compiling = true ;
return ;
}
2013-03-11 09:18:27 +00:00
FlushAll ( ) ;
2013-01-21 02:48:54 +00:00
CONDITIONAL_LOG_EXIT ( targetAddr ) ;
2013-08-16 07:44:23 +00:00
WriteExit ( targetAddr , js . nextExit + + ) ;
2012-11-01 15:19:01 +00:00
break ;
default :
_dbg_assert_msg_ ( CPU , 0 , " Trying to compile instruction that can't be compiled " ) ;
break ;
}
js . compiling = false ;
}
static u32 savedPC ;
2013-08-24 21:43:49 +00:00
void Jit : : Comp_JumpReg ( MIPSOpcode op )
2012-11-01 15:19:01 +00:00
{
2013-01-21 02:48:54 +00:00
CONDITIONAL_LOG ;
2012-12-26 07:37:53 +00:00
if ( js . inDelaySlot ) {
2013-06-30 20:19:27 +00:00
ERROR_LOG_REPORT ( JIT , " Branch in JumpReg delay slot at %08x in block starting at %08x " , js . compilerPC , js . blockStart ) ;
2012-12-26 07:37:53 +00:00
return ;
}
2013-08-25 02:31:12 +00:00
MIPSGPReg rs = _RS ;
2013-10-17 14:39:33 +00:00
MIPSGPReg rd = _RD ;
2013-11-14 07:44:15 +00:00
bool andLink = ( op & 0x3f ) = = 9 ;
2012-11-01 15:19:01 +00:00
2013-08-24 21:43:49 +00:00
MIPSOpcode delaySlotOp = Memory : : Read_Instruction ( js . compilerPC + 4 ) ;
2013-01-24 16:29:32 +00:00
bool delaySlotIsNice = IsDelaySlotNiceReg ( op , delaySlotOp , rs ) ;
2013-11-14 07:44:15 +00:00
if ( andLink & & rs = = rd )
delaySlotIsNice = false ;
2013-01-25 03:11:03 +00:00
CONDITIONAL_NICE_DELAYSLOT ;
2012-11-01 15:19:01 +00:00
2013-01-24 08:53:05 +00:00
if ( IsSyscall ( delaySlotOp ) )
{
// If this is a syscall, write the pc (for thread switching and other good reasons.)
2013-11-09 14:23:31 +00:00
gpr . MapReg ( rs , true , false ) ;
2014-10-12 22:16:09 +00:00
MOV ( 32 , M ( & mips_ - > pc ) , gpr . R ( rs ) ) ;
2013-11-14 07:44:15 +00:00
if ( andLink )
gpr . SetImm ( rd , js . compilerPC + 8 ) ;
2013-01-24 09:56:47 +00:00
CompileDelaySlot ( DELAYSLOT_FLUSH ) ;
2013-01-24 08:53:05 +00:00
// Syscalls write the exit code for us.
_dbg_assert_msg_ ( JIT , ! js . compiling , " Expected syscall to write an exit code. " ) ;
return ;
}
else if ( delaySlotIsNice )
2012-11-01 15:19:01 +00:00
{
2013-11-14 07:44:15 +00:00
if ( andLink )
gpr . SetImm ( rd , js . compilerPC + 8 ) ;
2013-01-24 09:56:47 +00:00
CompileDelaySlot ( DELAYSLOT_NICE ) ;
2013-11-08 11:30:31 +00:00
2013-11-14 07:44:15 +00:00
if ( ! andLink & & rs = = MIPS_REG_RA & & g_Config . bDiscardRegsOnJRRA ) {
2013-11-08 11:30:31 +00:00
// According to the MIPS ABI, there are some regs we don't need to preserve.
// Let's discard them so we don't need to write them back.
// NOTE: Not all games follow the MIPS ABI! Tekken 6, for example, will crash
// with this enabled.
2013-11-11 04:29:30 +00:00
gpr . DiscardRegContentsIfCached ( MIPS_REG_COMPILER_SCRATCH ) ;
2013-11-08 11:30:31 +00:00
for ( int i = MIPS_REG_A0 ; i < = MIPS_REG_T7 ; i + + )
gpr . DiscardRegContentsIfCached ( ( MIPSGPReg ) i ) ;
gpr . DiscardRegContentsIfCached ( MIPS_REG_T8 ) ;
gpr . DiscardRegContentsIfCached ( MIPS_REG_T9 ) ;
}
2013-11-11 04:55:20 +00:00
if ( jo . continueJumps & & gpr . IsImm ( rs ) & & js . numInstructions < jo . continueMaxInstructions )
2013-11-11 04:29:30 +00:00
{
// Account for the increment in the loop.
2013-11-11 04:55:20 +00:00
js . compilerPC = gpr . GetImm ( rs ) - 4 ;
2013-11-11 04:29:30 +00:00
// In case the delay slot was a break or something.
js . compiling = true ;
return ;
}
MOV ( 32 , R ( EAX ) , gpr . R ( rs ) ) ;
2012-11-01 15:19:01 +00:00
FlushAll ( ) ;
}
else
{
2013-01-24 08:53:05 +00:00
// Latch destination now - save it in memory.
2013-11-09 14:23:31 +00:00
gpr . MapReg ( rs , true , false ) ;
2012-11-01 15:19:01 +00:00
MOV ( 32 , M ( & savedPC ) , gpr . R ( rs ) ) ;
2013-11-14 07:44:15 +00:00
if ( andLink )
gpr . SetImm ( rd , js . compilerPC + 8 ) ;
2013-01-24 09:56:47 +00:00
CompileDelaySlot ( DELAYSLOT_NICE ) ;
2012-11-01 15:19:01 +00:00
MOV ( 32 , R ( EAX ) , M ( & savedPC ) ) ;
2013-01-24 09:56:47 +00:00
FlushAll ( ) ;
2012-11-01 15:19:01 +00:00
}
switch ( op & 0x3f )
{
case 8 : //jr
break ;
case 9 : //jalr
break ;
default :
_dbg_assert_msg_ ( CPU , 0 , " Trying to compile instruction that can't be compiled " ) ;
break ;
}
2013-01-21 02:48:54 +00:00
CONDITIONAL_LOG_EXIT_EAX ( ) ;
2012-11-01 15:19:01 +00:00
WriteExitDestInEAX ( ) ;
js . compiling = false ;
}
2013-08-24 21:43:49 +00:00
void Jit : : Comp_Syscall ( MIPSOpcode op )
2012-11-01 15:19:01 +00:00
{
2013-11-11 04:29:30 +00:00
// TODO: Maybe discard v0, v1, and some temps? Definitely at?
2012-11-01 15:19:01 +00:00
FlushAll ( ) ;
2013-01-08 18:30:28 +00:00
2013-01-22 06:57:53 +00:00
// If we're in a delay slot, this is off by one.
const int offset = js . inDelaySlot ? - 1 : 0 ;
WriteDowncount ( offset ) ;
2014-10-12 18:03:39 +00:00
RestoreRoundingMode ( ) ;
2013-01-22 06:57:53 +00:00
js . downcountAmount = - offset ;
2013-11-02 20:15:44 +00:00
// Skip the CallSyscall where possible.
void * quickFunc = GetQuickSyscallFunc ( op ) ;
if ( quickFunc )
ABI_CallFunctionP ( quickFunc , ( void * ) GetSyscallInfo ( op ) ) ;
2013-08-15 08:35:17 +00:00
else
2014-01-18 17:57:13 +00:00
ABI_CallFunctionC ( & CallSyscall , op . encoding ) ;
2012-11-01 15:19:01 +00:00
2014-10-12 18:03:39 +00:00
ApplyRoundingMode ( ) ;
2012-11-01 15:19:01 +00:00
WriteSyscallExit ( ) ;
js . compiling = false ;
}
2013-08-24 21:43:49 +00:00
void Jit : : Comp_Break ( MIPSOpcode op )
2013-02-01 08:49:14 +00:00
{
Comp_Generic ( op ) ;
WriteSyscallExit ( ) ;
js . compiling = false ;
}
2012-11-01 15:19:01 +00:00
} // namespace Mipscomp