2015-05-24 06:55:12 +02:00
|
|
|
// Copyright 2008 Dolphin Emulator Project
|
2021-07-05 03:22:19 +02:00
|
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
2008-12-08 05:30:24 +00:00
|
|
|
|
2017-06-08 00:53:59 -04:00
|
|
|
#include "Core/PowerPC/PPCAnalyst.h"
|
|
|
|
|
2014-08-30 18:32:09 -04:00
|
|
|
#include <algorithm>
|
2017-06-06 23:36:16 -04:00
|
|
|
#include <map>
|
2010-09-26 06:58:21 +00:00
|
|
|
#include <queue>
|
2014-02-17 05:18:15 -05:00
|
|
|
#include <string>
|
2017-06-06 23:36:16 -04:00
|
|
|
#include <vector>
|
2008-12-08 05:30:24 +00:00
|
|
|
|
2019-10-20 07:35:11 -04:00
|
|
|
#include <fmt/format.h>
|
|
|
|
|
2017-05-03 20:43:29 +01:00
|
|
|
#include "Common/Assert.h"
|
2016-01-10 04:04:28 -05:00
|
|
|
#include "Common/CommonTypes.h"
|
2016-10-07 22:55:13 +02:00
|
|
|
#include "Common/Logging/Log.h"
|
2014-02-17 05:18:15 -05:00
|
|
|
#include "Common/StringUtil.h"
|
2021-12-31 03:00:39 +01:00
|
|
|
#include "Core/Config/MainSettings.h"
|
2014-02-17 05:18:15 -05:00
|
|
|
#include "Core/ConfigManager.h"
|
2018-06-09 08:13:49 -04:00
|
|
|
#include "Core/PowerPC/JitCommon/JitBase.h"
|
2018-05-17 17:09:55 -04:00
|
|
|
#include "Core/PowerPC/MMU.h"
|
2014-02-17 05:18:15 -05:00
|
|
|
#include "Core/PowerPC/PPCSymbolDB.h"
|
|
|
|
#include "Core/PowerPC/PPCTables.h"
|
|
|
|
#include "Core/PowerPC/PowerPC.h"
|
2016-12-06 15:43:41 +00:00
|
|
|
#include "Core/PowerPC/SignatureDB/SignatureDB.h"
|
2008-12-08 05:30:24 +00:00
|
|
|
|
2008-07-12 17:40:22 +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.
|
2008-12-08 05:30:24 +00:00
|
|
|
|
2008-10-16 22:06:06 +00:00
|
|
|
// The results of this analysis is displayed in the code browsing sections at the bottom left
|
|
|
|
// of the disassembly window (debugger).
|
2008-12-08 05:30:24 +00:00
|
|
|
|
2008-10-16 22:06:06 +00:00
|
|
|
// 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.
|
2008-12-08 05:30:24 +00:00
|
|
|
|
2014-08-09 22:44:27 -04:00
|
|
|
namespace PPCAnalyst
|
|
|
|
{
|
2010-09-09 02:14:03 +00:00
|
|
|
// 0 does not perform block merging
|
2017-01-22 19:13:29 +01:00
|
|
|
constexpr u32 BRANCH_FOLLOWING_THRESHOLD = 2;
|
2008-12-08 05:30:24 +00:00
|
|
|
|
2016-09-26 19:58:21 -04:00
|
|
|
constexpr u32 INVALID_BRANCH_TARGET = 0xFFFFFFFF;
|
|
|
|
|
2015-08-09 11:15:44 +02:00
|
|
|
static u32 EvaluateBranchTarget(UGeckoInstruction instr, u32 pc)
|
2008-07-12 17:40:22 +00:00
|
|
|
{
|
|
|
|
switch (instr.OPCD)
|
|
|
|
{
|
2017-05-07 03:39:39 +01:00
|
|
|
case 16: // bcx - Branch Conditional instructions
|
|
|
|
{
|
|
|
|
u32 target = SignExt16(instr.BD << 2);
|
|
|
|
if (!instr.AA)
|
|
|
|
target += pc;
|
|
|
|
|
|
|
|
return target;
|
|
|
|
}
|
|
|
|
case 18: // bx - Branch instructions
|
2008-07-12 17:40:22 +00:00
|
|
|
{
|
|
|
|
u32 target = SignExt26(instr.LI << 2);
|
|
|
|
if (!instr.AA)
|
|
|
|
target += pc;
|
2016-06-24 10:43:46 +02:00
|
|
|
|
2008-07-12 17:40:22 +00:00
|
|
|
return target;
|
|
|
|
}
|
|
|
|
default:
|
2016-09-26 19:58:21 -04:00
|
|
|
return INVALID_BRANCH_TARGET;
|
2008-07-12 17:40:22 +00:00
|
|
|
}
|
|
|
|
}
|
2008-12-08 05:30:24 +00:00
|
|
|
|
2008-07-12 17:40:22 +00:00
|
|
|
// To find the size of each found function, scan
|
2017-04-10 17:25:33 +01:00
|
|
|
// forward until we hit blr or rfi. In the meantime, collect information
|
2008-07-12 17:40:22 +00:00
|
|
|
// about which functions this function calls.
|
2017-04-10 17:25:33 +01:00
|
|
|
// Also collect which internal branch goes the farthest.
|
|
|
|
// If any one goes farther than the blr or rfi, assume that there is more than
|
|
|
|
// one blr or rfi, and keep scanning.
|
2018-05-27 17:37:52 -04:00
|
|
|
bool AnalyzeFunction(u32 startAddr, Common::Symbol& func, u32 max_size)
|
2008-07-12 17:40:22 +00:00
|
|
|
{
|
2017-08-16 02:40:24 +01:00
|
|
|
if (func.name.empty())
|
2019-10-20 07:35:11 -04:00
|
|
|
func.Rename(fmt::format("zz_{:08x}_", startAddr));
|
2016-09-13 21:16:04 -04:00
|
|
|
if (func.analyzed)
|
2008-08-24 15:46:08 +00:00
|
|
|
return true; // No error, just already did it.
|
2016-06-24 10:43:46 +02:00
|
|
|
|
2008-07-12 17:40:22 +00:00
|
|
|
func.calls.clear();
|
|
|
|
func.callers.clear();
|
|
|
|
func.size = 0;
|
2018-05-27 17:37:52 -04:00
|
|
|
func.flags = Common::FFLAG_LEAF;
|
2016-06-24 10:43:46 +02:00
|
|
|
|
2008-07-12 17:40:22 +00:00
|
|
|
u32 farthestInternalBranchTarget = startAddr;
|
|
|
|
int numInternalBranches = 0;
|
2017-05-07 03:39:39 +01:00
|
|
|
for (u32 addr = startAddr; true; addr += 4)
|
2008-07-12 17:40:22 +00:00
|
|
|
{
|
2008-08-30 12:11:25 +00:00
|
|
|
func.size += 4;
|
2018-06-09 08:13:49 -04:00
|
|
|
if (func.size >= JitBase::code_buffer_size * 4 || !PowerPC::HostIsInstructionRAMAddress(addr))
|
2008-08-24 15:46:08 +00:00
|
|
|
return false;
|
2016-06-24 10:43:46 +02:00
|
|
|
|
2008-08-24 21:30:59 +00:00
|
|
|
if (max_size && func.size > max_size)
|
|
|
|
{
|
|
|
|
func.address = startAddr;
|
2016-09-13 21:16:04 -04:00
|
|
|
func.analyzed = true;
|
2017-05-07 04:34:02 +01:00
|
|
|
func.size -= 4;
|
|
|
|
func.hash = HashSignatureDB::ComputeCodeChecksum(startAddr, addr - 4);
|
2008-08-24 21:30:59 +00:00
|
|
|
if (numInternalBranches == 0)
|
2018-05-27 17:37:52 -04:00
|
|
|
func.flags |= Common::FFLAG_STRAIGHT;
|
2008-08-24 21:30:59 +00:00
|
|
|
return true;
|
|
|
|
}
|
2017-03-28 17:45:17 +01:00
|
|
|
const PowerPC::TryReadInstResult read_result = PowerPC::TryReadInstruction(addr);
|
|
|
|
const UGeckoInstruction instr = read_result.hex;
|
|
|
|
if (read_result.valid && PPCTables::IsValidInstruction(instr))
|
2008-07-12 17:40:22 +00:00
|
|
|
{
|
2017-04-10 17:25:33 +01:00
|
|
|
// BLR or RFI
|
|
|
|
// 4e800021 is blrl, not the end of a function
|
|
|
|
if (instr.hex == 0x4e800020 || instr.hex == 0x4C000064)
|
2008-07-12 17:40:22 +00:00
|
|
|
{
|
2017-05-07 03:39:39 +01:00
|
|
|
// Not this one, continue..
|
2008-07-12 17:40:22 +00:00
|
|
|
if (farthestInternalBranchTarget > addr)
|
2017-05-07 03:39:39 +01:00
|
|
|
continue;
|
|
|
|
|
|
|
|
// 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 = true;
|
|
|
|
func.hash = HashSignatureDB::ComputeCodeChecksum(startAddr, addr);
|
|
|
|
if (numInternalBranches == 0)
|
2018-05-27 17:37:52 -04:00
|
|
|
func.flags |= Common::FFLAG_STRAIGHT;
|
2017-05-07 03:39:39 +01:00
|
|
|
return true;
|
2008-07-12 17:40:22 +00:00
|
|
|
}
|
|
|
|
else if (instr.hex == 0x4e800021 || instr.hex == 0x4e800420 || instr.hex == 0x4e800421)
|
|
|
|
{
|
2018-05-27 17:37:52 -04:00
|
|
|
func.flags &= ~Common::FFLAG_LEAF;
|
|
|
|
func.flags |= Common::FFLAG_EVIL;
|
2008-07-12 17:40:22 +00:00
|
|
|
}
|
|
|
|
else if (instr.hex == 0x4c000064)
|
|
|
|
{
|
2018-05-27 17:37:52 -04:00
|
|
|
func.flags &= ~Common::FFLAG_LEAF;
|
|
|
|
func.flags |= Common::FFLAG_RFI;
|
2008-07-12 17:40:22 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2017-05-07 03:39:39 +01:00
|
|
|
u32 target = EvaluateBranchTarget(instr, addr);
|
|
|
|
if (target == INVALID_BRANCH_TARGET)
|
|
|
|
continue;
|
2016-06-24 10:43:46 +02:00
|
|
|
|
2017-05-07 03:51:26 +01:00
|
|
|
const bool is_external = target < startAddr || (max_size && target >= startAddr + max_size);
|
|
|
|
if (instr.LK || is_external)
|
2017-05-07 03:39:39 +01:00
|
|
|
{
|
2017-05-07 03:51:26 +01:00
|
|
|
// Found a function call
|
2017-05-07 03:39:39 +01:00
|
|
|
func.calls.emplace_back(target, addr);
|
2018-05-27 17:37:52 -04:00
|
|
|
func.flags &= ~Common::FFLAG_LEAF;
|
2008-07-12 17:40:22 +00:00
|
|
|
}
|
2017-05-07 03:39:39 +01:00
|
|
|
else if (instr.OPCD == 16)
|
2008-07-12 17:40:22 +00:00
|
|
|
{
|
2017-05-07 03:39:39 +01:00
|
|
|
// Found a conditional branch
|
|
|
|
if (target > farthestInternalBranchTarget)
|
2008-07-12 17:40:22 +00:00
|
|
|
{
|
2017-05-07 03:39:39 +01:00
|
|
|
farthestInternalBranchTarget = target;
|
2008-07-12 17:40:22 +00:00
|
|
|
}
|
2017-05-07 03:39:39 +01:00
|
|
|
numInternalBranches++;
|
2008-07-12 17:40:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2008-08-24 15:46:08 +00:00
|
|
|
return false;
|
2008-07-12 17:40:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2008-12-08 05:30:24 +00:00
|
|
|
|
2018-05-27 17:37:52 -04:00
|
|
|
bool ReanalyzeFunction(u32 start_addr, Common::Symbol& func, u32 max_size)
|
2017-05-03 20:43:29 +01:00
|
|
|
{
|
2018-03-22 03:18:25 -04:00
|
|
|
ASSERT_MSG(SYMBOLS, func.analyzed, "The function wasn't previously analyzed!");
|
2017-05-03 20:43:29 +01:00
|
|
|
|
|
|
|
func.analyzed = false;
|
|
|
|
return AnalyzeFunction(start_addr, func, max_size);
|
|
|
|
}
|
|
|
|
|
2008-07-12 17:40:22 +00:00
|
|
|
// Second pass analysis, done after the first pass is done for all functions
|
|
|
|
// so we have more information to work with
|
2018-05-27 17:37:52 -04:00
|
|
|
static void AnalyzeFunction2(Common::Symbol* func)
|
2008-07-12 17:40:22 +00:00
|
|
|
{
|
2008-12-18 10:44:03 +00:00
|
|
|
u32 flags = func->flags;
|
2016-06-24 10:43:46 +02:00
|
|
|
|
2016-09-28 17:12:21 -04:00
|
|
|
bool nonleafcall = std::any_of(func->calls.begin(), func->calls.end(), [](const auto& call) {
|
2018-05-27 17:37:52 -04:00
|
|
|
const Common::Symbol* called_func = g_symbolDB.GetSymbolFromAddr(call.function);
|
|
|
|
return called_func && (called_func->flags & Common::FFLAG_LEAF) == 0;
|
2016-09-28 17:12:21 -04:00
|
|
|
});
|
2016-06-24 10:43:46 +02:00
|
|
|
|
2018-05-27 17:37:52 -04:00
|
|
|
if (nonleafcall && !(flags & Common::FFLAG_EVIL) && !(flags & Common::FFLAG_RFI))
|
|
|
|
flags |= Common::FFLAG_ONLYCALLSNICELEAFS;
|
2016-06-24 10:43:46 +02:00
|
|
|
|
2008-12-18 10:44:03 +00:00
|
|
|
func->flags = flags;
|
2008-07-12 17:40:22 +00:00
|
|
|
}
|
2008-12-08 05:30:24 +00:00
|
|
|
|
2021-12-31 17:46:45 +01:00
|
|
|
bool PPCAnalyzer::CanSwapAdjacentOps(const CodeOp& a, const CodeOp& b) const
|
2008-12-14 23:22:56 +00:00
|
|
|
{
|
2014-09-07 08:30:11 -07:00
|
|
|
const GekkoOPInfo* a_info = a.opinfo;
|
2010-07-23 19:30:00 +00:00
|
|
|
const GekkoOPInfo* b_info = b.opinfo;
|
2021-03-24 18:54:46 +01:00
|
|
|
u64 a_flags = a_info->flags;
|
|
|
|
u64 b_flags = b_info->flags;
|
2016-06-24 10:43:46 +02:00
|
|
|
|
2015-01-03 22:59:28 -08:00
|
|
|
// can't reorder around breakpoints
|
2021-12-31 03:00:39 +01:00
|
|
|
if (m_is_debugging_enabled && (PowerPC::breakpoints.IsAddressBreakPoint(a.address) ||
|
|
|
|
PowerPC::breakpoints.IsAddressBreakPoint(b.address)))
|
|
|
|
{
|
2015-01-03 22:59:28 -08:00
|
|
|
return false;
|
2021-12-31 03:00:39 +01:00
|
|
|
}
|
2021-06-29 15:54:50 +02:00
|
|
|
// 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://bugs.dolphin-emu.org/issues/5864#note-7
|
|
|
|
if (a.canCauseException || b.canCauseException)
|
|
|
|
return false;
|
|
|
|
if (a_flags & FL_ENDBLOCK)
|
|
|
|
return false;
|
2014-09-07 08:30:11 -07:00
|
|
|
if (b_flags & (FL_SET_CRx | FL_ENDBLOCK | FL_TIMER | FL_EVIL | FL_SET_OE))
|
2008-12-14 23:22:56 +00:00
|
|
|
return false;
|
2014-09-07 08:30:11 -07:00
|
|
|
if ((b_flags & (FL_RC_BIT | FL_RC_BIT_F)) && (b.inst.Rc))
|
|
|
|
return false;
|
|
|
|
if ((a_flags & (FL_SET_CA | FL_READ_CA)) && (b_flags & (FL_SET_CA | FL_READ_CA)))
|
2008-12-14 23:22:56 +00:00
|
|
|
return false;
|
2016-06-24 10:43:46 +02:00
|
|
|
|
2008-12-14 23:22:56 +00:00
|
|
|
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;
|
|
|
|
}
|
2016-06-24 10:43:46 +02:00
|
|
|
|
2021-06-29 15:54:50 +02:00
|
|
|
// For now, only integer ops are acceptable.
|
2018-03-18 18:16:26 -04:00
|
|
|
if (b_info->type != OpType::Integer)
|
2008-12-14 23:22:56 +00:00
|
|
|
return false;
|
2016-06-24 10:43:46 +02:00
|
|
|
|
2008-12-14 23:22:56 +00:00
|
|
|
// 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.
|
2014-10-16 21:49:48 -04:00
|
|
|
// register collision: b outputs to one of a's inputs
|
|
|
|
if (b.regsOut & a.regsIn)
|
|
|
|
return false;
|
|
|
|
// register collision: a outputs to one of b's inputs
|
|
|
|
if (a.regsOut & b.regsIn)
|
|
|
|
return false;
|
|
|
|
// register collision: b outputs to one of a's outputs (overwriting it)
|
|
|
|
if (b.regsOut & a.regsOut)
|
|
|
|
return false;
|
2016-06-24 10:43:46 +02:00
|
|
|
|
2008-12-14 23:22:56 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2008-08-24 15:46:08 +00:00
|
|
|
// Most functions that are relevant to analyze should be
|
|
|
|
// called by another function. Therefore, let's scan the
|
|
|
|
// entire space for bl operations and find what functions
|
|
|
|
// get called.
|
2018-05-27 17:37:52 -04:00
|
|
|
static void FindFunctionsFromBranches(u32 startAddr, u32 endAddr, Common::SymbolDB* func_db)
|
2008-07-12 17:40:22 +00:00
|
|
|
{
|
2008-08-24 15:46:08 +00:00
|
|
|
for (u32 addr = startAddr; addr < endAddr; addr += 4)
|
2008-07-12 17:40:22 +00:00
|
|
|
{
|
2017-03-28 17:45:17 +01:00
|
|
|
const PowerPC::TryReadInstResult read_result = PowerPC::TryReadInstruction(addr);
|
|
|
|
const UGeckoInstruction instr = read_result.hex;
|
2016-06-24 10:43:46 +02:00
|
|
|
|
2017-03-28 17:45:17 +01:00
|
|
|
if (read_result.valid && PPCTables::IsValidInstruction(instr))
|
2008-07-12 17:40:22 +00:00
|
|
|
{
|
|
|
|
switch (instr.OPCD)
|
|
|
|
{
|
|
|
|
case 18: // branch instruction
|
|
|
|
{
|
|
|
|
if (instr.LK) // bl
|
|
|
|
{
|
2008-08-10 18:21:16 +00:00
|
|
|
u32 target = SignExt26(instr.LI << 2);
|
2008-07-12 17:40:22 +00:00
|
|
|
if (!instr.AA)
|
|
|
|
target += addr;
|
2015-01-17 13:17:36 -08:00
|
|
|
if (PowerPC::HostIsRAMAddress(target))
|
2008-08-10 18:21:16 +00:00
|
|
|
{
|
2008-08-24 15:46:08 +00:00
|
|
|
func_db->AddFunction(target);
|
2008-08-10 18:21:16 +00:00
|
|
|
}
|
2008-07-12 17:40:22 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2008-12-08 05:30:24 +00:00
|
|
|
|
2017-04-10 19:35:22 +01:00
|
|
|
static void FindFunctionsFromHandlers(PPCSymbolDB* func_db)
|
|
|
|
{
|
|
|
|
static const std::map<u32, const char* const> handlers = {
|
|
|
|
{0x80000100, "system_reset_exception_handler"},
|
|
|
|
{0x80000200, "machine_check_exception_handler"},
|
|
|
|
{0x80000300, "dsi_exception_handler"},
|
|
|
|
{0x80000400, "isi_exception_handler"},
|
|
|
|
{0x80000500, "external_interrupt_exception_handler"},
|
|
|
|
{0x80000600, "alignment_exception_handler"},
|
|
|
|
{0x80000700, "program_exception_handler"},
|
|
|
|
{0x80000800, "floating_point_unavailable_exception_handler"},
|
|
|
|
{0x80000900, "decrementer_exception_handler"},
|
|
|
|
{0x80000C00, "system_call_exception_handler"},
|
|
|
|
{0x80000D00, "trace_exception_handler"},
|
|
|
|
{0x80000E00, "floating_point_assist_exception_handler"},
|
|
|
|
{0x80000F00, "performance_monitor_interrupt_handler"},
|
|
|
|
{0x80001300, "instruction_address_breakpoint_exception_handler"},
|
|
|
|
{0x80001400, "system_management_interrupt_handler"},
|
|
|
|
{0x80001700, "thermal_management_interrupt_exception_handler"}};
|
|
|
|
|
|
|
|
for (const auto& entry : handlers)
|
|
|
|
{
|
|
|
|
const PowerPC::TryReadInstResult read_result = PowerPC::TryReadInstruction(entry.first);
|
|
|
|
if (read_result.valid && PPCTables::IsValidInstruction(read_result.hex))
|
|
|
|
{
|
|
|
|
// Check if this function is already mapped
|
2018-05-27 17:37:52 -04:00
|
|
|
Common::Symbol* f = func_db->AddFunction(entry.first);
|
2017-04-10 19:35:22 +01:00
|
|
|
if (!f)
|
|
|
|
continue;
|
2017-08-16 02:40:24 +01:00
|
|
|
f->Rename(entry.second);
|
2017-04-10 19:35:22 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-10 17:25:33 +01:00
|
|
|
static void FindFunctionsAfterReturnInstruction(PPCSymbolDB* func_db)
|
2008-07-12 17:40:22 +00:00
|
|
|
{
|
2014-08-09 22:44:27 -04:00
|
|
|
std::vector<u32> funcAddrs;
|
2016-06-24 10:43:46 +02:00
|
|
|
|
2014-02-12 16:00:34 +01:00
|
|
|
for (const auto& func : func_db->Symbols())
|
|
|
|
funcAddrs.push_back(func.second.address + func.second.size);
|
2016-06-24 10:43:46 +02:00
|
|
|
|
2014-02-12 16:00:34 +01:00
|
|
|
for (u32& location : funcAddrs)
|
2008-07-12 17:40:22 +00:00
|
|
|
{
|
|
|
|
while (true)
|
|
|
|
{
|
2017-04-10 23:43:06 +01:00
|
|
|
// Skip zeroes (e.g. Donkey Kong Country Returns) and nop (e.g. libogc)
|
|
|
|
// that sometimes pad function to 16 byte boundary.
|
2017-03-28 17:45:17 +01:00
|
|
|
PowerPC::TryReadInstResult read_result = PowerPC::TryReadInstruction(location);
|
2017-04-10 23:43:06 +01:00
|
|
|
while (read_result.valid && (location & 0xf) != 0)
|
2017-03-28 17:45:17 +01:00
|
|
|
{
|
2017-04-10 23:43:06 +01:00
|
|
|
if (read_result.hex != 0 && read_result.hex != 0x60000000)
|
|
|
|
break;
|
2015-01-22 02:00:35 +10:30
|
|
|
location += 4;
|
2017-03-28 17:45:17 +01:00
|
|
|
read_result = PowerPC::TryReadInstruction(location);
|
|
|
|
}
|
|
|
|
if (read_result.valid && PPCTables::IsValidInstruction(read_result.hex))
|
2008-07-12 17:40:22 +00:00
|
|
|
{
|
|
|
|
// check if this function is already mapped
|
2018-05-27 17:37:52 -04:00
|
|
|
Common::Symbol* f = func_db->AddFunction(location);
|
2008-07-12 17:40:22 +00:00
|
|
|
if (!f)
|
|
|
|
break;
|
|
|
|
else
|
2008-08-24 18:50:51 +00:00
|
|
|
location += f->size;
|
2008-07-12 17:40:22 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2008-12-08 05:30:24 +00:00
|
|
|
|
2009-06-21 08:39:21 +00:00
|
|
|
void FindFunctions(u32 startAddr, u32 endAddr, PPCSymbolDB* func_db)
|
2008-07-12 17:40:22 +00:00
|
|
|
{
|
|
|
|
// Step 1: Find all functions
|
2008-08-24 15:46:08 +00:00
|
|
|
FindFunctionsFromBranches(startAddr, endAddr, func_db);
|
2017-04-10 19:35:22 +01:00
|
|
|
FindFunctionsFromHandlers(func_db);
|
2017-04-10 17:25:33 +01:00
|
|
|
FindFunctionsAfterReturnInstruction(func_db);
|
2016-06-24 10:43:46 +02:00
|
|
|
|
2008-07-12 17:40:22 +00:00
|
|
|
// Step 2:
|
2008-08-24 15:46:08 +00:00
|
|
|
func_db->FillInCallers();
|
2022-05-03 21:20:00 -05:00
|
|
|
func_db->Index();
|
2016-06-24 10:43:46 +02:00
|
|
|
|
2008-10-25 15:59:09 +00:00
|
|
|
int numLeafs = 0, numNice = 0, numUnNice = 0;
|
|
|
|
int numTimer = 0, numRFI = 0, numStraightLeaf = 0;
|
2008-07-12 17:40:22 +00:00
|
|
|
int leafSize = 0, niceSize = 0, unniceSize = 0;
|
2014-02-12 16:00:34 +01:00
|
|
|
for (auto& func : func_db->AccessSymbols())
|
2008-07-12 17:40:22 +00:00
|
|
|
{
|
2014-02-12 16:00:34 +01:00
|
|
|
if (func.second.address == 4)
|
2008-07-12 17:40:22 +00:00
|
|
|
{
|
2020-11-25 08:45:21 -05:00
|
|
|
WARN_LOG_FMT(SYMBOLS, "Weird function");
|
2008-07-12 17:40:22 +00:00
|
|
|
continue;
|
|
|
|
}
|
2014-02-12 16:00:34 +01:00
|
|
|
AnalyzeFunction2(&(func.second));
|
2018-05-27 17:37:52 -04:00
|
|
|
Common::Symbol& f = func.second;
|
2008-12-18 10:44:03 +00:00
|
|
|
if (f.name.substr(0, 3) == "zzz")
|
2008-08-24 21:30:59 +00:00
|
|
|
{
|
2018-05-27 17:37:52 -04:00
|
|
|
if (f.flags & Common::FFLAG_LEAF)
|
2017-08-16 02:40:24 +01:00
|
|
|
f.Rename(f.name + "_leaf");
|
2018-05-27 17:37:52 -04:00
|
|
|
if (f.flags & Common::FFLAG_STRAIGHT)
|
2017-08-16 02:40:24 +01:00
|
|
|
f.Rename(f.name + "_straight");
|
2008-08-24 21:30:59 +00:00
|
|
|
}
|
2018-05-27 17:37:52 -04:00
|
|
|
if (f.flags & Common::FFLAG_LEAF)
|
2008-07-12 17:40:22 +00:00
|
|
|
{
|
|
|
|
numLeafs++;
|
|
|
|
leafSize += f.size;
|
|
|
|
}
|
2018-05-27 17:37:52 -04:00
|
|
|
else if (f.flags & Common::FFLAG_ONLYCALLSNICELEAFS)
|
2008-07-12 17:40:22 +00:00
|
|
|
{
|
|
|
|
numNice++;
|
|
|
|
niceSize += f.size;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
numUnNice++;
|
|
|
|
unniceSize += f.size;
|
|
|
|
}
|
2016-06-24 10:43:46 +02:00
|
|
|
|
2018-05-27 17:37:52 -04:00
|
|
|
if (f.flags & Common::FFLAG_TIMERINSTRUCTIONS)
|
2008-07-12 17:40:22 +00:00
|
|
|
numTimer++;
|
2018-05-27 17:37:52 -04:00
|
|
|
if (f.flags & Common::FFLAG_RFI)
|
2008-07-12 17:40:22 +00:00
|
|
|
numRFI++;
|
2018-05-27 17:37:52 -04:00
|
|
|
if ((f.flags & Common::FFLAG_STRAIGHT) && (f.flags & Common::FFLAG_LEAF))
|
2008-07-12 17:40:22 +00:00
|
|
|
numStraightLeaf++;
|
|
|
|
}
|
|
|
|
if (numLeafs == 0)
|
|
|
|
leafSize = 0;
|
|
|
|
else
|
|
|
|
leafSize /= numLeafs;
|
2016-06-24 10:43:46 +02:00
|
|
|
|
2008-07-12 17:40:22 +00:00
|
|
|
if (numNice == 0)
|
|
|
|
niceSize = 0;
|
|
|
|
else
|
|
|
|
niceSize /= numNice;
|
2016-06-24 10:43:46 +02:00
|
|
|
|
2008-07-12 17:40:22 +00:00
|
|
|
if (numUnNice == 0)
|
|
|
|
unniceSize = 0;
|
|
|
|
else
|
|
|
|
unniceSize /= numUnNice;
|
2016-06-24 10:43:46 +02:00
|
|
|
|
2020-11-25 08:45:21 -05:00
|
|
|
INFO_LOG_FMT(SYMBOLS,
|
|
|
|
"Functions analyzed. {} leafs, {} nice, {} unnice."
|
|
|
|
"{} timer, {} rfi. {} are branchless leafs.",
|
|
|
|
numLeafs, numNice, numUnNice, numTimer, numRFI, numStraightLeaf);
|
|
|
|
INFO_LOG_FMT(SYMBOLS, "Average size: {} (leaf), {} (nice), {}(unnice)", leafSize, niceSize,
|
|
|
|
unniceSize);
|
2008-07-12 17:40:22 +00:00
|
|
|
}
|
2008-12-08 05:30:24 +00:00
|
|
|
|
2014-09-07 08:30:11 -07:00
|
|
|
static bool isCmp(const CodeOp& a)
|
2014-04-30 00:14:24 -05:00
|
|
|
{
|
2014-09-07 08:30:11 -07:00
|
|
|
return (a.inst.OPCD == 10 || a.inst.OPCD == 11) ||
|
|
|
|
(a.inst.OPCD == 31 && (a.inst.SUBOP10 == 0 || a.inst.SUBOP10 == 32));
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool isCarryOp(const CodeOp& a)
|
|
|
|
{
|
|
|
|
return (a.opinfo->flags & FL_SET_CA) && !(a.opinfo->flags & FL_SET_OE) &&
|
2018-03-18 18:16:26 -04:00
|
|
|
a.opinfo->type == OpType::Integer;
|
2014-09-07 08:30:11 -07:00
|
|
|
}
|
|
|
|
|
2014-10-21 02:56:38 -07:00
|
|
|
static bool isCror(const CodeOp& a)
|
|
|
|
{
|
|
|
|
return a.inst.OPCD == 19 && a.inst.SUBOP10 == 449;
|
|
|
|
}
|
|
|
|
|
2014-09-07 08:30:11 -07:00
|
|
|
void PPCAnalyzer::ReorderInstructionsCore(u32 instructions, CodeOp* code, bool reverse,
|
2021-12-31 17:46:45 +01:00
|
|
|
ReorderType type) const
|
2014-09-07 08:30:11 -07:00
|
|
|
{
|
|
|
|
// Bubbling an instruction sometimes reveals another opportunity to bubble an instruction, so do
|
|
|
|
// multiple passes.
|
|
|
|
while (true)
|
2014-04-30 00:14:24 -05:00
|
|
|
{
|
2014-09-07 08:30:11 -07:00
|
|
|
// Instruction Reordering Pass
|
|
|
|
// Carry pass: bubble carry-using instructions as close to each other as possible, so we can
|
|
|
|
// avoid
|
|
|
|
// storing the carry flag.
|
|
|
|
// Compare pass: bubble compare instructions next to branches, so they can be merged.
|
|
|
|
bool swapped = false;
|
|
|
|
int increment = reverse ? -1 : 1;
|
|
|
|
int start = reverse ? instructions - 1 : 0;
|
|
|
|
int end = reverse ? 0 : instructions - 1;
|
|
|
|
for (int i = start; i != end; i += increment)
|
2014-04-30 00:14:24 -05:00
|
|
|
{
|
2014-09-07 08:30:11 -07:00
|
|
|
CodeOp& a = code[i];
|
|
|
|
CodeOp& b = code[i + increment];
|
|
|
|
// Reorder integer compares, rlwinm., and carry-affecting ops
|
|
|
|
// (if we add more merged branch instructions, add them here!)
|
2018-04-08 21:38:16 -04:00
|
|
|
if ((type == ReorderType::CROR && isCror(a)) ||
|
|
|
|
(type == ReorderType::Carry && isCarryOp(a)) ||
|
|
|
|
(type == ReorderType::CMP && (isCmp(a) || a.outputCR0)))
|
2014-08-30 17:23:13 -04:00
|
|
|
{
|
2014-09-07 08:30:11 -07:00
|
|
|
// once we're next to a carry instruction, don't move away!
|
2018-04-08 21:38:16 -04:00
|
|
|
if (type == ReorderType::Carry && i != start)
|
2014-09-07 08:30:11 -07:00
|
|
|
{
|
|
|
|
// if we read the CA flag, and the previous instruction sets it, don't move away.
|
|
|
|
if (!reverse && (a.opinfo->flags & FL_READ_CA) &&
|
|
|
|
(code[i - increment].opinfo->flags & FL_SET_CA))
|
|
|
|
continue;
|
|
|
|
// if we set the CA flag, and the next instruction reads it, don't move away.
|
|
|
|
if (reverse && (a.opinfo->flags & FL_SET_CA) &&
|
|
|
|
(code[i - increment].opinfo->flags & FL_READ_CA))
|
|
|
|
continue;
|
|
|
|
}
|
2016-06-24 10:43:46 +02:00
|
|
|
|
2014-09-07 08:30:11 -07:00
|
|
|
if (CanSwapAdjacentOps(a, b))
|
|
|
|
{
|
|
|
|
// Alright, let's bubble it!
|
|
|
|
std::swap(a, b);
|
|
|
|
swapped = true;
|
|
|
|
}
|
2014-04-30 00:14:24 -05:00
|
|
|
}
|
|
|
|
}
|
2014-09-07 08:30:11 -07:00
|
|
|
if (!swapped)
|
|
|
|
return;
|
2014-04-30 00:14:24 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-31 17:46:45 +01:00
|
|
|
void PPCAnalyzer::ReorderInstructions(u32 instructions, CodeOp* code) const
|
2014-09-07 08:30:11 -07:00
|
|
|
{
|
2014-10-21 02:56:38 -07:00
|
|
|
// Reorder cror instructions upwards (e.g. towards an fcmp). Technically we should be more
|
|
|
|
// picky about this, but cror seems to almost solely be used for this purpose in real code.
|
|
|
|
// Additionally, the other boolean ops seem to almost never be used.
|
2015-01-03 22:59:28 -08:00
|
|
|
if (HasOption(OPTION_CROR_MERGE))
|
2018-04-08 21:38:16 -04:00
|
|
|
ReorderInstructionsCore(instructions, code, true, ReorderType::CROR);
|
2014-09-07 08:30:11 -07:00
|
|
|
// For carry, bubble instructions *towards* each other; one direction often isn't enough
|
|
|
|
// to get pairs like addc/adde next to each other.
|
2014-09-11 03:59:40 -07:00
|
|
|
if (HasOption(OPTION_CARRY_MERGE))
|
|
|
|
{
|
2018-04-08 21:38:16 -04:00
|
|
|
ReorderInstructionsCore(instructions, code, false, ReorderType::Carry);
|
|
|
|
ReorderInstructionsCore(instructions, code, true, ReorderType::Carry);
|
2014-09-11 03:59:40 -07:00
|
|
|
}
|
|
|
|
if (HasOption(OPTION_BRANCH_MERGE))
|
2018-04-08 21:38:16 -04:00
|
|
|
ReorderInstructionsCore(instructions, code, false, ReorderType::CMP);
|
2014-09-07 08:30:11 -07:00
|
|
|
}
|
|
|
|
|
2017-02-18 04:13:24 -05:00
|
|
|
void PPCAnalyzer::SetInstructionStats(CodeBlock* block, CodeOp* code, const GekkoOPInfo* opinfo,
|
2021-12-31 17:46:45 +01:00
|
|
|
u32 index) const
|
2014-04-30 00:14:24 -05:00
|
|
|
{
|
|
|
|
code->wantsCR0 = false;
|
|
|
|
code->wantsCR1 = false;
|
2016-06-24 10:43:46 +02:00
|
|
|
|
2021-08-18 12:39:02 +02:00
|
|
|
bool first_fpu_instruction = false;
|
2014-04-30 00:14:24 -05:00
|
|
|
if (opinfo->flags & FL_USE_FPU)
|
2021-08-18 12:39:02 +02:00
|
|
|
{
|
|
|
|
first_fpu_instruction = !block->m_fpa->any;
|
2014-04-30 00:14:24 -05:00
|
|
|
block->m_fpa->any = true;
|
2021-08-18 12:39:02 +02:00
|
|
|
}
|
2016-06-24 10:43:46 +02:00
|
|
|
|
2014-04-30 00:14:24 -05:00
|
|
|
if (opinfo->flags & FL_TIMER)
|
|
|
|
block->m_gpa->anyTimer = true;
|
2016-06-24 10:43:46 +02:00
|
|
|
|
2014-04-30 00:14:24 -05:00
|
|
|
// 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
|
2018-04-08 21:42:40 -04:00
|
|
|
code->outputCR0 = (opinfo->flags & FL_SET_CR0) != 0;
|
2016-06-24 10:43:46 +02:00
|
|
|
|
2014-04-30 00:14:24 -05:00
|
|
|
// 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
|
2018-04-08 21:42:40 -04:00
|
|
|
code->outputCR1 = (opinfo->flags & FL_SET_CR1) != 0;
|
2016-06-24 10:43:46 +02:00
|
|
|
|
2018-04-08 21:42:40 -04:00
|
|
|
code->wantsFPRF = (opinfo->flags & FL_READ_FPRF) != 0;
|
|
|
|
code->outputFPRF = (opinfo->flags & FL_SET_FPRF) != 0;
|
|
|
|
code->canEndBlock = (opinfo->flags & FL_ENDBLOCK) != 0;
|
2016-06-24 10:43:46 +02:00
|
|
|
|
2022-01-03 07:07:32 +01:00
|
|
|
code->canCauseException = first_fpu_instruction ||
|
|
|
|
(opinfo->flags & (FL_LOADSTORE | FL_PROGRAMEXCEPTION)) != 0 ||
|
|
|
|
(m_enable_float_exceptions && (opinfo->flags & FL_FLOAT_EXCEPTION)) ||
|
|
|
|
(m_enable_div_by_zero_exceptions && (opinfo->flags & FL_FLOAT_DIV));
|
2020-12-20 13:23:17 +01:00
|
|
|
|
2018-04-08 21:42:40 -04:00
|
|
|
code->wantsCA = (opinfo->flags & FL_READ_CA) != 0;
|
|
|
|
code->outputCA = (opinfo->flags & FL_SET_CA) != 0;
|
2016-06-24 10:43:46 +02:00
|
|
|
|
2014-09-07 00:37:47 -07:00
|
|
|
// We're going to try to avoid storing carry in XER if we can avoid it -- keep it in the x86 carry
|
|
|
|
// flag!
|
|
|
|
// If the instruction reads CA but doesn't write it, we still need to store CA in XER; we can't
|
|
|
|
// leave it in flags.
|
2014-09-11 03:59:40 -07:00
|
|
|
if (HasOption(OPTION_CARRY_MERGE))
|
2018-03-18 18:16:26 -04:00
|
|
|
code->wantsCAInFlags = code->wantsCA && code->outputCA && opinfo->type == OpType::Integer;
|
2014-09-11 03:59:40 -07:00
|
|
|
else
|
|
|
|
code->wantsCAInFlags = false;
|
2016-06-24 10:43:46 +02:00
|
|
|
|
2014-08-21 13:56:18 -07:00
|
|
|
// mfspr/mtspr can affect/use XER, so be super careful here
|
2014-09-07 00:37:47 -07:00
|
|
|
// we need to note specifically that mfspr needs CA in XER, not in the x86 carry flag
|
2014-08-21 13:56:18 -07:00
|
|
|
if (code->inst.OPCD == 31 && code->inst.SUBOP10 == 339) // mfspr
|
|
|
|
code->wantsCA = ((code->inst.SPRU << 5) | (code->inst.SPRL & 0x1F)) == SPR_XER;
|
|
|
|
if (code->inst.OPCD == 31 && code->inst.SUBOP10 == 467) // mtspr
|
|
|
|
code->outputCA = ((code->inst.SPRU << 5) | (code->inst.SPRL & 0x1F)) == SPR_XER;
|
2016-06-24 10:43:46 +02:00
|
|
|
|
2014-10-16 21:49:48 -04:00
|
|
|
code->regsIn = BitSet32(0);
|
|
|
|
code->regsOut = BitSet32(0);
|
2014-04-30 00:14:24 -05:00
|
|
|
if (opinfo->flags & FL_OUT_A)
|
|
|
|
{
|
2014-10-16 21:49:48 -04:00
|
|
|
code->regsOut[code->inst.RA] = true;
|
2014-04-30 00:14:24 -05:00
|
|
|
block->m_gpa->SetOutputRegister(code->inst.RA, index);
|
|
|
|
}
|
|
|
|
if (opinfo->flags & FL_OUT_D)
|
|
|
|
{
|
2014-10-16 21:49:48 -04:00
|
|
|
code->regsOut[code->inst.RD] = true;
|
2014-04-30 00:14:24 -05:00
|
|
|
block->m_gpa->SetOutputRegister(code->inst.RD, index);
|
|
|
|
}
|
|
|
|
if ((opinfo->flags & FL_IN_A) || ((opinfo->flags & FL_IN_A0) && code->inst.RA != 0))
|
|
|
|
{
|
2014-10-16 21:49:48 -04:00
|
|
|
code->regsIn[code->inst.RA] = true;
|
2014-04-30 00:14:24 -05:00
|
|
|
block->m_gpa->SetInputRegister(code->inst.RA, index);
|
|
|
|
}
|
|
|
|
if (opinfo->flags & FL_IN_B)
|
|
|
|
{
|
2014-10-16 21:49:48 -04:00
|
|
|
code->regsIn[code->inst.RB] = true;
|
2014-04-30 00:14:24 -05:00
|
|
|
block->m_gpa->SetInputRegister(code->inst.RB, index);
|
|
|
|
}
|
|
|
|
if (opinfo->flags & FL_IN_C)
|
|
|
|
{
|
2014-10-16 21:49:48 -04:00
|
|
|
code->regsIn[code->inst.RC] = true;
|
2014-04-30 00:14:24 -05:00
|
|
|
block->m_gpa->SetInputRegister(code->inst.RC, index);
|
|
|
|
}
|
|
|
|
if (opinfo->flags & FL_IN_S)
|
|
|
|
{
|
2014-10-16 21:49:48 -04:00
|
|
|
code->regsIn[code->inst.RS] = true;
|
2014-04-30 00:14:24 -05:00
|
|
|
block->m_gpa->SetInputRegister(code->inst.RS, index);
|
|
|
|
}
|
2014-12-02 15:50:18 -06:00
|
|
|
if (code->inst.OPCD == 46) // lmw
|
|
|
|
{
|
|
|
|
for (int iReg = code->inst.RD; iReg < 32; ++iReg)
|
|
|
|
{
|
|
|
|
code->regsOut[iReg] = true;
|
|
|
|
block->m_gpa->SetOutputRegister(iReg, index);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (code->inst.OPCD == 47) // stmw
|
|
|
|
{
|
|
|
|
for (int iReg = code->inst.RS; iReg < 32; ++iReg)
|
|
|
|
{
|
|
|
|
code->regsIn[iReg] = true;
|
|
|
|
block->m_gpa->SetInputRegister(iReg, index);
|
|
|
|
}
|
|
|
|
}
|
2016-06-24 10:43:46 +02:00
|
|
|
|
2014-09-13 05:34:38 -07:00
|
|
|
code->fregOut = -1;
|
|
|
|
if (opinfo->flags & FL_OUT_FLOAT_D)
|
|
|
|
code->fregOut = code->inst.FD;
|
2016-06-24 10:43:46 +02:00
|
|
|
|
2014-10-16 21:49:48 -04:00
|
|
|
code->fregsIn = BitSet32(0);
|
2014-09-13 05:34:38 -07:00
|
|
|
if (opinfo->flags & FL_IN_FLOAT_A)
|
2014-10-16 21:49:48 -04:00
|
|
|
code->fregsIn[code->inst.FA] = true;
|
2014-09-13 05:34:38 -07:00
|
|
|
if (opinfo->flags & FL_IN_FLOAT_B)
|
2014-10-16 21:49:48 -04:00
|
|
|
code->fregsIn[code->inst.FB] = true;
|
2014-09-13 05:34:38 -07:00
|
|
|
if (opinfo->flags & FL_IN_FLOAT_C)
|
2014-10-16 21:49:48 -04:00
|
|
|
code->fregsIn[code->inst.FC] = true;
|
2014-09-13 05:34:38 -07:00
|
|
|
if (opinfo->flags & FL_IN_FLOAT_D)
|
2014-10-16 21:49:48 -04:00
|
|
|
code->fregsIn[code->inst.FD] = true;
|
2014-09-13 05:34:38 -07:00
|
|
|
if (opinfo->flags & FL_IN_FLOAT_S)
|
2014-10-16 21:49:48 -04:00
|
|
|
code->fregsIn[code->inst.FS] = true;
|
2016-06-24 10:43:46 +02:00
|
|
|
|
2018-07-28 01:52:22 +02:00
|
|
|
code->branchUsesCtr = false;
|
|
|
|
code->branchTo = UINT32_MAX;
|
|
|
|
|
|
|
|
// For branch with immediate addresses (bx/bcx), compute the destination.
|
|
|
|
if (code->inst.OPCD == 18) // bx
|
|
|
|
{
|
|
|
|
if (code->inst.AA) // absolute
|
|
|
|
code->branchTo = SignExt26(code->inst.LI << 2);
|
|
|
|
else
|
|
|
|
code->branchTo = code->address + SignExt26(code->inst.LI << 2);
|
|
|
|
}
|
|
|
|
else if (code->inst.OPCD == 16) // bcx
|
|
|
|
{
|
|
|
|
if (code->inst.AA) // absolute
|
|
|
|
code->branchTo = SignExt16(code->inst.BD << 2);
|
|
|
|
else
|
|
|
|
code->branchTo = code->address + SignExt16(code->inst.BD << 2);
|
|
|
|
if (!(code->inst.BO & BO_DONT_DECREMENT_FLAG))
|
|
|
|
code->branchUsesCtr = true;
|
|
|
|
}
|
|
|
|
else if (code->inst.OPCD == 19 && code->inst.SUBOP10 == 16) // bclrx
|
|
|
|
{
|
|
|
|
if (!(code->inst.BO & BO_DONT_DECREMENT_FLAG))
|
|
|
|
code->branchUsesCtr = true;
|
|
|
|
}
|
|
|
|
else if (code->inst.OPCD == 19 && code->inst.SUBOP10 == 528) // bcctrx
|
|
|
|
{
|
|
|
|
if (!(code->inst.BO & BO_DONT_DECREMENT_FLAG))
|
|
|
|
code->branchUsesCtr = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-31 17:46:45 +01:00
|
|
|
bool PPCAnalyzer::IsBusyWaitLoop(CodeBlock* block, CodeOp* code, size_t instructions) const
|
2018-07-28 01:52:22 +02:00
|
|
|
{
|
|
|
|
// Very basic algorithm to detect busy wait loops:
|
|
|
|
// * It loops to itself and does not contain any other branches.
|
|
|
|
// * It does not write to memory.
|
|
|
|
// * It only reads from registers it wrote to earlier in the loop, or it
|
|
|
|
// does not write to these registers.
|
|
|
|
//
|
|
|
|
// Would benefit a lot from basic inlining support - a lot of the most
|
|
|
|
// used busy loops are DSP register interactions, which are bl/cmp/bne
|
|
|
|
// (with the bl target a pure function that follows the above rules). We
|
|
|
|
// don't detect these at the moment.
|
|
|
|
std::bitset<32> write_disallowed_regs;
|
|
|
|
std::bitset<32> written_regs;
|
|
|
|
for (size_t i = 0; i <= instructions; ++i)
|
|
|
|
{
|
|
|
|
if (code[i].opinfo->type == OpType::Branch)
|
|
|
|
{
|
|
|
|
if (code[i].branchUsesCtr)
|
|
|
|
return false;
|
|
|
|
if (code[i].branchTo == block->m_address && i == instructions)
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else if (code[i].opinfo->type != OpType::Integer && code[i].opinfo->type != OpType::Load)
|
|
|
|
{
|
|
|
|
// In the future, some subsets of other instruction types might get
|
|
|
|
// supported. Right now, only try loops that have this very
|
|
|
|
// restricted instruction set.
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
for (int reg : code[i].regsIn)
|
|
|
|
{
|
|
|
|
if (reg == -1)
|
|
|
|
continue;
|
|
|
|
if (written_regs[reg])
|
|
|
|
continue;
|
|
|
|
write_disallowed_regs[reg] = true;
|
|
|
|
}
|
|
|
|
for (int reg : code[i].regsOut)
|
|
|
|
{
|
|
|
|
if (reg == -1)
|
|
|
|
continue;
|
|
|
|
if (write_disallowed_regs[reg])
|
|
|
|
return false;
|
|
|
|
written_regs[reg] = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
2014-04-30 00:14:24 -05:00
|
|
|
}
|
|
|
|
|
2021-12-31 17:46:45 +01:00
|
|
|
u32 PPCAnalyzer::Analyze(u32 address, CodeBlock* block, CodeBuffer* buffer,
|
|
|
|
std::size_t block_size) const
|
2014-04-30 00:14:24 -05:00
|
|
|
{
|
|
|
|
// Clear block stats
|
2018-05-19 18:27:27 -04:00
|
|
|
*block->m_stats = {};
|
2016-06-24 10:43:46 +02:00
|
|
|
|
2014-04-30 00:14:24 -05:00
|
|
|
// Clear register stats
|
|
|
|
block->m_gpa->any = true;
|
|
|
|
block->m_fpa->any = false;
|
2016-06-24 10:43:46 +02:00
|
|
|
|
2014-04-30 00:14:24 -05:00
|
|
|
block->m_gpa->Clear();
|
|
|
|
block->m_fpa->Clear();
|
2016-06-24 10:43:46 +02:00
|
|
|
|
2014-04-30 00:14:24 -05:00
|
|
|
// Set the blocks start address
|
|
|
|
block->m_address = address;
|
2016-06-24 10:43:46 +02:00
|
|
|
|
2014-04-30 00:14:24 -05:00
|
|
|
// Reset our block state
|
|
|
|
block->m_broken = false;
|
2014-05-09 09:10:45 -05:00
|
|
|
block->m_memory_exception = false;
|
2014-04-30 03:49:17 -05:00
|
|
|
block->m_num_instructions = 0;
|
2015-01-04 04:20:59 -08:00
|
|
|
block->m_gqr_used = BitSet8(0);
|
2017-01-22 16:23:56 +01:00
|
|
|
block->m_physical_addresses.clear();
|
2016-06-24 10:43:46 +02:00
|
|
|
|
2018-05-18 16:14:31 -04:00
|
|
|
CodeOp* const code = buffer->data();
|
2016-06-24 10:43:46 +02:00
|
|
|
|
2014-04-30 00:14:24 -05:00
|
|
|
bool found_exit = false;
|
2017-01-22 19:13:29 +01:00
|
|
|
bool found_call = false;
|
|
|
|
size_t caller = 0;
|
2014-04-30 00:14:24 -05:00
|
|
|
u32 numFollows = 0;
|
|
|
|
u32 num_inst = 0;
|
2016-06-24 10:43:46 +02:00
|
|
|
|
2022-01-03 07:07:32 +01:00
|
|
|
const bool enable_follow = m_enable_branch_following;
|
2018-07-08 21:26:34 +02:00
|
|
|
|
2018-05-18 16:14:31 -04:00
|
|
|
for (std::size_t i = 0; i < block_size; ++i)
|
2014-04-30 00:14:24 -05:00
|
|
|
{
|
2015-01-17 13:17:36 -08:00
|
|
|
auto result = PowerPC::TryReadInstruction(address);
|
|
|
|
if (!result.valid)
|
|
|
|
{
|
|
|
|
if (i == 0)
|
|
|
|
block->m_memory_exception = true;
|
|
|
|
break;
|
|
|
|
}
|
2016-06-24 10:43:46 +02:00
|
|
|
|
2015-01-23 16:01:05 -08:00
|
|
|
num_inst++;
|
2016-06-24 10:43:46 +02:00
|
|
|
|
2018-05-19 18:27:27 -04:00
|
|
|
const UGeckoInstruction inst = result.hex;
|
|
|
|
GekkoOPInfo* opinfo = PPCTables::GetOpInfo(inst);
|
|
|
|
code[i] = {};
|
2015-01-23 16:01:05 -08:00
|
|
|
code[i].opinfo = opinfo;
|
|
|
|
code[i].address = address;
|
|
|
|
code[i].inst = inst;
|
|
|
|
code[i].skip = false;
|
|
|
|
block->m_stats->numCycles += opinfo->numCycles;
|
2017-01-22 16:23:56 +01:00
|
|
|
block->m_physical_addresses.insert(result.physical_address);
|
2016-06-24 10:43:46 +02:00
|
|
|
|
2018-05-18 16:14:31 -04:00
|
|
|
SetInstructionStats(block, &code[i], opinfo, static_cast<u32>(i));
|
2016-06-24 10:43:46 +02:00
|
|
|
|
2015-01-23 16:01:05 -08:00
|
|
|
bool follow = false;
|
2016-06-24 10:43:46 +02:00
|
|
|
|
2015-01-23 16:01:05 -08:00
|
|
|
bool conditional_continue = false;
|
2016-06-24 10:43:46 +02:00
|
|
|
|
2017-01-22 19:13:29 +01:00
|
|
|
// TODO: Find the optimal value for BRANCH_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.
|
2018-08-09 09:40:12 +02:00
|
|
|
if (enable_follow && HasOption(OPTION_BRANCH_FOLLOW))
|
2015-01-23 16:01:05 -08:00
|
|
|
{
|
2018-05-18 16:14:31 -04:00
|
|
|
if (inst.OPCD == 18 && block_size > 1)
|
2014-04-30 00:14:24 -05:00
|
|
|
{
|
2017-01-22 19:13:29 +01:00
|
|
|
// Always follow BX instructions.
|
2018-07-28 08:18:05 +02:00
|
|
|
follow = true;
|
2017-01-22 19:13:29 +01:00
|
|
|
if (inst.LK)
|
|
|
|
{
|
|
|
|
found_call = true;
|
|
|
|
caller = i;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (inst.OPCD == 16 && (inst.BO & BO_DONT_DECREMENT_FLAG) &&
|
2018-05-18 16:14:31 -04:00
|
|
|
(inst.BO & BO_DONT_CHECK_CONDITION) && block_size > 1)
|
2017-01-22 19:13:29 +01:00
|
|
|
{
|
|
|
|
// Always follow unconditional BCX instructions, but they are very rare.
|
|
|
|
follow = true;
|
|
|
|
if (inst.LK)
|
|
|
|
{
|
|
|
|
found_call = true;
|
|
|
|
caller = i;
|
|
|
|
}
|
2014-04-30 00:14:24 -05:00
|
|
|
}
|
2018-08-09 09:40:12 +02:00
|
|
|
else if (inst.OPCD == 19 && inst.SUBOP10 == 16 && !inst.LK && found_call)
|
2015-01-23 16:01:05 -08:00
|
|
|
{
|
2018-07-28 01:52:22 +02:00
|
|
|
code[i].branchTo = code[caller].address + 4;
|
2018-08-09 09:40:12 +02:00
|
|
|
if ((inst.BO & BO_DONT_DECREMENT_FLAG) && (inst.BO & BO_DONT_CHECK_CONDITION) &&
|
|
|
|
numFollows < BRANCH_FOLLOWING_THRESHOLD)
|
|
|
|
{
|
|
|
|
// bclrx with unconditional branch = return
|
|
|
|
// Follow it if we can propagate the LR value of the last CALL instruction.
|
|
|
|
// Through it would be easy to track the upper level of call/return,
|
|
|
|
// we can't guarantee the LR value. The PPC ABI forces all functions to push
|
|
|
|
// the LR value on the stack as there are no spare registers. So we'd need
|
|
|
|
// to check all store instruction to not alias with the stack.
|
|
|
|
follow = true;
|
|
|
|
found_call = false;
|
|
|
|
code[i].skip = true;
|
2016-06-24 10:43:46 +02:00
|
|
|
|
2018-08-09 09:40:12 +02:00
|
|
|
// Skip the RET, so also don't generate the stack entry for the BLR optimization.
|
|
|
|
code[caller].skipLRStack = true;
|
|
|
|
}
|
2015-01-23 16:01:05 -08:00
|
|
|
}
|
|
|
|
else if (inst.OPCD == 31 && inst.SUBOP10 == 467)
|
2014-04-30 00:14:24 -05:00
|
|
|
{
|
2017-01-22 19:13:29 +01:00
|
|
|
// mtspr, skip CALL/RET merging as LR is overwritten.
|
2015-01-23 16:01:05 -08:00
|
|
|
const u32 index = (inst.SPRU << 5) | (inst.SPRL & 0x1F);
|
|
|
|
if (index == SPR_LR)
|
2014-04-30 00:14:24 -05:00
|
|
|
{
|
2015-01-23 16:01:05 -08:00
|
|
|
// We give up to follow the return address
|
|
|
|
// because we have to check the register usage.
|
2017-01-22 19:13:29 +01:00
|
|
|
found_call = false;
|
2014-04-30 00:14:24 -05:00
|
|
|
}
|
2016-06-24 10:43:46 +02:00
|
|
|
}
|
2015-01-23 16:01:05 -08:00
|
|
|
}
|
2016-06-24 10:43:46 +02:00
|
|
|
|
2015-01-23 16:01:05 -08:00
|
|
|
if (HasOption(OPTION_CONDITIONAL_CONTINUE))
|
|
|
|
{
|
|
|
|
if (inst.OPCD == 16 &&
|
|
|
|
((inst.BO & BO_DONT_DECREMENT_FLAG) == 0 || (inst.BO & BO_DONT_CHECK_CONDITION) == 0))
|
2014-04-30 00:14:24 -05:00
|
|
|
{
|
2015-01-23 16:01:05 -08:00
|
|
|
// bcx with conditional branch
|
|
|
|
conditional_continue = true;
|
2014-04-30 00:14:24 -05:00
|
|
|
}
|
2015-01-23 16:01:05 -08:00
|
|
|
else if (inst.OPCD == 19 && inst.SUBOP10 == 16 &&
|
|
|
|
((inst.BO & BO_DONT_DECREMENT_FLAG) == 0 ||
|
|
|
|
(inst.BO & BO_DONT_CHECK_CONDITION) == 0))
|
2014-04-30 00:14:24 -05:00
|
|
|
{
|
2015-01-23 16:01:05 -08:00
|
|
|
// 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;
|
2014-04-30 00:14:24 -05:00
|
|
|
}
|
|
|
|
}
|
2016-06-24 10:43:46 +02:00
|
|
|
|
2018-08-09 09:40:12 +02:00
|
|
|
code[i].branchIsIdleLoop =
|
|
|
|
code[i].branchTo == block->m_address && IsBusyWaitLoop(block, code, i);
|
|
|
|
|
|
|
|
if (follow && numFollows < BRANCH_FOLLOWING_THRESHOLD)
|
2015-01-23 16:01:05 -08:00
|
|
|
{
|
2017-01-22 19:13:29 +01:00
|
|
|
// Follow the unconditional branch.
|
|
|
|
numFollows++;
|
2018-07-28 01:52:22 +02:00
|
|
|
address = code[i].branchTo;
|
2017-01-22 19:13:29 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Just pick the next instruction
|
2015-01-23 16:01:05 -08:00
|
|
|
address += 4;
|
|
|
|
if (!conditional_continue && opinfo->flags & FL_ENDBLOCK) // right now we stop early
|
|
|
|
{
|
|
|
|
found_exit = true;
|
|
|
|
break;
|
|
|
|
}
|
2017-01-22 19:13:29 +01:00
|
|
|
if (conditional_continue)
|
|
|
|
{
|
|
|
|
// If we skip any conditional branch, we can't garantee to get the matching CALL/RET pair.
|
|
|
|
// So we stop inling the RET here and let the BLR optitmization handle this case.
|
|
|
|
found_call = false;
|
|
|
|
}
|
2015-01-23 16:01:05 -08:00
|
|
|
}
|
2014-04-30 00:14:24 -05:00
|
|
|
}
|
2016-06-24 10:43:46 +02:00
|
|
|
|
2014-08-21 11:19:23 -07:00
|
|
|
block->m_num_instructions = num_inst;
|
2016-06-24 10:43:46 +02:00
|
|
|
|
2014-04-30 03:49:17 -05:00
|
|
|
if (block->m_num_instructions > 1)
|
|
|
|
ReorderInstructions(block->m_num_instructions, code);
|
2016-06-24 10:43:46 +02:00
|
|
|
|
2018-05-18 16:14:31 -04:00
|
|
|
if ((!found_exit && num_inst > 0) || block_size == 1)
|
2014-04-30 00:14:24 -05:00
|
|
|
{
|
|
|
|
// We couldn't find an exit
|
|
|
|
block->m_broken = true;
|
|
|
|
}
|
2016-06-24 10:43:46 +02:00
|
|
|
|
2014-08-21 13:56:18 -07:00
|
|
|
// Scan for flag dependencies; assume the next block (or any branch that can leave the block)
|
|
|
|
// wants flags, to be safe.
|
2014-10-09 16:11:10 -07:00
|
|
|
bool wantsCR0 = true, wantsCR1 = true, wantsFPRF = true, wantsCA = true;
|
2020-12-20 13:23:17 +01:00
|
|
|
BitSet32 fprInUse, gprInUse, gprDiscardable, fprDiscardable, fprInXmm;
|
2014-04-30 03:49:17 -05:00
|
|
|
for (int i = block->m_num_instructions - 1; i >= 0; i--)
|
2014-04-30 00:14:24 -05:00
|
|
|
{
|
2018-05-19 15:29:09 -04:00
|
|
|
CodeOp& op = code[i];
|
|
|
|
|
|
|
|
const bool opWantsCR0 = op.wantsCR0;
|
|
|
|
const bool opWantsCR1 = op.wantsCR1;
|
|
|
|
const bool opWantsFPRF = op.wantsFPRF;
|
|
|
|
const bool opWantsCA = op.wantsCA;
|
2021-08-18 12:39:02 +02:00
|
|
|
op.wantsCR0 = wantsCR0 || op.canEndBlock || op.canCauseException;
|
|
|
|
op.wantsCR1 = wantsCR1 || op.canEndBlock || op.canCauseException;
|
|
|
|
op.wantsFPRF = wantsFPRF || op.canEndBlock || op.canCauseException;
|
|
|
|
op.wantsCA = wantsCA || op.canEndBlock || op.canCauseException;
|
|
|
|
wantsCR0 |= opWantsCR0 || op.canEndBlock || op.canCauseException;
|
|
|
|
wantsCR1 |= opWantsCR1 || op.canEndBlock || op.canCauseException;
|
|
|
|
wantsFPRF |= opWantsFPRF || op.canEndBlock || op.canCauseException;
|
|
|
|
wantsCA |= opWantsCA || op.canEndBlock || op.canCauseException;
|
2018-05-19 15:29:09 -04:00
|
|
|
wantsCR0 &= !op.outputCR0 || opWantsCR0;
|
|
|
|
wantsCR1 &= !op.outputCR1 || opWantsCR1;
|
|
|
|
wantsFPRF &= !op.outputFPRF || opWantsFPRF;
|
|
|
|
wantsCA &= !op.outputCA || opWantsCA;
|
|
|
|
op.gprInUse = gprInUse;
|
|
|
|
op.fprInUse = fprInUse;
|
2020-12-20 13:23:17 +01:00
|
|
|
op.gprDiscardable = gprDiscardable;
|
|
|
|
op.fprDiscardable = fprDiscardable;
|
2018-05-19 15:29:09 -04:00
|
|
|
op.fprInXmm = fprInXmm;
|
2022-02-14 22:09:21 +01:00
|
|
|
gprInUse |= op.regsIn | op.regsOut;
|
|
|
|
fprInUse |= op.fregsIn | op.GetFregsOut();
|
2020-12-20 13:23:17 +01:00
|
|
|
if (op.canEndBlock || op.canCauseException)
|
|
|
|
{
|
|
|
|
gprDiscardable = BitSet32{};
|
|
|
|
fprDiscardable = BitSet32{};
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
gprDiscardable |= op.regsOut;
|
|
|
|
gprDiscardable &= ~op.regsIn;
|
2021-04-17 13:53:03 +02:00
|
|
|
fprDiscardable |= op.GetFregsOut();
|
2020-12-20 13:23:17 +01:00
|
|
|
fprDiscardable &= ~op.fregsIn;
|
|
|
|
}
|
2018-05-19 15:29:09 -04:00
|
|
|
if (strncmp(op.opinfo->opname, "stfd", 4))
|
|
|
|
fprInXmm |= op.fregsIn;
|
2014-10-11 13:07:31 -07:00
|
|
|
}
|
2016-06-24 10:43:46 +02:00
|
|
|
|
2014-10-11 14:22:44 -07:00
|
|
|
// Forward scan, for flags that need the other direction for calculation.
|
2016-06-17 21:31:27 +10:00
|
|
|
BitSet32 fprIsSingle, fprIsDuplicated, fprIsStoreSafe, gprDefined, gprBlockInputs;
|
2015-01-04 04:20:59 -08:00
|
|
|
BitSet8 gqrUsed, gqrModified;
|
2014-10-11 13:07:31 -07:00
|
|
|
for (u32 i = 0; i < block->m_num_instructions; i++)
|
|
|
|
{
|
2018-05-19 15:29:09 -04:00
|
|
|
CodeOp& op = code[i];
|
2016-06-17 21:31:27 +10:00
|
|
|
|
2018-05-19 15:29:09 -04:00
|
|
|
gprBlockInputs |= op.regsIn & ~gprDefined;
|
|
|
|
gprDefined |= op.regsOut;
|
|
|
|
|
|
|
|
op.fprIsSingle = fprIsSingle;
|
|
|
|
op.fprIsDuplicated = fprIsDuplicated;
|
2021-02-02 21:43:50 +01:00
|
|
|
op.fprIsStoreSafeBeforeInst = fprIsStoreSafe;
|
2018-05-19 15:29:09 -04:00
|
|
|
if (op.fregOut >= 0)
|
2014-10-11 13:07:31 -07:00
|
|
|
{
|
2021-05-15 16:28:36 +02:00
|
|
|
BitSet32 bitexact_inputs;
|
|
|
|
if (op.opinfo->flags &
|
|
|
|
(FL_IN_FLOAT_A_BITEXACT | FL_IN_FLOAT_B_BITEXACT | FL_IN_FLOAT_C_BITEXACT))
|
|
|
|
{
|
|
|
|
if (op.opinfo->flags & FL_IN_FLOAT_A_BITEXACT)
|
|
|
|
bitexact_inputs[op.inst.FA] = true;
|
|
|
|
if (op.opinfo->flags & FL_IN_FLOAT_B_BITEXACT)
|
|
|
|
bitexact_inputs[op.inst.FB] = true;
|
|
|
|
if (op.opinfo->flags & FL_IN_FLOAT_C_BITEXACT)
|
|
|
|
bitexact_inputs[op.inst.FC] = true;
|
|
|
|
}
|
|
|
|
|
2021-05-15 18:00:56 +02:00
|
|
|
if (op.opinfo->type == OpType::SingleFP || !strncmp(op.opinfo->opname, "frsp", 4))
|
2014-10-11 14:22:44 -07:00
|
|
|
{
|
2018-05-19 15:29:09 -04:00
|
|
|
fprIsSingle[op.fregOut] = true;
|
|
|
|
fprIsDuplicated[op.fregOut] = true;
|
2014-10-11 14:22:44 -07:00
|
|
|
}
|
2021-02-02 20:50:29 +01:00
|
|
|
else if (!strncmp(op.opinfo->opname, "lfs", 3))
|
2014-10-11 14:22:44 -07:00
|
|
|
{
|
2018-05-19 15:29:09 -04:00
|
|
|
fprIsSingle[op.fregOut] = true;
|
|
|
|
fprIsDuplicated[op.fregOut] = true;
|
2014-10-11 14:22:44 -07:00
|
|
|
}
|
2021-05-15 16:28:36 +02:00
|
|
|
else if (bitexact_inputs)
|
|
|
|
{
|
|
|
|
fprIsSingle[op.fregOut] = (fprIsSingle & bitexact_inputs) == bitexact_inputs;
|
|
|
|
fprIsDuplicated[op.fregOut] = false;
|
|
|
|
}
|
2021-02-02 20:50:29 +01:00
|
|
|
else if (op.opinfo->type == OpType::PS || op.opinfo->type == OpType::LoadPS)
|
2014-10-11 14:22:44 -07:00
|
|
|
{
|
2018-05-19 15:29:09 -04:00
|
|
|
fprIsSingle[op.fregOut] = true;
|
2021-02-02 20:50:29 +01:00
|
|
|
fprIsDuplicated[op.fregOut] = false;
|
2014-10-11 14:22:44 -07:00
|
|
|
}
|
2021-02-02 20:50:29 +01:00
|
|
|
else
|
|
|
|
{
|
|
|
|
fprIsSingle[op.fregOut] = false;
|
|
|
|
fprIsDuplicated[op.fregOut] = false;
|
|
|
|
}
|
|
|
|
|
2018-05-19 15:29:09 -04:00
|
|
|
if (!strncmp(op.opinfo->opname, "mtfs", 4))
|
2021-02-02 20:50:29 +01:00
|
|
|
{
|
|
|
|
// Careful: changing the float mode in a block breaks the store-safe optimization,
|
|
|
|
// since a previous float op might have had FTZ off while the later store has FTZ on.
|
|
|
|
// So, discard all information we have.
|
2014-10-11 14:22:44 -07:00
|
|
|
fprIsStoreSafe = BitSet32(0);
|
2021-02-02 20:50:29 +01:00
|
|
|
}
|
2021-05-15 16:28:36 +02:00
|
|
|
else if (bitexact_inputs)
|
2021-02-02 20:50:29 +01:00
|
|
|
{
|
|
|
|
// If the instruction copies bits between registers (without flushing denormals to zero
|
|
|
|
// or turning SNaN into QNaN), the output is store-safe if the inputs are.
|
|
|
|
fprIsStoreSafe[op.fregOut] = (fprIsStoreSafe & bitexact_inputs) == bitexact_inputs;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Other FPU instructions are store-safe if they perform a single-precision
|
|
|
|
// arithmetic operation.
|
|
|
|
|
|
|
|
// TODO: if we go directly from a load to store, skip conversion entirely?
|
|
|
|
// TODO: if we go directly from a load to a float instruction, and the value isn't used
|
|
|
|
// for anything else, we can use fast single -> double conversion after the load.
|
|
|
|
|
2021-05-15 18:00:56 +02:00
|
|
|
fprIsStoreSafe[op.fregOut] = op.opinfo->type == OpType::SingleFP ||
|
|
|
|
op.opinfo->type == OpType::PS ||
|
|
|
|
!strncmp(op.opinfo->opname, "frsp", 4);
|
2021-02-02 20:50:29 +01:00
|
|
|
}
|
2014-09-25 16:01:29 -07:00
|
|
|
}
|
2021-02-02 21:43:50 +01:00
|
|
|
op.fprIsStoreSafeAfterInst = fprIsStoreSafe;
|
2016-06-24 10:43:46 +02:00
|
|
|
|
2018-05-19 15:29:09 -04:00
|
|
|
if (op.opinfo->type == OpType::StorePS || op.opinfo->type == OpType::LoadPS)
|
2015-01-04 04:20:59 -08:00
|
|
|
{
|
2018-05-19 15:29:09 -04:00
|
|
|
const int gqr = op.inst.OPCD == 4 ? op.inst.Ix : op.inst.I;
|
2015-01-04 04:20:59 -08:00
|
|
|
gqrUsed[gqr] = true;
|
|
|
|
}
|
2016-06-24 10:43:46 +02:00
|
|
|
|
2018-05-19 15:29:09 -04:00
|
|
|
if (op.inst.OPCD == 31 && op.inst.SUBOP10 == 467) // mtspr
|
2015-01-04 04:20:59 -08:00
|
|
|
{
|
2018-05-19 15:29:09 -04:00
|
|
|
const int gqr = ((op.inst.SPRU << 5) | op.inst.SPRL) - SPR_GQR0;
|
2015-01-04 04:20:59 -08:00
|
|
|
if (gqr >= 0 && gqr <= 7)
|
|
|
|
gqrModified[gqr] = true;
|
|
|
|
}
|
2014-04-30 00:14:24 -05:00
|
|
|
}
|
2015-01-04 04:20:59 -08:00
|
|
|
block->m_gqr_used = gqrUsed;
|
|
|
|
block->m_gqr_modified = gqrModified;
|
2016-06-17 21:31:27 +10:00
|
|
|
block->m_gpr_inputs = gprBlockInputs;
|
2014-04-30 00:14:24 -05:00
|
|
|
return address;
|
|
|
|
}
|
|
|
|
|
2019-05-05 23:48:12 +00:00
|
|
|
} // namespace PPCAnalyst
|