[llvm] [AArch64] Add SME clustering (PR #211483)
Tomer Shafir via llvm-commits
llvm-commits at lists.llvm.org
Thu Jul 23 01:09:04 PDT 2026
https://github.com/tomershafir updated https://github.com/llvm/llvm-project/pull/211483
>From adc0681e068081f3d49410e20df02742761b65bb Mon Sep 17 00:00:00 2001
From: tomershafir <tomer.shafir8 at gmail.com>
Date: Thu, 23 Jul 2026 10:22:05 +0300
Subject: [PATCH] [AArch64] Add SME clustering on Apple CPU
This patch adds a subtarget feature that controls scheduling SME isntruction back to back. Enabled on Apple CPU.
---
llvm/lib/Target/AArch64/AArch64Features.td | 5 +
.../lib/Target/AArch64/AArch64MacroFusion.cpp | 69 ++++++++
llvm/lib/Target/AArch64/AArch64Processors.td | 4 +-
llvm/lib/Target/AArch64/AArch64Subtarget.h | 2 +-
.../CodeGen/AArch64/misched-fusion-sme.mir | 147 ++++++++++++++++++
.../TableGen/aarch64-apple-tuning-features.td | 2 +
6 files changed, 227 insertions(+), 2 deletions(-)
create mode 100644 llvm/test/CodeGen/AArch64/misched-fusion-sme.mir
diff --git a/llvm/lib/Target/AArch64/AArch64Features.td b/llvm/lib/Target/AArch64/AArch64Features.td
index 7cb7cd32f673b..7e278cdbd29c0 100644
--- a/llvm/lib/Target/AArch64/AArch64Features.td
+++ b/llvm/lib/Target/AArch64/AArch64Features.td
@@ -856,6 +856,11 @@ def FeatureFuseAddSub2RegAndConstOne : SubtargetFeature<
"CPU fuses (a + b + 1) and (a - b - 1)",
[], InlineIgnore>;
+def FeatureFuseSME : SubtargetFeature<
+ "fuse-sme", "HasFuseSME", "true",
+ "CPU fuses SME matrix operations",
+ [], InlineIgnore>;
+
def FeatureDisableLatencySchedHeuristic : SubtargetFeature<
"disable-latency-sched-heuristic", "DisableLatencySchedHeuristic", "true",
"Disable latency scheduling heuristic",
diff --git a/llvm/lib/Target/AArch64/AArch64MacroFusion.cpp b/llvm/lib/Target/AArch64/AArch64MacroFusion.cpp
index 31b3e097de8ca..468194945c028 100644
--- a/llvm/lib/Target/AArch64/AArch64MacroFusion.cpp
+++ b/llvm/lib/Target/AArch64/AArch64MacroFusion.cpp
@@ -537,6 +537,72 @@ static bool isAddSub2RegAndConstOnePair(const MachineInstr *FirstMI,
return false;
}
+static bool definesRegInClass(const MachineInstr &MI,
+ const TargetRegisterInfo *TRI,
+ const TargetRegisterClass &Class) {
+ return llvm::any_of(Class, [&MI, TRI](MCPhysReg Reg) {
+ return MI.definesRegister(Reg, TRI);
+ });
+}
+
+static bool readsRegInClass(const MachineInstr &MI,
+ const TargetRegisterInfo *TRI,
+ const TargetRegisterClass &Class) {
+ return llvm::any_of(
+ Class, [&MI, TRI](MCPhysReg Reg) { return MI.readsRegister(Reg, TRI); });
+}
+
+static bool isFusableSMEOp(const MachineInstr &MI, const TargetInstrInfo &TII,
+ const TargetRegisterInfo *TRI) {
+ const bool ReadOrWriteZA = MI.readsRegister(AArch64::ZA, TRI) ||
+ MI.definesRegister(AArch64::ZA, TRI);
+
+ // (read\write ZA or read\write Z)
+ if (!ReadOrWriteZA && !definesRegInClass(MI, TRI, AArch64::ZPRRegClass))
+ return false;
+
+ // (NOT load\store)
+ if (MI.mayLoad() || MI.mayStore())
+ return false;
+
+ // (NOT write P)
+ if (definesRegInClass(MI, TRI, AArch64::PPRRegClass))
+ return false;
+
+ // (NOT write GPR)
+ const bool WriteGPR = definesRegInClass(MI, TRI, AArch64::GPR32RegClass) ||
+ definesRegInClass(MI, TRI, AArch64::GPR64RegClass);
+ if (WriteGPR)
+ return false;
+
+ // (NOT read\write NZCV)
+ if (MI.readsRegister(AArch64::NZCV, TRI) ||
+ MI.definesRegister(AArch64::NZCV, TRI))
+ return false;
+
+ const bool ReadGPR = readsRegInClass(MI, TRI, AArch64::GPR32RegClass) &&
+ readsRegInClass(MI, TRI, AArch64::GPR64RegClass);
+
+ // ( (NOT read GPR) or read\write ZA )
+ if (ReadGPR && !ReadOrWriteZA)
+ return false;
+
+ return true;
+}
+
+static bool isSMEPair(const MachineInstr *FirstMI, const MachineInstr &SecondMI,
+ const TargetInstrInfo &TII,
+ const TargetRegisterInfo *TRI) {
+ if (!isFusableSMEOp(SecondMI, TII, TRI))
+ return false;
+ // Assume the 1st instr to be a wildcard if it is unspecified.
+ if (FirstMI == nullptr)
+ return true;
+ if (isFusableSMEOp(*FirstMI, TII, TRI))
+ return true;
+ return false;
+}
+
/// \brief Check if the instr pair, FirstMI and SecondMI, should be fused
/// together. Given SecondMI, when FirstMI is unspecified, then check if
/// SecondMI may be part of a fused pair at all.
@@ -576,6 +642,9 @@ static bool shouldScheduleAdjacent(const TargetInstrInfo &TII,
if (ST.hasFuseAddSub2RegAndConstOne() &&
isAddSub2RegAndConstOnePair(FirstMI, SecondMI))
return true;
+ const TargetRegisterInfo *TRI = TSI.getRegisterInfo();
+ if (ST.hasFuseSME() && isSMEPair(FirstMI, SecondMI, TII, TRI))
+ return true;
return false;
}
diff --git a/llvm/lib/Target/AArch64/AArch64Processors.td b/llvm/lib/Target/AArch64/AArch64Processors.td
index f8b344c61ba1e..b5eaccb1b0a0d 100644
--- a/llvm/lib/Target/AArch64/AArch64Processors.td
+++ b/llvm/lib/Target/AArch64/AArch64Processors.td
@@ -447,8 +447,10 @@ def TuneAppleA17 : SubtargetFeature<"apple-a17", "ARMProcFamily", "AppleA17",
def TuneAppleM4 : SubtargetFeature<"apple-m4", "ARMProcFamily", "AppleM4",
"Apple M4",
+ !listconcat(
!listremove(TuneAppleA17.Implies,
- [FeatureStorePairSuppress])>;
+ [FeatureStorePairSuppress]),
+ [FeatureFuseSME])>;
def TuneAppleM5 : SubtargetFeature<"apple-m5", "ARMProcFamily", "AppleM5",
"Apple M5", TuneAppleM4.Implies>;
diff --git a/llvm/lib/Target/AArch64/AArch64Subtarget.h b/llvm/lib/Target/AArch64/AArch64Subtarget.h
index b8303224a8976..b999bf7f3b3e9 100644
--- a/llvm/lib/Target/AArch64/AArch64Subtarget.h
+++ b/llvm/lib/Target/AArch64/AArch64Subtarget.h
@@ -266,7 +266,7 @@ class AArch64Subtarget final : public AArch64GenSubtargetInfo {
return hasArithmeticBccFusion() || hasArithmeticCbzFusion() ||
hasFuseAES() || hasFuseArithmeticLogic() || hasFuseCmpCSel() ||
hasFuseFCmpFCSel() || hasFuseCmpCSet() || hasFuseAdrpAdd() ||
- hasFuseLiterals();
+ hasFuseLiterals() || hasFuseSME();
}
unsigned getEpilogueVectorizationMinVF() const {
diff --git a/llvm/test/CodeGen/AArch64/misched-fusion-sme.mir b/llvm/test/CodeGen/AArch64/misched-fusion-sme.mir
new file mode 100644
index 0000000000000..adc64c2e5b451
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/misched-fusion-sme.mir
@@ -0,0 +1,147 @@
+# REQUIRES: asserts
+
+# RUN: llc -o /dev/null %s -mtriple=arm64-apple-macosx -mattr=+fuse-sme -passes=machine-scheduler -misched-print-dags -print-before=machine-scheduler 2>&1 | FileCheck %s --check-prefixes=CHECK,FUSE
+# RUN: llc -o /dev/null %s -mtriple=arm64-apple-macosx -mcpu=apple-m4 -passes=machine-scheduler -misched-print-dags -print-before=machine-scheduler 2>&1 | FileCheck %s --check-prefixes=CHECK,FUSE
+# RUN: llc -o /dev/null %s -mtriple=arm64-apple-macosx -mcpu=apple-m5 -passes=machine-scheduler -misched-print-dags -print-before=machine-scheduler 2>&1 | FileCheck %s --check-prefixes=CHECK,FUSE
+
+# RUN: llc -o /dev/null %s -mtriple=arm64-apple-macosx -passes=machine-scheduler -misched-print-dags -print-before=machine-scheduler 2>&1 | FileCheck %s --check-prefixes=CHECK,NOFUSE
+# RUN: llc -o /dev/null %s -mtriple=arm64-apple-macosx -mcpu=apple-m4 -mattr=-fuse-sme -passes=machine-scheduler -misched-print-dags -print-before=machine-scheduler 2>&1 | FileCheck %s --check-prefixes=CHECK,NOFUSE
+
+# CHECK-LABEL: fuse_fmla
+# CHECK: SU({{[0-9]+}}): $za = FMLA_VG2_M2ZZ_S $za(tied-def 0), $w8, 0, $z0_z1, $z2
+# CHECK: Successors:
+# FUSE: SU({{[0-9]+}}): Ord Latency={{[0-9]+}} Cluster
+# NOFUSE-NOT: SU({{[0-9]+}}): Ord Latency={{[0-9]+}} Cluster
+# CHECK: SU({{[0-9]+}}): $za = FMLA_VG2_M2ZZ_S $za(tied-def 0), $w8, 7, $z0_z1, $z2
+---
+name: fuse_fmla
+tracksRegLiveness: true
+body: |
+ bb.0:
+ liveins: $za, $w8, $z0_z1, $z2, $w2
+ $za = FMLA_VG2_M2ZZ_S $za, $w8, 0, $z0_z1, $z2
+ $w3 = ORRWri $w2, 4096
+ $za = FMLA_VG2_M2ZZ_S $za, $w8, 7, $z0_z1, $z2
+ RET undef $lr
+...
+
+# CHECK-LABEL: fuse_za_write
+# CHECK: SU({{[0-9]+}}): ZERO_M 255, implicit-def $za
+# CHECK: Successors:
+# FUSE: SU({{[0-9]+}}): Ord Latency={{[0-9]+}} Cluster
+# NOFUSE-NOT: SU({{[0-9]+}}): Ord Latency={{[0-9]+}} Cluster
+# CHECK: SU({{[0-9]+}}): $za = FMLA_VG2_M2ZZ_S $za(tied-def 0), $w8, 0, $z0_z1, $z2
+---
+name: fuse_za_write
+tracksRegLiveness: true
+body: |
+ bb.0:
+ liveins: $w8, $z0_z1, $z2, $w2
+ ZERO_M 255, implicit-def $za
+ $w3 = ORRWri $w2, 4096
+ $za = FMLA_VG2_M2ZZ_S $za, $w8, 0, $z0_z1, $z2
+ RET undef $lr
+...
+
+# CHECK-LABEL: fuse_z_write
+# CHECK: SU({{[0-9]+}}): $z0 = ADD_ZZZ_S $z1, $z2
+# CHECK: Successors:
+# FUSE: SU({{[0-9]+}}): Ord Latency={{[0-9]+}} Cluster
+# NOFUSE-NOT: SU({{[0-9]+}}): Ord Latency={{[0-9]+}} Cluster
+# CHECK: SU({{[0-9]+}}): $z4 = ADD_ZZZ_S $z0, $z3
+---
+name: fuse_z_write
+tracksRegLiveness: true
+body: |
+ bb.0:
+ liveins: $z1, $z2, $z3, $w2
+ $z0 = ADD_ZZZ_S $z1, $z2
+ $w3 = ORRWri $w2, 4096
+ $z4 = ADD_ZZZ_S $z0, $z3
+ RET undef $lr
+...
+
+# CHECK-LABEL: no_fuse_load
+# CHECK: SU({{[0-9]+}}): $z2 = LDR_ZXI $x0, 0
+# CHECK: Successors:
+# CHECK-NOT: SU({{[0-9]+}}): Ord Latency={{[0-9]+}} Cluster
+# CHECK: SU({{[0-9]+}}): $za = FMLA_VG2_M2ZZ_S
+---
+name: no_fuse_load
+tracksRegLiveness: true
+body: |
+ bb.0:
+ liveins: $za, $w8, $z0_z1, $x0, $w2
+ $z2 = LDR_ZXI $x0, 0
+ $w3 = ORRWri $w2, 4096
+ $za = FMLA_VG2_M2ZZ_S $za, $w8, 0, $z0_z1, $z2
+ RET undef $lr
+...
+
+# CHECK-LABEL: no_fuse_store
+# CHECK: SU({{[0-9]+}}): $za = FMLA_VG2_M2ZZ_S
+# CHECK: Successors:
+# CHECK-NOT: SU({{[0-9]+}}): Ord Latency={{[0-9]+}} Cluster
+# CHECK: SU({{[0-9]+}}): STR_ZA
+---
+name: no_fuse_store
+tracksRegLiveness: true
+body: |
+ bb.0:
+ liveins: $za, $w8, $z0_z1, $z2, $w12, $x0, $w2
+ $za = FMLA_VG2_M2ZZ_S $za, $w8, 0, $z0_z1, $z2
+ $w3 = ORRWri $w2, 4096
+ STR_ZA $za, $w12, 0, $x0, 0
+ RET undef $lr
+...
+
+# CHECK-LABEL: no_fuse_write_p
+# CHECK: SU({{[0-9]+}}): $z0 = ADD_ZZZ_S $z1, $z2
+# CHECK: Successors:
+# CHECK-NOT: SU({{[0-9]+}}): Ord Latency={{[0-9]+}} Cluster
+# CHECK: SU({{[0-9]+}}): $p1 = FCMEQ_PPzZZ_S $p0, $z0, $z3
+---
+name: no_fuse_write_p
+tracksRegLiveness: true
+body: |
+ bb.0:
+ liveins: $p0, $z1, $z2, $z3, $w2
+ $z0 = ADD_ZZZ_S $z1, $z2
+ $w3 = ORRWri $w2, 4096
+ $p1 = FCMEQ_PPzZZ_S $p0, $z0, $z3
+ RET undef $lr
+...
+
+# CHECK-LABEL: no_fuse_write_gpr32
+# CHECK: SU({{[0-9]+}}): $z0 = ADD_ZZZ_S $z1, $z2
+# CHECK: Successors:
+# CHECK-NOT: SU({{[0-9]+}}): Ord Latency={{[0-9]+}} Cluster
+# CHECK: SU({{[0-9]+}}): $w0 = LASTB_RPZ_S $p0, $z0
+---
+name: no_fuse_write_gpr32
+tracksRegLiveness: true
+body: |
+ bb.0:
+ liveins: $p0, $z1, $z2, $w2
+ $z0 = ADD_ZZZ_S $z1, $z2
+ $w3 = ORRWri $w2, 4096
+ $w0 = LASTB_RPZ_S $p0, $z0
+ RET undef $lr
+...
+
+# CHECK-LABEL: no_fuse_write_gpr64
+# CHECK: SU({{[0-9]+}}): $z0 = ADD_ZZZ_S $z1, $z2
+# CHECK: Successors:
+# CHECK-NOT: SU({{[0-9]+}}): Ord Latency={{[0-9]+}} Cluster
+# CHECK: SU({{[0-9]+}}): $x0 = LASTB_RPZ_D $p0, $z0
+---
+name: no_fuse_write_gpr64
+tracksRegLiveness: true
+body: |
+ bb.0:
+ liveins: $p0, $z1, $z2, $w2
+ $z0 = ADD_ZZZ_S $z1, $z2
+ $w3 = ORRWri $w2, 4096
+ $x0 = LASTB_RPZ_D $p0, $z0
+ RET undef $lr
+...
diff --git a/llvm/test/TableGen/aarch64-apple-tuning-features.td b/llvm/test/TableGen/aarch64-apple-tuning-features.td
index fb0bd1e1c204f..d3e4f007b2512 100644
--- a/llvm/test/TableGen/aarch64-apple-tuning-features.td
+++ b/llvm/test/TableGen/aarch64-apple-tuning-features.td
@@ -250,6 +250,7 @@
// CHECK-NEXT: FeatureFuseCmpCSel,
// CHECK-NEXT: FeatureFuseFCmpFCSel,
// CHECK-NEXT: FeatureFuseLiterals,
+// CHECK-NEXT: FeatureFuseSME,
// CHECK-NEXT: FeatureMaxInterleaveFactor4,
// CHECK-NEXT: FeatureNoZCZeroingFPR64,
// CHECK-NEXT: FeatureZCRegMoveFPR128,
@@ -279,6 +280,7 @@
// CHECK-NEXT: FeatureFuseCmpCSel,
// CHECK-NEXT: FeatureFuseFCmpFCSel,
// CHECK-NEXT: FeatureFuseLiterals,
+// CHECK-NEXT: FeatureFuseSME,
// CHECK-NEXT: FeatureMaxInterleaveFactor4,
// CHECK-NEXT: FeatureNoZCZeroingFPR64,
// CHECK-NEXT: FeatureZCRegMoveFPR128,
More information about the llvm-commits
mailing list