SPU LLVM: SVE multiply optimizations

- Reduces most SPU multiply instructions to a single operation
- Add SVE length detection, only use SVE instructions for 128b sve for
  now
This commit is contained in:
Malcolm 2026-05-22 00:20:54 -04:00 committed by Ani
parent 2f2ac69d60
commit 6349ea2ee9
3 changed files with 171 additions and 11 deletions

View File

@ -3790,8 +3790,81 @@ template <typename T1, typename T2, typename T3>
return from_sve_vector(result, fixed_type);
}
template <typename T1, typename T2>
template <typename T, typename T1, typename T2>
value_t<T> sve_mull(llvm::Intrinsic::ID id, T1 a, T2 b)
{
value_t<T> result;
const auto fixed_type = llvm::cast<llvm::FixedVectorType>(get_type<T>());
const auto scalable_type = llvm::ScalableVectorType::get(fixed_type->getElementType(), fixed_type->getNumElements());
const auto data0 = to_sve_vector(a.eval(m_ir));
const auto data1 = to_sve_vector(b.eval(m_ir));
const std::array<llvm::Type*, 1> types{scalable_type};
result.value = from_sve_vector(m_ir->CreateIntrinsic(id, types, {data0, data1}), fixed_type);
return result;
}
template <typename T, typename T0, typename T1, typename T2>
value_t<T> sve_mlal(llvm::Intrinsic::ID id, T0 acc, T1 a, T2 b)
{
value_t<T> result;
const auto fixed_type = llvm::cast<llvm::FixedVectorType>(get_type<T>());
const auto scalable_type = llvm::ScalableVectorType::get(fixed_type->getElementType(), fixed_type->getNumElements());
const auto data0 = to_sve_vector(acc.eval(m_ir));
const auto data1 = to_sve_vector(a.eval(m_ir));
const auto data2 = to_sve_vector(b.eval(m_ir));
const std::array<llvm::Type*, 1> types{scalable_type};
result.value = from_sve_vector(m_ir->CreateIntrinsic(id, types, {data0, data1, data2}), fixed_type);
return result;
}
template <typename T1, typename T2>
value_t<s32[4]> sve_smullb(T1 a, T2 b)
{
return sve_mull<s32[4]>(llvm::Intrinsic::aarch64_sve_smullb, a, b);
}
template <typename T1, typename T2>
value_t<s32[4]> sve_smullt(T1 a, T2 b)
{
return sve_mull<s32[4]>(llvm::Intrinsic::aarch64_sve_smullt, a, b);
}
template <typename T1, typename T2>
value_t<u32[4]> sve_umullb(T1 a, T2 b)
{
return sve_mull<u32[4]>(llvm::Intrinsic::aarch64_sve_umullb, a, b);
}
template <typename T1, typename T2>
value_t<u32[4]> sve_umullt(T1 a, T2 b)
{
return sve_mull<u32[4]>(llvm::Intrinsic::aarch64_sve_umullt, a, b);
}
template <typename T0, typename T1, typename T2>
value_t<s32[4]> sve_smlalb(T0 acc, T1 a, T2 b)
{
return sve_mlal<s32[4]>(llvm::Intrinsic::aarch64_sve_smlalb, acc, a, b);
}
template <typename T0, typename T1, typename T2>
value_t<s32[4]> sve_smlalt(T0 acc, T1 a, T2 b)
{
return sve_mlal<s32[4]>(llvm::Intrinsic::aarch64_sve_smlalt, acc, a, b);
}
template <typename T0, typename T1, typename T2>
value_t<u32[4]> sve_umlalt(T0 acc, T1 a, T2 b)
{
return sve_mlal<u32[4]>(llvm::Intrinsic::aarch64_sve_umlalt, acc, a, b);
}
template <typename T1, typename T2>
auto addp(T1 a, T2 b)
{
using T_vector = typename is_llvm_expr<T1>::type;

View File

@ -6265,6 +6265,15 @@ public:
void MPYHHU(spu_opcode_t op)
{
#ifdef ARCH_ARM64
const auto [a, b] = get_vrs<u32[4]>(op.ra, op.rb);
if (m_use_sve2_128)
{
set_vr(op.rt, sve_umullt(bitcast<u16[8]>(a), bitcast<u16[8]>(b)));
return;
}
#endif
set_vr(op.rt, (get_vr(op.ra) >> 16) * (get_vr(op.rb) >> 16));
}
@ -6295,11 +6304,29 @@ public:
void MPYHHA(spu_opcode_t op)
{
#ifdef ARCH_ARM64
const auto [a, b] = get_vrs<s32[4]>(op.ra, op.rb);
if (m_use_sve2_128)
{
set_vr(op.rt, sve_smlalt(get_vr<s32[4]>(op.rt), bitcast<s16[8]>(a), bitcast<s16[8]>(b)));
return;
}
#endif
set_vr(op.rt, (get_vr<s32[4]>(op.ra) >> 16) * (get_vr<s32[4]>(op.rb) >> 16) + get_vr<s32[4]>(op.rt));
}
void MPYHHAU(spu_opcode_t op)
{
#ifdef ARCH_ARM64
const auto [a, b] = get_vrs<u32[4]>(op.ra, op.rb);
if (m_use_sve2_128)
{
set_vr(op.rt, sve_umlalt(get_vr<u32[4]>(op.rt), bitcast<u16[8]>(a), bitcast<u16[8]>(b)));
return;
}
#endif
set_vr(op.rt, (get_vr(op.ra) >> 16) * (get_vr(op.rb) >> 16) + get_vr(op.rt));
}
@ -6307,7 +6334,15 @@ public:
{
#ifdef ARCH_ARM64
const auto [a, b] = get_vrs<s32[4]>(op.ra, op.rb);
set_vr(op.rt, smull(zshuffle(bitcast<s16[8]>(a), 0, 2, 4, 6), zshuffle(bitcast<s16[8]>(b), 0, 2, 4, 6)));
if (m_use_sve2_128)
{
set_vr(op.rt, sve_smullb(bitcast<s16[8]>(a), bitcast<s16[8]>(b)));
}
else
{
set_vr(op.rt, smull(zshuffle(bitcast<s16[8]>(a), 0, 2, 4, 6), zshuffle(bitcast<s16[8]>(b), 0, 2, 4, 6)));
}
#else
set_vr(op.rt, (get_vr<s32[4]>(op.ra) << 16 >> 16) * (get_vr<s32[4]>(op.rb) << 16 >> 16));
#endif
@ -6320,6 +6355,15 @@ public:
void MPYHH(spu_opcode_t op)
{
#ifdef ARCH_ARM64
const auto [a, b] = get_vrs<s32[4]>(op.ra, op.rb);
if (m_use_sve2_128)
{
set_vr(op.rt, sve_smullt(bitcast<s16[8]>(a), bitcast<s16[8]>(b)));
return;
}
#endif
set_vr(op.rt, (get_vr<s32[4]>(op.ra) >> 16) * (get_vr<s32[4]>(op.rb) >> 16));
}
@ -6327,7 +6371,15 @@ public:
{
#ifdef ARCH_ARM64
const auto [a, b] = get_vrs<s32[4]>(op.ra, op.rb);
set_vr(op.rt, smull(zshuffle(bitcast<s16[8]>(a), 0, 2, 4, 6), zshuffle(bitcast<s16[8]>(b), 0, 2, 4, 6)) >> 16);
if (m_use_sve2_128)
{
set_vr(op.rt, sve_smullb(bitcast<s16[8]>(a), bitcast<s16[8]>(b)) >> 16);
}
else
{
set_vr(op.rt, smull(zshuffle(bitcast<s16[8]>(a), 0, 2, 4, 6), zshuffle(bitcast<s16[8]>(b), 0, 2, 4, 6)) >> 16);
}
#else
set_vr(op.rt, (get_vr<s32[4]>(op.ra) << 16 >> 16) * (get_vr<s32[4]>(op.rb) << 16 >> 16) >> 16);
#endif
@ -6342,7 +6394,15 @@ public:
{
#ifdef ARCH_ARM64
const auto [a, b] = get_vrs<u32[4]>(op.ra, op.rb);
set_vr(op.rt, umull(zshuffle(bitcast<u16[8]>(a), 0, 2, 4, 6), zshuffle(bitcast<u16[8]>(b), 0, 2, 4, 6)));
if (m_use_sve2_128)
{
set_vr(op.rt, sve_umullb(bitcast<u16[8]>(a), bitcast<u16[8]>(b)));
}
else
{
set_vr(op.rt, umull(zshuffle(bitcast<u16[8]>(a), 0, 2, 4, 6), zshuffle(bitcast<u16[8]>(b), 0, 2, 4, 6)));
}
#else
set_vr(op.rt, mpyu(get_vr(op.ra), get_vr(op.rb)));
#endif
@ -6495,7 +6555,14 @@ public:
void MPYI(spu_opcode_t op)
{
#ifdef ARCH_ARM64
set_vr(op.rt, smull(zshuffle(bitcast<s16[8]>(get_vr<s32[4]>(op.ra)), 0, 2, 4, 6), get_imm<s16[4]>(op.si10)));
if (m_use_sve2_128)
{
set_vr(op.rt, sve_smullb(bitcast<s16[8]>(get_vr<s32[4]>(op.ra)), get_imm<s16[8]>(op.si10)));
}
else
{
set_vr(op.rt, smull(zshuffle(bitcast<s16[8]>(get_vr<s32[4]>(op.ra)), 0, 2, 4, 6), get_imm<s16[4]>(op.si10)));
}
#else
set_vr(op.rt, (get_vr<s32[4]>(op.ra) << 16 >> 16) * get_imm<s32[4]>(op.si10));
#endif
@ -6504,7 +6571,14 @@ public:
void MPYUI(spu_opcode_t op)
{
#ifdef ARCH_ARM64
set_vr(op.rt, umull(zshuffle(bitcast<u16[8]>(get_vr<u32[4]>(op.ra)), 0, 2, 4, 6), get_imm<u16[4]>(op.si10)));
if (m_use_sve2_128)
{
set_vr(op.rt, sve_umullb(bitcast<u16[8]>(get_vr<u32[4]>(op.ra)), get_imm<u16[8]>(op.si10)));
}
else
{
set_vr(op.rt, umull(zshuffle(bitcast<u16[8]>(get_vr<u32[4]>(op.ra)), 0, 2, 4, 6), get_imm<u16[4]>(op.si10)));
}
#else
set_vr(op.rt, (get_vr(op.ra) << 16 >> 16) * (get_imm(op.si10) & 0xffff));
#endif
@ -7091,7 +7165,15 @@ public:
{
#ifdef ARCH_ARM64
const auto [a, b] = get_vrs<s32[4]>(op.ra, op.rb);
set_vr(op.rt4, smull(zshuffle(bitcast<s16[8]>(a), 0, 2, 4, 6), zshuffle(bitcast<s16[8]>(b), 0, 2, 4, 6)) + get_vr<s32[4]>(op.rc));
if (m_use_sve2_128)
{
set_vr(op.rt4, sve_smlalb(get_vr<s32[4]>(op.rc), bitcast<s16[8]>(a), bitcast<s16[8]>(b)));
}
else
{
set_vr(op.rt4, smull(zshuffle(bitcast<s16[8]>(a), 0, 2, 4, 6), zshuffle(bitcast<s16[8]>(b), 0, 2, 4, 6)) + get_vr<s32[4]>(op.rc));
}
#else
set_vr(op.rt4, (get_vr<s32[4]>(op.ra) << 16 >> 16) * (get_vr<s32[4]>(op.rb) << 16 >> 16) + get_vr<s32[4]>(op.rc));
#endif

View File

@ -531,13 +531,18 @@ std::string utils::get_system_info()
}
#ifdef ARCH_ARM64
if (has_neon())
if (!has_neon())
{
result += " | Neon";
fmt::throw_exception("Neon support not present");
}
if (has_sve())
{
fmt::append(result, " | SVE%s-%d", has_sve2() ? "2" : "", sve_length());
}
else
{
fmt::throw_exception("Neon support not present");
result += " | Neon";
}
#else