From e6c91241194cb85edeada092463a3cb3941981b9 Mon Sep 17 00:00:00 2001 From: Uproared Date: Fri, 24 Apr 2026 05:16:28 -0400 Subject: [PATCH] XenonAnalyse: add optional AnalyzerSwitchTableMap parameter to Function::Analyze MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Defines AnalyzerSwitchTable { defaultLabel, labels[] } and AnalyzerSwitchTableMap = unordered_map. Function::Analyze gains an optional `const AnalyzerSwitchTableMap* switchMap = nullptr` parameter. Default nullptr preserves existing behavior exactly: no call site needs to change, and the walker currently ignores the parameter. The next commit wires the parameter into the walker's unconditional- bctr handling: when switchMap is populated and the walker reaches a bctr whose guest VA has a map entry, it will push every switch label (and the default) as a successor block rather than terminating. Rationale for keying by BCTR VA rather than by XenonAnalyse's own `SwitchTable::base` (first-insn-of-pattern VA): the producer of the TOML-emitted switch tables and the consumer of Function::Analyze are often the same caller, but the walker encounters instructions at the BCTR's address directly. Keying the map by BCTR VA removes the need for the walker to track pattern offsets (6 insns for absolute-form switches, 8 for computed / short-offset, 7 for byte-offset) or to handle NOP padding variations — the caller does the offset math once at map-construction time. Fixes the well-known class of issues where switch-dispatched functions have their successor blocks swept away by the discontinuity pass, and downstream consumers (e.g., recompilers) emit "jump outside function" errors for labels that are actually reachable. Observed on an Xbox 360 title whose compiler emitted forward-jumping switch tables in this shape, and referenced in UnleashedRecomp-lineage work; other Xbox 360 recompile targets likely hit it too. See also: - A follow-up test commit adds synthetic hand-crafted PPC fixtures covering the happy path + null-map safety + wrong-map miss. --- XenonAnalyse/function.cpp | 8 +++++++- XenonAnalyse/function.h | 36 +++++++++++++++++++++++++++++++----- 2 files changed, 38 insertions(+), 6 deletions(-) diff --git a/XenonAnalyse/function.cpp b/XenonAnalyse/function.cpp index 34add40..bc9392a 100644 --- a/XenonAnalyse/function.cpp +++ b/XenonAnalyse/function.cpp @@ -38,8 +38,14 @@ size_t Function::SearchBlock(size_t address) const return -1; } -Function Function::Analyze(const void* code, size_t size, size_t base) +Function Function::Analyze(const void* code, size_t size, size_t base, + const AnalyzerSwitchTableMap* switchMap) { + (void)switchMap; // consumed in a follow-up commit; receiving now so + // that callers can adopt the signature and pass a + // map immediately without waiting for the walker + // change to land. + Function fn{ base, 0 }; if (*((uint32_t*)code + 1) == 0x04000048) // shifted ptr tail call diff --git a/XenonAnalyse/function.h b/XenonAnalyse/function.h index 7df1c6a..ea71ea6 100644 --- a/XenonAnalyse/function.h +++ b/XenonAnalyse/function.h @@ -1,6 +1,8 @@ #pragma once #include +#include +#include #include #ifdef _DEBUG @@ -9,6 +11,29 @@ #define DEBUG(X) #endif +// Optional switch-table map consumed by the block walker in +// Function::Analyze when it reaches an unconditional bctr at a known +// switch-dispatch site. When the caller hands in a populated map, the +// walker pushes every label (and the default) as a successor block +// instead of terminating, which grows `fn.size` to include switch- +// dispatched code that the legacy walker would discard via the end- +// of-Analyze discontinuity pass. +// +// The map is keyed by the BCTR guest VA — NOT by the first-instruction- +// of-pattern VA that XenonAnalyse's own switch-table TOML emits. The +// caller (typically XenonRecomp) performs the switch-base → bctr-VA +// offset math at map-construction time and hands us a pre-keyed map. +// +// Passing nullptr (the default) preserves the legacy walker behavior +// byte-for-byte. No existing callers need to change. +struct AnalyzerSwitchTable +{ + uint32_t defaultLabel{}; + std::vector labels{}; +}; + +using AnalyzerSwitchTableMap = std::unordered_map; + struct Function { struct Block @@ -18,16 +43,16 @@ struct Function size_t projectedSize{ static_cast(-1) }; // scratch DEBUG(size_t parent{}); - Block() + Block() { } Block(size_t base, size_t size) - : base(base), size(size) + : base(base), size(size) { } - Block(size_t base, size_t size, size_t projectedSize) + Block(size_t base, size_t size, size_t projectedSize) : base(base), size(size), projectedSize(projectedSize) { } @@ -45,7 +70,8 @@ struct Function : base(base), size(size) { } - + size_t SearchBlock(size_t address) const; - static Function Analyze(const void* code, size_t size, size_t base); + static Function Analyze(const void* code, size_t size, size_t base, + const AnalyzerSwitchTableMap* switchMap = nullptr); };