From 02d2cb5cc4bfbf047a3ee136923c263ae11f8356 Mon Sep 17 00:00:00 2001 From: "Herman S." <429230+has207@users.noreply.github.com> Date: Tue, 24 Mar 2026 15:12:10 +0900 Subject: [PATCH] [A64] Add v8-v15 to allocatable VEC pool Expands the vector register allocator from 20 to 28 registers by including v8-v15. The HostToGuestThunk now saves/restores full Q8-Q15 (128-bit) instead of just D8-D15, and the GuestToHostThunk adds Q8-Q15 to its save/restore set since AAPCS64 only preserves their lower 64 bits across C calls. Reduces register pressure for VMX-heavy guest code at the cost of slightly larger thunk stack frames. --- src/xenia/cpu/backend/a64/a64_backend.cc | 99 +++++++++++--------- src/xenia/cpu/backend/a64/a64_emitter.cc | 5 +- src/xenia/cpu/backend/a64/a64_emitter.h | 4 +- src/xenia/cpu/backend/a64/a64_stack_layout.h | 37 +++----- 4 files changed, 74 insertions(+), 71 deletions(-) diff --git a/src/xenia/cpu/backend/a64/a64_backend.cc b/src/xenia/cpu/backend/a64/a64_backend.cc index fffa3edb5..701389fe4 100644 --- a/src/xenia/cpu/backend/a64/a64_backend.cc +++ b/src/xenia/cpu/backend/a64/a64_backend.cc @@ -112,11 +112,11 @@ HostToGuestThunk A64HelperEmitter::EmitHostToGuestThunk() { stp(x27, x28, ptr(sp, 0x40)); stp(x29, x30, ptr(sp, 0x50)); - // Save callee-saved NEON regs: d8-d15 - stp(d8, d9, ptr(sp, 0x60)); - stp(d10, d11, ptr(sp, 0x70)); - stp(d12, d13, ptr(sp, 0x80)); - stp(d14, d15, ptr(sp, 0x90)); + // Save callee-saved NEON regs: full q8-q15 (JIT uses all 128 bits). + stp(Xbyak_aarch64::QReg(8), Xbyak_aarch64::QReg(9), ptr(sp, 0x60)); + stp(Xbyak_aarch64::QReg(10), Xbyak_aarch64::QReg(11), ptr(sp, 0x80)); + stp(Xbyak_aarch64::QReg(12), Xbyak_aarch64::QReg(13), ptr(sp, 0xA0)); + stp(Xbyak_aarch64::QReg(14), Xbyak_aarch64::QReg(15), ptr(sp, 0xC0)); code_offsets.body = getSize(); @@ -146,11 +146,11 @@ HostToGuestThunk A64HelperEmitter::EmitHostToGuestThunk() { code_offsets.epilog = getSize(); - // Restore callee-saved NEON regs. - ldp(d14, d15, ptr(sp, 0x90)); - ldp(d12, d13, ptr(sp, 0x80)); - ldp(d10, d11, ptr(sp, 0x70)); - ldp(d8, d9, ptr(sp, 0x60)); + // Restore callee-saved NEON regs (full q8-q15). + ldp(Xbyak_aarch64::QReg(14), Xbyak_aarch64::QReg(15), ptr(sp, 0xC0)); + ldp(Xbyak_aarch64::QReg(12), Xbyak_aarch64::QReg(13), ptr(sp, 0xA0)); + ldp(Xbyak_aarch64::QReg(10), Xbyak_aarch64::QReg(11), ptr(sp, 0x80)); + ldp(Xbyak_aarch64::QReg(8), Xbyak_aarch64::QReg(9), ptr(sp, 0x60)); // Restore callee-saved GPRs. ldp(x29, x30, ptr(sp, 0x50)); @@ -201,41 +201,50 @@ GuestToHostThunk A64HelperEmitter::EmitGuestToHostThunk() { code_offsets.prolog = getSize(); - // The guest JIT uses v4-v7 and v16-v31 as allocatable VEC regs. - // These are caller-saved in AAPCS64, so the host C function WILL clobber - // them. We must save all guest-allocated VEC regs (full 128-bit Q regs). + // The guest JIT uses v4-v15, v16-v31 as allocatable VEC regs. + // v0-v7, v16-v31 are caller-saved in AAPCS64 (fully clobbered by C). + // v8-v15 lower 64 bits are callee-saved, but upper 64 bits are not. + // We must save all guest-allocated VEC regs (full 128-bit Q regs). // GPRs x19-x28 are callee-saved in AAPCS64, so the C function preserves them. // // Stack layout: // q4, q5 sp + 0x000 (32 bytes) // q6, q7 sp + 0x020 - // q16, q17 sp + 0x040 - // q18, q19 sp + 0x060 - // q20, q21 sp + 0x080 - // q22, q23 sp + 0x0A0 - // q24, q25 sp + 0x0C0 - // q26, q27 sp + 0x0E0 - // q28, q29 sp + 0x100 - // q30, q31 sp + 0x120 - // x29, x30 sp + 0x140 - // Total: 0x150 = 336 bytes (16-byte aligned) - const size_t g2h_stack = 336; + // q8, q9 sp + 0x040 + // q10, q11 sp + 0x060 + // q12, q13 sp + 0x080 + // q14, q15 sp + 0x0A0 + // q16, q17 sp + 0x0C0 + // q18, q19 sp + 0x0E0 + // q20, q21 sp + 0x100 + // q22, q23 sp + 0x120 + // q24, q25 sp + 0x140 + // q26, q27 sp + 0x160 + // q28, q29 sp + 0x180 + // q30, q31 sp + 0x1A0 + // x29, x30 sp + 0x1C0 + // Total: 0x1D0 = 464 bytes (16-byte aligned) + const size_t g2h_stack = 464; sub(sp, sp, static_cast(g2h_stack)); code_offsets.prolog_stack_alloc = getSize(); // Save guest-allocated VEC regs (full Q = 128-bit). stp(Xbyak_aarch64::QReg(4), Xbyak_aarch64::QReg(5), ptr(sp, 0x000)); stp(Xbyak_aarch64::QReg(6), Xbyak_aarch64::QReg(7), ptr(sp, 0x020)); - stp(Xbyak_aarch64::QReg(16), Xbyak_aarch64::QReg(17), ptr(sp, 0x040)); - stp(Xbyak_aarch64::QReg(18), Xbyak_aarch64::QReg(19), ptr(sp, 0x060)); - stp(Xbyak_aarch64::QReg(20), Xbyak_aarch64::QReg(21), ptr(sp, 0x080)); - stp(Xbyak_aarch64::QReg(22), Xbyak_aarch64::QReg(23), ptr(sp, 0x0A0)); - stp(Xbyak_aarch64::QReg(24), Xbyak_aarch64::QReg(25), ptr(sp, 0x0C0)); - stp(Xbyak_aarch64::QReg(26), Xbyak_aarch64::QReg(27), ptr(sp, 0x0E0)); - stp(Xbyak_aarch64::QReg(28), Xbyak_aarch64::QReg(29), ptr(sp, 0x100)); - stp(Xbyak_aarch64::QReg(30), Xbyak_aarch64::QReg(31), ptr(sp, 0x120)); + stp(Xbyak_aarch64::QReg(8), Xbyak_aarch64::QReg(9), ptr(sp, 0x040)); + stp(Xbyak_aarch64::QReg(10), Xbyak_aarch64::QReg(11), ptr(sp, 0x060)); + stp(Xbyak_aarch64::QReg(12), Xbyak_aarch64::QReg(13), ptr(sp, 0x080)); + stp(Xbyak_aarch64::QReg(14), Xbyak_aarch64::QReg(15), ptr(sp, 0x0A0)); + stp(Xbyak_aarch64::QReg(16), Xbyak_aarch64::QReg(17), ptr(sp, 0x0C0)); + stp(Xbyak_aarch64::QReg(18), Xbyak_aarch64::QReg(19), ptr(sp, 0x0E0)); + stp(Xbyak_aarch64::QReg(20), Xbyak_aarch64::QReg(21), ptr(sp, 0x100)); + stp(Xbyak_aarch64::QReg(22), Xbyak_aarch64::QReg(23), ptr(sp, 0x120)); + stp(Xbyak_aarch64::QReg(24), Xbyak_aarch64::QReg(25), ptr(sp, 0x140)); + stp(Xbyak_aarch64::QReg(26), Xbyak_aarch64::QReg(27), ptr(sp, 0x160)); + stp(Xbyak_aarch64::QReg(28), Xbyak_aarch64::QReg(29), ptr(sp, 0x180)); + stp(Xbyak_aarch64::QReg(30), Xbyak_aarch64::QReg(31), ptr(sp, 0x1A0)); // Save x29/x30 (FP/LR). - stp(x29, x30, ptr(sp, 0x140)); + stp(x29, x30, ptr(sp, 0x1C0)); code_offsets.body = getSize(); @@ -256,15 +265,19 @@ GuestToHostThunk A64HelperEmitter::EmitGuestToHostThunk() { code_offsets.epilog = getSize(); // Restore. - ldp(x29, x30, ptr(sp, 0x140)); - ldp(Xbyak_aarch64::QReg(30), Xbyak_aarch64::QReg(31), ptr(sp, 0x120)); - ldp(Xbyak_aarch64::QReg(28), Xbyak_aarch64::QReg(29), ptr(sp, 0x100)); - ldp(Xbyak_aarch64::QReg(26), Xbyak_aarch64::QReg(27), ptr(sp, 0x0E0)); - ldp(Xbyak_aarch64::QReg(24), Xbyak_aarch64::QReg(25), ptr(sp, 0x0C0)); - ldp(Xbyak_aarch64::QReg(22), Xbyak_aarch64::QReg(23), ptr(sp, 0x0A0)); - ldp(Xbyak_aarch64::QReg(20), Xbyak_aarch64::QReg(21), ptr(sp, 0x080)); - ldp(Xbyak_aarch64::QReg(18), Xbyak_aarch64::QReg(19), ptr(sp, 0x060)); - ldp(Xbyak_aarch64::QReg(16), Xbyak_aarch64::QReg(17), ptr(sp, 0x040)); + ldp(x29, x30, ptr(sp, 0x1C0)); + ldp(Xbyak_aarch64::QReg(30), Xbyak_aarch64::QReg(31), ptr(sp, 0x1A0)); + ldp(Xbyak_aarch64::QReg(28), Xbyak_aarch64::QReg(29), ptr(sp, 0x180)); + ldp(Xbyak_aarch64::QReg(26), Xbyak_aarch64::QReg(27), ptr(sp, 0x160)); + ldp(Xbyak_aarch64::QReg(24), Xbyak_aarch64::QReg(25), ptr(sp, 0x140)); + ldp(Xbyak_aarch64::QReg(22), Xbyak_aarch64::QReg(23), ptr(sp, 0x120)); + ldp(Xbyak_aarch64::QReg(20), Xbyak_aarch64::QReg(21), ptr(sp, 0x100)); + ldp(Xbyak_aarch64::QReg(18), Xbyak_aarch64::QReg(19), ptr(sp, 0x0E0)); + ldp(Xbyak_aarch64::QReg(16), Xbyak_aarch64::QReg(17), ptr(sp, 0x0C0)); + ldp(Xbyak_aarch64::QReg(14), Xbyak_aarch64::QReg(15), ptr(sp, 0x0A0)); + ldp(Xbyak_aarch64::QReg(12), Xbyak_aarch64::QReg(13), ptr(sp, 0x080)); + ldp(Xbyak_aarch64::QReg(10), Xbyak_aarch64::QReg(11), ptr(sp, 0x060)); + ldp(Xbyak_aarch64::QReg(8), Xbyak_aarch64::QReg(9), ptr(sp, 0x040)); ldp(Xbyak_aarch64::QReg(6), Xbyak_aarch64::QReg(7), ptr(sp, 0x020)); ldp(Xbyak_aarch64::QReg(4), Xbyak_aarch64::QReg(5), ptr(sp, 0x000)); @@ -592,7 +605,7 @@ bool A64Backend::Initialize(Processor* processor) { std::strcpy(gpr_set.name, "gpr"); gpr_set.types = MachineInfo::RegisterSet::INT_TYPES; gpr_set.count = A64Emitter::GPR_COUNT; - // VEC set: v4-v7, v16-v31 (20 registers, v0-v3 scratch) + // VEC set: v4-v15, v16-v31 (28 registers, v0-v3 scratch) auto& vec_set = machine_info_.register_sets[1]; vec_set.id = 1; std::strcpy(vec_set.name, "vec"); diff --git a/src/xenia/cpu/backend/a64/a64_emitter.cc b/src/xenia/cpu/backend/a64/a64_emitter.cc index 99f8e647e..aab8b03f5 100644 --- a/src/xenia/cpu/backend/a64/a64_emitter.cc +++ b/src/xenia/cpu/backend/a64/a64_emitter.cc @@ -63,10 +63,11 @@ const uint32_t A64Emitter::gpr_reg_map_[GPR_COUNT] = { 22, 23, 24, 25, 26, 27, 28, }; -// VEC allocatable registers: v4-v7, v16-v31 +// VEC allocatable registers: v4-v15, v16-v31 // (v0-v3 are scratch) const uint32_t A64Emitter::vec_reg_map_[VEC_COUNT] = { - 4, 5, 6, 7, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, + 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, + 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, }; A64Emitter::A64Emitter(A64Backend* backend, XbyakA64Allocator* allocator) diff --git a/src/xenia/cpu/backend/a64/a64_emitter.h b/src/xenia/cpu/backend/a64/a64_emitter.h index ef341610d..714456bfc 100644 --- a/src/xenia/cpu/backend/a64/a64_emitter.h +++ b/src/xenia/cpu/backend/a64/a64_emitter.h @@ -75,8 +75,8 @@ class A64Emitter : public Xbyak_aarch64::CodeGenerator { // Scratch: x0-x18 (caller-saved), v0-v3 // Available GPRs for register allocator: x22-x28 static constexpr int GPR_COUNT = 7; - // Available VEC regs: v4-v7, v16-v31 - static constexpr int VEC_COUNT = 20; + // Available VEC regs: v4-v15, v16-v31 + static constexpr int VEC_COUNT = 28; static constexpr size_t kStashOffset = 32; static void SetupReg(const hir::Value* v, Xbyak_aarch64::WReg& r) { diff --git a/src/xenia/cpu/backend/a64/a64_stack_layout.h b/src/xenia/cpu/backend/a64/a64_stack_layout.h index 2edfef5d4..40b645939 100644 --- a/src/xenia/cpu/backend/a64/a64_stack_layout.h +++ b/src/xenia/cpu/backend/a64/a64_stack_layout.h @@ -20,35 +20,24 @@ namespace a64 { class StackLayout { public: /** - * ARM64 Thunk Stack Layout + * ARM64 Thunk Stack Layout (HostToGuest) * NOTE: stack must always be 16-byte aligned. * - * Thunk (HostToGuest/GuestToHost): * +------------------+ - * | x19 | sp + 0x000 - * | x20 (context) | sp + 0x008 - * | x21 (membase) | sp + 0x010 - * | x22 | sp + 0x018 - * | x23 | sp + 0x020 - * | x24 | sp + 0x028 - * | x25 | sp + 0x030 - * | x26 | sp + 0x038 - * | x27 | sp + 0x040 - * | x28 | sp + 0x048 - * | x29 (fp) | sp + 0x050 - * | x30 (lr) | sp + 0x058 - * | d8 | sp + 0x060 - * | d9 | sp + 0x068 - * | d10 | sp + 0x070 - * | d11 | sp + 0x078 - * | d12 | sp + 0x080 - * | d13 | sp + 0x088 - * | d14 | sp + 0x090 - * | d15 | sp + 0x098 + * | x19, x20 | sp + 0x000 + * | x21, x22 | sp + 0x010 + * | x23, x24 | sp + 0x020 + * | x25, x26 | sp + 0x030 + * | x27, x28 | sp + 0x040 + * | x29 (fp), x30 | sp + 0x050 + * | q8, q9 | sp + 0x060 (full 128-bit, used by JIT) + * | q10, q11 | sp + 0x080 + * | q12, q13 | sp + 0x0A0 + * | q14, q15 | sp + 0x0C0 * +------------------+ - * Total: 0xA0 = 160 bytes (already 16-byte aligned) + * Total: 0xE0 = 224 bytes (16-byte aligned) */ - static constexpr size_t THUNK_STACK_SIZE = 160; + static constexpr size_t THUNK_STACK_SIZE = 224; /** * ARM64 Guest Stack Layout