[llvm-branch-commits] [llvm] [RegAllocFast] Fold foldable inline asm operands under register pressure (PR #229630)

via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Tue Oct 6 18:57:39 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-backend-x86

Author: Bill Wendling (isanbard)

<details>
<summary>Changes</summary>

An inline asm register operand marked foldable (from an "rm" constraint) may be replaced with a stack slot when the register allocator runs out of registers. The greedy allocator does that when it spills the value. The fast allocator can't: it assigns the operands of an instruction one at a time, and folding replaces the instruction. So it reported "inline assembly requires more registers than available" instead.

Before allocating a block, estimate from each inline asm's own operands whether they fit in registers, and fold as many foldable registers as needed to make them fit, so the common case without pressure still gets a register. Values that only live across the asm don't count, since the allocator spills them when it needs their registers. The estimate follows allocateInstruction():

- First all defs get distinct registers, avoiding physreg defs; then all uses, along with the defs still occupied while the uses are read (early-clobber and tied defs, see isLiveThroughDef()), get distinct registers, avoiding physreg uses and early-clobber physreg defs.
- A register counts against another register class as many times as one of its registers overlaps that class's registers, so e.g. 32-bit "rm" operands compete with 64-bit operands for the same registers.
- A value used by both an "rm" and an "r" operand isn't folded, since that frees no register.
- An input tied to a def keeps a register of its own when the allocator lowers the tie itself and the input lives on.

Folding a register folds all of its operands at once, with one store before the asm and one reload after it. It uses the register's own spill slot, so that a block that reloads the register, such as an INLINEASM_BR's indirect target, which the reload after the asm doesn't reach, finds the value the asm wrote.

The whole thing is skipped for functions without inline asm.

Nothing in tree marks an inline asm register operand foldable yet, so the tests set the "foldable" flag in MIR.

This follows the shape of the unmerged #<!-- -->74344, "[RegAllocFast] fold foldable inline asm", with a pressure estimate instead of folding unconditionally, per the review there.

Assisted-by: Claude Opus 5.5

---

Patch is 44.95 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/229630.diff


3 Files Affected:

- (modified) llvm/lib/CodeGen/RegAllocFast.cpp (+280) 
- (added) llvm/test/CodeGen/X86/regallocfast-inline-asm-fold-pressure.mir (+479) 
- (added) llvm/test/CodeGen/X86/regallocfast-inline-asm-fold.mir (+58) 


``````````diff
diff --git a/llvm/lib/CodeGen/RegAllocFast.cpp b/llvm/lib/CodeGen/RegAllocFast.cpp
index ff5548a17b2de..f083c829fd0b6 100644
--- a/llvm/lib/CodeGen/RegAllocFast.cpp
+++ b/llvm/lib/CodeGen/RegAllocFast.cpp
@@ -20,6 +20,10 @@
 /// Where the target enables it, TwoAddressInstructionPass is left out of the
 /// pipeline: this pass lowers tied operands and expands REG_SEQUENCE and
 /// INSERT_SUBREG itself.
+///
+/// An inline asm register operand that may be folded to memory (from an "rm"
+/// constraint) is folded to a stack slot before its block is allocated, if
+/// the asm's register operands wouldn't fit in registers otherwise.
 //
 //===----------------------------------------------------------------------===//
 
@@ -64,6 +68,8 @@ using namespace llvm;
 STATISTIC(NumStores, "Number of stores added");
 STATISTIC(NumLoads, "Number of loads added");
 STATISTIC(NumCoalesced, "Number of copies coalesced");
+STATISTIC(NumInlineAsmFolds,
+          "Number of inline asm registers folded to stack slots");
 
 static RegisterRegAlloc fastRegAlloc("fast", "fast register allocator",
                                      createFastRegisterAllocator);
@@ -411,6 +417,11 @@ class RegAllocFastImpl {
 
   bool mayBeSpillFromInlineAsmBr(const MachineInstr &MI) const;
 
+  void foldInlineAsmOperands(MachineBasicBlock &MBB);
+  void selectInlineAsmRegsToFold(const MachineInstr &MI,
+                                 SmallVectorImpl<Register> &ToFold) const;
+  void foldInlineAsmReg(MachineInstr *&MI, Register Reg);
+
   void dumpState() const;
 };
 
@@ -1928,6 +1939,270 @@ void RegAllocFastImpl::handleBundle(MachineInstr &MI) {
   }
 }
 
+/// Choose which foldable ("rm") registers of the inline asm \p MI to fold to
+/// stack slots so that the rest of its register operands fit, and add them to
+/// \p ToFold.
+///
+/// The greedy allocator folds such an operand when it runs out of registers.
+/// This allocator can't: it assigns the operands of an instruction one at a
+/// time, and folding replaces the instruction. So estimate up front, from the
+/// asm's own operands, whether they fit. Values that only live across the asm
+/// don't count, since this allocator spills them when it needs their
+/// registers. The estimate follows allocateInstruction(): first all defs get
+/// distinct registers, avoiding physreg defs; then all uses, along with the
+/// defs still occupied while the uses are read (early-clobber and tied defs),
+/// get distinct registers, avoiding physreg uses and early-clobber physreg
+/// defs. Folding a register removes it from both.
+///
+/// A register counts against another register class as many times as one of
+/// its registers overlaps registers of that class, e.g. once for GR64 against
+/// GR32. That, and keeping physreg uses out of the defs' registers, errs
+/// toward folding more than necessary rather than running out of registers.
+void RegAllocFastImpl::selectInlineAsmRegsToFold(
+    const MachineInstr &MI, SmallVectorImpl<Register> &ToFold) const {
+  // The virtual registers of MI's register operands.
+  struct AsmReg {
+    Register Reg;
+    const TargetRegisterClass *RC;
+    // Needs a register while the defs are assigned.
+    bool InDefs = false;
+    // Needs a register while the uses are read.
+    bool InUses = false;
+    // Inputs tied to this def that keep registers of their own, to be copied
+    // from (see lowerTiedUse()). Folding the def folds them too.
+    unsigned TiedInputs = 0;
+    // Read by an operand that isn't tied to its def.
+    bool HasUntiedUse = false;
+    // Every untied operand of the register can be folded.
+    bool Foldable = true;
+    bool Folded = false;
+  };
+  SmallVector<AsmReg, 8> Regs;
+  auto getAsmReg = [&](Register Reg) -> AsmReg & {
+    for (AsmReg &R : Regs)
+      if (R.Reg == Reg)
+        return R;
+
+    return Regs.emplace_back(AsmReg{Reg, MRI->getRegClass(Reg)});
+  };
+
+  // Physical registers taken while the defs, and while the uses, are assigned.
+  SmallVector<MCRegister, 8> DefTaken;
+  SmallVector<MCRegister, 8> UseTaken;
+
+  for (unsigned I = InlineAsm::MIOp_FirstOperand, E = MI.getNumOperands();
+       I != E; ++I) {
+    const MachineOperand &MO = MI.getOperand(I);
+    if (!MO.isReg() || !MO.getReg() || (MO.isUse() && MO.isUndef()))
+      continue;
+
+    Register Reg = MO.getReg();
+    if (Reg.isPhysical()) {
+      DefTaken.push_back(Reg);
+      if (MO.isUse() || isLiveThroughDef(MI, MO))
+        UseTaken.push_back(Reg);
+
+      continue;
+    }
+
+    if (!shouldAllocateRegister(Reg))
+      continue;
+
+    // A tied use is read through its def's register. Unless that is its own
+    // register (once tied operands have been rewritten), it can also need a
+    // register of its own to copy from: if it doesn't die here, and whenever
+    // the def is a fixed register, which might not be allocatable.
+    if (MO.isUse() && MO.isTied()) {
+      Register DefReg = MI.getOperand(MI.findTiedOperandIdx(I)).getReg();
+      if (Reg == DefReg)
+        continue;
+
+      if (DefReg.isVirtual()) {
+        if (!MO.isKill())
+          ++getAsmReg(DefReg).TiedInputs;
+      } else {
+        AsmReg &R = getAsmReg(Reg);
+        R.InUses = true;
+        R.Foldable = false;
+      }
+
+      continue;
+    }
+
+    AsmReg &R = getAsmReg(Reg);
+    R.Foldable &= MI.mayFoldInlineAsmRegOp(I);
+    if (MO.isUse()) {
+      R.InUses = true;
+      R.HasUntiedUse = true;
+    } else {
+      R.InDefs = true;
+      R.InUses |= isLiveThroughDef(MI, MO);
+    }
+  }
+
+  // The asm's own read of a register it also writes can't share the def's
+  // stack slot unless that's how the two are tied, so only fold a register
+  // the asm either reads or writes.
+  for (AsmReg &R : Regs)
+    R.Foldable &= !(R.InDefs && R.HasUntiedUse);
+
+  // Fold within one register class at a time, in operand order. Registers
+  // folded for one class no longer count against the next.
+  SmallVector<const TargetRegisterClass *, 4> Done;
+  for (const AsmReg &Candidate : Regs) {
+    const TargetRegisterClass *RC = Candidate.RC;
+    if (!Candidate.Foldable || is_contained(Done, RC))
+      continue;
+
+    Done.push_back(RC);
+
+    ArrayRef<MCPhysReg> Order = RegClassInfo.getOrder(RC);
+    auto countFree = [&](ArrayRef<MCRegister> Taken) -> unsigned {
+      return count_if(Order, [&](MCPhysReg PhysReg) {
+        return none_of(
+            Taken, [&](MCRegister T) { return TRI->regsOverlap(PhysReg, T); });
+      });
+    };
+    unsigned DefsFree = countFree(DefTaken);
+    unsigned UsesFree = countFree(UseTaken);
+
+    // How many registers of RC one register of each AsmReg can take.
+    SmallVector<unsigned, 8> Weight;
+    for (const AsmReg &R : Regs) {
+      if (RC->hasSubClassEq(R.RC) || R.RC->hasSubClassEq(RC)) {
+        Weight.push_back(1);
+        continue;
+      }
+
+      unsigned Max = 0;
+      for (MCPhysReg PhysReg : RegClassInfo.getOrder(R.RC))
+        Max = std::max<unsigned>(Max, count_if(Order, [&](MCPhysReg Other) {
+                                   return TRI->regsOverlap(PhysReg, Other);
+                                 }));
+
+      Weight.push_back(Max);
+    }
+
+    while (true) {
+      unsigned DefsNeeded = 0;
+      unsigned UsesNeeded = 0;
+      for (auto [R, W] : zip(Regs, Weight)) {
+        if (R.Folded)
+          continue;
+
+        DefsNeeded += R.InDefs * W;
+        UsesNeeded += (R.InUses + R.TiedInputs) * W;
+      }
+
+      bool DefsShort = DefsNeeded > DefsFree;
+      bool UsesShort = UsesNeeded > UsesFree;
+      if (!DefsShort && !UsesShort)
+        break;
+
+      // Fold the first register that relieves the most phases that are short.
+      AsmReg *Best = nullptr;
+      unsigned BestRelief = 0;
+      for (auto [R, W] : zip(Regs, Weight)) {
+        if (R.Folded || !R.Foldable || !W)
+          continue;
+
+        unsigned Relief =
+            (DefsShort && R.InDefs) + (UsesShort && (R.InUses || R.TiedInputs));
+        if (Relief > BestRelief) {
+          Best = &R;
+          BestRelief = Relief;
+        }
+      }
+
+      // Without anything left to fold, allocation reports the error.
+      if (!Best)
+        break;
+
+      Best->Folded = true;
+      ToFold.push_back(Best->Reg);
+    }
+  }
+}
+
+/// Fold the operands of \p Reg in the inline asm \p MI to Reg's stack slot,
+/// storing the value the asm reads there before it, and reloading Reg from it
+/// afterward if the asm writes Reg. \p MI is replaced by the new instruction.
+void RegAllocFastImpl::foldInlineAsmReg(MachineInstr *&MI, Register Reg) {
+  // Fold every operand of Reg. A tied use goes along with its def, and reads
+  // the def's slot, which therefore needs the tied input's value, whichever
+  // register that is.
+  SmallVector<unsigned, 2> Ops;
+  Register ReadReg;
+  bool Writes = false;
+  for (unsigned I = InlineAsm::MIOp_FirstOperand, E = MI->getNumOperands();
+       I != E; ++I) {
+    const MachineOperand &MO = MI->getOperand(I);
+    if (!MO.isReg() || MO.getReg() != Reg)
+      continue;
+
+    if (MO.isUse()) {
+      if (!MO.isTied())
+        Ops.push_back(I);
+
+      if (MO.readsReg())
+        ReadReg = Reg;
+
+      continue;
+    }
+
+    Ops.push_back(I);
+    Writes |= !MO.isDead();
+    if (MO.isTied()) {
+      const MachineOperand &Use = MI->getOperand(MI->findTiedOperandIdx(I));
+      if (Use.readsReg())
+        ReadReg = Use.getReg();
+    }
+  }
+
+  // Use Reg's own spill slot: a block that reloads Reg, such as an
+  // INLINEASM_BR's indirect target, which the reload after the asm doesn't
+  // reach, then finds the value the asm wrote.
+  MachineBasicBlock &MBB = *MI->getParent();
+  const TargetRegisterClass *RC = MRI->getRegClass(Reg);
+  int FI = getStackSpaceFor(Reg);
+  MachineInstr *CopyMI = nullptr;
+  MachineInstr *NewMI = TII->foldMemoryOperand(*MI, Ops, FI, CopyMI);
+  assert(NewMI && !CopyMI && "inline asm register should fold");
+
+  // NewMI is inserted before MI. The allocator works out the kill flags.
+  if (ReadReg) {
+    TII->storeRegToStackSlot(MBB, NewMI->getIterator(), ReadReg,
+                             /*isKill=*/false, FI, RC, ReadReg);
+    ++NumStores;
+  }
+
+  if (Writes) {
+    TII->loadRegFromStackSlot(MBB, std::next(NewMI->getIterator()), Reg, FI, RC,
+                              Reg);
+    ++NumLoads;
+  }
+
+  ++NumInlineAsmFolds;
+
+  MI->eraseFromParent();
+  MI = NewMI;
+}
+
+/// Fold the operands of each inline asm in \p MBB that won't fit in registers
+/// (see selectInlineAsmRegsToFold()).
+void RegAllocFastImpl::foldInlineAsmOperands(MachineBasicBlock &MBB) {
+  for (MachineInstr &MI : make_early_inc_range(MBB)) {
+    if (!MI.isInlineAsm())
+      continue;
+
+    SmallVector<Register, 4> ToFold;
+    selectInlineAsmRegsToFold(MI, ToFold);
+    MachineInstr *AsmMI = &MI;
+    for (Register Reg : ToFold)
+      foldInlineAsmReg(AsmMI, Reg);
+  }
+}
+
 void RegAllocFastImpl::allocateBasicBlock(MachineBasicBlock &MBB) {
   this->MBB = &MBB;
   LLVM_DEBUG(dbgs() << "\nAllocating " << MBB);
@@ -1941,6 +2216,11 @@ void RegAllocFastImpl::allocateBasicBlock(MachineBasicBlock &MBB) {
 
   Coalesced.clear();
 
+  // Folding replaces an inline asm, which allocateInstruction() can't do
+  // partway through, so fold the operands that won't fit in registers first.
+  if (MBB.getParent()->hasInlineAsm())
+    foldInlineAsmOperands(MBB);
+
   // Lowering a tied operand inserts a copy ahead of MI. Its registers are
   // already assigned, so visiting it would evict what still lives in the
   // source.
diff --git a/llvm/test/CodeGen/X86/regallocfast-inline-asm-fold-pressure.mir b/llvm/test/CodeGen/X86/regallocfast-inline-asm-fold-pressure.mir
new file mode 100644
index 0000000000000..87f7239f56bdb
--- /dev/null
+++ b/llvm/test/CodeGen/X86/regallocfast-inline-asm-fold-pressure.mir
@@ -0,0 +1,479 @@
+# NOTE: Assertions have been autogenerated by utils/update_mir_test_checks.py UTC_ARGS: --version 6
+# RUN: llc -mtriple=x86_64-unknown-linux-gnu -run-pass=regallocfast \
+# RUN:   -verify-machineinstrs -verify-regalloc %s -o - | FileCheck %s
+
+# The fast register allocator folds foldable ("rm") inline asm operands to
+# stack slots when the asm's register operands wouldn't fit in registers
+# otherwise, and only then.
+
+---
+# The asm clobbers rax/rbx/rbp/r10-r15, leaving six of the 15 GPRs for its
+# register operands, and its six "r" inputs take all of them. The "=&rm"
+# output can't share a register with an input, so it's folded to a stack slot.
+name:            test_rm_output_pressure
+tracksRegLiveness: true
+registers:
+  - { id: 0, class: gr64 }
+  - { id: 1, class: gr64 }
+  - { id: 2, class: gr64 }
+  - { id: 3, class: gr64 }
+  - { id: 4, class: gr64 }
+  - { id: 5, class: gr64 }
+  - { id: 6, class: gr64_norex2 }
+  - { id: 7, class: gr64_norex2 }
+  - { id: 8, class: gr64_norex2 }
+  - { id: 9, class: gr64_norex2 }
+  - { id: 10, class: gr64_norex2 }
+  - { id: 11, class: gr64_norex2 }
+  - { id: 12, class: gr64_norex2 }
+body:             |
+  bb.0:
+    liveins: $rdi, $rsi, $rdx, $rcx, $r8, $r9
+
+    ; CHECK-LABEL: name: test_rm_output_pressure
+    ; CHECK: liveins: $rdi, $rsi, $rdx, $rcx, $r8, $r9
+    ; CHECK-NEXT: {{  $}}
+    ; CHECK-NEXT: INLINEASM &"# $0 $1 $2 $3 $4 $5 $6", sideeffect maystore attdialect, mem:m, %stack.0, 1, $noreg, 0, $noreg, reguse:GR64_NOREX2, killed renamable $rdi, reguse:GR64_NOREX2, killed renamable $rsi, reguse:GR64_NOREX2, killed renamable $rdx, reguse:GR64_NOREX2, killed renamable $rcx, reguse:GR64_NOREX2, killed renamable $r8, reguse:GR64_NOREX2, killed renamable $r9, clobber, implicit-def dead early-clobber $rax, clobber, implicit-def dead early-clobber $rbx, clobber, implicit-def dead early-clobber $rbp, clobber, implicit-def dead early-clobber $r10, clobber, implicit-def dead early-clobber $r11, clobber, implicit-def dead early-clobber $r12, clobber, implicit-def dead early-clobber $r13, clobber, implicit-def dead early-clobber $r14, clobber, implicit-def dead early-clobber $r15 :: (store (s64) into %stack.0)
+    ; CHECK-NEXT: renamable $rax = MOV64rm %stack.0, 1, $noreg, 0, $noreg :: (load (s64) from %stack.0)
+    ; CHECK-NEXT: RET 0, killed $rax
+    %5:gr64 = COPY $r9
+    %4:gr64 = COPY $r8
+    %3:gr64 = COPY $rcx
+    %2:gr64 = COPY $rdx
+    %1:gr64 = COPY $rsi
+    %0:gr64 = COPY $rdi
+    %7:gr64_norex2 = COPY %0
+    %8:gr64_norex2 = COPY %1
+    %9:gr64_norex2 = COPY %2
+    %10:gr64_norex2 = COPY %3
+    %11:gr64_norex2 = COPY %4
+    %12:gr64_norex2 = COPY %5
+    INLINEASM &"# $0 $1 $2 $3 $4 $5 $6", sideeffect attdialect, regdef-ec:GR64_NOREX2 foldable, def early-clobber %6, reguse:GR64_NOREX2, %7, reguse:GR64_NOREX2, %8, reguse:GR64_NOREX2, %9, reguse:GR64_NOREX2, %10, reguse:GR64_NOREX2, %11, reguse:GR64_NOREX2, %12, clobber, implicit-def dead early-clobber $rax, clobber, implicit-def dead early-clobber $rbx, clobber, implicit-def dead early-clobber $rbp, clobber, implicit-def dead early-clobber $r10, clobber, implicit-def dead early-clobber $r11, clobber, implicit-def dead early-clobber $r12, clobber, implicit-def dead early-clobber $r13, clobber, implicit-def dead early-clobber $r14, clobber, implicit-def dead early-clobber $r15
+    $rax = COPY %6
+    RET 0, $rax
+...
+---
+# One value passed to two "rm" operands: spilling it means folding both
+# operands into its stack slot at once.
+name:            test_rm_same_value_twice_pressure
+tracksRegLiveness: true
+registers:
+  - { id: 0, class: gr64 }
+  - { id: 1, class: gr64 }
+  - { id: 2, class: gr64 }
+  - { id: 3, class: gr64 }
+  - { id: 4, class: gr64 }
+  - { id: 5, class: gr64 }
+  - { id: 6, class: gr64_norex2 }
+  - { id: 7, class: gr64_norex2 }
+  - { id: 8, class: gr64_norex2 }
+  - { id: 9, class: gr64_norex2 }
+  - { id: 10, class: gr64_norex2 }
+  - { id: 11, class: gr64_norex2 }
+  - { id: 12, class: gr64_norex2 }
+  - { id: 13, class: gr64_norex2 }
+  - { id: 14, class: gr64 }
+fixedStack:
+  - { id: 0, size: 8, alignment: 16, isImmutable: true }
+body:             |
+  bb.0:
+    liveins: $rdi, $rsi, $rdx, $rcx, $r8, $r9
+
+    ; CHECK-LABEL: name: test_rm_same_value_twice_pressure
+    ; CHECK: liveins: $rdi, $rsi, $rdx, $rcx, $r8, $r9
+    ; CHECK-NEXT: {{  $}}
+    ; CHECK-NEXT: renamable $rax = COPY killed $rdi
+    ; CHECK-NEXT: renamable $rdi = MOV64rm %fixed-stack.0, 1, $noreg, 0, $noreg :: (load (s64) from %fixed-stack.0, align 16)
+    ; CHECK-NEXT: MOV64mr %stack.0, 1, $noreg, 0, $noreg, killed renamable $rax :: (store (s64) into %stack.0)
+    ; CHECK-NEXT: INLINEASM &"# $0 $1 $2 $3 $4 $5 $6 $7", sideeffect mayload attdialect, mem:m, %stack.0, 1, $noreg, 0, $noreg, mem:m, %stack.0, 1, $noreg, 0, $noreg, reguse:GR64_NOREX2, killed renamable $rsi, reguse:GR64_NOREX2, killed renamable $rdx, reguse:GR64_NOREX2, killed renamable $rcx, reguse:GR64_NOREX2, killed renamable $r8, reguse:GR64_NOREX2, killed renamable $r9, reguse:GR64_NOREX2, killed renamable $rdi, clobber, implicit-def dead early-clobber $rax, clobber, implicit-def dead early-clobber $rbx, clobber, implicit-def dead early-clobber $rbp, clobber, implicit-def dead early-clobber $r10, clobber, implicit-def dead early-clobber $r11, clobber, implicit-def dead early-clobber $r12, clobber, implicit-def dead early-clobber $r13, clobber, implicit-def dead early-clobber $r14, clobber, implicit-def dead early-clobber $r15 :: (load (s64) from %stack.0)
+    ; CHECK-NEXT: RET 0
+    %5:gr64 = COPY $r9
+    %4:gr64 = COPY $r8
+    %3:gr64 = COPY $rcx
+    %2:gr64 = COPY $rdx
+    %1:gr64 = COPY $rsi
+    %0:gr64 = COPY $rdi
+    %14:gr64 = MOV64rm %fixed-stack.0, 1, $noreg, 0, $noreg :: (load (s64) from %fixed-stack.0, align 16)
+    %6:gr64_norex2 = COPY %0
+    %8:gr64_norex2 = COPY %1
+    %9:gr64_norex2 = COPY %2
+    %10:gr64_norex2 = COPY %3
+    %11:gr64_norex2 = COPY %4
+    %12:gr64_norex2 = COPY %5
+    %13:gr64_norex2 = COPY %14
+    INLINEASM &"# $0 $1 $2 $3 $4 $5 $6 $7", sideeffect attdialect, reguse:GR64_NOREX2 foldable, %6, reguse:GR64_NOREX2 foldable, %6, reguse:GR64_NOREX2, %8, reguse:GR64_NOREX2, %9, reguse:GR64_NOREX2, %10, reguse:GR64_NOREX2, %11, reguse:GR64_NOREX2, %12, reguse:GR64_NOREX2, %13, clobber, implicit-def dead early-clobber $rax, clobber, implicit-def dead early-clobber $rbx, clobber, implicit-def dead early-clobber $rbp, clobber, implicit-def dead early-clobber $r10, clobber, implicit-def dead early-clobber $r11, clobber, implicit-def dead early-clobber $r12, clobber, implicit-def dead early-clobber $r13, clobber, implicit-def dead early-clobber $r14, clobber, implicit-def dead early-clobber $r15
+    RET 0
+...
+---
+# Folding the "rm" input moves the operands after it, including the input
+# tied to the "=r" output, whose tie must survive.
+name:            test_rm_before_tied_pressure
+tracksRegLiveness: true
+registers:
+  - { id: 0, class: gr64 }
+  - { id: 1, class: gr64 }
+  - { id: 2, class: gr64 }
+  - { id: 3, class: gr64 }
+  - { id: 4, class: gr64 }
+  - { id: 5, class: gr64 }
+  - { id: 6, class: gr64_norex2 }
+  - { id: 7, class: gr64_norex2 }
+  - { id: 8, class: gr64_norex2 }
+  - { id: 9, class: gr64_norex2 }
+  - { id: 10, class: gr64_norex2 }
+  - { id: 11, class: gr64_norex2 }
+  - { id: 12, class: gr64_norex2 }
+  - { id: 13, class: gr64_norex2 }
+  - { id: 14, class: gr64 }
+fixedStack:
+  - { id: 0, size: 8, alignment: 16, isImmutable: true }
+body:             |
+  bb.0:
+    liveins: $rdi, $rsi, $rdx, $rcx, $r8, $r9
+
+    ; CHECK-LABEL: name: test_rm_before_tied_pressure
+    ; CHECK: liveins: $rdi, $rsi, $rdx, $rcx, $r8, $r9
+    ; CHECK-NEXT: {{  $}}
+    ; CHECK-NEXT: MOV64mr %stack.2, 1, $noreg, 0, $noreg, $rcx :: (store (s64) into %stack.2)
+    ; CHECK-NEXT: renamable $rax = COPY $rdi
+    ; CHECK-NEXT: $rdi = MOV64rm %stack.2, 1, $noreg, 0, $noreg :: (load (s64) from %stack.2)
+    ; CHECK-NEXT: renamable $rcx = MOV64rm %fixed-stack.0, 1, $noreg, 0, $noreg :: (load (s64) from %fixed-stack.0, align 16)
+    ; CHECK-NEXT: MOV64mr %stack.0, 1, $noreg, 0, $noreg, killed renamable $rax :: (store (s64) into %stack.0)
+    ; CHECK-NEXT: INLINEASM &"# $0 $1 $2 $3 $4 $5 $6 $7", sideeffect mayload attdialect, regdef:GR64_NOREX2, def renamable $rcx, mem:m, %stack.0, 1, $noreg, 0, $noreg, reguse:GR64_NOREX2, killed renamable $rsi, reguse:GR64_NOREX2, killed renamable $rdx, reguse:GR64_NOREX2, killed renamable $rdi, reguse:GR64_NOREX2, killed renamable $r8, reguse:GR64_NO...
[truncated]

``````````

</details>


https://github.com/llvm/llvm-project/pull/229630


More information about the llvm-branch-commits mailing list