improve Skate 3 codegen and instruction support

This commit is contained in:
mchughalex 2026-05-19 23:58:46 +01:00
parent 3ec2b41a6f
commit 7325992255
41 changed files with 1038 additions and 278 deletions

View file

@ -74,6 +74,7 @@ struct FunctionEntry {
struct RecompilerConfig { struct RecompilerConfig {
// === Required user-provided fields === // === Required user-provided fields ===
std::string projectName = "rex"; ///< Project name for output files std::string projectName = "rex"; ///< Project name for output files
std::string symbolPrefix; ///< Optional C++ symbol prefix for generated functions
std::string filePath; ///< Path to XEX/ELF file std::string filePath; ///< Path to XEX/ELF file
std::string outDirectoryPath; ///< Output directory for generated code std::string outDirectoryPath; ///< Output directory for generated code
std::string templateDir; ///< Optional custom template directory for overrides std::string templateDir; ///< Optional custom template directory for overrides

View file

@ -100,6 +100,10 @@ class FunctionGraph {
size_t pendingCount() const; size_t pendingCount() const;
size_t sealedCount() const; size_t sealedCount() const;
// Prefix generated C++ symbols for this graph. Imports are never prefixed.
void setSymbolPrefix(std::string prefix);
std::string makeSymbolName(std::string_view name, FunctionAuthority authority) const;
//========================================================================= //=========================================================================
// Function Setup (called during Discover phase) // Function Setup (called during Discover phase)
//========================================================================= //=========================================================================
@ -165,7 +169,10 @@ class FunctionGraph {
void setMemoryReader(MemoryReader reader) { memoryReader_ = std::move(reader); } void setMemoryReader(MemoryReader reader) { memoryReader_ = std::move(reader); }
// Register a chunk (address range claimed by config, blocks vacancy) // Register a chunk (address range claimed by config, blocks vacancy)
void registerChunk(uint32_t base, uint32_t size); void registerChunk(uint32_t base, uint32_t size, uint32_t parent = 0);
// Return the configured parent for a chunk entry, or 0 for non-chunks.
uint32_t chunkParent(uint32_t base) const;
// Check if a region is vacant for absorption // Check if a region is vacant for absorption
// fromAddr: the address we're expanding from (to check for null boundary) // fromAddr: the address we're expanding from (to check for null boundary)
@ -193,6 +200,8 @@ class FunctionGraph {
functionsByBase_; // sorted by base for O(log f) interval lookup functionsByBase_; // sorted by base for O(log f) interval lookup
std::unordered_map<uint32_t, bool> functionHasXrefs_; // entry -> hasXrefs std::unordered_map<uint32_t, bool> functionHasXrefs_; // entry -> hasXrefs
std::vector<std::pair<uint32_t, uint32_t>> chunks_; // base, size pairs std::vector<std::pair<uint32_t, uint32_t>> chunks_; // base, size pairs
std::unordered_map<uint32_t, uint32_t> chunkParents_; // chunk base -> parent base
std::string symbolPrefix_;
MemoryReader memoryReader_; MemoryReader memoryReader_;
// Notify all PENDING functions that a new function was added // Notify all PENDING functions that a new function was added

View file

@ -94,8 +94,8 @@ class FunctionNode {
/// Emit C++ code for this function /// Emit C++ code for this function
/// Requires: state() == kSealed /// Requires: state() == kSealed
/// For imports: emits REX_IMPORT macro /// For imports: emits PPC_IMPORT macro
/// For normal functions: emits REX_FUNC with blocks and instructions /// For normal functions: emits PPC_FUNC with blocks and instructions
std::string emitCpp(const EmitContext& ctx) const; std::string emitCpp(const EmitContext& ctx) const;
//========================================================================= //=========================================================================

View file

@ -13,6 +13,7 @@
#pragma once #pragma once
#include <atomic>
#include <bit> #include <bit>
#include <cstdint> #include <cstdint>
#include <cstring> #include <cstring>
@ -183,6 +184,9 @@ constexpr uint32_t kRoundMask = 0x03;
struct FPSCRRegister { struct FPSCRRegister {
uint32_t csr; uint32_t csr;
uint32_t fpu_csr;
uint32_t vmx_csr;
bool vmx_mode;
static constexpr size_t HostToGuest[] = {kRoundNearest, kRoundDown, kRoundUp, kRoundTowardZero}; static constexpr size_t HostToGuest[] = {kRoundNearest, kRoundDown, kRoundUp, kRoundTowardZero};
@ -190,48 +194,69 @@ struct FPSCRRegister {
static constexpr size_t RoundShift = Platform::RoundShift; static constexpr size_t RoundShift = Platform::RoundShift;
static constexpr size_t RoundMaskVal = Platform::RoundMaskVal; static constexpr size_t RoundMaskVal = Platform::RoundMaskVal;
static constexpr size_t FlushMask = Platform::FlushMask; static constexpr size_t FlushMask = Platform::FlushMask;
static constexpr size_t ExceptionMask = Platform::ExceptionMask;
inline uint32_t getcsr() noexcept { return Platform::getcsr(); } inline uint32_t getcsr() noexcept { return Platform::getcsr(); }
inline void setcsr(uint32_t csr) noexcept { Platform::setcsr(csr); } inline void setcsr(uint32_t csr) noexcept { Platform::setcsr(csr); }
inline uint32_t loadFromHost() noexcept { inline uint32_t loadFromHost() noexcept {
csr = getcsr(); if (!vmx_mode) {
return HostToGuest[(csr & RoundMaskVal) >> RoundShift]; fpu_csr = getcsr();
Platform::InitHostExceptions(fpu_csr);
fpu_csr &= ~FlushMask;
if (vmx_csr == 0) {
vmx_csr = ExceptionMask | FlushMask | Platform::GuestToHost[kRoundNearest];
}
csr = fpu_csr;
}
return HostToGuest[(fpu_csr & RoundMaskVal) >> RoundShift];
} }
inline void storeFromGuest(uint32_t value) noexcept { inline void storeFromGuest(uint32_t value) noexcept {
csr &= ~RoundMaskVal; fpu_csr &= ~RoundMaskVal;
csr |= Platform::GuestToHost[value & kRoundMask]; fpu_csr |= Platform::GuestToHost[value & kRoundMask];
fpu_csr &= ~FlushMask;
fpu_csr |= ExceptionMask;
csr = fpu_csr;
vmx_mode = false;
setcsr(csr); setcsr(csr);
} }
inline void enableFlushModeUnconditional() noexcept { inline void enableFlushModeUnconditional() noexcept {
csr |= FlushMask; csr = vmx_csr;
vmx_mode = true;
setcsr(csr); setcsr(csr);
} }
inline void disableFlushModeUnconditional() noexcept { inline void disableFlushModeUnconditional() noexcept {
csr &= ~FlushMask; csr = fpu_csr;
vmx_mode = false;
setcsr(csr); setcsr(csr);
} }
inline void enableFlushMode() noexcept { inline void enableFlushMode() noexcept {
if ((csr & FlushMask) != FlushMask) [[unlikely]] { if (!vmx_mode || getcsr() != vmx_csr) [[unlikely]] {
csr |= FlushMask; csr = vmx_csr;
vmx_mode = true;
setcsr(csr); setcsr(csr);
} }
} }
inline void disableFlushMode() noexcept { inline void disableFlushMode() noexcept {
if ((csr & FlushMask) != 0) [[unlikely]] { if (vmx_mode || getcsr() != fpu_csr) [[unlikely]] {
csr &= ~FlushMask; csr = fpu_csr;
vmx_mode = false;
setcsr(csr); setcsr(csr);
} }
} }
inline void InitHost() noexcept { inline void InitHost() noexcept {
csr = getcsr(); fpu_csr = getcsr();
Platform::InitHostExceptions(csr); Platform::InitHostExceptions(fpu_csr);
fpu_csr &= ~FlushMask;
vmx_csr = ExceptionMask | FlushMask | Platform::GuestToHost[kRoundNearest];
csr = fpu_csr;
vmx_mode = false;
setcsr(csr); setcsr(csr);
} }
}; };
@ -301,6 +326,12 @@ struct alignas(0x40) PPCContext {
#endif #endif
#if !defined(REX_CONFIG_RESERVED_AS_LOCAL) #if !defined(REX_CONFIG_RESERVED_AS_LOCAL)
PPCRegister reserved; PPCRegister reserved;
uint32_t reserved_address;
uint32_t reserved_size;
uint32_t reserved_bitmap_word;
uint64_t reserved_bitmap_mask;
bool reserved_global_acquired;
bool reserved_valid;
#endif #endif
#if !defined(REX_CONFIG_SKIP_MSR) #if !defined(REX_CONFIG_SKIP_MSR)
uint32_t msr = 0x200A000; uint32_t msr = 0x200A000;
@ -531,3 +562,47 @@ struct alignas(0x40) PPCContext {
inline void RestoreNonVolatiles(const uint8_t*) {} inline void RestoreNonVolatiles(const uint8_t*) {}
#endif #endif
}; };
namespace rex::ppc {
constexpr uint32_t kReserveBlockShift = 16;
constexpr uint32_t kReserveBlockCount = 1u << (32 - kReserveBlockShift);
constexpr uint32_t kReserveBitmapWordCount = kReserveBlockCount / 64;
inline std::atomic<uint64_t> g_reservation_bitmap[kReserveBitmapWordCount] = {};
inline void ReleaseReservation(PPCContext& ctx) {
ctx.reserved_global_acquired = false;
ctx.reserved_valid = false;
}
inline bool AcquireReservation(PPCContext& ctx, uint32_t address, uint32_t size) {
ReleaseReservation(ctx);
ctx.reserved_address = address;
ctx.reserved_size = size;
ctx.reserved_bitmap_word = 0;
ctx.reserved_bitmap_mask = 0;
ctx.reserved_global_acquired = false;
ctx.reserved_valid = true;
return ctx.reserved_valid;
}
inline bool ReservationMatches(const PPCContext& ctx, uint32_t address) {
if (!ctx.reserved_valid) {
return false;
}
return ctx.reserved_address == address;
}
inline void ImportReservation(PPCContext& ctx, uint32_t address, uint64_t value) {
ReleaseReservation(ctx);
ctx.reserved.u64 = value;
ctx.reserved_address = address;
ctx.reserved_size = 0;
ctx.reserved_bitmap_word = 0;
ctx.reserved_bitmap_mask = 0;
ctx.reserved_global_acquired = false;
ctx.reserved_valid = true;
}
} // namespace rex::ppc

View file

@ -13,6 +13,7 @@
#pragma once #pragma once
#include <bit>
#include <climits> #include <climits>
#include <cmath> #include <cmath>
#include <cstring> #include <cstring>
@ -30,6 +31,100 @@
namespace rex::ppc { namespace rex::ppc {
inline uint32_t ppc_vrsqrtefp_bits(uint32_t bits) {
static constexpr uint32_t table[32] = {
0x0568B4FD, 0x04F3AF97, 0x048DAAA5, 0x0435A618, 0x03E7A1E4, 0x03A29DFE,
0x03659A5C, 0x032E96F8, 0x02FC93CA, 0x02D090CE, 0x02A88DFE, 0x02838B57,
0x026188D4, 0x02438673, 0x02268431, 0x020B820B, 0x03D27FFA, 0x03807C29,
0x033878AA, 0x02F97572, 0x02C27279, 0x02926FB7, 0x02666D26, 0x023F6AC0,
0x021D6881, 0x01FD6665, 0x01E16468, 0x01C76287, 0x01AF60C1, 0x01995F12,
0x01855D79, 0x01735BF4,
};
uint32_t sign = bits >> 31;
uint32_t biased_exp = (bits >> 23) & 0xFF;
uint32_t mantissa = bits & 0x007FFFFF;
if (bits == 0xFF800000u)
return 0x7FC00000u;
if (biased_exp == 0)
return sign ? 0xFF800000u : 0x7F800000u;
if (biased_exp == 0xFF) {
if (mantissa == 0)
return 0;
return bits | 0x00400000u;
}
if (sign)
return 0x7FC00000u;
int32_t unbiased_exp = int32_t(biased_exp) - 127;
uint32_t index = ((((uint32_t(unbiased_exp) << 4) & 16) | (mantissa >> 19)) ^ 16);
uint32_t interp = (mantissa >> 9) & 1023;
int32_t result_exp = (127 - int32_t(biased_exp)) >> 1;
uint32_t entry = table[index];
uint32_t slope = entry >> 16;
uint32_t base = (entry << 10) & 0x3FFFC00u;
int32_t raw = int32_t(base) - int32_t(interp * slope);
if (!(raw & (1 << 25))) {
uint32_t val = uint32_t(raw) & 0x1FFFFFFu;
uint32_t lz = std::countl_zero(val);
int32_t shift = int32_t(lz) - 6;
result_exp += 6 - int32_t(lz);
raw <<= shift;
}
if ((raw & 5) && (raw & 2))
raw += 4;
uint32_t result = uint32_t((result_exp << 23) + 0x3F800000) | ((uint32_t(raw) >> 2) & 0x7FFFFFu);
if (((result >> 23) & 0xFF) == 0 && (result & 0x7FFFFF))
result = 0;
return result;
}
inline float ppc_vrsqrtefp(float value) {
return std::bit_cast<float>(ppc_vrsqrtefp_bits(std::bit_cast<uint32_t>(value)));
}
inline simde__m128 simde_mm_vrsqrtefp_ps(simde__m128 value) {
alignas(16) float lanes[4];
simde_mm_store_ps(lanes, value);
for (float& lane : lanes)
lane = ppc_vrsqrtefp(lane);
return simde_mm_load_ps(lanes);
}
// Match Xenia's vmsum3fp128/vmsum4fp128 lowering: a single-precision SSE dot
// product, followed by the emulator's overflow-to-QNaN behavior and guest FTZ.
inline float ppc_vmsumfp_result(float value) {
constexpr uint32_t qnan_bits = 0x7FC00000u;
if (!std::isfinite(value))
return std::bit_cast<float>(qnan_bits);
uint32_t bits = std::bit_cast<uint32_t>(value);
if (((bits >> 23) & 0xFF) == 0 && (bits & 0x007FFFFFu) != 0) {
bits &= 0x80000000u; // preserve sign, clear exponent + mantissa
}
return std::bit_cast<float>(bits);
}
inline simde__m128 ppc_vmsumfp_result(simde__m128 value) {
alignas(16) float lanes[4];
simde_mm_store_ps(lanes, value);
return simde_mm_set1_ps(ppc_vmsumfp_result(lanes[0]));
}
inline simde__m128 simde_mm_vmsum3fp128_ps(simde__m128 a, simde__m128 b) {
return ppc_vmsumfp_result(simde_mm_dp_ps(a, b, 0xEF));
}
inline simde__m128 simde_mm_vmsum4fp128_ps(simde__m128 a, simde__m128 b) {
return ppc_vmsumfp_result(simde_mm_dp_ps(a, b, 0xFF));
}
//============================================================================= //=============================================================================
// Vector Load/Store Mask Tables // Vector Load/Store Mask Tables
//============================================================================= //=============================================================================

View file

@ -7,19 +7,19 @@
void RegisterRecompiledModules(rex::system::KernelState*); void RegisterRecompiledModules(rex::system::KernelState*);
{% endif %} {% endif %}
const rex::PPCImageInfo PPCImageConfig = { const rex::PPCImageInfo {{ project }}_PPCImageConfig = {
.code_base = REX_CODE_BASE, .code_base = REX_CODE_BASE,
.code_size = REX_CODE_SIZE, .code_size = REX_CODE_SIZE,
.image_base = REX_IMAGE_BASE, .image_base = REX_IMAGE_BASE,
.image_size = REX_IMAGE_SIZE, .image_size = REX_IMAGE_SIZE,
.func_mappings = PPCFuncMappings, .func_mappings = {{ project }}_PPCFuncMappings,
.rexcrt_heap = REXCRT_HEAP, .rexcrt_heap = REXCRT_HEAP,
{% if has_dll_modules and not is_dll %} {% if has_dll_modules and not is_dll %}
.register_modules = RegisterRecompiledModules, .register_modules = RegisterRecompiledModules,
{% endif %} {% endif %}
}; };
PPCFuncMapping PPCFuncMappings[] = { PPCFuncMapping {{ project }}_PPCFuncMappings[] = {
{% for fn in functions %}{% if not fn.below_code_base or fn.is_import %} { {{ fn.address }}, {{ fn.name }} }, {% for fn in functions %}{% if not fn.below_code_base or fn.is_import %} { {{ fn.address }}, {{ fn.name }} },
{% endif %}{% endfor %} { 0, nullptr } {% endif %}{% endfor %} { 0, nullptr }
}; };

View file

@ -49,8 +49,8 @@
// Image Info // Image Info
//============================================================================= //=============================================================================
extern const rex::PPCImageInfo PPCImageConfig; extern const rex::PPCImageInfo {{ project }}_PPCImageConfig;
extern PPCFuncMapping PPCFuncMappings[]; extern PPCFuncMapping {{ project }}_PPCFuncMappings[];
//============================================================================= //=============================================================================
// Function Definition Macros // Function Definition Macros

View file

@ -6,7 +6,6 @@
# #
set(GENERATED_SOURCES set(GENERATED_SOURCES
{{ cmake_var("CMAKE_CURRENT_LIST_DIR") }}/{{ project }}_init.cpp {{ cmake_var("CMAKE_CURRENT_LIST_DIR") }}/{{ project }}_init.cpp
{{ cmake_var("CMAKE_CURRENT_LIST_DIR") }}/{{ project }}_register.cpp
{% if has_dll_modules and not is_dll %} {{ cmake_var("CMAKE_CURRENT_LIST_DIR") }}/module_registry.cpp {% if has_dll_modules and not is_dll %} {{ cmake_var("CMAKE_CURRENT_LIST_DIR") }}/module_registry.cpp
{% endif %}{% for file in recomp_files %} {{ cmake_var("CMAKE_CURRENT_LIST_DIR") }}/{{ file }} {% endif %}{% for file in recomp_files %} {{ cmake_var("CMAKE_CURRENT_LIST_DIR") }}/{{ file }}
{% endfor %}) {% endfor %})

View file

@ -468,6 +468,15 @@ bool build_vadduhm(BuilderContext& ctx);
bool build_vadduwm(BuilderContext& ctx); bool build_vadduwm(BuilderContext& ctx);
bool build_vadduws(BuilderContext& ctx); bool build_vadduws(BuilderContext& ctx);
bool build_vadduhs(BuilderContext& ctx); bool build_vadduhs(BuilderContext& ctx);
bool build_vmhaddshs(BuilderContext& ctx);
bool build_vmhraddshs(BuilderContext& ctx);
bool build_vmladduhm(BuilderContext& ctx);
bool build_vmsumubm(BuilderContext& ctx);
bool build_vmsummbm(BuilderContext& ctx);
bool build_vmsumuhm(BuilderContext& ctx);
bool build_vmsumuhs(BuilderContext& ctx);
bool build_vmsumshm(BuilderContext& ctx);
bool build_vmsumshs(BuilderContext& ctx);
bool build_vsubsbs(BuilderContext& ctx); bool build_vsubsbs(BuilderContext& ctx);
bool build_vsubshs(BuilderContext& ctx); bool build_vsubshs(BuilderContext& ctx);
bool build_vsubsws(BuilderContext& ctx); bool build_vsubsws(BuilderContext& ctx);

View file

@ -195,7 +195,7 @@ void BuilderContext::emit_function_call(uint32_t address) {
// Handle save/restore helpers // Handle save/restore helpers
if (cfg.nonVolatileRegistersAsLocalVariables && if (cfg.nonVolatileRegistersAsLocalVariables &&
(name.find("__rest") == 0 || name.find("__save") == 0)) { (name.find("__rest") != std::string::npos || name.find("__save") != std::string::npos)) {
// print nothing - these are handled by local variable tracking // print nothing - these are handled by local variable tracking
return; return;
} }
@ -242,6 +242,11 @@ void BuilderContext::emit_function_call(uint32_t address) {
} }
// No pre-resolved target found - this is an error // No pre-resolved target found - this is an error
if (auto* targetFn = graph().getFunction(address)) {
println("\t{}(ctx, base);", targetFn->name());
return;
}
REXCODEGEN_ERROR("Unresolved function 0x{:08X} from 0x{:08X} (no CallTarget in FunctionNode)", REXCODEGEN_ERROR("Unresolved function 0x{:08X} from 0x{:08X} (no CallTarget in FunctionNode)",
address, base); address, base);
println("\t// FATAL: unresolved function 0x{:08X} (no CallTarget in FunctionNode)", address); println("\t// FATAL: unresolved function 0x{:08X} (no CallTarget in FunctionNode)", address);
@ -253,7 +258,7 @@ void BuilderContext::emit_conditional_branch(bool not_, std::string_view cond) {
// Use classifyTarget for consistent branch classification // Use classifyTarget for consistent branch classification
// false = branch instruction (not a call), so own-base means loop back // false = branch instruction (not a call), so own-base means loop back
auto kind = graph().classifyTarget(target, base, false); auto kind = graph().classifyTarget(target, fn.base(), false);
switch (kind) { switch (kind) {
case TargetKind::InternalLabel: case TargetKind::InternalLabel:
@ -283,18 +288,33 @@ void BuilderContext::emit_conditional_branch(bool not_, std::string_view cond) {
println("\t}}"); println("\t}}");
} }
} else { } else {
REXCODEGEN_ERROR("Unresolved conditional branch to 0x{:08X} from 0x{:08X} (no CallTarget)", if (auto* targetFn = graph().getFunction(target)) {
println("\tif ({}{}.{}) {{", not_ ? "!" : "", cr(insn.operands[0]), cond);
println("\t\t{}(ctx, base);", targetFn->name());
println("\t\treturn;");
println("\t}}");
} else {
REXCODEGEN_ERROR(
"Unresolved conditional branch to 0x{:08X} from 0x{:08X} (no CallTarget)",
target, base); target, base);
println("\tif ({}{}.{}) REX_FATAL(\"Unresolved branch from 0x{:08X} to 0x{:08X}\");", println("\tif ({}{}.{}) REX_FATAL(\"Unresolved branch from 0x{:08X} to 0x{:08X}\");",
not_ ? "!" : "", cr(insn.operands[0]), cond, base, target); not_ ? "!" : "", cr(insn.operands[0]), cond, base, target);
} }
}
break; break;
case TargetKind::Unknown: case TargetKind::Unknown:
if (auto* targetFn = graph().getFunction(target)) {
println("\tif ({}{}.{}) {{", not_ ? "!" : "", cr(insn.operands[0]), cond);
println("\t\t{}(ctx, base);", targetFn->name());
println("\t\treturn;");
println("\t}}");
} else {
REXCODEGEN_ERROR("Unresolved conditional branch to 0x{:08X} from 0x{:08X}", target, base); REXCODEGEN_ERROR("Unresolved conditional branch to 0x{:08X} from 0x{:08X}", target, base);
println("\t// ERROR: conditional branch to unknown address 0x{:08X}", target); println("\t// ERROR: conditional branch to unknown address 0x{:08X}", target);
println("\tif ({}{}.{}) REX_FATAL(\"Unresolved branch from 0x{:08X} to 0x{:08X}\");", println("\tif ({}{}.{}) REX_FATAL(\"Unresolved branch from 0x{:08X} to 0x{:08X}\");",
not_ ? "!" : "", cr(insn.operands[0]), cond, base, target); not_ ? "!" : "", cr(insn.operands[0]), cond, base, target);
}
break; break;
} }
} }
@ -303,7 +323,7 @@ void BuilderContext::emit_set_flush_mode(bool enable) {
auto newState = enable ? CSRState::VMX : CSRState::FPU; auto newState = enable ? CSRState::VMX : CSRState::FPU;
if (csrState != newState) { if (csrState != newState) {
auto prefix = enable ? "enable" : "disable"; auto prefix = enable ? "enable" : "disable";
auto suffix = csrState != CSRState::Unknown ? "Unconditional" : ""; auto suffix = csrState == CSRState::Unknown ? "Unconditional" : "";
println("\tctx.fpscr.{}FlushMode{}();", prefix, suffix); println("\tctx.fpscr.{}FlushMode{}();", prefix, suffix);
csrState = newState; csrState = newState;

View file

@ -20,6 +20,28 @@
namespace rex::codegen { namespace rex::codegen {
namespace {
bool hasEmittedBlockContaining(const FunctionNode& fn, uint32_t addr) {
for (const auto& block : fn.blocks()) {
if (block.contains(addr)) {
return true;
}
}
return false;
}
bool isLocalChunkTarget(const BuilderContext& ctx, uint32_t target) {
const uint32_t targetParent = ctx.graph().chunkParent(target);
if (targetParent == 0) {
return false;
}
return hasEmittedBlockContaining(ctx.fn, target);
}
} // namespace
//============================================================================= //=============================================================================
// Unconditional Branch // Unconditional Branch
//============================================================================= //=============================================================================
@ -27,9 +49,16 @@ namespace rex::codegen {
bool build_b(BuilderContext& ctx) { bool build_b(BuilderContext& ctx) {
uint32_t target = ctx.insn.operands[0]; uint32_t target = ctx.insn.operands[0];
if (ctx.graph().getFunction(target) && target != ctx.fn.base() &&
!isLocalChunkTarget(ctx, target)) {
ctx.emit_function_call(target);
ctx.println("\treturn;");
return true;
}
// Use graph to classify the target - handles thunks that branch to nearby functions // Use graph to classify the target - handles thunks that branch to nearby functions
// false = branch instruction (not a call), so own-base means loop back // false = branch instruction (not a call), so own-base means loop back
auto kind = ctx.graph().classifyTarget(target, ctx.base, false); auto kind = ctx.graph().classifyTarget(target, ctx.fn.base(), false);
switch (kind) { switch (kind) {
case TargetKind::InternalLabel: case TargetKind::InternalLabel:
@ -67,7 +96,7 @@ bool build_bl(BuilderContext& ctx) {
// Use graph to classify the target // Use graph to classify the target
// true = call instruction, so own-base means recursive call (not loop back) // true = call instruction, so own-base means recursive call (not loop back)
auto kind = ctx.graph().classifyTarget(target, ctx.base, true); auto kind = ctx.graph().classifyTarget(target, ctx.fn.base(), true);
switch (kind) { switch (kind) {
case TargetKind::InternalLabel: case TargetKind::InternalLabel:
@ -138,7 +167,7 @@ bool build_bctr(BuilderContext& ctx) {
continue; continue;
} }
auto kind = ctx.graph().classifyTarget(label, ctx.base, false); auto kind = ctx.graph().classifyTarget(label, ctx.fn.base(), false);
switch (kind) { switch (kind) {
case TargetKind::InternalLabel: case TargetKind::InternalLabel:
ctx.println("\t\tgoto loc_{:X};", label); ctx.println("\t\tgoto loc_{:X};", label);

View file

@ -267,7 +267,7 @@ inline const char* getStoreMacro(BuilderContext& ctx, const char* normal_macro,
/** /**
* Emit atomic load-and-reserve instruction (lwarx/ldarx pattern). * Emit atomic load-and-reserve instruction (lwarx/ldarx pattern).
* *
* Pattern: EA = rA + rB; reserved = *(T*)REX_RAW_ADDR(EA); rD = bswap(reserved) * Pattern: EA = rA + rB; reserve EA; reserved = *(T*)REX_RAW_ADDR(EA); rD = bswap(reserved)
* *
* @param ctx The builder context * @param ctx The builder context
* @param ptr_type The pointer type (e.g., "uint32_t", "uint64_t") * @param ptr_type The pointer type (e.g., "uint32_t", "uint64_t")
@ -280,8 +280,9 @@ inline void emitAtomicLoadReserve(BuilderContext& ctx, const char* ptr_type, con
if (ctx.insn.operands[1] != 0) if (ctx.insn.operands[1] != 0)
ctx.print("{}.u32 + ", ctx.r(ctx.insn.operands[1])); ctx.print("{}.u32 + ", ctx.r(ctx.insn.operands[1]));
ctx.println("{}.u32;", ctx.r(ctx.insn.operands[2])); ctx.println("{}.u32;", ctx.r(ctx.insn.operands[2]));
ctx.println("\t{}.{} = *({}*)REX_RAW_ADDR({});", ctx.reserved(), reserved_field, ptr_type, ctx.println("\trex::ppc::AcquireReservation(ctx, {}, sizeof({}));", ctx.ea(), ptr_type);
ctx.ea()); ctx.println("\t{}.{} = *(volatile {}*)REX_RAW_ADDR({});", ctx.reserved(), reserved_field,
ptr_type, ctx.ea());
ctx.println("\t{}.u64 = {}({}.{});", ctx.r(ctx.insn.operands[0]), bswap_func, ctx.reserved(), ctx.println("\t{}.u64 = {}({}.{});", ctx.r(ctx.insn.operands[0]), bswap_func, ctx.reserved(),
reserved_field); reserved_field);
} }
@ -289,7 +290,7 @@ inline void emitAtomicLoadReserve(BuilderContext& ctx, const char* ptr_type, con
/** /**
* Emit atomic store-conditional instruction (stwcx./stdcx. pattern). * Emit atomic store-conditional instruction (stwcx./stdcx. pattern).
* *
* Pattern: EA = rA + rB; cr0 = CAS(EA, reserved, bswap(rS)) * Pattern: EA = rA + rB; cr0 = reservation_matches(EA) && CAS(EA, reserved, bswap(rS))
* *
* @param ctx The builder context * @param ctx The builder context
* @param ptr_type The pointer type (e.g., "uint32_t", "uint64_t") * @param ptr_type The pointer type (e.g., "uint32_t", "uint64_t")
@ -305,10 +306,12 @@ inline void emitAtomicStoreConditional(BuilderContext& ctx, const char* ptr_type
ctx.println("\t{}.lt = 0;", ctx.cr(0)); ctx.println("\t{}.lt = 0;", ctx.cr(0));
ctx.println("\t{}.gt = 0;", ctx.cr(0)); ctx.println("\t{}.gt = 0;", ctx.cr(0));
ctx.println( ctx.println(
"\t{}.eq = __sync_bool_compare_and_swap(reinterpret_cast<{}*>(REX_RAW_ADDR({})), " "\t{}.eq = rex::ppc::ReservationMatches(ctx, {}) && "
"__sync_bool_compare_and_swap(reinterpret_cast<{}*>(REX_RAW_ADDR({})), "
"{}.{}, {}({}.{}));", "{}.{}, {}({}.{}));",
ctx.cr(0), ptr_type, ctx.ea(), ctx.reserved(), field, bswap_func, ctx.r(ctx.insn.operands[0]), ctx.cr(0), ctx.ea(), ptr_type, ctx.ea(), ctx.reserved(), field, bswap_func,
field); ctx.r(ctx.insn.operands[0]), field);
ctx.println("\trex::ppc::ReleaseReservation(ctx);");
ctx.println("\t{}.so = {}.so;", ctx.cr(0), ctx.xer()); ctx.println("\t{}.so = {}.so;", ctx.cr(0), ctx.xer());
} }
@ -387,13 +390,25 @@ inline bool isMMIOUpperBits(uint32_t imm) {
*/ */
inline void emitBranchWithBoundsCheck(BuilderContext& ctx, uint32_t target, inline void emitBranchWithBoundsCheck(BuilderContext& ctx, uint32_t target,
std::string_view condition, std::string_view instr_name) { std::string_view condition, std::string_view instr_name) {
if (target < ctx.fn.base() || target >= ctx.fn.end()) { auto kind = ctx.graph().classifyTarget(target, ctx.fn.base(), false);
REXCODEGEN_WARN("{} at {:X} branches outside function to {:X}", instr_name, ctx.base, target); if (kind == TargetKind::InternalLabel) {
ctx.println("\tif ({}) {{ /* branch to 0x{:X} outside function */ return; }}", condition,
target);
} else {
ctx.println("\tif ({}) goto loc_{:X};", condition, target); ctx.println("\tif ({}) goto loc_{:X};", condition, target);
return;
} }
if (auto* targetFn = ctx.graph().getFunction(target)) {
REXCODEGEN_WARN("{} at {:X} branches outside function to {:X}", instr_name, ctx.base, target);
ctx.println("\tif ({}) {{", condition);
ctx.println("\t\t{}(ctx, base);", targetFn->name());
ctx.println("\t\treturn;");
ctx.println("\t}}");
return;
}
REXCODEGEN_WARN("{} at {:X} branches outside function to unresolved {:X}", instr_name,
ctx.base, target);
ctx.println("\tif ({}) REX_FATAL(\"Unresolved branch from 0x{:08X} to 0x{:08X}\");",
condition, ctx.base, target);
} }
//============================================================================= //=============================================================================

View file

@ -31,20 +31,19 @@ bool build_attn(BuilderContext& ctx) {
} }
bool build_sync(BuilderContext& ctx) { bool build_sync(BuilderContext& ctx) {
// Memory barrier, x86 has strong ordering so this is a no-op // Guest threads use sync/lwsync around lock-free handoffs. Even on x86,
(void)ctx; // preserve the compiler and host-thread ordering point in generated C++.
ctx.println("\tstd::atomic_thread_fence(std::memory_order_seq_cst);");
return true; return true;
} }
bool build_lwsync(BuilderContext& ctx) { bool build_lwsync(BuilderContext& ctx) {
// Lightweight memory barrier, x86 has strong ordering so this is a no-op ctx.println("\tstd::atomic_thread_fence(std::memory_order_seq_cst);");
(void)ctx;
return true; return true;
} }
bool build_eieio(BuilderContext& ctx) { bool build_eieio(BuilderContext& ctx) {
// Enforce in-order execution of I/O, x86 has strong ordering so this is a no-op ctx.println("\tstd::atomic_thread_fence(std::memory_order_seq_cst);");
(void)ctx;
return true; return true;
} }

View file

@ -30,16 +30,6 @@ namespace rex::codegen {
// This file uses several magic constants from Intel SSE/SSE4 intrinsics. // This file uses several magic constants from Intel SSE/SSE4 intrinsics.
// Here are the key constants and their meanings: // Here are the key constants and their meanings:
// //
// === Dot Product Masks (simde_mm_dp_ps) ===
// The mask byte controls which elements participate in the dot product and
// where the result is broadcast. Format: 0bAAAABBBB
// High nibble (AAAA): Which source elements to multiply (bit 7=x, 6=y, 5=z, 4=w)
// Low nibble (BBBB): Which destination elements receive the result
//
// 0xEF = 0b11101111: Dot product of elements y,z,w (bits 765 set), result to all (bits 3210 set)
// This computes dot(yzw) due to guest->host vector element reversal.
// 0xFF = 0b11111111: Full 4-element dot product, result broadcast to all elements
//
// === Floating-Point Sign Bit === // === Floating-Point Sign Bit ===
// 0x80000000: IEEE 754 sign bit mask for 32-bit float // 0x80000000: IEEE 754 sign bit mask for 32-bit float
// Used for negation via XOR and sign extraction // Used for negation via XOR and sign extraction
@ -93,80 +83,69 @@ bool build_vmulfp128(BuilderContext& ctx) {
bool build_vmaddfp(BuilderContext& ctx) { bool build_vmaddfp(BuilderContext& ctx) {
ctx.emit_set_flush_mode(true); ctx.emit_set_flush_mode(true);
ctx.println( ctx.println(
"\tsimde_mm_store_ps({}.f32, simde_mm_add_ps(simde_mm_mul_ps(simde_mm_load_ps({}.f32), " "\tsimde_mm_store_ps({}.f32, simde_mm_add_ps("
"simde_mm_load_ps({}.f32)), simde_mm_load_ps({}.f32)));", "simde_mm_mul_ps(simde_mm_load_ps({}.f32), simde_mm_load_ps({}.f32)), "
"simde_mm_load_ps({}.f32)));",
ctx.v(ctx.insn.operands[0]), ctx.v(ctx.insn.operands[1]), ctx.v(ctx.insn.operands[2]), ctx.v(ctx.insn.operands[0]), ctx.v(ctx.insn.operands[1]), ctx.v(ctx.insn.operands[2]),
ctx.v(ctx.insn.operands[3])); ctx.v(ctx.insn.operands[3]));
return true; return true;
} }
bool build_vnmsubfp(BuilderContext& ctx) { bool build_vnmsubfp(BuilderContext& ctx) {
// vnmsubfp: vD = -(vA * vB - vC) - negation done by XOR with sign bit (0x80000000)
ctx.emit_set_flush_mode(true); ctx.emit_set_flush_mode(true);
ctx.println( ctx.println(
"\tsimde_mm_store_ps({}.f32, " "\tsimde_mm_store_ps({}.f32, simde_mm_sub_ps("
"simde_mm_xor_ps(simde_mm_sub_ps(simde_mm_mul_ps(simde_mm_load_ps({}.f32), " "simde_mm_load_ps({}.f32), "
"simde_mm_load_ps({}.f32)), simde_mm_load_ps({}.f32)), " "simde_mm_mul_ps(simde_mm_load_ps({}.f32), simde_mm_load_ps({}.f32))));",
"simde_mm_castsi128_ps(simde_mm_set1_epi32(int(0x80000000)))));", ctx.v(ctx.insn.operands[0]), ctx.v(ctx.insn.operands[3]), ctx.v(ctx.insn.operands[1]),
ctx.v(ctx.insn.operands[0]), ctx.v(ctx.insn.operands[1]), ctx.v(ctx.insn.operands[2]), ctx.v(ctx.insn.operands[2]));
ctx.v(ctx.insn.operands[3]));
return true; return true;
} }
bool build_vmaxfp(BuilderContext& ctx) { bool build_vmaxfp(BuilderContext& ctx) {
ctx.emit_set_flush_mode(true); ctx.emit_set_flush_mode(true);
ctx.emit_vec_fp_binary("max"); ctx.println(
"\tsimde_mm_store_ps({}.f32, simde_mm_max_ps(simde_mm_load_ps({}.f32), "
"simde_mm_load_ps({}.f32)));",
ctx.v(ctx.insn.operands[0]), ctx.v(ctx.insn.operands[1]), ctx.v(ctx.insn.operands[2]));
return true; return true;
} }
bool build_vminfp(BuilderContext& ctx) { bool build_vminfp(BuilderContext& ctx) {
ctx.emit_set_flush_mode(true); ctx.emit_set_flush_mode(true);
ctx.emit_vec_fp_binary("min"); ctx.println(
"\tsimde_mm_store_ps({}.f32, simde_mm_min_ps(simde_mm_load_ps({}.f32), "
"simde_mm_load_ps({}.f32)));",
ctx.v(ctx.insn.operands[0]), ctx.v(ctx.insn.operands[1]), ctx.v(ctx.insn.operands[2]));
return true; return true;
} }
bool build_vrefp(BuilderContext& ctx) { bool build_vrefp(BuilderContext& ctx) {
// TODO: see if we can use rcp safely // Match Xenia's OPCODE_RECIP lowering. Xenia intentionally avoids rcpps here:
// the x86 estimate is less accurate than Altivec's vrefp guarantee and is
// known to break collision-sensitive paths in other titles.
ctx.emit_set_flush_mode(true); ctx.emit_set_flush_mode(true);
ctx.emit_vec_fp_unary_expr("simde_mm_div_ps(simde_mm_set1_ps(1), simde_mm_load_ps({vA}.f32))"); ctx.emit_vec_fp_unary_expr("simde_mm_div_ps(simde_mm_set1_ps(1.0f), simde_mm_load_ps({vA}.f32))");
return true; return true;
} }
bool build_vrsqrtefp(BuilderContext& ctx) { bool build_vrsqrtefp(BuilderContext& ctx) {
// TODO: see if we can use rsqrt safely // Match Xenia's OPCODE_RSQRT lowering. The x86 estimate is not accurate
// enough for collision-sensitive code, so use sqrt + divide.
ctx.emit_set_flush_mode(true); ctx.emit_set_flush_mode(true);
ctx.emit_vec_fp_unary_expr( ctx.emit_vec_fp_unary_expr(
"simde_mm_div_ps(simde_mm_set1_ps(1), simde_mm_sqrt_ps(simde_mm_load_ps({vA}.f32)))"); "simde_mm_div_ps(simde_mm_set1_ps(1.0f), simde_mm_sqrt_ps(simde_mm_load_ps({vA}.f32)))");
return true; return true;
} }
bool build_vexptefp(BuilderContext& ctx) { bool build_vexptefp(BuilderContext& ctx) {
// SIMD exp2 estimate (~12-bit precision, matching PPC vexptefp spec) // Xenia's x64 backend lowers the corresponding POW2 vector opcode through
// Algorithm: exp2(x) = 2^n * poly(f), where n = floor(x), f = x - n // std::exp2 rather than a hardware estimate. Match that behavior so traces
auto vD = ctx.v(ctx.insn.operands[0]); // compare against the emulator reference instead of PPC-estimate accuracy.
auto vA = ctx.v(ctx.insn.operands[1]);
ctx.emit_set_flush_mode(true); ctx.emit_set_flush_mode(true);
ctx.println("\t{{"); for (size_t i = 0; i < 4; i++)
ctx.println("\t\tsimde__m128 x = simde_mm_load_ps({}.f32);", vA); ctx.println("\t{}.f32[{}] = exp2f({}.f32[{}]);", ctx.v(ctx.insn.operands[0]), i,
ctx.println( ctx.v(ctx.insn.operands[1]), i);
"\t\tsimde__m128 n = simde_mm_round_ps(x, "
"SIMDE_MM_FROUND_TO_NEG_INF | SIMDE_MM_FROUND_NO_EXC);");
ctx.println("\t\tsimde__m128 f = simde_mm_sub_ps(x, n);");
// 4th-order minimax polynomial for 2^f, f in [0,1), ~12-bit accuracy
ctx.println("\t\tsimde__m128 p = simde_mm_set1_ps(1.8775767e-3f);");
ctx.println("\t\tp = simde_mm_add_ps(simde_mm_mul_ps(p, f), simde_mm_set1_ps(8.9893397e-3f));");
ctx.println("\t\tp = simde_mm_add_ps(simde_mm_mul_ps(p, f), simde_mm_set1_ps(5.5826318e-2f));");
ctx.println("\t\tp = simde_mm_add_ps(simde_mm_mul_ps(p, f), simde_mm_set1_ps(2.4015361e-1f));");
ctx.println("\t\tp = simde_mm_add_ps(simde_mm_mul_ps(p, f), simde_mm_set1_ps(6.9315308e-1f));");
ctx.println("\t\tp = simde_mm_add_ps(simde_mm_mul_ps(p, f), simde_mm_set1_ps(1.0f));");
// Construct 2^n by adding n to the IEEE 754 exponent bias and shifting into place
ctx.println("\t\tsimde__m128i exp_bits = simde_mm_slli_epi32(");
ctx.println("\t\t\tsimde_mm_add_epi32(simde_mm_cvttps_epi32(n), simde_mm_set1_epi32(127)), 23);");
ctx.println(
"\t\tsimde_mm_store_ps({}.f32, "
"simde_mm_mul_ps(p, simde_mm_castsi128_ps(exp_bits)));",
vD);
ctx.println("\t}}");
return true; return true;
} }
@ -184,22 +163,19 @@ bool build_vlogefp(BuilderContext& ctx) {
//============================================================================= //=============================================================================
bool build_vmsum3fp128(BuilderContext& ctx) { bool build_vmsum3fp128(BuilderContext& ctx) {
// 3-element dot product accounting for guest->host vector element reversal
// 0xEF = dot(yzw) with result broadcast to all elements (see constants doc)
ctx.emit_set_flush_mode(true); ctx.emit_set_flush_mode(true);
ctx.println( ctx.println(
"\tsimde_mm_store_ps({}.f32, simde_mm_dp_ps(simde_mm_load_ps({}.f32), " "\tsimde_mm_store_ps({}.f32, rex::ppc::simde_mm_vmsum3fp128_ps("
"simde_mm_load_ps({}.f32), 0xEF));", "simde_mm_load_ps({}.f32), simde_mm_load_ps({}.f32)));",
ctx.v(ctx.insn.operands[0]), ctx.v(ctx.insn.operands[1]), ctx.v(ctx.insn.operands[2])); ctx.v(ctx.insn.operands[0]), ctx.v(ctx.insn.operands[1]), ctx.v(ctx.insn.operands[2]));
return true; return true;
} }
bool build_vmsum4fp128(BuilderContext& ctx) { bool build_vmsum4fp128(BuilderContext& ctx) {
// 4-element dot product: 0xFF = all 4 elements, result to all (see constants doc)
ctx.emit_set_flush_mode(true); ctx.emit_set_flush_mode(true);
ctx.println( ctx.println(
"\tsimde_mm_store_ps({}.f32, simde_mm_dp_ps(simde_mm_load_ps({}.f32), " "\tsimde_mm_store_ps({}.f32, rex::ppc::simde_mm_vmsum4fp128_ps("
"simde_mm_load_ps({}.f32), 0xFF));", "simde_mm_load_ps({}.f32), simde_mm_load_ps({}.f32)));",
ctx.v(ctx.insn.operands[0]), ctx.v(ctx.insn.operands[1]), ctx.v(ctx.insn.operands[2])); ctx.v(ctx.insn.operands[0]), ctx.v(ctx.insn.operands[1]), ctx.v(ctx.insn.operands[2]));
return true; return true;
} }
@ -250,7 +226,25 @@ bool build_vaddsbs(BuilderContext& ctx) {
} }
bool build_vaddshs(BuilderContext& ctx) { bool build_vaddshs(BuilderContext& ctx) {
ctx.emit_vec_int_binary("adds_epi16", "s16"); auto vD = ctx.v(ctx.insn.operands[0]);
auto vA = ctx.v(ctx.insn.operands[1]);
auto vB = ctx.v(ctx.insn.operands[2]);
ctx.println("\t{{");
ctx.println("\t\tfor (int i = 0; i < 8; ++i) {{");
ctx.println("\t\t\tint32_t sum = static_cast<int32_t>({}.s16[i]) + static_cast<int32_t>({}.s16[i]);", vA,
vB);
ctx.println("\t\t\tif (sum > 32767) {{");
ctx.println("\t\t\t\t{}.s16[i] = 32767;", vD);
ctx.println("\t\t\t\tctx.vscr_sat = 1;");
ctx.println("\t\t\t}} else if (sum < -32768) {{");
ctx.println("\t\t\t\t{}.s16[i] = -32768;", vD);
ctx.println("\t\t\t\tctx.vscr_sat = 1;");
ctx.println("\t\t\t}} else {{");
ctx.println("\t\t\t\t{}.s16[i] = static_cast<int16_t>(sum);", vD);
ctx.println("\t\t\t}}");
ctx.println("\t\t}}");
ctx.println("\t}}");
return true; return true;
} }
@ -261,25 +255,19 @@ bool build_vaddsws(BuilderContext& ctx) {
auto vB = ctx.v(ctx.insn.operands[2]); auto vB = ctx.v(ctx.insn.operands[2]);
ctx.println("\t{{"); ctx.println("\t{{");
// No direct SSE intrinsic, so use overflow detection and blend ctx.println("\t\tfor (int i = 0; i < 4; ++i) {{");
ctx.println("\t\tsimde__m128i a = simde_mm_load_si128((simde__m128i*){}.u8);", vA); ctx.println("\t\t\tlong long sum = static_cast<long long>({}.s32[i]) + static_cast<long long>({}.s32[i]);", vA,
ctx.println("\t\tsimde__m128i b = simde_mm_load_si128((simde__m128i*){}.u8);", vB); vB);
ctx.println("\t\tsimde__m128i sum = simde_mm_add_epi32(a, b);"); ctx.println("\t\t\tif (sum > 2147483647LL) {{");
// Overflow if: (a ^ sum) & (b ^ sum) has MSB set (signs of a,b match but sum differs) ctx.println("\t\t\t\t{}.s32[i] = 2147483647;", vD);
ctx.println( ctx.println("\t\t\t\tctx.vscr_sat = 1;");
"\t\tsimde__m128i overflow = simde_mm_and_si128(simde_mm_xor_si128(a, sum), " ctx.println("\t\t\t}} else if (sum < (-2147483647LL - 1LL)) {{");
"simde_mm_xor_si128(b, sum));"); ctx.println("\t\t\t\t{}.s32[i] = -2147483647 - 1;", vD);
// Saturation value: if a positive (MSB=0), use INT32_MAX; ctx.println("\t\t\t\tctx.vscr_sat = 1;");
// if negative, use INT32_MIN (a >> 31) gives all 1s if negative, all 0s ctx.println("\t\t\t}} else {{");
// if positive XOR with 0x7FFFFFFF: negative -> 0x80000000, positive -> 0x7FFFFFFF ctx.println("\t\t\t\t{}.s32[i] = static_cast<int32_t>(sum);", vD);
ctx.println( ctx.println("\t\t\t}}");
"\t\tsimde__m128i sat_val = simde_mm_xor_si128(simde_mm_srai_epi32(a, 31), " ctx.println("\t\t}}");
"simde_mm_set1_epi32(0x7FFFFFFF));");
// Blend: select sat_val where overflow MSB is set, else sum
ctx.println(
"\t\tsimde_mm_store_si128((simde__m128i*){}.u8, simde_mm_blendv_epi8(sum, sat_val, "
"overflow));",
vD);
ctx.println("\t}}"); ctx.println("\t}}");
return true; return true;
} }
@ -318,13 +306,187 @@ bool build_vadduhs(BuilderContext& ctx) {
return true; return true;
} }
bool build_vmhaddshs(BuilderContext& ctx) {
auto vD = ctx.v(ctx.insn.operands[0]);
auto vA = ctx.v(ctx.insn.operands[1]);
auto vB = ctx.v(ctx.insn.operands[2]);
auto vC = ctx.v(ctx.insn.operands[3]);
ctx.println("\t{{");
ctx.println("\t\tfor (int i = 0; i < 8; ++i) {{");
ctx.println("\t\t\tint32_t value = ((static_cast<int32_t>({}.s16[i]) * static_cast<int32_t>({}.s16[i])) >> 15) + static_cast<int32_t>({}.s16[i]);", vA,
vB, vC);
ctx.println("\t\t\tif (value > 32767) {{");
ctx.println("\t\t\t\t{}.s16[i] = 32767;", vD);
ctx.println("\t\t\t\tctx.vscr_sat = 1;");
ctx.println("\t\t\t}} else if (value < -32768) {{");
ctx.println("\t\t\t\t{}.s16[i] = -32768;", vD);
ctx.println("\t\t\t\tctx.vscr_sat = 1;");
ctx.println("\t\t\t}} else {{");
ctx.println("\t\t\t\t{}.s16[i] = static_cast<int16_t>(value);", vD);
ctx.println("\t\t\t}}");
ctx.println("\t\t}}");
ctx.println("\t}}");
return true;
}
bool build_vmhraddshs(BuilderContext& ctx) {
auto vD = ctx.v(ctx.insn.operands[0]);
auto vA = ctx.v(ctx.insn.operands[1]);
auto vB = ctx.v(ctx.insn.operands[2]);
auto vC = ctx.v(ctx.insn.operands[3]);
ctx.println("\t{{");
ctx.println("\t\tfor (int i = 0; i < 8; ++i) {{");
ctx.println("\t\t\tint32_t value = (((static_cast<int32_t>({}.s16[i]) * static_cast<int32_t>({}.s16[i])) + 0x4000) >> 15) + static_cast<int32_t>({}.s16[i]);", vA,
vB, vC);
ctx.println("\t\t\tif (value > 32767) {{");
ctx.println("\t\t\t\t{}.s16[i] = 32767;", vD);
ctx.println("\t\t\t\tctx.vscr_sat = 1;");
ctx.println("\t\t\t}} else if (value < -32768) {{");
ctx.println("\t\t\t\t{}.s16[i] = -32768;", vD);
ctx.println("\t\t\t\tctx.vscr_sat = 1;");
ctx.println("\t\t\t}} else {{");
ctx.println("\t\t\t\t{}.s16[i] = static_cast<int16_t>(value);", vD);
ctx.println("\t\t\t}}");
ctx.println("\t\t}}");
ctx.println("\t}}");
return true;
}
bool build_vmladduhm(BuilderContext& ctx) {
auto vD = ctx.v(ctx.insn.operands[0]);
auto vA = ctx.v(ctx.insn.operands[1]);
auto vB = ctx.v(ctx.insn.operands[2]);
auto vC = ctx.v(ctx.insn.operands[3]);
for (int i = 0; i < 8; ++i) {
ctx.println("\t{}.u16[{}] = static_cast<uint16_t>((static_cast<uint32_t>({}.u16[{}]) * static_cast<uint32_t>({}.u16[{}])) + static_cast<uint32_t>({}.u16[{}]));",
vD, i, vA, i, vB, i, vC, i);
}
return true;
}
bool build_vmsumubm(BuilderContext& ctx) {
auto vD = ctx.v(ctx.insn.operands[0]);
auto vA = ctx.v(ctx.insn.operands[1]);
auto vB = ctx.v(ctx.insn.operands[2]);
auto vC = ctx.v(ctx.insn.operands[3]);
for (int i = 0; i < 4; ++i) {
const int b = i * 4;
ctx.println("\t{}.u32[{}] = {}.u32[{}] + static_cast<uint32_t>({}.u8[{}]) * static_cast<uint32_t>({}.u8[{}]) + static_cast<uint32_t>({}.u8[{}]) * static_cast<uint32_t>({}.u8[{}]) + static_cast<uint32_t>({}.u8[{}]) * static_cast<uint32_t>({}.u8[{}]) + static_cast<uint32_t>({}.u8[{}]) * static_cast<uint32_t>({}.u8[{}]);",
vD, i, vC, i, vA, b, vB, b, vA, b + 1, vB, b + 1, vA, b + 2, vB, b + 2, vA, b + 3,
vB, b + 3);
}
return true;
}
bool build_vmsummbm(BuilderContext& ctx) {
auto vD = ctx.v(ctx.insn.operands[0]);
auto vA = ctx.v(ctx.insn.operands[1]);
auto vB = ctx.v(ctx.insn.operands[2]);
auto vC = ctx.v(ctx.insn.operands[3]);
for (int i = 0; i < 4; ++i) {
const int b = i * 4;
ctx.println("\t{}.u32[{}] = static_cast<uint32_t>(static_cast<int64_t>({}.s32[{}]) + static_cast<int64_t>({}.s8[{}]) * static_cast<int64_t>({}.u8[{}]) + static_cast<int64_t>({}.s8[{}]) * static_cast<int64_t>({}.u8[{}]) + static_cast<int64_t>({}.s8[{}]) * static_cast<int64_t>({}.u8[{}]) + static_cast<int64_t>({}.s8[{}]) * static_cast<int64_t>({}.u8[{}]));",
vD, i, vC, i, vA, b, vB, b, vA, b + 1, vB, b + 1, vA, b + 2, vB, b + 2, vA, b + 3,
vB, b + 3);
}
return true;
}
bool build_vmsumuhm(BuilderContext& ctx) {
auto vD = ctx.v(ctx.insn.operands[0]);
auto vA = ctx.v(ctx.insn.operands[1]);
auto vB = ctx.v(ctx.insn.operands[2]);
auto vC = ctx.v(ctx.insn.operands[3]);
for (int i = 0; i < 4; ++i) {
const int h = i * 2;
ctx.println("\t{}.u32[{}] = {}.u32[{}] + static_cast<uint32_t>({}.u16[{}]) * static_cast<uint32_t>({}.u16[{}]) + static_cast<uint32_t>({}.u16[{}]) * static_cast<uint32_t>({}.u16[{}]);",
vD, i, vC, i, vA, h, vB, h, vA, h + 1, vB, h + 1);
}
return true;
}
bool build_vmsumuhs(BuilderContext& ctx) {
auto vD = ctx.v(ctx.insn.operands[0]);
auto vA = ctx.v(ctx.insn.operands[1]);
auto vB = ctx.v(ctx.insn.operands[2]);
auto vC = ctx.v(ctx.insn.operands[3]);
ctx.println("\t{{");
ctx.println("\t\tfor (int i = 0; i < 4; ++i) {{");
ctx.println("\t\t\tint h = i * 2;");
ctx.println("\t\t\tuint64_t value = static_cast<uint64_t>({}.u32[i]) + static_cast<uint64_t>({}.u16[h]) * static_cast<uint64_t>({}.u16[h]) + static_cast<uint64_t>({}.u16[h + 1]) * static_cast<uint64_t>({}.u16[h + 1]);",
vC, vA, vB, vA, vB);
ctx.println("\t\t\tif (value > 0xFFFFFFFFULL) {{");
ctx.println("\t\t\t\t{}.u32[i] = 0xFFFFFFFFu;", vD);
ctx.println("\t\t\t\tctx.vscr_sat = 1;");
ctx.println("\t\t\t}} else {{");
ctx.println("\t\t\t\t{}.u32[i] = static_cast<uint32_t>(value);", vD);
ctx.println("\t\t\t}}");
ctx.println("\t\t}}");
ctx.println("\t}}");
return true;
}
bool build_vmsumshm(BuilderContext& ctx) {
auto vD = ctx.v(ctx.insn.operands[0]);
auto vA = ctx.v(ctx.insn.operands[1]);
auto vB = ctx.v(ctx.insn.operands[2]);
auto vC = ctx.v(ctx.insn.operands[3]);
for (int i = 0; i < 4; ++i) {
const int h = i * 2;
ctx.println("\t{}.u32[{}] = static_cast<uint32_t>(static_cast<int64_t>({}.s32[{}]) + static_cast<int64_t>({}.s16[{}]) * static_cast<int64_t>({}.s16[{}]) + static_cast<int64_t>({}.s16[{}]) * static_cast<int64_t>({}.s16[{}]));",
vD, i, vC, i, vA, h, vB, h, vA, h + 1, vB, h + 1);
}
return true;
}
bool build_vmsumshs(BuilderContext& ctx) {
auto vD = ctx.v(ctx.insn.operands[0]);
auto vA = ctx.v(ctx.insn.operands[1]);
auto vB = ctx.v(ctx.insn.operands[2]);
auto vC = ctx.v(ctx.insn.operands[3]);
ctx.println("\t{{");
ctx.println("\t\tfor (int i = 0; i < 4; ++i) {{");
ctx.println("\t\t\tint h = i * 2;");
ctx.println("\t\t\tint64_t value = static_cast<int64_t>({}.s32[i]) + static_cast<int64_t>({}.s16[h]) * static_cast<int64_t>({}.s16[h]) + static_cast<int64_t>({}.s16[h + 1]) * static_cast<int64_t>({}.s16[h + 1]);",
vC, vA, vB, vA, vB);
ctx.println("\t\t\tif (value > 2147483647LL) {{");
ctx.println("\t\t\t\t{}.s32[i] = 2147483647;", vD);
ctx.println("\t\t\t\tctx.vscr_sat = 1;");
ctx.println("\t\t\t}} else if (value < (-2147483647LL - 1LL)) {{");
ctx.println("\t\t\t\t{}.s32[i] = -2147483647 - 1;", vD);
ctx.println("\t\t\t\tctx.vscr_sat = 1;");
ctx.println("\t\t\t}} else {{");
ctx.println("\t\t\t\t{}.s32[i] = static_cast<int32_t>(value);", vD);
ctx.println("\t\t\t}}");
ctx.println("\t\t}}");
ctx.println("\t}}");
return true;
}
bool build_vsubsws(BuilderContext& ctx) { bool build_vsubsws(BuilderContext& ctx) {
// TODO: vectorize // TODO: vectorize
for (size_t i = 0; i < 4; i++) { for (size_t i = 0; i < 4; i++) {
ctx.println("\t{}.s64 = int64_t({}.s32[{}]) - int64_t({}.s32[{}]);", ctx.temp(), ctx.println("\t{}.s64 = int64_t({}.s32[{}]) - int64_t({}.s32[{}]);", ctx.temp(),
ctx.v(ctx.insn.operands[1]), i, ctx.v(ctx.insn.operands[2]), i); ctx.v(ctx.insn.operands[1]), i, ctx.v(ctx.insn.operands[2]), i);
ctx.println("\t{}.s32[{}] = {}.s64 > INT_MAX ? INT_MAX : {}.s64 < INT_MIN ? INT_MIN : {}.s64;", ctx.println("\tif ({}.s64 > INT_MAX) {{", ctx.temp());
ctx.v(ctx.insn.operands[0]), i, ctx.temp(), ctx.temp(), ctx.temp()); ctx.println("\t\t{}.s32[{}] = INT_MAX;", ctx.v(ctx.insn.operands[0]), i);
ctx.println("\t\tctx.vscr_sat = 1;");
ctx.println("\t}} else if ({}.s64 < INT_MIN) {{", ctx.temp());
ctx.println("\t\t{}.s32[{}] = INT_MIN;", ctx.v(ctx.insn.operands[0]), i);
ctx.println("\t\tctx.vscr_sat = 1;");
ctx.println("\t}} else {{");
ctx.println("\t\t{}.s32[{}] = static_cast<int32_t>({}.s64);", ctx.v(ctx.insn.operands[0]), i,
ctx.temp());
ctx.println("\t}}");
} }
return true; return true;
} }
@ -566,9 +728,10 @@ bool build_vcmpbfp(BuilderContext& ctx) {
"\tsimde_mm_store_ps({}.f32, simde_mm_and_ps(simde_mm_cmpgt_ps(simde_mm_load_ps({}.f32), " "\tsimde_mm_store_ps({}.f32, simde_mm_and_ps(simde_mm_cmpgt_ps(simde_mm_load_ps({}.f32), "
"simde_mm_load_ps({}.f32)), simde_mm_castsi128_ps(simde_mm_set1_epi32(int(0x80000000)))));", "simde_mm_load_ps({}.f32)), simde_mm_castsi128_ps(simde_mm_set1_epi32(int(0x80000000)))));",
ctx.v_temp(), vA, vB); ctx.v_temp(), vA, vB);
// lt_neg_mask = (vA < -vB) & 0x40000000 // lt_neg_mask = !(vA >= -vB) & 0x40000000. This intentionally treats
// unordered lanes as out-of-bounds, matching Xenia's vcmpbfp lowering.
ctx.println( ctx.println(
"\tsimde_mm_store_ps({}.f32, simde_mm_and_ps(simde_mm_cmplt_ps(simde_mm_load_ps({}.f32), " "\tsimde_mm_store_ps({}.f32, simde_mm_andnot_ps(simde_mm_cmpge_ps(simde_mm_load_ps({}.f32), "
"simde_mm_xor_ps(simde_mm_load_ps({}.f32), " "simde_mm_xor_ps(simde_mm_load_ps({}.f32), "
"simde_mm_castsi128_ps(simde_mm_set1_epi32(int(0x80000000))))), " "simde_mm_castsi128_ps(simde_mm_set1_epi32(int(0x80000000))))), "
"simde_mm_castsi128_ps(simde_mm_set1_epi32(int(0x40000000)))));", "simde_mm_castsi128_ps(simde_mm_set1_epi32(int(0x40000000)))));",
@ -1238,7 +1401,7 @@ bool build_vpkd3d128(BuilderContext& ctx) {
// Pack x (10 bits, position 0-9) - Guest element 0 is at index 3 // Pack x (10 bits, position 0-9) - Guest element 0 is at index 3
ctx.println( ctx.println(
"\t{}.f32 = {}.f32[3] < kPack2101010_Min10 ? kPack2101010_Min10 : ({}.f32[3] > " "\t{}.f32 = !({}.f32[3] >= kPack2101010_Min10) ? kPack2101010_Min10 : ({}.f32[3] > "
"kPack2101010_Max10 ? kPack2101010_Max10 : {}.f32[3]);", "kPack2101010_Max10 ? kPack2101010_Max10 : {}.f32[3]);",
ctx.temp(), ctx.v(ctx.insn.operands[1]), ctx.v(ctx.insn.operands[1]), ctx.temp(), ctx.v(ctx.insn.operands[1]), ctx.v(ctx.insn.operands[1]),
ctx.v(ctx.insn.operands[1])); ctx.v(ctx.insn.operands[1]));
@ -1246,7 +1409,7 @@ bool build_vpkd3d128(BuilderContext& ctx) {
// Pack y (10 bits, position 10-19) - Guest element 1 is at index 2 // Pack y (10 bits, position 10-19) - Guest element 1 is at index 2
ctx.println( ctx.println(
"\t{}.f32 = {}.f32[2] < kPack2101010_Min10 ? kPack2101010_Min10 : ({}.f32[2] > " "\t{}.f32 = !({}.f32[2] >= kPack2101010_Min10) ? kPack2101010_Min10 : ({}.f32[2] > "
"kPack2101010_Max10 ? kPack2101010_Max10 : {}.f32[2]);", "kPack2101010_Max10 ? kPack2101010_Max10 : {}.f32[2]);",
ctx.temp(), ctx.v(ctx.insn.operands[1]), ctx.v(ctx.insn.operands[1]), ctx.temp(), ctx.v(ctx.insn.operands[1]), ctx.v(ctx.insn.operands[1]),
ctx.v(ctx.insn.operands[1])); ctx.v(ctx.insn.operands[1]));
@ -1254,7 +1417,7 @@ bool build_vpkd3d128(BuilderContext& ctx) {
// Pack z (10 bits, position 20-29) - Guest element 2 is at index 1 // Pack z (10 bits, position 20-29) - Guest element 2 is at index 1
ctx.println( ctx.println(
"\t{}.f32 = {}.f32[1] < kPack2101010_Min10 ? kPack2101010_Min10 : ({}.f32[1] > " "\t{}.f32 = !({}.f32[1] >= kPack2101010_Min10) ? kPack2101010_Min10 : ({}.f32[1] > "
"kPack2101010_Max10 ? kPack2101010_Max10 : {}.f32[1]);", "kPack2101010_Max10 ? kPack2101010_Max10 : {}.f32[1]);",
ctx.temp(), ctx.v(ctx.insn.operands[1]), ctx.v(ctx.insn.operands[1]), ctx.temp(), ctx.v(ctx.insn.operands[1]), ctx.v(ctx.insn.operands[1]),
ctx.v(ctx.insn.operands[1])); ctx.v(ctx.insn.operands[1]));
@ -1262,7 +1425,7 @@ bool build_vpkd3d128(BuilderContext& ctx) {
// Pack w (2 bits, position 30-31) - Guest element 3 is at index 0 // Pack w (2 bits, position 30-31) - Guest element 3 is at index 0
ctx.println( ctx.println(
"\t{}.f32 = {}.f32[0] < kPack2101010_Min2 ? kPack2101010_Min2 : ({}.f32[0] > " "\t{}.f32 = !({}.f32[0] >= kPack2101010_Min2) ? kPack2101010_Min2 : ({}.f32[0] > "
"kPack2101010_Max2 ? kPack2101010_Max2 : {}.f32[0]);", "kPack2101010_Max2 ? kPack2101010_Max2 : {}.f32[0]);",
ctx.temp(), ctx.v(ctx.insn.operands[1]), ctx.v(ctx.insn.operands[1]), ctx.temp(), ctx.v(ctx.insn.operands[1]), ctx.v(ctx.insn.operands[1]),
ctx.v(ctx.insn.operands[1])); ctx.v(ctx.insn.operands[1]));
@ -1282,21 +1445,8 @@ bool build_vpkd3d128(BuilderContext& ctx) {
size_t srcIdx = 3 - i; // Guest element i is at host array index 3-i size_t srcIdx = 3 - i; // Guest element i is at host array index 3-i
size_t dstIdx = size_t dstIdx =
(1 - i) + (2 * ctx.insn.operands[4]); // Output reversed: elem 0 to high, elem 1 to low (1 - i) + (2 * ctx.insn.operands[4]); // Output reversed: elem 0 to high, elem 1 to low
ctx.println("\t{}.u32 = ({}.u32[{}]&0x7FFFFFFF);", ctx.temp(), ctx.v(ctx.insn.operands[1]), ctx.println("\t{}.u16[{}] = rex::float_to_xenos_half({}.f32[{}]);",
srcIdx); ctx.v(ctx.insn.operands[0]), dstIdx, ctx.v(ctx.insn.operands[1]), srcIdx);
ctx.println(
"\t{0}.u8[0] = ({1}.f32 != {1}.f32) || ({1}.f32 > 65504.0f) ? 0xFF : "
"(({2}.u32[{3}]&0x7f800000)>>23);",
ctx.v_temp(), ctx.temp(), ctx.v(ctx.insn.operands[1]), srcIdx);
ctx.println("\t{}.u16 = {}.u8[0] != 0xFF ? (({}.u32[{}]&0x7FE000)>>13) : 0x0;", ctx.temp(),
ctx.v_temp(), ctx.v(ctx.insn.operands[1]), srcIdx);
ctx.println(
"\t{0}.u16[{1}] = {2}.u8[0] != 0xFF ? ({2}.u8[0] > 0x70 ? "
"((({2}.u8[0]-0x70)<<10)+{3}.u16) : (0x71-{2}.u8[0] > 31 ? 0x0 : "
"((0x400+{3}.u16)>>(0x71-{2}.u8[0])))) : 0x7FFF;",
ctx.v(ctx.insn.operands[0]), dstIdx, ctx.v_temp(), ctx.temp());
ctx.println("\t{}.u16[{}] |= (({}.u32[{}]&0x80000000)>>16);", ctx.v(ctx.insn.operands[0]),
dstIdx, ctx.v(ctx.insn.operands[1]), srcIdx);
} }
break; break;
} }
@ -1321,20 +1471,16 @@ bool build_vpkd3d128(BuilderContext& ctx) {
case 5: // float16_4 case 5: // float16_4
{ {
// NOTE: These combinations come from game traces (heuristic handling), not the official spec. // The instruction tests and Skate 3's two-pass packing both rely on untouched lanes being
// The spec only defines the encoding, not the exact semantics of y (mask) and z (shift) here. // preserved; clearing the opposite half corrupts previously packed float16_4 data.
// Anyone reading this later should know it may need extending if new combos turn up in other
// games. Combinations observed so far: mask=2, shift=0 → write u16[3..0], zero u16[4..7]
// mask=2, shift=2 → write u16[7..4], zero u16[3..0] (first pass)
// mask=3, shift=0 → write u16[3..0] without zeroing (second pass, preserves the upper half)
uint32_t mask = ctx.insn.operands[3]; uint32_t mask = ctx.insn.operands[3];
uint32_t shift = ctx.insn.operands[4]; uint32_t shift = ctx.insn.operands[4];
// Guard fix: The shift bound was too loose (shift=3 would cause dstIdx to reach 9, an OOB // Guard fix: The shift bound was too loose (shift=3 would cause dstIdx to reach 9, an OOB
// store). We also explicitly reject shift == 1 since the clear block below only handles 0 // store). We also explicitly reject shift == 1 because only the lower and upper half
// and 2. Furthermore, we explicitly warn on unexpected mask values (e.g., 0, 1) to avoid // placements are currently understood. Furthermore, we explicitly warn on unexpected mask
// silent fallthroughs and silently generating miscompiled code. // values (e.g., 0, 1) to avoid silent fallthroughs and silently generating miscompiled code.
if ((shift != 0 && shift != 2) || (mask != 2 && mask != 3)) { if ((shift != 0 && shift != 2) || (mask != 2 && mask != 3)) {
REXCODEGEN_WARN("Unexpected float16_4 pack instruction at {:X} (mask={}, shift={})", REXCODEGEN_WARN("Unexpected float16_4 pack instruction at {:X} (mask={}, shift={})",
ctx.base, mask, shift); ctx.base, mask, shift);
@ -1343,39 +1489,15 @@ bool build_vpkd3d128(BuilderContext& ctx) {
return true; return true;
} }
// mask=2: before writing, clear the half that will NOT be written. // Preserve untouched lanes; paired float16_4 packs fill lower/upper halves separately.
// Shift is guaranteed to be 0 or 2 at this point due to the guard above. // Guest element order is reversed in Rex's host storage, but each packed half keeps
if (mask == 2) { // element 0 in the high halfword and element 1 in the low halfword.
// Optimization: Emit a single u64 write instead of two u32 writes.
// shift=0 → clears upper half u64[1]
// shift=2 → clears lower half u64[0]
size_t clearU64Start = (shift == 0) ? 1 : 0;
ctx.println("\t{}.u64[{}] = 0;", ctx.v(ctx.insn.operands[0]), clearU64Start);
}
// Invariant: dstIdx must stay under 8 (valid u16 lanes are 0..7).
// Capping the shift to 0 or 2 in the guard check above ensures this is safe:
// it restricts the max dstIdx to (3 - 0) + (2 * 2) = 7.
// Do not widen the shift bounds without adjusting this logic.
for (size_t i = 0; i < 4; i++) { for (size_t i = 0; i < 4; i++) {
size_t srcIdx = 3 - i; size_t srcIdx = 3 - i;
size_t dstIdx = (3 - i) + (2 * shift); size_t dstIdx = (3 - i) + (2 * shift);
ctx.println("\t{}.u32 = ({}.u32[{}]&0x7FFFFFFF);", ctx.temp(), ctx.v(ctx.insn.operands[1]), ctx.println("\t{}.u16[{}] = rex::float_to_xenos_half({}.f32[{}], false, true);",
srcIdx); ctx.v(ctx.insn.operands[0]), dstIdx, ctx.v(ctx.insn.operands[1]), srcIdx);
ctx.println(
"\t{0}.u8[0] = ({1}.f32 != {1}.f32) || ({1}.f32 > 65504.0f) ? 0xFF : "
"(({2}.u32[{3}]&0x7f800000)>>23);",
ctx.v_temp(), ctx.temp(), ctx.v(ctx.insn.operands[1]), srcIdx);
ctx.println("\t{}.u16 = {}.u8[0] != 0xFF ? (({}.u32[{}]&0x7FE000)>>13) : 0x0;", ctx.temp(),
ctx.v_temp(), ctx.v(ctx.insn.operands[1]), srcIdx);
ctx.println(
"\t{0}.u16[{1}] = {2}.u8[0] != 0xFF ? ({2}.u8[0] > 0x70 ? "
"((({2}.u8[0]-0x70)<<10)+{3}.u16) : (0x71-{2}.u8[0] > 31 ? 0x0 : "
"((0x400+{3}.u16)>>(0x71-{2}.u8[0])))) : 0x7FFF;",
ctx.v(ctx.insn.operands[0]), dstIdx, ctx.v_temp(), ctx.temp());
ctx.println("\t{}.u16[{}] |= (({}.u32[{}]&0x80000000)>>16);", ctx.v(ctx.insn.operands[0]),
dstIdx, ctx.v(ctx.insn.operands[1]), srcIdx);
} }
break; break;
} }
@ -1429,7 +1551,8 @@ bool build_vupkd3d128(BuilderContext& ctx) {
for (size_t i = 0; i < 2; i++) { for (size_t i = 0; i < 2; i++) {
ctx.println("\t{}.f32 = 3.0f;", ctx.temp()); ctx.println("\t{}.f32 = 3.0f;", ctx.temp());
ctx.println("\t{}.s32 += {}.s16[{}];", ctx.temp(), ctx.v(ctx.insn.operands[1]), 1 - i); ctx.println("\t{}.s32 += {}.s16[{}];", ctx.temp(), ctx.v(ctx.insn.operands[1]), 1 - i);
ctx.println("\t{}.f32[{}] = {}.f32;", ctx.v_temp(), 3 - i, ctx.temp()); ctx.println("\t{}.u32[{}] = {}.u32 == 0x403F8000u ? 0x7FC00000u : {}.u32;",
ctx.v_temp(), 3 - i, ctx.temp(), ctx.temp());
} }
ctx.println("\t{}.f32[1] = 0.0f;", ctx.v_temp()); ctx.println("\t{}.f32[1] = 0.0f;", ctx.v_temp());
ctx.println("\t{}.f32[0] = 1.0f;", ctx.v_temp()); ctx.println("\t{}.f32[0] = 1.0f;", ctx.v_temp());
@ -1451,11 +1574,13 @@ bool build_vupkd3d128(BuilderContext& ctx) {
ctx.println("\t{}.u32[3] = {}.u32[0];", vDst, ctx.v_temp()); ctx.println("\t{}.u32[3] = {}.u32[0];", vDst, ctx.v_temp());
// y (bits 10-19) - sign extend from 10 bits --> Guest element 1 (host u32[2]) // y (bits 10-19) - sign extend from 10 bits --> Guest element 1 (host u32[2])
ctx.println("\t{}.s32 = ({}.s32[0] << 12) >> 22;", ctx.temp(), vSrc); ctx.println("\t{}.s32 = ({}.s32[0] << 12) >> 22;", ctx.temp(), vSrc);
ctx.println("\t{}.s32[0] = {}.s32 + 0x40400000;", ctx.v_temp(), ctx.temp()); ctx.println("\t{}.s32[0] = {}.s32 == -512 ? 0x7FC00000 : ({}.s32 + 0x40400000);",
ctx.v_temp(), ctx.temp(), ctx.temp());
ctx.println("\t{}.u32[2] = {}.u32[0];", vDst, ctx.v_temp()); ctx.println("\t{}.u32[2] = {}.u32[0];", vDst, ctx.v_temp());
// z (bits 20-29) - sign extend from 10 bits --> Guest element 2 (host u32[1]) // z (bits 20-29) - sign extend from 10 bits --> Guest element 2 (host u32[1])
ctx.println("\t{}.s32 = ({}.s32[0] << 2) >> 22;", ctx.temp(), vSrc); ctx.println("\t{}.s32 = ({}.s32[0] << 2) >> 22;", ctx.temp(), vSrc);
ctx.println("\t{}.s32[0] = {}.s32 + 0x40400000;", ctx.v_temp(), ctx.temp()); ctx.println("\t{}.s32[0] = {}.s32 == -512 ? 0x7FC00000 : ({}.s32 + 0x40400000);",
ctx.v_temp(), ctx.temp(), ctx.temp());
ctx.println("\t{}.u32[1] = {}.u32[0];", vDst, ctx.v_temp()); ctx.println("\t{}.u32[1] = {}.u32[0];", vDst, ctx.v_temp());
// w (bits 30-31) - 2 bits, convert to 1.0+w form --> Guest element 3 (host u32[0]) // w (bits 30-31) - 2 bits, convert to 1.0+w form --> Guest element 3 (host u32[0])
ctx.println("\t{}.u32[0] = ({}.u32[0] >> 30) | 0x3F800000;", ctx.v_temp(), vSrc); ctx.println("\t{}.u32[0] = ({}.u32[0] >> 30) | 0x3F800000;", ctx.v_temp(), vSrc);
@ -1472,16 +1597,8 @@ bool build_vupkd3d128(BuilderContext& ctx) {
for (size_t i = 0; i < 2; i++) { for (size_t i = 0; i < 2; i++) {
size_t srcIdx = 1 - i; // Read from u16[1], u16[0] size_t srcIdx = 1 - i; // Read from u16[1], u16[0]
size_t dstIdx = 3 - i; // Write to u32[3], u32[2] size_t dstIdx = 3 - i; // Write to u32[3], u32[2]
ctx.println("\t{}.u32 = {}.u16[{}];", ctx.temp(), vSrc, srcIdx); ctx.println("\t{}.f32[{}] = rex::xenos_half_to_float({}.u16[{}]);", vDst, dstIdx, vSrc,
// Extract sign, exponent, mantissa srcIdx);
ctx.println(
"\t{}.u32[0] = (({}.u32 & 0x8000) << 16) | ((({}.u32 & 0x7C00) + 0x1C000) << 13) | "
"(({}.u32 & 0x03FF) << 13);",
ctx.v_temp(), ctx.temp(), ctx.temp(), ctx.temp());
// Handle zero/denorm case
ctx.println("\tif (({}.u32 & 0x7C00) == 0) {}.u32[0] = ({}.u32 & 0x8000) << 16;",
ctx.temp(), ctx.v_temp(), ctx.temp());
ctx.println("\t{}.u32[{}] = {}.u32[0];", vDst, dstIdx, ctx.v_temp());
} }
ctx.println("\t{}.f32[1] = 0.0f;", vDst); ctx.println("\t{}.f32[1] = 0.0f;", vDst);
ctx.println("\t{}.f32[0] = 1.0f;", vDst); ctx.println("\t{}.f32[0] = 1.0f;", vDst);
@ -1497,7 +1614,8 @@ bool build_vupkd3d128(BuilderContext& ctx) {
size_t dstIdx = 3 - i; // Write to f32 indices 3, 2, 1, 0 (Guest elements 0, 1, 2, 3) size_t dstIdx = 3 - i; // Write to f32 indices 3, 2, 1, 0 (Guest elements 0, 1, 2, 3)
ctx.println("\t{}.f32 = 3.0f;", ctx.temp()); ctx.println("\t{}.f32 = 3.0f;", ctx.temp());
ctx.println("\t{}.s32 += {}.s16[{}];", ctx.temp(), ctx.v(ctx.insn.operands[1]), srcIdx); ctx.println("\t{}.s32 += {}.s16[{}];", ctx.temp(), ctx.v(ctx.insn.operands[1]), srcIdx);
ctx.println("\t{}.f32[{}] = {}.f32;", ctx.v(ctx.insn.operands[0]), dstIdx, ctx.temp()); ctx.println("\t{}.u32[{}] = {}.u32 == 0x403F8000u ? 0x7FC00000u : {}.u32;",
ctx.v(ctx.insn.operands[0]), dstIdx, ctx.temp(), ctx.temp());
} }
break; break;
} }
@ -1506,21 +1624,12 @@ bool build_vupkd3d128(BuilderContext& ctx) {
{ {
auto vSrc = ctx.v(ctx.insn.operands[1]); auto vSrc = ctx.v(ctx.insn.operands[1]);
auto vDst = ctx.v(ctx.insn.operands[0]); auto vDst = ctx.v(ctx.insn.operands[0]);
// Unpack 4 float16s from Guest elements 2-3 (host u16[0-3]) to elements 0-3 // Read the same halfword order produced by FLOAT16_4 packing.
// Guest element order is reversed in host arrays
for (size_t i = 0; i < 4; i++) { for (size_t i = 0; i < 4; i++) {
size_t srcIdx = 3 - i; // Read from u16 indices 3, 2, 1, 0 (guest shorts 0, 1, 2, 3) size_t srcIdx = 3 - i;
size_t dstIdx = 3 - i; // Write to u32 indices 3, 2, 1, 0 (Guest elements 0, 1, 2, 3) size_t dstIdx = 3 - i; // Write to u32 indices 3, 2, 1, 0 (Guest elements 0, 1, 2, 3)
ctx.println("\t{}.u32 = {}.u16[{}];", ctx.temp(), vSrc, srcIdx); ctx.println("\t{}.f32[{}] = rex::xenos_half_to_float({}.u16[{}]);", vDst, dstIdx, vSrc,
// Extract sign, exponent, mantissa and convert to float32 srcIdx);
ctx.println(
"\t{}.u32[0] = (({}.u32 & 0x8000) << 16) | ((({}.u32 & 0x7C00) + 0x1C000) << 13) | "
"(({}.u32 & 0x03FF) << 13);",
ctx.v_temp(), ctx.temp(), ctx.temp(), ctx.temp());
// Handle zero/denorm case
ctx.println("\tif (({}.u32 & 0x7C00) == 0) {}.u32[0] = ({}.u32 & 0x8000) << 16;",
ctx.temp(), ctx.v_temp(), ctx.temp());
ctx.println("\t{}.u32[{}] = {}.u32[0];", vDst, dstIdx, ctx.v_temp());
} }
break; break;
} }

View file

@ -61,11 +61,11 @@ nlohmann::json buildTemplateData(const rex::codegen::CodegenContext& ctx,
funcName = crtIt->second; funcName = crtIt->second;
isRexcrt = true; isRexcrt = true;
} else if (fn->base() == ctx.analysisState().entryPoint) { } else if (fn->base() == ctx.analysisState().entryPoint) {
funcName = "xstart"; funcName = cfg.symbolPrefix + "xstart";
} else if (!fn->name().empty()) { } else if (!fn->name().empty()) {
funcName = fn->name(); funcName = fn->name();
} else { } else {
funcName = fmt::format("sub_{:08X}", fn->base()); funcName = fmt::format("{}sub_{:08X}", cfg.symbolPrefix, fn->base());
} }
functionsJson.push_back({ functionsJson.push_back({

View file

@ -88,6 +88,9 @@ void ApplyToml(const toml::table& toml, RecompilerConfig& cfg, const std::string
if (auto v = toml["project_name"].value<std::string>()) { if (auto v = toml["project_name"].value<std::string>()) {
MergeScalar(cfg.projectName, *v, "project_name"); MergeScalar(cfg.projectName, *v, "project_name");
} }
if (auto v = toml["symbol_prefix"].value<std::string>()) {
MergeScalar(cfg.symbolPrefix, *v, "symbol_prefix");
}
if (auto v = toml["file_path"].value<std::string>()) { if (auto v = toml["file_path"].value<std::string>()) {
MergeScalar(cfg.filePath, *v, "file_path"); MergeScalar(cfg.filePath, *v, "file_path");
} }
@ -486,9 +489,6 @@ bool FinalizeConfig(RecompilerConfig& cfg) {
for (const auto& error : result.errors) { for (const auto& error : result.errors) {
REXCODEGEN_ERROR("[config] {}", error); REXCODEGEN_ERROR("[config] {}", error);
} }
if (!result.valid) {
ok = false;
}
return ok; return ok;
} }

View file

@ -61,6 +61,18 @@ const char* AuthorityName(FunctionAuthority auth) {
} }
} }
static bool HasEmittedBlockContaining(const FunctionNode* node, uint32_t addr) {
if (!node) {
return false;
}
for (const auto& block : node->blocks()) {
if (block.contains(addr)) {
return true;
}
}
return false;
}
//============================================================================= //=============================================================================
// FunctionNode // FunctionNode
//============================================================================= //=============================================================================
@ -359,11 +371,11 @@ std::string FunctionNode::emitCpp(const EmitContext& ctx) const {
std::string name; std::string name;
if (base() == ctx.entryPoint) { if (base() == ctx.entryPoint) {
name = "xstart"; name = fmt::format("{}xstart", ctx.config.symbolPrefix);
} else if (!name_.empty()) { } else if (!name_.empty()) {
name = name_; name = name_;
} else { } else {
name = fmt::format("sub_{:08X}", base()); name = fmt::format("{}sub_{:08X}", ctx.config.symbolPrefix, base());
} }
emit_println(out, "// STUB: Function at 0x{:08X} has no discovered code blocks", base()); emit_println(out, "// STUB: Function at 0x{:08X} has no discovered code blocks", base());
@ -471,11 +483,11 @@ std::string FunctionNode::emitCpp(const EmitContext& ctx) const {
// --- Function name --- // --- Function name ---
std::string name; std::string name;
if (base() == ctx.entryPoint) { if (base() == ctx.entryPoint) {
name = "xstart"; name = fmt::format("{}xstart", ctx.config.symbolPrefix);
} else if (!name_.empty()) { } else if (!name_.empty()) {
name = name_; name = name_;
} else { } else {
name = fmt::format("sub_{:08X}", base()); name = fmt::format("{}sub_{:08X}", ctx.config.symbolPrefix, base());
} }
// Function signature with weak/alias pattern // Function signature with weak/alias pattern
@ -564,7 +576,7 @@ std::string FunctionNode::emitCpp(const EmitContext& ctx) const {
for (auto label : activeJt->targets) { for (auto label : activeJt->targets) {
labels.emplace(label); labels.emplace(label);
} }
REXCODEGEN_TRACE("Late-detected jump table at 0x{:08X} with {} entries", blockBase, REXCODEGEN_INFO("Late-detected jump table at 0x{:08X} with {} entries", blockBase,
activeJt->targets.size()); activeJt->targets.size());
} }
} }
@ -617,7 +629,8 @@ std::string FunctionNode::emitCpp(const EmitContext& ctx) const {
for (auto it = sehInfo->scopes.rbegin(); it != sehInfo->scopes.rend(); ++it) { for (auto it = sehInfo->scopes.rbegin(); it != sehInfo->scopes.rend(); ++it) {
const auto& scope = *it; const auto& scope = *it;
if (scope.filter == 0 && scope.handler != 0) { if (scope.filter == 0 && scope.handler != 0) {
emit_println(body, "\t\t\tsub_{:08X}(ctx, base); // __finally handler", scope.handler); emit_println(body, "\t\t\t{}sub_{:08X}(ctx, base); // __finally handler",
ctx.config.symbolPrefix, scope.handler);
} }
} }
@ -754,6 +767,7 @@ FunctionNode* FunctionGraph::addFunction(uint32_t base, uint32_t size, FunctionA
// Create new node // Create new node
auto node = std::make_unique<FunctionNode>(base, size, authority); auto node = std::make_unique<FunctionNode>(base, size, authority);
FunctionNode* nodePtr = node.get(); FunctionNode* nodePtr = node.get();
nodePtr->setName(makeSymbolName(nodePtr->name(), authority));
functions_[base] = std::move(node); functions_[base] = std::move(node);
functionsByBase_[base] = nodePtr; functionsByBase_[base] = nodePtr;
@ -770,7 +784,7 @@ FunctionNode* FunctionGraph::addFunction(uint32_t base, uint32_t size, FunctionA
std::string_view name, bool hasXrefs) { std::string_view name, bool hasXrefs) {
auto* node = addFunction(base, size, authority, hasXrefs); auto* node = addFunction(base, size, authority, hasXrefs);
if (node && !name.empty()) { if (node && !name.empty()) {
node->setName(std::string(name)); node->setName(makeSymbolName(name, authority));
} }
return node; return node;
} }
@ -880,10 +894,24 @@ size_t FunctionGraph::sealedCount() const {
void FunctionGraph::setFunctionName(uint32_t entry, std::string name) { void FunctionGraph::setFunctionName(uint32_t entry, std::string name) {
if (auto* node = getFunction(entry)) { if (auto* node = getFunction(entry)) {
node->setName(std::move(name)); node->setName(makeSymbolName(name, node->authority()));
} }
} }
void FunctionGraph::setSymbolPrefix(std::string prefix) {
symbolPrefix_ = std::move(prefix);
}
std::string FunctionGraph::makeSymbolName(std::string_view name, FunctionAuthority authority) const {
if (authority == FunctionAuthority::IMPORT || symbolPrefix_.empty()) {
return std::string(name);
}
if (name.starts_with(symbolPrefix_)) {
return std::string(name);
}
return fmt::format("{}{}", symbolPrefix_, name);
}
void FunctionGraph::setFunctionHasExceptionHandler(uint32_t entry, bool val) { void FunctionGraph::setFunctionHasExceptionHandler(uint32_t entry, bool val) {
if (auto* node = getFunction(entry)) { if (auto* node = getFunction(entry)) {
node->setHasExceptionHandler(val); node->setHasExceptionHandler(val);
@ -932,6 +960,18 @@ void FunctionGraph::addUnresolvedJumpToFunction(uint32_t entry, uint32_t site, u
if (!node) if (!node)
return; return;
// Configured chunks are address-taken entry points inside a parent function.
// They are local labels only when the destination block is actually emitted
// in this function body; otherwise the generated C++ must tail-call the
// separate landing function instead of emitting an invalid cross-function goto.
if (!isCall && chunkParent(target) != 0 && HasEmittedBlockContaining(node, target)) {
node->addLabel(target);
REXCODEGEN_TRACE(
"FunctionGraph: resolved 0x{:08X}->0x{:08X} as emitted chunk label in 0x{:08X}",
site, target, entry);
return;
}
// Try immediate resolution against existing functions/imports // Try immediate resolution against existing functions/imports
if (auto* targetFn = getFunction(target)) { if (auto* targetFn = getFunction(target)) {
// Target is a known function - resolve as call or tail call // Target is a known function - resolve as call or tail call
@ -983,9 +1023,24 @@ size_t FunctionGraph::tryResolveFunction(uint32_t entry) {
jumps.size()); jumps.size());
for (const auto& jump : jumps) { for (const auto& jump : jumps) {
// Try internal label first // Configured chunks are internal labels only if their destination block is
if (node->tryResolveAsInternalLabel(jump.target)) { // emitted in this function body. Other chunk entries are separate callable
REXCODEGEN_TRACE(" 0x{:08X}->0x{:08X}: resolved as internal label", jump.site, jump.target); // landings and must stay tail-call edges.
if (!jump.isCall && chunkParent(jump.target) != 0 &&
HasEmittedBlockContaining(node, jump.target)) {
node->addLabel(jump.target);
node->removeUnresolvedJump(jump.site);
resolved++;
REXCODEGEN_TRACE(" 0x{:08X}->0x{:08X}: resolved as emitted chunk label", jump.site,
jump.target);
continue;
}
// A branch to this function's own entry is a loop. Other known entry points
// inside an overlapping PDATA range must stay function/tail-call edges.
if (!jump.isCall && jump.target == entry && node->tryResolveAsInternalLabel(jump.target)) {
REXCODEGEN_TRACE(" 0x{:08X}->0x{:08X}: resolved as internal entry label", jump.site,
jump.target);
node->removeUnresolvedJump(jump.site); node->removeUnresolvedJump(jump.site);
resolved++; resolved++;
continue; continue;
@ -1005,6 +1060,14 @@ size_t FunctionGraph::tryResolveFunction(uint32_t entry) {
continue; continue;
} }
// Try internal label after entry-point resolution.
if (node->tryResolveAsInternalLabel(jump.target)) {
REXCODEGEN_TRACE(" 0x{:08X}->0x{:08X}: resolved as internal label", jump.site, jump.target);
node->removeUnresolvedJump(jump.site);
resolved++;
continue;
}
// Try import // Try import
if (isImport(jump.target)) { if (isImport(jump.target)) {
auto* importNode = getFunction(jump.target); auto* importNode = getFunction(jump.target);
@ -1056,10 +1119,10 @@ size_t FunctionGraph::sealAllReady() {
sealed++; sealed++;
} else { } else {
couldNotSeal++; couldNotSeal++;
REXCODEGEN_DEBUG("FunctionGraph::sealAllReady: 0x{:08X} ({}) cannot seal ({} unresolved)", REXCODEGEN_WARN("FunctionGraph::sealAllReady: 0x{:08X} ({}) cannot seal ({} unresolved)",
base, node->name(), node->unresolvedJumps().size()); base, node->name(), node->unresolvedJumps().size());
for (const auto& jump : node->unresolvedJumps()) { for (const auto& jump : node->unresolvedJumps()) {
REXCODEGEN_DEBUG(" 0x{:08X} -> 0x{:08X}", jump.site, jump.target); REXCODEGEN_WARN(" 0x{:08X} -> 0x{:08X}", jump.site, jump.target);
} }
} }
} }
@ -1102,16 +1165,25 @@ void FunctionGraph::sealAll() {
throw std::runtime_error(msg); throw std::runtime_error(msg);
} }
REXCODEGEN_TRACE("FunctionGraph::sealAll: all {} functions sealed", functions_.size()); REXCODEGEN_INFO("FunctionGraph::sealAll: all {} functions sealed", functions_.size());
} }
//============================================================================= //=============================================================================
// Vacancy Checking // Vacancy Checking
//============================================================================= //=============================================================================
void FunctionGraph::registerChunk(uint32_t base, uint32_t size) { void FunctionGraph::registerChunk(uint32_t base, uint32_t size, uint32_t parent) {
chunks_.emplace_back(base, size); chunks_.emplace_back(base, size);
REXCODEGEN_TRACE("FunctionGraph: registered chunk 0x{:08X}-0x{:08X}", base, base + size); if (parent != 0) {
chunkParents_[base] = parent;
}
REXCODEGEN_TRACE("FunctionGraph: registered chunk 0x{:08X}-0x{:08X} parent=0x{:08X}", base,
base + size, parent);
}
uint32_t FunctionGraph::chunkParent(uint32_t base) const {
auto it = chunkParents_.find(base);
return it != chunkParents_.end() ? it->second : 0;
} }
bool FunctionGraph::isVacant(uint32_t fromAddr, uint32_t targetAddr) const { bool FunctionGraph::isVacant(uint32_t fromAddr, uint32_t targetAddr) const {
@ -1173,41 +1245,74 @@ bool FunctionGraph::isMergeableEntryPoint(uint32_t addr) const {
TargetKind FunctionGraph::classifyTarget(uint32_t target, uint32_t callerAddr, TargetKind FunctionGraph::classifyTarget(uint32_t target, uint32_t callerAddr,
bool isCallInstruction) const { bool isCallInstruction) const {
// Find the caller's function // Most builder callers pass the current function base as callerAddr. Prefer
const FunctionNode* callerFn = getFunctionContaining(callerAddr); // the exact node so overlapping PDATA ranges do not make branches look like
// self-loops in an enclosing function.
const FunctionNode* callerFn = getFunction(callerAddr);
if (!callerFn) {
callerFn = getFunctionContaining(callerAddr);
}
// Case 1: Target is an import - always a call/tail-call // Case 1: Target is an import - always a call/tail-call
if (isImport(target)) { if (isImport(target)) {
return TargetKind::Import; return TargetKind::Import;
} }
// Case 2: Target is the caller's own entry point // Case 2: Target is a configured chunk whose block is emitted in this
if (callerFn && target == callerFn->base()) { // function body. If the chunk is only present as a separate landing function,
// it must be treated as a tail call so generated C++ never jumps to a label
// declared in another function.
if (callerFn && !isCallInstruction) {
if (chunkParent(target) != 0 && HasEmittedBlockContaining(callerFn, target)) {
return TargetKind::InternalLabel;
}
}
// Case 3: Target is the emitting function's own entry point. Builder callers
// pass the current function base as callerAddr, which is more reliable than
// containing-range lookup when PDATA functions overlap.
if (target == callerAddr) {
// bl to own base = recursive call (Function) // bl to own base = recursive call (Function)
// b to own base = loop back to start (InternalLabel) // b to own base = loop back to start (InternalLabel)
return isCallInstruction ? TargetKind::Function : TargetKind::InternalLabel; return isCallInstruction ? TargetKind::Function : TargetKind::InternalLabel;
} }
// Case 3: Target is a DIFFERENT function's entry point - this is a call/tail-call // Case 4: Target is a DIFFERENT function's entry point - this is a call/tail-call
// This handles cases where a small thunk function branches to another function // This handles cases where a small thunk function branches to another function
// whose entry point happens to fall within the thunk's address range // whose entry point happens to fall within the thunk's address range
if (isEntryPoint(target)) { if (isEntryPoint(target)) {
return TargetKind::Function; return TargetKind::Function;
} }
// Case 4: Target is inside caller's function -> InternalLabel // Case 5: Target is inside caller's function -> InternalLabel
// For bl, this would be a rare PIC code pattern // For bl, this would be a rare PIC code pattern
if (callerFn && callerFn->containsAddress(target)) { if (callerFn && callerFn->containsAddress(target)) {
return TargetKind::InternalLabel; return TargetKind::InternalLabel;
} }
// Case 5: Unknown target // Case 6: Unknown target
return TargetKind::Unknown; return TargetKind::Unknown;
} }
void FunctionGraph::notifyFunctionAdded(FunctionNode* newFunction) { void FunctionGraph::notifyFunctionAdded(FunctionNode* newFunction) {
for (auto& [base, node] : functions_) { for (auto& [base, node] : functions_) {
if (node.get() != newFunction && node->isPending()) { if (node.get() != newFunction && node->isPending()) {
auto jumps = node->unresolvedJumps();
for (const auto& jump : jumps) {
if (jump.target != newFunction->base()) {
continue;
}
if (!jump.isCall && chunkParent(jump.target) != 0 &&
HasEmittedBlockContaining(node.get(), jump.target)) {
node->addLabel(jump.target);
node->removeUnresolvedJump(jump.site);
REXCODEGEN_TRACE(
"FunctionGraph: resolved 0x{:08X}->0x{:08X} as emitted chunk label in 0x{:08X}",
jump.site, jump.target, node->base());
}
}
node->tryResolveAgainst(newFunction); node->tryResolveAgainst(newFunction);
} }
} }

View file

@ -1299,9 +1299,13 @@ BoundsInfo scanForBounds(DecodedBinary& decoded, uint32_t bctrAddr, const CodeRe
if (!insn) if (!insn)
break; break;
// Stop at unconditional terminators - scanning past basic block boundaries // EA commonly emits switch bounds as:
// risks finding unrelated comparisons on the index register // cmplwi idx,max; ble table_path; b default_path; table_path: ... bctr
if (isTerminator(*insn) && !isConditional(*insn)) // Keep scanning past that unconditional default branch so the compare is
// visible to the jump-table reader.
if (isTerminator(*insn) && !isConditional(*insn) &&
insn->opcode != rex::codegen::ppc::Opcode::b &&
insn->opcode != rex::codegen::ppc::Opcode::ba)
break; break;
using namespace rex::codegen::ppc; using namespace rex::codegen::ppc;

View file

@ -462,6 +462,15 @@ static const std::unordered_map<int, Builder>& GetDispatchTable() {
{PPC_INST_VADDUWM, build_vadduwm}, {PPC_INST_VADDUWM, build_vadduwm},
{PPC_INST_VADDUWS, build_vadduws}, {PPC_INST_VADDUWS, build_vadduws},
{PPC_INST_VADDUHS, build_vadduhs}, {PPC_INST_VADDUHS, build_vadduhs},
{PPC_INST_VMHADDSHS, build_vmhaddshs},
{PPC_INST_VMHRADDSHS, build_vmhraddshs},
{PPC_INST_VMLADDUHM, build_vmladduhm},
{PPC_INST_VMSUMUBM, build_vmsumubm},
{PPC_INST_VMSUMMBM, build_vmsummbm},
{PPC_INST_VMSUMUHM, build_vmsumuhm},
{PPC_INST_VMSUMUHS, build_vmsumuhs},
{PPC_INST_VMSUMSHM, build_vmsumshm},
{PPC_INST_VMSUMSHS, build_vmsumshs},
{PPC_INST_VSUBSBS, build_vsubsbs}, {PPC_INST_VSUBSBS, build_vsubsbs},
{PPC_INST_VSUBSWS, build_vsubsws}, {PPC_INST_VSUBSWS, build_vsubsws},
{PPC_INST_VSUBUBM, build_vsububm}, {PPC_INST_VSUBUBM, build_vsububm},

View file

@ -81,6 +81,22 @@ void discoverFunction(CodegenContext& ctx, uint32_t funcAddr,
} }
} }
// Address-taken chunks are alternate entries into a parent function. When
// emitted as standalone dispatcher targets, they still need to run forward
// through the parent's shared tail/epilogue instead of stopping at the small
// chunk extent recorded in config.
if (uint32_t parentAddr = graph.chunkParent(funcAddr); parentAddr != 0) {
if (auto* parentNode = graph.getFunction(parentAddr)) {
const uint32_t parentEnd = parentNode->base() + parentNode->size();
if (parentEnd > funcAddr && parentEnd - funcAddr > pdataSize) {
pdataSize = parentEnd - funcAddr;
REXCODEGEN_TRACE(
"Analyze: 0x{:08X} is chunk of 0x{:08X}, using parent-derived size={}",
funcAddr, parentAddr, pdataSize);
}
}
}
// Find the code region containing this function // Find the code region containing this function
const CodeRegion* region = nullptr; const CodeRegion* region = nullptr;
for (const auto& r : ctx.scan.codeRegions) { for (const auto& r : ctx.scan.codeRegions) {
@ -94,8 +110,29 @@ void discoverFunction(CodegenContext& ctx, uint32_t funcAddr,
return; return;
} }
// Pass pdataSize so forward branches within function extent are correctly identified // Pass pdataSize so forward branches within function extent are correctly identified.
auto result = discoverBlocks(decoded, funcAddr, *region, knownFunctions, pdataSize); // Configured chunks are address-taken entries inside their parent, not hard local
// branch boundaries for that parent.
auto effectiveKnownFunctions = knownFunctions;
const uint32_t chunkParent = graph.chunkParent(funcAddr);
const uint32_t chunkLookupParent = chunkParent != 0 ? chunkParent : funcAddr;
auto chunkIt = ctx.analysisState().chunksByParent.find(chunkLookupParent);
if (chunkIt != ctx.analysisState().chunksByParent.end()) {
for (uint32_t chunkAddr : chunkIt->second) {
effectiveKnownFunctions.erase(chunkAddr);
}
}
if (pdataSize != 0) {
const uint32_t funcEnd = funcAddr + pdataSize;
for (const auto& [_, chunkAddrs] : ctx.analysisState().chunksByParent) {
for (uint32_t chunkAddr : chunkAddrs) {
if (chunkAddr >= funcAddr && chunkAddr < funcEnd) {
effectiveKnownFunctions.erase(chunkAddr);
}
}
}
}
auto result = discoverBlocks(decoded, funcAddr, *region, effectiveKnownFunctions, pdataSize);
if (result.blocks.empty()) { if (result.blocks.empty()) {
REXCODEGEN_WARN("Analyze: no blocks found for function 0x{:08X}", funcAddr); REXCODEGEN_WARN("Analyze: no blocks found for function 0x{:08X}", funcAddr);
@ -190,7 +227,7 @@ void discoverFunction(CodegenContext& ctx, uint32_t funcAddr,
} }
void discoverAllFunctions(CodegenContext& ctx) { void discoverAllFunctions(CodegenContext& ctx) {
REXCODEGEN_TRACE("Analyze: starting iterative discovery..."); REXCODEGEN_INFO("Analyze: starting iterative discovery...");
auto& graph = ctx.graph; auto& graph = ctx.graph;
auto& binary = ctx.binary(); auto& binary = ctx.binary();
@ -218,7 +255,7 @@ void discoverAllFunctions(CodegenContext& ctx) {
} }
} }
REXCODEGEN_TRACE("Analyze: {} functions after call graph expansion", graph.functionCount()); REXCODEGEN_INFO("Analyze: {} functions after call graph expansion", graph.functionCount());
// VTable scanning // VTable scanning
{ {
@ -227,6 +264,7 @@ void discoverAllFunctions(CodegenContext& ctx) {
size_t newFunctions = 0; size_t newFunctions = 0;
size_t vtableLandings = 0;
for (const auto& vt : vtables) { for (const auto& vt : vtables) {
for (size_t i = 0; i < vt.slots.size(); i++) { for (size_t i = 0; i < vt.slots.size(); i++) {
uint32_t funcAddr = vt.slots[i]; uint32_t funcAddr = vt.slots[i];
@ -236,12 +274,35 @@ void discoverAllFunctions(CodegenContext& ctx) {
if (binary.isInImportExportRange(funcAddr)) if (binary.isInImportExportRange(funcAddr))
continue; continue;
// Multi-entry-point handling: if the vtable slot falls *inside* an
// already-discovered function's body (not at its entry), promote the
// slot to a chunk (mid-function landing) of that parent. This mirrors
// what skate3_observed_runtime_landings.toml does for runtime-observed
// mid-function entries, but driven by static vtable analysis.
//
// Without this, addresses like 0x82EBAE4C (referenced by ~37 vtables
// but lying inside sub_82EBAE34) never enter the function table. xenia
// hybrid then can't route calls there and falls back to JIT.
if (FunctionNode* parent = graph.getFunctionContaining(funcAddr);
parent != nullptr && parent->base() != funcAddr) {
graph.registerChunk(funcAddr, 4, parent->base());
graph.addFunction(funcAddr, 4, FunctionAuthority::VTABLE, true);
vtableLandings++;
newFunctions++;
continue;
}
graph.addFunction(funcAddr, 4, FunctionAuthority::VTABLE, true); graph.addFunction(funcAddr, 4, FunctionAuthority::VTABLE, true);
newFunctions++; newFunctions++;
} }
} }
if (vtableLandings > 0) {
REXCODEGEN_INFO("Analyze: VTable scan promoted {} slots to chunks of "
"their containing functions (multi-entry-point)",
vtableLandings);
}
REXCODEGEN_TRACE("Analyze: VTable scan found {} vtables, {} new functions", vtables.size(), REXCODEGEN_INFO("Analyze: VTable scan found {} vtables, {} new functions", vtables.size(),
newFunctions); newFunctions);
// Continue discovery for vtable functions // Continue discovery for vtable functions
@ -263,7 +324,7 @@ void discoverAllFunctions(CodegenContext& ctx) {
} }
} }
REXCODEGEN_TRACE("Analyze: {} total functions after vtable scan", graph.functionCount()); REXCODEGEN_INFO("Analyze: {} total functions after vtable scan", graph.functionCount());
} }
//============================================================================= //=============================================================================
@ -398,7 +459,7 @@ void functionPointerScan(CodegenContext& ctx) {
} }
} }
REXCODEGEN_TRACE("functionPointerScan: found {} new function pointer targets", foundCount); REXCODEGEN_INFO("functionPointerScan: found {} new function pointer targets", foundCount);
} }
} // anonymous namespace } // anonymous namespace

View file

@ -426,6 +426,8 @@ VoidResult registerEntryPoints(CodegenContext& ctx) {
auto& binary = ctx.binary(); auto& binary = ctx.binary();
auto& ehDiscoveredFuncs = state.ehDiscoveredFuncs; auto& ehDiscoveredFuncs = state.ehDiscoveredFuncs;
graph.setSymbolPrefix(config.symbolPrefix);
// Merge user hints into analysis state // Merge user hints into analysis state
for (const auto& [addr, size] : config.invalidInstructionHints) { for (const auto& [addr, size] : config.invalidInstructionHints) {
state.invalidInstructions[addr] = size; state.invalidInstructions[addr] = size;
@ -555,7 +557,7 @@ VoidResult registerEntryPoints(CodegenContext& ctx) {
configFuncs++; configFuncs++;
if (cfg.isChunk()) { if (cfg.isChunk()) {
graph.registerChunk(address, size); graph.registerChunk(address, size, cfg.parent);
configChunks++; configChunks++;
} }
} }

View file

@ -30,7 +30,7 @@ constexpr u32 extract_bits(u32 value, u32 start, u32 count) {
// Opcode information table // Opcode information table
//============================================================================= //=============================================================================
static const std::array<OpcodeInfo, 320> g_opcode_table = {{ static const std::array<OpcodeInfo, 330> g_opcode_table = {{
// Primary opcode 16: bcx (conditional branch) - all variants // Primary opcode 16: bcx (conditional branch) - all variants
{Opcode::bc, InstrFormat::kB, OpcodeGroup::kBranch, "bc", 16, 0, false}, {Opcode::bc, InstrFormat::kB, OpcodeGroup::kBranch, "bc", 16, 0, false},
{Opcode::bca, InstrFormat::kB, OpcodeGroup::kBranch, "bca", 16, 0, false}, {Opcode::bca, InstrFormat::kB, OpcodeGroup::kBranch, "bca", 16, 0, false},
@ -261,8 +261,8 @@ static const std::array<OpcodeInfo, 320> g_opcode_table = {{
//========================================================================= //=========================================================================
{Opcode::vaddfp, InstrFormat::kX, OpcodeGroup::kVector, "vaddfp", 4, 10, true}, {Opcode::vaddfp, InstrFormat::kX, OpcodeGroup::kVector, "vaddfp", 4, 10, true},
{Opcode::vsubfp, InstrFormat::kX, OpcodeGroup::kVector, "vsubfp", 4, 74, true}, {Opcode::vsubfp, InstrFormat::kX, OpcodeGroup::kVector, "vsubfp", 4, 74, true},
{Opcode::vmaddfp, InstrFormat::kX, OpcodeGroup::kVector, "vmaddfp", 4, 32, true}, {Opcode::vmaddfp, InstrFormat::kVA, OpcodeGroup::kVector, "vmaddfp", 4, 46, true},
{Opcode::vnmsubfp, InstrFormat::kX, OpcodeGroup::kVector, "vnmsubfp", 4, 33, true}, {Opcode::vnmsubfp, InstrFormat::kVA, OpcodeGroup::kVector, "vnmsubfp", 4, 47, true},
{Opcode::vmaxfp, InstrFormat::kX, OpcodeGroup::kVector, "vmaxfp", 4, 1034, true}, {Opcode::vmaxfp, InstrFormat::kX, OpcodeGroup::kVector, "vmaxfp", 4, 1034, true},
{Opcode::vminfp, InstrFormat::kX, OpcodeGroup::kVector, "vminfp", 4, 1098, true}, {Opcode::vminfp, InstrFormat::kX, OpcodeGroup::kVector, "vminfp", 4, 1098, true},
{Opcode::vrsqrtefp, InstrFormat::kX, OpcodeGroup::kVector, "vrsqrtefp", 4, 330, true}, {Opcode::vrsqrtefp, InstrFormat::kX, OpcodeGroup::kVector, "vrsqrtefp", 4, 330, true},
@ -279,6 +279,15 @@ static const std::array<OpcodeInfo, 320> g_opcode_table = {{
{Opcode::vsububm, InstrFormat::kX, OpcodeGroup::kVector, "vsububm", 4, 1024, true}, {Opcode::vsububm, InstrFormat::kX, OpcodeGroup::kVector, "vsububm", 4, 1024, true},
{Opcode::vsubuhm, InstrFormat::kX, OpcodeGroup::kVector, "vsubuhm", 4, 1088, true}, {Opcode::vsubuhm, InstrFormat::kX, OpcodeGroup::kVector, "vsubuhm", 4, 1088, true},
{Opcode::vsubuwm, InstrFormat::kX, OpcodeGroup::kVector, "vsubuwm", 4, 1152, true}, {Opcode::vsubuwm, InstrFormat::kX, OpcodeGroup::kVector, "vsubuwm", 4, 1152, true},
{Opcode::vmhaddshs, InstrFormat::kVA, OpcodeGroup::kVector, "vmhaddshs", 4, 32, true},
{Opcode::vmhraddshs, InstrFormat::kVA, OpcodeGroup::kVector, "vmhraddshs", 4, 33, true},
{Opcode::vmladduhm, InstrFormat::kVA, OpcodeGroup::kVector, "vmladduhm", 4, 34, true},
{Opcode::vmsumubm, InstrFormat::kVA, OpcodeGroup::kVector, "vmsumubm", 4, 36, true},
{Opcode::vmsummbm, InstrFormat::kVA, OpcodeGroup::kVector, "vmsummbm", 4, 37, true},
{Opcode::vmsumuhm, InstrFormat::kVA, OpcodeGroup::kVector, "vmsumuhm", 4, 38, true},
{Opcode::vmsumuhs, InstrFormat::kVA, OpcodeGroup::kVector, "vmsumuhs", 4, 39, true},
{Opcode::vmsumshm, InstrFormat::kVA, OpcodeGroup::kVector, "vmsumshm", 4, 40, true},
{Opcode::vmsumshs, InstrFormat::kVA, OpcodeGroup::kVector, "vmsumshs", 4, 41, true},
{Opcode::vmuloub, InstrFormat::kX, OpcodeGroup::kVector, "vmuloub", 4, 8, true}, {Opcode::vmuloub, InstrFormat::kX, OpcodeGroup::kVector, "vmuloub", 4, 8, true},
{Opcode::vmulouh, InstrFormat::kX, OpcodeGroup::kVector, "vmulouh", 4, 72, true}, {Opcode::vmulouh, InstrFormat::kX, OpcodeGroup::kVector, "vmulouh", 4, 72, true},
{Opcode::vmuleub, InstrFormat::kX, OpcodeGroup::kVector, "vmuleub", 4, 264, true}, {Opcode::vmuleub, InstrFormat::kX, OpcodeGroup::kVector, "vmuleub", 4, 264, true},
@ -295,7 +304,7 @@ static const std::array<OpcodeInfo, 320> g_opcode_table = {{
{Opcode::vor, InstrFormat::kX, OpcodeGroup::kVector, "vor", 4, 1156, true}, {Opcode::vor, InstrFormat::kX, OpcodeGroup::kVector, "vor", 4, 1156, true},
{Opcode::vxor, InstrFormat::kX, OpcodeGroup::kVector, "vxor", 4, 1220, true}, {Opcode::vxor, InstrFormat::kX, OpcodeGroup::kVector, "vxor", 4, 1220, true},
{Opcode::vnor, InstrFormat::kX, OpcodeGroup::kVector, "vnor", 4, 1284, true}, {Opcode::vnor, InstrFormat::kX, OpcodeGroup::kVector, "vnor", 4, 1284, true},
{Opcode::vsel, InstrFormat::kX, OpcodeGroup::kVector, "vsel", 4, 42, true}, {Opcode::vsel, InstrFormat::kVA, OpcodeGroup::kVector, "vsel", 4, 42, true},
//========================================================================= //=========================================================================
// VMX Compare (Floating-Point) // VMX Compare (Floating-Point)
@ -324,7 +333,7 @@ static const std::array<OpcodeInfo, 320> g_opcode_table = {{
//========================================================================= //=========================================================================
// VMX Permute/Merge // VMX Permute/Merge
//========================================================================= //=========================================================================
{Opcode::vperm, InstrFormat::kX, OpcodeGroup::kVector, "vperm", 4, 43, true}, {Opcode::vperm, InstrFormat::kVA, OpcodeGroup::kVector, "vperm", 4, 43, true},
{Opcode::vmrghb, InstrFormat::kX, OpcodeGroup::kVector, "vmrghb", 4, 12, true}, {Opcode::vmrghb, InstrFormat::kX, OpcodeGroup::kVector, "vmrghb", 4, 12, true},
{Opcode::vmrghh, InstrFormat::kX, OpcodeGroup::kVector, "vmrghh", 4, 76, true}, {Opcode::vmrghh, InstrFormat::kX, OpcodeGroup::kVector, "vmrghh", 4, 76, true},
{Opcode::vmrghw, InstrFormat::kX, OpcodeGroup::kVector, "vmrghw", 4, 140, true}, {Opcode::vmrghw, InstrFormat::kX, OpcodeGroup::kVector, "vmrghw", 4, 140, true},
@ -377,6 +386,7 @@ static const std::array<OpcodeInfo, 320> g_opcode_table = {{
{Opcode::vsr, InstrFormat::kX, OpcodeGroup::kVector, "vsr", 4, 708, true}, {Opcode::vsr, InstrFormat::kX, OpcodeGroup::kVector, "vsr", 4, 708, true},
{Opcode::vslo, InstrFormat::kX, OpcodeGroup::kVector, "vslo", 4, 1036, true}, {Opcode::vslo, InstrFormat::kX, OpcodeGroup::kVector, "vslo", 4, 1036, true},
{Opcode::vsro, InstrFormat::kX, OpcodeGroup::kVector, "vsro", 4, 1100, true}, {Opcode::vsro, InstrFormat::kX, OpcodeGroup::kVector, "vsro", 4, 1100, true},
{Opcode::vsldoi, InstrFormat::kVA, OpcodeGroup::kVector, "vsldoi", 4, 44, true},
//========================================================================= //=========================================================================
// VMX Conversion // VMX Conversion
@ -802,13 +812,33 @@ Opcode lookup_opcode(u32 code) {
extended = extract_bits(code, 26, 6); extended = extract_bits(code, 26, 6);
switch (extended) { switch (extended) {
case 32: case 32:
return Opcode::vmaddfp; // vmaddfp vD, vA, vC, vB return Opcode::vmhaddshs; // vmhaddshs vD, vA, vB, vC
case 33: case 33:
return Opcode::vnmsubfp; // vnmsubfp vD, vA, vC, vB return Opcode::vmhraddshs; // vmhraddshs vD, vA, vB, vC
case 34:
return Opcode::vmladduhm; // vmladduhm vD, vA, vB, vC
case 36:
return Opcode::vmsumubm; // vmsumubm vD, vA, vB, vC
case 37:
return Opcode::vmsummbm; // vmsummbm vD, vA, vB, vC
case 38:
return Opcode::vmsumuhm; // vmsumuhm vD, vA, vB, vC
case 39:
return Opcode::vmsumuhs; // vmsumuhs vD, vA, vB, vC
case 40:
return Opcode::vmsumshm; // vmsumshm vD, vA, vB, vC
case 41:
return Opcode::vmsumshs; // vmsumshs vD, vA, vB, vC
case 42:
return Opcode::vsel; // vsel vD, vA, vB, vC
case 43: case 43:
return Opcode::vperm; // vperm vD, vA, vB, vC return Opcode::vperm; // vperm vD, vA, vB, vC
case 44: case 44:
return Opcode::vsel; // vsel vD, vA, vB, vC return Opcode::vsldoi; // vsldoi vD, vA, vB, SHB
case 46:
return Opcode::vmaddfp; // vmaddfp vD, vA, vC, vB
case 47:
return Opcode::vnmsubfp; // vnmsubfp vD, vA, vC, vB
} }
// Try VX-form (bits 21-31, 11-bit XO) // Try VX-form (bits 21-31, 11-bit XO)

View file

@ -31,6 +31,7 @@ enum class InstrFormat : uint8_t {
kM, // Rotate/mask (RS, RA, RB, MB, ME, Rc) kM, // Rotate/mask (RS, RA, RB, MB, ME, Rc)
kMD, // Rotate double-word (RS, RA, sh, mb, XO, Rc) kMD, // Rotate double-word (RS, RA, sh, mb, XO, Rc)
kA, // Floating-point arithmetic (FRT, FRA, FRB, FRC, XO, Rc) kA, // Floating-point arithmetic (FRT, FRA, FRB, FRC, XO, Rc)
kVA, // Vector four-operand (VRT, VRA, VRB, VRC, XO)
kVXR, // Vector with record bit (VRT, VRA, VRB, Rc, XO) kVXR, // Vector with record bit (VRT, VRA, VRB, Rc, XO)
}; };
@ -315,6 +316,15 @@ enum class Opcode : uint16_t {
vsububm, // Vector subtract unsigned byte modulo vsububm, // Vector subtract unsigned byte modulo
vsubuhm, // Vector subtract unsigned halfword modulo vsubuhm, // Vector subtract unsigned halfword modulo
vsubuwm, // Vector subtract unsigned word modulo vsubuwm, // Vector subtract unsigned word modulo
vmhaddshs, // Vector multiply-high and add signed halfword saturate
vmhraddshs, // Vector multiply-high round and add signed halfword saturate
vmladduhm, // Vector multiply-low and add unsigned halfword modulo
vmsumubm, // Vector multiply-sum unsigned byte modulo
vmsummbm, // Vector multiply-sum mixed-sign byte modulo
vmsumuhm, // Vector multiply-sum unsigned halfword modulo
vmsumuhs, // Vector multiply-sum unsigned halfword saturate
vmsumshm, // Vector multiply-sum signed halfword modulo
vmsumshs, // Vector multiply-sum signed halfword saturate
vmuloub, // Vector multiply odd unsigned byte vmuloub, // Vector multiply odd unsigned byte
vmulouh, // Vector multiply odd unsigned halfword vmulouh, // Vector multiply odd unsigned halfword
vmulouw, // Vector multiply odd unsigned word vmulouw, // Vector multiply odd unsigned word
@ -441,6 +451,7 @@ enum class Opcode : uint16_t {
vsr, // Vector shift right (128-bit) vsr, // Vector shift right (128-bit)
vslo, // Vector shift left by octet vslo, // Vector shift left by octet
vsro, // Vector shift right by octet vsro, // Vector shift right by octet
vsldoi, // Vector shift left double by octet immediate
// Vector conversion // Vector conversion
vcfux, // Vector convert from unsigned fixed-point word vcfux, // Vector convert from unsigned fixed-point word

View file

@ -930,6 +930,8 @@ bool XexModule::LoadContinue() {
REXLOG_ERROR("Failed to load XEX PE headers!"); REXLOG_ERROR("Failed to load XEX PE headers!");
return false; return false;
} }
REXLOG_INFO("XEX image loaded at {:08X}-{:08X} (size {:08X})", base_address_,
base_address_ + image_size(), image_size());
// Parse any "unsafe" headers into safer variants // Parse any "unsafe" headers into safer variants
xex2_opt_generic_u32* alternate_titleids; xex2_opt_generic_u32* alternate_titleids;
@ -1009,7 +1011,10 @@ bool XexModule::LoadContinue() {
} }
} }
// Setup memory protection. // Native recomp code performs direct host stores for guest writes. Some
// titles write to pages described as read-only in the XEX metadata, so keep
// loaded image pages writable instead of letting host page protection turn
// valid guest stores into access violations.
for (uint32_t i = 0, page = 0; i < sec_header->page_descriptor_count; i++) { for (uint32_t i = 0, page = 0; i < sec_header->page_descriptor_count; i++) {
// Byteswap the bitfield manually. // Byteswap the bitfield manually.
xex2_page_descriptor desc; xex2_page_descriptor desc;
@ -1017,15 +1022,7 @@ bool XexModule::LoadContinue() {
auto address = base_address_ + (page * page_size); auto address = base_address_ + (page * page_size);
auto size = desc.page_count * page_size; auto size = desc.page_count * page_size;
switch (desc.info) {
case XEX_SECTION_CODE:
case XEX_SECTION_READONLY_DATA:
heap->Protect(address, size, memory::kMemoryProtectRead);
break;
case XEX_SECTION_DATA:
heap->Protect(address, size, memory::kMemoryProtectRead | memory::kMemoryProtectWrite); heap->Protect(address, size, memory::kMemoryProtectRead | memory::kMemoryProtectWrite);
break;
}
page += desc.page_count; page += desc.page_count;
} }

View file

@ -0,0 +1,15 @@
test_vaddsws_saturates:
#_ REGISTER_IN v3 [7FFFFFFF, 7FFFFFFE, 80000000, 80000001]
#_ REGISTER_IN v4 [00000001, 00000001, FFFFFFFF, FFFFFFFF]
vaddsws v3, v3, v4
blr
#_ REGISTER_OUT v3 [7FFFFFFF, 7FFFFFFF, 80000000, 80000000]
#_ REGISTER_OUT v4 [00000001, 00000001, FFFFFFFF, FFFFFFFF]
test_vaddsws_mixed:
#_ REGISTER_IN v3 [40000000, C0000000, 12345678, 87654321]
#_ REGISTER_IN v4 [40000000, C0000000, EDCBA988, 789ABCDF]
vaddsws v3, v3, v4
blr
#_ REGISTER_OUT v3 [7FFFFFFF, 80000000, 00000000, 00000000]
#_ REGISTER_OUT v4 [40000000, C0000000, EDCBA988, 789ABCDF]

View file

@ -0,0 +1,6 @@
test_vcfpsxws128_signed_edges:
#_ REGISTER_IN v3 [3FC00000, BFC00000, 4F000000, CF000000]
vcfpsxws128 v4, v3, 0
blr
#_ REGISTER_OUT v3 [3FC00000, BFC00000, 4F000000, CF000000]
#_ REGISTER_OUT v4 [00000001, FFFFFFFF, 7FFFFFFF, 80000000]

View file

@ -0,0 +1,6 @@
test_vcfpuxws128_unsigned_edges:
#_ REGISTER_IN v3 [3FC00000, BFC00000, 4F000000, 4F800000]
vcfpuxws128 v4, v3, 0
blr
#_ REGISTER_OUT v3 [3FC00000, BFC00000, 4F000000, 4F800000]
#_ REGISTER_OUT v4 [00000001, 00000000, 80000000, FFFFFFFF]

View file

@ -0,0 +1,20 @@
test_vcfux_unsigned_edges:
#_ REGISTER_IN v3 [00000000, 00000001, 7FFFFFFF, 80000000]
vcfux v4, v3, 0
blr
#_ REGISTER_OUT v3 [00000000, 00000001, 7FFFFFFF, 80000000]
#_ REGISTER_OUT v4 [00000000, 3F800000, 4F000000, 4F000000]
test_vcfux_unsigned_max:
#_ REGISTER_IN v3 [FFFFFFFF, FFFFFFFE, 00FFFFFF, 01000000]
vcfux v4, v3, 0
blr
#_ REGISTER_OUT v3 [FFFFFFFF, FFFFFFFE, 00FFFFFF, 01000000]
#_ REGISTER_OUT v4 [4F800000, 4F800000, 4B7FFFFF, 4B800000]
test_vcfux_scale:
#_ REGISTER_IN v3 [80000000, 40000000, 00000400, 00000001]
vcfux v4, v3, 10
blr
#_ REGISTER_OUT v3 [80000000, 40000000, 00000400, 00000001]
#_ REGISTER_OUT v4 [4A000000, 49800000, 3F800000, 3A800000]

View file

@ -0,0 +1,13 @@
test_vcuxwfp128_unsigned_edges:
#_ REGISTER_IN v3 [00000000, 00000001, 7FFFFFFF, 80000000]
vcuxwfp128 v4, v3, 0
blr
#_ REGISTER_OUT v3 [00000000, 00000001, 7FFFFFFF, 80000000]
#_ REGISTER_OUT v4 [00000000, 3F800000, 4F000000, 4F000000]
test_vcuxwfp128_unsigned_max:
#_ REGISTER_IN v3 [FFFFFFFF, FFFFFFFE, 00FFFFFF, 01000000]
vcuxwfp128 v4, v3, 0
blr
#_ REGISTER_OUT v3 [FFFFFFFF, FFFFFFFE, 00FFFFFF, 01000000]
#_ REGISTER_OUT v4 [4F800000, 4F800000, 4B7FFFFF, 4B800000]

View file

@ -0,0 +1,19 @@
test_vmaddfp128_accumulates_vd:
#_ REGISTER_IN v3 [40000000, 40400000, 40800000, 40A00000]
#_ REGISTER_IN v4 [41200000, 41A00000, 41F00000, 42200000]
#_ REGISTER_IN v5 [3F800000, 3F800000, 3F800000, 3F800000]
vmaddfp128 v5, v3, v4, v5
blr
#_ REGISTER_OUT v3 [40000000, 40400000, 40800000, 40A00000]
#_ REGISTER_OUT v4 [41200000, 41A00000, 41F00000, 42200000]
#_ REGISTER_OUT v5 [41A80000, 42740000, 42F20000, 43490000]
test_vmaddcfp128_accumulates_vb:
#_ REGISTER_IN v3 [41200000, 41A00000, 41F00000, 42200000]
#_ REGISTER_IN v4 [3F800000, 3F800000, 3F800000, 3F800000]
#_ REGISTER_IN v5 [40000000, 40400000, 40800000, 40A00000]
vmaddcfp128 v5, v3, v5, v4
blr
#_ REGISTER_OUT v3 [41200000, 41A00000, 41F00000, 42200000]
#_ REGISTER_OUT v4 [3F800000, 3F800000, 3F800000, 3F800000]
#_ REGISTER_OUT v5 [41A80000, 42740000, 42F20000, 43490000]

View file

@ -8,3 +8,13 @@ test_vmaxfp128_1:
#_ REGISTER_OUT v0 [41200000, 41A00000, 41700000, 41F00000] #_ REGISTER_OUT v0 [41200000, 41A00000, 41700000, 41F00000]
#_ REGISTER_OUT v3 [41200000, C1200000, 41700000, C1700000] #_ REGISTER_OUT v3 [41200000, C1200000, 41700000, C1700000]
#_ REGISTER_OUT v4 [C1200000, 41A00000, C1A00000, 41F00000] #_ REGISTER_OUT v4 [C1200000, 41A00000, C1A00000, 41F00000]
test_vmaxfp128_nan:
# Match Xenia's single maxps lowering: if either operand is NaN, the second source is returned.
#_ REGISTER_IN v3 [7FC00000, 3F800000, 7FC00000, C0000000]
#_ REGISTER_IN v4 [3F800000, 7FC00000, 7FA00001, C0400000]
vmaxfp128 v0, v3, v4
blr
#_ REGISTER_OUT v0 [3F800000, 7FC00000, 7FA00001, C0000000]
#_ REGISTER_OUT v3 [7FC00000, 3F800000, 7FC00000, C0000000]
#_ REGISTER_OUT v4 [3F800000, 7FC00000, 7FA00001, C0400000]

View file

@ -8,3 +8,13 @@ test_vminfp128_1:
#_ REGISTER_OUT v0 [C1200000, C1200000, C1A00000, C1700000] #_ REGISTER_OUT v0 [C1200000, C1200000, C1A00000, C1700000]
#_ REGISTER_OUT v3 [41200000, C1200000, 41700000, C1700000] #_ REGISTER_OUT v3 [41200000, C1200000, 41700000, C1700000]
#_ REGISTER_OUT v4 [C1200000, 41A00000, C1A00000, 41F00000] #_ REGISTER_OUT v4 [C1200000, 41A00000, C1A00000, 41F00000]
test_vminfp128_nan:
# Match Xenia's single minps lowering: if either operand is NaN, the second source is returned.
#_ REGISTER_IN v3 [7FC00000, 3F800000, 7FC00000, C0000000]
#_ REGISTER_IN v4 [3F800000, 7FC00000, 7FA00001, C0400000]
vminfp128 v0, v3, v4
blr
#_ REGISTER_OUT v0 [3F800000, 7FC00000, 7FA00001, C0400000]
#_ REGISTER_OUT v3 [7FC00000, 3F800000, 7FC00000, C0000000]
#_ REGISTER_OUT v4 [3F800000, 7FC00000, 7FA00001, C0400000]

View file

@ -8,3 +8,12 @@ test_vmsum3fp128_1:
#_ REGISTER_OUT v3 [3f800000, 3fc00000, 3f8ccccd, 01020304] #_ REGISTER_OUT v3 [3f800000, 3fc00000, 3f8ccccd, 01020304]
#_ REGISTER_OUT v4 [40000000, 40700000, 4013d70a, 01020304] #_ REGISTER_OUT v4 [40000000, 40700000, 4013d70a, 01020304]
#_ REGISTER_OUT v5 [4122A7F0, 4122A7F0, 4122A7F0, 4122A7F0] #_ REGISTER_OUT v5 [4122A7F0, 4122A7F0, 4122A7F0, 4122A7F0]
test_vmsum3fp128_overflow:
#_ REGISTER_IN v3 [7f7fffff, 7f7fffff, 00000000, 00000000]
#_ REGISTER_IN v4 [7f7fffff, 7f7fffff, 00000000, 00000000]
vmsum3fp128 v5, v3, v4
blr
#_ REGISTER_OUT v3 [7f7fffff, 7f7fffff, 00000000, 00000000]
#_ REGISTER_OUT v4 [7f7fffff, 7f7fffff, 00000000, 00000000]
#_ REGISTER_OUT v5 [7FC00000, 7FC00000, 7FC00000, 7FC00000]

View file

@ -6,3 +6,12 @@ test_vmsum4fp128_1:
#_ REGISTER_OUT v3 [3f800000, 3fc00000, 3f8ccccd, 3ff33333] #_ REGISTER_OUT v3 [3f800000, 3fc00000, 3f8ccccd, 3ff33333]
#_ REGISTER_OUT v4 [40000000, 40700000, 4013d70a, 40b051eb] #_ REGISTER_OUT v4 [40000000, 40700000, 4013d70a, 40b051eb]
#_ REGISTER_OUT v5 [41A5147B, 41A5147B, 41A5147B, 41A5147B] #_ REGISTER_OUT v5 [41A5147B, 41A5147B, 41A5147B, 41A5147B]
test_vmsum4fp128_overflow:
#_ REGISTER_IN v3 [7f7fffff, 7f7fffff, 00000000, 00000000]
#_ REGISTER_IN v4 [7f7fffff, 7f7fffff, 00000000, 00000000]
vmsum4fp128 v5, v3, v4
blr
#_ REGISTER_OUT v3 [7f7fffff, 7f7fffff, 00000000, 00000000]
#_ REGISTER_OUT v4 [7f7fffff, 7f7fffff, 00000000, 00000000]
#_ REGISTER_OUT v5 [7FC00000, 7FC00000, 7FC00000, 7FC00000]

View file

@ -0,0 +1,9 @@
test_vnmsubfp128_accumulates_vd:
#_ REGISTER_IN v3 [40000000, 40400000, 40800000, 40A00000]
#_ REGISTER_IN v4 [41200000, 41A00000, 41F00000, 42200000]
#_ REGISTER_IN v5 [42C80000, 42C80000, 42C80000, 42C80000]
vnmsubfp128 v5, v3, v4, v5
blr
#_ REGISTER_OUT v3 [40000000, 40400000, 40800000, 40A00000]
#_ REGISTER_OUT v4 [41200000, 41A00000, 41F00000, 42200000]
#_ REGISTER_OUT v5 [42A00000, 42200000, C1A00000, C2C80000]

View file

@ -0,0 +1,10 @@
test_vperm128_ignores_control_high_bits:
#_ REGISTER_IN v0 [00010203, 10111213, 20212F3F, 0F1F0414]
#_ REGISTER_IN v3 [00010203, 04050607, 08090A0B, 0C0D0E0F]
#_ REGISTER_IN v4 [10111213, 14151617, 18191A1B, 1C1D1E1F]
vperm128 v6, v3, v4, v0
blr
#_ REGISTER_OUT v0 [00010203, 10111213, 20212F3F, 0F1F0414]
#_ REGISTER_OUT v3 [00010203, 04050607, 08090A0B, 0C0D0E0F]
#_ REGISTER_OUT v4 [10111213, 14151617, 18191A1B, 1C1D1E1F]
#_ REGISTER_OUT v6 [00010203, 10111213, 00010F1F, 0F1F0414]

View file

@ -0,0 +1,6 @@
test_vrsqrtefp128_exact_xenia_lowering:
#_ REGISTER_IN v3 [3F800000, 40800000, 3E800000, 41800000]
vrsqrtefp128 v4, v3
blr
#_ REGISTER_OUT v3 [3F800000, 40800000, 3E800000, 41800000]
#_ REGISTER_OUT v4 [3F800000, 3F000000, 40000000, 3E800000]

View file

@ -0,0 +1,8 @@
test_vslw128_variable:
#_ REGISTER_IN v3 [00000001, 00000001, 00000001, 00000001]
#_ REGISTER_IN v4 [00000000, 00000001, 0000001F, 00000020]
vslw128 v5, v3, v4
blr
#_ REGISTER_OUT v3 [00000001, 00000001, 00000001, 00000001]
#_ REGISTER_OUT v4 [00000000, 00000001, 0000001F, 00000020]
#_ REGISTER_OUT v5 [00000001, 00000002, 80000000, 00000001]

View file

@ -0,0 +1,8 @@
test_vsraw128_variable:
#_ REGISTER_IN v3 [80000000, 80000000, 7FFFFFFF, FFFFFFFF]
#_ REGISTER_IN v4 [00000000, 00000001, 0000001F, 00000020]
vsraw128 v5, v3, v4
blr
#_ REGISTER_OUT v3 [80000000, 80000000, 7FFFFFFF, FFFFFFFF]
#_ REGISTER_OUT v4 [00000000, 00000001, 0000001F, 00000020]
#_ REGISTER_OUT v5 [80000000, C0000000, 00000000, FFFFFFFF]

View file

@ -0,0 +1,8 @@
test_vsrw128_variable:
#_ REGISTER_IN v3 [80000000, 80000000, FFFFFFFF, FFFFFFFF]
#_ REGISTER_IN v4 [00000000, 00000001, 0000001F, 00000020]
vsrw128 v5, v3, v4
blr
#_ REGISTER_OUT v3 [80000000, 80000000, FFFFFFFF, FFFFFFFF]
#_ REGISTER_OUT v4 [00000000, 00000001, 0000001F, 00000020]
#_ REGISTER_OUT v5 [80000000, 40000000, 00000001, FFFFFFFF]

View file

@ -0,0 +1,15 @@
test_vsubsws_saturates:
#_ REGISTER_IN v3 [80000000, 80000001, 7FFFFFFF, 7FFFFFFE]
#_ REGISTER_IN v4 [00000001, 00000001, FFFFFFFF, FFFFFFFF]
vsubsws v3, v3, v4
blr
#_ REGISTER_OUT v3 [80000000, 80000000, 7FFFFFFF, 7FFFFFFF]
#_ REGISTER_OUT v4 [00000001, 00000001, FFFFFFFF, FFFFFFFF]
test_vsubsws_mixed:
#_ REGISTER_IN v3 [40000000, C0000000, 12345678, 87654321]
#_ REGISTER_IN v4 [C0000000, 40000000, 12345678, 87654321]
vsubsws v3, v3, v4
blr
#_ REGISTER_OUT v3 [7FFFFFFF, 80000000, 00000000, 00000000]
#_ REGISTER_OUT v4 [C0000000, 40000000, 12345678, 87654321]