[llvm] [llvm-exegesis] Process memory operands with unsuitable register class (PR #190342)

Timur Baidusenov via llvm-commits llvm-commits at lists.llvm.org
Mon Apr 13 05:58:51 PDT 2026


https://github.com/bai-tim updated https://github.com/llvm/llvm-project/pull/190342

>From 71f3c3435b2a40e6a23a8c90231ea5d4477f5b0c Mon Sep 17 00:00:00 2001
From: bai-tim <timurbaidusenov at gmail.com>
Date: Fri, 6 Mar 2026 13:46:05 +0000
Subject: [PATCH] [llvm-exegesis] Process memory operands with unsuitable
 register class

---
 .../llvm-exegesis/RISCV/mem-register-sp.s     | 41 +++++++++++
 llvm/tools/llvm-exegesis/lib/Assembler.cpp    |  2 +
 .../tools/llvm-exegesis/lib/BenchmarkResult.h |  2 +
 llvm/tools/llvm-exegesis/lib/CodeTemplate.cpp | 12 ++++
 llvm/tools/llvm-exegesis/lib/CodeTemplate.h   |  3 +
 .../lib/ParallelSnippetGenerator.cpp          | 34 ++++++---
 .../lib/ParallelSnippetGenerator.h            |  5 +-
 llvm/tools/llvm-exegesis/lib/RISCV/Target.cpp | 50 +++++++------
 .../lib/SerialSnippetGenerator.cpp            | 48 +++++++------
 .../llvm-exegesis/lib/SnippetGenerator.cpp    | 72 ++++++++++++++++++-
 .../llvm-exegesis/lib/SnippetGenerator.h      |  6 ++
 llvm/tools/llvm-exegesis/lib/Target.h         |  8 +++
 12 files changed, 229 insertions(+), 54 deletions(-)
 create mode 100644 llvm/test/tools/llvm-exegesis/RISCV/mem-register-sp.s

diff --git a/llvm/test/tools/llvm-exegesis/RISCV/mem-register-sp.s b/llvm/test/tools/llvm-exegesis/RISCV/mem-register-sp.s
new file mode 100644
index 0000000000000..0bb6b08750194
--- /dev/null
+++ b/llvm/test/tools/llvm-exegesis/RISCV/mem-register-sp.s
@@ -0,0 +1,41 @@
+# RUN: llvm-exegesis -mode=latency --benchmark-phase=assemble-measured-code -opcode-name=C_LWSP --min-instructions=100000 \
+# RUN:   --dump-object-to-disk=%d -mtriple=riscv32-unknown-linux-gnu --mcpu=generic --repetition-mode=loop --loop-body-size=100 \
+# RUN:   -mattr=+c
+# RUN: llvm-objdump -M numeric -d %d > %t.s
+# RUN: FileCheck %s < %t.s --check-prefix=CHECK-LOAD-ASM
+
+# RUN: llvm-exegesis -mode=latency --benchmark-phase=assemble-measured-code -opcode-name=C_LWSP --min-instructions=100000 \
+# RUN:   --dump-object-to-disk=%d -mtriple=riscv32-unknown-linux-gnu --mcpu=generic --repetition-mode=loop --loop-body-size=100 \
+# RUN:   -mattr=+c --mode=inverse_throughput
+# RUN: llvm-objdump -M numeric -d %d > %t.s
+# RUN: FileCheck %s < %t.s --check-prefix=CHECK-LOAD-ASM
+
+# RUN: llvm-exegesis -mode=latency --benchmark-phase=assemble-measured-code -opcode-name=C_LWSP --min-instructions=100000 \
+# RUN:   --dump-object-to-disk=%d -mtriple=riscv64-unknown-linux-gnu --mcpu=generic --repetition-mode=loop --loop-body-size=100 \
+# RUN:   -mattr=+c
+# RUN: llvm-objdump -M numeric -d %d > %t.s
+# RUN: FileCheck %s < %t.s --check-prefix=CHECK-LOAD-ASM
+
+# RUN: llvm-exegesis -mode=latency --benchmark-phase=assemble-measured-code -opcode-name=C_LDSP --min-instructions=100000 \
+# RUN:   --dump-object-to-disk=%d -mtriple=riscv64-unknown-linux-gnu --mcpu=generic --repetition-mode=loop --loop-body-size=100 \
+# RUN:   -mattr=+c
+# RUN: llvm-objdump -M numeric -d %d > %t.s
+# RUN: FileCheck %s < %t.s --check-prefix=CHECK-LOAD-ASM
+
+# RUN: llvm-exegesis -mode=latency --benchmark-phase=assemble-measured-code -opcode-name=C_SWSP --min-instructions=100000 \
+# RUN:   --dump-object-to-disk=%d -mtriple=riscv32-unknown-linux-gnu --mcpu=generic --repetition-mode=loop --loop-body-size=100 \
+# RUN:   -mattr=+c --mode=inverse_throughput
+# RUN: llvm-objdump -M numeric -d %d > %t.s
+# RUN: FileCheck %s < %t.s --check-prefix=CHECK-STORE-ASM
+
+# RUN: llvm-exegesis -mode=latency --benchmark-phase=assemble-measured-code -opcode-name=C_SDSP --min-instructions=100000 \
+# RUN:   --dump-object-to-disk=%d -mtriple=riscv64-unknown-linux-gnu --mcpu=generic --repetition-mode=loop --loop-body-size=100 \
+# RUN:   -mattr=+c --mode=inverse_throughput
+# RUN: llvm-objdump -M numeric -d %d > %t.s
+# RUN: FileCheck %s < %t.s --check-prefix=CHECK-STORE-ASM
+
+CHECK-LOAD-ASM: mv x2, x10
+CHECK-LOAD-ASM-COUNT-100: l{{[wd]}} x{{[0-9]+}}, 0x0(x2)
+
+CHECK-STORE-ASM: mv x2, x10
+CHECK-STORE-ASM-COUNT-100: s{{[wd]}} x{{[0-9]+}}, 0x0(x2)
diff --git a/llvm/tools/llvm-exegesis/lib/Assembler.cpp b/llvm/tools/llvm-exegesis/lib/Assembler.cpp
index 7191e49c6dccd..61bdac8ba3605 100644
--- a/llvm/tools/llvm-exegesis/lib/Assembler.cpp
+++ b/llvm/tools/llvm-exegesis/lib/Assembler.cpp
@@ -291,6 +291,8 @@ Error assembleToStream(const ExegesisTarget &ET,
       Entry.MBB->addLiveIn(Reg);
   }
 
+  Entry.addInstructions(Key.PrologueInstructions);
+
   const bool IsSnippetSetupComplete = generateSnippetSetupCode(
       ET, TM->getMCSubtargetInfo(), Entry, Key, GenerateMemoryInstructions);
 
diff --git a/llvm/tools/llvm-exegesis/lib/BenchmarkResult.h b/llvm/tools/llvm-exegesis/lib/BenchmarkResult.h
index 7984c8805cadc..c737ccd2f5b7d 100644
--- a/llvm/tools/llvm-exegesis/lib/BenchmarkResult.h
+++ b/llvm/tools/llvm-exegesis/lib/BenchmarkResult.h
@@ -76,6 +76,8 @@ struct BenchmarkKey {
   uintptr_t SnippetAddress = 0;
   // The register that should be used to hold the loop counter.
   MCRegister LoopRegister;
+  // Instructions for inserting into entry.
+  std::vector<MCInst> PrologueInstructions;
 };
 
 struct BenchmarkMeasure {
diff --git a/llvm/tools/llvm-exegesis/lib/CodeTemplate.cpp b/llvm/tools/llvm-exegesis/lib/CodeTemplate.cpp
index fd156ee01e7ce..72080f117b336 100644
--- a/llvm/tools/llvm-exegesis/lib/CodeTemplate.cpp
+++ b/llvm/tools/llvm-exegesis/lib/CodeTemplate.cpp
@@ -63,6 +63,18 @@ bool InstructionTemplate::hasImmediateVariables() const {
   });
 }
 
+Operand InstructionTemplate::getMemOpReg() const {
+  auto &I = getInstr();
+  auto MemOpIt =
+      find_if(I.Operands, [](const Operand &Op) { return Op.isMemory(); });
+  assert(MemOpIt != I.Operands.end() &&
+         "Instruction must have memory operands");
+
+  const Operand &MemOp = *MemOpIt;
+  assert(MemOp.isReg() && "Memory operand expected to be register");
+  return MemOp;
+}
+
 MCInst InstructionTemplate::build() const {
   MCInst Result;
   Result.setOpcode(Instr->Description.Opcode);
diff --git a/llvm/tools/llvm-exegesis/lib/CodeTemplate.h b/llvm/tools/llvm-exegesis/lib/CodeTemplate.h
index a65015b45b786..d1a3d2faa3bca 100644
--- a/llvm/tools/llvm-exegesis/lib/CodeTemplate.h
+++ b/llvm/tools/llvm-exegesis/lib/CodeTemplate.h
@@ -38,6 +38,7 @@ struct InstructionTemplate {
   bool hasImmediateVariables() const;
   const Instruction &getInstr() const { return *Instr; }
   ArrayRef<MCOperand> getVariableValues() const { return VariableValues; }
+  Operand getMemOpReg() const;
   void setVariableValues(ArrayRef<MCOperand> NewVariableValues) {
     assert(VariableValues.size() == NewVariableValues.size() &&
            "Value count mismatch");
@@ -133,6 +134,8 @@ struct CodeTemplate {
   // the pointer to this memory is passed in to the function.
   MCRegister ScratchSpacePointerInReg;
 
+  std::vector<MCInst> Prologue;
+
 #if defined(__GNUC__) && (defined(__clang__) || LLVM_GNUC_PREREQ(8, 0, 0))
   // FIXME: GCC7 bug workaround. Drop #if after GCC7 no longer supported.
 private:
diff --git a/llvm/tools/llvm-exegesis/lib/ParallelSnippetGenerator.cpp b/llvm/tools/llvm-exegesis/lib/ParallelSnippetGenerator.cpp
index aa409138ae71b..2333fb2d2131d 100644
--- a/llvm/tools/llvm-exegesis/lib/ParallelSnippetGenerator.cpp
+++ b/llvm/tools/llvm-exegesis/lib/ParallelSnippetGenerator.cpp
@@ -88,28 +88,36 @@ static bool hasVariablesWithTiedOperands(const Instruction &Instr) {
 
 ParallelSnippetGenerator::~ParallelSnippetGenerator() = default;
 
-void ParallelSnippetGenerator::instantiateMemoryOperands(
+Error ParallelSnippetGenerator::callInstantiateMemoryOperands(
     const MCRegister ScratchSpacePointerInReg,
-    std::vector<InstructionTemplate> &Instructions) const {
+    std::vector<InstructionTemplate> &Instructions,
+    std::vector<MCInst> &Prologue, const BitVector &ForbiddenRegisters) const {
   if (!ScratchSpacePointerInReg)
-    return; // no memory operands.
+    return Error::success(); // no memory operands.
   const auto &ET = State.getExegesisTarget();
   const unsigned MemStep = ET.getMaxMemoryAccessSize();
   const size_t OriginalInstructionsSize = Instructions.size();
   size_t I = 0;
   for (InstructionTemplate &IT : Instructions) {
-    ET.fillMemoryOperands(IT, ScratchSpacePointerInReg, I * MemStep);
+    if (auto Err = instantiateMemoryOperands(
+            State, IT, ScratchSpacePointerInReg, I * MemStep, Prologue,
+            ForbiddenRegisters))
+      return Err;
     ++I;
   }
 
   while (Instructions.size() < kMinNumDifferentAddresses) {
     InstructionTemplate IT = Instructions[I % OriginalInstructionsSize];
-    ET.fillMemoryOperands(IT, ScratchSpacePointerInReg, I * MemStep);
+    if (auto Err = instantiateMemoryOperands(
+            State, IT, ScratchSpacePointerInReg, I * MemStep, Prologue,
+            ForbiddenRegisters))
+      return Err;
     ++I;
     Instructions.push_back(std::move(IT));
   }
   assert(I * MemStep < BenchmarkRunner::ScratchSpace::kSize &&
          "not enough scratch space");
+  return Error::success();
 }
 
 enum class RegRandomizationStrategy : uint8_t {
@@ -304,13 +312,19 @@ ParallelSnippetGenerator::generateCodeTemplates(
   if (SelfAliasing.empty()) {
     CT.Info = "instruction is parallel, repeating a random one.";
     CT.Instructions.push_back(std::move(Variant));
-    instantiateMemoryOperands(CT.ScratchSpacePointerInReg, CT.Instructions);
+    if (auto Err = callInstantiateMemoryOperands(CT.ScratchSpacePointerInReg,
+                                                 CT.Instructions, CT.Prologue,
+                                                 ForbiddenRegisters))
+      return std::move(Err);
     return getSingleton(std::move(CT));
   }
   if (SelfAliasing.hasImplicitAliasing()) {
     CT.Info = "instruction is serial, repeating a random one.";
     CT.Instructions.push_back(std::move(Variant));
-    instantiateMemoryOperands(CT.ScratchSpacePointerInReg, CT.Instructions);
+    if (auto Err = callInstantiateMemoryOperands(CT.ScratchSpacePointerInReg,
+                                                 CT.Instructions, CT.Prologue,
+                                                 ForbiddenRegisters))
+      return std::move(Err);
     return getSingleton(std::move(CT));
   }
   std::vector<CodeTemplate> Result;
@@ -343,8 +357,10 @@ ParallelSnippetGenerator::generateCodeTemplates(
       return make_error<StringError>(
           Twine("Failed to produce any snippet via: ").concat(CurrCT.Info),
           inconvertibleErrorCode());
-    instantiateMemoryOperands(CurrCT.ScratchSpacePointerInReg,
-                              CurrCT.Instructions);
+    if (auto Err = callInstantiateMemoryOperands(
+            CurrCT.ScratchSpacePointerInReg, CurrCT.Instructions,
+            CurrCT.Prologue, ForbiddenRegisters))
+      return std::move(Err);
     Result.push_back(std::move(CurrCT));
   }
   return Result;
diff --git a/llvm/tools/llvm-exegesis/lib/ParallelSnippetGenerator.h b/llvm/tools/llvm-exegesis/lib/ParallelSnippetGenerator.h
index d3c85c0c303a2..4609c2397d47e 100644
--- a/llvm/tools/llvm-exegesis/lib/ParallelSnippetGenerator.h
+++ b/llvm/tools/llvm-exegesis/lib/ParallelSnippetGenerator.h
@@ -54,9 +54,10 @@ class ParallelSnippetGenerator : public SnippetGenerator {
   //   mov eax, [rdi + 128]
   //   add eax, [rdi + 192]
   //   mov eax, [rdi + 256]
-  void instantiateMemoryOperands(
+  Error callInstantiateMemoryOperands(
       MCRegister ScratchSpaceReg,
-      std::vector<InstructionTemplate> &SnippetTemplate) const;
+      std::vector<InstructionTemplate> &SnippetTemplate,
+      std::vector<MCInst> &Prologue, const BitVector &ForbiddenRegisters) const;
 };
 
 } // namespace exegesis
diff --git a/llvm/tools/llvm-exegesis/lib/RISCV/Target.cpp b/llvm/tools/llvm-exegesis/lib/RISCV/Target.cpp
index 887df7ac7b393..14c720a5d8c7a 100644
--- a/llvm/tools/llvm-exegesis/lib/RISCV/Target.cpp
+++ b/llvm/tools/llvm-exegesis/lib/RISCV/Target.cpp
@@ -736,6 +736,10 @@ class ExegesisRISCVTarget : public ExegesisTarget {
   generateInstructionVariants(const Instruction &Instr,
                               unsigned MaxConfigsPerOpcode) const override;
 
+  virtual std::optional<MCInst>
+  createCopyInstruction(MCRegister Src, MCRegister Dst) const override;
+  void processInstructionReservedRegs(InstructionTemplate &IT) const override;
+
   void addTargetSpecificPasses(PassManagerBase &PM) const override {
     // Turn AVL operand of physical registers into virtual registers.
     PM.add(exegesis::createRISCVPreprocessingPass());
@@ -808,29 +812,18 @@ void ExegesisRISCVTarget::fillMemoryOperands(InstructionTemplate &IT,
                                              unsigned Offset) const {
   // TODO: for now we ignore Offset because have no way
   // to detect it in instruction.
-  auto &I = IT.getInstr();
-
-  auto MemOpIt =
-      find_if(I.Operands, [](const Operand &Op) { return Op.isMemory(); });
-  assert(MemOpIt != I.Operands.end() &&
-         "Instruction must have memory operands");
-
-  const Operand &MemOp = *MemOpIt;
-
-  assert(MemOp.isReg() && "Memory operand expected to be register");
-
-  unsigned Opcode = I.getOpcode();
-  if (Opcode == RISCV::C_LDSP || Opcode == RISCV::C_LWSP ||
-      Opcode == RISCV::C_SDSP || Opcode == RISCV::C_SWSP) {
-    IT.getValueFor(I.Operands[0]) = MCOperand::createReg(RISCV::X2);
-    // Force base register to SP (X2)
-    IT.getValueFor(MemOp) = MCOperand::createReg(RISCV::X2);
-    return;
-  }
-
+  const Operand &MemOp = IT.getMemOpReg();
   IT.getValueFor(MemOp) = MCOperand::createReg(Reg);
 }
 
+std::optional<MCInst>
+ExegesisRISCVTarget::createCopyInstruction(MCRegister Src,
+                                           MCRegister Dst) const {
+  if (!RISCV::GPRRegClass.contains(Src) || !RISCV::GPRRegClass.contains(Dst))
+    return std::nullopt;
+  return MCInstBuilder(RISCV::ADDI).addReg(Dst).addReg(Src).addImm(0);
+}
+
 const MCPhysReg UnavailableRegisters[4] = {RISCV::X0, DefaultLoopCounterReg,
                                            ScratchIntReg, ScratchMemoryReg};
 
@@ -890,6 +883,23 @@ ExegesisRISCVTarget::generateInstructionVariants(
   return {IT};
 }
 
+// Process instructions that used reserved registers.
+void ExegesisRISCVTarget::processInstructionReservedRegs(
+    InstructionTemplate &IT) const {
+  MCOperand &AssignedValue = IT.getValueFor(IT.getMemOpReg());
+
+  switch (IT.getOpcode()) {
+  case RISCV::C_LWSP:
+  case RISCV::C_LDSP:
+  case RISCV::C_SWSP:
+  case RISCV::C_SDSP:
+    AssignedValue = MCOperand::createReg(RISCV::X2);
+    break;
+  default:
+    break;
+  }
+}
+
 } // anonymous namespace
 
 static ExegesisTarget *getTheRISCVExegesisTarget() {
diff --git a/llvm/tools/llvm-exegesis/lib/SerialSnippetGenerator.cpp b/llvm/tools/llvm-exegesis/lib/SerialSnippetGenerator.cpp
index 71b2d71e084e1..911331a97ec28 100644
--- a/llvm/tools/llvm-exegesis/lib/SerialSnippetGenerator.cpp
+++ b/llvm/tools/llvm-exegesis/lib/SerialSnippetGenerator.cpp
@@ -89,12 +89,13 @@ static ExecutionMode getExecutionModes(const Instruction &Instr,
   return EM;
 }
 
-static void appendCodeTemplates(const LLVMState &State,
-                                InstructionTemplate Variant,
-                                const BitVector &ForbiddenRegisters,
-                                ExecutionMode ExecutionModeBit,
-                                StringRef ExecutionClassDescription,
-                                std::vector<CodeTemplate> &CodeTemplates) {
+static Error appendCodeTemplates(const LLVMState &State,
+                                 const SerialSnippetGenerator &Generator,
+                                 InstructionTemplate Variant,
+                                 const BitVector &ForbiddenRegisters,
+                                 ExecutionMode ExecutionModeBit,
+                                 StringRef ExecutionClassDescription,
+                                 std::vector<CodeTemplate> &CodeTemplates) {
   assert(isEnumValue(ExecutionModeBit) && "Bit must be a power of two");
   switch (ExecutionModeBit) {
   case ExecutionMode::ALWAYS_SERIAL_IMPLICIT_REGS_ALIAS:
@@ -108,7 +109,7 @@ static void appendCodeTemplates(const LLVMState &State,
     CT.Info = std::string(ExecutionClassDescription);
     CT.Instructions.push_back(std::move(Variant));
     CodeTemplates.push_back(std::move(CT));
-    return;
+    return Error::success();
   }
   case ExecutionMode::SERIAL_VIA_MEMORY_INSTR: {
     // Select back-to-back memory instruction.
@@ -126,7 +127,7 @@ static void appendCodeTemplates(const LLVMState &State,
       });
 
       if (DefOpIt == I.Operands.end())
-        return;
+        return Error::success();
 
       const Operand &DefOp = *DefOpIt;
       const ExegesisTarget &ET = State.getExegesisTarget();
@@ -139,17 +140,17 @@ static void appendCodeTemplates(const LLVMState &State,
       // Register classes of def operand and memory operand must be the same
       // to perform aliasing.
       if (!RegClass.contains(ScratchMemoryRegister))
-        return;
-
-      ET.fillMemoryOperands(Variant, ScratchMemoryRegister, 0);
-
-      // Only force the def register to ScratchMemoryRegister if the target
-      // hasn't assigned a value yet.
-      MCOperand &DefVal = Variant.getValueFor(DefOp);
-      if (!DefVal.isValid())
-        DefVal = MCOperand::createReg(ScratchMemoryRegister);
+        return Error::success();
 
       CodeTemplate CT;
+      const Operand &MemOp = Variant.getMemOpReg();
+      if (auto Err = Generator.instantiateMemoryOperands(
+              State, Variant, ScratchMemoryRegister, 0, CT.Prologue,
+              ForbiddenRegisters))
+        return Err;
+      MCOperand MemOpVal = Variant.getValueFor(MemOp);
+      Variant.getValueFor(DefOp) = MCOperand::createReg(MemOpVal.getReg());
+
       CT.Execution = ExecutionModeBit;
       CT.ScratchSpacePointerInReg = ScratchMemoryRegister;
 
@@ -159,7 +160,7 @@ static void appendCodeTemplates(const LLVMState &State,
     }
 
     // TODO: implement more cases
-    return;
+    return Error::success();
   }
   case ExecutionMode::SERIAL_VIA_EXPLICIT_REGS: {
     // Making the execution of this instruction serial by selecting one def
@@ -176,7 +177,7 @@ static void appendCodeTemplates(const LLVMState &State,
     CT.Info = std::string(ExecutionClassDescription);
     CT.Instructions.push_back(std::move(Variant));
     CodeTemplates.push_back(std::move(CT));
-    return;
+    return Error::success();
   }
   case ExecutionMode::SERIAL_VIA_NON_MEMORY_INSTR: {
     const Instruction &Instr = Variant.getInstr();
@@ -199,11 +200,12 @@ static void appendCodeTemplates(const LLVMState &State,
       CT.Instructions.push_back(std::move(OtherIT));
       CodeTemplates.push_back(std::move(CT));
     }
-    return;
+    return Error::success();
   }
   default:
     llvm_unreachable("Unhandled enum value");
   }
+  return Error::success();
 }
 
 SerialSnippetGenerator::~SerialSnippetGenerator() = default;
@@ -216,8 +218,10 @@ SerialSnippetGenerator::generateCodeTemplates(
       getExecutionModes(Variant.getInstr(), ForbiddenRegisters);
   for (const auto EC : kExecutionClasses) {
     for (const auto ExecutionModeBit : getExecutionModeBits(EM & EC.Mask))
-      appendCodeTemplates(State, Variant, ForbiddenRegisters, ExecutionModeBit,
-                          EC.Description, Results);
+      if (auto Err =
+              appendCodeTemplates(State, *this, Variant, ForbiddenRegisters,
+                                  ExecutionModeBit, EC.Description, Results))
+        return std::move(Err);
     if (!Results.empty())
       break;
   }
diff --git a/llvm/tools/llvm-exegesis/lib/SnippetGenerator.cpp b/llvm/tools/llvm-exegesis/lib/SnippetGenerator.cpp
index 86d4e197b6063..5badcc63a01e4 100644
--- a/llvm/tools/llvm-exegesis/lib/SnippetGenerator.cpp
+++ b/llvm/tools/llvm-exegesis/lib/SnippetGenerator.cpp
@@ -63,7 +63,6 @@ Error SnippetGenerator::generateConfigurations(
     const auto &ScratchRegAliases =
         State.getRATC().getRegister(ScratchSpacePointerInReg).aliasedBits();
     // If the instruction implicitly writes to ScratchSpacePointerInReg , abort.
-    // FIXME: We could make a copy of the scratch register.
     for (const auto &Op : Variant.getInstr().Operands) {
       if (Op.isDef() && Op.isImplicitReg() &&
           ScratchRegAliases.test(Op.getImplicitReg().id()))
@@ -95,6 +94,9 @@ Error SnippetGenerator::generateConfigurations(
             return Error;
           BC.Key.Instructions.push_back(Inst);
         }
+
+        BC.Key.PrologueInstructions = CT.Prologue;
+
         if (CT.ScratchSpacePointerInReg)
           BC.LiveIns.push_back(CT.ScratchSpacePointerInReg);
         BC.Key.RegisterInitialValues =
@@ -215,6 +217,74 @@ template <typename C> static decltype(auto) randomElement(const C &Container) {
   return Container[randomIndex(Container.size() - 1)];
 }
 
+// Instantiates memory operands for the given instruction template, ensuring
+// that the register used in the memory operand belongs to the required
+// register class.
+//
+// This function handles cases where the provided register (e.g., from a
+// forced scratch register insertion) may not be compatible with the memory
+// operand's register class. It attempts to:
+//   1. Use the provided register if it is already in the required class.
+//   2. Otherwise, reuse an existing register from the instruction template's
+//      memory operand if it is valid.
+//   3. As a fallback, allocate a free register from the required class and
+//      insert a copy prologue to move the value.
+Error SnippetGenerator::instantiateMemoryOperands(
+    const LLVMState &State, InstructionTemplate &IT, MCRegister Reg,
+    unsigned Offset, std::vector<MCInst> &Prologue,
+    const BitVector &ForbiddenRegisters) const {
+  const ExegesisTarget &ET = State.getExegesisTarget();
+  const Operand &MemOp = IT.getMemOpReg();
+  unsigned MemOpIndex = MemOp.getIndex();
+
+  const MCRegisterInfo &MRI = State.getRATC().regInfo();
+  const MCInstrDesc &Desc = IT.getInstr().Description;
+  const MCOperandInfo &OpInfo = Desc.operands()[MemOpIndex];
+  const MCRegisterClass &OpRC = MRI.getRegClass(OpInfo.RegClass);
+  unsigned OpBitSize = OpRC.getSizeInBits();
+
+  unsigned RegBitSize = 0;
+  for (unsigned RCId = 0, NumRC = MRI.getNumRegClasses(); RCId < NumRC;
+       ++RCId) {
+    const MCRegisterClass &RC = MRI.getRegClass(RCId);
+    if (RC.contains(Reg)) {
+      RegBitSize = RC.getSizeInBits();
+      break;
+    }
+  }
+
+  if (OpRC.contains(Reg) ||
+      (OpBitSize && RegBitSize && (OpBitSize != RegBitSize))) {
+    ET.fillMemoryOperands(IT, Reg, Offset);
+    return Error::success();
+  }
+
+  MCRegister NewReg;
+
+  ET.processInstructionReservedRegs(IT);
+  auto &MemOpValue = IT.getValueFor(MemOp);
+  if (MemOpValue.isValid() && MemOpValue.isReg()) {
+    NewReg = MemOpValue.getReg();
+  } else {
+    std::vector<MCPhysReg> Candidates;
+    for (MCPhysReg R : OpRC) {
+      if (!ForbiddenRegisters.test(R))
+        Candidates.push_back(R);
+    }
+
+    if (Candidates.empty())
+      return make_error<Failure>("No free register in required class");
+    NewReg = MCRegister::from(randomElement(Candidates));
+  }
+
+  auto Copy = ET.createCopyInstruction(Reg, NewReg);
+  if (!Copy)
+    return make_error<Failure>("Cannot copy into required register class");
+  Prologue.push_back(*Copy);
+  ET.fillMemoryOperands(IT, NewReg, Offset);
+  return Error::success();
+}
+
 static void setRegisterOperandValue(const RegisterOperandAssignment &ROV,
                                     InstructionTemplate &IB) {
   assert(ROV.Op);
diff --git a/llvm/tools/llvm-exegesis/lib/SnippetGenerator.h b/llvm/tools/llvm-exegesis/lib/SnippetGenerator.h
index 1ef0beb6d7c5a..7fd86f3e1bfb0 100644
--- a/llvm/tools/llvm-exegesis/lib/SnippetGenerator.h
+++ b/llvm/tools/llvm-exegesis/lib/SnippetGenerator.h
@@ -69,6 +69,12 @@ class SnippetGenerator {
   std::vector<RegisterValue> computeRegisterInitialValues(
       const std::vector<InstructionTemplate> &Snippet) const;
 
+  Error instantiateMemoryOperands(const LLVMState &State,
+                                  InstructionTemplate &IT, MCRegister Reg,
+                                  unsigned Offset,
+                                  std::vector<MCInst> &Prologue,
+                                  const BitVector &ForbiddenRegisters) const;
+
 protected:
   const LLVMState &State;
   const Options Opts;
diff --git a/llvm/tools/llvm-exegesis/lib/Target.h b/llvm/tools/llvm-exegesis/lib/Target.h
index 77fbaa6e95412..1f32de63bd3a3 100644
--- a/llvm/tools/llvm-exegesis/lib/Target.h
+++ b/llvm/tools/llvm-exegesis/lib/Target.h
@@ -207,6 +207,11 @@ class ExegesisTarget {
         "fillMemoryOperands() requires getScratchMemoryRegister() > 0");
   }
 
+  virtual std::optional<MCInst> createCopyInstruction(MCRegister Src,
+                                                      MCRegister Dst) const {
+    llvm_unreachable("");
+  }
+
   // Returns a counter usable as a loop counter.
   virtual MCRegister getDefaultLoopCounterRegister(const Triple &) const {
     return MCRegister();
@@ -244,6 +249,9 @@ class ExegesisTarget {
         "targets with target-specific operands should implement this");
   }
 
+  // Process instructions that used reserved registers.
+  virtual void processInstructionReservedRegs(InstructionTemplate &IT) const {}
+
   // Returns true if this instruction is supported as a back-to-back
   // instructions.
   // FIXME: Eventually we should discover this dynamically.



More information about the llvm-commits mailing list