From ed145540a2c7f1578b7f50ea10f9610f100cc320 Mon Sep 17 00:00:00 2001 From: Malcolm Date: Sat, 23 May 2026 21:30:17 -0400 Subject: [PATCH 1/7] SPU: Use multiply-accumulate comparisons for cmp_rdata and verification --- rpcs3/Emu/Cell/SPULLVMRecompiler.cpp | 139 +++++++++++++++++++++++++++ rpcs3/Emu/Cell/SPUThread.cpp | 21 ++++ 2 files changed, 160 insertions(+) diff --git a/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp b/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp index 689233e0b3..a417246589 100644 --- a/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp +++ b/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp @@ -1929,6 +1929,144 @@ public: const auto cond = m_ir->CreateICmpNE(elem, m_ir->getInt64(0)); m_ir->CreateCondBr(cond, label_diff, label_body, m_md_unlikely); } +#ifdef ARCH_ARM64 + else + { + const auto acc_init = m_use_dotprod ? ConstantAggregateZero::get(get_type()) : ConstantAggregateZero::get(get_type()); + llvm::Value* acc0 = acc_init; + llvm::Value* acc1 = acc_init; + llvm::Value* acc2 = acc_init; + llvm::Value* acc3 = acc_init; + llvm::Value** accs[4] = {&acc0, &acc1, &acc2, &acc3}; + u32 acc_index = 0; + llvm::Value* pending_cmp = nullptr; + u32 expected_hits = 0; + + const auto make_cmp = [&](u32 j) -> llvm::Value* + { + int indices[4]; + bool holes = false; + bool data = false; + + for (u32 i = 0; i < 4; i++) + { + const u32 k = j + i * 4; + + if (k < start || k >= end || !func.data[(k - start) / 4]) + { + indices[i] = 4; + holes = true; + } + else + { + indices[i] = i; + data = true; + } + } + + if (!data) + { + return nullptr; + } + + llvm::Value* vls = m_ir->CreateAlignedLoad(get_type(), _ptr(data_addr, j - starta), llvm::MaybeAlign{4}); + + if (holes) + { + vls = m_ir->CreateShuffleVector(vls, ConstantAggregateZero::get(vls->getType()), llvm::ArrayRef(indices, 4)); + } + + u32 words[4]; + + for (u32 i = 0; i < 4; i++) + { + const u32 k = j + i * 4; + words[i] = k >= start && k < end ? func.data[(k - start) / 4] : 0; + } + + const auto expected = ConstantDataVector::get(m_context, llvm::ArrayRef(words, 4)); + + if (m_use_dotprod) + { + return m_ir->CreateSExt(m_ir->CreateICmpEQ( + m_ir->CreateBitCast(vls, get_type()), + m_ir->CreateBitCast(expected, get_type())), get_type()); + } + + return m_ir->CreateSExt(m_ir->CreateICmpEQ( + m_ir->CreateBitCast(vls, get_type()), + m_ir->CreateBitCast(expected, get_type())), get_type()); + }; + + const auto accumulate_pair = [&](llvm::Value* lhs, llvm::Value* rhs) + { + llvm::Value*& acc = *accs[acc_index]; + + if (m_use_dotprod) + { + acc = m_ir->CreateCall(get_intrinsic(llvm::Intrinsic::aarch64_neon_udot), {acc, lhs, rhs}); + } + else + { + acc = m_ir->CreateAdd(acc, m_ir->CreateMul(lhs, rhs)); + } + + acc_index = (acc_index + 1) & 3; + expected_hits++; + }; + + for (u32 j = starta; j < end; j += 16) + { + if (const auto cmp = make_cmp(j)) + { + if (pending_cmp) + { + accumulate_pair(pending_cmp, cmp); + pending_cmp = nullptr; + } + else + { + pending_cmp = cmp; + } + + check_iterations++; + } + } + + if (pending_cmp) + { + accumulate_pair(pending_cmp, llvm::ConstantInt::get(pending_cmp->getType(), -1, true)); + } + + llvm::Value* expected = nullptr; + + if (m_use_dotprod) + { + u32 expected_words[4]; + expected_words[0] = expected_hits * 4 * 0xff * 0xff; + expected_words[1] = expected_words[0]; + expected_words[2] = expected_words[0]; + expected_words[3] = expected_words[0]; + expected = ConstantDataVector::get(m_context, llvm::ArrayRef(expected_words, 4)); + } + else + { + u16 expected_words[8]; + std::fill_n(expected_words, 8, static_cast(expected_hits)); + expected = ConstantDataVector::get(m_context, llvm::ArrayRef(expected_words, 8)); + } + + llvm::Value* acc = m_ir->CreateAdd(m_ir->CreateAdd(acc0, acc1), m_ir->CreateAdd(acc2, acc3)); + acc = m_ir->CreateXor(acc, expected); + acc = m_ir->CreateBitCast(acc, get_type()); + + llvm::Value* elem = m_ir->CreateExtractElement(acc, u64{0}); + elem = m_ir->CreateOr(elem, m_ir->CreateExtractElement(acc, u64{1})); + + const auto cond = m_ir->CreateICmpNE(elem, m_ir->getInt64(0)); + m_ir->CreateCondBr(cond, label_diff, label_body, m_md_unlikely); + } +#else else { for (u32 j = starta; j < end; j += stride) @@ -2031,6 +2169,7 @@ public: const auto cond = m_ir->CreateICmpNE(elem, m_ir->getInt64(0)); m_ir->CreateCondBr(cond, label_diff, label_body, m_md_unlikely); } +#endif } // Increase block counter with statistics diff --git a/rpcs3/Emu/Cell/SPUThread.cpp b/rpcs3/Emu/Cell/SPUThread.cpp index 9ef5da81eb..93af9e8b03 100644 --- a/rpcs3/Emu/Cell/SPUThread.cpp +++ b/rpcs3/Emu/Cell/SPUThread.cpp @@ -245,6 +245,17 @@ static FORCE_INLINE bool cmp_rdata_avx(const __m256i* lhs, const __m256i* rhs) } #endif +#if defined(ARCH_ARM64) +static FORCE_INLINE int16x8_t cmp16_pair_accum_arm64( + int16x8_t acc, const v128& lhs0, const v128& rhs0, const v128& lhs1, const v128& rhs1) +{ + const int16x8_t eq0 = vreinterpretq_s16_u16(vceqq_u16(static_cast(lhs0), static_cast(rhs0))); + const int16x8_t eq1 = vreinterpretq_s16_u16(vceqq_u16(static_cast(lhs1), static_cast(rhs1))); + return vmlaq_s16(acc, eq0, eq1); +} + +#endif + #ifdef _MSC_VER __forceinline #endif @@ -261,12 +272,22 @@ extern bool cmp_rdata(const spu_rdata_t& _lhs, const spu_rdata_t& _rhs) const auto lhs = reinterpret_cast(_lhs); const auto rhs = reinterpret_cast(_rhs); +#if defined(ARCH_ARM64) + int16x8_t hits = vdupq_n_s16(0); + hits = cmp16_pair_accum_arm64(hits, lhs[0], rhs[0], lhs[1], rhs[1]); + hits = cmp16_pair_accum_arm64(hits, lhs[2], rhs[2], lhs[3], rhs[3]); + hits = cmp16_pair_accum_arm64(hits, lhs[4], rhs[4], lhs[5], rhs[5]); + hits = cmp16_pair_accum_arm64(hits, lhs[6], rhs[6], lhs[7], rhs[7]); + + return vaddvq_s16(hits) == 32; +#else const v128 a = (lhs[0] ^ rhs[0]) | (lhs[1] ^ rhs[1]); const v128 c = (lhs[4] ^ rhs[4]) | (lhs[5] ^ rhs[5]); const v128 b = (lhs[2] ^ rhs[2]) | (lhs[3] ^ rhs[3]); const v128 d = (lhs[6] ^ rhs[6]) | (lhs[7] ^ rhs[7]); const v128 r = (a | b) | (c | d); return gv_testz(r); +#endif } #if defined(ARCH_X64) From 35f65c224483b7a57092c56690fffd975d74e690 Mon Sep 17 00:00:00 2001 From: Whatcookie Date: Tue, 26 May 2026 17:26:58 -0400 Subject: [PATCH 2/7] SPU LLVM: Add ARM checksum UABA path --- rpcs3/Emu/Cell/SPULLVMRecompiler.cpp | 102 +++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp b/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp index a417246589..be7fb77095 100644 --- a/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp +++ b/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp @@ -1835,15 +1835,18 @@ public: llvm::Value* starta_pc = m_ir->CreateAnd(get_pc(starta), 0x3fffc); llvm::Value* data_addr = _ptr(m_lsptr, starta_pc); +#ifndef ARCH_ARM64 llvm::Value* acc0 = nullptr; llvm::Value* acc1 = nullptr; bool toggle = true; +#endif // Use a 512bit simple checksum to verify integrity if size is atleast 512b * 3 // This code uses a 512bit vector for all hardware to ensure behavior matches. // The checksum path is still faster even on narrow hardware. if ((end - starta) >= 192 && !g_cfg.core.precise_spu_verification) { +#ifndef ARCH_ARM64 for (u32 j = starta; j < end; j += 64) { int indices[16]; @@ -1928,6 +1931,105 @@ public: // Compare result with zero const auto cond = m_ir->CreateICmpNE(elem, m_ir->getInt64(0)); m_ir->CreateCondBr(cond, label_diff, label_body, m_md_unlikely); +#else + // + // + // + + const auto acc_init = ConstantAggregateZero::get(get_type()); + llvm::Value* checksum_parts[4] = {acc_init, acc_init, acc_init, acc_init}; + u32 checksum[16] = {0}; + + for (u32 j = starta; j < end; j += 64) + { + llvm::Value* vls[4] = {}; + u32 words[16] = {}; + bool any_data = false; + + for (u32 part = 0; part < 4; part++) + { + int indices[4]; + bool holes = false; + bool data = false; + + for (u32 i = 0; i < 4; i++) + { + const u32 k = j + (part * 4 + i) * 4; + + if (k < start || k >= end || !func.data[(k - start) / 4]) + { + indices[i] = 4; + holes = true; + } + else + { + indices[i] = i; + data = true; + words[part * 4 + i] = func.data[(k - start) / 4]; + } + } + + if (!data) + { + vls[part] = acc_init; + continue; + } + + any_data = true; + + // Load unaligned code block from LS + vls[part] = m_ir->CreateAlignedLoad(get_type(), _ptr(data_addr, j + part * 16 - starta), llvm::MaybeAlign{4}); + + // Mask if necessary + if (holes) + { + vls[part] = m_ir->CreateShuffleVector(vls[part], acc_init, llvm::ArrayRef(indices, 4)); + } + } + + if (!any_data) + { + // Skip full-sized holes + continue; + } + + // Keep the checksum logically 512-bit wide while using four NEON vectors. + // The add/uabd pattern is intended to select UABA for the difference lanes. + checksum_parts[0] = m_ir->CreateAdd(checksum_parts[0], vls[0]); + checksum_parts[1] = m_ir->CreateAdd(checksum_parts[1], m_ir->CreateCall(get_intrinsic(llvm::Intrinsic::aarch64_neon_uabd), {vls[0], vls[1]})); + checksum_parts[2] = m_ir->CreateAdd(checksum_parts[2], vls[2]); + checksum_parts[3] = m_ir->CreateAdd(checksum_parts[3], m_ir->CreateCall(get_intrinsic(llvm::Intrinsic::aarch64_neon_uabd), {vls[2], vls[3]})); + + for (u32 i = 0; i < 4; i++) + { + checksum[i] += words[i]; + checksum[4 + i] += words[i] > words[4 + i] ? words[i] - words[4 + i] : words[4 + i] - words[i]; + checksum[8 + i] += words[8 + i]; + checksum[12 + i] += words[8 + i] > words[12 + i] ? words[8 + i] - words[12 + i] : words[12 + i] - words[8 + i]; + } + + check_iterations++; + } + + llvm::Value* elem = nullptr; + + for (u32 part = 0; part < 4; part++) + { + auto* const_vector = ConstantDataVector::get(m_context, llvm::ArrayRef(checksum + part * 4, 4)); + llvm::Value* acc = m_ir->CreateXor(checksum_parts[part], const_vector); + acc = m_ir->CreateBitCast(acc, get_type()); + + for (u32 i = 0; i < 2; i++) + { + const auto lane = m_ir->CreateExtractElement(acc, i); + elem = elem ? m_ir->CreateOr(elem, lane) : lane; + } + } + + // Compare result with zero + const auto cond = m_ir->CreateICmpNE(elem, m_ir->getInt64(0)); + m_ir->CreateCondBr(cond, label_diff, label_body, m_md_unlikely); +#endif } #ifdef ARCH_ARM64 else From fa5b8994117867f2818223a28006b16770484c61 Mon Sep 17 00:00:00 2001 From: Malcolm Date: Sat, 30 May 2026 22:55:17 -0400 Subject: [PATCH 3/7] SPU LLVM: Reduce dot comparison result with addv --- rpcs3/Emu/Cell/SPULLVMRecompiler.cpp | 37 ++++++++++++++-------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp b/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp index be7fb77095..bb4aa217af 100644 --- a/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp +++ b/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp @@ -2140,33 +2140,32 @@ public: accumulate_pair(pending_cmp, llvm::ConstantInt::get(pending_cmp->getType(), -1, true)); } - llvm::Value* expected = nullptr; - if (m_use_dotprod) { - u32 expected_words[4]; - expected_words[0] = expected_hits * 4 * 0xff * 0xff; - expected_words[1] = expected_words[0]; - expected_words[2] = expected_words[0]; - expected_words[3] = expected_words[0]; - expected = ConstantDataVector::get(m_context, llvm::ArrayRef(expected_words, 4)); + llvm::Value* acc = m_ir->CreateAdd(m_ir->CreateAdd(acc0, acc1), m_ir->CreateAdd(acc2, acc3)); + acc = m_ir->CreateCall(get_intrinsic(llvm::Intrinsic::aarch64_neon_uaddv), {acc}); + + constexpr u64 dot_match_value = 0xff * 0xff; + const u32 expected = static_cast(expected_hits * 16 * dot_match_value); + const auto cond = m_ir->CreateICmpNE(acc, m_ir->getInt32(expected)); + m_ir->CreateCondBr(cond, label_diff, label_body, m_md_unlikely); } else { u16 expected_words[8]; std::fill_n(expected_words, 8, static_cast(expected_hits)); - expected = ConstantDataVector::get(m_context, llvm::ArrayRef(expected_words, 8)); + const auto expected = ConstantDataVector::get(m_context, llvm::ArrayRef(expected_words, 8)); + + llvm::Value* acc = m_ir->CreateAdd(m_ir->CreateAdd(acc0, acc1), m_ir->CreateAdd(acc2, acc3)); + acc = m_ir->CreateXor(acc, expected); + acc = m_ir->CreateBitCast(acc, get_type()); + + llvm::Value* elem = m_ir->CreateExtractElement(acc, u64{0}); + elem = m_ir->CreateOr(elem, m_ir->CreateExtractElement(acc, u64{1})); + + const auto cond = m_ir->CreateICmpNE(elem, m_ir->getInt64(0)); + m_ir->CreateCondBr(cond, label_diff, label_body, m_md_unlikely); } - - llvm::Value* acc = m_ir->CreateAdd(m_ir->CreateAdd(acc0, acc1), m_ir->CreateAdd(acc2, acc3)); - acc = m_ir->CreateXor(acc, expected); - acc = m_ir->CreateBitCast(acc, get_type()); - - llvm::Value* elem = m_ir->CreateExtractElement(acc, u64{0}); - elem = m_ir->CreateOr(elem, m_ir->CreateExtractElement(acc, u64{1})); - - const auto cond = m_ir->CreateICmpNE(elem, m_ir->getInt64(0)); - m_ir->CreateCondBr(cond, label_diff, label_body, m_md_unlikely); } #else else From 3f27cb8ff5b428c39f1de57995ab1886dcbc8387 Mon Sep 17 00:00:00 2001 From: Malcolm Date: Sat, 30 May 2026 23:20:34 -0400 Subject: [PATCH 4/7] SPU LLVM: Loop large checksum verification --- rpcs3/Emu/Cell/SPULLVMRecompiler.cpp | 179 +++++++++++++++++++++++---- 1 file changed, 158 insertions(+), 21 deletions(-) diff --git a/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp b/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp index bb4aa217af..c459febcdc 100644 --- a/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp +++ b/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp @@ -1846,8 +1846,79 @@ public: // The checksum path is still faster even on narrow hardware. if ((end - starta) >= 192 && !g_cfg.core.precise_spu_verification) { +#ifdef ARCH_ARM64 + constexpr u32 checksum_block_size = 96; +#else + constexpr u32 checksum_block_size = 64; +#endif + constexpr u32 checksum_loop_vectors = 16; + const u32 checksum_vectors_per_block = checksum_block_size / stride; + const u32 checksum_loop_blocks = (checksum_loop_vectors + checksum_vectors_per_block - 1) / checksum_vectors_per_block; + const u32 checksum_loop_size = checksum_block_size * checksum_loop_blocks; + const u32 checksum_loop_end = starta + ((end - starta) / checksum_loop_size) * checksum_loop_size; + + bool use_checksum_loop = (checksum_loop_end - starta) >= checksum_loop_size * 2; + + for (u32 j = starta; use_checksum_loop && j < checksum_loop_end; j += 4) + { + if (!func.data[(j - start) / 4]) + { + use_checksum_loop = false; + } + } + #ifndef ARCH_ARM64 - for (u32 j = starta; j < end; j += 64) + if (use_checksum_loop) + { + const auto acc_init = ConstantAggregateZero::get(get_type()); + const auto loop_block = BasicBlock::Create(m_context, "spu_checksum_loop", m_function); + const auto loop_next = BasicBlock::Create(m_context, "spu_checksum_next", m_function); + const auto loop_preheader = m_ir->GetInsertBlock(); + m_ir->CreateBr(loop_block); + + m_ir->SetInsertPoint(loop_block); + const auto offset = m_ir->CreatePHI(get_type(), 2); + const auto acc0_phi = m_ir->CreatePHI(get_type(), 2); + const auto acc1_phi = m_ir->CreatePHI(get_type(), 2); + + offset->addIncoming(m_ir->getInt32(0), loop_preheader); + acc0_phi->addIncoming(acc_init, loop_preheader); + acc1_phi->addIncoming(acc_init, loop_preheader); + + const auto offset64 = m_ir->CreateZExt(offset, get_type()); + llvm::Value* next_acc0 = acc0_phi; + llvm::Value* next_acc1 = acc1_phi; + + for (u32 block = 0; block < checksum_loop_blocks; block++) + { + const auto vls = m_ir->CreateAlignedLoad(get_type(), _ptr(data_addr, m_ir->CreateAdd(offset64, m_ir->getInt64(block * checksum_block_size))), llvm::MaybeAlign{4}); + + if (block & 1) + { + next_acc1 = m_ir->CreateAdd(next_acc1, vls); + } + else + { + next_acc0 = m_ir->CreateAdd(next_acc0, vls); + } + } + + const auto next_offset = m_ir->CreateAdd(offset, m_ir->getInt32(checksum_loop_size)); + const auto loop_again = m_ir->CreateICmpULT(next_offset, m_ir->getInt32(checksum_loop_end - starta)); + m_ir->CreateCondBr(loop_again, loop_block, loop_next); + + offset->addIncoming(next_offset, loop_block); + acc0_phi->addIncoming(next_acc0, loop_block); + acc1_phi->addIncoming(next_acc1, loop_block); + acc0 = next_acc0; + acc1 = next_acc1; + + check_iterations += (checksum_loop_end - starta) / checksum_block_size; + + m_ir->SetInsertPoint(loop_next); + } + + for (u32 j = use_checksum_loop ? checksum_loop_end : starta; j < end; j += checksum_block_size) { int indices[16]; bool holes = false; @@ -1932,21 +2003,95 @@ public: const auto cond = m_ir->CreateICmpNE(elem, m_ir->getInt64(0)); m_ir->CreateCondBr(cond, label_diff, label_body, m_md_unlikely); #else - // - // - // - const auto acc_init = ConstantAggregateZero::get(get_type()); llvm::Value* checksum_parts[4] = {acc_init, acc_init, acc_init, acc_init}; u32 checksum[16] = {0}; - for (u32 j = starta; j < end; j += 64) + const auto update_checksum = [&](const u32* words) { - llvm::Value* vls[4] = {}; - u32 words[16] = {}; - bool any_data = false; + for (u32 i = 0; i < 4; i++) + { + checksum[i] += words[i]; + checksum[4 + i] += words[4 + i] > words[8 + i] ? words[4 + i] - words[8 + i] : words[8 + i] - words[4 + i]; + checksum[8 + i] += words[12 + i]; + checksum[12 + i] += words[16 + i] > words[20 + i] ? words[16 + i] - words[20 + i] : words[20 + i] - words[16 + i]; + } + }; + + if (use_checksum_loop) + { + for (u32 j = starta; j < checksum_loop_end; j += checksum_block_size) + { + u32 words[24]; + + for (u32 i = 0; i < 24; i++) + { + words[i] = func.data[(j + i * 4 - start) / 4]; + } + + update_checksum(words); + } + + const auto loop_block = BasicBlock::Create(m_context, "spu_checksum_loop", m_function); + const auto loop_next = BasicBlock::Create(m_context, "spu_checksum_next", m_function); + const auto loop_preheader = m_ir->GetInsertBlock(); + m_ir->CreateBr(loop_block); + + m_ir->SetInsertPoint(loop_block); + const auto offset = m_ir->CreatePHI(get_type(), 2); + llvm::PHINode* acc_phi[4]; + llvm::Value* next_acc[4]; for (u32 part = 0; part < 4; part++) + { + acc_phi[part] = m_ir->CreatePHI(get_type(), 2); + acc_phi[part]->addIncoming(checksum_parts[part], loop_preheader); + next_acc[part] = acc_phi[part]; + } + + offset->addIncoming(m_ir->getInt32(0), loop_preheader); + + const auto offset64 = m_ir->CreateZExt(offset, get_type()); + + for (u32 block = 0; block < checksum_loop_blocks; block++) + { + llvm::Value* vls[6]; + + for (u32 part = 0; part < 6; part++) + { + vls[part] = m_ir->CreateAlignedLoad(get_type(), _ptr(data_addr, m_ir->CreateAdd(offset64, m_ir->getInt64(block * checksum_block_size + part * 16))), llvm::MaybeAlign{4}); + } + + next_acc[0] = m_ir->CreateAdd(next_acc[0], vls[0]); + next_acc[1] = m_ir->CreateAdd(next_acc[1], m_ir->CreateCall(get_intrinsic(llvm::Intrinsic::aarch64_neon_uabd), {vls[1], vls[2]})); + next_acc[2] = m_ir->CreateAdd(next_acc[2], vls[3]); + next_acc[3] = m_ir->CreateAdd(next_acc[3], m_ir->CreateCall(get_intrinsic(llvm::Intrinsic::aarch64_neon_uabd), {vls[4], vls[5]})); + } + + const auto next_offset = m_ir->CreateAdd(offset, m_ir->getInt32(checksum_loop_size)); + const auto loop_again = m_ir->CreateICmpULT(next_offset, m_ir->getInt32(checksum_loop_end - starta)); + m_ir->CreateCondBr(loop_again, loop_block, loop_next); + + offset->addIncoming(next_offset, loop_block); + + for (u32 part = 0; part < 4; part++) + { + acc_phi[part]->addIncoming(next_acc[part], loop_block); + checksum_parts[part] = next_acc[part]; + } + + check_iterations += (checksum_loop_end - starta) / checksum_block_size; + + m_ir->SetInsertPoint(loop_next); + } + + for (u32 j = use_checksum_loop ? checksum_loop_end : starta; j < end; j += checksum_block_size) + { + llvm::Value* vls[6] = {}; + u32 words[24] = {}; + bool any_data = false; + + for (u32 part = 0; part < 6; part++) { int indices[4]; bool holes = false; @@ -1993,20 +2138,12 @@ public: continue; } - // Keep the checksum logically 512-bit wide while using four NEON vectors. - // The add/uabd pattern is intended to select UABA for the difference lanes. checksum_parts[0] = m_ir->CreateAdd(checksum_parts[0], vls[0]); - checksum_parts[1] = m_ir->CreateAdd(checksum_parts[1], m_ir->CreateCall(get_intrinsic(llvm::Intrinsic::aarch64_neon_uabd), {vls[0], vls[1]})); - checksum_parts[2] = m_ir->CreateAdd(checksum_parts[2], vls[2]); - checksum_parts[3] = m_ir->CreateAdd(checksum_parts[3], m_ir->CreateCall(get_intrinsic(llvm::Intrinsic::aarch64_neon_uabd), {vls[2], vls[3]})); + checksum_parts[1] = m_ir->CreateAdd(checksum_parts[1], m_ir->CreateCall(get_intrinsic(llvm::Intrinsic::aarch64_neon_uabd), {vls[1], vls[2]})); + checksum_parts[2] = m_ir->CreateAdd(checksum_parts[2], vls[3]); + checksum_parts[3] = m_ir->CreateAdd(checksum_parts[3], m_ir->CreateCall(get_intrinsic(llvm::Intrinsic::aarch64_neon_uabd), {vls[4], vls[5]})); - for (u32 i = 0; i < 4; i++) - { - checksum[i] += words[i]; - checksum[4 + i] += words[i] > words[4 + i] ? words[i] - words[4 + i] : words[4 + i] - words[i]; - checksum[8 + i] += words[8 + i]; - checksum[12 + i] += words[8 + i] > words[12 + i] ? words[8 + i] - words[12 + i] : words[12 + i] - words[8 + i]; - } + update_checksum(words); check_iterations++; } From 1b54e8910c6bbd1be27c30651efeecdbf8638491 Mon Sep 17 00:00:00 2001 From: Malcolm Date: Mon, 1 Jun 2026 16:45:22 -0400 Subject: [PATCH 5/7] SPU: Add some comments clarifying very obtuse code --- rpcs3/Emu/Cell/SPULLVMRecompiler.cpp | 13 +++++++++++++ rpcs3/Emu/Cell/SPUThread.cpp | 9 +++++++++ 2 files changed, 22 insertions(+) diff --git a/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp b/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp index c459febcdc..c8c3db5c29 100644 --- a/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp +++ b/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp @@ -1847,8 +1847,11 @@ public: if ((end - starta) >= 192 && !g_cfg.core.precise_spu_verification) { #ifdef ARCH_ARM64 + // Loop if there is at least 288 bytes of data to checksum on ARM. + // Each ARM checksum block consumes 6 NEON vectors: 2 direct adds and 2 UABD accumulates. constexpr u32 checksum_block_size = 96; #else + // Loop if there is atleast (16 * stride) bytes of data to checksum to save some instruction cache constexpr u32 checksum_block_size = 64; #endif constexpr u32 checksum_loop_vectors = 16; @@ -2003,6 +2006,13 @@ public: const auto cond = m_ir->CreateICmpNE(elem, m_ir->getInt64(0)); m_ir->CreateCondBr(cond, label_diff, label_body, m_md_unlikely); #else + // Very cursed "checksumming" code + // 96 bytes per ARM checksum step + //vls[0] -> add + //vls[1], vls[2] -> uaba + //vls[3] -> add + //vls[4], vls[5] -> uaba + //This allows us to save on some ALU ops relative to load instructions const auto acc_init = ConstantAggregateZero::get(get_type()); llvm::Value* checksum_parts[4] = {acc_init, acc_init, acc_init, acc_init}; u32 checksum[16] = {0}; @@ -2237,6 +2247,9 @@ public: m_ir->CreateBitCast(expected, get_type())), get_type()); }; + // Multiply accumulate based comparison + // See comment above cmp16_pair_accum_arm64 in SPUThread.cpp + // Dotproduct instructions have slightly higher throughput on many common ARM cores const auto accumulate_pair = [&](llvm::Value* lhs, llvm::Value* rhs) { llvm::Value*& acc = *accs[acc_index]; diff --git a/rpcs3/Emu/Cell/SPUThread.cpp b/rpcs3/Emu/Cell/SPUThread.cpp index 93af9e8b03..dee3f15309 100644 --- a/rpcs3/Emu/Cell/SPUThread.cpp +++ b/rpcs3/Emu/Cell/SPUThread.cpp @@ -245,6 +245,15 @@ static FORCE_INLINE bool cmp_rdata_avx(const __m256i* lhs, const __m256i* rhs) } #endif +// Insane idea to accelerate comparisons on Neon with a fixed length +// Common ARM chips like the a78 and a715 can Perform 3 128b loads/clock +// But only execute 2 128b instructions on the ALU per clock +// To consume data any faster, we need to use ALU instructions that take 3 inputs +// Idea: compare data, filling each lane with either -1 or 0 +// Then multiply each pair of comparisons together, resulting in 1 if both pairs were -1 +// Accummulate those results, and compare the accumulated value to the expected count +// Benchmarks showed this to be faster even on arm machines that aren't capable of more loads than ALU operations +// Tested on Tensor G1, Snapdragon 8 gen 2, and the Snapdragon 8 Elite gen 5 #if defined(ARCH_ARM64) static FORCE_INLINE int16x8_t cmp16_pair_accum_arm64( int16x8_t acc, const v128& lhs0, const v128& rhs0, const v128& lhs1, const v128& rhs1) From 03647fdb4e6dfd9f46dc80fc438d47f7a64f6fbf Mon Sep 17 00:00:00 2001 From: Malcolm Date: Tue, 2 Jun 2026 10:36:43 -0400 Subject: [PATCH 6/7] SPU LLVM: Break out of checksum hole scan --- rpcs3/Emu/Cell/SPULLVMRecompiler.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp b/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp index c8c3db5c29..73396eb08a 100644 --- a/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp +++ b/rpcs3/Emu/Cell/SPULLVMRecompiler.cpp @@ -1867,6 +1867,7 @@ public: if (!func.data[(j - start) / 4]) { use_checksum_loop = false; + break; } } From 6647c5a2bd71c3811fc6749e2d9bb78080407139 Mon Sep 17 00:00:00 2001 From: Cicero Hellmann Ratti Date: Wed, 1 Jul 2026 13:08:54 +0200 Subject: [PATCH 7/7] Fallback unsupported database SPU decoder on ARM64 (#18651) ## Summary - fall back from database-applied `SPU Decoder: Recompiler (ASMJIT)` to `Recompiler (LLVM)` on ARM64 builds during boot - log the fallback in `fixup_settings()` instead of letting SPU initialization fail later ## Problem `Need for Speed: Most Wanted [BLES01659]` receives `SPU Decoder: Recompiler (ASMJIT)` from the config database. On ARM64 builds that decoder is unsupported, which caused RPCS3 to abort during SPU initialization with `Unsupported spu decoder` while the UI remained on the loading screen. ## Validation - built RPCS3 locally on macOS Apple Silicon - booted `BLES01659` through the database config path with the custom per-game override temporarily removed - verified the log now shows the ARM64 fallback warning and `SPU Decoder: Recompiler (LLVM)` instead of the previous fatal error - observed the title proceed past the old boot blocker into active RSX program compilation --------- Co-authored-by: Elad <18193363+elad335@users.noreply.github.com> --- rpcs3/Emu/System.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/rpcs3/Emu/System.cpp b/rpcs3/Emu/System.cpp index 8f36b9a899..091d45e510 100644 --- a/rpcs3/Emu/System.cpp +++ b/rpcs3/Emu/System.cpp @@ -336,6 +336,20 @@ static void fixup_settings(const psf::registry* _psf) } } +#if defined(ARCH_ARM64) + if (g_cfg.core.spu_decoder == spu_decoder_type::asmjit) + { +#ifdef LLVM_AVAILABLE + constexpr auto arm64_spu_fallback = spu_decoder_type::dynamic; +#else + constexpr auto arm64_spu_fallback = spu_decoder_type::_static; +#endif + sys_log.warning("The setting '%s' is currently not supported on ARM64 builds and will therefore be changed from '%s' to '%s' during emulation.", + g_cfg.core.spu_decoder.get_name(), spu_decoder_type::asmjit, arm64_spu_fallback); + g_cfg.core.spu_decoder.set(arm64_spu_fallback); + } +#endif + if (const u32 psf_resolution = _psf ? psf::get_integer(*_psf, "RESOLUTION", 0) : 0) { const std::map resolutions