[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