From 7325992255beabf1ce3e9263f40a525b705fdde4 Mon Sep 17 00:00:00 2001 From: mchughalex <25671999+mchughalex@users.noreply.github.com> Date: Tue, 19 May 2026 23:58:46 +0100 Subject: [PATCH] improve Skate 3 codegen and instruction support --- include/rex/codegen/config.h | 1 + include/rex/codegen/function_graph.h | 11 +- include/rex/codegen/function_node.h | 4 +- include/rex/ppc/context.h | 99 +++- include/rex/ppc/intrinsics.h | 95 ++++ resources/templates/codegen/init_cpp.inja | 6 +- resources/templates/codegen/init_h.inja | 4 +- .../templates/codegen/sources_cmake.inja | 1 - src/codegen/builders.h | 9 + src/codegen/builders/context.cpp | 42 +- src/codegen/builders/control_flow.cpp | 35 +- src/codegen/builders/helpers.h | 39 +- src/codegen/builders/system.cpp | 11 +- src/codegen/builders/vector.cpp | 439 +++++++++++------- src/codegen/codegen_writer.cpp | 4 +- src/codegen/config.cpp | 6 +- src/codegen/function_graph.cpp | 155 ++++++- src/codegen/function_scanner.cpp | 10 +- src/codegen/instruction_dispatch.cpp | 9 + src/codegen/phase_discover.cpp | 77 ++- src/codegen/phase_register.cpp | 4 +- src/codegen/ppc/opcode.cpp | 46 +- src/codegen/ppc/opcode.h | 11 + src/system/xex_module.cpp | 17 +- tests/ppc/asm/instr_vaddsws.s | 15 + tests/ppc/asm/instr_vcfpsxws128.s | 6 + tests/ppc/asm/instr_vcfpuxws128.s | 6 + tests/ppc/asm/instr_vcfux.s | 20 + tests/ppc/asm/instr_vcuxwfp128.s | 13 + tests/ppc/asm/instr_vmaddfp128.s | 19 + tests/ppc/asm/instr_vmaxfp128.s | 10 + tests/ppc/asm/instr_vminfp128.s | 10 + tests/ppc/asm/instr_vmsum3fp128.s | 9 + tests/ppc/asm/instr_vmsum4fp128.s | 9 + tests/ppc/asm/instr_vnmsubfp128.s | 9 + tests/ppc/asm/instr_vperm128.s | 10 + tests/ppc/asm/instr_vrsqrtefp128.s | 6 + tests/ppc/asm/instr_vslw128.s | 8 + tests/ppc/asm/instr_vsraw128.s | 8 + tests/ppc/asm/instr_vsrw128.s | 8 + tests/ppc/asm/instr_vsubsws.s | 15 + 41 files changed, 1038 insertions(+), 278 deletions(-) create mode 100644 tests/ppc/asm/instr_vaddsws.s create mode 100644 tests/ppc/asm/instr_vcfpsxws128.s create mode 100644 tests/ppc/asm/instr_vcfpuxws128.s create mode 100644 tests/ppc/asm/instr_vcfux.s create mode 100644 tests/ppc/asm/instr_vcuxwfp128.s create mode 100644 tests/ppc/asm/instr_vmaddfp128.s create mode 100644 tests/ppc/asm/instr_vnmsubfp128.s create mode 100644 tests/ppc/asm/instr_vperm128.s create mode 100644 tests/ppc/asm/instr_vrsqrtefp128.s create mode 100644 tests/ppc/asm/instr_vslw128.s create mode 100644 tests/ppc/asm/instr_vsraw128.s create mode 100644 tests/ppc/asm/instr_vsrw128.s create mode 100644 tests/ppc/asm/instr_vsubsws.s diff --git a/include/rex/codegen/config.h b/include/rex/codegen/config.h index e8d3b47..10a58cd 100644 --- a/include/rex/codegen/config.h +++ b/include/rex/codegen/config.h @@ -74,6 +74,7 @@ struct FunctionEntry { struct RecompilerConfig { // === Required user-provided fields === 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 outDirectoryPath; ///< Output directory for generated code std::string templateDir; ///< Optional custom template directory for overrides diff --git a/include/rex/codegen/function_graph.h b/include/rex/codegen/function_graph.h index 171eb67..33202f6 100644 --- a/include/rex/codegen/function_graph.h +++ b/include/rex/codegen/function_graph.h @@ -100,6 +100,10 @@ class FunctionGraph { size_t pendingCount() 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) //========================================================================= @@ -165,7 +169,10 @@ class FunctionGraph { void setMemoryReader(MemoryReader reader) { memoryReader_ = std::move(reader); } // 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 // 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 std::unordered_map functionHasXrefs_; // entry -> hasXrefs std::vector> chunks_; // base, size pairs + std::unordered_map chunkParents_; // chunk base -> parent base + std::string symbolPrefix_; MemoryReader memoryReader_; // Notify all PENDING functions that a new function was added diff --git a/include/rex/codegen/function_node.h b/include/rex/codegen/function_node.h index a335b37..af5dd89 100644 --- a/include/rex/codegen/function_node.h +++ b/include/rex/codegen/function_node.h @@ -94,8 +94,8 @@ class FunctionNode { /// Emit C++ code for this function /// Requires: state() == kSealed - /// For imports: emits REX_IMPORT macro - /// For normal functions: emits REX_FUNC with blocks and instructions + /// For imports: emits PPC_IMPORT macro + /// For normal functions: emits PPC_FUNC with blocks and instructions std::string emitCpp(const EmitContext& ctx) const; //========================================================================= diff --git a/include/rex/ppc/context.h b/include/rex/ppc/context.h index 321fab3..ce766b6 100644 --- a/include/rex/ppc/context.h +++ b/include/rex/ppc/context.h @@ -13,6 +13,7 @@ #pragma once +#include #include #include #include @@ -183,6 +184,9 @@ constexpr uint32_t kRoundMask = 0x03; struct FPSCRRegister { uint32_t csr; + uint32_t fpu_csr; + uint32_t vmx_csr; + bool vmx_mode; 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 RoundMaskVal = Platform::RoundMaskVal; static constexpr size_t FlushMask = Platform::FlushMask; + static constexpr size_t ExceptionMask = Platform::ExceptionMask; inline uint32_t getcsr() noexcept { return Platform::getcsr(); } inline void setcsr(uint32_t csr) noexcept { Platform::setcsr(csr); } inline uint32_t loadFromHost() noexcept { - csr = getcsr(); - return HostToGuest[(csr & RoundMaskVal) >> RoundShift]; + if (!vmx_mode) { + 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 { - csr &= ~RoundMaskVal; - csr |= Platform::GuestToHost[value & kRoundMask]; + fpu_csr &= ~RoundMaskVal; + fpu_csr |= Platform::GuestToHost[value & kRoundMask]; + fpu_csr &= ~FlushMask; + fpu_csr |= ExceptionMask; + csr = fpu_csr; + vmx_mode = false; setcsr(csr); } inline void enableFlushModeUnconditional() noexcept { - csr |= FlushMask; + csr = vmx_csr; + vmx_mode = true; setcsr(csr); } inline void disableFlushModeUnconditional() noexcept { - csr &= ~FlushMask; + csr = fpu_csr; + vmx_mode = false; setcsr(csr); } inline void enableFlushMode() noexcept { - if ((csr & FlushMask) != FlushMask) [[unlikely]] { - csr |= FlushMask; + if (!vmx_mode || getcsr() != vmx_csr) [[unlikely]] { + csr = vmx_csr; + vmx_mode = true; setcsr(csr); } } inline void disableFlushMode() noexcept { - if ((csr & FlushMask) != 0) [[unlikely]] { - csr &= ~FlushMask; + if (vmx_mode || getcsr() != fpu_csr) [[unlikely]] { + csr = fpu_csr; + vmx_mode = false; setcsr(csr); } } inline void InitHost() noexcept { - csr = getcsr(); - Platform::InitHostExceptions(csr); + fpu_csr = getcsr(); + Platform::InitHostExceptions(fpu_csr); + fpu_csr &= ~FlushMask; + vmx_csr = ExceptionMask | FlushMask | Platform::GuestToHost[kRoundNearest]; + csr = fpu_csr; + vmx_mode = false; setcsr(csr); } }; @@ -301,6 +326,12 @@ struct alignas(0x40) PPCContext { #endif #if !defined(REX_CONFIG_RESERVED_AS_LOCAL) 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 #if !defined(REX_CONFIG_SKIP_MSR) uint32_t msr = 0x200A000; @@ -531,3 +562,47 @@ struct alignas(0x40) PPCContext { inline void RestoreNonVolatiles(const uint8_t*) {} #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 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 diff --git a/include/rex/ppc/intrinsics.h b/include/rex/ppc/intrinsics.h index 93d87b9..5a9828a 100644 --- a/include/rex/ppc/intrinsics.h +++ b/include/rex/ppc/intrinsics.h @@ -13,6 +13,7 @@ #pragma once +#include #include #include #include @@ -30,6 +31,100 @@ 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(ppc_vrsqrtefp_bits(std::bit_cast(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(qnan_bits); + uint32_t bits = std::bit_cast(value); + if (((bits >> 23) & 0xFF) == 0 && (bits & 0x007FFFFFu) != 0) { + bits &= 0x80000000u; // preserve sign, clear exponent + mantissa + } + return std::bit_cast(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 //============================================================================= diff --git a/resources/templates/codegen/init_cpp.inja b/resources/templates/codegen/init_cpp.inja index c379117..8a2927c 100644 --- a/resources/templates/codegen/init_cpp.inja +++ b/resources/templates/codegen/init_cpp.inja @@ -7,19 +7,19 @@ void RegisterRecompiledModules(rex::system::KernelState*); {% endif %} -const rex::PPCImageInfo PPCImageConfig = { +const rex::PPCImageInfo {{ project }}_PPCImageConfig = { .code_base = REX_CODE_BASE, .code_size = REX_CODE_SIZE, .image_base = REX_IMAGE_BASE, .image_size = REX_IMAGE_SIZE, - .func_mappings = PPCFuncMappings, + .func_mappings = {{ project }}_PPCFuncMappings, .rexcrt_heap = REXCRT_HEAP, {% if has_dll_modules and not is_dll %} .register_modules = RegisterRecompiledModules, {% endif %} }; -PPCFuncMapping PPCFuncMappings[] = { +PPCFuncMapping {{ project }}_PPCFuncMappings[] = { {% for fn in functions %}{% if not fn.below_code_base or fn.is_import %} { {{ fn.address }}, {{ fn.name }} }, {% endif %}{% endfor %} { 0, nullptr } }; diff --git a/resources/templates/codegen/init_h.inja b/resources/templates/codegen/init_h.inja index 8449a0b..c6c5ae6 100644 --- a/resources/templates/codegen/init_h.inja +++ b/resources/templates/codegen/init_h.inja @@ -49,8 +49,8 @@ // Image Info //============================================================================= -extern const rex::PPCImageInfo PPCImageConfig; -extern PPCFuncMapping PPCFuncMappings[]; +extern const rex::PPCImageInfo {{ project }}_PPCImageConfig; +extern PPCFuncMapping {{ project }}_PPCFuncMappings[]; //============================================================================= // Function Definition Macros diff --git a/resources/templates/codegen/sources_cmake.inja b/resources/templates/codegen/sources_cmake.inja index cfadd56..a99b9ea 100644 --- a/resources/templates/codegen/sources_cmake.inja +++ b/resources/templates/codegen/sources_cmake.inja @@ -6,7 +6,6 @@ # set(GENERATED_SOURCES {{ 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 {% endif %}{% for file in recomp_files %} {{ cmake_var("CMAKE_CURRENT_LIST_DIR") }}/{{ file }} {% endfor %}) diff --git a/src/codegen/builders.h b/src/codegen/builders.h index 24894f2..4afe51f 100644 --- a/src/codegen/builders.h +++ b/src/codegen/builders.h @@ -468,6 +468,15 @@ bool build_vadduhm(BuilderContext& ctx); bool build_vadduwm(BuilderContext& ctx); bool build_vadduws(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_vsubshs(BuilderContext& ctx); bool build_vsubsws(BuilderContext& ctx); diff --git a/src/codegen/builders/context.cpp b/src/codegen/builders/context.cpp index 6eae29d..a106d47 100644 --- a/src/codegen/builders/context.cpp +++ b/src/codegen/builders/context.cpp @@ -195,7 +195,7 @@ void BuilderContext::emit_function_call(uint32_t address) { // Handle save/restore helpers 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 return; } @@ -242,6 +242,11 @@ void BuilderContext::emit_function_call(uint32_t address) { } // 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)", address, base); 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 // 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) { case TargetKind::InternalLabel: @@ -283,18 +288,33 @@ void BuilderContext::emit_conditional_branch(bool not_, std::string_view cond) { println("\t}}"); } } else { - REXCODEGEN_ERROR("Unresolved conditional branch to 0x{:08X} from 0x{:08X} (no CallTarget)", - target, base); - println("\tif ({}{}.{}) REX_FATAL(\"Unresolved branch from 0x{:08X} to 0x{:08X}\");", - not_ ? "!" : "", cr(insn.operands[0]), cond, base, target); + 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); + println("\tif ({}{}.{}) REX_FATAL(\"Unresolved branch from 0x{:08X} to 0x{:08X}\");", + not_ ? "!" : "", cr(insn.operands[0]), cond, base, target); + } } break; case TargetKind::Unknown: - 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("\tif ({}{}.{}) REX_FATAL(\"Unresolved branch from 0x{:08X} to 0x{:08X}\");", - not_ ? "!" : "", cr(insn.operands[0]), cond, base, target); + 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); + println("\t// ERROR: conditional branch to unknown address 0x{:08X}", target); + println("\tif ({}{}.{}) REX_FATAL(\"Unresolved branch from 0x{:08X} to 0x{:08X}\");", + not_ ? "!" : "", cr(insn.operands[0]), cond, base, target); + } break; } } @@ -303,7 +323,7 @@ void BuilderContext::emit_set_flush_mode(bool enable) { auto newState = enable ? CSRState::VMX : CSRState::FPU; if (csrState != newState) { auto prefix = enable ? "enable" : "disable"; - auto suffix = csrState != CSRState::Unknown ? "Unconditional" : ""; + auto suffix = csrState == CSRState::Unknown ? "Unconditional" : ""; println("\tctx.fpscr.{}FlushMode{}();", prefix, suffix); csrState = newState; diff --git a/src/codegen/builders/control_flow.cpp b/src/codegen/builders/control_flow.cpp index 102a538..a5b359a 100644 --- a/src/codegen/builders/control_flow.cpp +++ b/src/codegen/builders/control_flow.cpp @@ -20,6 +20,28 @@ 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 //============================================================================= @@ -27,9 +49,16 @@ namespace rex::codegen { bool build_b(BuilderContext& ctx) { 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 // 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) { case TargetKind::InternalLabel: @@ -67,7 +96,7 @@ bool build_bl(BuilderContext& ctx) { // Use graph to classify the target // 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) { case TargetKind::InternalLabel: @@ -138,7 +167,7 @@ bool build_bctr(BuilderContext& ctx) { continue; } - auto kind = ctx.graph().classifyTarget(label, ctx.base, false); + auto kind = ctx.graph().classifyTarget(label, ctx.fn.base(), false); switch (kind) { case TargetKind::InternalLabel: ctx.println("\t\tgoto loc_{:X};", label); diff --git a/src/codegen/builders/helpers.h b/src/codegen/builders/helpers.h index dbc8d51..9fcf68c 100644 --- a/src/codegen/builders/helpers.h +++ b/src/codegen/builders/helpers.h @@ -267,7 +267,7 @@ inline const char* getStoreMacro(BuilderContext& ctx, const char* normal_macro, /** * 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 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) ctx.print("{}.u32 + ", ctx.r(ctx.insn.operands[1])); ctx.println("{}.u32;", ctx.r(ctx.insn.operands[2])); - ctx.println("\t{}.{} = *({}*)REX_RAW_ADDR({});", ctx.reserved(), reserved_field, ptr_type, - ctx.ea()); + ctx.println("\trex::ppc::AcquireReservation(ctx, {}, sizeof({}));", ctx.ea(), ptr_type); + 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(), reserved_field); } @@ -289,7 +290,7 @@ inline void emitAtomicLoadReserve(BuilderContext& ctx, const char* ptr_type, con /** * 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 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{}.gt = 0;", ctx.cr(0)); 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]), - field); + ctx.cr(0), ctx.ea(), ptr_type, ctx.ea(), ctx.reserved(), field, bswap_func, + ctx.r(ctx.insn.operands[0]), field); + ctx.println("\trex::ppc::ReleaseReservation(ctx);"); 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, std::string_view condition, std::string_view instr_name) { - if (target < ctx.fn.base() || target >= ctx.fn.end()) { - REXCODEGEN_WARN("{} at {:X} branches outside function to {:X}", instr_name, ctx.base, target); - ctx.println("\tif ({}) {{ /* branch to 0x{:X} outside function */ return; }}", condition, - target); - } else { + auto kind = ctx.graph().classifyTarget(target, ctx.fn.base(), false); + if (kind == TargetKind::InternalLabel) { 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); } //============================================================================= diff --git a/src/codegen/builders/system.cpp b/src/codegen/builders/system.cpp index b2d70f5..632b667 100644 --- a/src/codegen/builders/system.cpp +++ b/src/codegen/builders/system.cpp @@ -31,20 +31,19 @@ bool build_attn(BuilderContext& ctx) { } bool build_sync(BuilderContext& ctx) { - // Memory barrier, x86 has strong ordering so this is a no-op - (void)ctx; + // Guest threads use sync/lwsync around lock-free handoffs. Even on x86, + // preserve the compiler and host-thread ordering point in generated C++. + ctx.println("\tstd::atomic_thread_fence(std::memory_order_seq_cst);"); return true; } bool build_lwsync(BuilderContext& ctx) { - // Lightweight memory barrier, x86 has strong ordering so this is a no-op - (void)ctx; + ctx.println("\tstd::atomic_thread_fence(std::memory_order_seq_cst);"); return true; } bool build_eieio(BuilderContext& ctx) { - // Enforce in-order execution of I/O, x86 has strong ordering so this is a no-op - (void)ctx; + ctx.println("\tstd::atomic_thread_fence(std::memory_order_seq_cst);"); return true; } diff --git a/src/codegen/builders/vector.cpp b/src/codegen/builders/vector.cpp index d74e053..339f5e1 100644 --- a/src/codegen/builders/vector.cpp +++ b/src/codegen/builders/vector.cpp @@ -30,16 +30,6 @@ namespace rex::codegen { // This file uses several magic constants from Intel SSE/SSE4 intrinsics. // 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 === // 0x80000000: IEEE 754 sign bit mask for 32-bit float // Used for negation via XOR and sign extraction @@ -93,80 +83,69 @@ bool build_vmulfp128(BuilderContext& ctx) { bool build_vmaddfp(BuilderContext& ctx) { ctx.emit_set_flush_mode(true); ctx.println( - "\tsimde_mm_store_ps({}.f32, simde_mm_add_ps(simde_mm_mul_ps(simde_mm_load_ps({}.f32), " - "simde_mm_load_ps({}.f32)), simde_mm_load_ps({}.f32)));", + "\tsimde_mm_store_ps({}.f32, simde_mm_add_ps(" + "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[3])); return true; } 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.println( - "\tsimde_mm_store_ps({}.f32, " - "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_castsi128_ps(simde_mm_set1_epi32(int(0x80000000)))));", - ctx.v(ctx.insn.operands[0]), ctx.v(ctx.insn.operands[1]), ctx.v(ctx.insn.operands[2]), - ctx.v(ctx.insn.operands[3])); + "\tsimde_mm_store_ps({}.f32, simde_mm_sub_ps(" + "simde_mm_load_ps({}.f32), " + "simde_mm_mul_ps(simde_mm_load_ps({}.f32), simde_mm_load_ps({}.f32))));", + ctx.v(ctx.insn.operands[0]), ctx.v(ctx.insn.operands[3]), ctx.v(ctx.insn.operands[1]), + ctx.v(ctx.insn.operands[2])); return true; } bool build_vmaxfp(BuilderContext& ctx) { 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; } bool build_vminfp(BuilderContext& ctx) { 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; } 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_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; } 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_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; } bool build_vexptefp(BuilderContext& ctx) { - // SIMD exp2 estimate (~12-bit precision, matching PPC vexptefp spec) - // Algorithm: exp2(x) = 2^n * poly(f), where n = floor(x), f = x - n - auto vD = ctx.v(ctx.insn.operands[0]); - auto vA = ctx.v(ctx.insn.operands[1]); + // Xenia's x64 backend lowers the corresponding POW2 vector opcode through + // std::exp2 rather than a hardware estimate. Match that behavior so traces + // compare against the emulator reference instead of PPC-estimate accuracy. ctx.emit_set_flush_mode(true); - ctx.println("\t{{"); - ctx.println("\t\tsimde__m128 x = simde_mm_load_ps({}.f32);", vA); - ctx.println( - "\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}}"); + for (size_t i = 0; i < 4; i++) + ctx.println("\t{}.f32[{}] = exp2f({}.f32[{}]);", ctx.v(ctx.insn.operands[0]), i, + ctx.v(ctx.insn.operands[1]), i); return true; } @@ -184,22 +163,19 @@ bool build_vlogefp(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.println( - "\tsimde_mm_store_ps({}.f32, simde_mm_dp_ps(simde_mm_load_ps({}.f32), " - "simde_mm_load_ps({}.f32), 0xEF));", + "\tsimde_mm_store_ps({}.f32, rex::ppc::simde_mm_vmsum3fp128_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; } 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.println( - "\tsimde_mm_store_ps({}.f32, simde_mm_dp_ps(simde_mm_load_ps({}.f32), " - "simde_mm_load_ps({}.f32), 0xFF));", + "\tsimde_mm_store_ps({}.f32, rex::ppc::simde_mm_vmsum4fp128_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; } @@ -250,7 +226,25 @@ bool build_vaddsbs(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({}.s16[i]) + static_cast({}.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(sum);", vD); + ctx.println("\t\t\t}}"); + ctx.println("\t\t}}"); + ctx.println("\t}}"); return true; } @@ -261,25 +255,19 @@ bool build_vaddsws(BuilderContext& ctx) { auto vB = ctx.v(ctx.insn.operands[2]); ctx.println("\t{{"); - // No direct SSE intrinsic, so use overflow detection and blend - ctx.println("\t\tsimde__m128i a = simde_mm_load_si128((simde__m128i*){}.u8);", vA); - ctx.println("\t\tsimde__m128i b = simde_mm_load_si128((simde__m128i*){}.u8);", vB); - ctx.println("\t\tsimde__m128i sum = simde_mm_add_epi32(a, b);"); - // Overflow if: (a ^ sum) & (b ^ sum) has MSB set (signs of a,b match but sum differs) - ctx.println( - "\t\tsimde__m128i overflow = simde_mm_and_si128(simde_mm_xor_si128(a, sum), " - "simde_mm_xor_si128(b, sum));"); - // Saturation value: if a positive (MSB=0), use INT32_MAX; - // if negative, use INT32_MIN (a >> 31) gives all 1s if negative, all 0s - // if positive XOR with 0x7FFFFFFF: negative -> 0x80000000, positive -> 0x7FFFFFFF - ctx.println( - "\t\tsimde__m128i sat_val = simde_mm_xor_si128(simde_mm_srai_epi32(a, 31), " - "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\tfor (int i = 0; i < 4; ++i) {{"); + ctx.println("\t\t\tlong long sum = static_cast({}.s32[i]) + static_cast({}.s32[i]);", vA, + vB); + ctx.println("\t\t\tif (sum > 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 (sum < (-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(sum);", vD); + ctx.println("\t\t\t}}"); + ctx.println("\t\t}}"); ctx.println("\t}}"); return true; } @@ -318,13 +306,187 @@ bool build_vadduhs(BuilderContext& ctx) { 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({}.s16[i]) * static_cast({}.s16[i])) >> 15) + static_cast({}.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(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({}.s16[i]) * static_cast({}.s16[i])) + 0x4000) >> 15) + static_cast({}.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(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((static_cast({}.u16[{}]) * static_cast({}.u16[{}])) + static_cast({}.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({}.u8[{}]) * static_cast({}.u8[{}]) + static_cast({}.u8[{}]) * static_cast({}.u8[{}]) + static_cast({}.u8[{}]) * static_cast({}.u8[{}]) + static_cast({}.u8[{}]) * static_cast({}.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(static_cast({}.s32[{}]) + static_cast({}.s8[{}]) * static_cast({}.u8[{}]) + static_cast({}.s8[{}]) * static_cast({}.u8[{}]) + static_cast({}.s8[{}]) * static_cast({}.u8[{}]) + static_cast({}.s8[{}]) * static_cast({}.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({}.u16[{}]) * static_cast({}.u16[{}]) + static_cast({}.u16[{}]) * static_cast({}.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({}.u32[i]) + static_cast({}.u16[h]) * static_cast({}.u16[h]) + static_cast({}.u16[h + 1]) * static_cast({}.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(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(static_cast({}.s32[{}]) + static_cast({}.s16[{}]) * static_cast({}.s16[{}]) + static_cast({}.s16[{}]) * static_cast({}.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({}.s32[i]) + static_cast({}.s16[h]) * static_cast({}.s16[h]) + static_cast({}.s16[h + 1]) * static_cast({}.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(value);", vD); + ctx.println("\t\t\t}}"); + ctx.println("\t\t}}"); + ctx.println("\t}}"); + return true; +} + bool build_vsubsws(BuilderContext& ctx) { // TODO: vectorize for (size_t i = 0; i < 4; i++) { 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.println("\t{}.s32[{}] = {}.s64 > INT_MAX ? INT_MAX : {}.s64 < INT_MIN ? INT_MIN : {}.s64;", - ctx.v(ctx.insn.operands[0]), i, ctx.temp(), ctx.temp(), ctx.temp()); + ctx.println("\tif ({}.s64 > INT_MAX) {{", 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({}.s64);", ctx.v(ctx.insn.operands[0]), i, + ctx.temp()); + ctx.println("\t}}"); } 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), " "simde_mm_load_ps({}.f32)), simde_mm_castsi128_ps(simde_mm_set1_epi32(int(0x80000000)))));", 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( - "\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_castsi128_ps(simde_mm_set1_epi32(int(0x80000000))))), " "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 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]);", ctx.temp(), 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 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]);", ctx.temp(), 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 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]);", ctx.temp(), 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 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]);", ctx.temp(), 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 dstIdx = (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]), - 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); + ctx.println("\t{}.u16[{}] = rex::float_to_xenos_half({}.f32[{}]);", + ctx.v(ctx.insn.operands[0]), dstIdx, ctx.v(ctx.insn.operands[1]), srcIdx); } break; } @@ -1321,20 +1471,16 @@ bool build_vpkd3d128(BuilderContext& ctx) { case 5: // float16_4 { - // NOTE: These combinations come from game traces (heuristic handling), not the official spec. - // The spec only defines the encoding, not the exact semantics of y (mask) and z (shift) here. - // 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) + // The instruction tests and Skate 3's two-pass packing both rely on untouched lanes being + // preserved; clearing the opposite half corrupts previously packed float16_4 data. uint32_t mask = ctx.insn.operands[3]; 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 - // store). We also explicitly reject shift == 1 since the clear block below only handles 0 - // and 2. Furthermore, we explicitly warn on unexpected mask values (e.g., 0, 1) to avoid - // silent fallthroughs and silently generating miscompiled code. + // store). We also explicitly reject shift == 1 because only the lower and upper half + // placements are currently understood. Furthermore, we explicitly warn on unexpected mask + // values (e.g., 0, 1) to avoid silent fallthroughs and silently generating miscompiled code. if ((shift != 0 && shift != 2) || (mask != 2 && mask != 3)) { REXCODEGEN_WARN("Unexpected float16_4 pack instruction at {:X} (mask={}, shift={})", ctx.base, mask, shift); @@ -1343,39 +1489,15 @@ bool build_vpkd3d128(BuilderContext& ctx) { return true; } - // mask=2: before writing, clear the half that will NOT be written. - // Shift is guaranteed to be 0 or 2 at this point due to the guard above. - if (mask == 2) { - // 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. + // Preserve untouched lanes; paired float16_4 packs fill lower/upper halves separately. + // Guest element order is reversed in Rex's host storage, but each packed half keeps + // element 0 in the high halfword and element 1 in the low halfword. for (size_t i = 0; i < 4; i++) { size_t srcIdx = 3 - i; size_t dstIdx = (3 - i) + (2 * shift); - ctx.println("\t{}.u32 = ({}.u32[{}]&0x7FFFFFFF);", ctx.temp(), 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); + ctx.println("\t{}.u16[{}] = rex::float_to_xenos_half({}.f32[{}], false, true);", + ctx.v(ctx.insn.operands[0]), dstIdx, ctx.v(ctx.insn.operands[1]), srcIdx); } break; } @@ -1429,7 +1551,8 @@ bool build_vupkd3d128(BuilderContext& ctx) { for (size_t i = 0; i < 2; i++) { 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{}.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[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()); // 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[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()); // 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[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()); // 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); @@ -1472,16 +1597,8 @@ bool build_vupkd3d128(BuilderContext& ctx) { for (size_t i = 0; i < 2; i++) { size_t srcIdx = 1 - i; // Read from u16[1], u16[0] size_t dstIdx = 3 - i; // Write to u32[3], u32[2] - ctx.println("\t{}.u32 = {}.u16[{}];", ctx.temp(), vSrc, srcIdx); - // Extract sign, exponent, mantissa - 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[{}] = rex::xenos_half_to_float({}.u16[{}]);", vDst, dstIdx, vSrc, + srcIdx); } ctx.println("\t{}.f32[1] = 0.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) 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{}.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; } @@ -1506,21 +1624,12 @@ bool build_vupkd3d128(BuilderContext& ctx) { { auto vSrc = ctx.v(ctx.insn.operands[1]); auto vDst = ctx.v(ctx.insn.operands[0]); - // Unpack 4 float16s from Guest elements 2-3 (host u16[0-3]) to elements 0-3 - // Guest element order is reversed in host arrays + // Read the same halfword order produced by FLOAT16_4 packing. 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) - ctx.println("\t{}.u32 = {}.u16[{}];", ctx.temp(), vSrc, srcIdx); - // Extract sign, exponent, mantissa and convert to float32 - 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[{}] = rex::xenos_half_to_float({}.u16[{}]);", vDst, dstIdx, vSrc, + srcIdx); } break; } diff --git a/src/codegen/codegen_writer.cpp b/src/codegen/codegen_writer.cpp index 69e4793..5fa10ef 100644 --- a/src/codegen/codegen_writer.cpp +++ b/src/codegen/codegen_writer.cpp @@ -61,11 +61,11 @@ nlohmann::json buildTemplateData(const rex::codegen::CodegenContext& ctx, funcName = crtIt->second; isRexcrt = true; } else if (fn->base() == ctx.analysisState().entryPoint) { - funcName = "xstart"; + funcName = cfg.symbolPrefix + "xstart"; } else if (!fn->name().empty()) { funcName = fn->name(); } else { - funcName = fmt::format("sub_{:08X}", fn->base()); + funcName = fmt::format("{}sub_{:08X}", cfg.symbolPrefix, fn->base()); } functionsJson.push_back({ diff --git a/src/codegen/config.cpp b/src/codegen/config.cpp index 47ccd26..60f016b 100644 --- a/src/codegen/config.cpp +++ b/src/codegen/config.cpp @@ -88,6 +88,9 @@ void ApplyToml(const toml::table& toml, RecompilerConfig& cfg, const std::string if (auto v = toml["project_name"].value()) { MergeScalar(cfg.projectName, *v, "project_name"); } + if (auto v = toml["symbol_prefix"].value()) { + MergeScalar(cfg.symbolPrefix, *v, "symbol_prefix"); + } if (auto v = toml["file_path"].value()) { MergeScalar(cfg.filePath, *v, "file_path"); } @@ -486,9 +489,6 @@ bool FinalizeConfig(RecompilerConfig& cfg) { for (const auto& error : result.errors) { REXCODEGEN_ERROR("[config] {}", error); } - if (!result.valid) { - ok = false; - } return ok; } diff --git a/src/codegen/function_graph.cpp b/src/codegen/function_graph.cpp index 0c0d1be..9df3b03 100644 --- a/src/codegen/function_graph.cpp +++ b/src/codegen/function_graph.cpp @@ -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 //============================================================================= @@ -359,11 +371,11 @@ std::string FunctionNode::emitCpp(const EmitContext& ctx) const { std::string name; if (base() == ctx.entryPoint) { - name = "xstart"; + name = fmt::format("{}xstart", ctx.config.symbolPrefix); } else if (!name_.empty()) { name = name_; } 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()); @@ -471,11 +483,11 @@ std::string FunctionNode::emitCpp(const EmitContext& ctx) const { // --- Function name --- std::string name; if (base() == ctx.entryPoint) { - name = "xstart"; + name = fmt::format("{}xstart", ctx.config.symbolPrefix); } else if (!name_.empty()) { name = name_; } else { - name = fmt::format("sub_{:08X}", base()); + name = fmt::format("{}sub_{:08X}", ctx.config.symbolPrefix, base()); } // Function signature with weak/alias pattern @@ -564,8 +576,8 @@ std::string FunctionNode::emitCpp(const EmitContext& ctx) const { for (auto label : activeJt->targets) { labels.emplace(label); } - REXCODEGEN_TRACE("Late-detected jump table at 0x{:08X} with {} entries", blockBase, - activeJt->targets.size()); + REXCODEGEN_INFO("Late-detected jump table at 0x{:08X} with {} entries", blockBase, + 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) { const auto& scope = *it; 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 auto node = std::make_unique(base, size, authority); FunctionNode* nodePtr = node.get(); + nodePtr->setName(makeSymbolName(nodePtr->name(), authority)); functions_[base] = std::move(node); functionsByBase_[base] = nodePtr; @@ -770,7 +784,7 @@ FunctionNode* FunctionGraph::addFunction(uint32_t base, uint32_t size, FunctionA std::string_view name, bool hasXrefs) { auto* node = addFunction(base, size, authority, hasXrefs); if (node && !name.empty()) { - node->setName(std::string(name)); + node->setName(makeSymbolName(name, authority)); } return node; } @@ -880,10 +894,24 @@ size_t FunctionGraph::sealedCount() const { void FunctionGraph::setFunctionName(uint32_t entry, std::string name) { 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) { if (auto* node = getFunction(entry)) { node->setHasExceptionHandler(val); @@ -932,6 +960,18 @@ void FunctionGraph::addUnresolvedJumpToFunction(uint32_t entry, uint32_t site, u if (!node) 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 if (auto* targetFn = getFunction(target)) { // Target is a known function - resolve as call or tail call @@ -983,9 +1023,24 @@ size_t FunctionGraph::tryResolveFunction(uint32_t entry) { jumps.size()); for (const auto& jump : jumps) { - // Try internal label first - if (node->tryResolveAsInternalLabel(jump.target)) { - REXCODEGEN_TRACE(" 0x{:08X}->0x{:08X}: resolved as internal label", jump.site, jump.target); + // Configured chunks are internal labels only if their destination block is + // emitted in this function body. Other chunk entries are separate callable + // 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); resolved++; continue; @@ -1005,6 +1060,14 @@ size_t FunctionGraph::tryResolveFunction(uint32_t entry) { 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 if (isImport(jump.target)) { auto* importNode = getFunction(jump.target); @@ -1056,10 +1119,10 @@ size_t FunctionGraph::sealAllReady() { sealed++; } else { couldNotSeal++; - REXCODEGEN_DEBUG("FunctionGraph::sealAllReady: 0x{:08X} ({}) cannot seal ({} unresolved)", - base, node->name(), node->unresolvedJumps().size()); + REXCODEGEN_WARN("FunctionGraph::sealAllReady: 0x{:08X} ({}) cannot seal ({} unresolved)", + base, node->name(), node->unresolvedJumps().size()); 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); } - REXCODEGEN_TRACE("FunctionGraph::sealAll: all {} functions sealed", functions_.size()); + REXCODEGEN_INFO("FunctionGraph::sealAll: all {} functions sealed", functions_.size()); } //============================================================================= // 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); - 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 { @@ -1173,41 +1245,74 @@ bool FunctionGraph::isMergeableEntryPoint(uint32_t addr) const { TargetKind FunctionGraph::classifyTarget(uint32_t target, uint32_t callerAddr, bool isCallInstruction) const { - // Find the caller's function - const FunctionNode* callerFn = getFunctionContaining(callerAddr); + // Most builder callers pass the current function base as callerAddr. Prefer + // 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 if (isImport(target)) { return TargetKind::Import; } - // Case 2: Target is the caller's own entry point - if (callerFn && target == callerFn->base()) { + // Case 2: Target is a configured chunk whose block is emitted in this + // 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) // b to own base = loop back to start (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 // whose entry point happens to fall within the thunk's address range if (isEntryPoint(target)) { 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 if (callerFn && callerFn->containsAddress(target)) { return TargetKind::InternalLabel; } - // Case 5: Unknown target + // Case 6: Unknown target return TargetKind::Unknown; } void FunctionGraph::notifyFunctionAdded(FunctionNode* newFunction) { for (auto& [base, node] : functions_) { 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); } } diff --git a/src/codegen/function_scanner.cpp b/src/codegen/function_scanner.cpp index 05fb6bf..e72b476 100644 --- a/src/codegen/function_scanner.cpp +++ b/src/codegen/function_scanner.cpp @@ -1299,9 +1299,13 @@ BoundsInfo scanForBounds(DecodedBinary& decoded, uint32_t bctrAddr, const CodeRe if (!insn) break; - // Stop at unconditional terminators - scanning past basic block boundaries - // risks finding unrelated comparisons on the index register - if (isTerminator(*insn) && !isConditional(*insn)) + // EA commonly emits switch bounds as: + // cmplwi idx,max; ble table_path; b default_path; table_path: ... bctr + // 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; using namespace rex::codegen::ppc; diff --git a/src/codegen/instruction_dispatch.cpp b/src/codegen/instruction_dispatch.cpp index 40ae269..31c8bc2 100644 --- a/src/codegen/instruction_dispatch.cpp +++ b/src/codegen/instruction_dispatch.cpp @@ -462,6 +462,15 @@ static const std::unordered_map& GetDispatchTable() { {PPC_INST_VADDUWM, build_vadduwm}, {PPC_INST_VADDUWS, build_vadduws}, {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_VSUBSWS, build_vsubsws}, {PPC_INST_VSUBUBM, build_vsububm}, diff --git a/src/codegen/phase_discover.cpp b/src/codegen/phase_discover.cpp index cfae8b8..fedf6b8 100644 --- a/src/codegen/phase_discover.cpp +++ b/src/codegen/phase_discover.cpp @@ -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 const CodeRegion* region = nullptr; for (const auto& r : ctx.scan.codeRegions) { @@ -94,8 +110,29 @@ void discoverFunction(CodegenContext& ctx, uint32_t funcAddr, return; } - // Pass pdataSize so forward branches within function extent are correctly identified - auto result = discoverBlocks(decoded, funcAddr, *region, knownFunctions, pdataSize); + // Pass pdataSize so forward branches within function extent are correctly identified. + // 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()) { 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) { - REXCODEGEN_TRACE("Analyze: starting iterative discovery..."); + REXCODEGEN_INFO("Analyze: starting iterative discovery..."); auto& graph = ctx.graph; 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 { @@ -227,6 +264,7 @@ void discoverAllFunctions(CodegenContext& ctx) { size_t newFunctions = 0; + size_t vtableLandings = 0; for (const auto& vt : vtables) { for (size_t i = 0; i < vt.slots.size(); i++) { uint32_t funcAddr = vt.slots[i]; @@ -236,13 +274,36 @@ void discoverAllFunctions(CodegenContext& ctx) { if (binary.isInImportExportRange(funcAddr)) 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); 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(), - newFunctions); + REXCODEGEN_INFO("Analyze: VTable scan found {} vtables, {} new functions", vtables.size(), + newFunctions); // Continue discovery for vtable functions if (newFunctions > 0) { @@ -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 diff --git a/src/codegen/phase_register.cpp b/src/codegen/phase_register.cpp index 6487853..007cd02 100644 --- a/src/codegen/phase_register.cpp +++ b/src/codegen/phase_register.cpp @@ -426,6 +426,8 @@ VoidResult registerEntryPoints(CodegenContext& ctx) { auto& binary = ctx.binary(); auto& ehDiscoveredFuncs = state.ehDiscoveredFuncs; + graph.setSymbolPrefix(config.symbolPrefix); + // Merge user hints into analysis state for (const auto& [addr, size] : config.invalidInstructionHints) { state.invalidInstructions[addr] = size; @@ -555,7 +557,7 @@ VoidResult registerEntryPoints(CodegenContext& ctx) { configFuncs++; if (cfg.isChunk()) { - graph.registerChunk(address, size); + graph.registerChunk(address, size, cfg.parent); configChunks++; } } diff --git a/src/codegen/ppc/opcode.cpp b/src/codegen/ppc/opcode.cpp index c079469..cc7aea9 100644 --- a/src/codegen/ppc/opcode.cpp +++ b/src/codegen/ppc/opcode.cpp @@ -30,7 +30,7 @@ constexpr u32 extract_bits(u32 value, u32 start, u32 count) { // Opcode information table //============================================================================= -static const std::array g_opcode_table = {{ +static const std::array g_opcode_table = {{ // Primary opcode 16: bcx (conditional branch) - all variants {Opcode::bc, InstrFormat::kB, OpcodeGroup::kBranch, "bc", 16, 0, false}, {Opcode::bca, InstrFormat::kB, OpcodeGroup::kBranch, "bca", 16, 0, false}, @@ -261,8 +261,8 @@ static const std::array g_opcode_table = {{ //========================================================================= {Opcode::vaddfp, InstrFormat::kX, OpcodeGroup::kVector, "vaddfp", 4, 10, true}, {Opcode::vsubfp, InstrFormat::kX, OpcodeGroup::kVector, "vsubfp", 4, 74, true}, - {Opcode::vmaddfp, InstrFormat::kX, OpcodeGroup::kVector, "vmaddfp", 4, 32, true}, - {Opcode::vnmsubfp, InstrFormat::kX, OpcodeGroup::kVector, "vnmsubfp", 4, 33, true}, + {Opcode::vmaddfp, InstrFormat::kVA, OpcodeGroup::kVector, "vmaddfp", 4, 46, true}, + {Opcode::vnmsubfp, InstrFormat::kVA, OpcodeGroup::kVector, "vnmsubfp", 4, 47, true}, {Opcode::vmaxfp, InstrFormat::kX, OpcodeGroup::kVector, "vmaxfp", 4, 1034, true}, {Opcode::vminfp, InstrFormat::kX, OpcodeGroup::kVector, "vminfp", 4, 1098, true}, {Opcode::vrsqrtefp, InstrFormat::kX, OpcodeGroup::kVector, "vrsqrtefp", 4, 330, true}, @@ -279,6 +279,15 @@ static const std::array g_opcode_table = {{ {Opcode::vsububm, InstrFormat::kX, OpcodeGroup::kVector, "vsububm", 4, 1024, true}, {Opcode::vsubuhm, InstrFormat::kX, OpcodeGroup::kVector, "vsubuhm", 4, 1088, 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::vmulouh, InstrFormat::kX, OpcodeGroup::kVector, "vmulouh", 4, 72, true}, {Opcode::vmuleub, InstrFormat::kX, OpcodeGroup::kVector, "vmuleub", 4, 264, true}, @@ -295,7 +304,7 @@ static const std::array g_opcode_table = {{ {Opcode::vor, InstrFormat::kX, OpcodeGroup::kVector, "vor", 4, 1156, true}, {Opcode::vxor, InstrFormat::kX, OpcodeGroup::kVector, "vxor", 4, 1220, 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) @@ -324,7 +333,7 @@ static const std::array g_opcode_table = {{ //========================================================================= // 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::vmrghh, InstrFormat::kX, OpcodeGroup::kVector, "vmrghh", 4, 76, true}, {Opcode::vmrghw, InstrFormat::kX, OpcodeGroup::kVector, "vmrghw", 4, 140, true}, @@ -377,6 +386,7 @@ static const std::array g_opcode_table = {{ {Opcode::vsr, InstrFormat::kX, OpcodeGroup::kVector, "vsr", 4, 708, true}, {Opcode::vslo, InstrFormat::kX, OpcodeGroup::kVector, "vslo", 4, 1036, true}, {Opcode::vsro, InstrFormat::kX, OpcodeGroup::kVector, "vsro", 4, 1100, true}, + {Opcode::vsldoi, InstrFormat::kVA, OpcodeGroup::kVector, "vsldoi", 4, 44, true}, //========================================================================= // VMX Conversion @@ -802,13 +812,33 @@ Opcode lookup_opcode(u32 code) { extended = extract_bits(code, 26, 6); switch (extended) { case 32: - return Opcode::vmaddfp; // vmaddfp vD, vA, vC, vB + return Opcode::vmhaddshs; // vmhaddshs vD, vA, vB, vC 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: return Opcode::vperm; // vperm vD, vA, vB, vC 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) diff --git a/src/codegen/ppc/opcode.h b/src/codegen/ppc/opcode.h index 3bdba66..17c17d7 100644 --- a/src/codegen/ppc/opcode.h +++ b/src/codegen/ppc/opcode.h @@ -31,6 +31,7 @@ enum class InstrFormat : uint8_t { kM, // Rotate/mask (RS, RA, RB, MB, ME, Rc) kMD, // Rotate double-word (RS, RA, sh, mb, 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) }; @@ -315,6 +316,15 @@ enum class Opcode : uint16_t { vsububm, // Vector subtract unsigned byte modulo vsubuhm, // Vector subtract unsigned halfword 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 vmulouh, // Vector multiply odd unsigned halfword vmulouw, // Vector multiply odd unsigned word @@ -441,6 +451,7 @@ enum class Opcode : uint16_t { vsr, // Vector shift right (128-bit) vslo, // Vector shift left by octet vsro, // Vector shift right by octet + vsldoi, // Vector shift left double by octet immediate // Vector conversion vcfux, // Vector convert from unsigned fixed-point word diff --git a/src/system/xex_module.cpp b/src/system/xex_module.cpp index 04d44ae..725fc33 100644 --- a/src/system/xex_module.cpp +++ b/src/system/xex_module.cpp @@ -930,6 +930,8 @@ bool XexModule::LoadContinue() { REXLOG_ERROR("Failed to load XEX PE headers!"); 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 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++) { // Byteswap the bitfield manually. xex2_page_descriptor desc; @@ -1017,15 +1022,7 @@ bool XexModule::LoadContinue() { auto address = base_address_ + (page * 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); - break; - } + heap->Protect(address, size, memory::kMemoryProtectRead | memory::kMemoryProtectWrite); page += desc.page_count; } diff --git a/tests/ppc/asm/instr_vaddsws.s b/tests/ppc/asm/instr_vaddsws.s new file mode 100644 index 0000000..625a611 --- /dev/null +++ b/tests/ppc/asm/instr_vaddsws.s @@ -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] diff --git a/tests/ppc/asm/instr_vcfpsxws128.s b/tests/ppc/asm/instr_vcfpsxws128.s new file mode 100644 index 0000000..ddc2e98 --- /dev/null +++ b/tests/ppc/asm/instr_vcfpsxws128.s @@ -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] diff --git a/tests/ppc/asm/instr_vcfpuxws128.s b/tests/ppc/asm/instr_vcfpuxws128.s new file mode 100644 index 0000000..8bb2068 --- /dev/null +++ b/tests/ppc/asm/instr_vcfpuxws128.s @@ -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] diff --git a/tests/ppc/asm/instr_vcfux.s b/tests/ppc/asm/instr_vcfux.s new file mode 100644 index 0000000..0d381c7 --- /dev/null +++ b/tests/ppc/asm/instr_vcfux.s @@ -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] diff --git a/tests/ppc/asm/instr_vcuxwfp128.s b/tests/ppc/asm/instr_vcuxwfp128.s new file mode 100644 index 0000000..9f631f1 --- /dev/null +++ b/tests/ppc/asm/instr_vcuxwfp128.s @@ -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] diff --git a/tests/ppc/asm/instr_vmaddfp128.s b/tests/ppc/asm/instr_vmaddfp128.s new file mode 100644 index 0000000..c9f722f --- /dev/null +++ b/tests/ppc/asm/instr_vmaddfp128.s @@ -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] diff --git a/tests/ppc/asm/instr_vmaxfp128.s b/tests/ppc/asm/instr_vmaxfp128.s index fa86a87..4d87198 100644 --- a/tests/ppc/asm/instr_vmaxfp128.s +++ b/tests/ppc/asm/instr_vmaxfp128.s @@ -8,3 +8,13 @@ test_vmaxfp128_1: #_ REGISTER_OUT v0 [41200000, 41A00000, 41700000, 41F00000] #_ REGISTER_OUT v3 [41200000, C1200000, 41700000, C1700000] #_ 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] diff --git a/tests/ppc/asm/instr_vminfp128.s b/tests/ppc/asm/instr_vminfp128.s index 566dbff..4393b69 100644 --- a/tests/ppc/asm/instr_vminfp128.s +++ b/tests/ppc/asm/instr_vminfp128.s @@ -8,3 +8,13 @@ test_vminfp128_1: #_ REGISTER_OUT v0 [C1200000, C1200000, C1A00000, C1700000] #_ REGISTER_OUT v3 [41200000, C1200000, 41700000, C1700000] #_ 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] diff --git a/tests/ppc/asm/instr_vmsum3fp128.s b/tests/ppc/asm/instr_vmsum3fp128.s index f88cf8a..7b769fa 100644 --- a/tests/ppc/asm/instr_vmsum3fp128.s +++ b/tests/ppc/asm/instr_vmsum3fp128.s @@ -8,3 +8,12 @@ test_vmsum3fp128_1: #_ REGISTER_OUT v3 [3f800000, 3fc00000, 3f8ccccd, 01020304] #_ REGISTER_OUT v4 [40000000, 40700000, 4013d70a, 01020304] #_ 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] diff --git a/tests/ppc/asm/instr_vmsum4fp128.s b/tests/ppc/asm/instr_vmsum4fp128.s index 4243dd0..df05b14 100644 --- a/tests/ppc/asm/instr_vmsum4fp128.s +++ b/tests/ppc/asm/instr_vmsum4fp128.s @@ -6,3 +6,12 @@ test_vmsum4fp128_1: #_ REGISTER_OUT v3 [3f800000, 3fc00000, 3f8ccccd, 3ff33333] #_ REGISTER_OUT v4 [40000000, 40700000, 4013d70a, 40b051eb] #_ 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] diff --git a/tests/ppc/asm/instr_vnmsubfp128.s b/tests/ppc/asm/instr_vnmsubfp128.s new file mode 100644 index 0000000..9ccaab0 --- /dev/null +++ b/tests/ppc/asm/instr_vnmsubfp128.s @@ -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] diff --git a/tests/ppc/asm/instr_vperm128.s b/tests/ppc/asm/instr_vperm128.s new file mode 100644 index 0000000..5dfa0cb --- /dev/null +++ b/tests/ppc/asm/instr_vperm128.s @@ -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] diff --git a/tests/ppc/asm/instr_vrsqrtefp128.s b/tests/ppc/asm/instr_vrsqrtefp128.s new file mode 100644 index 0000000..724a677 --- /dev/null +++ b/tests/ppc/asm/instr_vrsqrtefp128.s @@ -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] diff --git a/tests/ppc/asm/instr_vslw128.s b/tests/ppc/asm/instr_vslw128.s new file mode 100644 index 0000000..8dd9f4c --- /dev/null +++ b/tests/ppc/asm/instr_vslw128.s @@ -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] diff --git a/tests/ppc/asm/instr_vsraw128.s b/tests/ppc/asm/instr_vsraw128.s new file mode 100644 index 0000000..7d7ad7c --- /dev/null +++ b/tests/ppc/asm/instr_vsraw128.s @@ -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] diff --git a/tests/ppc/asm/instr_vsrw128.s b/tests/ppc/asm/instr_vsrw128.s new file mode 100644 index 0000000..3626568 --- /dev/null +++ b/tests/ppc/asm/instr_vsrw128.s @@ -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] diff --git a/tests/ppc/asm/instr_vsubsws.s b/tests/ppc/asm/instr_vsubsws.s new file mode 100644 index 0000000..2e2931c --- /dev/null +++ b/tests/ppc/asm/instr_vsubsws.s @@ -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]