[A64] Optimizations for DIV, MUL_HI, SWIZZLE, LOAD_VECTOR_SHL/SHR

- DIV_I32/I64 and MUL_HI_I64: use source registers directly in
  sdiv/udiv/umulh/smulh instead of copying to scratch first
- SWIZZLE: fast paths for identity (no-op), broadcast (dup), and
  pair-swap (rev64) before falling back to general TBL
- LOAD_VECTOR_SHL/SHR: build base index vector with fmov+ins
  instead of stp+ldr stack round-trip
This commit is contained in:
Herman S.
2026-03-29 18:20:09 +09:00
parent 59f9fb9964
commit 2fc5aca628
2 changed files with 49 additions and 52 deletions

View File

@@ -1013,20 +1013,35 @@ struct SWIZZLE
uint8_t swizzle_mask = static_cast<uint8_t>(i.src2.value); uint8_t swizzle_mask = static_cast<uint8_t>(i.src2.value);
int s = SrcVReg(e, i.src1, 0); int s = SrcVReg(e, i.src1, 0);
int d = i.dest.reg().getIdx(); int d = i.dest.reg().getIdx();
// Build TBL control for dword swizzle. // swizzle_mask bits [1:0]=word0 src, [3:2]=word1, [5:4]=word2,
// swizzle_mask bits [1:0]=X(PPC word 0), [3:2]=Y, [5:4]=Z, [7:6]=W. // [7:6]=word3. Fast paths for common patterns.
// PPC word i = NEON element s[i] (direct mapping). uint8_t w0 = (swizzle_mask >> 0) & 0x3;
uint8_t ctrl[16]; uint8_t w1 = (swizzle_mask >> 2) & 0x3;
for (int idx = 0; idx < 4; idx++) { uint8_t w2 = (swizzle_mask >> 4) & 0x3;
uint8_t src_dw = (swizzle_mask >> (idx * 2)) & 0x3; uint8_t w3 = (swizzle_mask >> 6) & 0x3;
for (int b = 0; b < 4; b++) { if (w0 == 0 && w1 == 1 && w2 == 2 && w3 == 3) {
ctrl[idx * 4 + b] = src_dw * 4 + b; // Identity.
if (d != s) e.mov(VReg(d).b16, VReg(s).b16);
} else if (w0 == w1 && w1 == w2 && w2 == w3) {
// Broadcast single lane.
e.dup(VReg(d).s4, VReg(s).s4[w0]);
} else if (w0 == 1 && w1 == 0 && w2 == 3 && w3 == 2) {
// Swap pairs within 64-bit halves.
e.rev64(VReg(d).s4, VReg(s).s4);
} else {
// General case: TBL.
uint8_t ctrl[16];
for (int idx = 0; idx < 4; idx++) {
uint8_t src_dw = (swizzle_mask >> (idx * 2)) & 0x3;
for (int b = 0; b < 4; b++) {
ctrl[idx * 4 + b] = src_dw * 4 + b;
}
} }
vec128_t ctrl_vec;
std::memcpy(&ctrl_vec, ctrl, 16);
LoadV128Const(e, 2, ctrl_vec);
e.tbl(VReg(d).b16, VReg(s).b16, 1, VReg(2).b16);
} }
vec128_t ctrl_vec;
std::memcpy(&ctrl_vec, ctrl, 16);
LoadV128Const(e, 2, ctrl_vec);
e.tbl(VReg(d).b16, VReg(s).b16, 1, VReg(2).b16);
} else { } else {
e.DebugBreak(); e.DebugBreak();
} }
@@ -1046,9 +1061,8 @@ struct LOAD_VECTOR_SHL_I8
// {3,2,1,0, 7,6,5,4, 11,10,9,8, 15,14,13,12} // {3,2,1,0, 7,6,5,4, 11,10,9,8, 15,14,13,12}
e.mov(e.x0, static_cast<uint64_t>(0x0405060700010203ull)); e.mov(e.x0, static_cast<uint64_t>(0x0405060700010203ull));
e.mov(e.x1, static_cast<uint64_t>(0x0C0D0E0F08090A0Bull)); e.mov(e.x1, static_cast<uint64_t>(0x0C0D0E0F08090A0Bull));
e.stp(e.x0, e.x1, e.fmov(Xbyak_aarch64::DReg(d), e.x0);
ptr(e.sp, static_cast<int32_t>(StackLayout::GUEST_SCRATCH))); e.ins(VReg(d).d2[1], e.x1);
e.ldr(QReg(d), ptr(e.sp, static_cast<int32_t>(StackLayout::GUEST_SCRATCH)));
// Add shift amount (splatted). // Add shift amount (splatted).
if (i.src1.is_constant) { if (i.src1.is_constant) {
if (i.src1.constant() != 0) { if (i.src1.constant() != 0) {
@@ -1075,9 +1089,8 @@ struct LOAD_VECTOR_SHR_I8
// {19,18,17,16, 23,22,21,20, 27,26,25,24, 31,30,29,28} // {19,18,17,16, 23,22,21,20, 27,26,25,24, 31,30,29,28}
e.mov(e.x0, static_cast<uint64_t>(0x1415161710111213ull)); e.mov(e.x0, static_cast<uint64_t>(0x1415161710111213ull));
e.mov(e.x1, static_cast<uint64_t>(0x1C1D1E1F18191A1Bull)); e.mov(e.x1, static_cast<uint64_t>(0x1C1D1E1F18191A1Bull));
e.stp(e.x0, e.x1, e.fmov(Xbyak_aarch64::DReg(d), e.x0);
ptr(e.sp, static_cast<int32_t>(StackLayout::GUEST_SCRATCH))); e.ins(VReg(d).d2[1], e.x1);
e.ldr(QReg(d), ptr(e.sp, static_cast<int32_t>(StackLayout::GUEST_SCRATCH)));
// Subtract shift amount (splatted). // Subtract shift amount (splatted).
if (i.src1.is_constant) { if (i.src1.is_constant) {
if (i.src1.constant() != 0) { if (i.src1.constant() != 0) {

View File

@@ -1234,30 +1234,18 @@ EMITTER_OPCODE_TABLE(OPCODE_MUL, MUL_I32, MUL_I64, MUL_F32, MUL_F64, MUL_V128);
struct MUL_HI_I64 struct MUL_HI_I64
: Sequence<MUL_HI_I64, I<OPCODE_MUL_HI, I64Op, I64Op, I64Op>> { : Sequence<MUL_HI_I64, I<OPCODE_MUL_HI, I64Op, I64Op, I64Op>> {
static void Emit(A64Emitter& e, const EmitArgType& i) { static void Emit(A64Emitter& e, const EmitArgType& i) {
XReg s1 = i.src1.is_constant ? e.x0 : XReg(i.src1.reg().getIdx());
XReg s2 = i.src2.is_constant ? e.x1 : XReg(i.src2.reg().getIdx());
if (i.src1.is_constant) {
e.mov(e.x0, static_cast<uint64_t>(i.src1.constant()));
}
if (i.src2.is_constant) {
e.mov(e.x1, static_cast<uint64_t>(i.src2.constant()));
}
if (i.instr->flags & ARITHMETIC_UNSIGNED) { if (i.instr->flags & ARITHMETIC_UNSIGNED) {
if (i.src1.is_constant) { e.umulh(i.dest, s1, s2);
e.mov(e.x0, static_cast<uint64_t>(i.src1.constant()));
} else {
e.mov(e.x0, i.src1);
}
if (i.src2.is_constant) {
e.mov(e.x1, static_cast<uint64_t>(i.src2.constant()));
e.umulh(i.dest, e.x0, e.x1);
} else {
e.umulh(i.dest, e.x0, i.src2);
}
} else { } else {
if (i.src1.is_constant) { e.smulh(i.dest, s1, s2);
e.mov(e.x0, static_cast<uint64_t>(i.src1.constant()));
} else {
e.mov(e.x0, i.src1);
}
if (i.src2.is_constant) {
e.mov(e.x1, static_cast<uint64_t>(i.src2.constant()));
e.smulh(i.dest, e.x0, e.x1);
} else {
e.smulh(i.dest, e.x0, i.src2);
}
} }
} }
}; };
@@ -1269,41 +1257,37 @@ EMITTER_OPCODE_TABLE(OPCODE_MUL_HI, MUL_HI_I64);
struct DIV_I32 : Sequence<DIV_I32, I<OPCODE_DIV, I32Op, I32Op, I32Op>> { struct DIV_I32 : Sequence<DIV_I32, I<OPCODE_DIV, I32Op, I32Op, I32Op>> {
static void Emit(A64Emitter& e, const EmitArgType& i) { static void Emit(A64Emitter& e, const EmitArgType& i) {
// ARM64 sdiv/udiv returns 0 on divide by zero (no exception). // ARM64 sdiv/udiv returns 0 on divide by zero (no exception).
WReg s1 = i.src1.is_constant ? e.w0 : WReg(i.src1.reg().getIdx());
WReg s2 = i.src2.is_constant ? e.w1 : WReg(i.src2.reg().getIdx());
if (i.src1.is_constant) { if (i.src1.is_constant) {
e.mov(e.w0, e.mov(e.w0,
static_cast<uint64_t>(static_cast<uint32_t>(i.src1.constant()))); static_cast<uint64_t>(static_cast<uint32_t>(i.src1.constant())));
} else {
e.mov(e.w0, i.src1);
} }
if (i.src2.is_constant) { if (i.src2.is_constant) {
e.mov(e.w1, e.mov(e.w1,
static_cast<uint64_t>(static_cast<uint32_t>(i.src2.constant()))); static_cast<uint64_t>(static_cast<uint32_t>(i.src2.constant())));
} else {
e.mov(e.w1, i.src2);
} }
if (i.instr->flags & ARITHMETIC_UNSIGNED) { if (i.instr->flags & ARITHMETIC_UNSIGNED) {
e.udiv(i.dest, e.w0, e.w1); e.udiv(i.dest, s1, s2);
} else { } else {
e.sdiv(i.dest, e.w0, e.w1); e.sdiv(i.dest, s1, s2);
} }
} }
}; };
struct DIV_I64 : Sequence<DIV_I64, I<OPCODE_DIV, I64Op, I64Op, I64Op>> { struct DIV_I64 : Sequence<DIV_I64, I<OPCODE_DIV, I64Op, I64Op, I64Op>> {
static void Emit(A64Emitter& e, const EmitArgType& i) { static void Emit(A64Emitter& e, const EmitArgType& i) {
XReg s1 = i.src1.is_constant ? e.x0 : XReg(i.src1.reg().getIdx());
XReg s2 = i.src2.is_constant ? e.x1 : XReg(i.src2.reg().getIdx());
if (i.src1.is_constant) { if (i.src1.is_constant) {
e.mov(e.x0, static_cast<uint64_t>(i.src1.constant())); e.mov(e.x0, static_cast<uint64_t>(i.src1.constant()));
} else {
e.mov(e.x0, i.src1);
} }
if (i.src2.is_constant) { if (i.src2.is_constant) {
e.mov(e.x1, static_cast<uint64_t>(i.src2.constant())); e.mov(e.x1, static_cast<uint64_t>(i.src2.constant()));
} else {
e.mov(e.x1, i.src2);
} }
if (i.instr->flags & ARITHMETIC_UNSIGNED) { if (i.instr->flags & ARITHMETIC_UNSIGNED) {
e.udiv(i.dest, e.x0, e.x1); e.udiv(i.dest, s1, s2);
} else { } else {
e.sdiv(i.dest, e.x0, e.x1); e.sdiv(i.dest, s1, s2);
} }
} }
}; };