improve Skate 3 codegen and instruction support
This commit is contained in:
parent
3ec2b41a6f
commit
7325992255
41 changed files with 1038 additions and 278 deletions
|
|
@ -74,6 +74,7 @@ struct FunctionEntry {
|
||||||
struct RecompilerConfig {
|
struct RecompilerConfig {
|
||||||
// === Required user-provided fields ===
|
// === Required user-provided fields ===
|
||||||
std::string projectName = "rex"; ///< Project name for output files
|
std::string projectName = "rex"; ///< Project name for output files
|
||||||
|
std::string symbolPrefix; ///< Optional C++ symbol prefix for generated functions
|
||||||
std::string filePath; ///< Path to XEX/ELF file
|
std::string filePath; ///< Path to XEX/ELF file
|
||||||
std::string outDirectoryPath; ///< Output directory for generated code
|
std::string outDirectoryPath; ///< Output directory for generated code
|
||||||
std::string templateDir; ///< Optional custom template directory for overrides
|
std::string templateDir; ///< Optional custom template directory for overrides
|
||||||
|
|
|
||||||
|
|
@ -100,6 +100,10 @@ class FunctionGraph {
|
||||||
size_t pendingCount() const;
|
size_t pendingCount() const;
|
||||||
size_t sealedCount() const;
|
size_t sealedCount() const;
|
||||||
|
|
||||||
|
// Prefix generated C++ symbols for this graph. Imports are never prefixed.
|
||||||
|
void setSymbolPrefix(std::string prefix);
|
||||||
|
std::string makeSymbolName(std::string_view name, FunctionAuthority authority) const;
|
||||||
|
|
||||||
//=========================================================================
|
//=========================================================================
|
||||||
// Function Setup (called during Discover phase)
|
// Function Setup (called during Discover phase)
|
||||||
//=========================================================================
|
//=========================================================================
|
||||||
|
|
@ -165,7 +169,10 @@ class FunctionGraph {
|
||||||
void setMemoryReader(MemoryReader reader) { memoryReader_ = std::move(reader); }
|
void setMemoryReader(MemoryReader reader) { memoryReader_ = std::move(reader); }
|
||||||
|
|
||||||
// Register a chunk (address range claimed by config, blocks vacancy)
|
// Register a chunk (address range claimed by config, blocks vacancy)
|
||||||
void registerChunk(uint32_t base, uint32_t size);
|
void registerChunk(uint32_t base, uint32_t size, uint32_t parent = 0);
|
||||||
|
|
||||||
|
// Return the configured parent for a chunk entry, or 0 for non-chunks.
|
||||||
|
uint32_t chunkParent(uint32_t base) const;
|
||||||
|
|
||||||
// Check if a region is vacant for absorption
|
// Check if a region is vacant for absorption
|
||||||
// fromAddr: the address we're expanding from (to check for null boundary)
|
// fromAddr: the address we're expanding from (to check for null boundary)
|
||||||
|
|
@ -193,6 +200,8 @@ class FunctionGraph {
|
||||||
functionsByBase_; // sorted by base for O(log f) interval lookup
|
functionsByBase_; // sorted by base for O(log f) interval lookup
|
||||||
std::unordered_map<uint32_t, bool> functionHasXrefs_; // entry -> hasXrefs
|
std::unordered_map<uint32_t, bool> functionHasXrefs_; // entry -> hasXrefs
|
||||||
std::vector<std::pair<uint32_t, uint32_t>> chunks_; // base, size pairs
|
std::vector<std::pair<uint32_t, uint32_t>> chunks_; // base, size pairs
|
||||||
|
std::unordered_map<uint32_t, uint32_t> chunkParents_; // chunk base -> parent base
|
||||||
|
std::string symbolPrefix_;
|
||||||
MemoryReader memoryReader_;
|
MemoryReader memoryReader_;
|
||||||
|
|
||||||
// Notify all PENDING functions that a new function was added
|
// Notify all PENDING functions that a new function was added
|
||||||
|
|
|
||||||
|
|
@ -94,8 +94,8 @@ class FunctionNode {
|
||||||
|
|
||||||
/// Emit C++ code for this function
|
/// Emit C++ code for this function
|
||||||
/// Requires: state() == kSealed
|
/// Requires: state() == kSealed
|
||||||
/// For imports: emits REX_IMPORT macro
|
/// For imports: emits PPC_IMPORT macro
|
||||||
/// For normal functions: emits REX_FUNC with blocks and instructions
|
/// For normal functions: emits PPC_FUNC with blocks and instructions
|
||||||
std::string emitCpp(const EmitContext& ctx) const;
|
std::string emitCpp(const EmitContext& ctx) const;
|
||||||
|
|
||||||
//=========================================================================
|
//=========================================================================
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
#include <bit>
|
#include <bit>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
@ -183,6 +184,9 @@ constexpr uint32_t kRoundMask = 0x03;
|
||||||
|
|
||||||
struct FPSCRRegister {
|
struct FPSCRRegister {
|
||||||
uint32_t csr;
|
uint32_t csr;
|
||||||
|
uint32_t fpu_csr;
|
||||||
|
uint32_t vmx_csr;
|
||||||
|
bool vmx_mode;
|
||||||
|
|
||||||
static constexpr size_t HostToGuest[] = {kRoundNearest, kRoundDown, kRoundUp, kRoundTowardZero};
|
static constexpr size_t HostToGuest[] = {kRoundNearest, kRoundDown, kRoundUp, kRoundTowardZero};
|
||||||
|
|
||||||
|
|
@ -190,48 +194,69 @@ struct FPSCRRegister {
|
||||||
static constexpr size_t RoundShift = Platform::RoundShift;
|
static constexpr size_t RoundShift = Platform::RoundShift;
|
||||||
static constexpr size_t RoundMaskVal = Platform::RoundMaskVal;
|
static constexpr size_t RoundMaskVal = Platform::RoundMaskVal;
|
||||||
static constexpr size_t FlushMask = Platform::FlushMask;
|
static constexpr size_t FlushMask = Platform::FlushMask;
|
||||||
|
static constexpr size_t ExceptionMask = Platform::ExceptionMask;
|
||||||
|
|
||||||
inline uint32_t getcsr() noexcept { return Platform::getcsr(); }
|
inline uint32_t getcsr() noexcept { return Platform::getcsr(); }
|
||||||
inline void setcsr(uint32_t csr) noexcept { Platform::setcsr(csr); }
|
inline void setcsr(uint32_t csr) noexcept { Platform::setcsr(csr); }
|
||||||
|
|
||||||
inline uint32_t loadFromHost() noexcept {
|
inline uint32_t loadFromHost() noexcept {
|
||||||
csr = getcsr();
|
if (!vmx_mode) {
|
||||||
return HostToGuest[(csr & RoundMaskVal) >> RoundShift];
|
fpu_csr = getcsr();
|
||||||
|
Platform::InitHostExceptions(fpu_csr);
|
||||||
|
fpu_csr &= ~FlushMask;
|
||||||
|
if (vmx_csr == 0) {
|
||||||
|
vmx_csr = ExceptionMask | FlushMask | Platform::GuestToHost[kRoundNearest];
|
||||||
|
}
|
||||||
|
csr = fpu_csr;
|
||||||
|
}
|
||||||
|
return HostToGuest[(fpu_csr & RoundMaskVal) >> RoundShift];
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void storeFromGuest(uint32_t value) noexcept {
|
inline void storeFromGuest(uint32_t value) noexcept {
|
||||||
csr &= ~RoundMaskVal;
|
fpu_csr &= ~RoundMaskVal;
|
||||||
csr |= Platform::GuestToHost[value & kRoundMask];
|
fpu_csr |= Platform::GuestToHost[value & kRoundMask];
|
||||||
|
fpu_csr &= ~FlushMask;
|
||||||
|
fpu_csr |= ExceptionMask;
|
||||||
|
csr = fpu_csr;
|
||||||
|
vmx_mode = false;
|
||||||
setcsr(csr);
|
setcsr(csr);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void enableFlushModeUnconditional() noexcept {
|
inline void enableFlushModeUnconditional() noexcept {
|
||||||
csr |= FlushMask;
|
csr = vmx_csr;
|
||||||
|
vmx_mode = true;
|
||||||
setcsr(csr);
|
setcsr(csr);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void disableFlushModeUnconditional() noexcept {
|
inline void disableFlushModeUnconditional() noexcept {
|
||||||
csr &= ~FlushMask;
|
csr = fpu_csr;
|
||||||
|
vmx_mode = false;
|
||||||
setcsr(csr);
|
setcsr(csr);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void enableFlushMode() noexcept {
|
inline void enableFlushMode() noexcept {
|
||||||
if ((csr & FlushMask) != FlushMask) [[unlikely]] {
|
if (!vmx_mode || getcsr() != vmx_csr) [[unlikely]] {
|
||||||
csr |= FlushMask;
|
csr = vmx_csr;
|
||||||
|
vmx_mode = true;
|
||||||
setcsr(csr);
|
setcsr(csr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void disableFlushMode() noexcept {
|
inline void disableFlushMode() noexcept {
|
||||||
if ((csr & FlushMask) != 0) [[unlikely]] {
|
if (vmx_mode || getcsr() != fpu_csr) [[unlikely]] {
|
||||||
csr &= ~FlushMask;
|
csr = fpu_csr;
|
||||||
|
vmx_mode = false;
|
||||||
setcsr(csr);
|
setcsr(csr);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void InitHost() noexcept {
|
inline void InitHost() noexcept {
|
||||||
csr = getcsr();
|
fpu_csr = getcsr();
|
||||||
Platform::InitHostExceptions(csr);
|
Platform::InitHostExceptions(fpu_csr);
|
||||||
|
fpu_csr &= ~FlushMask;
|
||||||
|
vmx_csr = ExceptionMask | FlushMask | Platform::GuestToHost[kRoundNearest];
|
||||||
|
csr = fpu_csr;
|
||||||
|
vmx_mode = false;
|
||||||
setcsr(csr);
|
setcsr(csr);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
@ -301,6 +326,12 @@ struct alignas(0x40) PPCContext {
|
||||||
#endif
|
#endif
|
||||||
#if !defined(REX_CONFIG_RESERVED_AS_LOCAL)
|
#if !defined(REX_CONFIG_RESERVED_AS_LOCAL)
|
||||||
PPCRegister reserved;
|
PPCRegister reserved;
|
||||||
|
uint32_t reserved_address;
|
||||||
|
uint32_t reserved_size;
|
||||||
|
uint32_t reserved_bitmap_word;
|
||||||
|
uint64_t reserved_bitmap_mask;
|
||||||
|
bool reserved_global_acquired;
|
||||||
|
bool reserved_valid;
|
||||||
#endif
|
#endif
|
||||||
#if !defined(REX_CONFIG_SKIP_MSR)
|
#if !defined(REX_CONFIG_SKIP_MSR)
|
||||||
uint32_t msr = 0x200A000;
|
uint32_t msr = 0x200A000;
|
||||||
|
|
@ -531,3 +562,47 @@ struct alignas(0x40) PPCContext {
|
||||||
inline void RestoreNonVolatiles(const uint8_t*) {}
|
inline void RestoreNonVolatiles(const uint8_t*) {}
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
|
namespace rex::ppc {
|
||||||
|
|
||||||
|
constexpr uint32_t kReserveBlockShift = 16;
|
||||||
|
constexpr uint32_t kReserveBlockCount = 1u << (32 - kReserveBlockShift);
|
||||||
|
constexpr uint32_t kReserveBitmapWordCount = kReserveBlockCount / 64;
|
||||||
|
|
||||||
|
inline std::atomic<uint64_t> g_reservation_bitmap[kReserveBitmapWordCount] = {};
|
||||||
|
|
||||||
|
inline void ReleaseReservation(PPCContext& ctx) {
|
||||||
|
ctx.reserved_global_acquired = false;
|
||||||
|
ctx.reserved_valid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool AcquireReservation(PPCContext& ctx, uint32_t address, uint32_t size) {
|
||||||
|
ReleaseReservation(ctx);
|
||||||
|
ctx.reserved_address = address;
|
||||||
|
ctx.reserved_size = size;
|
||||||
|
ctx.reserved_bitmap_word = 0;
|
||||||
|
ctx.reserved_bitmap_mask = 0;
|
||||||
|
ctx.reserved_global_acquired = false;
|
||||||
|
ctx.reserved_valid = true;
|
||||||
|
return ctx.reserved_valid;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool ReservationMatches(const PPCContext& ctx, uint32_t address) {
|
||||||
|
if (!ctx.reserved_valid) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return ctx.reserved_address == address;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void ImportReservation(PPCContext& ctx, uint32_t address, uint64_t value) {
|
||||||
|
ReleaseReservation(ctx);
|
||||||
|
ctx.reserved.u64 = value;
|
||||||
|
ctx.reserved_address = address;
|
||||||
|
ctx.reserved_size = 0;
|
||||||
|
ctx.reserved_bitmap_word = 0;
|
||||||
|
ctx.reserved_bitmap_mask = 0;
|
||||||
|
ctx.reserved_global_acquired = false;
|
||||||
|
ctx.reserved_valid = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace rex::ppc
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <bit>
|
||||||
#include <climits>
|
#include <climits>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
@ -30,6 +31,100 @@
|
||||||
|
|
||||||
namespace rex::ppc {
|
namespace rex::ppc {
|
||||||
|
|
||||||
|
inline uint32_t ppc_vrsqrtefp_bits(uint32_t bits) {
|
||||||
|
static constexpr uint32_t table[32] = {
|
||||||
|
0x0568B4FD, 0x04F3AF97, 0x048DAAA5, 0x0435A618, 0x03E7A1E4, 0x03A29DFE,
|
||||||
|
0x03659A5C, 0x032E96F8, 0x02FC93CA, 0x02D090CE, 0x02A88DFE, 0x02838B57,
|
||||||
|
0x026188D4, 0x02438673, 0x02268431, 0x020B820B, 0x03D27FFA, 0x03807C29,
|
||||||
|
0x033878AA, 0x02F97572, 0x02C27279, 0x02926FB7, 0x02666D26, 0x023F6AC0,
|
||||||
|
0x021D6881, 0x01FD6665, 0x01E16468, 0x01C76287, 0x01AF60C1, 0x01995F12,
|
||||||
|
0x01855D79, 0x01735BF4,
|
||||||
|
};
|
||||||
|
|
||||||
|
uint32_t sign = bits >> 31;
|
||||||
|
uint32_t biased_exp = (bits >> 23) & 0xFF;
|
||||||
|
uint32_t mantissa = bits & 0x007FFFFF;
|
||||||
|
|
||||||
|
if (bits == 0xFF800000u)
|
||||||
|
return 0x7FC00000u;
|
||||||
|
|
||||||
|
if (biased_exp == 0)
|
||||||
|
return sign ? 0xFF800000u : 0x7F800000u;
|
||||||
|
|
||||||
|
if (biased_exp == 0xFF) {
|
||||||
|
if (mantissa == 0)
|
||||||
|
return 0;
|
||||||
|
return bits | 0x00400000u;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sign)
|
||||||
|
return 0x7FC00000u;
|
||||||
|
|
||||||
|
int32_t unbiased_exp = int32_t(biased_exp) - 127;
|
||||||
|
uint32_t index = ((((uint32_t(unbiased_exp) << 4) & 16) | (mantissa >> 19)) ^ 16);
|
||||||
|
uint32_t interp = (mantissa >> 9) & 1023;
|
||||||
|
int32_t result_exp = (127 - int32_t(biased_exp)) >> 1;
|
||||||
|
uint32_t entry = table[index];
|
||||||
|
uint32_t slope = entry >> 16;
|
||||||
|
uint32_t base = (entry << 10) & 0x3FFFC00u;
|
||||||
|
int32_t raw = int32_t(base) - int32_t(interp * slope);
|
||||||
|
|
||||||
|
if (!(raw & (1 << 25))) {
|
||||||
|
uint32_t val = uint32_t(raw) & 0x1FFFFFFu;
|
||||||
|
uint32_t lz = std::countl_zero(val);
|
||||||
|
int32_t shift = int32_t(lz) - 6;
|
||||||
|
result_exp += 6 - int32_t(lz);
|
||||||
|
raw <<= shift;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((raw & 5) && (raw & 2))
|
||||||
|
raw += 4;
|
||||||
|
|
||||||
|
uint32_t result = uint32_t((result_exp << 23) + 0x3F800000) | ((uint32_t(raw) >> 2) & 0x7FFFFFu);
|
||||||
|
if (((result >> 23) & 0xFF) == 0 && (result & 0x7FFFFF))
|
||||||
|
result = 0;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline float ppc_vrsqrtefp(float value) {
|
||||||
|
return std::bit_cast<float>(ppc_vrsqrtefp_bits(std::bit_cast<uint32_t>(value)));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline simde__m128 simde_mm_vrsqrtefp_ps(simde__m128 value) {
|
||||||
|
alignas(16) float lanes[4];
|
||||||
|
simde_mm_store_ps(lanes, value);
|
||||||
|
for (float& lane : lanes)
|
||||||
|
lane = ppc_vrsqrtefp(lane);
|
||||||
|
return simde_mm_load_ps(lanes);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Match Xenia's vmsum3fp128/vmsum4fp128 lowering: a single-precision SSE dot
|
||||||
|
// product, followed by the emulator's overflow-to-QNaN behavior and guest FTZ.
|
||||||
|
inline float ppc_vmsumfp_result(float value) {
|
||||||
|
constexpr uint32_t qnan_bits = 0x7FC00000u;
|
||||||
|
if (!std::isfinite(value))
|
||||||
|
return std::bit_cast<float>(qnan_bits);
|
||||||
|
uint32_t bits = std::bit_cast<uint32_t>(value);
|
||||||
|
if (((bits >> 23) & 0xFF) == 0 && (bits & 0x007FFFFFu) != 0) {
|
||||||
|
bits &= 0x80000000u; // preserve sign, clear exponent + mantissa
|
||||||
|
}
|
||||||
|
return std::bit_cast<float>(bits);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline simde__m128 ppc_vmsumfp_result(simde__m128 value) {
|
||||||
|
alignas(16) float lanes[4];
|
||||||
|
simde_mm_store_ps(lanes, value);
|
||||||
|
return simde_mm_set1_ps(ppc_vmsumfp_result(lanes[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline simde__m128 simde_mm_vmsum3fp128_ps(simde__m128 a, simde__m128 b) {
|
||||||
|
return ppc_vmsumfp_result(simde_mm_dp_ps(a, b, 0xEF));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline simde__m128 simde_mm_vmsum4fp128_ps(simde__m128 a, simde__m128 b) {
|
||||||
|
return ppc_vmsumfp_result(simde_mm_dp_ps(a, b, 0xFF));
|
||||||
|
}
|
||||||
|
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
// Vector Load/Store Mask Tables
|
// Vector Load/Store Mask Tables
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
|
|
|
||||||
|
|
@ -7,19 +7,19 @@
|
||||||
void RegisterRecompiledModules(rex::system::KernelState*);
|
void RegisterRecompiledModules(rex::system::KernelState*);
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
const rex::PPCImageInfo PPCImageConfig = {
|
const rex::PPCImageInfo {{ project }}_PPCImageConfig = {
|
||||||
.code_base = REX_CODE_BASE,
|
.code_base = REX_CODE_BASE,
|
||||||
.code_size = REX_CODE_SIZE,
|
.code_size = REX_CODE_SIZE,
|
||||||
.image_base = REX_IMAGE_BASE,
|
.image_base = REX_IMAGE_BASE,
|
||||||
.image_size = REX_IMAGE_SIZE,
|
.image_size = REX_IMAGE_SIZE,
|
||||||
.func_mappings = PPCFuncMappings,
|
.func_mappings = {{ project }}_PPCFuncMappings,
|
||||||
.rexcrt_heap = REXCRT_HEAP,
|
.rexcrt_heap = REXCRT_HEAP,
|
||||||
{% if has_dll_modules and not is_dll %}
|
{% if has_dll_modules and not is_dll %}
|
||||||
.register_modules = RegisterRecompiledModules,
|
.register_modules = RegisterRecompiledModules,
|
||||||
{% endif %}
|
{% endif %}
|
||||||
};
|
};
|
||||||
|
|
||||||
PPCFuncMapping PPCFuncMappings[] = {
|
PPCFuncMapping {{ project }}_PPCFuncMappings[] = {
|
||||||
{% for fn in functions %}{% if not fn.below_code_base or fn.is_import %} { {{ fn.address }}, {{ fn.name }} },
|
{% for fn in functions %}{% if not fn.below_code_base or fn.is_import %} { {{ fn.address }}, {{ fn.name }} },
|
||||||
{% endif %}{% endfor %} { 0, nullptr }
|
{% endif %}{% endfor %} { 0, nullptr }
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -49,8 +49,8 @@
|
||||||
// Image Info
|
// Image Info
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
|
|
||||||
extern const rex::PPCImageInfo PPCImageConfig;
|
extern const rex::PPCImageInfo {{ project }}_PPCImageConfig;
|
||||||
extern PPCFuncMapping PPCFuncMappings[];
|
extern PPCFuncMapping {{ project }}_PPCFuncMappings[];
|
||||||
|
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
// Function Definition Macros
|
// Function Definition Macros
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@
|
||||||
#
|
#
|
||||||
set(GENERATED_SOURCES
|
set(GENERATED_SOURCES
|
||||||
{{ cmake_var("CMAKE_CURRENT_LIST_DIR") }}/{{ project }}_init.cpp
|
{{ cmake_var("CMAKE_CURRENT_LIST_DIR") }}/{{ project }}_init.cpp
|
||||||
{{ cmake_var("CMAKE_CURRENT_LIST_DIR") }}/{{ project }}_register.cpp
|
|
||||||
{% if has_dll_modules and not is_dll %} {{ cmake_var("CMAKE_CURRENT_LIST_DIR") }}/module_registry.cpp
|
{% if has_dll_modules and not is_dll %} {{ cmake_var("CMAKE_CURRENT_LIST_DIR") }}/module_registry.cpp
|
||||||
{% endif %}{% for file in recomp_files %} {{ cmake_var("CMAKE_CURRENT_LIST_DIR") }}/{{ file }}
|
{% endif %}{% for file in recomp_files %} {{ cmake_var("CMAKE_CURRENT_LIST_DIR") }}/{{ file }}
|
||||||
{% endfor %})
|
{% endfor %})
|
||||||
|
|
|
||||||
|
|
@ -468,6 +468,15 @@ bool build_vadduhm(BuilderContext& ctx);
|
||||||
bool build_vadduwm(BuilderContext& ctx);
|
bool build_vadduwm(BuilderContext& ctx);
|
||||||
bool build_vadduws(BuilderContext& ctx);
|
bool build_vadduws(BuilderContext& ctx);
|
||||||
bool build_vadduhs(BuilderContext& ctx);
|
bool build_vadduhs(BuilderContext& ctx);
|
||||||
|
bool build_vmhaddshs(BuilderContext& ctx);
|
||||||
|
bool build_vmhraddshs(BuilderContext& ctx);
|
||||||
|
bool build_vmladduhm(BuilderContext& ctx);
|
||||||
|
bool build_vmsumubm(BuilderContext& ctx);
|
||||||
|
bool build_vmsummbm(BuilderContext& ctx);
|
||||||
|
bool build_vmsumuhm(BuilderContext& ctx);
|
||||||
|
bool build_vmsumuhs(BuilderContext& ctx);
|
||||||
|
bool build_vmsumshm(BuilderContext& ctx);
|
||||||
|
bool build_vmsumshs(BuilderContext& ctx);
|
||||||
bool build_vsubsbs(BuilderContext& ctx);
|
bool build_vsubsbs(BuilderContext& ctx);
|
||||||
bool build_vsubshs(BuilderContext& ctx);
|
bool build_vsubshs(BuilderContext& ctx);
|
||||||
bool build_vsubsws(BuilderContext& ctx);
|
bool build_vsubsws(BuilderContext& ctx);
|
||||||
|
|
|
||||||
|
|
@ -195,7 +195,7 @@ void BuilderContext::emit_function_call(uint32_t address) {
|
||||||
|
|
||||||
// Handle save/restore helpers
|
// Handle save/restore helpers
|
||||||
if (cfg.nonVolatileRegistersAsLocalVariables &&
|
if (cfg.nonVolatileRegistersAsLocalVariables &&
|
||||||
(name.find("__rest") == 0 || name.find("__save") == 0)) {
|
(name.find("__rest") != std::string::npos || name.find("__save") != std::string::npos)) {
|
||||||
// print nothing - these are handled by local variable tracking
|
// print nothing - these are handled by local variable tracking
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -242,6 +242,11 @@ void BuilderContext::emit_function_call(uint32_t address) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// No pre-resolved target found - this is an error
|
// No pre-resolved target found - this is an error
|
||||||
|
if (auto* targetFn = graph().getFunction(address)) {
|
||||||
|
println("\t{}(ctx, base);", targetFn->name());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
REXCODEGEN_ERROR("Unresolved function 0x{:08X} from 0x{:08X} (no CallTarget in FunctionNode)",
|
REXCODEGEN_ERROR("Unresolved function 0x{:08X} from 0x{:08X} (no CallTarget in FunctionNode)",
|
||||||
address, base);
|
address, base);
|
||||||
println("\t// FATAL: unresolved function 0x{:08X} (no CallTarget in FunctionNode)", address);
|
println("\t// FATAL: unresolved function 0x{:08X} (no CallTarget in FunctionNode)", address);
|
||||||
|
|
@ -253,7 +258,7 @@ void BuilderContext::emit_conditional_branch(bool not_, std::string_view cond) {
|
||||||
|
|
||||||
// Use classifyTarget for consistent branch classification
|
// Use classifyTarget for consistent branch classification
|
||||||
// false = branch instruction (not a call), so own-base means loop back
|
// false = branch instruction (not a call), so own-base means loop back
|
||||||
auto kind = graph().classifyTarget(target, base, false);
|
auto kind = graph().classifyTarget(target, fn.base(), false);
|
||||||
|
|
||||||
switch (kind) {
|
switch (kind) {
|
||||||
case TargetKind::InternalLabel:
|
case TargetKind::InternalLabel:
|
||||||
|
|
@ -283,18 +288,33 @@ void BuilderContext::emit_conditional_branch(bool not_, std::string_view cond) {
|
||||||
println("\t}}");
|
println("\t}}");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
REXCODEGEN_ERROR("Unresolved conditional branch to 0x{:08X} from 0x{:08X} (no CallTarget)",
|
if (auto* targetFn = graph().getFunction(target)) {
|
||||||
target, base);
|
println("\tif ({}{}.{}) {{", not_ ? "!" : "", cr(insn.operands[0]), cond);
|
||||||
println("\tif ({}{}.{}) REX_FATAL(\"Unresolved branch from 0x{:08X} to 0x{:08X}\");",
|
println("\t\t{}(ctx, base);", targetFn->name());
|
||||||
not_ ? "!" : "", cr(insn.operands[0]), cond, base, target);
|
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;
|
break;
|
||||||
|
|
||||||
case TargetKind::Unknown:
|
case TargetKind::Unknown:
|
||||||
REXCODEGEN_ERROR("Unresolved conditional branch to 0x{:08X} from 0x{:08X}", target, base);
|
if (auto* targetFn = graph().getFunction(target)) {
|
||||||
println("\t// ERROR: conditional branch to unknown address 0x{:08X}", target);
|
println("\tif ({}{}.{}) {{", not_ ? "!" : "", cr(insn.operands[0]), cond);
|
||||||
println("\tif ({}{}.{}) REX_FATAL(\"Unresolved branch from 0x{:08X} to 0x{:08X}\");",
|
println("\t\t{}(ctx, base);", targetFn->name());
|
||||||
not_ ? "!" : "", cr(insn.operands[0]), cond, base, target);
|
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;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -303,7 +323,7 @@ void BuilderContext::emit_set_flush_mode(bool enable) {
|
||||||
auto newState = enable ? CSRState::VMX : CSRState::FPU;
|
auto newState = enable ? CSRState::VMX : CSRState::FPU;
|
||||||
if (csrState != newState) {
|
if (csrState != newState) {
|
||||||
auto prefix = enable ? "enable" : "disable";
|
auto prefix = enable ? "enable" : "disable";
|
||||||
auto suffix = csrState != CSRState::Unknown ? "Unconditional" : "";
|
auto suffix = csrState == CSRState::Unknown ? "Unconditional" : "";
|
||||||
println("\tctx.fpscr.{}FlushMode{}();", prefix, suffix);
|
println("\tctx.fpscr.{}FlushMode{}();", prefix, suffix);
|
||||||
|
|
||||||
csrState = newState;
|
csrState = newState;
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,28 @@
|
||||||
|
|
||||||
namespace rex::codegen {
|
namespace rex::codegen {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
bool hasEmittedBlockContaining(const FunctionNode& fn, uint32_t addr) {
|
||||||
|
for (const auto& block : fn.blocks()) {
|
||||||
|
if (block.contains(addr)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isLocalChunkTarget(const BuilderContext& ctx, uint32_t target) {
|
||||||
|
const uint32_t targetParent = ctx.graph().chunkParent(target);
|
||||||
|
if (targetParent == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return hasEmittedBlockContaining(ctx.fn, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
// Unconditional Branch
|
// Unconditional Branch
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
|
|
@ -27,9 +49,16 @@ namespace rex::codegen {
|
||||||
bool build_b(BuilderContext& ctx) {
|
bool build_b(BuilderContext& ctx) {
|
||||||
uint32_t target = ctx.insn.operands[0];
|
uint32_t target = ctx.insn.operands[0];
|
||||||
|
|
||||||
|
if (ctx.graph().getFunction(target) && target != ctx.fn.base() &&
|
||||||
|
!isLocalChunkTarget(ctx, target)) {
|
||||||
|
ctx.emit_function_call(target);
|
||||||
|
ctx.println("\treturn;");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Use graph to classify the target - handles thunks that branch to nearby functions
|
// Use graph to classify the target - handles thunks that branch to nearby functions
|
||||||
// false = branch instruction (not a call), so own-base means loop back
|
// false = branch instruction (not a call), so own-base means loop back
|
||||||
auto kind = ctx.graph().classifyTarget(target, ctx.base, false);
|
auto kind = ctx.graph().classifyTarget(target, ctx.fn.base(), false);
|
||||||
|
|
||||||
switch (kind) {
|
switch (kind) {
|
||||||
case TargetKind::InternalLabel:
|
case TargetKind::InternalLabel:
|
||||||
|
|
@ -67,7 +96,7 @@ bool build_bl(BuilderContext& ctx) {
|
||||||
|
|
||||||
// Use graph to classify the target
|
// Use graph to classify the target
|
||||||
// true = call instruction, so own-base means recursive call (not loop back)
|
// true = call instruction, so own-base means recursive call (not loop back)
|
||||||
auto kind = ctx.graph().classifyTarget(target, ctx.base, true);
|
auto kind = ctx.graph().classifyTarget(target, ctx.fn.base(), true);
|
||||||
|
|
||||||
switch (kind) {
|
switch (kind) {
|
||||||
case TargetKind::InternalLabel:
|
case TargetKind::InternalLabel:
|
||||||
|
|
@ -138,7 +167,7 @@ bool build_bctr(BuilderContext& ctx) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto kind = ctx.graph().classifyTarget(label, ctx.base, false);
|
auto kind = ctx.graph().classifyTarget(label, ctx.fn.base(), false);
|
||||||
switch (kind) {
|
switch (kind) {
|
||||||
case TargetKind::InternalLabel:
|
case TargetKind::InternalLabel:
|
||||||
ctx.println("\t\tgoto loc_{:X};", label);
|
ctx.println("\t\tgoto loc_{:X};", label);
|
||||||
|
|
|
||||||
|
|
@ -267,7 +267,7 @@ inline const char* getStoreMacro(BuilderContext& ctx, const char* normal_macro,
|
||||||
/**
|
/**
|
||||||
* Emit atomic load-and-reserve instruction (lwarx/ldarx pattern).
|
* Emit atomic load-and-reserve instruction (lwarx/ldarx pattern).
|
||||||
*
|
*
|
||||||
* Pattern: EA = rA + rB; reserved = *(T*)REX_RAW_ADDR(EA); rD = bswap(reserved)
|
* Pattern: EA = rA + rB; reserve EA; reserved = *(T*)REX_RAW_ADDR(EA); rD = bswap(reserved)
|
||||||
*
|
*
|
||||||
* @param ctx The builder context
|
* @param ctx The builder context
|
||||||
* @param ptr_type The pointer type (e.g., "uint32_t", "uint64_t")
|
* @param ptr_type The pointer type (e.g., "uint32_t", "uint64_t")
|
||||||
|
|
@ -280,8 +280,9 @@ inline void emitAtomicLoadReserve(BuilderContext& ctx, const char* ptr_type, con
|
||||||
if (ctx.insn.operands[1] != 0)
|
if (ctx.insn.operands[1] != 0)
|
||||||
ctx.print("{}.u32 + ", ctx.r(ctx.insn.operands[1]));
|
ctx.print("{}.u32 + ", ctx.r(ctx.insn.operands[1]));
|
||||||
ctx.println("{}.u32;", ctx.r(ctx.insn.operands[2]));
|
ctx.println("{}.u32;", ctx.r(ctx.insn.operands[2]));
|
||||||
ctx.println("\t{}.{} = *({}*)REX_RAW_ADDR({});", ctx.reserved(), reserved_field, ptr_type,
|
ctx.println("\trex::ppc::AcquireReservation(ctx, {}, sizeof({}));", ctx.ea(), ptr_type);
|
||||||
ctx.ea());
|
ctx.println("\t{}.{} = *(volatile {}*)REX_RAW_ADDR({});", ctx.reserved(), reserved_field,
|
||||||
|
ptr_type, ctx.ea());
|
||||||
ctx.println("\t{}.u64 = {}({}.{});", ctx.r(ctx.insn.operands[0]), bswap_func, ctx.reserved(),
|
ctx.println("\t{}.u64 = {}({}.{});", ctx.r(ctx.insn.operands[0]), bswap_func, ctx.reserved(),
|
||||||
reserved_field);
|
reserved_field);
|
||||||
}
|
}
|
||||||
|
|
@ -289,7 +290,7 @@ inline void emitAtomicLoadReserve(BuilderContext& ctx, const char* ptr_type, con
|
||||||
/**
|
/**
|
||||||
* Emit atomic store-conditional instruction (stwcx./stdcx. pattern).
|
* Emit atomic store-conditional instruction (stwcx./stdcx. pattern).
|
||||||
*
|
*
|
||||||
* Pattern: EA = rA + rB; cr0 = CAS(EA, reserved, bswap(rS))
|
* Pattern: EA = rA + rB; cr0 = reservation_matches(EA) && CAS(EA, reserved, bswap(rS))
|
||||||
*
|
*
|
||||||
* @param ctx The builder context
|
* @param ctx The builder context
|
||||||
* @param ptr_type The pointer type (e.g., "uint32_t", "uint64_t")
|
* @param ptr_type The pointer type (e.g., "uint32_t", "uint64_t")
|
||||||
|
|
@ -305,10 +306,12 @@ inline void emitAtomicStoreConditional(BuilderContext& ctx, const char* ptr_type
|
||||||
ctx.println("\t{}.lt = 0;", ctx.cr(0));
|
ctx.println("\t{}.lt = 0;", ctx.cr(0));
|
||||||
ctx.println("\t{}.gt = 0;", ctx.cr(0));
|
ctx.println("\t{}.gt = 0;", ctx.cr(0));
|
||||||
ctx.println(
|
ctx.println(
|
||||||
"\t{}.eq = __sync_bool_compare_and_swap(reinterpret_cast<{}*>(REX_RAW_ADDR({})), "
|
"\t{}.eq = rex::ppc::ReservationMatches(ctx, {}) && "
|
||||||
|
"__sync_bool_compare_and_swap(reinterpret_cast<{}*>(REX_RAW_ADDR({})), "
|
||||||
"{}.{}, {}({}.{}));",
|
"{}.{}, {}({}.{}));",
|
||||||
ctx.cr(0), ptr_type, ctx.ea(), ctx.reserved(), field, bswap_func, ctx.r(ctx.insn.operands[0]),
|
ctx.cr(0), ctx.ea(), ptr_type, ctx.ea(), ctx.reserved(), field, bswap_func,
|
||||||
field);
|
ctx.r(ctx.insn.operands[0]), field);
|
||||||
|
ctx.println("\trex::ppc::ReleaseReservation(ctx);");
|
||||||
ctx.println("\t{}.so = {}.so;", ctx.cr(0), ctx.xer());
|
ctx.println("\t{}.so = {}.so;", ctx.cr(0), ctx.xer());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -387,13 +390,25 @@ inline bool isMMIOUpperBits(uint32_t imm) {
|
||||||
*/
|
*/
|
||||||
inline void emitBranchWithBoundsCheck(BuilderContext& ctx, uint32_t target,
|
inline void emitBranchWithBoundsCheck(BuilderContext& ctx, uint32_t target,
|
||||||
std::string_view condition, std::string_view instr_name) {
|
std::string_view condition, std::string_view instr_name) {
|
||||||
if (target < ctx.fn.base() || target >= ctx.fn.end()) {
|
auto kind = ctx.graph().classifyTarget(target, ctx.fn.base(), false);
|
||||||
REXCODEGEN_WARN("{} at {:X} branches outside function to {:X}", instr_name, ctx.base, target);
|
if (kind == TargetKind::InternalLabel) {
|
||||||
ctx.println("\tif ({}) {{ /* branch to 0x{:X} outside function */ return; }}", condition,
|
|
||||||
target);
|
|
||||||
} else {
|
|
||||||
ctx.println("\tif ({}) goto loc_{:X};", condition, target);
|
ctx.println("\tif ({}) goto loc_{:X};", condition, target);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (auto* targetFn = ctx.graph().getFunction(target)) {
|
||||||
|
REXCODEGEN_WARN("{} at {:X} branches outside function to {:X}", instr_name, ctx.base, target);
|
||||||
|
ctx.println("\tif ({}) {{", condition);
|
||||||
|
ctx.println("\t\t{}(ctx, base);", targetFn->name());
|
||||||
|
ctx.println("\t\treturn;");
|
||||||
|
ctx.println("\t}}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
REXCODEGEN_WARN("{} at {:X} branches outside function to unresolved {:X}", instr_name,
|
||||||
|
ctx.base, target);
|
||||||
|
ctx.println("\tif ({}) REX_FATAL(\"Unresolved branch from 0x{:08X} to 0x{:08X}\");",
|
||||||
|
condition, ctx.base, target);
|
||||||
}
|
}
|
||||||
|
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
|
|
|
||||||
|
|
@ -31,20 +31,19 @@ bool build_attn(BuilderContext& ctx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool build_sync(BuilderContext& ctx) {
|
bool build_sync(BuilderContext& ctx) {
|
||||||
// Memory barrier, x86 has strong ordering so this is a no-op
|
// Guest threads use sync/lwsync around lock-free handoffs. Even on x86,
|
||||||
(void)ctx;
|
// preserve the compiler and host-thread ordering point in generated C++.
|
||||||
|
ctx.println("\tstd::atomic_thread_fence(std::memory_order_seq_cst);");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool build_lwsync(BuilderContext& ctx) {
|
bool build_lwsync(BuilderContext& ctx) {
|
||||||
// Lightweight memory barrier, x86 has strong ordering so this is a no-op
|
ctx.println("\tstd::atomic_thread_fence(std::memory_order_seq_cst);");
|
||||||
(void)ctx;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool build_eieio(BuilderContext& ctx) {
|
bool build_eieio(BuilderContext& ctx) {
|
||||||
// Enforce in-order execution of I/O, x86 has strong ordering so this is a no-op
|
ctx.println("\tstd::atomic_thread_fence(std::memory_order_seq_cst);");
|
||||||
(void)ctx;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,16 +30,6 @@ namespace rex::codegen {
|
||||||
// This file uses several magic constants from Intel SSE/SSE4 intrinsics.
|
// This file uses several magic constants from Intel SSE/SSE4 intrinsics.
|
||||||
// Here are the key constants and their meanings:
|
// Here are the key constants and their meanings:
|
||||||
//
|
//
|
||||||
// === Dot Product Masks (simde_mm_dp_ps) ===
|
|
||||||
// The mask byte controls which elements participate in the dot product and
|
|
||||||
// where the result is broadcast. Format: 0bAAAABBBB
|
|
||||||
// High nibble (AAAA): Which source elements to multiply (bit 7=x, 6=y, 5=z, 4=w)
|
|
||||||
// Low nibble (BBBB): Which destination elements receive the result
|
|
||||||
//
|
|
||||||
// 0xEF = 0b11101111: Dot product of elements y,z,w (bits 765 set), result to all (bits 3210 set)
|
|
||||||
// This computes dot(yzw) due to guest->host vector element reversal.
|
|
||||||
// 0xFF = 0b11111111: Full 4-element dot product, result broadcast to all elements
|
|
||||||
//
|
|
||||||
// === Floating-Point Sign Bit ===
|
// === Floating-Point Sign Bit ===
|
||||||
// 0x80000000: IEEE 754 sign bit mask for 32-bit float
|
// 0x80000000: IEEE 754 sign bit mask for 32-bit float
|
||||||
// Used for negation via XOR and sign extraction
|
// Used for negation via XOR and sign extraction
|
||||||
|
|
@ -93,80 +83,69 @@ bool build_vmulfp128(BuilderContext& ctx) {
|
||||||
bool build_vmaddfp(BuilderContext& ctx) {
|
bool build_vmaddfp(BuilderContext& ctx) {
|
||||||
ctx.emit_set_flush_mode(true);
|
ctx.emit_set_flush_mode(true);
|
||||||
ctx.println(
|
ctx.println(
|
||||||
"\tsimde_mm_store_ps({}.f32, simde_mm_add_ps(simde_mm_mul_ps(simde_mm_load_ps({}.f32), "
|
"\tsimde_mm_store_ps({}.f32, simde_mm_add_ps("
|
||||||
"simde_mm_load_ps({}.f32)), simde_mm_load_ps({}.f32)));",
|
"simde_mm_mul_ps(simde_mm_load_ps({}.f32), simde_mm_load_ps({}.f32)), "
|
||||||
|
"simde_mm_load_ps({}.f32)));",
|
||||||
ctx.v(ctx.insn.operands[0]), ctx.v(ctx.insn.operands[1]), ctx.v(ctx.insn.operands[2]),
|
ctx.v(ctx.insn.operands[0]), ctx.v(ctx.insn.operands[1]), ctx.v(ctx.insn.operands[2]),
|
||||||
ctx.v(ctx.insn.operands[3]));
|
ctx.v(ctx.insn.operands[3]));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool build_vnmsubfp(BuilderContext& ctx) {
|
bool build_vnmsubfp(BuilderContext& ctx) {
|
||||||
// vnmsubfp: vD = -(vA * vB - vC) - negation done by XOR with sign bit (0x80000000)
|
|
||||||
ctx.emit_set_flush_mode(true);
|
ctx.emit_set_flush_mode(true);
|
||||||
ctx.println(
|
ctx.println(
|
||||||
"\tsimde_mm_store_ps({}.f32, "
|
"\tsimde_mm_store_ps({}.f32, simde_mm_sub_ps("
|
||||||
"simde_mm_xor_ps(simde_mm_sub_ps(simde_mm_mul_ps(simde_mm_load_ps({}.f32), "
|
"simde_mm_load_ps({}.f32), "
|
||||||
"simde_mm_load_ps({}.f32)), simde_mm_load_ps({}.f32)), "
|
"simde_mm_mul_ps(simde_mm_load_ps({}.f32), simde_mm_load_ps({}.f32))));",
|
||||||
"simde_mm_castsi128_ps(simde_mm_set1_epi32(int(0x80000000)))));",
|
ctx.v(ctx.insn.operands[0]), ctx.v(ctx.insn.operands[3]), ctx.v(ctx.insn.operands[1]),
|
||||||
ctx.v(ctx.insn.operands[0]), ctx.v(ctx.insn.operands[1]), ctx.v(ctx.insn.operands[2]),
|
ctx.v(ctx.insn.operands[2]));
|
||||||
ctx.v(ctx.insn.operands[3]));
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool build_vmaxfp(BuilderContext& ctx) {
|
bool build_vmaxfp(BuilderContext& ctx) {
|
||||||
ctx.emit_set_flush_mode(true);
|
ctx.emit_set_flush_mode(true);
|
||||||
ctx.emit_vec_fp_binary("max");
|
ctx.println(
|
||||||
|
"\tsimde_mm_store_ps({}.f32, simde_mm_max_ps(simde_mm_load_ps({}.f32), "
|
||||||
|
"simde_mm_load_ps({}.f32)));",
|
||||||
|
ctx.v(ctx.insn.operands[0]), ctx.v(ctx.insn.operands[1]), ctx.v(ctx.insn.operands[2]));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool build_vminfp(BuilderContext& ctx) {
|
bool build_vminfp(BuilderContext& ctx) {
|
||||||
ctx.emit_set_flush_mode(true);
|
ctx.emit_set_flush_mode(true);
|
||||||
ctx.emit_vec_fp_binary("min");
|
ctx.println(
|
||||||
|
"\tsimde_mm_store_ps({}.f32, simde_mm_min_ps(simde_mm_load_ps({}.f32), "
|
||||||
|
"simde_mm_load_ps({}.f32)));",
|
||||||
|
ctx.v(ctx.insn.operands[0]), ctx.v(ctx.insn.operands[1]), ctx.v(ctx.insn.operands[2]));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool build_vrefp(BuilderContext& ctx) {
|
bool build_vrefp(BuilderContext& ctx) {
|
||||||
// TODO: see if we can use rcp safely
|
// Match Xenia's OPCODE_RECIP lowering. Xenia intentionally avoids rcpps here:
|
||||||
|
// the x86 estimate is less accurate than Altivec's vrefp guarantee and is
|
||||||
|
// known to break collision-sensitive paths in other titles.
|
||||||
ctx.emit_set_flush_mode(true);
|
ctx.emit_set_flush_mode(true);
|
||||||
ctx.emit_vec_fp_unary_expr("simde_mm_div_ps(simde_mm_set1_ps(1), simde_mm_load_ps({vA}.f32))");
|
ctx.emit_vec_fp_unary_expr("simde_mm_div_ps(simde_mm_set1_ps(1.0f), simde_mm_load_ps({vA}.f32))");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool build_vrsqrtefp(BuilderContext& ctx) {
|
bool build_vrsqrtefp(BuilderContext& ctx) {
|
||||||
// TODO: see if we can use rsqrt safely
|
// Match Xenia's OPCODE_RSQRT lowering. The x86 estimate is not accurate
|
||||||
|
// enough for collision-sensitive code, so use sqrt + divide.
|
||||||
ctx.emit_set_flush_mode(true);
|
ctx.emit_set_flush_mode(true);
|
||||||
ctx.emit_vec_fp_unary_expr(
|
ctx.emit_vec_fp_unary_expr(
|
||||||
"simde_mm_div_ps(simde_mm_set1_ps(1), simde_mm_sqrt_ps(simde_mm_load_ps({vA}.f32)))");
|
"simde_mm_div_ps(simde_mm_set1_ps(1.0f), simde_mm_sqrt_ps(simde_mm_load_ps({vA}.f32)))");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool build_vexptefp(BuilderContext& ctx) {
|
bool build_vexptefp(BuilderContext& ctx) {
|
||||||
// SIMD exp2 estimate (~12-bit precision, matching PPC vexptefp spec)
|
// Xenia's x64 backend lowers the corresponding POW2 vector opcode through
|
||||||
// Algorithm: exp2(x) = 2^n * poly(f), where n = floor(x), f = x - n
|
// std::exp2 rather than a hardware estimate. Match that behavior so traces
|
||||||
auto vD = ctx.v(ctx.insn.operands[0]);
|
// compare against the emulator reference instead of PPC-estimate accuracy.
|
||||||
auto vA = ctx.v(ctx.insn.operands[1]);
|
|
||||||
ctx.emit_set_flush_mode(true);
|
ctx.emit_set_flush_mode(true);
|
||||||
ctx.println("\t{{");
|
for (size_t i = 0; i < 4; i++)
|
||||||
ctx.println("\t\tsimde__m128 x = simde_mm_load_ps({}.f32);", vA);
|
ctx.println("\t{}.f32[{}] = exp2f({}.f32[{}]);", ctx.v(ctx.insn.operands[0]), i,
|
||||||
ctx.println(
|
ctx.v(ctx.insn.operands[1]), i);
|
||||||
"\t\tsimde__m128 n = simde_mm_round_ps(x, "
|
|
||||||
"SIMDE_MM_FROUND_TO_NEG_INF | SIMDE_MM_FROUND_NO_EXC);");
|
|
||||||
ctx.println("\t\tsimde__m128 f = simde_mm_sub_ps(x, n);");
|
|
||||||
// 4th-order minimax polynomial for 2^f, f in [0,1), ~12-bit accuracy
|
|
||||||
ctx.println("\t\tsimde__m128 p = simde_mm_set1_ps(1.8775767e-3f);");
|
|
||||||
ctx.println("\t\tp = simde_mm_add_ps(simde_mm_mul_ps(p, f), simde_mm_set1_ps(8.9893397e-3f));");
|
|
||||||
ctx.println("\t\tp = simde_mm_add_ps(simde_mm_mul_ps(p, f), simde_mm_set1_ps(5.5826318e-2f));");
|
|
||||||
ctx.println("\t\tp = simde_mm_add_ps(simde_mm_mul_ps(p, f), simde_mm_set1_ps(2.4015361e-1f));");
|
|
||||||
ctx.println("\t\tp = simde_mm_add_ps(simde_mm_mul_ps(p, f), simde_mm_set1_ps(6.9315308e-1f));");
|
|
||||||
ctx.println("\t\tp = simde_mm_add_ps(simde_mm_mul_ps(p, f), simde_mm_set1_ps(1.0f));");
|
|
||||||
// Construct 2^n by adding n to the IEEE 754 exponent bias and shifting into place
|
|
||||||
ctx.println("\t\tsimde__m128i exp_bits = simde_mm_slli_epi32(");
|
|
||||||
ctx.println("\t\t\tsimde_mm_add_epi32(simde_mm_cvttps_epi32(n), simde_mm_set1_epi32(127)), 23);");
|
|
||||||
ctx.println(
|
|
||||||
"\t\tsimde_mm_store_ps({}.f32, "
|
|
||||||
"simde_mm_mul_ps(p, simde_mm_castsi128_ps(exp_bits)));",
|
|
||||||
vD);
|
|
||||||
ctx.println("\t}}");
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -184,22 +163,19 @@ bool build_vlogefp(BuilderContext& ctx) {
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
|
|
||||||
bool build_vmsum3fp128(BuilderContext& ctx) {
|
bool build_vmsum3fp128(BuilderContext& ctx) {
|
||||||
// 3-element dot product accounting for guest->host vector element reversal
|
|
||||||
// 0xEF = dot(yzw) with result broadcast to all elements (see constants doc)
|
|
||||||
ctx.emit_set_flush_mode(true);
|
ctx.emit_set_flush_mode(true);
|
||||||
ctx.println(
|
ctx.println(
|
||||||
"\tsimde_mm_store_ps({}.f32, simde_mm_dp_ps(simde_mm_load_ps({}.f32), "
|
"\tsimde_mm_store_ps({}.f32, rex::ppc::simde_mm_vmsum3fp128_ps("
|
||||||
"simde_mm_load_ps({}.f32), 0xEF));",
|
"simde_mm_load_ps({}.f32), simde_mm_load_ps({}.f32)));",
|
||||||
ctx.v(ctx.insn.operands[0]), ctx.v(ctx.insn.operands[1]), ctx.v(ctx.insn.operands[2]));
|
ctx.v(ctx.insn.operands[0]), ctx.v(ctx.insn.operands[1]), ctx.v(ctx.insn.operands[2]));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool build_vmsum4fp128(BuilderContext& ctx) {
|
bool build_vmsum4fp128(BuilderContext& ctx) {
|
||||||
// 4-element dot product: 0xFF = all 4 elements, result to all (see constants doc)
|
|
||||||
ctx.emit_set_flush_mode(true);
|
ctx.emit_set_flush_mode(true);
|
||||||
ctx.println(
|
ctx.println(
|
||||||
"\tsimde_mm_store_ps({}.f32, simde_mm_dp_ps(simde_mm_load_ps({}.f32), "
|
"\tsimde_mm_store_ps({}.f32, rex::ppc::simde_mm_vmsum4fp128_ps("
|
||||||
"simde_mm_load_ps({}.f32), 0xFF));",
|
"simde_mm_load_ps({}.f32), simde_mm_load_ps({}.f32)));",
|
||||||
ctx.v(ctx.insn.operands[0]), ctx.v(ctx.insn.operands[1]), ctx.v(ctx.insn.operands[2]));
|
ctx.v(ctx.insn.operands[0]), ctx.v(ctx.insn.operands[1]), ctx.v(ctx.insn.operands[2]));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -250,7 +226,25 @@ bool build_vaddsbs(BuilderContext& ctx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool build_vaddshs(BuilderContext& ctx) {
|
bool build_vaddshs(BuilderContext& ctx) {
|
||||||
ctx.emit_vec_int_binary("adds_epi16", "s16");
|
auto vD = ctx.v(ctx.insn.operands[0]);
|
||||||
|
auto vA = ctx.v(ctx.insn.operands[1]);
|
||||||
|
auto vB = ctx.v(ctx.insn.operands[2]);
|
||||||
|
|
||||||
|
ctx.println("\t{{");
|
||||||
|
ctx.println("\t\tfor (int i = 0; i < 8; ++i) {{");
|
||||||
|
ctx.println("\t\t\tint32_t sum = static_cast<int32_t>({}.s16[i]) + static_cast<int32_t>({}.s16[i]);", vA,
|
||||||
|
vB);
|
||||||
|
ctx.println("\t\t\tif (sum > 32767) {{");
|
||||||
|
ctx.println("\t\t\t\t{}.s16[i] = 32767;", vD);
|
||||||
|
ctx.println("\t\t\t\tctx.vscr_sat = 1;");
|
||||||
|
ctx.println("\t\t\t}} else if (sum < -32768) {{");
|
||||||
|
ctx.println("\t\t\t\t{}.s16[i] = -32768;", vD);
|
||||||
|
ctx.println("\t\t\t\tctx.vscr_sat = 1;");
|
||||||
|
ctx.println("\t\t\t}} else {{");
|
||||||
|
ctx.println("\t\t\t\t{}.s16[i] = static_cast<int16_t>(sum);", vD);
|
||||||
|
ctx.println("\t\t\t}}");
|
||||||
|
ctx.println("\t\t}}");
|
||||||
|
ctx.println("\t}}");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -261,25 +255,19 @@ bool build_vaddsws(BuilderContext& ctx) {
|
||||||
auto vB = ctx.v(ctx.insn.operands[2]);
|
auto vB = ctx.v(ctx.insn.operands[2]);
|
||||||
|
|
||||||
ctx.println("\t{{");
|
ctx.println("\t{{");
|
||||||
// No direct SSE intrinsic, so use overflow detection and blend
|
ctx.println("\t\tfor (int i = 0; i < 4; ++i) {{");
|
||||||
ctx.println("\t\tsimde__m128i a = simde_mm_load_si128((simde__m128i*){}.u8);", vA);
|
ctx.println("\t\t\tlong long sum = static_cast<long long>({}.s32[i]) + static_cast<long long>({}.s32[i]);", vA,
|
||||||
ctx.println("\t\tsimde__m128i b = simde_mm_load_si128((simde__m128i*){}.u8);", vB);
|
vB);
|
||||||
ctx.println("\t\tsimde__m128i sum = simde_mm_add_epi32(a, b);");
|
ctx.println("\t\t\tif (sum > 2147483647LL) {{");
|
||||||
// Overflow if: (a ^ sum) & (b ^ sum) has MSB set (signs of a,b match but sum differs)
|
ctx.println("\t\t\t\t{}.s32[i] = 2147483647;", vD);
|
||||||
ctx.println(
|
ctx.println("\t\t\t\tctx.vscr_sat = 1;");
|
||||||
"\t\tsimde__m128i overflow = simde_mm_and_si128(simde_mm_xor_si128(a, sum), "
|
ctx.println("\t\t\t}} else if (sum < (-2147483647LL - 1LL)) {{");
|
||||||
"simde_mm_xor_si128(b, sum));");
|
ctx.println("\t\t\t\t{}.s32[i] = -2147483647 - 1;", vD);
|
||||||
// Saturation value: if a positive (MSB=0), use INT32_MAX;
|
ctx.println("\t\t\t\tctx.vscr_sat = 1;");
|
||||||
// if negative, use INT32_MIN (a >> 31) gives all 1s if negative, all 0s
|
ctx.println("\t\t\t}} else {{");
|
||||||
// if positive XOR with 0x7FFFFFFF: negative -> 0x80000000, positive -> 0x7FFFFFFF
|
ctx.println("\t\t\t\t{}.s32[i] = static_cast<int32_t>(sum);", vD);
|
||||||
ctx.println(
|
ctx.println("\t\t\t}}");
|
||||||
"\t\tsimde__m128i sat_val = simde_mm_xor_si128(simde_mm_srai_epi32(a, 31), "
|
ctx.println("\t\t}}");
|
||||||
"simde_mm_set1_epi32(0x7FFFFFFF));");
|
|
||||||
// Blend: select sat_val where overflow MSB is set, else sum
|
|
||||||
ctx.println(
|
|
||||||
"\t\tsimde_mm_store_si128((simde__m128i*){}.u8, simde_mm_blendv_epi8(sum, sat_val, "
|
|
||||||
"overflow));",
|
|
||||||
vD);
|
|
||||||
ctx.println("\t}}");
|
ctx.println("\t}}");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -318,13 +306,187 @@ bool build_vadduhs(BuilderContext& ctx) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool build_vmhaddshs(BuilderContext& ctx) {
|
||||||
|
auto vD = ctx.v(ctx.insn.operands[0]);
|
||||||
|
auto vA = ctx.v(ctx.insn.operands[1]);
|
||||||
|
auto vB = ctx.v(ctx.insn.operands[2]);
|
||||||
|
auto vC = ctx.v(ctx.insn.operands[3]);
|
||||||
|
|
||||||
|
ctx.println("\t{{");
|
||||||
|
ctx.println("\t\tfor (int i = 0; i < 8; ++i) {{");
|
||||||
|
ctx.println("\t\t\tint32_t value = ((static_cast<int32_t>({}.s16[i]) * static_cast<int32_t>({}.s16[i])) >> 15) + static_cast<int32_t>({}.s16[i]);", vA,
|
||||||
|
vB, vC);
|
||||||
|
ctx.println("\t\t\tif (value > 32767) {{");
|
||||||
|
ctx.println("\t\t\t\t{}.s16[i] = 32767;", vD);
|
||||||
|
ctx.println("\t\t\t\tctx.vscr_sat = 1;");
|
||||||
|
ctx.println("\t\t\t}} else if (value < -32768) {{");
|
||||||
|
ctx.println("\t\t\t\t{}.s16[i] = -32768;", vD);
|
||||||
|
ctx.println("\t\t\t\tctx.vscr_sat = 1;");
|
||||||
|
ctx.println("\t\t\t}} else {{");
|
||||||
|
ctx.println("\t\t\t\t{}.s16[i] = static_cast<int16_t>(value);", vD);
|
||||||
|
ctx.println("\t\t\t}}");
|
||||||
|
ctx.println("\t\t}}");
|
||||||
|
ctx.println("\t}}");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool build_vmhraddshs(BuilderContext& ctx) {
|
||||||
|
auto vD = ctx.v(ctx.insn.operands[0]);
|
||||||
|
auto vA = ctx.v(ctx.insn.operands[1]);
|
||||||
|
auto vB = ctx.v(ctx.insn.operands[2]);
|
||||||
|
auto vC = ctx.v(ctx.insn.operands[3]);
|
||||||
|
|
||||||
|
ctx.println("\t{{");
|
||||||
|
ctx.println("\t\tfor (int i = 0; i < 8; ++i) {{");
|
||||||
|
ctx.println("\t\t\tint32_t value = (((static_cast<int32_t>({}.s16[i]) * static_cast<int32_t>({}.s16[i])) + 0x4000) >> 15) + static_cast<int32_t>({}.s16[i]);", vA,
|
||||||
|
vB, vC);
|
||||||
|
ctx.println("\t\t\tif (value > 32767) {{");
|
||||||
|
ctx.println("\t\t\t\t{}.s16[i] = 32767;", vD);
|
||||||
|
ctx.println("\t\t\t\tctx.vscr_sat = 1;");
|
||||||
|
ctx.println("\t\t\t}} else if (value < -32768) {{");
|
||||||
|
ctx.println("\t\t\t\t{}.s16[i] = -32768;", vD);
|
||||||
|
ctx.println("\t\t\t\tctx.vscr_sat = 1;");
|
||||||
|
ctx.println("\t\t\t}} else {{");
|
||||||
|
ctx.println("\t\t\t\t{}.s16[i] = static_cast<int16_t>(value);", vD);
|
||||||
|
ctx.println("\t\t\t}}");
|
||||||
|
ctx.println("\t\t}}");
|
||||||
|
ctx.println("\t}}");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool build_vmladduhm(BuilderContext& ctx) {
|
||||||
|
auto vD = ctx.v(ctx.insn.operands[0]);
|
||||||
|
auto vA = ctx.v(ctx.insn.operands[1]);
|
||||||
|
auto vB = ctx.v(ctx.insn.operands[2]);
|
||||||
|
auto vC = ctx.v(ctx.insn.operands[3]);
|
||||||
|
|
||||||
|
for (int i = 0; i < 8; ++i) {
|
||||||
|
ctx.println("\t{}.u16[{}] = static_cast<uint16_t>((static_cast<uint32_t>({}.u16[{}]) * static_cast<uint32_t>({}.u16[{}])) + static_cast<uint32_t>({}.u16[{}]));",
|
||||||
|
vD, i, vA, i, vB, i, vC, i);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool build_vmsumubm(BuilderContext& ctx) {
|
||||||
|
auto vD = ctx.v(ctx.insn.operands[0]);
|
||||||
|
auto vA = ctx.v(ctx.insn.operands[1]);
|
||||||
|
auto vB = ctx.v(ctx.insn.operands[2]);
|
||||||
|
auto vC = ctx.v(ctx.insn.operands[3]);
|
||||||
|
|
||||||
|
for (int i = 0; i < 4; ++i) {
|
||||||
|
const int b = i * 4;
|
||||||
|
ctx.println("\t{}.u32[{}] = {}.u32[{}] + static_cast<uint32_t>({}.u8[{}]) * static_cast<uint32_t>({}.u8[{}]) + static_cast<uint32_t>({}.u8[{}]) * static_cast<uint32_t>({}.u8[{}]) + static_cast<uint32_t>({}.u8[{}]) * static_cast<uint32_t>({}.u8[{}]) + static_cast<uint32_t>({}.u8[{}]) * static_cast<uint32_t>({}.u8[{}]);",
|
||||||
|
vD, i, vC, i, vA, b, vB, b, vA, b + 1, vB, b + 1, vA, b + 2, vB, b + 2, vA, b + 3,
|
||||||
|
vB, b + 3);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool build_vmsummbm(BuilderContext& ctx) {
|
||||||
|
auto vD = ctx.v(ctx.insn.operands[0]);
|
||||||
|
auto vA = ctx.v(ctx.insn.operands[1]);
|
||||||
|
auto vB = ctx.v(ctx.insn.operands[2]);
|
||||||
|
auto vC = ctx.v(ctx.insn.operands[3]);
|
||||||
|
|
||||||
|
for (int i = 0; i < 4; ++i) {
|
||||||
|
const int b = i * 4;
|
||||||
|
ctx.println("\t{}.u32[{}] = static_cast<uint32_t>(static_cast<int64_t>({}.s32[{}]) + static_cast<int64_t>({}.s8[{}]) * static_cast<int64_t>({}.u8[{}]) + static_cast<int64_t>({}.s8[{}]) * static_cast<int64_t>({}.u8[{}]) + static_cast<int64_t>({}.s8[{}]) * static_cast<int64_t>({}.u8[{}]) + static_cast<int64_t>({}.s8[{}]) * static_cast<int64_t>({}.u8[{}]));",
|
||||||
|
vD, i, vC, i, vA, b, vB, b, vA, b + 1, vB, b + 1, vA, b + 2, vB, b + 2, vA, b + 3,
|
||||||
|
vB, b + 3);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool build_vmsumuhm(BuilderContext& ctx) {
|
||||||
|
auto vD = ctx.v(ctx.insn.operands[0]);
|
||||||
|
auto vA = ctx.v(ctx.insn.operands[1]);
|
||||||
|
auto vB = ctx.v(ctx.insn.operands[2]);
|
||||||
|
auto vC = ctx.v(ctx.insn.operands[3]);
|
||||||
|
|
||||||
|
for (int i = 0; i < 4; ++i) {
|
||||||
|
const int h = i * 2;
|
||||||
|
ctx.println("\t{}.u32[{}] = {}.u32[{}] + static_cast<uint32_t>({}.u16[{}]) * static_cast<uint32_t>({}.u16[{}]) + static_cast<uint32_t>({}.u16[{}]) * static_cast<uint32_t>({}.u16[{}]);",
|
||||||
|
vD, i, vC, i, vA, h, vB, h, vA, h + 1, vB, h + 1);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool build_vmsumuhs(BuilderContext& ctx) {
|
||||||
|
auto vD = ctx.v(ctx.insn.operands[0]);
|
||||||
|
auto vA = ctx.v(ctx.insn.operands[1]);
|
||||||
|
auto vB = ctx.v(ctx.insn.operands[2]);
|
||||||
|
auto vC = ctx.v(ctx.insn.operands[3]);
|
||||||
|
|
||||||
|
ctx.println("\t{{");
|
||||||
|
ctx.println("\t\tfor (int i = 0; i < 4; ++i) {{");
|
||||||
|
ctx.println("\t\t\tint h = i * 2;");
|
||||||
|
ctx.println("\t\t\tuint64_t value = static_cast<uint64_t>({}.u32[i]) + static_cast<uint64_t>({}.u16[h]) * static_cast<uint64_t>({}.u16[h]) + static_cast<uint64_t>({}.u16[h + 1]) * static_cast<uint64_t>({}.u16[h + 1]);",
|
||||||
|
vC, vA, vB, vA, vB);
|
||||||
|
ctx.println("\t\t\tif (value > 0xFFFFFFFFULL) {{");
|
||||||
|
ctx.println("\t\t\t\t{}.u32[i] = 0xFFFFFFFFu;", vD);
|
||||||
|
ctx.println("\t\t\t\tctx.vscr_sat = 1;");
|
||||||
|
ctx.println("\t\t\t}} else {{");
|
||||||
|
ctx.println("\t\t\t\t{}.u32[i] = static_cast<uint32_t>(value);", vD);
|
||||||
|
ctx.println("\t\t\t}}");
|
||||||
|
ctx.println("\t\t}}");
|
||||||
|
ctx.println("\t}}");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool build_vmsumshm(BuilderContext& ctx) {
|
||||||
|
auto vD = ctx.v(ctx.insn.operands[0]);
|
||||||
|
auto vA = ctx.v(ctx.insn.operands[1]);
|
||||||
|
auto vB = ctx.v(ctx.insn.operands[2]);
|
||||||
|
auto vC = ctx.v(ctx.insn.operands[3]);
|
||||||
|
|
||||||
|
for (int i = 0; i < 4; ++i) {
|
||||||
|
const int h = i * 2;
|
||||||
|
ctx.println("\t{}.u32[{}] = static_cast<uint32_t>(static_cast<int64_t>({}.s32[{}]) + static_cast<int64_t>({}.s16[{}]) * static_cast<int64_t>({}.s16[{}]) + static_cast<int64_t>({}.s16[{}]) * static_cast<int64_t>({}.s16[{}]));",
|
||||||
|
vD, i, vC, i, vA, h, vB, h, vA, h + 1, vB, h + 1);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool build_vmsumshs(BuilderContext& ctx) {
|
||||||
|
auto vD = ctx.v(ctx.insn.operands[0]);
|
||||||
|
auto vA = ctx.v(ctx.insn.operands[1]);
|
||||||
|
auto vB = ctx.v(ctx.insn.operands[2]);
|
||||||
|
auto vC = ctx.v(ctx.insn.operands[3]);
|
||||||
|
|
||||||
|
ctx.println("\t{{");
|
||||||
|
ctx.println("\t\tfor (int i = 0; i < 4; ++i) {{");
|
||||||
|
ctx.println("\t\t\tint h = i * 2;");
|
||||||
|
ctx.println("\t\t\tint64_t value = static_cast<int64_t>({}.s32[i]) + static_cast<int64_t>({}.s16[h]) * static_cast<int64_t>({}.s16[h]) + static_cast<int64_t>({}.s16[h + 1]) * static_cast<int64_t>({}.s16[h + 1]);",
|
||||||
|
vC, vA, vB, vA, vB);
|
||||||
|
ctx.println("\t\t\tif (value > 2147483647LL) {{");
|
||||||
|
ctx.println("\t\t\t\t{}.s32[i] = 2147483647;", vD);
|
||||||
|
ctx.println("\t\t\t\tctx.vscr_sat = 1;");
|
||||||
|
ctx.println("\t\t\t}} else if (value < (-2147483647LL - 1LL)) {{");
|
||||||
|
ctx.println("\t\t\t\t{}.s32[i] = -2147483647 - 1;", vD);
|
||||||
|
ctx.println("\t\t\t\tctx.vscr_sat = 1;");
|
||||||
|
ctx.println("\t\t\t}} else {{");
|
||||||
|
ctx.println("\t\t\t\t{}.s32[i] = static_cast<int32_t>(value);", vD);
|
||||||
|
ctx.println("\t\t\t}}");
|
||||||
|
ctx.println("\t\t}}");
|
||||||
|
ctx.println("\t}}");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool build_vsubsws(BuilderContext& ctx) {
|
bool build_vsubsws(BuilderContext& ctx) {
|
||||||
// TODO: vectorize
|
// TODO: vectorize
|
||||||
for (size_t i = 0; i < 4; i++) {
|
for (size_t i = 0; i < 4; i++) {
|
||||||
ctx.println("\t{}.s64 = int64_t({}.s32[{}]) - int64_t({}.s32[{}]);", ctx.temp(),
|
ctx.println("\t{}.s64 = int64_t({}.s32[{}]) - int64_t({}.s32[{}]);", ctx.temp(),
|
||||||
ctx.v(ctx.insn.operands[1]), i, ctx.v(ctx.insn.operands[2]), i);
|
ctx.v(ctx.insn.operands[1]), i, ctx.v(ctx.insn.operands[2]), i);
|
||||||
ctx.println("\t{}.s32[{}] = {}.s64 > INT_MAX ? INT_MAX : {}.s64 < INT_MIN ? INT_MIN : {}.s64;",
|
ctx.println("\tif ({}.s64 > INT_MAX) {{", ctx.temp());
|
||||||
ctx.v(ctx.insn.operands[0]), i, ctx.temp(), ctx.temp(), ctx.temp());
|
ctx.println("\t\t{}.s32[{}] = INT_MAX;", ctx.v(ctx.insn.operands[0]), i);
|
||||||
|
ctx.println("\t\tctx.vscr_sat = 1;");
|
||||||
|
ctx.println("\t}} else if ({}.s64 < INT_MIN) {{", ctx.temp());
|
||||||
|
ctx.println("\t\t{}.s32[{}] = INT_MIN;", ctx.v(ctx.insn.operands[0]), i);
|
||||||
|
ctx.println("\t\tctx.vscr_sat = 1;");
|
||||||
|
ctx.println("\t}} else {{");
|
||||||
|
ctx.println("\t\t{}.s32[{}] = static_cast<int32_t>({}.s64);", ctx.v(ctx.insn.operands[0]), i,
|
||||||
|
ctx.temp());
|
||||||
|
ctx.println("\t}}");
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -566,9 +728,10 @@ bool build_vcmpbfp(BuilderContext& ctx) {
|
||||||
"\tsimde_mm_store_ps({}.f32, simde_mm_and_ps(simde_mm_cmpgt_ps(simde_mm_load_ps({}.f32), "
|
"\tsimde_mm_store_ps({}.f32, simde_mm_and_ps(simde_mm_cmpgt_ps(simde_mm_load_ps({}.f32), "
|
||||||
"simde_mm_load_ps({}.f32)), simde_mm_castsi128_ps(simde_mm_set1_epi32(int(0x80000000)))));",
|
"simde_mm_load_ps({}.f32)), simde_mm_castsi128_ps(simde_mm_set1_epi32(int(0x80000000)))));",
|
||||||
ctx.v_temp(), vA, vB);
|
ctx.v_temp(), vA, vB);
|
||||||
// lt_neg_mask = (vA < -vB) & 0x40000000
|
// lt_neg_mask = !(vA >= -vB) & 0x40000000. This intentionally treats
|
||||||
|
// unordered lanes as out-of-bounds, matching Xenia's vcmpbfp lowering.
|
||||||
ctx.println(
|
ctx.println(
|
||||||
"\tsimde_mm_store_ps({}.f32, simde_mm_and_ps(simde_mm_cmplt_ps(simde_mm_load_ps({}.f32), "
|
"\tsimde_mm_store_ps({}.f32, simde_mm_andnot_ps(simde_mm_cmpge_ps(simde_mm_load_ps({}.f32), "
|
||||||
"simde_mm_xor_ps(simde_mm_load_ps({}.f32), "
|
"simde_mm_xor_ps(simde_mm_load_ps({}.f32), "
|
||||||
"simde_mm_castsi128_ps(simde_mm_set1_epi32(int(0x80000000))))), "
|
"simde_mm_castsi128_ps(simde_mm_set1_epi32(int(0x80000000))))), "
|
||||||
"simde_mm_castsi128_ps(simde_mm_set1_epi32(int(0x40000000)))));",
|
"simde_mm_castsi128_ps(simde_mm_set1_epi32(int(0x40000000)))));",
|
||||||
|
|
@ -1238,7 +1401,7 @@ bool build_vpkd3d128(BuilderContext& ctx) {
|
||||||
|
|
||||||
// Pack x (10 bits, position 0-9) - Guest element 0 is at index 3
|
// Pack x (10 bits, position 0-9) - Guest element 0 is at index 3
|
||||||
ctx.println(
|
ctx.println(
|
||||||
"\t{}.f32 = {}.f32[3] < kPack2101010_Min10 ? kPack2101010_Min10 : ({}.f32[3] > "
|
"\t{}.f32 = !({}.f32[3] >= kPack2101010_Min10) ? kPack2101010_Min10 : ({}.f32[3] > "
|
||||||
"kPack2101010_Max10 ? kPack2101010_Max10 : {}.f32[3]);",
|
"kPack2101010_Max10 ? kPack2101010_Max10 : {}.f32[3]);",
|
||||||
ctx.temp(), ctx.v(ctx.insn.operands[1]), ctx.v(ctx.insn.operands[1]),
|
ctx.temp(), ctx.v(ctx.insn.operands[1]), ctx.v(ctx.insn.operands[1]),
|
||||||
ctx.v(ctx.insn.operands[1]));
|
ctx.v(ctx.insn.operands[1]));
|
||||||
|
|
@ -1246,7 +1409,7 @@ bool build_vpkd3d128(BuilderContext& ctx) {
|
||||||
|
|
||||||
// Pack y (10 bits, position 10-19) - Guest element 1 is at index 2
|
// Pack y (10 bits, position 10-19) - Guest element 1 is at index 2
|
||||||
ctx.println(
|
ctx.println(
|
||||||
"\t{}.f32 = {}.f32[2] < kPack2101010_Min10 ? kPack2101010_Min10 : ({}.f32[2] > "
|
"\t{}.f32 = !({}.f32[2] >= kPack2101010_Min10) ? kPack2101010_Min10 : ({}.f32[2] > "
|
||||||
"kPack2101010_Max10 ? kPack2101010_Max10 : {}.f32[2]);",
|
"kPack2101010_Max10 ? kPack2101010_Max10 : {}.f32[2]);",
|
||||||
ctx.temp(), ctx.v(ctx.insn.operands[1]), ctx.v(ctx.insn.operands[1]),
|
ctx.temp(), ctx.v(ctx.insn.operands[1]), ctx.v(ctx.insn.operands[1]),
|
||||||
ctx.v(ctx.insn.operands[1]));
|
ctx.v(ctx.insn.operands[1]));
|
||||||
|
|
@ -1254,7 +1417,7 @@ bool build_vpkd3d128(BuilderContext& ctx) {
|
||||||
|
|
||||||
// Pack z (10 bits, position 20-29) - Guest element 2 is at index 1
|
// Pack z (10 bits, position 20-29) - Guest element 2 is at index 1
|
||||||
ctx.println(
|
ctx.println(
|
||||||
"\t{}.f32 = {}.f32[1] < kPack2101010_Min10 ? kPack2101010_Min10 : ({}.f32[1] > "
|
"\t{}.f32 = !({}.f32[1] >= kPack2101010_Min10) ? kPack2101010_Min10 : ({}.f32[1] > "
|
||||||
"kPack2101010_Max10 ? kPack2101010_Max10 : {}.f32[1]);",
|
"kPack2101010_Max10 ? kPack2101010_Max10 : {}.f32[1]);",
|
||||||
ctx.temp(), ctx.v(ctx.insn.operands[1]), ctx.v(ctx.insn.operands[1]),
|
ctx.temp(), ctx.v(ctx.insn.operands[1]), ctx.v(ctx.insn.operands[1]),
|
||||||
ctx.v(ctx.insn.operands[1]));
|
ctx.v(ctx.insn.operands[1]));
|
||||||
|
|
@ -1262,7 +1425,7 @@ bool build_vpkd3d128(BuilderContext& ctx) {
|
||||||
|
|
||||||
// Pack w (2 bits, position 30-31) - Guest element 3 is at index 0
|
// Pack w (2 bits, position 30-31) - Guest element 3 is at index 0
|
||||||
ctx.println(
|
ctx.println(
|
||||||
"\t{}.f32 = {}.f32[0] < kPack2101010_Min2 ? kPack2101010_Min2 : ({}.f32[0] > "
|
"\t{}.f32 = !({}.f32[0] >= kPack2101010_Min2) ? kPack2101010_Min2 : ({}.f32[0] > "
|
||||||
"kPack2101010_Max2 ? kPack2101010_Max2 : {}.f32[0]);",
|
"kPack2101010_Max2 ? kPack2101010_Max2 : {}.f32[0]);",
|
||||||
ctx.temp(), ctx.v(ctx.insn.operands[1]), ctx.v(ctx.insn.operands[1]),
|
ctx.temp(), ctx.v(ctx.insn.operands[1]), ctx.v(ctx.insn.operands[1]),
|
||||||
ctx.v(ctx.insn.operands[1]));
|
ctx.v(ctx.insn.operands[1]));
|
||||||
|
|
@ -1282,21 +1445,8 @@ bool build_vpkd3d128(BuilderContext& ctx) {
|
||||||
size_t srcIdx = 3 - i; // Guest element i is at host array index 3-i
|
size_t srcIdx = 3 - i; // Guest element i is at host array index 3-i
|
||||||
size_t dstIdx =
|
size_t dstIdx =
|
||||||
(1 - i) + (2 * ctx.insn.operands[4]); // Output reversed: elem 0 to high, elem 1 to low
|
(1 - i) + (2 * ctx.insn.operands[4]); // Output reversed: elem 0 to high, elem 1 to low
|
||||||
ctx.println("\t{}.u32 = ({}.u32[{}]&0x7FFFFFFF);", ctx.temp(), ctx.v(ctx.insn.operands[1]),
|
ctx.println("\t{}.u16[{}] = rex::float_to_xenos_half({}.f32[{}]);",
|
||||||
srcIdx);
|
ctx.v(ctx.insn.operands[0]), dstIdx, ctx.v(ctx.insn.operands[1]), srcIdx);
|
||||||
ctx.println(
|
|
||||||
"\t{0}.u8[0] = ({1}.f32 != {1}.f32) || ({1}.f32 > 65504.0f) ? 0xFF : "
|
|
||||||
"(({2}.u32[{3}]&0x7f800000)>>23);",
|
|
||||||
ctx.v_temp(), ctx.temp(), ctx.v(ctx.insn.operands[1]), srcIdx);
|
|
||||||
ctx.println("\t{}.u16 = {}.u8[0] != 0xFF ? (({}.u32[{}]&0x7FE000)>>13) : 0x0;", ctx.temp(),
|
|
||||||
ctx.v_temp(), ctx.v(ctx.insn.operands[1]), srcIdx);
|
|
||||||
ctx.println(
|
|
||||||
"\t{0}.u16[{1}] = {2}.u8[0] != 0xFF ? ({2}.u8[0] > 0x70 ? "
|
|
||||||
"((({2}.u8[0]-0x70)<<10)+{3}.u16) : (0x71-{2}.u8[0] > 31 ? 0x0 : "
|
|
||||||
"((0x400+{3}.u16)>>(0x71-{2}.u8[0])))) : 0x7FFF;",
|
|
||||||
ctx.v(ctx.insn.operands[0]), dstIdx, ctx.v_temp(), ctx.temp());
|
|
||||||
ctx.println("\t{}.u16[{}] |= (({}.u32[{}]&0x80000000)>>16);", ctx.v(ctx.insn.operands[0]),
|
|
||||||
dstIdx, ctx.v(ctx.insn.operands[1]), srcIdx);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -1321,20 +1471,16 @@ bool build_vpkd3d128(BuilderContext& ctx) {
|
||||||
|
|
||||||
case 5: // float16_4
|
case 5: // float16_4
|
||||||
{
|
{
|
||||||
// NOTE: These combinations come from game traces (heuristic handling), not the official spec.
|
// The instruction tests and Skate 3's two-pass packing both rely on untouched lanes being
|
||||||
// The spec only defines the encoding, not the exact semantics of y (mask) and z (shift) here.
|
// preserved; clearing the opposite half corrupts previously packed float16_4 data.
|
||||||
// Anyone reading this later should know it may need extending if new combos turn up in other
|
|
||||||
// games. Combinations observed so far: mask=2, shift=0 → write u16[3..0], zero u16[4..7]
|
|
||||||
// mask=2, shift=2 → write u16[7..4], zero u16[3..0] (first pass)
|
|
||||||
// mask=3, shift=0 → write u16[3..0] without zeroing (second pass, preserves the upper half)
|
|
||||||
|
|
||||||
uint32_t mask = ctx.insn.operands[3];
|
uint32_t mask = ctx.insn.operands[3];
|
||||||
uint32_t shift = ctx.insn.operands[4];
|
uint32_t shift = ctx.insn.operands[4];
|
||||||
|
|
||||||
// Guard fix: The shift bound was too loose (shift=3 would cause dstIdx to reach 9, an OOB
|
// Guard fix: The shift bound was too loose (shift=3 would cause dstIdx to reach 9, an OOB
|
||||||
// store). We also explicitly reject shift == 1 since the clear block below only handles 0
|
// store). We also explicitly reject shift == 1 because only the lower and upper half
|
||||||
// and 2. Furthermore, we explicitly warn on unexpected mask values (e.g., 0, 1) to avoid
|
// placements are currently understood. Furthermore, we explicitly warn on unexpected mask
|
||||||
// silent fallthroughs and silently generating miscompiled code.
|
// values (e.g., 0, 1) to avoid silent fallthroughs and silently generating miscompiled code.
|
||||||
if ((shift != 0 && shift != 2) || (mask != 2 && mask != 3)) {
|
if ((shift != 0 && shift != 2) || (mask != 2 && mask != 3)) {
|
||||||
REXCODEGEN_WARN("Unexpected float16_4 pack instruction at {:X} (mask={}, shift={})",
|
REXCODEGEN_WARN("Unexpected float16_4 pack instruction at {:X} (mask={}, shift={})",
|
||||||
ctx.base, mask, shift);
|
ctx.base, mask, shift);
|
||||||
|
|
@ -1343,39 +1489,15 @@ bool build_vpkd3d128(BuilderContext& ctx) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// mask=2: before writing, clear the half that will NOT be written.
|
// Preserve untouched lanes; paired float16_4 packs fill lower/upper halves separately.
|
||||||
// Shift is guaranteed to be 0 or 2 at this point due to the guard above.
|
// Guest element order is reversed in Rex's host storage, but each packed half keeps
|
||||||
if (mask == 2) {
|
// element 0 in the high halfword and element 1 in the low halfword.
|
||||||
// Optimization: Emit a single u64 write instead of two u32 writes.
|
|
||||||
// shift=0 → clears upper half u64[1]
|
|
||||||
// shift=2 → clears lower half u64[0]
|
|
||||||
size_t clearU64Start = (shift == 0) ? 1 : 0;
|
|
||||||
ctx.println("\t{}.u64[{}] = 0;", ctx.v(ctx.insn.operands[0]), clearU64Start);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Invariant: dstIdx must stay under 8 (valid u16 lanes are 0..7).
|
|
||||||
// Capping the shift to 0 or 2 in the guard check above ensures this is safe:
|
|
||||||
// it restricts the max dstIdx to (3 - 0) + (2 * 2) = 7.
|
|
||||||
// Do not widen the shift bounds without adjusting this logic.
|
|
||||||
for (size_t i = 0; i < 4; i++) {
|
for (size_t i = 0; i < 4; i++) {
|
||||||
size_t srcIdx = 3 - i;
|
size_t srcIdx = 3 - i;
|
||||||
size_t dstIdx = (3 - i) + (2 * shift);
|
size_t dstIdx = (3 - i) + (2 * shift);
|
||||||
|
|
||||||
ctx.println("\t{}.u32 = ({}.u32[{}]&0x7FFFFFFF);", ctx.temp(), ctx.v(ctx.insn.operands[1]),
|
ctx.println("\t{}.u16[{}] = rex::float_to_xenos_half({}.f32[{}], false, true);",
|
||||||
srcIdx);
|
ctx.v(ctx.insn.operands[0]), dstIdx, ctx.v(ctx.insn.operands[1]), srcIdx);
|
||||||
ctx.println(
|
|
||||||
"\t{0}.u8[0] = ({1}.f32 != {1}.f32) || ({1}.f32 > 65504.0f) ? 0xFF : "
|
|
||||||
"(({2}.u32[{3}]&0x7f800000)>>23);",
|
|
||||||
ctx.v_temp(), ctx.temp(), ctx.v(ctx.insn.operands[1]), srcIdx);
|
|
||||||
ctx.println("\t{}.u16 = {}.u8[0] != 0xFF ? (({}.u32[{}]&0x7FE000)>>13) : 0x0;", ctx.temp(),
|
|
||||||
ctx.v_temp(), ctx.v(ctx.insn.operands[1]), srcIdx);
|
|
||||||
ctx.println(
|
|
||||||
"\t{0}.u16[{1}] = {2}.u8[0] != 0xFF ? ({2}.u8[0] > 0x70 ? "
|
|
||||||
"((({2}.u8[0]-0x70)<<10)+{3}.u16) : (0x71-{2}.u8[0] > 31 ? 0x0 : "
|
|
||||||
"((0x400+{3}.u16)>>(0x71-{2}.u8[0])))) : 0x7FFF;",
|
|
||||||
ctx.v(ctx.insn.operands[0]), dstIdx, ctx.v_temp(), ctx.temp());
|
|
||||||
ctx.println("\t{}.u16[{}] |= (({}.u32[{}]&0x80000000)>>16);", ctx.v(ctx.insn.operands[0]),
|
|
||||||
dstIdx, ctx.v(ctx.insn.operands[1]), srcIdx);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -1429,7 +1551,8 @@ bool build_vupkd3d128(BuilderContext& ctx) {
|
||||||
for (size_t i = 0; i < 2; i++) {
|
for (size_t i = 0; i < 2; i++) {
|
||||||
ctx.println("\t{}.f32 = 3.0f;", ctx.temp());
|
ctx.println("\t{}.f32 = 3.0f;", ctx.temp());
|
||||||
ctx.println("\t{}.s32 += {}.s16[{}];", ctx.temp(), ctx.v(ctx.insn.operands[1]), 1 - i);
|
ctx.println("\t{}.s32 += {}.s16[{}];", ctx.temp(), ctx.v(ctx.insn.operands[1]), 1 - i);
|
||||||
ctx.println("\t{}.f32[{}] = {}.f32;", ctx.v_temp(), 3 - i, ctx.temp());
|
ctx.println("\t{}.u32[{}] = {}.u32 == 0x403F8000u ? 0x7FC00000u : {}.u32;",
|
||||||
|
ctx.v_temp(), 3 - i, ctx.temp(), ctx.temp());
|
||||||
}
|
}
|
||||||
ctx.println("\t{}.f32[1] = 0.0f;", ctx.v_temp());
|
ctx.println("\t{}.f32[1] = 0.0f;", ctx.v_temp());
|
||||||
ctx.println("\t{}.f32[0] = 1.0f;", ctx.v_temp());
|
ctx.println("\t{}.f32[0] = 1.0f;", ctx.v_temp());
|
||||||
|
|
@ -1451,11 +1574,13 @@ bool build_vupkd3d128(BuilderContext& ctx) {
|
||||||
ctx.println("\t{}.u32[3] = {}.u32[0];", vDst, ctx.v_temp());
|
ctx.println("\t{}.u32[3] = {}.u32[0];", vDst, ctx.v_temp());
|
||||||
// y (bits 10-19) - sign extend from 10 bits --> Guest element 1 (host u32[2])
|
// y (bits 10-19) - sign extend from 10 bits --> Guest element 1 (host u32[2])
|
||||||
ctx.println("\t{}.s32 = ({}.s32[0] << 12) >> 22;", ctx.temp(), vSrc);
|
ctx.println("\t{}.s32 = ({}.s32[0] << 12) >> 22;", ctx.temp(), vSrc);
|
||||||
ctx.println("\t{}.s32[0] = {}.s32 + 0x40400000;", ctx.v_temp(), ctx.temp());
|
ctx.println("\t{}.s32[0] = {}.s32 == -512 ? 0x7FC00000 : ({}.s32 + 0x40400000);",
|
||||||
|
ctx.v_temp(), ctx.temp(), ctx.temp());
|
||||||
ctx.println("\t{}.u32[2] = {}.u32[0];", vDst, ctx.v_temp());
|
ctx.println("\t{}.u32[2] = {}.u32[0];", vDst, ctx.v_temp());
|
||||||
// z (bits 20-29) - sign extend from 10 bits --> Guest element 2 (host u32[1])
|
// z (bits 20-29) - sign extend from 10 bits --> Guest element 2 (host u32[1])
|
||||||
ctx.println("\t{}.s32 = ({}.s32[0] << 2) >> 22;", ctx.temp(), vSrc);
|
ctx.println("\t{}.s32 = ({}.s32[0] << 2) >> 22;", ctx.temp(), vSrc);
|
||||||
ctx.println("\t{}.s32[0] = {}.s32 + 0x40400000;", ctx.v_temp(), ctx.temp());
|
ctx.println("\t{}.s32[0] = {}.s32 == -512 ? 0x7FC00000 : ({}.s32 + 0x40400000);",
|
||||||
|
ctx.v_temp(), ctx.temp(), ctx.temp());
|
||||||
ctx.println("\t{}.u32[1] = {}.u32[0];", vDst, ctx.v_temp());
|
ctx.println("\t{}.u32[1] = {}.u32[0];", vDst, ctx.v_temp());
|
||||||
// w (bits 30-31) - 2 bits, convert to 1.0+w form --> Guest element 3 (host u32[0])
|
// w (bits 30-31) - 2 bits, convert to 1.0+w form --> Guest element 3 (host u32[0])
|
||||||
ctx.println("\t{}.u32[0] = ({}.u32[0] >> 30) | 0x3F800000;", ctx.v_temp(), vSrc);
|
ctx.println("\t{}.u32[0] = ({}.u32[0] >> 30) | 0x3F800000;", ctx.v_temp(), vSrc);
|
||||||
|
|
@ -1472,16 +1597,8 @@ bool build_vupkd3d128(BuilderContext& ctx) {
|
||||||
for (size_t i = 0; i < 2; i++) {
|
for (size_t i = 0; i < 2; i++) {
|
||||||
size_t srcIdx = 1 - i; // Read from u16[1], u16[0]
|
size_t srcIdx = 1 - i; // Read from u16[1], u16[0]
|
||||||
size_t dstIdx = 3 - i; // Write to u32[3], u32[2]
|
size_t dstIdx = 3 - i; // Write to u32[3], u32[2]
|
||||||
ctx.println("\t{}.u32 = {}.u16[{}];", ctx.temp(), vSrc, srcIdx);
|
ctx.println("\t{}.f32[{}] = rex::xenos_half_to_float({}.u16[{}]);", vDst, dstIdx, vSrc,
|
||||||
// Extract sign, exponent, mantissa
|
srcIdx);
|
||||||
ctx.println(
|
|
||||||
"\t{}.u32[0] = (({}.u32 & 0x8000) << 16) | ((({}.u32 & 0x7C00) + 0x1C000) << 13) | "
|
|
||||||
"(({}.u32 & 0x03FF) << 13);",
|
|
||||||
ctx.v_temp(), ctx.temp(), ctx.temp(), ctx.temp());
|
|
||||||
// Handle zero/denorm case
|
|
||||||
ctx.println("\tif (({}.u32 & 0x7C00) == 0) {}.u32[0] = ({}.u32 & 0x8000) << 16;",
|
|
||||||
ctx.temp(), ctx.v_temp(), ctx.temp());
|
|
||||||
ctx.println("\t{}.u32[{}] = {}.u32[0];", vDst, dstIdx, ctx.v_temp());
|
|
||||||
}
|
}
|
||||||
ctx.println("\t{}.f32[1] = 0.0f;", vDst);
|
ctx.println("\t{}.f32[1] = 0.0f;", vDst);
|
||||||
ctx.println("\t{}.f32[0] = 1.0f;", vDst);
|
ctx.println("\t{}.f32[0] = 1.0f;", vDst);
|
||||||
|
|
@ -1497,7 +1614,8 @@ bool build_vupkd3d128(BuilderContext& ctx) {
|
||||||
size_t dstIdx = 3 - i; // Write to f32 indices 3, 2, 1, 0 (Guest elements 0, 1, 2, 3)
|
size_t dstIdx = 3 - i; // Write to f32 indices 3, 2, 1, 0 (Guest elements 0, 1, 2, 3)
|
||||||
ctx.println("\t{}.f32 = 3.0f;", ctx.temp());
|
ctx.println("\t{}.f32 = 3.0f;", ctx.temp());
|
||||||
ctx.println("\t{}.s32 += {}.s16[{}];", ctx.temp(), ctx.v(ctx.insn.operands[1]), srcIdx);
|
ctx.println("\t{}.s32 += {}.s16[{}];", ctx.temp(), ctx.v(ctx.insn.operands[1]), srcIdx);
|
||||||
ctx.println("\t{}.f32[{}] = {}.f32;", ctx.v(ctx.insn.operands[0]), dstIdx, ctx.temp());
|
ctx.println("\t{}.u32[{}] = {}.u32 == 0x403F8000u ? 0x7FC00000u : {}.u32;",
|
||||||
|
ctx.v(ctx.insn.operands[0]), dstIdx, ctx.temp(), ctx.temp());
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -1506,21 +1624,12 @@ bool build_vupkd3d128(BuilderContext& ctx) {
|
||||||
{
|
{
|
||||||
auto vSrc = ctx.v(ctx.insn.operands[1]);
|
auto vSrc = ctx.v(ctx.insn.operands[1]);
|
||||||
auto vDst = ctx.v(ctx.insn.operands[0]);
|
auto vDst = ctx.v(ctx.insn.operands[0]);
|
||||||
// Unpack 4 float16s from Guest elements 2-3 (host u16[0-3]) to elements 0-3
|
// Read the same halfword order produced by FLOAT16_4 packing.
|
||||||
// Guest element order is reversed in host arrays
|
|
||||||
for (size_t i = 0; i < 4; i++) {
|
for (size_t i = 0; i < 4; i++) {
|
||||||
size_t srcIdx = 3 - i; // Read from u16 indices 3, 2, 1, 0 (guest shorts 0, 1, 2, 3)
|
size_t srcIdx = 3 - i;
|
||||||
size_t dstIdx = 3 - i; // Write to u32 indices 3, 2, 1, 0 (Guest elements 0, 1, 2, 3)
|
size_t dstIdx = 3 - i; // Write to u32 indices 3, 2, 1, 0 (Guest elements 0, 1, 2, 3)
|
||||||
ctx.println("\t{}.u32 = {}.u16[{}];", ctx.temp(), vSrc, srcIdx);
|
ctx.println("\t{}.f32[{}] = rex::xenos_half_to_float({}.u16[{}]);", vDst, dstIdx, vSrc,
|
||||||
// Extract sign, exponent, mantissa and convert to float32
|
srcIdx);
|
||||||
ctx.println(
|
|
||||||
"\t{}.u32[0] = (({}.u32 & 0x8000) << 16) | ((({}.u32 & 0x7C00) + 0x1C000) << 13) | "
|
|
||||||
"(({}.u32 & 0x03FF) << 13);",
|
|
||||||
ctx.v_temp(), ctx.temp(), ctx.temp(), ctx.temp());
|
|
||||||
// Handle zero/denorm case
|
|
||||||
ctx.println("\tif (({}.u32 & 0x7C00) == 0) {}.u32[0] = ({}.u32 & 0x8000) << 16;",
|
|
||||||
ctx.temp(), ctx.v_temp(), ctx.temp());
|
|
||||||
ctx.println("\t{}.u32[{}] = {}.u32[0];", vDst, dstIdx, ctx.v_temp());
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -61,11 +61,11 @@ nlohmann::json buildTemplateData(const rex::codegen::CodegenContext& ctx,
|
||||||
funcName = crtIt->second;
|
funcName = crtIt->second;
|
||||||
isRexcrt = true;
|
isRexcrt = true;
|
||||||
} else if (fn->base() == ctx.analysisState().entryPoint) {
|
} else if (fn->base() == ctx.analysisState().entryPoint) {
|
||||||
funcName = "xstart";
|
funcName = cfg.symbolPrefix + "xstart";
|
||||||
} else if (!fn->name().empty()) {
|
} else if (!fn->name().empty()) {
|
||||||
funcName = fn->name();
|
funcName = fn->name();
|
||||||
} else {
|
} else {
|
||||||
funcName = fmt::format("sub_{:08X}", fn->base());
|
funcName = fmt::format("{}sub_{:08X}", cfg.symbolPrefix, fn->base());
|
||||||
}
|
}
|
||||||
|
|
||||||
functionsJson.push_back({
|
functionsJson.push_back({
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,9 @@ void ApplyToml(const toml::table& toml, RecompilerConfig& cfg, const std::string
|
||||||
if (auto v = toml["project_name"].value<std::string>()) {
|
if (auto v = toml["project_name"].value<std::string>()) {
|
||||||
MergeScalar(cfg.projectName, *v, "project_name");
|
MergeScalar(cfg.projectName, *v, "project_name");
|
||||||
}
|
}
|
||||||
|
if (auto v = toml["symbol_prefix"].value<std::string>()) {
|
||||||
|
MergeScalar(cfg.symbolPrefix, *v, "symbol_prefix");
|
||||||
|
}
|
||||||
if (auto v = toml["file_path"].value<std::string>()) {
|
if (auto v = toml["file_path"].value<std::string>()) {
|
||||||
MergeScalar(cfg.filePath, *v, "file_path");
|
MergeScalar(cfg.filePath, *v, "file_path");
|
||||||
}
|
}
|
||||||
|
|
@ -486,9 +489,6 @@ bool FinalizeConfig(RecompilerConfig& cfg) {
|
||||||
for (const auto& error : result.errors) {
|
for (const auto& error : result.errors) {
|
||||||
REXCODEGEN_ERROR("[config] {}", error);
|
REXCODEGEN_ERROR("[config] {}", error);
|
||||||
}
|
}
|
||||||
if (!result.valid) {
|
|
||||||
ok = false;
|
|
||||||
}
|
|
||||||
return ok;
|
return ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,18 @@ const char* AuthorityName(FunctionAuthority auth) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool HasEmittedBlockContaining(const FunctionNode* node, uint32_t addr) {
|
||||||
|
if (!node) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (const auto& block : node->blocks()) {
|
||||||
|
if (block.contains(addr)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
// FunctionNode
|
// FunctionNode
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
|
|
@ -359,11 +371,11 @@ std::string FunctionNode::emitCpp(const EmitContext& ctx) const {
|
||||||
|
|
||||||
std::string name;
|
std::string name;
|
||||||
if (base() == ctx.entryPoint) {
|
if (base() == ctx.entryPoint) {
|
||||||
name = "xstart";
|
name = fmt::format("{}xstart", ctx.config.symbolPrefix);
|
||||||
} else if (!name_.empty()) {
|
} else if (!name_.empty()) {
|
||||||
name = name_;
|
name = name_;
|
||||||
} else {
|
} else {
|
||||||
name = fmt::format("sub_{:08X}", base());
|
name = fmt::format("{}sub_{:08X}", ctx.config.symbolPrefix, base());
|
||||||
}
|
}
|
||||||
|
|
||||||
emit_println(out, "// STUB: Function at 0x{:08X} has no discovered code blocks", base());
|
emit_println(out, "// STUB: Function at 0x{:08X} has no discovered code blocks", base());
|
||||||
|
|
@ -471,11 +483,11 @@ std::string FunctionNode::emitCpp(const EmitContext& ctx) const {
|
||||||
// --- Function name ---
|
// --- Function name ---
|
||||||
std::string name;
|
std::string name;
|
||||||
if (base() == ctx.entryPoint) {
|
if (base() == ctx.entryPoint) {
|
||||||
name = "xstart";
|
name = fmt::format("{}xstart", ctx.config.symbolPrefix);
|
||||||
} else if (!name_.empty()) {
|
} else if (!name_.empty()) {
|
||||||
name = name_;
|
name = name_;
|
||||||
} else {
|
} else {
|
||||||
name = fmt::format("sub_{:08X}", base());
|
name = fmt::format("{}sub_{:08X}", ctx.config.symbolPrefix, base());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function signature with weak/alias pattern
|
// Function signature with weak/alias pattern
|
||||||
|
|
@ -564,8 +576,8 @@ std::string FunctionNode::emitCpp(const EmitContext& ctx) const {
|
||||||
for (auto label : activeJt->targets) {
|
for (auto label : activeJt->targets) {
|
||||||
labels.emplace(label);
|
labels.emplace(label);
|
||||||
}
|
}
|
||||||
REXCODEGEN_TRACE("Late-detected jump table at 0x{:08X} with {} entries", blockBase,
|
REXCODEGEN_INFO("Late-detected jump table at 0x{:08X} with {} entries", blockBase,
|
||||||
activeJt->targets.size());
|
activeJt->targets.size());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -617,7 +629,8 @@ std::string FunctionNode::emitCpp(const EmitContext& ctx) const {
|
||||||
for (auto it = sehInfo->scopes.rbegin(); it != sehInfo->scopes.rend(); ++it) {
|
for (auto it = sehInfo->scopes.rbegin(); it != sehInfo->scopes.rend(); ++it) {
|
||||||
const auto& scope = *it;
|
const auto& scope = *it;
|
||||||
if (scope.filter == 0 && scope.handler != 0) {
|
if (scope.filter == 0 && scope.handler != 0) {
|
||||||
emit_println(body, "\t\t\tsub_{:08X}(ctx, base); // __finally handler", scope.handler);
|
emit_println(body, "\t\t\t{}sub_{:08X}(ctx, base); // __finally handler",
|
||||||
|
ctx.config.symbolPrefix, scope.handler);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -754,6 +767,7 @@ FunctionNode* FunctionGraph::addFunction(uint32_t base, uint32_t size, FunctionA
|
||||||
// Create new node
|
// Create new node
|
||||||
auto node = std::make_unique<FunctionNode>(base, size, authority);
|
auto node = std::make_unique<FunctionNode>(base, size, authority);
|
||||||
FunctionNode* nodePtr = node.get();
|
FunctionNode* nodePtr = node.get();
|
||||||
|
nodePtr->setName(makeSymbolName(nodePtr->name(), authority));
|
||||||
functions_[base] = std::move(node);
|
functions_[base] = std::move(node);
|
||||||
functionsByBase_[base] = nodePtr;
|
functionsByBase_[base] = nodePtr;
|
||||||
|
|
||||||
|
|
@ -770,7 +784,7 @@ FunctionNode* FunctionGraph::addFunction(uint32_t base, uint32_t size, FunctionA
|
||||||
std::string_view name, bool hasXrefs) {
|
std::string_view name, bool hasXrefs) {
|
||||||
auto* node = addFunction(base, size, authority, hasXrefs);
|
auto* node = addFunction(base, size, authority, hasXrefs);
|
||||||
if (node && !name.empty()) {
|
if (node && !name.empty()) {
|
||||||
node->setName(std::string(name));
|
node->setName(makeSymbolName(name, authority));
|
||||||
}
|
}
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
@ -880,10 +894,24 @@ size_t FunctionGraph::sealedCount() const {
|
||||||
|
|
||||||
void FunctionGraph::setFunctionName(uint32_t entry, std::string name) {
|
void FunctionGraph::setFunctionName(uint32_t entry, std::string name) {
|
||||||
if (auto* node = getFunction(entry)) {
|
if (auto* node = getFunction(entry)) {
|
||||||
node->setName(std::move(name));
|
node->setName(makeSymbolName(name, node->authority()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FunctionGraph::setSymbolPrefix(std::string prefix) {
|
||||||
|
symbolPrefix_ = std::move(prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string FunctionGraph::makeSymbolName(std::string_view name, FunctionAuthority authority) const {
|
||||||
|
if (authority == FunctionAuthority::IMPORT || symbolPrefix_.empty()) {
|
||||||
|
return std::string(name);
|
||||||
|
}
|
||||||
|
if (name.starts_with(symbolPrefix_)) {
|
||||||
|
return std::string(name);
|
||||||
|
}
|
||||||
|
return fmt::format("{}{}", symbolPrefix_, name);
|
||||||
|
}
|
||||||
|
|
||||||
void FunctionGraph::setFunctionHasExceptionHandler(uint32_t entry, bool val) {
|
void FunctionGraph::setFunctionHasExceptionHandler(uint32_t entry, bool val) {
|
||||||
if (auto* node = getFunction(entry)) {
|
if (auto* node = getFunction(entry)) {
|
||||||
node->setHasExceptionHandler(val);
|
node->setHasExceptionHandler(val);
|
||||||
|
|
@ -932,6 +960,18 @@ void FunctionGraph::addUnresolvedJumpToFunction(uint32_t entry, uint32_t site, u
|
||||||
if (!node)
|
if (!node)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// Configured chunks are address-taken entry points inside a parent function.
|
||||||
|
// They are local labels only when the destination block is actually emitted
|
||||||
|
// in this function body; otherwise the generated C++ must tail-call the
|
||||||
|
// separate landing function instead of emitting an invalid cross-function goto.
|
||||||
|
if (!isCall && chunkParent(target) != 0 && HasEmittedBlockContaining(node, target)) {
|
||||||
|
node->addLabel(target);
|
||||||
|
REXCODEGEN_TRACE(
|
||||||
|
"FunctionGraph: resolved 0x{:08X}->0x{:08X} as emitted chunk label in 0x{:08X}",
|
||||||
|
site, target, entry);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Try immediate resolution against existing functions/imports
|
// Try immediate resolution against existing functions/imports
|
||||||
if (auto* targetFn = getFunction(target)) {
|
if (auto* targetFn = getFunction(target)) {
|
||||||
// Target is a known function - resolve as call or tail call
|
// Target is a known function - resolve as call or tail call
|
||||||
|
|
@ -983,9 +1023,24 @@ size_t FunctionGraph::tryResolveFunction(uint32_t entry) {
|
||||||
jumps.size());
|
jumps.size());
|
||||||
|
|
||||||
for (const auto& jump : jumps) {
|
for (const auto& jump : jumps) {
|
||||||
// Try internal label first
|
// Configured chunks are internal labels only if their destination block is
|
||||||
if (node->tryResolveAsInternalLabel(jump.target)) {
|
// emitted in this function body. Other chunk entries are separate callable
|
||||||
REXCODEGEN_TRACE(" 0x{:08X}->0x{:08X}: resolved as internal label", jump.site, jump.target);
|
// landings and must stay tail-call edges.
|
||||||
|
if (!jump.isCall && chunkParent(jump.target) != 0 &&
|
||||||
|
HasEmittedBlockContaining(node, jump.target)) {
|
||||||
|
node->addLabel(jump.target);
|
||||||
|
node->removeUnresolvedJump(jump.site);
|
||||||
|
resolved++;
|
||||||
|
REXCODEGEN_TRACE(" 0x{:08X}->0x{:08X}: resolved as emitted chunk label", jump.site,
|
||||||
|
jump.target);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A branch to this function's own entry is a loop. Other known entry points
|
||||||
|
// inside an overlapping PDATA range must stay function/tail-call edges.
|
||||||
|
if (!jump.isCall && jump.target == entry && node->tryResolveAsInternalLabel(jump.target)) {
|
||||||
|
REXCODEGEN_TRACE(" 0x{:08X}->0x{:08X}: resolved as internal entry label", jump.site,
|
||||||
|
jump.target);
|
||||||
node->removeUnresolvedJump(jump.site);
|
node->removeUnresolvedJump(jump.site);
|
||||||
resolved++;
|
resolved++;
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -1005,6 +1060,14 @@ size_t FunctionGraph::tryResolveFunction(uint32_t entry) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Try internal label after entry-point resolution.
|
||||||
|
if (node->tryResolveAsInternalLabel(jump.target)) {
|
||||||
|
REXCODEGEN_TRACE(" 0x{:08X}->0x{:08X}: resolved as internal label", jump.site, jump.target);
|
||||||
|
node->removeUnresolvedJump(jump.site);
|
||||||
|
resolved++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Try import
|
// Try import
|
||||||
if (isImport(jump.target)) {
|
if (isImport(jump.target)) {
|
||||||
auto* importNode = getFunction(jump.target);
|
auto* importNode = getFunction(jump.target);
|
||||||
|
|
@ -1056,10 +1119,10 @@ size_t FunctionGraph::sealAllReady() {
|
||||||
sealed++;
|
sealed++;
|
||||||
} else {
|
} else {
|
||||||
couldNotSeal++;
|
couldNotSeal++;
|
||||||
REXCODEGEN_DEBUG("FunctionGraph::sealAllReady: 0x{:08X} ({}) cannot seal ({} unresolved)",
|
REXCODEGEN_WARN("FunctionGraph::sealAllReady: 0x{:08X} ({}) cannot seal ({} unresolved)",
|
||||||
base, node->name(), node->unresolvedJumps().size());
|
base, node->name(), node->unresolvedJumps().size());
|
||||||
for (const auto& jump : node->unresolvedJumps()) {
|
for (const auto& jump : node->unresolvedJumps()) {
|
||||||
REXCODEGEN_DEBUG(" 0x{:08X} -> 0x{:08X}", jump.site, jump.target);
|
REXCODEGEN_WARN(" 0x{:08X} -> 0x{:08X}", jump.site, jump.target);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1102,16 +1165,25 @@ void FunctionGraph::sealAll() {
|
||||||
throw std::runtime_error(msg);
|
throw std::runtime_error(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
REXCODEGEN_TRACE("FunctionGraph::sealAll: all {} functions sealed", functions_.size());
|
REXCODEGEN_INFO("FunctionGraph::sealAll: all {} functions sealed", functions_.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
// Vacancy Checking
|
// Vacancy Checking
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
|
|
||||||
void FunctionGraph::registerChunk(uint32_t base, uint32_t size) {
|
void FunctionGraph::registerChunk(uint32_t base, uint32_t size, uint32_t parent) {
|
||||||
chunks_.emplace_back(base, size);
|
chunks_.emplace_back(base, size);
|
||||||
REXCODEGEN_TRACE("FunctionGraph: registered chunk 0x{:08X}-0x{:08X}", base, base + size);
|
if (parent != 0) {
|
||||||
|
chunkParents_[base] = parent;
|
||||||
|
}
|
||||||
|
REXCODEGEN_TRACE("FunctionGraph: registered chunk 0x{:08X}-0x{:08X} parent=0x{:08X}", base,
|
||||||
|
base + size, parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t FunctionGraph::chunkParent(uint32_t base) const {
|
||||||
|
auto it = chunkParents_.find(base);
|
||||||
|
return it != chunkParents_.end() ? it->second : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FunctionGraph::isVacant(uint32_t fromAddr, uint32_t targetAddr) const {
|
bool FunctionGraph::isVacant(uint32_t fromAddr, uint32_t targetAddr) const {
|
||||||
|
|
@ -1173,41 +1245,74 @@ bool FunctionGraph::isMergeableEntryPoint(uint32_t addr) const {
|
||||||
|
|
||||||
TargetKind FunctionGraph::classifyTarget(uint32_t target, uint32_t callerAddr,
|
TargetKind FunctionGraph::classifyTarget(uint32_t target, uint32_t callerAddr,
|
||||||
bool isCallInstruction) const {
|
bool isCallInstruction) const {
|
||||||
// Find the caller's function
|
// Most builder callers pass the current function base as callerAddr. Prefer
|
||||||
const FunctionNode* callerFn = getFunctionContaining(callerAddr);
|
// the exact node so overlapping PDATA ranges do not make branches look like
|
||||||
|
// self-loops in an enclosing function.
|
||||||
|
const FunctionNode* callerFn = getFunction(callerAddr);
|
||||||
|
if (!callerFn) {
|
||||||
|
callerFn = getFunctionContaining(callerAddr);
|
||||||
|
}
|
||||||
|
|
||||||
// Case 1: Target is an import - always a call/tail-call
|
// Case 1: Target is an import - always a call/tail-call
|
||||||
if (isImport(target)) {
|
if (isImport(target)) {
|
||||||
return TargetKind::Import;
|
return TargetKind::Import;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Case 2: Target is the caller's own entry point
|
// Case 2: Target is a configured chunk whose block is emitted in this
|
||||||
if (callerFn && target == callerFn->base()) {
|
// function body. If the chunk is only present as a separate landing function,
|
||||||
|
// it must be treated as a tail call so generated C++ never jumps to a label
|
||||||
|
// declared in another function.
|
||||||
|
if (callerFn && !isCallInstruction) {
|
||||||
|
if (chunkParent(target) != 0 && HasEmittedBlockContaining(callerFn, target)) {
|
||||||
|
return TargetKind::InternalLabel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Case 3: Target is the emitting function's own entry point. Builder callers
|
||||||
|
// pass the current function base as callerAddr, which is more reliable than
|
||||||
|
// containing-range lookup when PDATA functions overlap.
|
||||||
|
if (target == callerAddr) {
|
||||||
// bl to own base = recursive call (Function)
|
// bl to own base = recursive call (Function)
|
||||||
// b to own base = loop back to start (InternalLabel)
|
// b to own base = loop back to start (InternalLabel)
|
||||||
return isCallInstruction ? TargetKind::Function : TargetKind::InternalLabel;
|
return isCallInstruction ? TargetKind::Function : TargetKind::InternalLabel;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Case 3: Target is a DIFFERENT function's entry point - this is a call/tail-call
|
// Case 4: Target is a DIFFERENT function's entry point - this is a call/tail-call
|
||||||
// This handles cases where a small thunk function branches to another function
|
// This handles cases where a small thunk function branches to another function
|
||||||
// whose entry point happens to fall within the thunk's address range
|
// whose entry point happens to fall within the thunk's address range
|
||||||
if (isEntryPoint(target)) {
|
if (isEntryPoint(target)) {
|
||||||
return TargetKind::Function;
|
return TargetKind::Function;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Case 4: Target is inside caller's function -> InternalLabel
|
// Case 5: Target is inside caller's function -> InternalLabel
|
||||||
// For bl, this would be a rare PIC code pattern
|
// For bl, this would be a rare PIC code pattern
|
||||||
if (callerFn && callerFn->containsAddress(target)) {
|
if (callerFn && callerFn->containsAddress(target)) {
|
||||||
return TargetKind::InternalLabel;
|
return TargetKind::InternalLabel;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Case 5: Unknown target
|
// Case 6: Unknown target
|
||||||
return TargetKind::Unknown;
|
return TargetKind::Unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
void FunctionGraph::notifyFunctionAdded(FunctionNode* newFunction) {
|
void FunctionGraph::notifyFunctionAdded(FunctionNode* newFunction) {
|
||||||
for (auto& [base, node] : functions_) {
|
for (auto& [base, node] : functions_) {
|
||||||
if (node.get() != newFunction && node->isPending()) {
|
if (node.get() != newFunction && node->isPending()) {
|
||||||
|
auto jumps = node->unresolvedJumps();
|
||||||
|
for (const auto& jump : jumps) {
|
||||||
|
if (jump.target != newFunction->base()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!jump.isCall && chunkParent(jump.target) != 0 &&
|
||||||
|
HasEmittedBlockContaining(node.get(), jump.target)) {
|
||||||
|
node->addLabel(jump.target);
|
||||||
|
node->removeUnresolvedJump(jump.site);
|
||||||
|
REXCODEGEN_TRACE(
|
||||||
|
"FunctionGraph: resolved 0x{:08X}->0x{:08X} as emitted chunk label in 0x{:08X}",
|
||||||
|
jump.site, jump.target, node->base());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
node->tryResolveAgainst(newFunction);
|
node->tryResolveAgainst(newFunction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1299,9 +1299,13 @@ BoundsInfo scanForBounds(DecodedBinary& decoded, uint32_t bctrAddr, const CodeRe
|
||||||
if (!insn)
|
if (!insn)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
// Stop at unconditional terminators - scanning past basic block boundaries
|
// EA commonly emits switch bounds as:
|
||||||
// risks finding unrelated comparisons on the index register
|
// cmplwi idx,max; ble table_path; b default_path; table_path: ... bctr
|
||||||
if (isTerminator(*insn) && !isConditional(*insn))
|
// Keep scanning past that unconditional default branch so the compare is
|
||||||
|
// visible to the jump-table reader.
|
||||||
|
if (isTerminator(*insn) && !isConditional(*insn) &&
|
||||||
|
insn->opcode != rex::codegen::ppc::Opcode::b &&
|
||||||
|
insn->opcode != rex::codegen::ppc::Opcode::ba)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
using namespace rex::codegen::ppc;
|
using namespace rex::codegen::ppc;
|
||||||
|
|
|
||||||
|
|
@ -462,6 +462,15 @@ static const std::unordered_map<int, Builder>& GetDispatchTable() {
|
||||||
{PPC_INST_VADDUWM, build_vadduwm},
|
{PPC_INST_VADDUWM, build_vadduwm},
|
||||||
{PPC_INST_VADDUWS, build_vadduws},
|
{PPC_INST_VADDUWS, build_vadduws},
|
||||||
{PPC_INST_VADDUHS, build_vadduhs},
|
{PPC_INST_VADDUHS, build_vadduhs},
|
||||||
|
{PPC_INST_VMHADDSHS, build_vmhaddshs},
|
||||||
|
{PPC_INST_VMHRADDSHS, build_vmhraddshs},
|
||||||
|
{PPC_INST_VMLADDUHM, build_vmladduhm},
|
||||||
|
{PPC_INST_VMSUMUBM, build_vmsumubm},
|
||||||
|
{PPC_INST_VMSUMMBM, build_vmsummbm},
|
||||||
|
{PPC_INST_VMSUMUHM, build_vmsumuhm},
|
||||||
|
{PPC_INST_VMSUMUHS, build_vmsumuhs},
|
||||||
|
{PPC_INST_VMSUMSHM, build_vmsumshm},
|
||||||
|
{PPC_INST_VMSUMSHS, build_vmsumshs},
|
||||||
{PPC_INST_VSUBSBS, build_vsubsbs},
|
{PPC_INST_VSUBSBS, build_vsubsbs},
|
||||||
{PPC_INST_VSUBSWS, build_vsubsws},
|
{PPC_INST_VSUBSWS, build_vsubsws},
|
||||||
{PPC_INST_VSUBUBM, build_vsububm},
|
{PPC_INST_VSUBUBM, build_vsububm},
|
||||||
|
|
|
||||||
|
|
@ -81,6 +81,22 @@ void discoverFunction(CodegenContext& ctx, uint32_t funcAddr,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Address-taken chunks are alternate entries into a parent function. When
|
||||||
|
// emitted as standalone dispatcher targets, they still need to run forward
|
||||||
|
// through the parent's shared tail/epilogue instead of stopping at the small
|
||||||
|
// chunk extent recorded in config.
|
||||||
|
if (uint32_t parentAddr = graph.chunkParent(funcAddr); parentAddr != 0) {
|
||||||
|
if (auto* parentNode = graph.getFunction(parentAddr)) {
|
||||||
|
const uint32_t parentEnd = parentNode->base() + parentNode->size();
|
||||||
|
if (parentEnd > funcAddr && parentEnd - funcAddr > pdataSize) {
|
||||||
|
pdataSize = parentEnd - funcAddr;
|
||||||
|
REXCODEGEN_TRACE(
|
||||||
|
"Analyze: 0x{:08X} is chunk of 0x{:08X}, using parent-derived size={}",
|
||||||
|
funcAddr, parentAddr, pdataSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Find the code region containing this function
|
// Find the code region containing this function
|
||||||
const CodeRegion* region = nullptr;
|
const CodeRegion* region = nullptr;
|
||||||
for (const auto& r : ctx.scan.codeRegions) {
|
for (const auto& r : ctx.scan.codeRegions) {
|
||||||
|
|
@ -94,8 +110,29 @@ void discoverFunction(CodegenContext& ctx, uint32_t funcAddr,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pass pdataSize so forward branches within function extent are correctly identified
|
// Pass pdataSize so forward branches within function extent are correctly identified.
|
||||||
auto result = discoverBlocks(decoded, funcAddr, *region, knownFunctions, pdataSize);
|
// Configured chunks are address-taken entries inside their parent, not hard local
|
||||||
|
// branch boundaries for that parent.
|
||||||
|
auto effectiveKnownFunctions = knownFunctions;
|
||||||
|
const uint32_t chunkParent = graph.chunkParent(funcAddr);
|
||||||
|
const uint32_t chunkLookupParent = chunkParent != 0 ? chunkParent : funcAddr;
|
||||||
|
auto chunkIt = ctx.analysisState().chunksByParent.find(chunkLookupParent);
|
||||||
|
if (chunkIt != ctx.analysisState().chunksByParent.end()) {
|
||||||
|
for (uint32_t chunkAddr : chunkIt->second) {
|
||||||
|
effectiveKnownFunctions.erase(chunkAddr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (pdataSize != 0) {
|
||||||
|
const uint32_t funcEnd = funcAddr + pdataSize;
|
||||||
|
for (const auto& [_, chunkAddrs] : ctx.analysisState().chunksByParent) {
|
||||||
|
for (uint32_t chunkAddr : chunkAddrs) {
|
||||||
|
if (chunkAddr >= funcAddr && chunkAddr < funcEnd) {
|
||||||
|
effectiveKnownFunctions.erase(chunkAddr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
auto result = discoverBlocks(decoded, funcAddr, *region, effectiveKnownFunctions, pdataSize);
|
||||||
|
|
||||||
if (result.blocks.empty()) {
|
if (result.blocks.empty()) {
|
||||||
REXCODEGEN_WARN("Analyze: no blocks found for function 0x{:08X}", funcAddr);
|
REXCODEGEN_WARN("Analyze: no blocks found for function 0x{:08X}", funcAddr);
|
||||||
|
|
@ -190,7 +227,7 @@ void discoverFunction(CodegenContext& ctx, uint32_t funcAddr,
|
||||||
}
|
}
|
||||||
|
|
||||||
void discoverAllFunctions(CodegenContext& ctx) {
|
void discoverAllFunctions(CodegenContext& ctx) {
|
||||||
REXCODEGEN_TRACE("Analyze: starting iterative discovery...");
|
REXCODEGEN_INFO("Analyze: starting iterative discovery...");
|
||||||
|
|
||||||
auto& graph = ctx.graph;
|
auto& graph = ctx.graph;
|
||||||
auto& binary = ctx.binary();
|
auto& binary = ctx.binary();
|
||||||
|
|
@ -218,7 +255,7 @@ void discoverAllFunctions(CodegenContext& ctx) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
REXCODEGEN_TRACE("Analyze: {} functions after call graph expansion", graph.functionCount());
|
REXCODEGEN_INFO("Analyze: {} functions after call graph expansion", graph.functionCount());
|
||||||
|
|
||||||
// VTable scanning
|
// VTable scanning
|
||||||
{
|
{
|
||||||
|
|
@ -227,6 +264,7 @@ void discoverAllFunctions(CodegenContext& ctx) {
|
||||||
|
|
||||||
size_t newFunctions = 0;
|
size_t newFunctions = 0;
|
||||||
|
|
||||||
|
size_t vtableLandings = 0;
|
||||||
for (const auto& vt : vtables) {
|
for (const auto& vt : vtables) {
|
||||||
for (size_t i = 0; i < vt.slots.size(); i++) {
|
for (size_t i = 0; i < vt.slots.size(); i++) {
|
||||||
uint32_t funcAddr = vt.slots[i];
|
uint32_t funcAddr = vt.slots[i];
|
||||||
|
|
@ -236,13 +274,36 @@ void discoverAllFunctions(CodegenContext& ctx) {
|
||||||
if (binary.isInImportExportRange(funcAddr))
|
if (binary.isInImportExportRange(funcAddr))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
// Multi-entry-point handling: if the vtable slot falls *inside* an
|
||||||
|
// already-discovered function's body (not at its entry), promote the
|
||||||
|
// slot to a chunk (mid-function landing) of that parent. This mirrors
|
||||||
|
// what skate3_observed_runtime_landings.toml does for runtime-observed
|
||||||
|
// mid-function entries, but driven by static vtable analysis.
|
||||||
|
//
|
||||||
|
// Without this, addresses like 0x82EBAE4C (referenced by ~37 vtables
|
||||||
|
// but lying inside sub_82EBAE34) never enter the function table. xenia
|
||||||
|
// hybrid then can't route calls there and falls back to JIT.
|
||||||
|
if (FunctionNode* parent = graph.getFunctionContaining(funcAddr);
|
||||||
|
parent != nullptr && parent->base() != funcAddr) {
|
||||||
|
graph.registerChunk(funcAddr, 4, parent->base());
|
||||||
|
graph.addFunction(funcAddr, 4, FunctionAuthority::VTABLE, true);
|
||||||
|
vtableLandings++;
|
||||||
|
newFunctions++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
graph.addFunction(funcAddr, 4, FunctionAuthority::VTABLE, true);
|
graph.addFunction(funcAddr, 4, FunctionAuthority::VTABLE, true);
|
||||||
newFunctions++;
|
newFunctions++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (vtableLandings > 0) {
|
||||||
|
REXCODEGEN_INFO("Analyze: VTable scan promoted {} slots to chunks of "
|
||||||
|
"their containing functions (multi-entry-point)",
|
||||||
|
vtableLandings);
|
||||||
|
}
|
||||||
|
|
||||||
REXCODEGEN_TRACE("Analyze: VTable scan found {} vtables, {} new functions", vtables.size(),
|
REXCODEGEN_INFO("Analyze: VTable scan found {} vtables, {} new functions", vtables.size(),
|
||||||
newFunctions);
|
newFunctions);
|
||||||
|
|
||||||
// Continue discovery for vtable functions
|
// Continue discovery for vtable functions
|
||||||
if (newFunctions > 0) {
|
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
|
} // anonymous namespace
|
||||||
|
|
|
||||||
|
|
@ -426,6 +426,8 @@ VoidResult registerEntryPoints(CodegenContext& ctx) {
|
||||||
auto& binary = ctx.binary();
|
auto& binary = ctx.binary();
|
||||||
auto& ehDiscoveredFuncs = state.ehDiscoveredFuncs;
|
auto& ehDiscoveredFuncs = state.ehDiscoveredFuncs;
|
||||||
|
|
||||||
|
graph.setSymbolPrefix(config.symbolPrefix);
|
||||||
|
|
||||||
// Merge user hints into analysis state
|
// Merge user hints into analysis state
|
||||||
for (const auto& [addr, size] : config.invalidInstructionHints) {
|
for (const auto& [addr, size] : config.invalidInstructionHints) {
|
||||||
state.invalidInstructions[addr] = size;
|
state.invalidInstructions[addr] = size;
|
||||||
|
|
@ -555,7 +557,7 @@ VoidResult registerEntryPoints(CodegenContext& ctx) {
|
||||||
configFuncs++;
|
configFuncs++;
|
||||||
|
|
||||||
if (cfg.isChunk()) {
|
if (cfg.isChunk()) {
|
||||||
graph.registerChunk(address, size);
|
graph.registerChunk(address, size, cfg.parent);
|
||||||
configChunks++;
|
configChunks++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ constexpr u32 extract_bits(u32 value, u32 start, u32 count) {
|
||||||
// Opcode information table
|
// Opcode information table
|
||||||
//=============================================================================
|
//=============================================================================
|
||||||
|
|
||||||
static const std::array<OpcodeInfo, 320> g_opcode_table = {{
|
static const std::array<OpcodeInfo, 330> g_opcode_table = {{
|
||||||
// Primary opcode 16: bcx (conditional branch) - all variants
|
// Primary opcode 16: bcx (conditional branch) - all variants
|
||||||
{Opcode::bc, InstrFormat::kB, OpcodeGroup::kBranch, "bc", 16, 0, false},
|
{Opcode::bc, InstrFormat::kB, OpcodeGroup::kBranch, "bc", 16, 0, false},
|
||||||
{Opcode::bca, InstrFormat::kB, OpcodeGroup::kBranch, "bca", 16, 0, false},
|
{Opcode::bca, InstrFormat::kB, OpcodeGroup::kBranch, "bca", 16, 0, false},
|
||||||
|
|
@ -261,8 +261,8 @@ static const std::array<OpcodeInfo, 320> g_opcode_table = {{
|
||||||
//=========================================================================
|
//=========================================================================
|
||||||
{Opcode::vaddfp, InstrFormat::kX, OpcodeGroup::kVector, "vaddfp", 4, 10, true},
|
{Opcode::vaddfp, InstrFormat::kX, OpcodeGroup::kVector, "vaddfp", 4, 10, true},
|
||||||
{Opcode::vsubfp, InstrFormat::kX, OpcodeGroup::kVector, "vsubfp", 4, 74, true},
|
{Opcode::vsubfp, InstrFormat::kX, OpcodeGroup::kVector, "vsubfp", 4, 74, true},
|
||||||
{Opcode::vmaddfp, InstrFormat::kX, OpcodeGroup::kVector, "vmaddfp", 4, 32, true},
|
{Opcode::vmaddfp, InstrFormat::kVA, OpcodeGroup::kVector, "vmaddfp", 4, 46, true},
|
||||||
{Opcode::vnmsubfp, InstrFormat::kX, OpcodeGroup::kVector, "vnmsubfp", 4, 33, true},
|
{Opcode::vnmsubfp, InstrFormat::kVA, OpcodeGroup::kVector, "vnmsubfp", 4, 47, true},
|
||||||
{Opcode::vmaxfp, InstrFormat::kX, OpcodeGroup::kVector, "vmaxfp", 4, 1034, true},
|
{Opcode::vmaxfp, InstrFormat::kX, OpcodeGroup::kVector, "vmaxfp", 4, 1034, true},
|
||||||
{Opcode::vminfp, InstrFormat::kX, OpcodeGroup::kVector, "vminfp", 4, 1098, true},
|
{Opcode::vminfp, InstrFormat::kX, OpcodeGroup::kVector, "vminfp", 4, 1098, true},
|
||||||
{Opcode::vrsqrtefp, InstrFormat::kX, OpcodeGroup::kVector, "vrsqrtefp", 4, 330, true},
|
{Opcode::vrsqrtefp, InstrFormat::kX, OpcodeGroup::kVector, "vrsqrtefp", 4, 330, true},
|
||||||
|
|
@ -279,6 +279,15 @@ static const std::array<OpcodeInfo, 320> g_opcode_table = {{
|
||||||
{Opcode::vsububm, InstrFormat::kX, OpcodeGroup::kVector, "vsububm", 4, 1024, true},
|
{Opcode::vsububm, InstrFormat::kX, OpcodeGroup::kVector, "vsububm", 4, 1024, true},
|
||||||
{Opcode::vsubuhm, InstrFormat::kX, OpcodeGroup::kVector, "vsubuhm", 4, 1088, true},
|
{Opcode::vsubuhm, InstrFormat::kX, OpcodeGroup::kVector, "vsubuhm", 4, 1088, true},
|
||||||
{Opcode::vsubuwm, InstrFormat::kX, OpcodeGroup::kVector, "vsubuwm", 4, 1152, true},
|
{Opcode::vsubuwm, InstrFormat::kX, OpcodeGroup::kVector, "vsubuwm", 4, 1152, true},
|
||||||
|
{Opcode::vmhaddshs, InstrFormat::kVA, OpcodeGroup::kVector, "vmhaddshs", 4, 32, true},
|
||||||
|
{Opcode::vmhraddshs, InstrFormat::kVA, OpcodeGroup::kVector, "vmhraddshs", 4, 33, true},
|
||||||
|
{Opcode::vmladduhm, InstrFormat::kVA, OpcodeGroup::kVector, "vmladduhm", 4, 34, true},
|
||||||
|
{Opcode::vmsumubm, InstrFormat::kVA, OpcodeGroup::kVector, "vmsumubm", 4, 36, true},
|
||||||
|
{Opcode::vmsummbm, InstrFormat::kVA, OpcodeGroup::kVector, "vmsummbm", 4, 37, true},
|
||||||
|
{Opcode::vmsumuhm, InstrFormat::kVA, OpcodeGroup::kVector, "vmsumuhm", 4, 38, true},
|
||||||
|
{Opcode::vmsumuhs, InstrFormat::kVA, OpcodeGroup::kVector, "vmsumuhs", 4, 39, true},
|
||||||
|
{Opcode::vmsumshm, InstrFormat::kVA, OpcodeGroup::kVector, "vmsumshm", 4, 40, true},
|
||||||
|
{Opcode::vmsumshs, InstrFormat::kVA, OpcodeGroup::kVector, "vmsumshs", 4, 41, true},
|
||||||
{Opcode::vmuloub, InstrFormat::kX, OpcodeGroup::kVector, "vmuloub", 4, 8, true},
|
{Opcode::vmuloub, InstrFormat::kX, OpcodeGroup::kVector, "vmuloub", 4, 8, true},
|
||||||
{Opcode::vmulouh, InstrFormat::kX, OpcodeGroup::kVector, "vmulouh", 4, 72, true},
|
{Opcode::vmulouh, InstrFormat::kX, OpcodeGroup::kVector, "vmulouh", 4, 72, true},
|
||||||
{Opcode::vmuleub, InstrFormat::kX, OpcodeGroup::kVector, "vmuleub", 4, 264, true},
|
{Opcode::vmuleub, InstrFormat::kX, OpcodeGroup::kVector, "vmuleub", 4, 264, true},
|
||||||
|
|
@ -295,7 +304,7 @@ static const std::array<OpcodeInfo, 320> g_opcode_table = {{
|
||||||
{Opcode::vor, InstrFormat::kX, OpcodeGroup::kVector, "vor", 4, 1156, true},
|
{Opcode::vor, InstrFormat::kX, OpcodeGroup::kVector, "vor", 4, 1156, true},
|
||||||
{Opcode::vxor, InstrFormat::kX, OpcodeGroup::kVector, "vxor", 4, 1220, true},
|
{Opcode::vxor, InstrFormat::kX, OpcodeGroup::kVector, "vxor", 4, 1220, true},
|
||||||
{Opcode::vnor, InstrFormat::kX, OpcodeGroup::kVector, "vnor", 4, 1284, true},
|
{Opcode::vnor, InstrFormat::kX, OpcodeGroup::kVector, "vnor", 4, 1284, true},
|
||||||
{Opcode::vsel, InstrFormat::kX, OpcodeGroup::kVector, "vsel", 4, 42, true},
|
{Opcode::vsel, InstrFormat::kVA, OpcodeGroup::kVector, "vsel", 4, 42, true},
|
||||||
|
|
||||||
//=========================================================================
|
//=========================================================================
|
||||||
// VMX Compare (Floating-Point)
|
// VMX Compare (Floating-Point)
|
||||||
|
|
@ -324,7 +333,7 @@ static const std::array<OpcodeInfo, 320> g_opcode_table = {{
|
||||||
//=========================================================================
|
//=========================================================================
|
||||||
// VMX Permute/Merge
|
// VMX Permute/Merge
|
||||||
//=========================================================================
|
//=========================================================================
|
||||||
{Opcode::vperm, InstrFormat::kX, OpcodeGroup::kVector, "vperm", 4, 43, true},
|
{Opcode::vperm, InstrFormat::kVA, OpcodeGroup::kVector, "vperm", 4, 43, true},
|
||||||
{Opcode::vmrghb, InstrFormat::kX, OpcodeGroup::kVector, "vmrghb", 4, 12, true},
|
{Opcode::vmrghb, InstrFormat::kX, OpcodeGroup::kVector, "vmrghb", 4, 12, true},
|
||||||
{Opcode::vmrghh, InstrFormat::kX, OpcodeGroup::kVector, "vmrghh", 4, 76, true},
|
{Opcode::vmrghh, InstrFormat::kX, OpcodeGroup::kVector, "vmrghh", 4, 76, true},
|
||||||
{Opcode::vmrghw, InstrFormat::kX, OpcodeGroup::kVector, "vmrghw", 4, 140, true},
|
{Opcode::vmrghw, InstrFormat::kX, OpcodeGroup::kVector, "vmrghw", 4, 140, true},
|
||||||
|
|
@ -377,6 +386,7 @@ static const std::array<OpcodeInfo, 320> g_opcode_table = {{
|
||||||
{Opcode::vsr, InstrFormat::kX, OpcodeGroup::kVector, "vsr", 4, 708, true},
|
{Opcode::vsr, InstrFormat::kX, OpcodeGroup::kVector, "vsr", 4, 708, true},
|
||||||
{Opcode::vslo, InstrFormat::kX, OpcodeGroup::kVector, "vslo", 4, 1036, true},
|
{Opcode::vslo, InstrFormat::kX, OpcodeGroup::kVector, "vslo", 4, 1036, true},
|
||||||
{Opcode::vsro, InstrFormat::kX, OpcodeGroup::kVector, "vsro", 4, 1100, true},
|
{Opcode::vsro, InstrFormat::kX, OpcodeGroup::kVector, "vsro", 4, 1100, true},
|
||||||
|
{Opcode::vsldoi, InstrFormat::kVA, OpcodeGroup::kVector, "vsldoi", 4, 44, true},
|
||||||
|
|
||||||
//=========================================================================
|
//=========================================================================
|
||||||
// VMX Conversion
|
// VMX Conversion
|
||||||
|
|
@ -802,13 +812,33 @@ Opcode lookup_opcode(u32 code) {
|
||||||
extended = extract_bits(code, 26, 6);
|
extended = extract_bits(code, 26, 6);
|
||||||
switch (extended) {
|
switch (extended) {
|
||||||
case 32:
|
case 32:
|
||||||
return Opcode::vmaddfp; // vmaddfp vD, vA, vC, vB
|
return Opcode::vmhaddshs; // vmhaddshs vD, vA, vB, vC
|
||||||
case 33:
|
case 33:
|
||||||
return Opcode::vnmsubfp; // vnmsubfp vD, vA, vC, vB
|
return Opcode::vmhraddshs; // vmhraddshs vD, vA, vB, vC
|
||||||
|
case 34:
|
||||||
|
return Opcode::vmladduhm; // vmladduhm vD, vA, vB, vC
|
||||||
|
case 36:
|
||||||
|
return Opcode::vmsumubm; // vmsumubm vD, vA, vB, vC
|
||||||
|
case 37:
|
||||||
|
return Opcode::vmsummbm; // vmsummbm vD, vA, vB, vC
|
||||||
|
case 38:
|
||||||
|
return Opcode::vmsumuhm; // vmsumuhm vD, vA, vB, vC
|
||||||
|
case 39:
|
||||||
|
return Opcode::vmsumuhs; // vmsumuhs vD, vA, vB, vC
|
||||||
|
case 40:
|
||||||
|
return Opcode::vmsumshm; // vmsumshm vD, vA, vB, vC
|
||||||
|
case 41:
|
||||||
|
return Opcode::vmsumshs; // vmsumshs vD, vA, vB, vC
|
||||||
|
case 42:
|
||||||
|
return Opcode::vsel; // vsel vD, vA, vB, vC
|
||||||
case 43:
|
case 43:
|
||||||
return Opcode::vperm; // vperm vD, vA, vB, vC
|
return Opcode::vperm; // vperm vD, vA, vB, vC
|
||||||
case 44:
|
case 44:
|
||||||
return Opcode::vsel; // vsel vD, vA, vB, vC
|
return Opcode::vsldoi; // vsldoi vD, vA, vB, SHB
|
||||||
|
case 46:
|
||||||
|
return Opcode::vmaddfp; // vmaddfp vD, vA, vC, vB
|
||||||
|
case 47:
|
||||||
|
return Opcode::vnmsubfp; // vnmsubfp vD, vA, vC, vB
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try VX-form (bits 21-31, 11-bit XO)
|
// Try VX-form (bits 21-31, 11-bit XO)
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,7 @@ enum class InstrFormat : uint8_t {
|
||||||
kM, // Rotate/mask (RS, RA, RB, MB, ME, Rc)
|
kM, // Rotate/mask (RS, RA, RB, MB, ME, Rc)
|
||||||
kMD, // Rotate double-word (RS, RA, sh, mb, XO, Rc)
|
kMD, // Rotate double-word (RS, RA, sh, mb, XO, Rc)
|
||||||
kA, // Floating-point arithmetic (FRT, FRA, FRB, FRC, XO, Rc)
|
kA, // Floating-point arithmetic (FRT, FRA, FRB, FRC, XO, Rc)
|
||||||
|
kVA, // Vector four-operand (VRT, VRA, VRB, VRC, XO)
|
||||||
kVXR, // Vector with record bit (VRT, VRA, VRB, Rc, XO)
|
kVXR, // Vector with record bit (VRT, VRA, VRB, Rc, XO)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -315,6 +316,15 @@ enum class Opcode : uint16_t {
|
||||||
vsububm, // Vector subtract unsigned byte modulo
|
vsububm, // Vector subtract unsigned byte modulo
|
||||||
vsubuhm, // Vector subtract unsigned halfword modulo
|
vsubuhm, // Vector subtract unsigned halfword modulo
|
||||||
vsubuwm, // Vector subtract unsigned word modulo
|
vsubuwm, // Vector subtract unsigned word modulo
|
||||||
|
vmhaddshs, // Vector multiply-high and add signed halfword saturate
|
||||||
|
vmhraddshs, // Vector multiply-high round and add signed halfword saturate
|
||||||
|
vmladduhm, // Vector multiply-low and add unsigned halfword modulo
|
||||||
|
vmsumubm, // Vector multiply-sum unsigned byte modulo
|
||||||
|
vmsummbm, // Vector multiply-sum mixed-sign byte modulo
|
||||||
|
vmsumuhm, // Vector multiply-sum unsigned halfword modulo
|
||||||
|
vmsumuhs, // Vector multiply-sum unsigned halfword saturate
|
||||||
|
vmsumshm, // Vector multiply-sum signed halfword modulo
|
||||||
|
vmsumshs, // Vector multiply-sum signed halfword saturate
|
||||||
vmuloub, // Vector multiply odd unsigned byte
|
vmuloub, // Vector multiply odd unsigned byte
|
||||||
vmulouh, // Vector multiply odd unsigned halfword
|
vmulouh, // Vector multiply odd unsigned halfword
|
||||||
vmulouw, // Vector multiply odd unsigned word
|
vmulouw, // Vector multiply odd unsigned word
|
||||||
|
|
@ -441,6 +451,7 @@ enum class Opcode : uint16_t {
|
||||||
vsr, // Vector shift right (128-bit)
|
vsr, // Vector shift right (128-bit)
|
||||||
vslo, // Vector shift left by octet
|
vslo, // Vector shift left by octet
|
||||||
vsro, // Vector shift right by octet
|
vsro, // Vector shift right by octet
|
||||||
|
vsldoi, // Vector shift left double by octet immediate
|
||||||
|
|
||||||
// Vector conversion
|
// Vector conversion
|
||||||
vcfux, // Vector convert from unsigned fixed-point word
|
vcfux, // Vector convert from unsigned fixed-point word
|
||||||
|
|
|
||||||
|
|
@ -930,6 +930,8 @@ bool XexModule::LoadContinue() {
|
||||||
REXLOG_ERROR("Failed to load XEX PE headers!");
|
REXLOG_ERROR("Failed to load XEX PE headers!");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
REXLOG_INFO("XEX image loaded at {:08X}-{:08X} (size {:08X})", base_address_,
|
||||||
|
base_address_ + image_size(), image_size());
|
||||||
|
|
||||||
// Parse any "unsafe" headers into safer variants
|
// Parse any "unsafe" headers into safer variants
|
||||||
xex2_opt_generic_u32* alternate_titleids;
|
xex2_opt_generic_u32* alternate_titleids;
|
||||||
|
|
@ -1009,7 +1011,10 @@ bool XexModule::LoadContinue() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup memory protection.
|
// Native recomp code performs direct host stores for guest writes. Some
|
||||||
|
// titles write to pages described as read-only in the XEX metadata, so keep
|
||||||
|
// loaded image pages writable instead of letting host page protection turn
|
||||||
|
// valid guest stores into access violations.
|
||||||
for (uint32_t i = 0, page = 0; i < sec_header->page_descriptor_count; i++) {
|
for (uint32_t i = 0, page = 0; i < sec_header->page_descriptor_count; i++) {
|
||||||
// Byteswap the bitfield manually.
|
// Byteswap the bitfield manually.
|
||||||
xex2_page_descriptor desc;
|
xex2_page_descriptor desc;
|
||||||
|
|
@ -1017,15 +1022,7 @@ bool XexModule::LoadContinue() {
|
||||||
|
|
||||||
auto address = base_address_ + (page * page_size);
|
auto address = base_address_ + (page * page_size);
|
||||||
auto size = desc.page_count * page_size;
|
auto size = desc.page_count * page_size;
|
||||||
switch (desc.info) {
|
heap->Protect(address, size, memory::kMemoryProtectRead | memory::kMemoryProtectWrite);
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
page += desc.page_count;
|
page += desc.page_count;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
15
tests/ppc/asm/instr_vaddsws.s
Normal file
15
tests/ppc/asm/instr_vaddsws.s
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
test_vaddsws_saturates:
|
||||||
|
#_ REGISTER_IN v3 [7FFFFFFF, 7FFFFFFE, 80000000, 80000001]
|
||||||
|
#_ REGISTER_IN v4 [00000001, 00000001, FFFFFFFF, FFFFFFFF]
|
||||||
|
vaddsws v3, v3, v4
|
||||||
|
blr
|
||||||
|
#_ REGISTER_OUT v3 [7FFFFFFF, 7FFFFFFF, 80000000, 80000000]
|
||||||
|
#_ REGISTER_OUT v4 [00000001, 00000001, FFFFFFFF, FFFFFFFF]
|
||||||
|
|
||||||
|
test_vaddsws_mixed:
|
||||||
|
#_ REGISTER_IN v3 [40000000, C0000000, 12345678, 87654321]
|
||||||
|
#_ REGISTER_IN v4 [40000000, C0000000, EDCBA988, 789ABCDF]
|
||||||
|
vaddsws v3, v3, v4
|
||||||
|
blr
|
||||||
|
#_ REGISTER_OUT v3 [7FFFFFFF, 80000000, 00000000, 00000000]
|
||||||
|
#_ REGISTER_OUT v4 [40000000, C0000000, EDCBA988, 789ABCDF]
|
||||||
6
tests/ppc/asm/instr_vcfpsxws128.s
Normal file
6
tests/ppc/asm/instr_vcfpsxws128.s
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
test_vcfpsxws128_signed_edges:
|
||||||
|
#_ REGISTER_IN v3 [3FC00000, BFC00000, 4F000000, CF000000]
|
||||||
|
vcfpsxws128 v4, v3, 0
|
||||||
|
blr
|
||||||
|
#_ REGISTER_OUT v3 [3FC00000, BFC00000, 4F000000, CF000000]
|
||||||
|
#_ REGISTER_OUT v4 [00000001, FFFFFFFF, 7FFFFFFF, 80000000]
|
||||||
6
tests/ppc/asm/instr_vcfpuxws128.s
Normal file
6
tests/ppc/asm/instr_vcfpuxws128.s
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
test_vcfpuxws128_unsigned_edges:
|
||||||
|
#_ REGISTER_IN v3 [3FC00000, BFC00000, 4F000000, 4F800000]
|
||||||
|
vcfpuxws128 v4, v3, 0
|
||||||
|
blr
|
||||||
|
#_ REGISTER_OUT v3 [3FC00000, BFC00000, 4F000000, 4F800000]
|
||||||
|
#_ REGISTER_OUT v4 [00000001, 00000000, 80000000, FFFFFFFF]
|
||||||
20
tests/ppc/asm/instr_vcfux.s
Normal file
20
tests/ppc/asm/instr_vcfux.s
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
test_vcfux_unsigned_edges:
|
||||||
|
#_ REGISTER_IN v3 [00000000, 00000001, 7FFFFFFF, 80000000]
|
||||||
|
vcfux v4, v3, 0
|
||||||
|
blr
|
||||||
|
#_ REGISTER_OUT v3 [00000000, 00000001, 7FFFFFFF, 80000000]
|
||||||
|
#_ REGISTER_OUT v4 [00000000, 3F800000, 4F000000, 4F000000]
|
||||||
|
|
||||||
|
test_vcfux_unsigned_max:
|
||||||
|
#_ REGISTER_IN v3 [FFFFFFFF, FFFFFFFE, 00FFFFFF, 01000000]
|
||||||
|
vcfux v4, v3, 0
|
||||||
|
blr
|
||||||
|
#_ REGISTER_OUT v3 [FFFFFFFF, FFFFFFFE, 00FFFFFF, 01000000]
|
||||||
|
#_ REGISTER_OUT v4 [4F800000, 4F800000, 4B7FFFFF, 4B800000]
|
||||||
|
|
||||||
|
test_vcfux_scale:
|
||||||
|
#_ REGISTER_IN v3 [80000000, 40000000, 00000400, 00000001]
|
||||||
|
vcfux v4, v3, 10
|
||||||
|
blr
|
||||||
|
#_ REGISTER_OUT v3 [80000000, 40000000, 00000400, 00000001]
|
||||||
|
#_ REGISTER_OUT v4 [4A000000, 49800000, 3F800000, 3A800000]
|
||||||
13
tests/ppc/asm/instr_vcuxwfp128.s
Normal file
13
tests/ppc/asm/instr_vcuxwfp128.s
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
test_vcuxwfp128_unsigned_edges:
|
||||||
|
#_ REGISTER_IN v3 [00000000, 00000001, 7FFFFFFF, 80000000]
|
||||||
|
vcuxwfp128 v4, v3, 0
|
||||||
|
blr
|
||||||
|
#_ REGISTER_OUT v3 [00000000, 00000001, 7FFFFFFF, 80000000]
|
||||||
|
#_ REGISTER_OUT v4 [00000000, 3F800000, 4F000000, 4F000000]
|
||||||
|
|
||||||
|
test_vcuxwfp128_unsigned_max:
|
||||||
|
#_ REGISTER_IN v3 [FFFFFFFF, FFFFFFFE, 00FFFFFF, 01000000]
|
||||||
|
vcuxwfp128 v4, v3, 0
|
||||||
|
blr
|
||||||
|
#_ REGISTER_OUT v3 [FFFFFFFF, FFFFFFFE, 00FFFFFF, 01000000]
|
||||||
|
#_ REGISTER_OUT v4 [4F800000, 4F800000, 4B7FFFFF, 4B800000]
|
||||||
19
tests/ppc/asm/instr_vmaddfp128.s
Normal file
19
tests/ppc/asm/instr_vmaddfp128.s
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
test_vmaddfp128_accumulates_vd:
|
||||||
|
#_ REGISTER_IN v3 [40000000, 40400000, 40800000, 40A00000]
|
||||||
|
#_ REGISTER_IN v4 [41200000, 41A00000, 41F00000, 42200000]
|
||||||
|
#_ REGISTER_IN v5 [3F800000, 3F800000, 3F800000, 3F800000]
|
||||||
|
vmaddfp128 v5, v3, v4, v5
|
||||||
|
blr
|
||||||
|
#_ REGISTER_OUT v3 [40000000, 40400000, 40800000, 40A00000]
|
||||||
|
#_ REGISTER_OUT v4 [41200000, 41A00000, 41F00000, 42200000]
|
||||||
|
#_ REGISTER_OUT v5 [41A80000, 42740000, 42F20000, 43490000]
|
||||||
|
|
||||||
|
test_vmaddcfp128_accumulates_vb:
|
||||||
|
#_ REGISTER_IN v3 [41200000, 41A00000, 41F00000, 42200000]
|
||||||
|
#_ REGISTER_IN v4 [3F800000, 3F800000, 3F800000, 3F800000]
|
||||||
|
#_ REGISTER_IN v5 [40000000, 40400000, 40800000, 40A00000]
|
||||||
|
vmaddcfp128 v5, v3, v5, v4
|
||||||
|
blr
|
||||||
|
#_ REGISTER_OUT v3 [41200000, 41A00000, 41F00000, 42200000]
|
||||||
|
#_ REGISTER_OUT v4 [3F800000, 3F800000, 3F800000, 3F800000]
|
||||||
|
#_ REGISTER_OUT v5 [41A80000, 42740000, 42F20000, 43490000]
|
||||||
|
|
@ -8,3 +8,13 @@ test_vmaxfp128_1:
|
||||||
#_ REGISTER_OUT v0 [41200000, 41A00000, 41700000, 41F00000]
|
#_ REGISTER_OUT v0 [41200000, 41A00000, 41700000, 41F00000]
|
||||||
#_ REGISTER_OUT v3 [41200000, C1200000, 41700000, C1700000]
|
#_ REGISTER_OUT v3 [41200000, C1200000, 41700000, C1700000]
|
||||||
#_ REGISTER_OUT v4 [C1200000, 41A00000, C1A00000, 41F00000]
|
#_ REGISTER_OUT v4 [C1200000, 41A00000, C1A00000, 41F00000]
|
||||||
|
|
||||||
|
test_vmaxfp128_nan:
|
||||||
|
# Match Xenia's single maxps lowering: if either operand is NaN, the second source is returned.
|
||||||
|
#_ REGISTER_IN v3 [7FC00000, 3F800000, 7FC00000, C0000000]
|
||||||
|
#_ REGISTER_IN v4 [3F800000, 7FC00000, 7FA00001, C0400000]
|
||||||
|
vmaxfp128 v0, v3, v4
|
||||||
|
blr
|
||||||
|
#_ REGISTER_OUT v0 [3F800000, 7FC00000, 7FA00001, C0000000]
|
||||||
|
#_ REGISTER_OUT v3 [7FC00000, 3F800000, 7FC00000, C0000000]
|
||||||
|
#_ REGISTER_OUT v4 [3F800000, 7FC00000, 7FA00001, C0400000]
|
||||||
|
|
|
||||||
|
|
@ -8,3 +8,13 @@ test_vminfp128_1:
|
||||||
#_ REGISTER_OUT v0 [C1200000, C1200000, C1A00000, C1700000]
|
#_ REGISTER_OUT v0 [C1200000, C1200000, C1A00000, C1700000]
|
||||||
#_ REGISTER_OUT v3 [41200000, C1200000, 41700000, C1700000]
|
#_ REGISTER_OUT v3 [41200000, C1200000, 41700000, C1700000]
|
||||||
#_ REGISTER_OUT v4 [C1200000, 41A00000, C1A00000, 41F00000]
|
#_ REGISTER_OUT v4 [C1200000, 41A00000, C1A00000, 41F00000]
|
||||||
|
|
||||||
|
test_vminfp128_nan:
|
||||||
|
# Match Xenia's single minps lowering: if either operand is NaN, the second source is returned.
|
||||||
|
#_ REGISTER_IN v3 [7FC00000, 3F800000, 7FC00000, C0000000]
|
||||||
|
#_ REGISTER_IN v4 [3F800000, 7FC00000, 7FA00001, C0400000]
|
||||||
|
vminfp128 v0, v3, v4
|
||||||
|
blr
|
||||||
|
#_ REGISTER_OUT v0 [3F800000, 7FC00000, 7FA00001, C0400000]
|
||||||
|
#_ REGISTER_OUT v3 [7FC00000, 3F800000, 7FC00000, C0000000]
|
||||||
|
#_ REGISTER_OUT v4 [3F800000, 7FC00000, 7FA00001, C0400000]
|
||||||
|
|
|
||||||
|
|
@ -8,3 +8,12 @@ test_vmsum3fp128_1:
|
||||||
#_ REGISTER_OUT v3 [3f800000, 3fc00000, 3f8ccccd, 01020304]
|
#_ REGISTER_OUT v3 [3f800000, 3fc00000, 3f8ccccd, 01020304]
|
||||||
#_ REGISTER_OUT v4 [40000000, 40700000, 4013d70a, 01020304]
|
#_ REGISTER_OUT v4 [40000000, 40700000, 4013d70a, 01020304]
|
||||||
#_ REGISTER_OUT v5 [4122A7F0, 4122A7F0, 4122A7F0, 4122A7F0]
|
#_ REGISTER_OUT v5 [4122A7F0, 4122A7F0, 4122A7F0, 4122A7F0]
|
||||||
|
|
||||||
|
test_vmsum3fp128_overflow:
|
||||||
|
#_ REGISTER_IN v3 [7f7fffff, 7f7fffff, 00000000, 00000000]
|
||||||
|
#_ REGISTER_IN v4 [7f7fffff, 7f7fffff, 00000000, 00000000]
|
||||||
|
vmsum3fp128 v5, v3, v4
|
||||||
|
blr
|
||||||
|
#_ REGISTER_OUT v3 [7f7fffff, 7f7fffff, 00000000, 00000000]
|
||||||
|
#_ REGISTER_OUT v4 [7f7fffff, 7f7fffff, 00000000, 00000000]
|
||||||
|
#_ REGISTER_OUT v5 [7FC00000, 7FC00000, 7FC00000, 7FC00000]
|
||||||
|
|
|
||||||
|
|
@ -6,3 +6,12 @@ test_vmsum4fp128_1:
|
||||||
#_ REGISTER_OUT v3 [3f800000, 3fc00000, 3f8ccccd, 3ff33333]
|
#_ REGISTER_OUT v3 [3f800000, 3fc00000, 3f8ccccd, 3ff33333]
|
||||||
#_ REGISTER_OUT v4 [40000000, 40700000, 4013d70a, 40b051eb]
|
#_ REGISTER_OUT v4 [40000000, 40700000, 4013d70a, 40b051eb]
|
||||||
#_ REGISTER_OUT v5 [41A5147B, 41A5147B, 41A5147B, 41A5147B]
|
#_ REGISTER_OUT v5 [41A5147B, 41A5147B, 41A5147B, 41A5147B]
|
||||||
|
|
||||||
|
test_vmsum4fp128_overflow:
|
||||||
|
#_ REGISTER_IN v3 [7f7fffff, 7f7fffff, 00000000, 00000000]
|
||||||
|
#_ REGISTER_IN v4 [7f7fffff, 7f7fffff, 00000000, 00000000]
|
||||||
|
vmsum4fp128 v5, v3, v4
|
||||||
|
blr
|
||||||
|
#_ REGISTER_OUT v3 [7f7fffff, 7f7fffff, 00000000, 00000000]
|
||||||
|
#_ REGISTER_OUT v4 [7f7fffff, 7f7fffff, 00000000, 00000000]
|
||||||
|
#_ REGISTER_OUT v5 [7FC00000, 7FC00000, 7FC00000, 7FC00000]
|
||||||
|
|
|
||||||
9
tests/ppc/asm/instr_vnmsubfp128.s
Normal file
9
tests/ppc/asm/instr_vnmsubfp128.s
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
test_vnmsubfp128_accumulates_vd:
|
||||||
|
#_ REGISTER_IN v3 [40000000, 40400000, 40800000, 40A00000]
|
||||||
|
#_ REGISTER_IN v4 [41200000, 41A00000, 41F00000, 42200000]
|
||||||
|
#_ REGISTER_IN v5 [42C80000, 42C80000, 42C80000, 42C80000]
|
||||||
|
vnmsubfp128 v5, v3, v4, v5
|
||||||
|
blr
|
||||||
|
#_ REGISTER_OUT v3 [40000000, 40400000, 40800000, 40A00000]
|
||||||
|
#_ REGISTER_OUT v4 [41200000, 41A00000, 41F00000, 42200000]
|
||||||
|
#_ REGISTER_OUT v5 [42A00000, 42200000, C1A00000, C2C80000]
|
||||||
10
tests/ppc/asm/instr_vperm128.s
Normal file
10
tests/ppc/asm/instr_vperm128.s
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
test_vperm128_ignores_control_high_bits:
|
||||||
|
#_ REGISTER_IN v0 [00010203, 10111213, 20212F3F, 0F1F0414]
|
||||||
|
#_ REGISTER_IN v3 [00010203, 04050607, 08090A0B, 0C0D0E0F]
|
||||||
|
#_ REGISTER_IN v4 [10111213, 14151617, 18191A1B, 1C1D1E1F]
|
||||||
|
vperm128 v6, v3, v4, v0
|
||||||
|
blr
|
||||||
|
#_ REGISTER_OUT v0 [00010203, 10111213, 20212F3F, 0F1F0414]
|
||||||
|
#_ REGISTER_OUT v3 [00010203, 04050607, 08090A0B, 0C0D0E0F]
|
||||||
|
#_ REGISTER_OUT v4 [10111213, 14151617, 18191A1B, 1C1D1E1F]
|
||||||
|
#_ REGISTER_OUT v6 [00010203, 10111213, 00010F1F, 0F1F0414]
|
||||||
6
tests/ppc/asm/instr_vrsqrtefp128.s
Normal file
6
tests/ppc/asm/instr_vrsqrtefp128.s
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
test_vrsqrtefp128_exact_xenia_lowering:
|
||||||
|
#_ REGISTER_IN v3 [3F800000, 40800000, 3E800000, 41800000]
|
||||||
|
vrsqrtefp128 v4, v3
|
||||||
|
blr
|
||||||
|
#_ REGISTER_OUT v3 [3F800000, 40800000, 3E800000, 41800000]
|
||||||
|
#_ REGISTER_OUT v4 [3F800000, 3F000000, 40000000, 3E800000]
|
||||||
8
tests/ppc/asm/instr_vslw128.s
Normal file
8
tests/ppc/asm/instr_vslw128.s
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
test_vslw128_variable:
|
||||||
|
#_ REGISTER_IN v3 [00000001, 00000001, 00000001, 00000001]
|
||||||
|
#_ REGISTER_IN v4 [00000000, 00000001, 0000001F, 00000020]
|
||||||
|
vslw128 v5, v3, v4
|
||||||
|
blr
|
||||||
|
#_ REGISTER_OUT v3 [00000001, 00000001, 00000001, 00000001]
|
||||||
|
#_ REGISTER_OUT v4 [00000000, 00000001, 0000001F, 00000020]
|
||||||
|
#_ REGISTER_OUT v5 [00000001, 00000002, 80000000, 00000001]
|
||||||
8
tests/ppc/asm/instr_vsraw128.s
Normal file
8
tests/ppc/asm/instr_vsraw128.s
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
test_vsraw128_variable:
|
||||||
|
#_ REGISTER_IN v3 [80000000, 80000000, 7FFFFFFF, FFFFFFFF]
|
||||||
|
#_ REGISTER_IN v4 [00000000, 00000001, 0000001F, 00000020]
|
||||||
|
vsraw128 v5, v3, v4
|
||||||
|
blr
|
||||||
|
#_ REGISTER_OUT v3 [80000000, 80000000, 7FFFFFFF, FFFFFFFF]
|
||||||
|
#_ REGISTER_OUT v4 [00000000, 00000001, 0000001F, 00000020]
|
||||||
|
#_ REGISTER_OUT v5 [80000000, C0000000, 00000000, FFFFFFFF]
|
||||||
8
tests/ppc/asm/instr_vsrw128.s
Normal file
8
tests/ppc/asm/instr_vsrw128.s
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
test_vsrw128_variable:
|
||||||
|
#_ REGISTER_IN v3 [80000000, 80000000, FFFFFFFF, FFFFFFFF]
|
||||||
|
#_ REGISTER_IN v4 [00000000, 00000001, 0000001F, 00000020]
|
||||||
|
vsrw128 v5, v3, v4
|
||||||
|
blr
|
||||||
|
#_ REGISTER_OUT v3 [80000000, 80000000, FFFFFFFF, FFFFFFFF]
|
||||||
|
#_ REGISTER_OUT v4 [00000000, 00000001, 0000001F, 00000020]
|
||||||
|
#_ REGISTER_OUT v5 [80000000, 40000000, 00000001, FFFFFFFF]
|
||||||
15
tests/ppc/asm/instr_vsubsws.s
Normal file
15
tests/ppc/asm/instr_vsubsws.s
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
test_vsubsws_saturates:
|
||||||
|
#_ REGISTER_IN v3 [80000000, 80000001, 7FFFFFFF, 7FFFFFFE]
|
||||||
|
#_ REGISTER_IN v4 [00000001, 00000001, FFFFFFFF, FFFFFFFF]
|
||||||
|
vsubsws v3, v3, v4
|
||||||
|
blr
|
||||||
|
#_ REGISTER_OUT v3 [80000000, 80000000, 7FFFFFFF, 7FFFFFFF]
|
||||||
|
#_ REGISTER_OUT v4 [00000001, 00000001, FFFFFFFF, FFFFFFFF]
|
||||||
|
|
||||||
|
test_vsubsws_mixed:
|
||||||
|
#_ REGISTER_IN v3 [40000000, C0000000, 12345678, 87654321]
|
||||||
|
#_ REGISTER_IN v4 [C0000000, 40000000, 12345678, 87654321]
|
||||||
|
vsubsws v3, v3, v4
|
||||||
|
blr
|
||||||
|
#_ REGISTER_OUT v3 [7FFFFFFF, 80000000, 00000000, 00000000]
|
||||||
|
#_ REGISTER_OUT v4 [C0000000, 40000000, 12345678, 87654321]
|
||||||
Loading…
Add table
Reference in a new issue