From db2b18c0a41f16f90872cda128da23a42e1a4d97 Mon Sep 17 00:00:00 2001 From: Walter Date: Tue, 7 Jul 2026 14:31:14 +1000 Subject: [PATCH] [SPU LLVM] Refactor `SELB` Refactors SELB by changing "division accuracy correction" pattern match to reduce nesting, include more comments, and use more descriptive variable names. Also replaced duplicated granularity-check loops with single loop which determines byte granularity as a integer. --- rpcs3/Emu/Cell/SPULLVMRecompiler.cpp | 200 +++++++++++++-------------- 1 file changed, 94 insertions(+), 106 deletions(-) diff --git a/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp b/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp index 73396eb08a..8be6f71e1b 100644 --- a/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp +++ b/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp @@ -7190,77 +7190,74 @@ public: using VT = typename decltype(MP)::type; // If the control mask comes from a comparison instruction, replace SELB with select - if (auto [ok, x] = match_expr(c, sext(match]>())); ok) + auto [select_match, sel_bool] = match_expr(c, sext(match]>())); + + if (!select_match) + return false; + + if constexpr (std::extent_v == 2) // u64[2] { - if constexpr (std::extent_v == 2) // u64[2] + // Try to select floats as floats if either is typed as f64[2] + if (auto [a, b] = match_vrs(op.ra, op.rb); a || b) { - // Try to select floats as floats if a OR b is typed as f64[2] - if (auto [a, b] = match_vrs(op.ra, op.rb); a || b) - { - set_vr(op.rt4, select(x, get_vr(op.rb), get_vr(op.ra))); - return true; - } + set_vr(op.rt4, select(sel_bool, get_vr(op.rb), get_vr(op.ra))); + return true; } - - if constexpr (std::extent_v == 4) // u32[4] - { - // Match division (adjusted) (TODO) - if (auto a = match_vr(op.ra)) - { - static const auto MT = match(); - - if (auto [div_ok, diva, divb] = match_expr(a, MT / MT); div_ok) - { - if (auto b = match_vr(op.rb)) - { - if (auto [add1_ok] = match_expr(b, bitcast(a) + splat(1)); add1_ok) - { - if (auto [fm_ok, a1, b1] = match_expr(x, bitcast(fm(MT, MT)) > splat(-1)); fm_ok) - { - if (auto [fnma_ok] = match_expr(a1, fnms(divb, bitcast(b), diva)); fnma_ok) - { - if (fabs(b1).eval(m_ir) == fsplat(1.0).eval(m_ir)) - { - set_vr(op.rt4, diva / divb); - return true; - } - - if (auto [sel_ok] = match_expr(b1, bitcast((bitcast(diva) & 0x80000000) | 0x3f800000)); sel_ok) - { - set_vr(op.rt4, diva / divb); - return true; - } - } - } - } - } - } - } - - if (auto [a, b] = match_vrs(op.ra, op.rb); a || b) - { - set_vr(op.rt4, select(x, get_vr(op.rb), get_vr(op.ra))); - return true; - } - - if (auto [a, b] = match_vrs(op.ra, op.rb); a || b) - { - set_vr(op.rt4, select(x, get_vr(op.rb), get_vr(op.ra))); - return true; - } - } - - if (auto [ok, y] = match_expr(x, bitcast]>(match>>())); ok) - { - // Don't ruin FSMB/FSM/FSMH instructions - return false; - } - - set_vr(op.rt4, select(x, get_vr(op.rb), get_vr(op.ra))); - return true; } - return false; + if constexpr (std::extent_v == 4) // u32[4] + { + // Replace "division accuracy correction" pattern with direct division + + auto quot = match_vr(op.ra); + auto quot_offset = match_vr(op.rb); + + if (quot && quot_offset) + { + // Capture arbitrary operand + static const auto MT = match(); + + // select(div_error > -1, nextafter(n/d), n/d) + const auto [errcmp_match, div_err, fone] = match_expr(sel_bool, bitcast(fm(MT, MT)) > splat(-1)); + const auto [divoffset_match] = match_expr(quot_offset, bitcast(quot) + splat(1)); + const auto [div_match, nume, denom] = match_expr(quot, MT / MT); + + if (errcmp_match && divoffset_match && div_match) + { + // "Undo" division to check rounding error + const auto [diverr_match] = match_expr(div_err, fnms(denom, bitcast(quot_offset), nume)); + + const auto is_one_const = fabs(fone).eval(m_ir) == fsplat(1.0).eval(m_ir); + const auto [copy_sign_match] = match_expr(fone, bitcast((bitcast(nume) & 0x80000000) | std::bit_cast(1.0f))); + + if (diverr_match && (is_one_const || copy_sign_match)) + { + // Correction isn't needed as IEEE division is accurate + set_vr(op.rt4, nume / denom); + return true; + } + } + } + + if (auto [a, b] = match_vrs(op.ra, op.rb); a || b) + { + set_vr(op.rt4, select(sel_bool, get_vr(op.rb), get_vr(op.ra))); + return true; + } + + if (auto [a, b] = match_vrs(op.ra, op.rb); a || b) + { + set_vr(op.rt4, select(sel_bool, get_vr(op.rb), get_vr(op.ra))); + return true; + } + } + + // Don't ruin FSMB/FSM/FSMH instructions + if (auto [ok, y] = match_expr(sel_bool, bitcast]>(match>>())); ok) + return false; + + set_vr(op.rt4, select(sel_bool, get_vr(op.rb), get_vr(op.ra))); + return true; })) { return; @@ -7271,63 +7268,54 @@ public: // Check if the constant mask doesn't require bit granularity if (auto [ok, mask] = get_const_vector(c.value, m_pos); ok) { - bool sel_32 = true; - for (u32 i = 0; i < 4; i++) + unsigned byte_granularity = 16; + + unsigned equals_run = 0; + u8 prev_elt = mask._u8[0]; + for (u8 cur_elt : mask._u8.m_data) { - if (mask._u32[i] && mask._u32[i] != 0xFFFFFFFF) + if (cur_elt != 0x00 && cur_elt != 0xFF) { - sel_32 = false; + byte_granularity = 0; break; } + + if (cur_elt != prev_elt) + { + // Fraction of 16 check + byte_granularity = (16 % equals_run) ? 1 : std::min(byte_granularity, equals_run); + equals_run = 0; // 1 after increment + } + + equals_run++; + prev_elt = cur_elt; } - if (sel_32) + switch (byte_granularity) { - if (auto [a, b] = match_vrs(op.ra, op.rb); a || b) + case 16: + case 8: + case 4: + { + if (const auto [a_f64, b_f64] = match_vrs(op.ra, op.rb); a_f64 || b_f64) { - set_vr(op.rt4, select(noncast(c) != 0, get_vr(op.rb), get_vr(op.ra))); - return; - } - else if (auto [a, b] = match_vrs(op.ra, op.rb); a || b) - { - set_vr(op.rt4, select(noncast(c) != 0, get_vr(op.rb), get_vr(op.ra))); + set_vr(op.rt4, select(noncast(c) != 0, get_vr(op.rb), get_vr(op.ra))); return; } - set_vr(op.rt4, select(noncast(c) != 0, get_vr(op.rb), get_vr(op.ra))); - return; - } - - bool sel_16 = true; - for (u32 i = 0; i < 8; i++) - { - if (mask._u16[i] && mask._u16[i] != 0xFFFF) + if (const auto [a_f32, b_f32] = match_vrs(op.ra, op.rb); a_f32 || b_f32) { - sel_16 = false; - break; + set_vr(op.rt4, select(noncast(c) != 0, get_vr(op.rb), get_vr(op.ra))); + return; } + [[fallthrough]]; } - - if (sel_16) + case 2: + case 1: { - set_vr(op.rt4, select(bitcast(c) != 0, get_vr(op.rb), get_vr(op.ra))); + set_vr(op.rt4, select(bitcast(c) != 0, get_vr(op.rb), get_vr(op.ra))); return; } - - bool sel_8 = true; - for (u32 i = 0; i < 16; i++) - { - if (mask._u8[i] && mask._u8[i] != 0xFF) - { - sel_8 = false; - break; - } - } - - if (sel_8) - { - set_vr(op.rt4, select(bitcast(c) != 0,get_vr(op.rb), get_vr(op.ra))); - return; } }