mirror of
https://github.com/open-goal/jak-project
synced 2026-08-01 08:27:22 -04:00
[Decompiler] Flag uses of vf registers for values outside of functions (#263)
* clean up warning system * add bad dependency warning * add q and acc
This commit is contained in:
@@ -359,6 +359,7 @@ void init_opcode_info() {
|
||||
.src_gpr(FT::RS); // Load Quadword to COP2
|
||||
|
||||
// COP2
|
||||
// NOTE: if adding more here, update AtomicOp.cpp AsmOp::update_register_info()
|
||||
cd_dvft_svfs(def(IK::VMOVE, "vmove")); // Transfer between Floating-Point Registers
|
||||
cd_dvft_svfs(def(IK::VFTOI0, "vftoi0")); // Conversion to Fixed Point
|
||||
cd_dvft_svfs(def(IK::VFTOI4, "vftoi4")); // Conversion to Fixed Point
|
||||
|
||||
@@ -208,7 +208,7 @@ enum class InstructionKind {
|
||||
RSQRTS,
|
||||
|
||||
// COP2
|
||||
VMOVE,
|
||||
VMOVE, // first cop2 macro instruction
|
||||
VFTOI0,
|
||||
VFTOI4,
|
||||
VFTOI12,
|
||||
@@ -231,6 +231,7 @@ enum class InstructionKind {
|
||||
VMUL_BC,
|
||||
VMULA_BC,
|
||||
VMADD_BC,
|
||||
|
||||
VADDA_BC,
|
||||
VMADDA_BC,
|
||||
VMSUBA_BC,
|
||||
@@ -267,11 +268,14 @@ enum class InstructionKind {
|
||||
VRNEXT,
|
||||
VNOP,
|
||||
VWAITQ,
|
||||
VCALLMS,
|
||||
VCALLMS, // last cop2 macro instruction
|
||||
|
||||
EE_OP_MAX
|
||||
};
|
||||
|
||||
constexpr InstructionKind FIRST_COP2_MACRO = InstructionKind::VMOVE;
|
||||
constexpr InstructionKind LAST_COP2_MACRO = InstructionKind::VCALLMS;
|
||||
|
||||
enum class FieldType {
|
||||
RS,
|
||||
RT,
|
||||
|
||||
@@ -53,11 +53,13 @@ const static char* vf_names[32] = {"vf0", "vf1", "vf2", "vf3", "vf4", "vf5"
|
||||
const static char* vi_names[32] = {
|
||||
"vi0", "vi1", "vi2", "vi3", "vi4", "vi5", "vi6", "vi7",
|
||||
"vi8", "vi9", "vi10", "vi11", "vi12", "vi13", "vi14", "vi15",
|
||||
"Status", "MAC", "Clipping", "INVALID3", "R", "I", "Q", "INVALID7",
|
||||
"Status", "MAC", "Clipping", "INVALID3", "vi_R", "vi_I", "vi_Q", "INVALID7",
|
||||
"INVALID8", "INVALID9", "TPC", "CMSAR0", "FBRST", "VPU-STAT", "INVALID14", "CMSAR1"};
|
||||
|
||||
const static char* pcr_names[2] = {"pcr0", "pcr1"};
|
||||
|
||||
const static char* cop2_macro_special[2] = {"Q", "ACC"};
|
||||
|
||||
/////////////////////////////
|
||||
// Register Names Conversion
|
||||
/////////////////////////////
|
||||
@@ -92,6 +94,11 @@ const char* pcr_to_charp(uint32_t pcr) {
|
||||
assert(pcr < 2);
|
||||
return pcr_names[pcr];
|
||||
}
|
||||
|
||||
const char* cop2_macro_special_to_charp(uint32_t reg) {
|
||||
assert(reg < 2);
|
||||
return cop2_macro_special[reg];
|
||||
}
|
||||
} // namespace
|
||||
|
||||
/////////////////////////////
|
||||
@@ -120,6 +127,7 @@ Register::Register(Reg::RegisterKind kind, uint32_t num) {
|
||||
assert(num < 32);
|
||||
break;
|
||||
case Reg::PCR:
|
||||
case Reg::COP2_MACRO_SPECIAL:
|
||||
assert(num < 2);
|
||||
break;
|
||||
default:
|
||||
@@ -164,6 +172,8 @@ const char* Register::to_charp() const {
|
||||
return cop0_to_charp(get_cop0());
|
||||
case Reg::PCR:
|
||||
return pcr_to_charp(get_pcr());
|
||||
case Reg::COP2_MACRO_SPECIAL:
|
||||
return cop2_macro_special_to_charp(get_cop2_macro_special());
|
||||
default:
|
||||
throw std::runtime_error("Unsupported Register");
|
||||
}
|
||||
@@ -245,6 +255,13 @@ uint32_t Register::get_pcr() const {
|
||||
return kind;
|
||||
}
|
||||
|
||||
Reg::Cop2MacroSpecial Register::get_cop2_macro_special() const {
|
||||
assert(get_kind() == Reg::COP2_MACRO_SPECIAL);
|
||||
uint16_t k = id & 0xff;
|
||||
assert(k < 2);
|
||||
return (Reg::Cop2MacroSpecial)k;
|
||||
}
|
||||
|
||||
bool Register::operator==(const Register& other) const {
|
||||
return id == other.id;
|
||||
}
|
||||
|
||||
@@ -5,9 +5,6 @@
|
||||
* Representation of an EE register.
|
||||
*/
|
||||
|
||||
#ifndef NEXT_REGISTER_H
|
||||
#define NEXT_REGISTER_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
|
||||
@@ -20,9 +17,10 @@ enum RegisterKind {
|
||||
VF = 2, // VU0 Floating point vector registers from EE, just called vf0 - vf31
|
||||
VI =
|
||||
3, // VU0 Integer registers from EE, the first 16 are vi00 - vi15, the rest are control regs.
|
||||
COP0 = 4, // EE COP0 Control Registers: full of fancy names (there are 32 of them)
|
||||
PCR = 5, // Performance Counter registers (PCR0, PCR1)
|
||||
MAX_KIND = 6
|
||||
COP0 = 4, // EE COP0 Control Registers: full of fancy names (there are 32 of them)
|
||||
PCR = 5, // Performance Counter registers (PCR0, PCR1)
|
||||
COP2_MACRO_SPECIAL = 6, // COP2 Q, ACC accessed from macro mode instructions.
|
||||
MAX_KIND = 7
|
||||
};
|
||||
|
||||
// nicknames for GPRs
|
||||
@@ -122,6 +120,11 @@ enum Vi {
|
||||
MAX_COP2 = 32
|
||||
};
|
||||
|
||||
enum Cop2MacroSpecial {
|
||||
MACRO_Q = 0,
|
||||
MACRO_ACC = 1,
|
||||
};
|
||||
|
||||
const extern bool allowed_local_gprs[Reg::MAX_GPR];
|
||||
|
||||
} // namespace Reg
|
||||
@@ -141,6 +144,7 @@ class Register {
|
||||
uint32_t get_vi() const;
|
||||
Reg::Cop0 get_cop0() const;
|
||||
uint32_t get_pcr() const;
|
||||
Reg::Cop2MacroSpecial get_cop2_macro_special() const;
|
||||
|
||||
bool operator==(const Register& other) const;
|
||||
bool operator!=(const Register& other) const;
|
||||
@@ -154,4 +158,3 @@ class Register {
|
||||
uint16_t id = -1;
|
||||
};
|
||||
} // namespace decompiler
|
||||
#endif // NEXT_REGISTER_H
|
||||
|
||||
@@ -1892,7 +1892,7 @@ std::shared_ptr<ControlFlowGraph> build_cfg(const LinkedObjectFile& file, int se
|
||||
}
|
||||
|
||||
if (!cfg->is_fully_resolved()) {
|
||||
func.warnings += ";; Failed to fully resolve CFG\n";
|
||||
func.warnings.cfg_build_warning("Could not fully resolve CFG");
|
||||
}
|
||||
|
||||
return cfg;
|
||||
|
||||
@@ -75,9 +75,11 @@ void Function::analyze_prologue(const LinkedObjectFile& file) {
|
||||
auto& instr = instructions.at(idx);
|
||||
// storing stack pointer on the stack is done by some ASM kernel functions
|
||||
if (instr.kind == InstructionKind::SW && instr.get_src(0).get_reg() == make_gpr(Reg::SP)) {
|
||||
printf("[Warning] %s Suspected ASM function based on this instruction in prologue: %s\n",
|
||||
guessed_name.to_string().c_str(), instr.to_string(file.labels).c_str());
|
||||
warnings += ";; Flagged as ASM function because of " + instr.to_string(file.labels) + "\n";
|
||||
lg::warn(
|
||||
"Function {} was flagged as asm due to this instruction: {}. Consider flagging as asm "
|
||||
"in config!",
|
||||
guessed_name.to_string(), instr.to_string(file.labels));
|
||||
warnings.general_warning("Flagged as asm because of {}", instr.to_string(file.labels));
|
||||
suspected_asm = true;
|
||||
return;
|
||||
}
|
||||
@@ -98,9 +100,11 @@ void Function::analyze_prologue(const LinkedObjectFile& file) {
|
||||
// storing s7 on the stack is done by interrupt handlers, which we probably don't want to
|
||||
// support
|
||||
if (instr.kind == InstructionKind::SD && instr.get_src(0).get_reg() == make_gpr(Reg::S7)) {
|
||||
lg::warn("{} Suspected ASM function based on this instruction in prologue: {}\n",
|
||||
guessed_name.to_string(), instr.to_string(file.labels));
|
||||
warnings += ";; Flagged as ASM function because of " + instr.to_string(file.labels) + "\n";
|
||||
lg::warn(
|
||||
"Function {} was flagged as asm due to this instruction: {}. Consider flagging as asm "
|
||||
"in config!",
|
||||
guessed_name.to_string(), instr.to_string(file.labels));
|
||||
warnings.general_warning("Flagged as asm because of {}", instr.to_string(file.labels));
|
||||
suspected_asm = true;
|
||||
return;
|
||||
}
|
||||
@@ -136,11 +140,9 @@ void Function::analyze_prologue(const LinkedObjectFile& file) {
|
||||
// sometimes stack memory is zeroed immediately after gpr backups, and this fools the previous
|
||||
// check.
|
||||
if (store_reg == make_gpr(Reg::R0)) {
|
||||
printf(
|
||||
"[Warning] %s Stack Zeroing Detected in Function::analyze_prologue, prologue may be "
|
||||
"wrong\n",
|
||||
guessed_name.to_string().c_str());
|
||||
warnings += ";; Stack Zeroing Detected, prologue may be wrong\n";
|
||||
lg::warn("Function {} has stack zeroing, manually check prologue!",
|
||||
guessed_name.to_string());
|
||||
warnings.general_warning("Stack zeroing, check prologue!");
|
||||
expect_nothing_after_gprs = true;
|
||||
break;
|
||||
}
|
||||
@@ -149,10 +151,8 @@ void Function::analyze_prologue(const LinkedObjectFile& file) {
|
||||
// avoid false positives here!
|
||||
if (store_reg == make_gpr(Reg::A0)) {
|
||||
suspected_asm = true;
|
||||
printf(
|
||||
"[Warning] %s Suspected ASM function because register $a0 was stored on the stack!\n",
|
||||
guessed_name.to_string().c_str());
|
||||
warnings += ";; a0 on stack detected, flagging as asm\n";
|
||||
lg::warn("Function {} stores a0 on the stack, flagging as asm!", guessed_name.to_string());
|
||||
warnings.general_warning("Flagged as asm due to storing a0 on stack");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -168,11 +168,10 @@ void Function::analyze_prologue(const LinkedObjectFile& file) {
|
||||
assert(this_offset == prologue.gpr_backup_offset + 16 * i);
|
||||
if (this_reg != get_expected_gpr_backup(i, n_gpr_backups)) {
|
||||
suspected_asm = true;
|
||||
printf("[Warning] %s Suspected asm function that isn't flagged due to stack store %s\n",
|
||||
guessed_name.to_string().c_str(),
|
||||
instructions.at(idx + i).to_string(file.labels).c_str());
|
||||
warnings += ";; Suspected asm function due to stack store: " +
|
||||
instructions.at(idx + i).to_string(file.labels) + "\n";
|
||||
lg::warn("Function {} stores on the stack in a strange way ({}), flagging as asm!",
|
||||
instructions.at(idx + i).to_string(file.labels), guessed_name.to_string());
|
||||
warnings.general_warning("Flagged as asm due to strange stack store: {}",
|
||||
instructions.at(idx + i).to_string(file.labels));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -198,11 +197,10 @@ void Function::analyze_prologue(const LinkedObjectFile& file) {
|
||||
assert(this_offset == prologue.fpr_backup_offset + 4 * i);
|
||||
if (this_reg != get_expected_fpr_backup(i, n_fpr_backups)) {
|
||||
suspected_asm = true;
|
||||
printf("[Warning] %s Suspected asm function that isn't flagged due to stack store %s\n",
|
||||
guessed_name.to_string().c_str(),
|
||||
instructions.at(idx + i).to_string(file.labels).c_str());
|
||||
warnings += ";; Suspected asm function due to stack store: " +
|
||||
instructions.at(idx + i).to_string(file.labels) + "\n";
|
||||
lg::warn("Function {} stores on the stack in a strange way ({}), flagging as asm!",
|
||||
instructions.at(idx + i).to_string(file.labels), guessed_name.to_string());
|
||||
warnings.general_warning("Flagged as asm due to strange stack store: {}",
|
||||
instructions.at(idx + i).to_string(file.labels));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -358,11 +356,9 @@ void Function::check_epilogue(const LinkedObjectFile& file) {
|
||||
idx--;
|
||||
assert(is_jr_ra(instructions.at(idx)));
|
||||
idx--;
|
||||
printf(
|
||||
"[Warning] %s Double Return Epilogue Hack! This is probably an ASM function in "
|
||||
"disguise\n",
|
||||
guessed_name.to_string().c_str());
|
||||
warnings += ";; Double Return Epilogue - this is probably an ASM function\n";
|
||||
lg::warn("Function {} has a double return and is being flagged as asm.",
|
||||
guessed_name.to_string());
|
||||
warnings.general_warning("Flagged as asm due to double return");
|
||||
}
|
||||
// delay slot should be daddiu sp, sp, offset
|
||||
assert(is_gpr_2_imm_int(instructions.at(idx), InstructionKind::DADDIU, make_gpr(Reg::SP),
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "CfgVtx.h"
|
||||
#include "common/type_system/TypeSpec.h"
|
||||
#include "decompiler/config.h"
|
||||
#include "Warnings.h"
|
||||
|
||||
namespace decompiler {
|
||||
class DecompilerTypeSystem;
|
||||
@@ -120,7 +121,8 @@ class Function {
|
||||
int epilogue_start = -1;
|
||||
int epilogue_end = -1;
|
||||
|
||||
std::string warnings;
|
||||
DecompWarnings warnings;
|
||||
|
||||
bool contains_asm_ops = false;
|
||||
|
||||
bool attempted_type_analysis = false;
|
||||
|
||||
@@ -116,8 +116,8 @@ bool Function::run_type_analysis_ir2(const TypeSpec& my_type,
|
||||
try {
|
||||
op_types.at(op_id) = op->propagate_types(*init_types, ir2.env, dts);
|
||||
} catch (std::runtime_error& e) {
|
||||
fmt::print("Type prop fail on {}: {}\n", guessed_name.to_string(), e.what());
|
||||
warnings += ";; Type prop attempted and failed.\n";
|
||||
lg::warn("Function {} failed type prop: {}", guessed_name.to_string(), e.what());
|
||||
warnings.type_prop_warning("{}", e.what());
|
||||
ir2.env.set_types(block_init_types, op_types, *ir2.atomic_ops);
|
||||
return false;
|
||||
}
|
||||
@@ -147,8 +147,7 @@ bool Function::run_type_analysis_ir2(const TypeSpec& my_type,
|
||||
|
||||
auto last_type = op_types.back().get(Register(Reg::GPR, Reg::V0)).typespec();
|
||||
if (last_type != my_type.last_arg()) {
|
||||
warnings += fmt::format(";; return type mismatch {} vs {}.\n", last_type.print(),
|
||||
my_type.last_arg().print());
|
||||
warnings.info("Return type mismatch {} vs {}.", last_type.print(), my_type.last_arg().print());
|
||||
}
|
||||
|
||||
ir2.env.set_types(block_init_types, op_types, *ir2.atomic_ops);
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
#pragma once
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <cassert>
|
||||
|
||||
#include "third-party/fmt/core.h"
|
||||
|
||||
class DecompWarnings {
|
||||
public:
|
||||
DecompWarnings() = default;
|
||||
|
||||
template <typename... Args>
|
||||
void general_warning(const std::string& str, Args&&... args) {
|
||||
warning(Warning::Kind::GENERAL, str, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
void expression_build_warning(const std::string& str, Args&&... args) {
|
||||
warning(Warning::Kind::EXPR_BUILD_FAILED, str, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
void cfg_build_warning(const std::string& str, Args&&... args) {
|
||||
warning(Warning::Kind::CFG_FAILED, str, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
void type_prop_warning(const std::string& str, Args&&... args) {
|
||||
warning(Warning::Kind::TYPE_PROP_FAILED, str, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
void bad_vf_dependency(const std::string& str, Args&&... args) {
|
||||
warning(Warning::Kind::BAD_VF_DEPENDENCY, str, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
void info(const std::string& str, Args&&... args) {
|
||||
warning(Warning::Kind::INFO, str, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
bool has_warnings() const { return !m_warnings.empty(); }
|
||||
|
||||
std::string get_warning_text(bool as_comment) const {
|
||||
std::string result;
|
||||
for (auto& w : m_warnings) {
|
||||
if (as_comment) {
|
||||
result += ";; ";
|
||||
}
|
||||
result += w.print();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
struct Warning {
|
||||
enum class Kind {
|
||||
GENERAL,
|
||||
EXPR_BUILD_FAILED,
|
||||
CFG_FAILED,
|
||||
TYPE_PROP_FAILED,
|
||||
INFO,
|
||||
BAD_VF_DEPENDENCY
|
||||
};
|
||||
Warning(Kind kind, std::string text) : warning_kind(kind), message(std::move(text)) {}
|
||||
|
||||
std::string print() const {
|
||||
switch (warning_kind) {
|
||||
case Kind::GENERAL:
|
||||
return fmt::format("WARN: {}\n", message);
|
||||
case Kind::EXPR_BUILD_FAILED:
|
||||
return fmt::format("WARN: Expression building failed: {}\n", message);
|
||||
case Kind::CFG_FAILED:
|
||||
return fmt::format("WARN: CFG building failed: {}\n", message);
|
||||
case Kind::TYPE_PROP_FAILED:
|
||||
return fmt::format("WARN: Type Propagation failed: {}\n", message);
|
||||
case Kind::BAD_VF_DEPENDENCY:
|
||||
return fmt::format("WARN: Bad vector register dependency: {}\n", message);
|
||||
case Kind::INFO:
|
||||
return fmt::format("INFO: {}\n", message);
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
Kind warning_kind;
|
||||
std::string message;
|
||||
};
|
||||
|
||||
template <typename... Args>
|
||||
void warning(Warning::Kind kind, const std::string& str, Args&&... args) {
|
||||
Warning warn(kind, fmt::format(str, std::forward<Args>(args)...));
|
||||
m_warnings.push_back(warn);
|
||||
}
|
||||
|
||||
std::vector<Warning> m_warnings;
|
||||
};
|
||||
@@ -2522,11 +2522,11 @@ void add_basic_ops_to_block(Function* func, const BasicBlock& block, LinkedObjec
|
||||
// everything failed
|
||||
if (!result) {
|
||||
// temp hack for debug:
|
||||
printf("Instruction -> BasicOp failed on %s\n", i.to_string(file->labels).c_str());
|
||||
lg::error("Instruction -> BasicOp failed on {}", i.to_string(file->labels));
|
||||
func->add_basic_op(std::make_shared<IR_Failed_Atomic>(), instr, instr + 1);
|
||||
} else {
|
||||
if (!func->contains_asm_ops && dynamic_cast<IR_AsmOp*>(result.get())) {
|
||||
func->warnings += ";; Function contains asm op\n";
|
||||
func->warnings.info("Contains asm ops");
|
||||
func->contains_asm_ops = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -422,7 +422,7 @@ AsmOp::AsmOp(Instruction instr, int my_idx) : AtomicOp(my_idx), m_instr(std::mov
|
||||
auto& dst = m_instr.get_dst(0);
|
||||
if (dst.is_reg()) {
|
||||
auto reg = dst.get_reg();
|
||||
if (reg.get_kind() == Reg::FPR || reg.get_kind() == Reg::GPR) {
|
||||
if (reg.get_kind() == Reg::FPR || reg.get_kind() == Reg::GPR || reg.get_kind() == Reg::VF) {
|
||||
m_dst = Variable(VariableMode::WRITE, reg, my_idx, true);
|
||||
}
|
||||
}
|
||||
@@ -433,7 +433,7 @@ AsmOp::AsmOp(Instruction instr, int my_idx) : AtomicOp(my_idx), m_instr(std::mov
|
||||
auto& src = m_instr.get_src(i);
|
||||
if (src.is_reg()) {
|
||||
auto reg = src.get_reg();
|
||||
if (reg.get_kind() == Reg::FPR || reg.get_kind() == Reg::GPR) {
|
||||
if (reg.get_kind() == Reg::FPR || reg.get_kind() == Reg::GPR || reg.get_kind() == Reg::VF) {
|
||||
m_src[i] = Variable(VariableMode::READ, reg, my_idx, true);
|
||||
}
|
||||
}
|
||||
@@ -497,6 +497,102 @@ void AsmOp::update_register_info() {
|
||||
m_read_regs.push_back(src->reg());
|
||||
}
|
||||
}
|
||||
|
||||
if (m_instr.kind >= FIRST_COP2_MACRO && m_instr.kind <= LAST_COP2_MACRO) {
|
||||
switch (m_instr.kind) {
|
||||
case InstructionKind::VMSUBQ:
|
||||
m_read_regs.push_back(Register(Reg::COP2_MACRO_SPECIAL, Reg::MACRO_Q));
|
||||
m_read_regs.push_back(Register(Reg::COP2_MACRO_SPECIAL, Reg::MACRO_ACC));
|
||||
break;
|
||||
|
||||
case InstructionKind::VMULAQ:
|
||||
m_read_regs.push_back(Register(Reg::COP2_MACRO_SPECIAL, Reg::MACRO_Q));
|
||||
m_write_regs.push_back(Register(Reg::COP2_MACRO_SPECIAL, Reg::MACRO_ACC));
|
||||
break;
|
||||
|
||||
// Read Q register
|
||||
case InstructionKind::VADDQ:
|
||||
case InstructionKind::VSUBQ:
|
||||
case InstructionKind::VMULQ:
|
||||
m_read_regs.push_back(Register(Reg::COP2_MACRO_SPECIAL, Reg::MACRO_Q));
|
||||
break;
|
||||
|
||||
// Write ACC register
|
||||
case InstructionKind::VADDA:
|
||||
case InstructionKind::VADDA_BC:
|
||||
case InstructionKind::VMULA:
|
||||
case InstructionKind::VMULA_BC:
|
||||
case InstructionKind::VOPMULA:
|
||||
m_write_regs.push_back(Register(Reg::COP2_MACRO_SPECIAL, Reg::MACRO_ACC));
|
||||
break;
|
||||
|
||||
// Write Q register
|
||||
case InstructionKind::VDIV:
|
||||
case InstructionKind::VSQRT:
|
||||
case InstructionKind::VRSQRT:
|
||||
m_write_regs.push_back(Register(Reg::COP2_MACRO_SPECIAL, Reg::MACRO_Q));
|
||||
break;
|
||||
|
||||
// Read acc register
|
||||
case InstructionKind::VMADD:
|
||||
case InstructionKind::VMADD_BC:
|
||||
case InstructionKind::VMSUB:
|
||||
case InstructionKind::VMSUB_BC:
|
||||
m_read_regs.push_back(Register(Reg::COP2_MACRO_SPECIAL, Reg::MACRO_ACC));
|
||||
break;
|
||||
case InstructionKind::VOPMSUB:
|
||||
m_read_regs.push_back(Register(Reg::COP2_MACRO_SPECIAL, Reg::MACRO_ACC));
|
||||
break;
|
||||
|
||||
// Read/Write acc register
|
||||
case InstructionKind::VMADDA:
|
||||
case InstructionKind::VMADDA_BC:
|
||||
case InstructionKind::VMSUBA_BC:
|
||||
m_write_regs.push_back(Register(Reg::COP2_MACRO_SPECIAL, Reg::MACRO_ACC));
|
||||
m_read_regs.push_back(Register(Reg::COP2_MACRO_SPECIAL, Reg::MACRO_ACC));
|
||||
break;
|
||||
|
||||
case InstructionKind::VMOVE:
|
||||
case InstructionKind::VFTOI0:
|
||||
case InstructionKind::VFTOI4:
|
||||
case InstructionKind::VFTOI12:
|
||||
case InstructionKind::VITOF0:
|
||||
case InstructionKind::VITOF12:
|
||||
case InstructionKind::VITOF15:
|
||||
case InstructionKind::VABS:
|
||||
case InstructionKind::VADD:
|
||||
case InstructionKind::VADD_BC:
|
||||
case InstructionKind::VSUB:
|
||||
case InstructionKind::VSUB_BC:
|
||||
case InstructionKind::VMUL:
|
||||
case InstructionKind::VMUL_BC:
|
||||
case InstructionKind::VMINI:
|
||||
case InstructionKind::VMINI_BC:
|
||||
case InstructionKind::VMAX:
|
||||
case InstructionKind::VMAX_BC:
|
||||
case InstructionKind::VCLIP:
|
||||
case InstructionKind::VNOP:
|
||||
|
||||
// anything using one of these should be manually analyzed.
|
||||
case InstructionKind::VMTIR:
|
||||
case InstructionKind::VIAND:
|
||||
case InstructionKind::VLQI:
|
||||
case InstructionKind::VIADDI:
|
||||
case InstructionKind::VSQI:
|
||||
case InstructionKind::VRGET:
|
||||
case InstructionKind::VRXOR:
|
||||
case InstructionKind::VRNEXT:
|
||||
case InstructionKind::VWAITQ: // okay if following vsqrt/vrsqrt/vdiv
|
||||
case InstructionKind::VCALLMS:
|
||||
|
||||
// do not read/write acc/q
|
||||
break;
|
||||
|
||||
default:
|
||||
assert(false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AsmOp::collect_vars(VariableSet& vars) const {
|
||||
|
||||
+18
-12
@@ -72,20 +72,24 @@ const std::string& Env::remapped_name(const std::string& name) const {
|
||||
}
|
||||
|
||||
goos::Object Env::get_variable_name(Register reg, int atomic_idx, VariableMode mode) const {
|
||||
std::string lookup_name = m_var_names.lookup(reg, atomic_idx, mode).name();
|
||||
auto remapped = m_var_remap.find(lookup_name);
|
||||
if (remapped != m_var_remap.end()) {
|
||||
lookup_name = remapped->second;
|
||||
}
|
||||
auto type_kv = m_typehints.find(atomic_idx);
|
||||
if (type_kv != m_typehints.end()) {
|
||||
for (auto& x : type_kv->second) {
|
||||
if (x.reg == reg) {
|
||||
return pretty_print::build_list("the-as", x.type_name, lookup_name);
|
||||
if (reg.get_kind() == Reg::FPR || reg.get_kind() == Reg::GPR) {
|
||||
std::string lookup_name = m_var_names.lookup(reg, atomic_idx, mode).name();
|
||||
auto remapped = m_var_remap.find(lookup_name);
|
||||
if (remapped != m_var_remap.end()) {
|
||||
lookup_name = remapped->second;
|
||||
}
|
||||
auto type_kv = m_typehints.find(atomic_idx);
|
||||
if (type_kv != m_typehints.end()) {
|
||||
for (auto& x : type_kv->second) {
|
||||
if (x.reg == reg) {
|
||||
return pretty_print::build_list("the-as", x.type_name, lookup_name);
|
||||
}
|
||||
}
|
||||
}
|
||||
return pretty_print::to_symbol(lookup_name);
|
||||
} else {
|
||||
return pretty_print::to_symbol(reg.to_charp());
|
||||
}
|
||||
return pretty_print::to_symbol(lookup_name);
|
||||
}
|
||||
|
||||
/*!
|
||||
@@ -169,7 +173,9 @@ std::vector<VariableNames::VarInfo> Env::extract_visible_variables(
|
||||
std::vector<std::pair<RegId, Variable>> vars;
|
||||
|
||||
for (auto& x : var_set) {
|
||||
vars.push_back(std::make_pair(get_ssa_var(x), x));
|
||||
if (x.reg().get_kind() == Reg::FPR || x.reg().get_kind() == Reg::GPR) {
|
||||
vars.push_back(std::make_pair(get_ssa_var(x), x));
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(vars.begin(), vars.end(),
|
||||
|
||||
@@ -548,7 +548,7 @@ std::string LinkedObjectFile::to_asm_json(const std::string& obj_file_name) {
|
||||
f["name"] = fname;
|
||||
f["type"] = func.type.print();
|
||||
f["segment"] = seg;
|
||||
f["warnings"] = func.warnings;
|
||||
f["warnings"] = func.warnings.get_warning_text(false);
|
||||
f["parent_object"] = obj_file_name;
|
||||
std::vector<nlohmann::json::object_t> ops;
|
||||
|
||||
@@ -601,8 +601,8 @@ std::string LinkedObjectFile::print_function_disassembly(Function& func,
|
||||
result += "; .function " + func.guessed_name.to_string() + " " + extra_name + "\n";
|
||||
result += ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n";
|
||||
result += func.prologue.to_string(2) + "\n";
|
||||
if (!func.warnings.empty()) {
|
||||
result += ";;Warnings:\n" + func.warnings + "\n";
|
||||
if (func.warnings.has_warnings()) {
|
||||
result += ";; Warnings:\n" + func.warnings.get_warning_text(true) + "\n";
|
||||
}
|
||||
|
||||
// print each instruction in the function.
|
||||
|
||||
@@ -716,7 +716,7 @@ void ObjectFileDB::analyze_functions_ir1() {
|
||||
unique_names.insert(name);
|
||||
|
||||
if (config.asm_functions_by_name.find(name) != config.asm_functions_by_name.end()) {
|
||||
func.warnings += ";; flagged as asm by config\n";
|
||||
func.warnings.info("Flagged as asm by config");
|
||||
func.suspected_asm = true;
|
||||
}
|
||||
}
|
||||
@@ -729,7 +729,7 @@ void ObjectFileDB::analyze_functions_ir1() {
|
||||
|
||||
if (duplicated_functions.find(name) != duplicated_functions.end()) {
|
||||
duplicated_functions[name].insert(data.to_unique_name());
|
||||
func.warnings += ";; this function exists in multiple non-identical object files\n";
|
||||
func.warnings.info("Exists in multiple non-identical object files");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -791,7 +791,6 @@ void ObjectFileDB::analyze_functions_ir1() {
|
||||
}
|
||||
} else {
|
||||
asm_funcs++;
|
||||
func.warnings.append(";; Assembly Function. Analysis passes were not attempted.\n");
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -119,7 +119,7 @@ void ObjectFileDB::ir2_top_level_pass() {
|
||||
|
||||
if (get_config().asm_functions_by_name.find(name) !=
|
||||
get_config().asm_functions_by_name.end()) {
|
||||
func.warnings += ";; flagged as asm by config\n";
|
||||
func.warnings.info("Flagged as asm by config");
|
||||
func.suspected_asm = true;
|
||||
}
|
||||
}
|
||||
@@ -133,7 +133,7 @@ void ObjectFileDB::ir2_top_level_pass() {
|
||||
|
||||
if (duplicated_functions.find(name) != duplicated_functions.end()) {
|
||||
duplicated_functions[name].insert(data.to_unique_name());
|
||||
func.warnings += ";; this function exists in multiple non-identical object files\n";
|
||||
func.warnings.info("this function exists in multiple non-identical object files");
|
||||
}
|
||||
});
|
||||
|
||||
@@ -207,7 +207,7 @@ void ObjectFileDB::ir2_basic_block_pass() {
|
||||
}
|
||||
|
||||
if (func.suspected_asm) {
|
||||
func.warnings.append(";; Assembly Function\n");
|
||||
func.warnings.info("Assembly Function");
|
||||
suspected_asm++;
|
||||
}
|
||||
});
|
||||
@@ -248,7 +248,7 @@ void ObjectFileDB::ir2_atomic_op_pass() {
|
||||
} catch (std::exception& e) {
|
||||
lg::warn("Function {} from {} could not be converted to atomic ops: {}",
|
||||
func.guessed_name.to_string(), data.to_unique_name(), e.what());
|
||||
func.warnings.append(";; Failed to convert to atomic ops\n");
|
||||
func.warnings.general_warning("Failed to convert to atomic ops: {}", e.what());
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -286,11 +286,12 @@ void ObjectFileDB::ir2_type_analysis_pass() {
|
||||
if (func.run_type_analysis_ir2(ts, dts, data.linked_data, hints)) {
|
||||
successful_functions++;
|
||||
} else {
|
||||
func.warnings.append(";; Type analysis failed\n");
|
||||
func.warnings.type_prop_warning("Type analysis failed");
|
||||
}
|
||||
} else {
|
||||
// lg::warn("Function {} didn't know its type", func.guessed_name.to_string());
|
||||
func.warnings.append(";; Type of function is unknown\n");
|
||||
func.warnings.type_prop_warning("Function {} has unknown type",
|
||||
func.guessed_name.to_string());
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -310,6 +311,19 @@ void ObjectFileDB::ir2_register_usage_pass() {
|
||||
if (!func.suspected_asm && func.ir2.atomic_ops_succeeded) {
|
||||
analyzed_funcs++;
|
||||
func.ir2.env.set_reg_use(analyze_ir2_register_usage(func));
|
||||
|
||||
auto block_0_start = func.ir2.env.reg_use().block.at(0).input;
|
||||
for (auto x : block_0_start) {
|
||||
if (x.get_kind() == Reg::VF && x.get_vf() != 0) {
|
||||
lg::error("Bad vf dependency on {} in {}", x.to_charp(), func.guessed_name.to_string());
|
||||
func.warnings.bad_vf_dependency("{}", x.to_string());
|
||||
}
|
||||
|
||||
if (x.get_kind() == Reg::COP2_MACRO_SPECIAL) {
|
||||
lg::error("Bad vf dependency on {} in {}", x.to_charp(), func.guessed_name.to_string());
|
||||
func.warnings.bad_vf_dependency("{}", x.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -531,8 +545,8 @@ std::string ObjectFileDB::ir2_function_to_string(ObjectFileData& data, Function&
|
||||
result += "; .function " + func.guessed_name.to_string() + "\n";
|
||||
result += ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n";
|
||||
result += func.prologue.to_string(2) + "\n";
|
||||
if (!func.warnings.empty()) {
|
||||
result += ";;Warnings:\n" + func.warnings + "\n";
|
||||
if (func.warnings.has_warnings()) {
|
||||
result += ";; Warnings:\n" + func.warnings.get_warning_text(true) + "\n";
|
||||
}
|
||||
|
||||
if (func.ir2.env.has_local_vars()) {
|
||||
|
||||
@@ -165,6 +165,7 @@ void clean_up_return_final(const Function& f, ReturnElement* ir) {
|
||||
if (!dead) {
|
||||
lg::error("failed to recognize dead code after return, got {}",
|
||||
ir->dead_code->to_string(f.ir2.env));
|
||||
throw std::runtime_error("failed to recognize dead code");
|
||||
}
|
||||
assert(dead);
|
||||
auto src = dynamic_cast<SimpleExpressionElement*>(dead->src()->try_as_single_element());
|
||||
|
||||
@@ -96,10 +96,7 @@ bool convert_to_expressions(Form* top_level_form,
|
||||
// fix up stuff
|
||||
|
||||
} catch (std::exception& e) {
|
||||
std::string warning =
|
||||
fmt::format("Expression building failed in {}: {}", f.guessed_name.to_string(), e.what());
|
||||
lg::warn(warning);
|
||||
f.warnings.append(";; " + warning);
|
||||
f.warnings.expression_build_warning("In {}: {}", f.guessed_name.to_string(), e.what());
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -104,6 +104,12 @@ std::string careful_function_to_string(
|
||||
const DecompilerTypeSystem& dts,
|
||||
FunctionDefSpecials special_mode = FunctionDefSpecials::NONE) {
|
||||
auto& env = func->ir2.env;
|
||||
|
||||
std::string result;
|
||||
if (func->warnings.has_warnings()) {
|
||||
result += func->warnings.get_warning_text(true);
|
||||
}
|
||||
|
||||
if (!func->ir2.top_form) {
|
||||
return ";; ERROR: function was not converted to expressions. Cannot decompile.\n\n";
|
||||
}
|
||||
@@ -119,7 +125,8 @@ std::string careful_function_to_string(
|
||||
return ";; ERROR: function has no register use analysis. Cannot decompile.\n\n";
|
||||
}
|
||||
|
||||
return final_defun_out(*func, func->ir2.env, dts, special_mode) + "\n\n";
|
||||
result += final_defun_out(*func, func->ir2.env, dts, special_mode) + "\n\n";
|
||||
return result;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
|
||||
@@ -312,7 +312,9 @@ SSA make_rc_ssa(const Function& function, const RegUsageInfo& rui, const Functio
|
||||
const auto& start_op_op = ops.ops.at(start_op);
|
||||
auto init_regs = start_op_info.live;
|
||||
for (auto reg : start_op_op->read_regs()) {
|
||||
init_regs.insert(reg);
|
||||
if (reg.get_kind() == Reg::FPR || reg.get_kind() == Reg::GPR) {
|
||||
init_regs.insert(reg);
|
||||
}
|
||||
}
|
||||
|
||||
for (auto reg : init_regs) {
|
||||
@@ -353,19 +355,23 @@ SSA make_rc_ssa(const Function& function, const RegUsageInfo& rui, const Functio
|
||||
assert(op->write_regs().size() <= 1);
|
||||
// reads:
|
||||
for (auto r : op->read_regs()) {
|
||||
ssa_i.src.push_back(current_regs.at(r));
|
||||
if (r.get_kind() == Reg::FPR || r.get_kind() == Reg::GPR) {
|
||||
ssa_i.src.push_back(current_regs.at(r));
|
||||
}
|
||||
}
|
||||
// writes:
|
||||
if (!op->write_regs().empty()) {
|
||||
auto w = op->write_regs().front();
|
||||
auto var = ssa.map.allocate(w);
|
||||
ssa_i.dst = var;
|
||||
// avoid operator[] again
|
||||
auto it = current_regs.find(w);
|
||||
if (it != current_regs.end()) {
|
||||
it->second = var;
|
||||
} else {
|
||||
current_regs.insert(std::make_pair(w, var));
|
||||
if (w.get_kind() == Reg::FPR || w.get_kind() == Reg::GPR) {
|
||||
auto var = ssa.map.allocate(w);
|
||||
ssa_i.dst = var;
|
||||
// avoid operator[] again
|
||||
auto it = current_regs.find(w);
|
||||
if (it != current_regs.end()) {
|
||||
it->second = var;
|
||||
} else {
|
||||
current_regs.insert(std::make_pair(w, var));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user