From 2fe20d8d4f6d9a7757713e27f5c2d7c7b3c157c4 Mon Sep 17 00:00:00 2001 From: "Herman S." <429230+has207@users.noreply.github.com> Date: Tue, 24 Mar 2026 15:48:08 +0900 Subject: [PATCH] [A64] Inline LVL/LVR using NEON TBL instead of C helpers Replace CallNativeSafe round-trips with inline TBL shuffles. LVL uses a single-register TBL where out-of-range indices naturally produce zero. LVR uses a 2-register TBL over {zeros, mem} so indices 0-15 map to zeros and 16-31 map to memory bytes. --- src/xenia/cpu/backend/a64/a64_seq_vector.cc | 64 +++++++++++++++++---- 1 file changed, 52 insertions(+), 12 deletions(-) diff --git a/src/xenia/cpu/backend/a64/a64_seq_vector.cc b/src/xenia/cpu/backend/a64/a64_seq_vector.cc index 1708fb954..567f8a59a 100644 --- a/src/xenia/cpu/backend/a64/a64_seq_vector.cc +++ b/src/xenia/cpu/backend/a64/a64_seq_vector.cc @@ -1824,15 +1824,33 @@ EMITTER_OPCODE_TABLE(OPCODE_UNPACK, UNPACK); // ============================================================================ struct LVL_V128 : Sequence> { static void Emit(A64Emitter& e, const EmitArgType& i) { + // Inline LVL using TBL. The bswap-within-lanes base pattern plus the + // address offset gives a TBL control vector. Indices >= 16 naturally + // produce zero from TBL, which is the correct LVL behaviour. auto addr = ComputeMemoryAddress(e, i.src1); int d = i.dest.reg().getIdx(); - // x1 = host address = membase + guest_addr - e.add(e.x1, e.GetMembaseReg(), addr); - // x2 = result pointer (scratch area) - e.add(e.x2, e.sp, static_cast(StackLayout::GUEST_SCRATCH)); - e.CallNativeSafe(reinterpret_cast(EmulateLVL)); - e.ldr(QReg(d), - ptr(e.sp, static_cast(StackLayout::GUEST_SCRATCH))); + + // x0 = host address + e.add(e.x0, e.GetMembaseReg(), addr); + // w17 = offset within 16-byte block + e.and_(e.w17, e.w0, 0xF); + // Align address down to 16-byte boundary and load. + e.and_(e.x0, e.x0, ~0xFull); + e.ldr(QReg(0), ptr(e.x0)); + + // Build bswap-within-lanes base pattern in v1: + // {3,2,1,0, 7,6,5,4, 11,10,9,8, 15,14,13,12} + e.mov(e.x0, 0x0405060700010203ull); + e.fmov(DReg(1), e.x0); + e.mov(e.x0, 0x0C0D0E0F08090A0Bull); + e.ins(VReg(1).d2[1], e.x0); + + // ctrl = base + offset; TBL gives 0 for ctrl >= 16. + e.dup(VReg(2).b16, e.w17); + e.add(VReg(1).b16, VReg(1).b16, VReg(2).b16); + + // Single-register TBL: dest[i] = mem[ctrl[i]] or 0. + e.tbl(VReg(d).b16, VReg(0).b16, 1, VReg(1).b16); } }; EMITTER_OPCODE_TABLE(OPCODE_LVL, LVL_V128); @@ -1842,13 +1860,35 @@ EMITTER_OPCODE_TABLE(OPCODE_LVL, LVL_V128); // ============================================================================ struct LVR_V128 : Sequence> { static void Emit(A64Emitter& e, const EmitArgType& i) { + // Inline LVR using a 2-register TBL. Table = {zeros, mem}. + // Indices 0-15 read zeros (from v0), 16-31 read mem (from v1). + // base + offset produces indices > 15 exactly where LVR should output + // the memory bytes, and <= 15 where it should output zero. + // When offset == 0, all indices are 0-15 → all zeros, which is correct. auto addr = ComputeMemoryAddress(e, i.src1); int d = i.dest.reg().getIdx(); - e.add(e.x1, e.GetMembaseReg(), addr); - e.add(e.x2, e.sp, static_cast(StackLayout::GUEST_SCRATCH)); - e.CallNativeSafe(reinterpret_cast(EmulateLVR)); - e.ldr(QReg(d), - ptr(e.sp, static_cast(StackLayout::GUEST_SCRATCH))); + + // x0 = host address + e.add(e.x0, e.GetMembaseReg(), addr); + // w17 = offset + e.and_(e.w17, e.w0, 0xF); + // Align and load. v0=zeros (table reg 0), v1=mem (table reg 1). + e.movi(VReg(0).d2, 0); + e.and_(e.x0, e.x0, ~0xFull); + e.ldr(QReg(1), ptr(e.x0)); + + // Build base pattern in v2. + e.mov(e.x0, 0x0405060700010203ull); + e.fmov(DReg(2), e.x0); + e.mov(e.x0, 0x0C0D0E0F08090A0Bull); + e.ins(VReg(2).d2[1], e.x0); + + // ctrl = base + offset. + e.dup(VReg(3).b16, e.w17); + e.add(VReg(2).b16, VReg(2).b16, VReg(3).b16); + + // 2-register TBL over {v0, v1}. + e.tbl(VReg(d).b16, VReg(0).b16, 2, VReg(2).b16); } }; EMITTER_OPCODE_TABLE(OPCODE_LVR, LVR_V128);