2013-04-17 22:43:11 -04:00
|
|
|
// Copyright 2013 Dolphin Emulator Project
|
|
|
|
// Licensed under GPLv2
|
|
|
|
// Refer to the license.txt file included.
|
2008-12-08 05:30:24 +00:00
|
|
|
|
2010-09-26 06:58:21 +00:00
|
|
|
#include <queue>
|
2014-02-17 05:18:15 -05:00
|
|
|
#include <string>
|
2008-12-08 05:30:24 +00:00
|
|
|
|
2014-02-17 05:18:15 -05:00
|
|
|
#include "Common/StringUtil.h"
|
|
|
|
|
|
|
|
#include "Core/ConfigManager.h"
|
|
|
|
#include "Core/GeckoCode.h"
|
|
|
|
#include "Core/HW/Memmap.h"
|
|
|
|
#include "Core/PowerPC/JitInterface.h"
|
|
|
|
#include "Core/PowerPC/PPCAnalyst.h"
|
|
|
|
#include "Core/PowerPC/PPCSymbolDB.h"
|
|
|
|
#include "Core/PowerPC/PPCTables.h"
|
|
|
|
#include "Core/PowerPC/SignatureDB.h"
|
|
|
|
#include "Core/PowerPC/Interpreter/Interpreter.h"
|
2008-12-08 05:30:24 +00:00
|
|
|
|
|
|
|
// Analyzes PowerPC code in memory to find functions
|
|
|
|
// After running, for each function we will know what functions it calls
|
|
|
|
// and what functions calls it. That is, we will have an incomplete call graph,
|
|
|
|
// but only missing indirect branches.
|
|
|
|
|
|
|
|
// The results of this analysis is displayed in the code browsing sections at the bottom left
|
|
|
|
// of the disassembly window (debugger).
|
|
|
|
|
|
|
|
// It is also useful for finding function boundaries so that we can find, fingerprint and detect library functions.
|
|
|
|
// We don't do this much currently. Only for the special case Super Monkey Ball.
|
|
|
|
|
|
|
|
namespace PPCAnalyst {
|
|
|
|
|
|
|
|
using namespace std;
|
|
|
|
|
2010-09-09 02:14:03 +00:00
|
|
|
static const int CODEBUFFER_SIZE = 32000;
|
|
|
|
// 0 does not perform block merging
|
2010-09-16 03:46:23 +00:00
|
|
|
static const int FUNCTION_FOLLOWING_THRESHOLD = 16;
|
2008-12-08 05:30:24 +00:00
|
|
|
|
2008-12-18 10:44:03 +00:00
|
|
|
CodeBuffer::CodeBuffer(int size)
|
2008-12-08 05:30:24 +00:00
|
|
|
{
|
2008-12-18 10:44:03 +00:00
|
|
|
codebuffer = new PPCAnalyst::CodeOp[size];
|
2008-12-20 17:22:30 +00:00
|
|
|
size_ = size;
|
2008-12-08 05:30:24 +00:00
|
|
|
}
|
|
|
|
|
2008-12-18 10:44:03 +00:00
|
|
|
CodeBuffer::~CodeBuffer()
|
2008-12-08 05:30:24 +00:00
|
|
|
{
|
2010-04-11 11:16:57 +00:00
|
|
|
delete[] codebuffer;
|
2008-12-08 05:30:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void AnalyzeFunction2(Symbol &func);
|
|
|
|
u32 EvaluateBranchTarget(UGeckoInstruction instr, u32 pc);
|
|
|
|
|
|
|
|
#define INVALID_TARGET ((u32)-1)
|
|
|
|
|
|
|
|
u32 EvaluateBranchTarget(UGeckoInstruction instr, u32 pc)
|
|
|
|
{
|
|
|
|
switch (instr.OPCD)
|
|
|
|
{
|
|
|
|
case 18://branch instruction
|
|
|
|
{
|
|
|
|
u32 target = SignExt26(instr.LI<<2);
|
|
|
|
if (!instr.AA)
|
|
|
|
target += pc;
|
|
|
|
|
|
|
|
return target;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
return INVALID_TARGET;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//To find the size of each found function, scan
|
|
|
|
//forward until we hit blr. In the meantime, collect information
|
|
|
|
//about which functions this function calls.
|
|
|
|
//Also collect which internal branch goes the farthest
|
|
|
|
//If any one goes farther than the blr, assume that there is more than
|
|
|
|
//one blr, and keep scanning.
|
|
|
|
|
|
|
|
bool AnalyzeFunction(u32 startAddr, Symbol &func, int max_size)
|
|
|
|
{
|
|
|
|
if (!func.name.size())
|
|
|
|
func.name = StringFromFormat("zz_%07x_", startAddr & 0x0FFFFFF);
|
|
|
|
if (func.analyzed >= 1)
|
|
|
|
return true; // No error, just already did it.
|
|
|
|
|
|
|
|
func.calls.clear();
|
|
|
|
func.callers.clear();
|
|
|
|
func.size = 0;
|
|
|
|
func.flags = FFLAG_LEAF;
|
2013-10-29 01:23:17 -04:00
|
|
|
u32 addr = startAddr;
|
|
|
|
|
2008-12-08 05:30:24 +00:00
|
|
|
u32 farthestInternalBranchTarget = startAddr;
|
|
|
|
int numInternalBranches = 0;
|
|
|
|
while (true)
|
|
|
|
{
|
|
|
|
func.size += 4;
|
|
|
|
if (func.size >= CODEBUFFER_SIZE * 4) //weird
|
|
|
|
return false;
|
2013-10-29 01:23:17 -04:00
|
|
|
|
2008-12-08 05:30:24 +00:00
|
|
|
UGeckoInstruction instr = (UGeckoInstruction)Memory::ReadUnchecked_U32(addr);
|
|
|
|
if (max_size && func.size > max_size)
|
|
|
|
{
|
|
|
|
func.address = startAddr;
|
|
|
|
func.analyzed = 1;
|
|
|
|
func.hash = SignatureDB::ComputeCodeChecksum(startAddr, addr);
|
|
|
|
if (numInternalBranches == 0)
|
|
|
|
func.flags |= FFLAG_STRAIGHT;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (PPCTables::IsValidInstruction(instr))
|
|
|
|
{
|
|
|
|
if (instr.hex == 0x4e800020) //4e800021 is blrl, not the end of a function
|
|
|
|
{
|
|
|
|
//BLR
|
|
|
|
if (farthestInternalBranchTarget > addr)
|
|
|
|
{
|
|
|
|
//bah, not this one, continue..
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
//a final blr!
|
|
|
|
//We're done! Looks like we have a neat valid function. Perfect.
|
|
|
|
//Let's calc the checksum and get outta here
|
|
|
|
func.address = startAddr;
|
|
|
|
func.analyzed = 1;
|
|
|
|
func.hash = SignatureDB::ComputeCodeChecksum(startAddr, addr);
|
|
|
|
if (numInternalBranches == 0)
|
|
|
|
func.flags |= FFLAG_STRAIGHT;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
else if ((instr.hex & 0xFC000000) == (0x4b000000 & 0xFC000000) && !instr.LK) {
|
|
|
|
u32 target = addr + SignExt26(instr.LI << 2);
|
|
|
|
if (target < startAddr || (max_size && target > max_size+startAddr))
|
|
|
|
{
|
|
|
|
//block ends by branching away. We're done!
|
|
|
|
func.size *= 4; // into bytes
|
|
|
|
func.address = startAddr;
|
|
|
|
func.analyzed = 1;
|
|
|
|
func.hash = SignatureDB::ComputeCodeChecksum(startAddr, addr);
|
|
|
|
if (numInternalBranches == 0)
|
|
|
|
func.flags |= FFLAG_STRAIGHT;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}*/
|
|
|
|
else if (instr.hex == 0x4e800021 || instr.hex == 0x4e800420 || instr.hex == 0x4e800421)
|
|
|
|
{
|
|
|
|
func.flags &= ~FFLAG_LEAF;
|
|
|
|
func.flags |= FFLAG_EVIL;
|
|
|
|
}
|
|
|
|
else if (instr.hex == 0x4c000064)
|
|
|
|
{
|
|
|
|
func.flags &= ~FFLAG_LEAF;
|
|
|
|
func.flags |= FFLAG_RFI;
|
|
|
|
}
|
2013-10-29 01:23:17 -04:00
|
|
|
else
|
2008-12-08 05:30:24 +00:00
|
|
|
{
|
|
|
|
if (instr.OPCD == 16)
|
|
|
|
{
|
|
|
|
u32 target = SignExt16(instr.BD << 2);
|
2013-10-29 01:23:17 -04:00
|
|
|
|
2008-12-08 05:30:24 +00:00
|
|
|
if (!instr.AA)
|
|
|
|
target += addr;
|
|
|
|
|
|
|
|
if (target > farthestInternalBranchTarget && !instr.LK)
|
|
|
|
{
|
|
|
|
farthestInternalBranchTarget = target;
|
|
|
|
}
|
|
|
|
numInternalBranches++;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
u32 target = EvaluateBranchTarget(instr, addr);
|
|
|
|
if (target != INVALID_TARGET && instr.LK)
|
|
|
|
{
|
|
|
|
//we found a branch-n-link!
|
|
|
|
func.calls.push_back(SCall(target,addr));
|
|
|
|
func.flags &= ~FFLAG_LEAF;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
addr += 4;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Second pass analysis, done after the first pass is done for all functions
|
|
|
|
// so we have more information to work with
|
2008-12-18 10:44:03 +00:00
|
|
|
void AnalyzeFunction2(Symbol *func)
|
2008-12-08 05:30:24 +00:00
|
|
|
{
|
2008-12-18 10:44:03 +00:00
|
|
|
u32 flags = func->flags;
|
2008-12-08 05:30:24 +00:00
|
|
|
|
|
|
|
bool nonleafcall = false;
|
2014-02-12 16:00:34 +01:00
|
|
|
for (const SCall& c : func->calls)
|
2008-12-08 05:30:24 +00:00
|
|
|
{
|
|
|
|
Symbol *called_func = g_symbolDB.GetSymbolFromAddr(c.function);
|
|
|
|
if (called_func && (called_func->flags & FFLAG_LEAF) == 0)
|
|
|
|
{
|
|
|
|
nonleafcall = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (nonleafcall && !(flags & FFLAG_EVIL) && !(flags & FFLAG_RFI))
|
|
|
|
flags |= FFLAG_ONLYCALLSNICELEAFS;
|
|
|
|
|
2008-12-18 10:44:03 +00:00
|
|
|
func->flags = flags;
|
2008-12-08 05:30:24 +00:00
|
|
|
}
|
|
|
|
|
2008-12-14 23:22:56 +00:00
|
|
|
// IMPORTANT - CURRENTLY ASSUMES THAT A IS A COMPARE
|
|
|
|
bool CanSwapAdjacentOps(const CodeOp &a, const CodeOp &b)
|
|
|
|
{
|
2010-07-23 19:30:00 +00:00
|
|
|
const GekkoOPInfo *b_info = b.opinfo;
|
2008-12-14 23:22:56 +00:00
|
|
|
int b_flags = b_info->flags;
|
|
|
|
if (b_flags & (FL_SET_CRx | FL_ENDBLOCK | FL_TIMER | FL_EVIL))
|
|
|
|
return false;
|
|
|
|
if ((b_flags & (FL_RC_BIT | FL_RC_BIT_F)) && (b.inst.hex & 1))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
switch (b.inst.OPCD)
|
|
|
|
{
|
|
|
|
case 16:
|
|
|
|
case 18:
|
|
|
|
//branches. Do not swap.
|
|
|
|
case 17: //sc
|
|
|
|
case 46: //lmw
|
|
|
|
case 19: //table19 - lots of tricky stuff
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2013-01-08 00:00:30 +01:00
|
|
|
// For now, only integer ops acceptable. Any instruction which can raise an
|
|
|
|
// interrupt is *not* a possible swap candidate: see [1] for an example of
|
|
|
|
// a crash caused by this error.
|
|
|
|
//
|
|
|
|
// [1] https://code.google.com/p/dolphin-emu/issues/detail?id=5864#c7
|
|
|
|
if (b_info->type != OPTYPE_INTEGER)
|
2008-12-14 23:22:56 +00:00
|
|
|
return false;
|
|
|
|
|
|
|
|
// Check that we have no register collisions.
|
2008-12-15 19:22:34 +00:00
|
|
|
// That is, check that none of b's outputs matches any of a's inputs,
|
|
|
|
// and that none of a's outputs matches any of b's inputs.
|
|
|
|
// The latter does not apply if a is a cmp, of course, but doesn't hurt to check.
|
2008-12-14 23:22:56 +00:00
|
|
|
for (int j = 0; j < 3; j++)
|
|
|
|
{
|
2008-12-15 19:22:34 +00:00
|
|
|
int regInA = a.regsIn[j];
|
|
|
|
int regInB = b.regsIn[j];
|
2013-10-29 01:23:17 -04:00
|
|
|
if (regInA >= 0 &&
|
2013-04-16 23:14:36 -04:00
|
|
|
(b.regsOut[0] == regInA ||
|
|
|
|
b.regsOut[1] == regInA))
|
2008-12-15 19:22:34 +00:00
|
|
|
{
|
|
|
|
// reg collision! don't swap
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (regInB >= 0 &&
|
2013-04-16 23:14:36 -04:00
|
|
|
(a.regsOut[0] == regInB ||
|
|
|
|
a.regsOut[1] == regInB))
|
2008-12-14 23:22:56 +00:00
|
|
|
{
|
|
|
|
// reg collision! don't swap
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2008-12-15 19:22:34 +00:00
|
|
|
// Does not yet perform inlining - although there are plans for that.
|
2010-04-10 15:41:44 +00:00
|
|
|
// Returns the exit address of the next PC
|
2010-09-22 02:42:17 +00:00
|
|
|
u32 Flatten(u32 address, int *realsize, BlockStats *st, BlockRegStats *gpa,
|
|
|
|
BlockRegStats *fpa, bool &broken_block, CodeBuffer *buffer,
|
|
|
|
int blockSize, u32* merged_addresses,
|
|
|
|
int capacity_of_merged_addresses, int& size_of_merged_addresses)
|
2008-12-08 05:30:24 +00:00
|
|
|
{
|
2010-09-22 02:42:17 +00:00
|
|
|
if (capacity_of_merged_addresses < FUNCTION_FOLLOWING_THRESHOLD) {
|
2013-03-31 19:10:21 -04:00
|
|
|
PanicAlert("Capacity of merged_addresses is too small!");
|
2010-09-22 02:42:17 +00:00
|
|
|
}
|
|
|
|
std::fill_n(merged_addresses, capacity_of_merged_addresses, 0);
|
|
|
|
merged_addresses[0] = address;
|
|
|
|
size_of_merged_addresses = 1;
|
|
|
|
|
2012-03-03 20:07:20 +01:00
|
|
|
memset(st, 0, sizeof(*st));
|
2013-10-29 01:23:17 -04:00
|
|
|
|
2010-07-06 07:21:35 +00:00
|
|
|
// Disabled the following optimization in preference of FAST_ICACHE
|
|
|
|
//UGeckoInstruction previnst = Memory::Read_Opcode_JIT_LC(address - 4);
|
|
|
|
//if (previnst.hex == 0x4e800020)
|
2014-02-16 23:51:41 -05:00
|
|
|
// st->isFirstBlockOfFunction = true;
|
2008-12-20 17:22:30 +00:00
|
|
|
|
2008-12-18 10:44:03 +00:00
|
|
|
gpa->any = true;
|
|
|
|
fpa->any = false;
|
2013-10-29 01:23:17 -04:00
|
|
|
|
2010-07-25 19:01:32 +00:00
|
|
|
for (int i = 0; i < 32; i++)
|
|
|
|
{
|
|
|
|
gpa->firstRead[i] = -1;
|
|
|
|
gpa->firstWrite[i] = -1;
|
|
|
|
gpa->numReads[i] = 0;
|
|
|
|
gpa->numWrites[i] = 0;
|
|
|
|
}
|
2008-12-08 05:30:24 +00:00
|
|
|
|
2008-12-20 17:22:30 +00:00
|
|
|
u32 blockstart = address;
|
2010-04-10 15:41:44 +00:00
|
|
|
int maxsize = blockSize;
|
|
|
|
|
2008-12-18 10:44:03 +00:00
|
|
|
int num_inst = 0;
|
|
|
|
int numFollows = 0;
|
2008-12-20 17:22:30 +00:00
|
|
|
int numCycles = 0;
|
2008-12-08 05:30:24 +00:00
|
|
|
|
2008-12-18 10:44:03 +00:00
|
|
|
CodeOp *code = buffer->codebuffer;
|
|
|
|
bool foundExit = false;
|
2008-12-08 05:30:24 +00:00
|
|
|
|
2010-09-23 02:05:04 +00:00
|
|
|
u32 returnAddress = 0;
|
|
|
|
|
2010-07-25 19:01:32 +00:00
|
|
|
// Do analysis of the code, look for dependencies etc
|
|
|
|
int numSystemInstructions = 0;
|
2010-04-10 15:41:44 +00:00
|
|
|
for (int i = 0; i < maxsize; i++)
|
2010-09-26 06:31:45 +00:00
|
|
|
{
|
2013-12-16 22:27:13 -05:00
|
|
|
UGeckoInstruction inst = JitInterface::Read_Opcode_JIT(address);
|
2013-10-29 01:23:17 -04:00
|
|
|
|
2010-07-29 12:17:47 +00:00
|
|
|
if (inst.hex != 0)
|
|
|
|
{
|
2010-07-31 02:29:11 +00:00
|
|
|
num_inst++;
|
|
|
|
memset(&code[i], 0, sizeof(CodeOp));
|
|
|
|
GekkoOPInfo *opinfo = GetOpInfo(inst);
|
|
|
|
code[i].opinfo = opinfo;
|
2010-09-26 06:31:45 +00:00
|
|
|
// FIXME: code[i].address may not be correct due to CST1 code.
|
2010-07-31 02:29:11 +00:00
|
|
|
code[i].address = address;
|
2010-07-29 12:17:47 +00:00
|
|
|
code[i].inst = inst;
|
|
|
|
code[i].branchTo = -1;
|
|
|
|
code[i].branchToIndex = -1;
|
|
|
|
code[i].skip = false;
|
2014-04-30 19:04:02 +08:00
|
|
|
numCycles += opinfo->numCycles;
|
2010-07-29 12:17:47 +00:00
|
|
|
|
|
|
|
code[i].wantsCR0 = false;
|
|
|
|
code[i].wantsCR1 = false;
|
|
|
|
code[i].wantsPS1 = false;
|
|
|
|
|
|
|
|
int flags = opinfo->flags;
|
|
|
|
|
|
|
|
if (flags & FL_USE_FPU)
|
|
|
|
fpa->any = true;
|
|
|
|
|
|
|
|
if (flags & FL_TIMER)
|
|
|
|
gpa->anyTimer = true;
|
|
|
|
|
|
|
|
// Does the instruction output CR0?
|
|
|
|
if (flags & FL_RC_BIT)
|
|
|
|
code[i].outputCR0 = inst.hex & 1; //todo fix
|
|
|
|
else if ((flags & FL_SET_CRn) && inst.CRFD == 0)
|
|
|
|
code[i].outputCR0 = true;
|
|
|
|
else
|
|
|
|
code[i].outputCR0 = (flags & FL_SET_CR0) ? true : false;
|
2008-12-08 05:30:24 +00:00
|
|
|
|
2010-07-29 12:17:47 +00:00
|
|
|
// Does the instruction output CR1?
|
|
|
|
if (flags & FL_RC_BIT_F)
|
|
|
|
code[i].outputCR1 = inst.hex & 1; //todo fix
|
|
|
|
else if ((flags & FL_SET_CRn) && inst.CRFD == 1)
|
|
|
|
code[i].outputCR1 = true;
|
|
|
|
else
|
|
|
|
code[i].outputCR1 = (flags & FL_SET_CR1) ? true : false;
|
2008-12-08 05:30:24 +00:00
|
|
|
|
2010-07-29 12:17:47 +00:00
|
|
|
int numOut = 0;
|
|
|
|
int numIn = 0;
|
|
|
|
if (flags & FL_OUT_A)
|
|
|
|
{
|
|
|
|
code[i].regsOut[numOut++] = inst.RA;
|
|
|
|
gpa->SetOutputRegister(inst.RA, i);
|
|
|
|
}
|
|
|
|
if (flags & FL_OUT_D)
|
|
|
|
{
|
|
|
|
code[i].regsOut[numOut++] = inst.RD;
|
|
|
|
gpa->SetOutputRegister(inst.RD, i);
|
|
|
|
}
|
|
|
|
if (flags & FL_OUT_S)
|
|
|
|
{
|
|
|
|
code[i].regsOut[numOut++] = inst.RS;
|
|
|
|
gpa->SetOutputRegister(inst.RS, i);
|
|
|
|
}
|
|
|
|
if ((flags & FL_IN_A) || ((flags & FL_IN_A0) && inst.RA != 0))
|
|
|
|
{
|
|
|
|
code[i].regsIn[numIn++] = inst.RA;
|
|
|
|
gpa->SetInputRegister(inst.RA, i);
|
|
|
|
}
|
|
|
|
if (flags & FL_IN_B)
|
|
|
|
{
|
|
|
|
code[i].regsIn[numIn++] = inst.RB;
|
|
|
|
gpa->SetInputRegister(inst.RB, i);
|
|
|
|
}
|
|
|
|
if (flags & FL_IN_C)
|
|
|
|
{
|
|
|
|
code[i].regsIn[numIn++] = inst.RC;
|
|
|
|
gpa->SetInputRegister(inst.RC, i);
|
|
|
|
}
|
|
|
|
if (flags & FL_IN_S)
|
|
|
|
{
|
|
|
|
code[i].regsIn[numIn++] = inst.RS;
|
|
|
|
gpa->SetInputRegister(inst.RS, i);
|
|
|
|
}
|
2008-12-14 23:22:56 +00:00
|
|
|
|
2010-07-29 12:17:47 +00:00
|
|
|
// Set remaining register slots as unused (-1)
|
|
|
|
for (int j = numIn; j < 3; j++)
|
|
|
|
code[i].regsIn[j] = -1;
|
|
|
|
for (int j = numOut; j < 2; j++)
|
|
|
|
code[i].regsOut[j] = -1;
|
|
|
|
for (int j = 0; j < 3; j++)
|
|
|
|
code[i].fregsIn[j] = -1;
|
|
|
|
code[i].fregOut = -1;
|
2010-07-25 19:01:32 +00:00
|
|
|
|
2013-10-29 01:23:17 -04:00
|
|
|
switch (opinfo->type)
|
2008-12-08 05:30:24 +00:00
|
|
|
{
|
2010-07-29 12:17:47 +00:00
|
|
|
case OPTYPE_INTEGER:
|
|
|
|
case OPTYPE_LOAD:
|
|
|
|
case OPTYPE_STORE:
|
|
|
|
case OPTYPE_LOADFP:
|
|
|
|
case OPTYPE_STOREFP:
|
|
|
|
break;
|
|
|
|
case OPTYPE_FPU:
|
|
|
|
break;
|
|
|
|
case OPTYPE_BRANCH:
|
|
|
|
if (code[i].inst.hex == 0x4e800020)
|
|
|
|
{
|
|
|
|
// For analysis purposes, we can assume that blr eats flags.
|
|
|
|
code[i].outputCR0 = true;
|
|
|
|
code[i].outputCR1 = true;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case OPTYPE_SYSTEM:
|
|
|
|
case OPTYPE_SYSTEMFP:
|
|
|
|
numSystemInstructions++;
|
|
|
|
break;
|
2008-12-08 05:30:24 +00:00
|
|
|
}
|
|
|
|
|
2010-07-29 12:17:47 +00:00
|
|
|
bool follow = false;
|
2010-09-16 04:22:27 +00:00
|
|
|
u32 destination = 0;
|
2010-07-29 12:17:47 +00:00
|
|
|
if (inst.OPCD == 18 && blockSize > 1)
|
|
|
|
{
|
|
|
|
//Is bx - should we inline? yes!
|
|
|
|
if (inst.AA)
|
|
|
|
destination = SignExt26(inst.LI << 2);
|
|
|
|
else
|
|
|
|
destination = address + SignExt26(inst.LI << 2);
|
|
|
|
if (destination != blockstart)
|
|
|
|
follow = true;
|
|
|
|
}
|
2010-09-23 02:05:04 +00:00
|
|
|
else if (inst.OPCD == 19 && inst.SUBOP10 == 16 &&
|
|
|
|
(inst.BO & (1 << 4)) && (inst.BO & (1 << 2)) &&
|
|
|
|
returnAddress != 0)
|
|
|
|
{
|
|
|
|
// bclrx with unconditional branch = return
|
|
|
|
follow = true;
|
|
|
|
destination = returnAddress;
|
|
|
|
returnAddress = 0;
|
|
|
|
|
|
|
|
if (inst.LK)
|
|
|
|
returnAddress = address + 4;
|
|
|
|
}
|
|
|
|
else if (inst.OPCD == 31 && inst.SUBOP10 == 467)
|
|
|
|
{
|
|
|
|
// mtspr
|
|
|
|
const u32 index = (inst.SPRU << 5) | (inst.SPRL & 0x1F);
|
|
|
|
if (index == SPR_LR) {
|
|
|
|
// We give up to follow the return address
|
|
|
|
// because we have to check the register usage.
|
|
|
|
returnAddress = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2010-07-29 12:17:47 +00:00
|
|
|
if (follow)
|
|
|
|
numFollows++;
|
2010-09-09 02:14:03 +00:00
|
|
|
// TODO: Find the optimal value for FUNCTION_FOLLOWING_THRESHOLD.
|
|
|
|
// If it is small, the performance will be down.
|
|
|
|
// If it is big, the size of generated code will be big and
|
|
|
|
// cache clearning will happen many times.
|
|
|
|
// TODO: Investivate the reason why
|
|
|
|
// "0" is fastest in some games, MP2 for example.
|
|
|
|
if (numFollows > FUNCTION_FOLLOWING_THRESHOLD)
|
2010-07-29 12:17:47 +00:00
|
|
|
follow = false;
|
2010-09-09 02:14:03 +00:00
|
|
|
|
2010-09-14 13:50:25 +00:00
|
|
|
if (!SConfig::GetInstance().m_LocalCoreStartupParameter.bMergeBlocks) {
|
|
|
|
follow = false;
|
|
|
|
}
|
|
|
|
|
2010-07-29 12:17:47 +00:00
|
|
|
if (!follow)
|
2010-07-25 19:01:32 +00:00
|
|
|
{
|
2010-07-29 12:17:47 +00:00
|
|
|
if (opinfo->flags & FL_ENDBLOCK) //right now we stop early
|
|
|
|
{
|
|
|
|
foundExit = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
address += 4;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2010-09-09 02:14:03 +00:00
|
|
|
// We don't "code[i].skip = true" here
|
|
|
|
// because bx may store a certain value to the link register.
|
|
|
|
// Instead, we skip a part of bx in Jit**::bx().
|
2010-07-29 12:17:47 +00:00
|
|
|
address = destination;
|
2010-09-22 02:42:17 +00:00
|
|
|
merged_addresses[size_of_merged_addresses++] = address;
|
2010-07-25 19:01:32 +00:00
|
|
|
}
|
2010-07-25 15:37:56 +00:00
|
|
|
}
|
2010-07-25 19:01:32 +00:00
|
|
|
else
|
|
|
|
{
|
2012-05-15 09:32:21 +10:00
|
|
|
// ISI exception or other critical memory exception occurred (game over)
|
2010-07-29 12:17:47 +00:00
|
|
|
break;
|
2010-07-25 19:01:32 +00:00
|
|
|
}
|
2010-07-29 12:17:47 +00:00
|
|
|
}
|
2010-07-25 19:01:32 +00:00
|
|
|
st->numCycles = numCycles;
|
2008-12-08 05:30:24 +00:00
|
|
|
|
2008-12-14 23:22:56 +00:00
|
|
|
// Instruction Reordering Pass
|
2010-07-31 02:29:11 +00:00
|
|
|
if (num_inst > 1)
|
2008-12-14 23:22:56 +00:00
|
|
|
{
|
2010-04-10 15:41:44 +00:00
|
|
|
// Bubble down compares towards branches, so that they can be merged.
|
|
|
|
// -2: -1 for the pair, -1 for not swapping with the final instruction which is probably the branch.
|
|
|
|
for (int i = 0; i < num_inst - 2; i++)
|
2008-12-14 23:22:56 +00:00
|
|
|
{
|
2010-04-10 15:41:44 +00:00
|
|
|
CodeOp &a = code[i];
|
|
|
|
CodeOp &b = code[i + 1];
|
|
|
|
// All integer compares can be reordered.
|
|
|
|
if ((a.inst.OPCD == 10 || a.inst.OPCD == 11) ||
|
|
|
|
(a.inst.OPCD == 31 && (a.inst.SUBOP10 == 0 || a.inst.SUBOP10 == 32)))
|
|
|
|
{
|
|
|
|
// Got a compare instruction.
|
|
|
|
if (CanSwapAdjacentOps(a, b)) {
|
|
|
|
// Alright, let's bubble it down!
|
|
|
|
CodeOp c = a;
|
|
|
|
a = b;
|
|
|
|
b = c;
|
|
|
|
}
|
2008-12-14 23:22:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2010-07-29 12:17:47 +00:00
|
|
|
|
2010-07-31 02:29:11 +00:00
|
|
|
if (!foundExit && num_inst > 0)
|
2010-07-29 12:17:47 +00:00
|
|
|
{
|
|
|
|
// A broken block is a block that does not end in a branch
|
|
|
|
broken_block = true;
|
|
|
|
}
|
2013-10-29 01:23:17 -04:00
|
|
|
|
2008-12-15 19:22:34 +00:00
|
|
|
// Scan for CR0 dependency
|
|
|
|
// assume next block wants CR0 to be safe
|
2008-12-08 05:30:24 +00:00
|
|
|
bool wantsCR0 = true;
|
|
|
|
bool wantsCR1 = true;
|
|
|
|
bool wantsPS1 = true;
|
2010-07-25 19:01:32 +00:00
|
|
|
for (int i = num_inst - 1; i >= 0; i--)
|
2008-12-08 05:30:24 +00:00
|
|
|
{
|
|
|
|
if (code[i].outputCR0)
|
|
|
|
wantsCR0 = false;
|
|
|
|
if (code[i].outputCR1)
|
|
|
|
wantsCR1 = false;
|
|
|
|
if (code[i].outputPS1)
|
|
|
|
wantsPS1 = false;
|
|
|
|
wantsCR0 |= code[i].wantsCR0;
|
|
|
|
wantsCR1 |= code[i].wantsCR1;
|
|
|
|
wantsPS1 |= code[i].wantsPS1;
|
|
|
|
code[i].wantsCR0 = wantsCR0;
|
|
|
|
code[i].wantsCR1 = wantsCR1;
|
|
|
|
code[i].wantsPS1 = wantsPS1;
|
|
|
|
}
|
|
|
|
|
2008-12-18 10:44:03 +00:00
|
|
|
*realsize = num_inst;
|
|
|
|
// ...
|
2010-04-10 15:41:44 +00:00
|
|
|
return address;
|
2008-12-08 05:30:24 +00:00
|
|
|
}
|
|
|
|
|
2008-12-18 10:44:03 +00:00
|
|
|
|
2008-12-08 05:30:24 +00:00
|
|
|
// Most functions that are relevant to analyze should be
|
2013-10-29 01:23:17 -04:00
|
|
|
// called by another function. Therefore, let's scan the
|
2008-12-08 05:30:24 +00:00
|
|
|
// entire space for bl operations and find what functions
|
2013-10-29 01:23:17 -04:00
|
|
|
// get called.
|
2008-12-08 05:30:24 +00:00
|
|
|
void FindFunctionsFromBranches(u32 startAddr, u32 endAddr, SymbolDB *func_db)
|
|
|
|
{
|
|
|
|
for (u32 addr = startAddr; addr < endAddr; addr+=4)
|
|
|
|
{
|
|
|
|
UGeckoInstruction instr = (UGeckoInstruction)Memory::ReadUnchecked_U32(addr);
|
|
|
|
|
|
|
|
if (PPCTables::IsValidInstruction(instr))
|
|
|
|
{
|
|
|
|
switch (instr.OPCD)
|
|
|
|
{
|
|
|
|
case 18://branch instruction
|
|
|
|
{
|
|
|
|
if (instr.LK) //bl
|
|
|
|
{
|
|
|
|
u32 target = SignExt26(instr.LI << 2);
|
|
|
|
if (!instr.AA)
|
|
|
|
target += addr;
|
|
|
|
if (Memory::IsRAMAddress(target))
|
|
|
|
{
|
|
|
|
func_db->AddFunction(target);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-06-21 08:39:21 +00:00
|
|
|
void FindFunctionsAfterBLR(PPCSymbolDB *func_db)
|
2008-12-08 05:30:24 +00:00
|
|
|
{
|
|
|
|
vector<u32> funcAddrs;
|
|
|
|
|
2014-02-12 16:00:34 +01:00
|
|
|
for (const auto& func : func_db->Symbols())
|
|
|
|
funcAddrs.push_back(func.second.address + func.second.size);
|
2008-12-08 05:30:24 +00:00
|
|
|
|
2014-02-12 16:00:34 +01:00
|
|
|
for (u32& location : funcAddrs)
|
2008-12-08 05:30:24 +00:00
|
|
|
{
|
|
|
|
while (true)
|
|
|
|
{
|
|
|
|
if (PPCTables::IsValidInstruction(Memory::Read_Instruction(location)))
|
|
|
|
{
|
2013-10-29 01:23:17 -04:00
|
|
|
//check if this function is already mapped
|
2008-12-08 05:30:24 +00:00
|
|
|
Symbol *f = func_db->AddFunction(location);
|
|
|
|
if (!f)
|
|
|
|
break;
|
|
|
|
else
|
|
|
|
location += f->size;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2009-06-21 08:39:21 +00:00
|
|
|
void FindFunctions(u32 startAddr, u32 endAddr, PPCSymbolDB *func_db)
|
2008-12-08 05:30:24 +00:00
|
|
|
{
|
|
|
|
//Step 1: Find all functions
|
|
|
|
FindFunctionsFromBranches(startAddr, endAddr, func_db);
|
|
|
|
FindFunctionsAfterBLR(func_db);
|
|
|
|
|
2013-10-29 01:23:17 -04:00
|
|
|
//Step 2:
|
2008-12-08 05:30:24 +00:00
|
|
|
func_db->FillInCallers();
|
|
|
|
|
|
|
|
int numLeafs = 0, numNice = 0, numUnNice = 0;
|
|
|
|
int numTimer = 0, numRFI = 0, numStraightLeaf = 0;
|
|
|
|
int leafSize = 0, niceSize = 0, unniceSize = 0;
|
2014-02-12 16:00:34 +01:00
|
|
|
for (auto& func : func_db->AccessSymbols())
|
2008-12-08 05:30:24 +00:00
|
|
|
{
|
2014-02-12 16:00:34 +01:00
|
|
|
if (func.second.address == 4)
|
2008-12-08 05:30:24 +00:00
|
|
|
{
|
2013-03-31 19:10:21 -04:00
|
|
|
WARN_LOG(OSHLE, "Weird function");
|
2008-12-08 05:30:24 +00:00
|
|
|
continue;
|
|
|
|
}
|
2014-02-12 16:00:34 +01:00
|
|
|
AnalyzeFunction2(&(func.second));
|
|
|
|
Symbol &f = func.second;
|
2008-12-18 10:44:03 +00:00
|
|
|
if (f.name.substr(0, 3) == "zzz")
|
2008-12-08 05:30:24 +00:00
|
|
|
{
|
|
|
|
if (f.flags & FFLAG_LEAF)
|
|
|
|
f.name += "_leaf";
|
|
|
|
if (f.flags & FFLAG_STRAIGHT)
|
|
|
|
f.name += "_straight";
|
|
|
|
}
|
|
|
|
if (f.flags & FFLAG_LEAF)
|
|
|
|
{
|
|
|
|
numLeafs++;
|
|
|
|
leafSize += f.size;
|
|
|
|
}
|
|
|
|
else if (f.flags & FFLAG_ONLYCALLSNICELEAFS)
|
|
|
|
{
|
|
|
|
numNice++;
|
|
|
|
niceSize += f.size;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
numUnNice++;
|
|
|
|
unniceSize += f.size;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (f.flags & FFLAG_TIMERINSTRUCTIONS)
|
|
|
|
numTimer++;
|
|
|
|
if (f.flags & FFLAG_RFI)
|
|
|
|
numRFI++;
|
|
|
|
if ((f.flags & FFLAG_STRAIGHT) && (f.flags & FFLAG_LEAF))
|
|
|
|
numStraightLeaf++;
|
|
|
|
}
|
2013-10-29 01:23:17 -04:00
|
|
|
if (numLeafs == 0)
|
2008-12-08 05:30:24 +00:00
|
|
|
leafSize = 0;
|
|
|
|
else
|
|
|
|
leafSize /= numLeafs;
|
|
|
|
|
|
|
|
if (numNice == 0)
|
|
|
|
niceSize = 0;
|
|
|
|
else
|
2013-04-16 23:14:36 -04:00
|
|
|
niceSize /= numNice;
|
2008-12-08 05:30:24 +00:00
|
|
|
|
|
|
|
if (numUnNice == 0)
|
|
|
|
unniceSize = 0;
|
|
|
|
else
|
|
|
|
unniceSize /= numUnNice;
|
|
|
|
|
2010-12-05 15:59:11 +00:00
|
|
|
INFO_LOG(OSHLE, "Functions analyzed. %i leafs, %i nice, %i unnice."
|
|
|
|
"%i timer, %i rfi. %i are branchless leafs.", numLeafs,
|
|
|
|
numNice, numUnNice, numTimer, numRFI, numStraightLeaf);
|
|
|
|
INFO_LOG(OSHLE, "Average size: %i (leaf), %i (nice), %i(unnice)",
|
|
|
|
leafSize, niceSize, unniceSize);
|
2008-12-08 05:30:24 +00:00
|
|
|
}
|
|
|
|
|
2014-04-30 00:14:24 -05:00
|
|
|
void PPCAnalyser::ReorderInstructions(u32 instructions, CodeOp *code)
|
|
|
|
{
|
|
|
|
// Instruction Reordering Pass
|
|
|
|
// Bubble down compares towards branches, so that they can be merged.
|
|
|
|
// -2: -1 for the pair, -1 for not swapping with the final instruction which is probably the branch.
|
|
|
|
for (u32 i = 0; i < (instructions - 2); ++i)
|
|
|
|
{
|
|
|
|
CodeOp &a = code[i];
|
|
|
|
CodeOp &b = code[i + 1];
|
|
|
|
// All integer compares can be reordered.
|
|
|
|
if ((a.inst.OPCD == 10 || a.inst.OPCD == 11) ||
|
|
|
|
(a.inst.OPCD == 31 && (a.inst.SUBOP10 == 0 || a.inst.SUBOP10 == 32)))
|
|
|
|
{
|
|
|
|
// Got a compare instruction.
|
|
|
|
if (CanSwapAdjacentOps(a, b)) {
|
|
|
|
// Alright, let's bubble it down!
|
|
|
|
CodeOp c = a;
|
|
|
|
a = b;
|
|
|
|
b = c;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void PPCAnalyser::SetInstructionStats(CodeBlock *block, CodeOp *code, GekkoOPInfo *opinfo, u32 index)
|
|
|
|
{
|
|
|
|
code->wantsCR0 = false;
|
|
|
|
code->wantsCR1 = false;
|
|
|
|
code->wantsPS1 = false;
|
|
|
|
|
|
|
|
if (opinfo->flags & FL_USE_FPU)
|
|
|
|
block->m_fpa->any = true;
|
|
|
|
|
|
|
|
if (opinfo->flags & FL_TIMER)
|
|
|
|
block->m_gpa->anyTimer = true;
|
|
|
|
|
|
|
|
// Does the instruction output CR0?
|
|
|
|
if (opinfo->flags & FL_RC_BIT)
|
|
|
|
code->outputCR0 = code->inst.hex & 1; //todo fix
|
|
|
|
else if ((opinfo->flags & FL_SET_CRn) && code->inst.CRFD == 0)
|
|
|
|
code->outputCR0 = true;
|
|
|
|
else
|
|
|
|
code->outputCR0 = (opinfo->flags & FL_SET_CR0) ? true : false;
|
|
|
|
|
|
|
|
// Does the instruction output CR1?
|
|
|
|
if (opinfo->flags & FL_RC_BIT_F)
|
|
|
|
code->outputCR1 = code->inst.hex & 1; //todo fix
|
|
|
|
else if ((opinfo->flags & FL_SET_CRn) && code->inst.CRFD == 1)
|
|
|
|
code->outputCR1 = true;
|
|
|
|
else
|
|
|
|
code->outputCR1 = (opinfo->flags & FL_SET_CR1) ? true : false;
|
|
|
|
|
|
|
|
int numOut = 0;
|
|
|
|
int numIn = 0;
|
|
|
|
if (opinfo->flags & FL_OUT_A)
|
|
|
|
{
|
|
|
|
code->regsOut[numOut++] = code->inst.RA;
|
|
|
|
block->m_gpa->SetOutputRegister(code->inst.RA, index);
|
|
|
|
}
|
|
|
|
if (opinfo->flags & FL_OUT_D)
|
|
|
|
{
|
|
|
|
code->regsOut[numOut++] = code->inst.RD;
|
|
|
|
block->m_gpa->SetOutputRegister(code->inst.RD, index);
|
|
|
|
}
|
|
|
|
if (opinfo->flags & FL_OUT_S)
|
|
|
|
{
|
|
|
|
code->regsOut[numOut++] = code->inst.RS;
|
|
|
|
block->m_gpa->SetOutputRegister(code->inst.RS, index);
|
|
|
|
}
|
|
|
|
if ((opinfo->flags & FL_IN_A) || ((opinfo->flags & FL_IN_A0) && code->inst.RA != 0))
|
|
|
|
{
|
|
|
|
code->regsIn[numIn++] = code->inst.RA;
|
|
|
|
block->m_gpa->SetInputRegister(code->inst.RA, index);
|
|
|
|
}
|
|
|
|
if (opinfo->flags & FL_IN_B)
|
|
|
|
{
|
|
|
|
code->regsIn[numIn++] = code->inst.RB;
|
|
|
|
block->m_gpa->SetInputRegister(code->inst.RB, index);
|
|
|
|
}
|
|
|
|
if (opinfo->flags & FL_IN_C)
|
|
|
|
{
|
|
|
|
code->regsIn[numIn++] = code->inst.RC;
|
|
|
|
block->m_gpa->SetInputRegister(code->inst.RC, index);
|
|
|
|
}
|
|
|
|
if (opinfo->flags & FL_IN_S)
|
|
|
|
{
|
|
|
|
code->regsIn[numIn++] = code->inst.RS;
|
|
|
|
block->m_gpa->SetInputRegister(code->inst.RS, index);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set remaining register slots as unused (-1)
|
|
|
|
for (int j = numIn; j < 3; j++)
|
|
|
|
code->regsIn[j] = -1;
|
|
|
|
for (int j = numOut; j < 2; j++)
|
|
|
|
code->regsOut[j] = -1;
|
|
|
|
for (int j = 0; j < 3; j++)
|
|
|
|
code->fregsIn[j] = -1;
|
|
|
|
code->fregOut = -1;
|
|
|
|
|
|
|
|
switch (opinfo->type)
|
|
|
|
{
|
|
|
|
case OPTYPE_INTEGER:
|
|
|
|
case OPTYPE_LOAD:
|
|
|
|
case OPTYPE_STORE:
|
|
|
|
case OPTYPE_LOADFP:
|
|
|
|
case OPTYPE_STOREFP:
|
|
|
|
break;
|
|
|
|
case OPTYPE_FPU:
|
|
|
|
break;
|
|
|
|
case OPTYPE_BRANCH:
|
|
|
|
if (code->inst.hex == 0x4e800020)
|
|
|
|
{
|
|
|
|
// For analysis purposes, we can assume that blr eats opinfo->flags.
|
|
|
|
code->outputCR0 = true;
|
|
|
|
code->outputCR1 = true;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case OPTYPE_SYSTEM:
|
|
|
|
case OPTYPE_SYSTEMFP:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
u32 PPCAnalyser::Analyse(u32 address, CodeBlock *block, CodeBuffer *buffer, u32 blockSize)
|
|
|
|
{
|
|
|
|
// Clear block stats
|
|
|
|
memset(block->m_stats, 0, sizeof(BlockStats));
|
|
|
|
|
|
|
|
// Clear register stats
|
|
|
|
block->m_gpa->any = true;
|
|
|
|
block->m_fpa->any = false;
|
|
|
|
|
|
|
|
block->m_gpa->Clear();
|
|
|
|
block->m_fpa->Clear();
|
|
|
|
|
|
|
|
// Set the blocks start address
|
|
|
|
block->m_address = address;
|
|
|
|
|
|
|
|
// Reset our block state
|
|
|
|
block->m_broken = false;
|
|
|
|
block->m_instructions = 0;
|
|
|
|
|
|
|
|
CodeOp *code = buffer->codebuffer;
|
|
|
|
|
|
|
|
bool found_exit = false;
|
|
|
|
u32 returnAddress = 0;
|
|
|
|
u32 numFollows = 0;
|
|
|
|
u32 num_inst = 0;
|
|
|
|
|
|
|
|
for (u32 i = 0; i < blockSize; ++i)
|
|
|
|
{
|
|
|
|
UGeckoInstruction inst = JitInterface::Read_Opcode_JIT(address);
|
|
|
|
|
|
|
|
if (inst.hex != 0)
|
|
|
|
{
|
|
|
|
num_inst++;
|
|
|
|
memset(&code[i], 0, sizeof(CodeOp));
|
|
|
|
GekkoOPInfo *opinfo = GetOpInfo(inst);
|
|
|
|
|
|
|
|
code[i].opinfo = opinfo;
|
|
|
|
code[i].address = address;
|
|
|
|
code[i].inst = inst;
|
|
|
|
code[i].branchTo = -1;
|
|
|
|
code[i].branchToIndex = -1;
|
|
|
|
code[i].skip = false;
|
|
|
|
block->m_stats->numCycles += opinfo->numCycles;
|
|
|
|
|
|
|
|
SetInstructionStats(block, &code[i], opinfo, i);
|
|
|
|
|
|
|
|
bool follow = false;
|
|
|
|
u32 destination = 0;
|
|
|
|
|
|
|
|
bool conditional_continue = false;
|
|
|
|
|
|
|
|
// Do we inline leaf functions?
|
|
|
|
if (HasOption(OPTION_LEAF_INLINE))
|
|
|
|
{
|
|
|
|
if (inst.OPCD == 18 && blockSize > 1)
|
|
|
|
{
|
|
|
|
//Is bx - should we inline? yes!
|
|
|
|
if (inst.AA)
|
|
|
|
destination = SignExt26(inst.LI << 2);
|
|
|
|
else
|
|
|
|
destination = address + SignExt26(inst.LI << 2);
|
|
|
|
if (destination != block->m_address)
|
|
|
|
follow = true;
|
|
|
|
}
|
|
|
|
else if (inst.OPCD == 19 && inst.SUBOP10 == 16 &&
|
|
|
|
(inst.BO & (1 << 4)) && (inst.BO & (1 << 2)) &&
|
|
|
|
returnAddress != 0)
|
|
|
|
{
|
|
|
|
// bclrx with unconditional branch = return
|
|
|
|
follow = true;
|
|
|
|
destination = returnAddress;
|
|
|
|
returnAddress = 0;
|
|
|
|
|
|
|
|
if (inst.LK)
|
|
|
|
returnAddress = address + 4;
|
|
|
|
}
|
|
|
|
else if (inst.OPCD == 31 && inst.SUBOP10 == 467)
|
|
|
|
{
|
|
|
|
// mtspr
|
|
|
|
const u32 index = (inst.SPRU << 5) | (inst.SPRL & 0x1F);
|
|
|
|
if (index == SPR_LR) {
|
|
|
|
// We give up to follow the return address
|
|
|
|
// because we have to check the register usage.
|
|
|
|
returnAddress = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: Find the optimal value for FUNCTION_FOLLOWING_THRESHOLD.
|
|
|
|
// If it is small, the performance will be down.
|
|
|
|
// If it is big, the size of generated code will be big and
|
|
|
|
// cache clearning will happen many times.
|
|
|
|
// TODO: Investivate the reason why
|
|
|
|
// "0" is fastest in some games, MP2 for example.
|
|
|
|
if (numFollows > FUNCTION_FOLLOWING_THRESHOLD)
|
|
|
|
follow = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (HasOption(OPTION_CONDITIONAL_CONTINUE))
|
|
|
|
{
|
|
|
|
if (inst.OPCD == 16 &&
|
|
|
|
((inst.BO & BO_DONT_DECREMENT_FLAG) == 0 || (inst.BO & BO_DONT_CHECK_CONDITION) == 0))
|
|
|
|
{
|
|
|
|
// bcx with conditional branch
|
|
|
|
conditional_continue = true;
|
|
|
|
}
|
|
|
|
else if (inst.OPCD == 19 && inst.SUBOP10 == 16 &&
|
|
|
|
((inst.BO & BO_DONT_DECREMENT_FLAG) == 0 || (inst.BO & BO_DONT_CHECK_CONDITION) == 0))
|
|
|
|
{
|
|
|
|
// bclrx with conditional branch
|
|
|
|
conditional_continue = true;
|
|
|
|
}
|
|
|
|
else if (inst.OPCD == 3 ||
|
|
|
|
(inst.OPCD == 31 && inst.SUBOP10 == 4))
|
|
|
|
{
|
|
|
|
// tw/twi tests and raises an exception
|
|
|
|
conditional_continue = true;
|
|
|
|
}
|
|
|
|
else if (inst.OPCD == 19 && inst.SUBOP10 == 528 &&
|
|
|
|
(inst.BO_2 & BO_DONT_CHECK_CONDITION) == 0)
|
|
|
|
{
|
|
|
|
// Rare bcctrx with conditional branch
|
|
|
|
// Seen in NES games
|
|
|
|
conditional_continue = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!follow)
|
|
|
|
{
|
|
|
|
if (!conditional_continue && opinfo->flags & FL_ENDBLOCK) //right now we stop early
|
|
|
|
{
|
|
|
|
found_exit = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
address += 4;
|
|
|
|
}
|
|
|
|
// XXX: We don't support inlining yet.
|
|
|
|
#if 0
|
|
|
|
else
|
|
|
|
{
|
|
|
|
numFollows++;
|
|
|
|
// We don't "code[i].skip = true" here
|
|
|
|
// because bx may store a certain value to the link register.
|
|
|
|
// Instead, we skip a part of bx in Jit**::bx().
|
|
|
|
address = destination;
|
|
|
|
merged_addresses[size_of_merged_addresses++] = address;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// ISI exception or other critical memory exception occured (game over)
|
|
|
|
ERROR_LOG(DYNA_REC, "Instruction hex was 0!");
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (block->m_instructions > 1)
|
|
|
|
ReorderInstructions(block->m_instructions, code);
|
|
|
|
|
|
|
|
if ((!found_exit && num_inst > 0) || blockSize == 1)
|
|
|
|
{
|
|
|
|
// We couldn't find an exit
|
|
|
|
block->m_broken = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Scan for CR0 dependency
|
|
|
|
// assume next block wants CR0 to be safe
|
|
|
|
bool wantsCR0 = true;
|
|
|
|
bool wantsCR1 = true;
|
|
|
|
bool wantsPS1 = true;
|
|
|
|
for (int i = block->m_instructions - 1; i >= 0; i--)
|
|
|
|
{
|
|
|
|
if (code[i].outputCR0)
|
|
|
|
wantsCR0 = false;
|
|
|
|
if (code[i].outputCR1)
|
|
|
|
wantsCR1 = false;
|
|
|
|
if (code[i].outputPS1)
|
|
|
|
wantsPS1 = false;
|
|
|
|
wantsCR0 |= code[i].wantsCR0;
|
|
|
|
wantsCR1 |= code[i].wantsCR1;
|
|
|
|
wantsPS1 |= code[i].wantsPS1;
|
|
|
|
code[i].wantsCR0 = wantsCR0;
|
|
|
|
code[i].wantsCR1 = wantsCR1;
|
|
|
|
code[i].wantsPS1 = wantsPS1;
|
|
|
|
}
|
|
|
|
block->m_instructions = num_inst;
|
|
|
|
return address;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2008-12-08 05:30:24 +00:00
|
|
|
} // namespace
|