[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.
This commit is contained in:
Herman S.
2026-03-24 15:12:10 +09:00
parent 756ae7a558
commit 02d2cb5cc4
4 changed files with 74 additions and 71 deletions

View File

@@ -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<uint32_t>(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");

View File

@@ -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)

View File

@@ -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) {

View File

@@ -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