[llvm] [AArch64] Support instruction fusion for MCA (PR #215789)
via llvm-commits
llvm-commits at lists.llvm.org
Wed Aug 12 07:37:20 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-tools-llvm-mca
Author: Andrew Savonichev (asavonic)
<details>
<summary>Changes</summary>
The patch adds `AArch64CustomBehaviour` to change MCA simulation for cases that are not fully defined in the schedule model.
For now it handles Macro Fusion, where an instruction can be fused with the previous instruction. For example, a pair of `AESE` and `AESMC` are fused into 1 uOp if both instructions define the same register and the second instruction immediately follows the first one in the program.
The custom behaviour for MCA handles some of the cases that `AArch64MacroFusion` supports:
- `AESE` followed by `AESMC`.
- `AESD` followed by `AESIMC`.
- `CMP` followed by `CSET` or `CSEL`.
- `CMN`, `CMP`, `TST` followed by `Bcc`.
Detection code is very similar to `AArch64MacroFusion`, but it operates on `MCInst` instead of `MachineInst`, and is tied to MCA instead of DAG MacroFusion.
When a fusion pair is detected, `AArch64CustomBehaviour` moves all reads and writes from the second instruction of the pair to the first one, and "eliminates" the second instruction. The second instruction still appears on the timeline, but it takes no resources:
[0,0] DeER . . . cmp w3, w5
[0,1] D--R . . . csel w1, w0, w19, ne
When two instructions are not fused, both instructions are not modified. For example, shifted or extended register operands can prevent fusion:
[0,16] . DeER . . cmp w3, w5, lsl #<!-- -->1
[0,17] . D=eER . . csel w1, w0, w19, ne
"Elimination" of instruction currently does not eliminate the corresponding uOp, so both instructions are counted for statistic, dispatch limits, etc.
There is also `NOP` instruction that is listed in s4.14 "Instruction fusion" of the optimization guide for Cortex-A78, for example. However, it does not seem to work exactly like other fusions, so there will be a follow up patch to add it.
---
Patch is 72.34 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/215789.diff
8 Files Affected:
- (modified) llvm/lib/MCA/Stages/InOrderIssueStage.cpp (+2-1)
- (modified) llvm/lib/Target/AArch64/CMakeLists.txt (+1)
- (added) llvm/lib/Target/AArch64/MCA/AArch64CustomBehaviour.cpp (+354)
- (added) llvm/lib/Target/AArch64/MCA/CMakeLists.txt (+12)
- (added) llvm/test/tools/llvm-mca/AArch64/Neoverse/N2-fuse-aes.s (+118)
- (added) llvm/test/tools/llvm-mca/AArch64/Neoverse/N2-fuse-bcc.s (+455)
- (added) llvm/test/tools/llvm-mca/AArch64/Neoverse/N2-fuse-csel.s (+234)
- (added) llvm/test/tools/llvm-mca/AArch64/Neoverse/N2-fuse-cset.s (+234)
``````````diff
diff --git a/llvm/lib/MCA/Stages/InOrderIssueStage.cpp b/llvm/lib/MCA/Stages/InOrderIssueStage.cpp
index 9590ebfd3201f..5a618c9371295 100644
--- a/llvm/lib/MCA/Stages/InOrderIssueStage.cpp
+++ b/llvm/lib/MCA/Stages/InOrderIssueStage.cpp
@@ -157,7 +157,8 @@ static void addRegisterReadWrite(RegisterFile &PRF, Instruction &IS,
unsigned SourceIndex,
const MCSubtargetInfo &STI,
SmallVectorImpl<unsigned> &UsedRegs) {
- assert(!IS.isEliminated());
+ if (IS.isEliminated())
+ return;
for (ReadState &RS : IS.getUses())
PRF.addRegisterRead(RS, STI);
diff --git a/llvm/lib/Target/AArch64/CMakeLists.txt b/llvm/lib/Target/AArch64/CMakeLists.txt
index 12a2214f8e58e..998992c869180 100644
--- a/llvm/lib/Target/AArch64/CMakeLists.txt
+++ b/llvm/lib/Target/AArch64/CMakeLists.txt
@@ -128,6 +128,7 @@ add_llvm_target(AArch64CodeGen
add_subdirectory(AsmParser)
add_subdirectory(Disassembler)
+add_subdirectory(MCA)
add_subdirectory(MCTargetDesc)
add_subdirectory(TargetInfo)
add_subdirectory(Utils)
diff --git a/llvm/lib/Target/AArch64/MCA/AArch64CustomBehaviour.cpp b/llvm/lib/Target/AArch64/MCA/AArch64CustomBehaviour.cpp
new file mode 100644
index 0000000000000..8554491269b2f
--- /dev/null
+++ b/llvm/lib/Target/AArch64/MCA/AArch64CustomBehaviour.cpp
@@ -0,0 +1,354 @@
+//===--------------- AArch64CustomBehaviour.cpp -----------------*-C++ -* -===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+/// \file
+///
+/// This file defines custom behaviour for MCA that is not expressed in the
+/// schedule model.
+///
+//===----------------------------------------------------------------------===//
+
+#include "AArch64InstrInfo.h"
+#include "AArch64Subtarget.h"
+#include "TargetInfo/AArch64TargetInfo.h"
+#include "llvm-c/Visibility.h"
+#include "llvm/MC/TargetRegistry.h"
+#include "llvm/MCA/CustomBehaviour.h"
+#include "llvm/TargetParser/TargetParser.h"
+
+using namespace llvm;
+using namespace mca;
+
+class AArch64InstrPostProcess : public InstrPostProcess {
+ mca::Instruction *PreviousInst;
+ const MCInst *PreviousMCI;
+
+public:
+ AArch64InstrPostProcess(const MCSubtargetInfo &STI, const MCInstrInfo &MCII)
+ : InstrPostProcess(STI, MCII), PreviousInst(nullptr),
+ PreviousMCI(nullptr) {}
+
+ ~AArch64InstrPostProcess() override = default;
+
+ void postProcessInstruction(mca::Instruction &Inst,
+ const MCInst &MCI) override;
+ void resetState() override;
+};
+
+/// AES encoding or decoding.
+static bool isAESPair(const MCInst &First, const MCInst &Second) {
+ switch (Second.getOpcode()) {
+ // AES encode.
+ case AArch64::AESMCrr: {
+ if (First.getOpcode() != AArch64::AESErr)
+ return false;
+
+ MCRegister EDef = First.getOperand(0).getReg();
+ MCRegister MCDef = Second.getOperand(0).getReg();
+ MCRegister MCUse = Second.getOperand(1).getReg();
+ return (EDef == MCDef && EDef == MCUse);
+ }
+ // AES decode.
+ case AArch64::AESIMCrr: {
+ if (First.getOpcode() != AArch64::AESDrr)
+ return false;
+
+ MCRegister DDef = First.getOperand(0).getReg();
+ MCRegister IMCDef = Second.getOperand(0).getReg();
+ MCRegister IMCUse = Second.getOperand(1).getReg();
+ return (DDef == IMCDef && DDef == IMCUse);
+ }
+ }
+ return false;
+}
+
+/// Compare and conditional select.
+static bool isCmpCSelPair(const MCInst &First, const MCInst &Second) {
+ switch (Second.getOpcode()) {
+ case AArch64::CSELWr: {
+ // 32 bits
+ if (!First.getNumOperands())
+ return false;
+
+ MCOperand Zero = First.getOperand(0);
+ if (!Zero.isReg() || Zero.getReg() != AArch64::WZR)
+ return false;
+
+ switch (First.getOpcode()) {
+ case AArch64::SUBSWrs:
+ return !AArch64_MC::hasShiftedReg(First);
+ case AArch64::SUBSWrx:
+ return !AArch64_MC::hasExtendedReg(First);
+ case AArch64::SUBSWrr:
+ case AArch64::SUBSWri:
+ return true;
+ }
+ return false;
+ }
+ case AArch64::CSELXr: {
+ // 64 bits
+ if (!First.getNumOperands())
+ return false;
+
+ MCOperand Zero = First.getOperand(0);
+ if (!Zero.isReg() || Zero.getReg() != AArch64::XZR)
+ return false;
+
+ switch (First.getOpcode()) {
+ case AArch64::SUBSXrs:
+ return !AArch64_MC::hasShiftedReg(First);
+ case AArch64::SUBSXrx:
+ case AArch64::SUBSXrx64:
+ return !AArch64_MC::hasExtendedReg(First);
+ case AArch64::SUBSXrr:
+ case AArch64::SUBSXri:
+ return true;
+ }
+ return false;
+ }
+ }
+
+ return false;
+}
+
+/// Compare and conditional set.
+static bool isCmpCSetPair(const MCInst &First, const MCInst &Second) {
+ switch (Second.getOpcode()) {
+ case AArch64::CSINCWr: {
+ // 32 bits
+ MCOperand Op1 = Second.getOperand(1);
+ MCOperand Op2 = Second.getOperand(2);
+ if (!Op1.isReg() || Op1.getReg() != AArch64::WZR)
+ return false;
+ if (!Op2.isReg() || Op2.getReg() != AArch64::WZR)
+ return false;
+
+ switch (First.getOpcode()) {
+ case AArch64::SUBSWrs: {
+ if (AArch64_MC::hasShiftedReg(First))
+ return false;
+ break;
+ }
+ case AArch64::SUBSWrx: {
+ if (AArch64_MC::hasExtendedReg(First))
+ return false;
+ break;
+ }
+ case AArch64::SUBSWri:
+ case AArch64::SUBSWrr:
+ break;
+ default:
+ return false;
+ }
+
+ MCOperand Def = First.getOperand(0);
+ return (Def.isReg() && Def.getReg() == AArch64::WZR);
+ }
+ case AArch64::CSINCXr: {
+ // 64 bits
+ MCOperand Op1 = Second.getOperand(1);
+ MCOperand Op2 = Second.getOperand(2);
+ if (!Op1.isReg() || Op1.getReg() != AArch64::XZR)
+ return false;
+ if (!Op2.isReg() || Op2.getReg() != AArch64::XZR)
+ return false;
+
+ switch (First.getOpcode()) {
+ case AArch64::SUBSXrs: {
+ if (AArch64_MC::hasShiftedReg(First))
+ return false;
+ break;
+ }
+ case AArch64::SUBSXrx:
+ case AArch64::SUBSXrx64: {
+ if (AArch64_MC::hasExtendedReg(First))
+ return false;
+ break;
+ }
+ case AArch64::SUBSXri:
+ case AArch64::SUBSXrr:
+ break;
+ }
+
+ MCOperand Def = First.getOperand(0);
+ return (Def.isReg() && Def.getReg() == AArch64::XZR);
+ }
+ }
+ return false;
+}
+
+/// CMN, CMP, TST followed by Bcc
+static bool isArithmeticBccPair(const MCInst &First, const MCInst &Second,
+ bool CmpOnly) {
+ if (Second.getOpcode() != AArch64::Bcc)
+ return false;
+
+ // If we're in CmpOnly mode, we only fuse arithmetic instructions that
+ // discard their result.
+ if (CmpOnly) {
+ MCOperand Def = First.getOperand(0);
+ if (!Def.isReg())
+ return false;
+
+ if (Def.getReg() != AArch64::XZR && Def.getReg() != AArch64::WZR) {
+ return false;
+ }
+ }
+
+ switch (First.getOpcode()) {
+ case AArch64::ADDSWri:
+ case AArch64::ADDSWrr:
+ case AArch64::ADDSXri:
+ case AArch64::ADDSXrr:
+ case AArch64::ANDSWri:
+ case AArch64::ANDSWrr:
+ case AArch64::ANDSXri:
+ case AArch64::ANDSXrr:
+ case AArch64::SUBSWri:
+ case AArch64::SUBSWrr:
+ case AArch64::SUBSXri:
+ case AArch64::SUBSXrr:
+ case AArch64::BICSWrr:
+ case AArch64::BICSXrr:
+ return true;
+ case AArch64::ADDSWrs:
+ case AArch64::ADDSXrs:
+ case AArch64::ANDSWrs:
+ case AArch64::ANDSXrs:
+ case AArch64::SUBSWrs:
+ case AArch64::SUBSXrs:
+ case AArch64::BICSWrs:
+ case AArch64::BICSXrs:
+ return !AArch64_MC::hasShiftedReg(First);
+ }
+
+ return false;
+}
+
+// Move all writes from Second to First instruction.
+static void moveWrites(mca::Instruction &First, mca::Instruction &Second) {
+ for (WriteState &SecondDef : Second.getDefs()) {
+ bool AlreadyDefined = false;
+ for (WriteState &FirstDef : First.getDefs()) {
+ if (FirstDef.getRegisterID() == SecondDef.getRegisterID()) {
+ AlreadyDefined = true;
+ break;
+ }
+ }
+
+ if (!AlreadyDefined)
+ First.getDefs().push_back(SecondDef);
+
+ // Second instruction is going to be eliminated, so it cannot have
+ // any active writes.
+ SecondDef.setRegisterID(0);
+ }
+}
+
+// Move all reads from Second to First instruction.
+static void moveReads(mca::Instruction &First, mca::Instruction &Second) {
+ for (ReadState &SecondUse : Second.getUses()) {
+ bool AlreadyUsed = false;
+ for (ReadState &FirstUse : First.getUses()) {
+ if (SecondUse.getRegisterID() == FirstUse.getRegisterID()) {
+ AlreadyUsed = true;
+ break;
+ }
+ }
+
+ // Ignore reads of registers which are defined by the instruction
+ // we move them to.
+ bool IsDef = false;
+ for (WriteState &FirstDef : First.getDefs()) {
+ if (SecondUse.getRegisterID() == FirstDef.getRegisterID()) {
+ IsDef = true;
+ break;
+ }
+ }
+
+ if (!AlreadyUsed && !IsDef)
+ First.getUses().push_back(SecondUse);
+ }
+}
+
+static void fuseInstructions(mca::Instruction &First,
+ mca::Instruction &Second) {
+ moveReads(First, Second);
+ moveWrites(First, Second);
+ Second.setEliminated();
+}
+
+static bool tryFuseInstructions(mca::Instruction &First, const MCInst &FirstMCI,
+ mca::Instruction &Second,
+ const MCInst &SecondMCI,
+ const MCSubtargetInfo &STI) {
+ if (STI.hasFeature(AArch64::FeatureFuseAES) &&
+ isAESPair(FirstMCI, SecondMCI)) {
+ fuseInstructions(First, Second);
+ return true;
+ }
+
+ if (STI.hasFeature(AArch64::FeatureFuseCmpCSel) &&
+ isCmpCSelPair(FirstMCI, SecondMCI)) {
+ fuseInstructions(First, Second);
+ return true;
+ }
+
+ if (STI.hasFeature(AArch64::FeatureFuseCmpCSet) &&
+ isCmpCSetPair(FirstMCI, SecondMCI)) {
+ fuseInstructions(First, Second);
+ return true;
+ }
+
+ if (STI.hasFeature(AArch64::FeatureCmpBccFusion) ||
+ STI.hasFeature(AArch64::FeatureArithmeticBccFusion)) {
+ bool CmpOnly = !STI.hasFeature(AArch64::FeatureArithmeticBccFusion);
+ if (isArithmeticBccPair(FirstMCI, SecondMCI, CmpOnly)) {
+ fuseInstructions(First, Second);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void AArch64InstrPostProcess::postProcessInstruction(mca::Instruction &Inst,
+ const MCInst &MCI) {
+ if (!PreviousInst) {
+ PreviousInst = &Inst;
+ PreviousMCI = &MCI;
+ return;
+ }
+
+ if (tryFuseInstructions(*PreviousInst, *PreviousMCI, Inst, MCI, STI)) {
+ PreviousInst = nullptr;
+ PreviousMCI = nullptr;
+ } else {
+ PreviousInst = &Inst;
+ PreviousMCI = &MCI;
+ }
+}
+
+void AArch64InstrPostProcess::resetState() { PreviousInst = nullptr; }
+
+static InstrPostProcess *
+createAArch64InstrPostProcess(const MCSubtargetInfo &STI,
+ const MCInstrInfo &MCII) {
+ return new AArch64InstrPostProcess(STI, MCII);
+}
+
+extern "C" LLVM_C_ABI void LLVMInitializeAArch64TargetMCA() {
+ Target *Targets[] = {
+ &getTheAArch64leTarget(), &getTheAArch64beTarget(),
+ &getTheAArch64_32Target(), &getTheARM64Target(),
+ &getTheARM64_32Target(),
+ };
+
+ for (Target *T : Targets) {
+ TargetRegistry::RegisterInstrPostProcess(*T, createAArch64InstrPostProcess);
+ }
+}
diff --git a/llvm/lib/Target/AArch64/MCA/CMakeLists.txt b/llvm/lib/Target/AArch64/MCA/CMakeLists.txt
new file mode 100644
index 0000000000000..430fce941efb8
--- /dev/null
+++ b/llvm/lib/Target/AArch64/MCA/CMakeLists.txt
@@ -0,0 +1,12 @@
+add_llvm_component_library(LLVMAArch64TargetMCA
+ AArch64CustomBehaviour.cpp
+
+ LINK_COMPONENTS
+ AArch64Desc
+ AArch64Info
+ MCA
+ Support
+
+ ADD_TO_COMPONENT
+ AArch64
+ )
diff --git a/llvm/test/tools/llvm-mca/AArch64/Neoverse/N2-fuse-aes.s b/llvm/test/tools/llvm-mca/AArch64/Neoverse/N2-fuse-aes.s
new file mode 100644
index 0000000000000..967679ac3e626
--- /dev/null
+++ b/llvm/test/tools/llvm-mca/AArch64/Neoverse/N2-fuse-aes.s
@@ -0,0 +1,118 @@
+# NOTE: Assertions have been autogenerated by utils/update_mca_test_checks.py
+# RUN: llvm-mca -mtriple=aarch64 -mcpu=neoverse-n2 -timeline -mattr=+aes,+fuse-aes < %s | FileCheck %s
+
+aese v0.16b, v1.16b
+aesmc v0.16b, v0.16b
+aesd v2.16b, v3.16b
+aesimc v2.16b, v2.16b
+
+# CHECK: Iterations: 100
+# CHECK-NEXT: Instructions: 400
+# CHECK-NEXT: Total Cycles: 203
+# CHECK-NEXT: Total uOps: 400
+
+# CHECK: Dispatch Width: 5
+# CHECK-NEXT: uOps Per Cycle: 1.97
+# CHECK-NEXT: IPC: 1.97
+# CHECK-NEXT: Block RThroughput: 2.0
+
+# CHECK: Instruction Info:
+# CHECK-NEXT: [1]: #uOps
+# CHECK-NEXT: [2]: Latency
+# CHECK-NEXT: [3]: RThroughput
+# CHECK-NEXT: [4]: MayLoad
+# CHECK-NEXT: [5]: MayStore
+# CHECK-NEXT: [6]: HasSideEffects (U)
+
+# CHECK: [1] [2] [3] [4] [5] [6] Instructions:
+# CHECK-NEXT: 1 2 0.50 aese v0.16b, v1.16b
+# CHECK-NEXT: 1 2 0.50 aesmc v0.16b, v0.16b
+# CHECK-NEXT: 1 2 0.50 aesd v2.16b, v3.16b
+# CHECK-NEXT: 1 2 0.50 aesimc v2.16b, v2.16b
+
+# CHECK: Resources:
+# CHECK-NEXT: [0.0] - N2UnitB
+# CHECK-NEXT: [0.1] - N2UnitB
+# CHECK-NEXT: [1.0] - N2UnitD
+# CHECK-NEXT: [1.1] - N2UnitD
+# CHECK-NEXT: [2.0] - N2UnitFlg
+# CHECK-NEXT: [2.1] - N2UnitFlg
+# CHECK-NEXT: [2.2] - N2UnitFlg
+# CHECK-NEXT: [3] - N2UnitL2
+# CHECK-NEXT: [4.0] - N2UnitL01
+# CHECK-NEXT: [4.1] - N2UnitL01
+# CHECK-NEXT: [5] - N2UnitM0
+# CHECK-NEXT: [6] - N2UnitM1
+# CHECK-NEXT: [7.0] - N2UnitS
+# CHECK-NEXT: [7.1] - N2UnitS
+# CHECK-NEXT: [8] - N2UnitV0
+# CHECK-NEXT: [9] - N2UnitV1
+
+# CHECK: Resource pressure per iteration:
+# CHECK-NEXT: [0.0] [0.1] [1.0] [1.1] [2.0] [2.1] [2.2] [3] [4.0] [4.1] [5] [6] [7.0] [7.1] [8] [9]
+# CHECK-NEXT: - - - - - - - - - - - - - - 1.00 1.00
+
+# CHECK: Resource pressure by instruction:
+# CHECK-NEXT: [0.0] [0.1] [1.0] [1.1] [2.0] [2.1] [2.2] [3] [4.0] [4.1] [5] [6] [7.0] [7.1] [8] [9] Instructions:
+# CHECK-NEXT: - - - - - - - - - - - - - - - 1.00 aese v0.16b, v1.16b
+# CHECK-NEXT: - - - - - - - - - - - - - - - - aesmc v0.16b, v0.16b
+# CHECK-NEXT: - - - - - - - - - - - - - - 1.00 - aesd v2.16b, v3.16b
+# CHECK-NEXT: - - - - - - - - - - - - - - - - aesimc v2.16b, v2.16b
+
+# CHECK: Timeline view:
+# CHECK-NEXT: 0123456789
+# CHECK-NEXT: Index 0123456789 012
+
+# CHECK: [0,0] DeeER. . . . . aese v0.16b, v1.16b
+# CHECK-NEXT: [0,1] D---R. . . . . aesmc v0.16b, v0.16b
+# CHECK-NEXT: [0,2] DeeER. . . . . aesd v2.16b, v3.16b
+# CHECK-NEXT: [0,3] D---R. . . . . aesimc v2.16b, v2.16b
+# CHECK-NEXT: [1,0] D==eeER . . . . aese v0.16b, v1.16b
+# CHECK-NEXT: [1,1] .D----R . . . . aesmc v0.16b, v0.16b
+# CHECK-NEXT: [1,2] .D=eeER . . . . aesd v2.16b, v3.16b
+# CHECK-NEXT: [1,3] .D----R . . . . aesimc v2.16b, v2.16b
+# CHECK-NEXT: [2,0] .D===eeER . . . . aese v0.16b, v1.16b
+# CHECK-NEXT: [2,1] .D------R . . . . aesmc v0.16b, v0.16b
+# CHECK-NEXT: [2,2] . D==eeER . . . . aesd v2.16b, v3.16b
+# CHECK-NEXT: [2,3] . D-----R . . . . aesimc v2.16b, v2.16b
+# CHECK-NEXT: [3,0] . D====eeER . . . aese v0.16b, v1.16b
+# CHECK-NEXT: [3,1] . D-------R . . . aesmc v0.16b, v0.16b
+# CHECK-NEXT: [3,2] . D====eeER . . . aesd v2.16b, v3.16b
+# CHECK-NEXT: [3,3] . D------R . . . aesimc v2.16b, v2.16b
+# CHECK-NEXT: [4,0] . D=====eeER . . . aese v0.16b, v1.16b
+# CHECK-NEXT: [4,1] . D--------R . . . aesmc v0.16b, v0.16b
+# CHECK-NEXT: [4,2] . D=====eeER . . . aesd v2.16b, v3.16b
+# CHECK-NEXT: [4,3] . D--------R . . . aesimc v2.16b, v2.16b
+# CHECK-NEXT: [5,0] . D======eeER. . . aese v0.16b, v1.16b
+# CHECK-NEXT: [5,1] . D---------R. . . aesmc v0.16b, v0.16b
+# CHECK-NEXT: [5,2] . D======eeER. . . aesd v2.16b, v3.16b
+# CHECK-NEXT: [5,3] . D---------R. . . aesimc v2.16b, v2.16b
+# CHECK-NEXT: [6,0] . D========eeER . . aese v0.16b, v1.16b
+# CHECK-NEXT: [6,1] . D----------R . . aesmc v0.16b, v0.16b
+# CHECK-NEXT: [6,2] . D=======eeER . . aesd v2.16b, v3.16b
+# CHECK-NEXT: [6,3] . D----------R . . aesimc v2.16b, v2.16b
+# CHECK-NEXT: [7,0] . D=========eeER . . aese v0.16b, v1.16b
+# CHECK-NEXT: [7,1] . D------------R . . aesmc v0.16b, v0.16b
+# CHECK-NEXT: [7,2] . .D========eeER . . aesd v2.16b, v3.16b
+# CHECK-NEXT: [7,3] . .D-----------R . . aesimc v2.16b, v2.16b
+# CHECK-NEXT: [8,0] . .D==========eeER . aese v0.16b, v1.16b
+# CHECK-NEXT: [8,1] . .D-------------R . aesmc v0.16b, v0.16b
+# CHECK-NEXT: [8,2] . .D==========eeER . aesd v2.16b, v3.16b
+# CHECK-NEXT: [8,3] . . D------------R . aesimc v2.16b, v2.16b
+# CHECK-NEXT: [9,0] . . D===========eeER aese v0.16b, v1.16b
+# CHECK-NEXT: [9,1] . . D--------------R aesmc v0.16b, v0.16b
+# CHECK-NEXT: [9,2] . . D===========eeER aesd v2.16b, v3.16b
+# CHECK-NEXT: [9,3] . . D--------------R aesimc v2.16b, v2.16b
+
+# CHECK: Average Wait times (based on the timeline view):
+# CHECK-NEXT: [0]: Executions
+# CHECK-NEXT: [1]: Average time spent waiting in a scheduler's queue
+# CHECK-NEXT: [2]: Average time spent waiting in a scheduler's queue while ready
+# CHECK-NEXT: [3]: Average time elapsed from WB until retire stage
+
+# CHECK: [0] [1] [2] [3]
+# CHECK-NEXT: 0. 10 6.8 0.1 0.0 aese v0.16b, v1.16b
+# CHECK-NEXT: 1. 10 0.0 0.0 8.6 aesmc v0.16b, v0.16b
+# CHECK-NEXT: 2. 10 6.4 0.1 0.0 aesd v2.16b, v3.16b
+# CHECK-NEXT: 3. 10 0.0 0.0 8.2 aesimc v2.16b, v2.16b
+# CHECK-NEXT: 40 3.3 0.1 4.2 <total>
diff --git a/llvm/test/tools/llvm-mca/AArch64/Neoverse/N2-fuse-bcc.s b/llvm/test/tools/llvm-mca/AArch64/Neoverse/N2-fuse-bcc.s
new file mode 100644
index 0000000000000..2236b664b6dda
--- /dev/null
+++ b/llvm/test/tools/llvm-mca/AArch64/Neoverse/N2-fuse-bcc.s
@@ -0,0 +1,455 @@
+# NOTE: Assertions have been autogenerated by utils/update_mca_test_checks.py
+# RUN: llvm-mca -mtriple=aarch64 -mcpu=neoverse-n2 -mattr=+cmp-bcc-fusion -timeline -iterations=2 < %s | FileCheck %s
+
+# Fusion
+
+cmp w3, w5
+b.ne #4
+
+cmp w3, #1
+b.ne #4
+
+cmp w3, #1, lsl 0
+b.ne #4
+
+cmn w3, w5
+b.ne #4
+
+cmn w3, #1
+b.ne #4
+
+cmn w3, #1, lsl 0
+b.ne #4
+
+tst w3, w5
+b.ne #4
+
+tst w3, #1
+b.ne #4
+
+tst w3, w5, lsl 0
+b.ne #4
+
+bics wzr, w5, w7
+b.ne #4
+
+cmp x3, x5
+b.ne #4
+
+cmp x3, #1
+b.ne #4
+
+cmp x3, #1, lsl 0
+b.ne #4
+
+cmn x3, x5
+b.ne #4
+
+cmn x3, #1
+b.ne #4
+
+cmn x3, #1, lsl 0
+b.ne #4
+
+tst x3, x5
+b.ne #4
+
+tst x3, #1
+b.ne #4
+
+tst x3, x5, lsl 0
+b.ne #4
+
+bics xzr, x5, x7
+b.ne #4
+
+# No fusion
+
+subs w0, w3, w5
+b.ne #4
+
+subs w0, w3, #1
+b.ne #4
+
+cmp w3, w5, lsl 1
+b.ne #4
+
+adds w0, w3, w5
+b.ne #4
+
+adds w0, w3, #1
+b.ne #4
+
+cmn w3, w5, lsl 1
+b.ne #4
+
+ands w0, w3, w5
+b.ne #4
+
+ands w0, w3, #1
+b.ne #4
+
+tst w3, w5, lsl 1
+b.ne #4
+
+bics wzr, w5, w7, lsl 1
+b.ne #4
+
+# CHECK: Iterations: 2
+# CHECK-NEXT: Instructions: 120
+# CHECK-NEXT: Total Cycles: 29
+# CHECK-NEXT: Total uOps: 120
+
+# CHECK: Dispatch...
[truncated]
``````````
</details>
https://github.com/llvm/llvm-project/pull/215789
More information about the llvm-commits
mailing list