N64Recomp/src/analysis.h
Matthew Stanley bd5f42fb22 analysis: discover_function_bounds — real CFG walk with jump-table support
Adds a public N64Recomp::discover_function_bounds() in src/analysis.h
that performs a BFS-based control-flow walk of a function's body,
following:
  - Conditional branches (target + fall-through)
  - Unconditional j/jal targets when intra-body
  - jr $ra returns (block ends after delay slot)
  - jr-via-jump-table dispatches: the existing register-state
    simulator from analyze_function detects the lui+addiu+addu+lw+jr
    pattern and records the jtbl base; we then read entries out of
    the body bytes and feed targets back into the BFS until
    convergence.

Returns the function's byte size (max-reachable + 4 to cover the
delay slot of the last instruction). On failure, populates a specific
error message with the offending offset and reason — caller treats
this as a build error, NOT a graceful skip (per the project's
no-stubs principle).

Wires into decompressed.cpp's pattern path, replacing the prior
inline BFS that had a TODO for jump-table handling. The pattern
caller now propagates failures via `synthesize_decompressed_patterns`
returning false, which surfaces in main.cpp's exit_failure path.

Concrete behavior change: activating a pattern that includes a
fragment with computed jumps now produces a build error pointing at
the specific section name + offset + the analyzer's failure reason,
instead of silently producing a partial binary. Tested on Stadium's
0x8FF00000 slot — first failing wrapper is at ROM 0x8CC400 with an
indirect jr at offset 0x827C the simulator doesn't pattern-match.
The static [[input.decompressed_section]] path for fragment78 is
unaffected (still recompiles cleanly, no regression on boot logo +
PIKA jingle).

Future work surfaced by this change: the simulator's lui+addiu
+addu+lw+jr pattern doesn't cover every jump-table shape Stadium
uses. Each gap surfaces as a specific build-error offset; resolution
is to extend analyze_instruction to recognize the additional pattern
(or, when it's a true tail-call rather than a jtbl, distinguish
those at the jr site).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-05 21:47:44 -07:00

51 lines
No EOL
1.9 KiB
C++

#ifndef __RECOMP_ANALYSIS_H__
#define __RECOMP_ANALYSIS_H__
#include <cstdint>
#include <string>
#include <vector>
#include "recompiler/context.h"
namespace N64Recomp {
struct AbsoluteJump {
uint32_t jump_target;
uint32_t instruction_vram;
AbsoluteJump(uint32_t jump_target, uint32_t instruction_vram) : jump_target(jump_target), instruction_vram(instruction_vram) {}
};
struct FunctionStats {
std::vector<JumpTable> jump_tables;
};
bool analyze_function(const Context& context, const Function& function, const std::vector<rabbitizer::InstructionCpu>& instructions, FunctionStats& stats);
// Discover the byte-size of a function whose entry sits at
// `entry_offset` within `body`. Performs a BFS-based control-flow
// walk that follows conditional branches (target + fall-through),
// unconditional j/jal targets when intra-body, jr $ra returns,
// and jr-via-jump-table dispatches (resolved by the existing
// register-state simulator from analyze_function).
//
// `body` is the raw decompressed bytes of the section's body in
// big-endian instruction layout (same shape as Function::words but
// as a byte buffer; bytes_size is the upper bound).
//
// `vram_base` is the link-time vram of body[0] — used to translate
// branch/jal targets back to body offsets.
//
// On success, sets `size_out` to the function's byte size (always
// a multiple of 4) and returns true.
//
// On failure, populates `error_out` with a specific message
// identifying the offending instruction or jump-table issue, and
// returns false. Per the project's no-stubs principle, callers
// should treat false as a build-time error, NOT a graceful skip.
bool discover_function_bounds(
const uint8_t* body, size_t bytes_size,
uint32_t vram_base, uint32_t entry_offset,
size_t& size_out, std::string& error_out);
}
#endif