jak-project/decompiler/analysis/stack_spill.cpp
Hat Kid 2969833b2d
decomp3: more engine stuff, detect non-virtual state inheritance (#3377)
- `speech`
- `ambient`
- `water-h`
- `vol-h`
- `generic-obs`
- `carry-h`
- `pilot-h`
- `board-h`
- `gun-h`
- `flut-h`
- `indax-h`
- `lightjak-h`
- `darkjak-h`
- `target-util`
- `history`
- `collide-reaction-target`
- `logic-target`
- `sidekick`
- `projectile`
- `voicebox`
- `ragdoll-edit`
- most of `ragdoll` (not added to gsrc yet)
- `curves`
- `find-nearest`
- `lightjak-wings`
- `target-handler`
- `target-anim`
- `target`
- `target2`
- `target-swim`
- `target-lightjak`
- `target-invisible`
- `target-death`
- `target-gun`
- `gun-util`
- `board-util`
- `target-board`
- `board-states`
- `mech-h`
- `vol`
- `vent`
- `viewer`
- `gem-pool`
- `collectables`
- `crates`
- `secrets-menu`

Additionally:

- Detection of non-virtual state inheritance
- Added a config file that allows overriding the process stack size set
by `stack-size-set!` calls
- Fix for integer multiplication with `r0`
- Fixed detection for the following macros:
	- `static-attack-info`
- `defpart` and `defpartgroup` (probably still needs adjustments, uses
Jak 2 implementation at the moment)
- `sound-play` (Jak 3 seems to always call `sound-play-by-name` with a
`sound-group` of 0, so the macro has been temporarily defaulted to use
that)

One somewhat significant change made here that should be noted is that
the return type of `process::init-from-entity!` was changed to `object`.
I've been thinking about this for a while, since it looks a bit nicer
without the `(none)` at the end and I have recently encountered init
methods that early return `0`.
2024-03-03 15:15:27 -05:00

119 lines
3.8 KiB
C++

#include <stdexcept>
#include "decompiler/Disasm/DecompilerLabel.h"
#include "stack_spill.h"
#include "third-party/fmt/core.h"
namespace decompiler {
std::string StackSpillSlot::print() const {
return fmt::format("[{:3d}] {}{}", offset, is_signed ? 's' : 'u', size * 8);
}
void StackSpillMap::add_access(const StackSpillSlot& access) {
auto existing = m_slot_map.find(access.offset);
if (existing != m_slot_map.end()) {
if (access != existing->second) {
// the GOAL float -> GPR loads are just totally wrong.
if (existing->second.size == 16 && access.size == 4) {
existing->second.size = 4;
return;
}
if (existing->second.size == 4 && access.size == 16) {
return;
}
throw std::runtime_error(fmt::format("Inconsistent stack access:\n{}\n{}\n",
existing->second.print(), access.print()));
}
} else {
m_slot_map.insert({access.offset, access});
}
}
const StackSpillSlot& StackSpillMap::lookup(int offset) const {
auto result = m_slot_map.find(offset);
if (result == m_slot_map.end()) {
throw std::runtime_error(fmt::format("unknown stack spill slot at offset {}", offset));
}
return result->second;
}
void StackSpillMap::finalize() {
// how many variables exist at each byte. should be 1 or 0.
int max_offset = 0;
for (auto& slot : m_slot_map) {
max_offset = std::max(max_offset, slot.second.offset + slot.second.size);
}
ASSERT(max_offset < 8192); // just a sanity check here
std::vector<int> var_count(max_offset, 0);
for (auto& slot : m_slot_map) {
for (int i = 0; i < slot.second.size; i++) {
var_count.at(slot.second.offset + i)++;
}
}
for (size_t i = 0; i < var_count.size(); i++) {
if (var_count[i] > 1) {
throw std::runtime_error(
fmt::format("There are {} variables at stack offset {}", var_count[i], i));
}
}
}
int StackSpillMap::size() const {
return m_slot_map.size();
}
namespace {
struct StackInstrInfo {
InstructionKind kind;
bool is_load;
int size;
bool is_signed;
};
constexpr StackInstrInfo stack_instrs[] = {{InstructionKind::SQ, false, 16, false},
{InstructionKind::LQ, true, 16, false},
{InstructionKind::SW, false, 4, false},
{InstructionKind::SH, false, 2, false},
{InstructionKind::SB, false, 1, false},
{InstructionKind::LBU, true, 1, false},
//{InstructionKind::LWU, true, 4, false}
{InstructionKind::SD, false, 8, false},
{InstructionKind::SWC1, false, 4, false},
{InstructionKind::LWC1, true, 4, false},
{InstructionKind::SB, false, 1, false},
{InstructionKind::LBU, true, 1, false}};
} // namespace
StackSpillMap build_spill_map(const std::vector<Instruction>& instructions, Range<int> range) {
StackSpillMap map;
for (auto idx : range) {
auto& instr = instructions.at(idx);
for (auto& instr_template : stack_instrs) {
if (instr.kind == instr_template.kind) {
// we are the right kind.
auto src_reg = instr.get_src(instr_template.is_load ? 1 : 2).get_reg();
if (src_reg == Register(Reg::GPR, Reg::SP)) {
StackSpillSlot slot;
slot.offset = instr.get_src(instr_template.is_load ? 0 : 1).get_imm();
slot.size = instr_template.size;
slot.is_signed = instr_template.is_signed;
map.add_access(slot);
}
break;
}
}
}
map.finalize();
return map;
}
} // namespace decompiler