From 06f56a20eaddec9941bf97c00b7768773237c973 Mon Sep 17 00:00:00 2001 From: "Herman S." <429230+has207@users.noreply.github.com> Date: Sun, 29 Mar 2026 18:35:40 +0900 Subject: [PATCH] [A64] Inline V128 bit-shifts with NEON, remove C helper thunks --- src/xenia/cpu/backend/a64/a64_sequences.cc | 108 ++++++++------------- src/xenia/cpu/testing/shl_test.cc | 48 +++++++++ 2 files changed, 86 insertions(+), 70 deletions(-) diff --git a/src/xenia/cpu/backend/a64/a64_sequences.cc b/src/xenia/cpu/backend/a64/a64_sequences.cc index 29c59d51a..ead271af3 100644 --- a/src/xenia/cpu/backend/a64/a64_sequences.cc +++ b/src/xenia/cpu/backend/a64/a64_sequences.cc @@ -1934,52 +1934,36 @@ struct SHL_I64 : Sequence> { } } }; -// SHL_V128 C helper: shift entire 128-bit vector left by N BITS (0-7). -// Args: x0=PPCContext* (unused), x1=pointer to scratch area -// [0..15] = src vec128, [16] = shift amount in bits (0-7). -// Result stored in [0..15]. -static void EmulateShlV128(void* /*ctx*/, void* vdata) { - auto* bytes = reinterpret_cast(vdata); - uint8_t shamt = bytes[16] & 0x7; - if (shamt == 0) return; - // PPC left shift: bits move from PPC byte 15 (LSB) toward PPC byte 0 (MSB). - // PPC byte i maps to LE memory byte i^3 (byte-swap within 32-bit words). - for (int i = 0; i < 15; ++i) { - bytes[i ^ 0x3] = - (bytes[i ^ 0x3] << shamt) | (bytes[(i + 1) ^ 0x3] >> (8 - shamt)); - } - bytes[15 ^ 0x3] = bytes[15 ^ 0x3] << shamt; -} - struct SHL_V128 : Sequence> { static void Emit(A64Emitter& e, const EmitArgType& i) { + // PPC 128-bit SHL by N bits (0-7). The value is stored as 4 word-swapped + // 32-bit lanes. Carries flow from higher NEON lanes to lower: + // lane[i] = (lane[i] << N) | (lane[i+1] >> (32-N)) int s = SrcVReg(e, i.src1, 0); int d = i.dest.reg().getIdx(); - // PPC 128-bit SHL operates on PPC byte order which is word-reversed - // relative to NEON bit significance. Always use the C helper which - // correctly handles the byte-swap within 32-bit words (like x64 does). if (i.src2.is_constant) { uint8_t sh = i.src2.constant() & 0x7; if (sh == 0) { - if (d != s) e.orr(VReg(d).b16, VReg(s).b16, VReg(s).b16); + if (d != s) e.mov(VReg(d).b16, VReg(s).b16); return; } - e.str(QReg(s), - ptr(e.sp, static_cast(StackLayout::GUEST_SCRATCH))); - e.mov(e.w0, static_cast(sh)); - e.strb(e.w0, - ptr(e.sp, static_cast(StackLayout::GUEST_SCRATCH + 16))); + // Read carry before writing result (handles dest==src aliasing). + e.ushr(VReg(0).s4, VReg(s).s4, 32 - sh); + e.shl(VReg(d).s4, VReg(s).s4, sh); } else { - e.str(QReg(s), - ptr(e.sp, static_cast(StackLayout::GUEST_SCRATCH))); - e.mov(e.w0, WReg(i.src2.reg().getIdx())); - e.strb(e.w0, - ptr(e.sp, static_cast(StackLayout::GUEST_SCRATCH + 16))); + // Variable shift: mask to 0-7, splat, use ushl. + e.and_(e.w0, WReg(i.src2.reg().getIdx()), 7); + e.dup(VReg(1).s4, e.w0); + e.movi(VReg(2).s4, 32); + e.sub(VReg(2).s4, VReg(2).s4, VReg(1).s4); // 32-N + e.neg(VReg(2).s4, VReg(2).s4); // -(32-N) for right shift + e.ushl(VReg(0).s4, VReg(s).s4, VReg(2).s4); // carry: lane >> (32-N) + e.ushl(VReg(d).s4, VReg(s).s4, VReg(1).s4); // result: lane << N } - e.add(e.x1, e.sp, static_cast(StackLayout::GUEST_SCRATCH)); - e.CallNativeSafe(reinterpret_cast(EmulateShlV128)); - e.ldr(QReg(d), - ptr(e.sp, static_cast(StackLayout::GUEST_SCRATCH))); + // Shift carries from lane i+1 to lane i; lane 3 gets zero. + e.movi(VReg(1).s4, 0); + e.ext(VReg(0).b16, VReg(0).b16, VReg(1).b16, 4); + e.orr(VReg(d).b16, VReg(d).b16, VReg(0).b16); } }; EMITTER_OPCODE_TABLE(OPCODE_SHL, SHL_I8, SHL_I16, SHL_I32, SHL_I64, SHL_V128); @@ -2075,52 +2059,36 @@ struct SHR_I64 : Sequence> { } } }; -// SHR_V128 C helper: shift entire 128-bit vector right by N BITS (0-7). -// Args: x0=PPCContext* (unused), x1=pointer to scratch area -// [0..15] = src vec128, [16] = shift amount in bits (0-7). -// Result stored in [0..15]. -static void EmulateShrV128(void* /*ctx*/, void* vdata) { - auto* bytes = reinterpret_cast(vdata); - uint8_t shamt = bytes[16] & 0x7; - if (shamt == 0) return; - // PPC right shift: bits move from PPC byte 0 (MSB) toward PPC byte 15 (LSB). - // PPC byte i maps to LE memory byte i^3 (byte-swap within 32-bit words). - for (int i = 15; i > 0; --i) { - bytes[i ^ 0x3] = - (bytes[i ^ 0x3] >> shamt) | (bytes[(i - 1) ^ 0x3] << (8 - shamt)); - } - bytes[0 ^ 0x3] = bytes[0 ^ 0x3] >> shamt; -} - struct SHR_V128 : Sequence> { static void Emit(A64Emitter& e, const EmitArgType& i) { + // PPC 128-bit SHR by N bits (0-7). Carries flow from lower NEON lanes + // to higher: + // lane[i] = (lane[i] >> N) | (lane[i-1] << (32-N)) int s = SrcVReg(e, i.src1, 0); int d = i.dest.reg().getIdx(); - // PPC 128-bit SHR operates on PPC byte order which is word-reversed - // relative to NEON bit significance. Always use the C helper which - // correctly handles the byte-swap within 32-bit words (like x64 does). if (i.src2.is_constant) { uint8_t sh = i.src2.constant() & 0x7; if (sh == 0) { - if (d != s) e.orr(VReg(d).b16, VReg(s).b16, VReg(s).b16); + if (d != s) e.mov(VReg(d).b16, VReg(s).b16); return; } - e.str(QReg(s), - ptr(e.sp, static_cast(StackLayout::GUEST_SCRATCH))); - e.mov(e.w0, static_cast(sh)); - e.strb(e.w0, - ptr(e.sp, static_cast(StackLayout::GUEST_SCRATCH + 16))); + // Read carry before writing result (handles dest==src aliasing). + e.shl(VReg(0).s4, VReg(s).s4, 32 - sh); + e.ushr(VReg(d).s4, VReg(s).s4, sh); } else { - e.str(QReg(s), - ptr(e.sp, static_cast(StackLayout::GUEST_SCRATCH))); - e.mov(e.w0, WReg(i.src2.reg().getIdx())); - e.strb(e.w0, - ptr(e.sp, static_cast(StackLayout::GUEST_SCRATCH + 16))); + // Variable shift: mask to 0-7, splat, use ushl. + e.and_(e.w0, WReg(i.src2.reg().getIdx()), 7); + e.dup(VReg(1).s4, e.w0); + e.movi(VReg(2).s4, 32); + e.sub(VReg(2).s4, VReg(2).s4, VReg(1).s4); // 32-N + e.ushl(VReg(0).s4, VReg(s).s4, VReg(2).s4); // carry: lane << (32-N) + e.neg(VReg(1).s4, VReg(1).s4); // -N for right shift + e.ushl(VReg(d).s4, VReg(s).s4, VReg(1).s4); // result: lane >> N } - e.add(e.x1, e.sp, static_cast(StackLayout::GUEST_SCRATCH)); - e.CallNativeSafe(reinterpret_cast(EmulateShrV128)); - e.ldr(QReg(d), - ptr(e.sp, static_cast(StackLayout::GUEST_SCRATCH))); + // Shift carries from lane i-1 to lane i; lane 0 gets zero. + e.movi(VReg(1).s4, 0); + e.ext(VReg(0).b16, VReg(1).b16, VReg(0).b16, 12); + e.orr(VReg(d).b16, VReg(d).b16, VReg(0).b16); } }; EMITTER_OPCODE_TABLE(OPCODE_SHR, SHR_I8, SHR_I16, SHR_I32, SHR_I64, SHR_V128); diff --git a/src/xenia/cpu/testing/shl_test.cc b/src/xenia/cpu/testing/shl_test.cc index d63ddf5a0..b08afd630 100644 --- a/src/xenia/cpu/testing/shl_test.cc +++ b/src/xenia/cpu/testing/shl_test.cc @@ -9,6 +9,7 @@ #include "xenia/cpu/testing/util.h" +using namespace xe; using namespace xe::cpu::hir; using namespace xe::cpu; using namespace xe::cpu::testing; @@ -232,3 +233,50 @@ TEST_CASE("SHL_I64", "[instr]") { REQUIRE(result == 0x8000000000000000ull); }); } + +TEST_CASE("SHL_V128", "[instr]") { + TestFunction test([](HIRBuilder& b) { + StoreVR(b, 3, b.Shl(LoadVR(b, 4), b.Truncate(LoadGPR(b, 1), INT8_TYPE))); + b.Return(); + }); + // Shift by 0 — identity. + test.Run( + [](PPCContext* ctx) { + ctx->r[1] = 0; + ctx->v[4] = vec128i(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF); + }, + [](PPCContext* ctx) { + REQUIRE(ctx->v[3] == + vec128i(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF)); + }); + // Shift by 1 — MSB of each word carries into LSB of the previous word. + test.Run( + [](PPCContext* ctx) { + ctx->r[1] = 1; + ctx->v[4] = vec128i(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF); + }, + [](PPCContext* ctx) { + REQUIRE(ctx->v[3] == + vec128i(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFE)); + }); + // Shift by 4 — verify carry propagation across word boundaries. + test.Run( + [](PPCContext* ctx) { + ctx->r[1] = 4; + ctx->v[4] = vec128i(0x01234567, 0x89ABCDEF, 0x01234567, 0x89ABCDEF); + }, + [](PPCContext* ctx) { + REQUIRE(ctx->v[3] == + vec128i(0x12345678, 0x9ABCDEF0, 0x12345678, 0x9ABCDEF0)); + }); + // Shift by 8 — masked to 0, identity. + test.Run( + [](PPCContext* ctx) { + ctx->r[1] = 8; + ctx->v[4] = vec128i(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF); + }, + [](PPCContext* ctx) { + REQUIRE(ctx->v[3] == + vec128i(0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF)); + }); +}