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

Bill Wendling via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Thu Oct 8 00:31:37 PDT 2026


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

>From f6d851e4860891780af565f07bf6c2f3f5ee6ff1 Mon Sep 17 00:00:00 2001
From: Bill Wendling <isanbard at gmail.com>
Date: Tue, 6 Oct 2026 06:05:08 -0700
Subject: [PATCH 1/4] [MIR] Serialize the "foldable" inline asm register
 operand flag

The symbolic MIR syntax for inline asm operand flags dropped
InlineAsm::Flag's RegMayBeFolded bit, which marks a register operand
(from an "rm" constraint) that the register allocator may fold to a
stack slot. Printing MIR and parsing it back therefore silently changed
what the allocator was allowed to do, e.g. with -stop-before and
-run-pass, and a test could only set the bit through a raw numeric flag.

Print the bit as a trailing "foldable", as MachineInstr::print() already
does, and parse it back. A tied use stores its matched operand number in
the same bits, so it never prints the marker.

Assisted-by: Claude Opus 5.5
---
 llvm/lib/CodeGen/MIRParser/MIParser.cpp       | 26 ++++++++++++++-----
 llvm/lib/CodeGen/MIRPrinter.cpp               | 12 ++++++---
 .../Generic/inline-asm-foldable-not-reg.mir   | 11 ++++++++
 .../CodeGen/MIR/X86/inline-asm-foldable.mir   | 25 ++++++++++++++++++
 4 files changed, 65 insertions(+), 9 deletions(-)
 create mode 100644 llvm/test/CodeGen/MIR/Generic/inline-asm-foldable-not-reg.mir
 create mode 100644 llvm/test/CodeGen/MIR/X86/inline-asm-foldable.mir

diff --git a/llvm/lib/CodeGen/MIRParser/MIParser.cpp b/llvm/lib/CodeGen/MIRParser/MIParser.cpp
index 1a4bcbd0345b28..83282e12833a0d 100644
--- a/llvm/lib/CodeGen/MIRParser/MIParser.cpp
+++ b/llvm/lib/CodeGen/MIRParser/MIParser.cpp
@@ -1853,6 +1853,23 @@ bool MIParser::parseSymbolicInlineAsmOperand(unsigned OpIdx,
   // Create the flag with default of 1 operand.
   InlineAsm::Flag F(K, 1);
 
+  // Finish an untied operand, which may end in 'foldable': the register
+  // allocator may fold its register to a stack slot (see
+  // InlineAsm::Flag::setRegMayBeFolded()).
+  auto ParseOptionalFoldable = [&]() {
+    if (Token.is(MIToken::Identifier) && Token.stringValue() == "foldable") {
+      if (K != InlineAsm::Kind::RegDef && K != InlineAsm::Kind::RegUse &&
+          K != InlineAsm::Kind::RegDefEarlyClobber)
+        return error("only a register operand can be 'foldable'");
+
+      F.setRegMayBeFolded(true);
+      lex();
+    }
+
+    Dest = MachineOperand::CreateImm(F);
+    return false;
+  };
+
   // Parse optional tiedto constraint: tiedto:$N.
   if (Token.is(MIToken::Identifier) && Token.stringValue() == "tiedto") {
     lex();
@@ -1873,10 +1890,8 @@ bool MIParser::parseSymbolicInlineAsmOperand(unsigned OpIdx,
   }
 
   // Parse optional constraint after ':'.
-  if (Token.isNot(MIToken::colon)) {
-    Dest = MachineOperand::CreateImm(F);
-    return false;
-  }
+  if (Token.isNot(MIToken::colon))
+    return ParseOptionalFoldable();
 
   lex();
 
@@ -1930,8 +1945,7 @@ bool MIParser::parseSymbolicInlineAsmOperand(unsigned OpIdx,
 
   lex();
 
-  Dest = MachineOperand::CreateImm(F);
-  return false;
+  return ParseOptionalFoldable();
 }
 
 bool MIParser::parseTargetImmMnemonic(const unsigned OpCode,
diff --git a/llvm/lib/CodeGen/MIRPrinter.cpp b/llvm/lib/CodeGen/MIRPrinter.cpp
index 7ac0c40dfd93fb..865f37af7c1aa8 100644
--- a/llvm/lib/CodeGen/MIRPrinter.cpp
+++ b/llvm/lib/CodeGen/MIRPrinter.cpp
@@ -1010,10 +1010,10 @@ static void printMIOperand(raw_ostream &OS, MFPrintState &State,
         InlineAsm::Flag F(Op.getImm());
         OS << F.getKindName();
 
+        bool IsReg = F.isRegDefKind() || F.isRegUseKind() ||
+                     F.isRegDefEarlyClobberKind();
         unsigned RCID;
-        if ((F.isRegDefKind() || F.isRegUseKind() ||
-             F.isRegDefEarlyClobberKind()) &&
-            F.hasRegClassConstraint(RCID))
+        if (IsReg && F.hasRegClassConstraint(RCID))
           OS << ':' << TRI->getRegClassName(TRI->getRegClass(RCID));
 
         if (F.isMemKind()) {
@@ -1021,9 +1021,15 @@ static void printMIOperand(raw_ostream &OS, MFPrintState &State,
           OS << ':' << InlineAsm::getMemConstraintName(MCID);
         }
 
+        // A tied use stores its matched operand number in the bits that
+        // otherwise hold RegMayBeFolded, so only an untied register operand
+        // can be foldable.
         unsigned TiedTo;
         if (F.isUseOperandTiedToDef(TiedTo))
           OS << " tiedto:$" << TiedTo;
+        else if (IsReg && F.getRegMayBeFolded())
+          OS << " foldable";
+
         break;
       }
     }
diff --git a/llvm/test/CodeGen/MIR/Generic/inline-asm-foldable-not-reg.mir b/llvm/test/CodeGen/MIR/Generic/inline-asm-foldable-not-reg.mir
new file mode 100644
index 00000000000000..03727369f4d0e2
--- /dev/null
+++ b/llvm/test/CodeGen/MIR/Generic/inline-asm-foldable-not-reg.mir
@@ -0,0 +1,11 @@
+# RUN: not llc -run-pass=none -filetype=null %s 2>&1 | FileCheck %s
+
+# Test parsing error for a 'foldable' marker on a non-register operand.
+
+# CHECK: [[@LINE+5]]:{{[0-9]+}}: only a register operand can be 'foldable'
+---
+name: foo
+body: |
+  bb.0:
+    INLINEASM &"", attdialect, mem:m foldable
+...
diff --git a/llvm/test/CodeGen/MIR/X86/inline-asm-foldable.mir b/llvm/test/CodeGen/MIR/X86/inline-asm-foldable.mir
new file mode 100644
index 00000000000000..d56293aa355359
--- /dev/null
+++ b/llvm/test/CodeGen/MIR/X86/inline-asm-foldable.mir
@@ -0,0 +1,25 @@
+# RUN: llc -mtriple=x86_64 -run-pass=none -o - %s | FileCheck %s
+# RUN: llc -mtriple=x86_64 -run-pass=none -o - %s \
+# RUN:   | llc -mtriple=x86_64 -run-pass=none -x mir -o - | FileCheck %s
+
+# A register operand marked foldable (from an "rm" constraint) may be folded
+# to a stack slot by the register allocator. Check that the marker survives a
+# print/parse round trip, whether it was written symbolically or as part of a
+# numeric flag, and that a tied use, whose matched operand number shares the
+# marker's bits, never prints it.
+
+---
+name: foldable
+tracksRegLiveness: true
+body: |
+  bb.0:
+    liveins: $rdi, $rsi, $edx
+
+    ; CHECK-LABEL: name: foldable
+    ; CHECK: INLINEASM &"", attdialect, regdef:GR64 foldable, def %3, regdef-ec:GR64 foldable, def early-clobber %4, regdef:GR64, def %5, reguse:GR64 foldable, %0, reguse:GR32 foldable, %2, reguse tiedto:$0, %1(tied-def 3)
+    %0:gr64 = COPY $rdi
+    %1:gr64 = COPY $rsi
+    %2:gr32 = COPY $edx
+    INLINEASM &"", attdialect, regdef:GR64 foldable, def %3:gr64, regdef-ec:GR64 foldable, def early-clobber %4:gr64, regdef:GR64, def %5:gr64, reguse:GR64 foldable, %0, 1076101129 /* reguse:GR32 foldable */, %2, reguse tiedto:$0, %1(tied-def 3)
+    RET64
+...

>From 5cce908d9d18e61ece00c397a50e80b198861f46 Mon Sep 17 00:00:00 2001
From: Bill Wendling <isanbard at gmail.com>
Date: Thu, 8 Oct 2026 00:28:24 -0700
Subject: [PATCH 2/4] [TargetInstrInfo] Fix folding inline asm operands next to
 tied operands

foldInlineAsmMemOperand() swapped a register operand for the target's
memory operands with MachineInstr::removeOperand(), which asserts when a
later operand is tied, because moving it would break the tie. Inline asm
lists every input after every output, so folding any operand that comes
before another tied pair asserted, e.g. an "rm" input followed by the
input of a "+r" operand, or one of two "+rm" operands. Without
assertions, the moved operands kept stale tie indices. Untie the
operands, rebuild the operand list, and re-tie the remaining pairs at
their new positions.

It also gave up when the register appears in more than one operand, e.g.
one value passed to two "rm" operands, which left the greedy allocator
unable to spill that value at all. Fold every such operand into the
stack slot.

Finally, take MayLoad from the folded operands rather than from every
read of the register: a folded def whose tied use is another virtual
register (as when the fast register allocator lowers tied operands
itself) still reads the slot, and an unrelated register read of the
same value doesn't.

Nothing in tree marks an inline asm register operand foldable yet, so
the test sets the "foldable" flag in MIR and runs the greedy allocator.

Assisted-by: Claude Opus 5.5
Co-authored-by: Nick Desaulniers <ndesaulniers at google.com>
---
 llvm/lib/CodeGen/TargetInstrInfo.cpp          | 117 +++++++++++++-----
 .../CodeGen/X86/greedy-inline-asm-fold.mir    | 108 ++++++++++++++++
 2 files changed, 193 insertions(+), 32 deletions(-)
 create mode 100644 llvm/test/CodeGen/X86/greedy-inline-asm-fold.mir

diff --git a/llvm/lib/CodeGen/TargetInstrInfo.cpp b/llvm/lib/CodeGen/TargetInstrInfo.cpp
index e5bb73dc4b5811..47e46045756724 100644
--- a/llvm/lib/CodeGen/TargetInstrInfo.cpp
+++ b/llvm/lib/CodeGen/TargetInstrInfo.cpp
@@ -650,29 +650,66 @@ static MachineInstr *foldPatchpoint(MachineFunction &MF, MachineInstr &MI,
   return NewMI;
 }
 
-static void foldInlineAsmMemOperand(MachineInstr *MI, unsigned OpNo, int FI,
-                                    const TargetInstrInfo &TII) {
-  // If the machine operand is tied, untie it first.
-  if (MI->getOperand(OpNo).isTied()) {
-    unsigned TiedTo = MI->findTiedOperandIdx(OpNo);
-    MI->untieRegOperand(OpNo);
-    // Intentional recursion!
-    foldInlineAsmMemOperand(MI, TiedTo, FI, TII);
+/// Rewrite the register operands \p Ops of the inline asm \p MI to refer to
+/// stack slot \p FI. The use tied to a folded def is folded along with it,
+/// since the two name a single location.
+static void foldInlineAsmMemOperands(MachineInstr &MI, ArrayRef<unsigned> Ops,
+                                     int FI, const TargetInstrInfo &TII) {
+  SmallVector<MachineOperand, 5> MemOps;
+  TII.getFrameIndexOperands(MemOps, FI);
+  assert(!MemOps.empty() && "getFrameIndexOperands didn't create any operands");
+  InlineAsm::Flag MemFlag(InlineAsm::Kind::Mem, MemOps.size());
+  MemFlag.setMemConstraint(InlineAsm::ConstraintCode::m);
+
+  // Find the uses to fold along with their defs, and the ties to keep. A
+  // folded operand becomes MemOps.size() operands, moving every later one, and
+  // MachineInstr can't move a tied operand. So untie everything (untying a use
+  // also unties its def), re-add the operands from the first folded one on,
+  // and re-tie the pairs that are still registers at their new positions.
+  SmallVector<unsigned, 4> FoldOps(Ops);
+  SmallVector<std::pair<unsigned, unsigned>, 4> Ties;
+  for (unsigned I = InlineAsm::MIOp_FirstOperand, E = MI.getNumOperands();
+       I != E; ++I) {
+    unsigned DefIdx;
+    if (!MI.isRegTiedToDefOperand(I, &DefIdx))
+      continue;
+
+    MI.untieRegOperand(I);
+    if (is_contained(Ops, DefIdx))
+      FoldOps.push_back(I);
+    else
+      Ties.emplace_back(DefIdx, I);
   }
 
-  SmallVector<MachineOperand, 5> NewOps;
-  TII.getFrameIndexOperands(NewOps, FI);
-  assert(!NewOps.empty() && "getFrameIndexOperands didn't create any operands");
-  MI->removeOperand(OpNo);
-  MI->insert(MI->operands_begin() + OpNo, NewOps);
+  unsigned First = *llvm::min_element(FoldOps);
+  SmallVector<MachineOperand, 16> Tail(MI.operands_begin() + First,
+                                       MI.operands_end());
+  while (MI.getNumOperands() > First)
+    MI.removeOperand(MI.getNumOperands() - 1);
+
+  SmallVector<unsigned, 16> NewIdx;
+  for (auto [I, MO] : enumerate(Tail)) {
+    NewIdx.push_back(MI.getNumOperands());
+    if (!is_contained(FoldOps, First + I)) {
+      MI.addOperand(MO);
+      continue;
+    }
+
+    // Only the first register after a flag can be folded, so the operand just
+    // before this one is its flag. It now describes the memory operand.
+    MachineOperand &FlagMO = MI.getOperand(MI.getNumOperands() - 1);
+    assert(InlineAsm::Flag(FlagMO.getImm()).getNumOperandRegisters() == 1 &&
+           "cannot fold one register of a multi-register operand");
+    FlagMO.setImm(MemFlag);
+    for (const MachineOperand &MemOp : MemOps)
+      MI.addOperand(MemOp);
+  }
 
-  // Change the previous operand to a MemKind InlineAsm::Flag. The second param
-  // is the per-target number of operands that represent the memory operand
-  // excluding this one (MD). This includes MO.
-  InlineAsm::Flag F(InlineAsm::Kind::Mem, NewOps.size());
-  F.setMemConstraint(InlineAsm::ConstraintCode::m);
-  MachineOperand &MD = MI->getOperand(OpNo - 1);
-  MD.setImm(F);
+  auto Remap = [&](unsigned Idx) {
+    return Idx < First ? Idx : NewIdx[Idx - First];
+  };
+  for (auto [DefIdx, UseIdx] : Ties)
+    MI.tieOperands(Remap(DefIdx), Remap(UseIdx));
 }
 
 // Returns nullptr if not possible to fold.
@@ -680,29 +717,45 @@ static MachineInstr *foldInlineAsmMemOperand(MachineInstr &MI,
                                              ArrayRef<unsigned> Ops, int FI,
                                              const TargetInstrInfo &TII) {
   assert(MI.isInlineAsm() && "wrong opcode");
-  if (Ops.size() > 1)
-    return nullptr;
-  unsigned Op = Ops[0];
-  assert(Op && "should never be first operand");
-  assert(MI.getOperand(Op).isReg() && "shouldn't be folding non-reg operands");
 
-  if (!MI.mayFoldInlineAsmRegOp(Op))
+  // Sanity check that there are operands to fold.
+  if (Ops.empty())
     return nullptr;
 
-  MachineInstr &NewMI = TII.duplicate(*MI.getParent(), MI.getIterator(), MI);
+  // Every operand in Ops holds the same register (e.g. one value passed to two
+  // "rm" operands), so they all fold to the same stack slot. The asm reads
+  // the slot through a use, and through a def tied to a use, which need not
+  // be the same virtual register when the allocator lowers ties itself.
+  bool Reads = false;
+  bool Writes = false;
+  for (unsigned Op : Ops) {
+    assert(Op && "should never be first operand");
+    const MachineOperand &MO = MI.getOperand(Op);
+    assert(MO.isReg() && "shouldn't be folding non-reg operands");
+
+    // A tied use is only ever folded along with its def.
+    if (MI.isRegTiedToDefOperand(Op) || !MI.mayFoldInlineAsmRegOp(Op))
+      return nullptr;
 
-  foldInlineAsmMemOperand(&NewMI, Op, FI, TII);
+    Reads |= MO.readsReg();
+    if (MO.isDef()) {
+      Writes = true;
+      Reads |=
+          MO.isTied() && MI.getOperand(MI.findTiedOperandIdx(Op)).readsReg();
+    }
+  }
+
+  MachineInstr &NewMI = TII.duplicate(*MI.getParent(), MI.getIterator(), MI);
+  foldInlineAsmMemOperands(NewMI, Ops, FI, TII);
 
   // Update mayload/maystore metadata, and memoperands.
-  const VirtRegInfo &RI =
-      AnalyzeVirtRegInBundle(MI, MI.getOperand(Op).getReg());
   MachineOperand &ExtraMO = NewMI.getOperand(InlineAsm::MIOp_ExtraInfo);
   MachineMemOperand::Flags Flags = MachineMemOperand::MONone;
-  if (RI.Reads) {
+  if (Reads) {
     ExtraMO.setImm(ExtraMO.getImm() | InlineAsm::Extra_MayLoad);
     Flags |= MachineMemOperand::MOLoad;
   }
-  if (RI.Writes) {
+  if (Writes) {
     ExtraMO.setImm(ExtraMO.getImm() | InlineAsm::Extra_MayStore);
     Flags |= MachineMemOperand::MOStore;
   }
diff --git a/llvm/test/CodeGen/X86/greedy-inline-asm-fold.mir b/llvm/test/CodeGen/X86/greedy-inline-asm-fold.mir
new file mode 100644
index 00000000000000..f7e1c695ebfc48
--- /dev/null
+++ b/llvm/test/CodeGen/X86/greedy-inline-asm-fold.mir
@@ -0,0 +1,108 @@
+# RUN: llc -mtriple=x86_64-unknown-linux-gnu -run-pass=greedy \
+# RUN:   -verify-machineinstrs %s -o - | FileCheck %s
+
+# The greedy register allocator spills a value used by a foldable ("rm")
+# inline asm operand by folding the operand to the value's stack slot. The
+# clobbers below leave six GPRs, and the asm needs seven registers, so one
+# value is spilled. Folding replaces an operand with the five operands of a
+# memory reference, moving every operand after it, which must keep any tied
+# operands tied.
+#
+# Each test is derived from the C function and inline asm statement in its
+# comment, where CLOBBERS is:
+#
+#   "rax", "rbx", "rbp", "r10", "r11", "r12", "r13", "r14", "r15"
+
+---
+# One value passed to two "rm" operands: both operands fold to the same stack
+# slot.
+#
+#   void rm_same_value_twice(long a, long b, long c, long d, long e, long f,
+#                            long g) {
+#     asm volatile("" : : "rm"(a), "rm"(a), "r"(b), "r"(c), "r"(d), "r"(e),
+#                         "r"(f), "r"(g) : CLOBBERS);
+#   }
+name: rm_same_value_twice
+tracksRegLiveness: true
+fixedStack:
+  - { id: 0, size: 8, alignment: 16, isImmutable: true }
+body: |
+  bb.0:
+    liveins: $rdi, $rsi, $rdx, $rcx, $r8, $r9
+
+    ; CHECK-LABEL: name: rm_same_value_twice
+    ; CHECK: INLINEASM &"", sideeffect mayload attdialect, mem:m, %stack.[[SLOT:[0-9]+]], 1, $noreg, 0, $noreg, mem:m, %stack.[[SLOT]], 1, $noreg, 0, $noreg, reguse:GR64,
+    %0:gr64 = COPY $rdi
+    %1:gr64 = COPY $rsi
+    %2:gr64 = COPY $rdx
+    %3:gr64 = COPY $rcx
+    %4:gr64 = COPY $r8
+    %5:gr64 = COPY $r9
+    %6:gr64 = MOV64rm %fixed-stack.0, 1, $noreg, 0, $noreg :: (load (s64) from %fixed-stack.0, align 16)
+    INLINEASM &"", sideeffect attdialect, reguse:GR64 foldable, %0, reguse:GR64 foldable, %0, reguse:GR64, %1, reguse:GR64, %2, reguse:GR64, %3, reguse:GR64, %4, reguse:GR64, %5, reguse:GR64, %6, 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 input tied to the "=r" output after it.
+#
+#   long rm_before_tied(long a, long b, long c, long d, long e, long f,
+#                       long g) {
+#     asm volatile("" : "+r"(g) : "rm"(a), "r"(b), "r"(c), "r"(d), "r"(e),
+#                                 "r"(f) : CLOBBERS);
+#     return g;
+#   }
+name: rm_before_tied
+tracksRegLiveness: true
+fixedStack:
+  - { id: 0, size: 8, alignment: 16, isImmutable: true }
+body: |
+  bb.0:
+    liveins: $rdi, $rsi, $rdx, $rcx, $r8, $r9
+
+    ; CHECK-LABEL: name: rm_before_tied
+    ; CHECK: INLINEASM &"", sideeffect mayload attdialect, regdef:GR64, def %{{[0-9]+}}, mem:m, %stack.{{[0-9]+}}, 1, $noreg, 0, $noreg, {{.*}}, reguse tiedto:$0, %{{[0-9]+}}(tied-def 3), clobber,
+    %0:gr64 = COPY $rdi
+    %1:gr64 = COPY $rsi
+    %2:gr64 = COPY $rdx
+    %3:gr64 = COPY $rcx
+    %4:gr64 = COPY $r8
+    %5:gr64 = COPY $r9
+    %6:gr64 = MOV64rm %fixed-stack.0, 1, $noreg, 0, $noreg :: (load (s64) from %fixed-stack.0, align 16)
+    INLINEASM &"", sideeffect attdialect, regdef:GR64, def %6, reguse:GR64 foldable, %0, reguse:GR64, %1, reguse:GR64, %2, reguse:GR64, %3, reguse:GR64, %4, reguse:GR64, %5, reguse tiedto:$0, %6(tied-def 3), 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
+...
+---
+# Two read-write "+rm" operands: folding one tied pair, which reads and writes
+# the stack slot, keeps the other pair tied.
+#
+#   struct pair { long a, b; };
+#
+#   struct pair two_tied_rm(long a, long b, long c, long d, long e, long f,
+#                           long g) {
+#     asm volatile("" : "+rm"(a), "+rm"(b) : "r"(c), "r"(d), "r"(e), "r"(f),
+#                                            "r"(g) : CLOBBERS);
+#     return (struct pair){a, b};
+#   }
+name: two_tied_rm
+tracksRegLiveness: true
+fixedStack:
+  - { id: 0, size: 8, alignment: 16, isImmutable: true }
+body: |
+  bb.0:
+    liveins: $rdi, $rsi, $rdx, $rcx, $r8, $r9
+
+    ; CHECK-LABEL: name: two_tied_rm
+    ; CHECK: INLINEASM &"", sideeffect mayload maystore attdialect, regdef:GR64 foldable, def %{{[0-9]+}}, mem:m, %stack.[[SLOT:[0-9]+]], 1, $noreg, 0, $noreg, {{.*}}, reguse tiedto:$0, %{{[0-9]+}}(tied-def 3), mem:m, %stack.[[SLOT]], 1, $noreg, 0, $noreg, clobber,
+    %0:gr64 = COPY $rdi
+    %1:gr64 = COPY $rsi
+    %2:gr64 = COPY $rdx
+    %3:gr64 = COPY $rcx
+    %4:gr64 = COPY $r8
+    %5:gr64 = COPY $r9
+    %6:gr64 = MOV64rm %fixed-stack.0, 1, $noreg, 0, $noreg :: (load (s64) from %fixed-stack.0, align 16)
+    INLINEASM &"", sideeffect attdialect, regdef:GR64 foldable, def %0, regdef:GR64 foldable, def %1, reguse:GR64, %2, reguse:GR64, %3, reguse:GR64, %4, reguse:GR64, %5, reguse:GR64, %6, reguse tiedto:$0, %0(tied-def 3), reguse tiedto:$1, %1(tied-def 5), 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 %0
+    $rdx = COPY %1
+    RET 0, $rax, $rdx
+...

>From 2db942933665d858950577261d5a29cdb3b4b035 Mon Sep 17 00:00:00 2001
From: Bill Wendling <isanbard at gmail.com>
Date: Tue, 6 Oct 2026 06:12:08 -0700
Subject: [PATCH 3/4] [CodeGen] Report an error for a direct inline asm output
 in memory

An inline asm output returned by value has no memory to write to, yet a
constraint such as "=rm" picks memory, the most general constraint, as
does "=m". SelectionDAG asserted on that ("Can only indirectify direct
input operands!"), and GlobalISel dereferenced a null pointer. Clang
never emits such an output, since it passes the address of a memory
output, but other IR can. Report "cannot handle direct memory outputs
yet for constraint 'm'" instead, like the other inline asm errors there.

Assisted-by: Claude Opus 5.5
---
 .../CodeGen/GlobalISel/InlineAsmLowering.cpp  | 10 ++++++
 .../SelectionDAG/SelectionDAGBuilder.cpp      | 22 +++++++++----
 .../inline-asm-direct-mem-output-error.ll     | 13 ++++++++
 .../X86/inline-asm-direct-mem-output-error.ll | 31 +++++++++++++++++++
 4 files changed, 70 insertions(+), 6 deletions(-)
 create mode 100644 llvm/test/CodeGen/AArch64/inline-asm-direct-mem-output-error.ll
 create mode 100644 llvm/test/CodeGen/X86/inline-asm-direct-mem-output-error.ll

diff --git a/llvm/lib/CodeGen/GlobalISel/InlineAsmLowering.cpp b/llvm/lib/CodeGen/GlobalISel/InlineAsmLowering.cpp
index 19eae930cc26b1..5d66cb5e332276 100644
--- a/llvm/lib/CodeGen/GlobalISel/InlineAsmLowering.cpp
+++ b/llvm/lib/CodeGen/GlobalISel/InlineAsmLowering.cpp
@@ -337,6 +337,16 @@ bool InlineAsmLowering::lowerInlineAsm(
     switch (OpInfo.Type) {
     case InlineAsm::isOutput:
       if (OpInfo.ConstraintType == TargetLowering::C_Memory) {
+        // A direct output is the call's result, with no address to write to.
+        if (!OpInfo.isIndirect) {
+          emitInlineAsmError(MIRBuilder, Call,
+                             "cannot handle direct memory outputs yet for "
+                             "constraint '" +
+                                 Twine(OpInfo.ConstraintCode) + "'",
+                             GetOrCreateVRegs(Call));
+          return true;
+        }
+
         const InlineAsm::ConstraintCode ConstraintID =
             TLI->getInlineAsmMemConstraint(OpInfo.ConstraintCode);
         assert(ConstraintID != InlineAsm::ConstraintCode::Unknown &&
diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
index dfbf9a401b53ea..de009524f97c37 100644
--- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
@@ -10491,8 +10491,9 @@ constructOperandInfo(ConstraintDecisionInfo &Info,
   return false;
 }
 
-/// Compute which constraint option to use for each operand.
-static void
+/// Compute which constraint option to use for each operand. Returns true (and
+/// sets Info.ErrorMsg) on failure.
+static bool
 computeConstraintToUse(ConstraintDecisionInfo &Info, const CallBase &Call,
                        TargetLowering::AsmOperandInfoVector &TargetConstraints,
                        SelectionDAGBuilder &Builder, const TargetLowering &TLI,
@@ -10554,9 +10555,14 @@ computeConstraintToUse(ConstraintDecisionInfo &Info, const CallBase &Call,
     // need to provide an address for the memory input.
     if (OpInfo.ConstraintType == TargetLowering::C_Memory &&
         !OpInfo.isIndirect) {
-      assert((OpInfo.isMultipleAlternative ||
-              (OpInfo.Type == InlineAsm::isInput)) &&
-             "Can only indirectify direct input operands!");
+      // Only an input has a value to store to memory. A direct output, which
+      // Clang never emits with a memory constraint but other IR can, would
+      // need a stack slot to be reloaded after the asm.
+      if (!OpInfo.isMultipleAlternative && OpInfo.Type != InlineAsm::isInput) {
+        Info.ErrorMsg << "cannot handle direct memory outputs yet for "
+                      << "constraint '" << OpInfo.ConstraintCode << "'";
+        return true;
+      }
 
       // Memory operands really want the address of the value.
       Info.Chain = getAddressForMemoryInput(Info.Chain, Builder.getCurSDLoc(),
@@ -10569,6 +10575,8 @@ computeConstraintToUse(ConstraintDecisionInfo &Info, const CallBase &Call,
       OpInfo.isIndirect = true;
     }
   }
+
+  return false;
 }
 
 /// Prepare DAG-level operands. As part of this, assign virtual and physical
@@ -10862,7 +10870,9 @@ determineConstraints(ConstraintDecisionInfo &Info,
     Info.Chain = Builder.lowerStartEH(Info.Chain, EHPadBB, Info.BeginLabel);
 
   // Second pass: Compute which constraint option to use.
-  computeConstraintToUse(Info, Call, TargetConstraints, Builder, TLI, TM, DAG);
+  if (computeConstraintToUse(Info, Call, TargetConstraints, Builder, TLI, TM,
+                             DAG))
+    return true;
 
   // AsmNodeOperands - The operands for the ISD::INLINEASM node.
   Info.AsmNodeOperands.push_back(SDValue()); // reserve space for input chain
diff --git a/llvm/test/CodeGen/AArch64/inline-asm-direct-mem-output-error.ll b/llvm/test/CodeGen/AArch64/inline-asm-direct-mem-output-error.ll
new file mode 100644
index 00000000000000..7ebe9f006943f9
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/inline-asm-direct-mem-output-error.ll
@@ -0,0 +1,13 @@
+; RUN: not llc -mtriple=aarch64 -filetype=null < %s 2>&1 | FileCheck %s
+; RUN: not llc -mtriple=aarch64 -global-isel -global-isel-abort=1 \
+; RUN:   -filetype=null < %s 2>&1 | FileCheck %s
+
+; A direct output is the asm's result, so there is no memory to write it to.
+; Picking a memory constraint for one is an error, not a crash, with either
+; instruction selector.
+
+; CHECK: error: cannot handle direct memory outputs yet for constraint 'm'
+define i64 @rm_output() {
+  %r = call i64 asm "# $0", "=rm"()
+  ret i64 %r
+}
diff --git a/llvm/test/CodeGen/X86/inline-asm-direct-mem-output-error.ll b/llvm/test/CodeGen/X86/inline-asm-direct-mem-output-error.ll
new file mode 100644
index 00000000000000..3f3a9168de38f8
--- /dev/null
+++ b/llvm/test/CodeGen/X86/inline-asm-direct-mem-output-error.ll
@@ -0,0 +1,31 @@
+; RUN: not llc -mtriple=x86_64-unknown-linux-gnu -O0 -filetype=null < %s 2>&1 \
+; RUN:   | FileCheck --check-prefix=O0 --implicit-check-not=error: %s
+; RUN: not llc -mtriple=x86_64-unknown-linux-gnu -filetype=null < %s 2>&1 \
+; RUN:   | FileCheck --check-prefix=O2 --implicit-check-not=error: %s
+
+; A direct output is the asm's result, so there is no memory to write it to.
+; Picking a memory constraint for one is an error, not a crash.
+
+; "rm" picks memory, the most general constraint.
+; O0: error: cannot handle direct memory outputs yet for constraint 'm'
+; O2: error: cannot handle direct memory outputs yet for constraint 'm'
+define i32 @rm_i32() {
+  %r = call i32 asm "# $0", "=rm"()
+  ret i32 %r
+}
+
+; The same for a value no 'r' register can hold.
+; O0: error: cannot handle direct memory outputs yet for constraint 'm'
+; O2: error: cannot handle direct memory outputs yet for constraint 'm'
+define x86_fp80 @rm_x86_fp80() {
+  %r = call x86_fp80 asm "# $0", "=rm"()
+  ret x86_fp80 %r
+}
+
+; "=m" has no other choice.
+; O0: error: cannot handle direct memory outputs yet for constraint 'm'
+; O2: error: cannot handle direct memory outputs yet for constraint 'm'
+define i32 @m_i32() {
+  %r = call i32 asm "# $0", "=m"()
+  ret i32 %r
+}

>From e54c2bfe1466ac1b17290e2621b151867de07858 Mon Sep 17 00:00:00 2001
From: Bill Wendling <isanbard at gmail.com>
Date: Tue, 6 Oct 2026 06:14:26 -0700
Subject: [PATCH 4/4] [RegAllocFast] Fold foldable inline asm operands under
 register pressure

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 is read later in the block. Kill
  flags are optional, so a missing one doesn't mean 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
---
 llvm/lib/CodeGen/RegAllocFast.cpp             | 320 ++++++++++++
 .../regallocfast-inline-asm-fold-pressure.mir | 479 ++++++++++++++++++
 .../X86/regallocfast-inline-asm-fold.mir      |  80 +++
 3 files changed, 879 insertions(+)
 create mode 100644 llvm/test/CodeGen/X86/regallocfast-inline-asm-fold-pressure.mir
 create mode 100644 llvm/test/CodeGen/X86/regallocfast-inline-asm-fold.mir

diff --git a/llvm/lib/CodeGen/RegAllocFast.cpp b/llvm/lib/CodeGen/RegAllocFast.cpp
index dfdbe7d4d2121a..ddec4254f75905 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);
@@ -431,6 +437,12 @@ class RegAllocFastImpl {
 
   bool mayBeSpillFromInlineAsmBr(const MachineInstr &MI) const;
 
+  void foldInlineAsmOperands(MachineBasicBlock &MBB);
+  bool tiedUseNeedsOwnReg(const MachineInstr &MI, unsigned UseIdx) const;
+  void selectInlineAsmRegsToFold(const MachineInstr &MI,
+                                 SmallVectorImpl<Register> &ToFold) const;
+  void foldInlineAsmReg(MachineInstr *&MI, Register Reg);
+
   void dumpState() const;
 };
 
@@ -1956,6 +1968,309 @@ void RegAllocFastImpl::handleBundle(MachineInstr &MI) {
   }
 }
 
+/// Return true if lowerTiedUse() will give the input \p UseIdx of \p MI, tied
+/// to a virtual def, a register of its own to copy into the def's register.
+/// It does when the value already has a register below MI, i.e. it's read
+/// further down the block (a read in another block reloads it from its stack
+/// slot), and when the value can't simply move into the def's register. Kill
+/// flags are optional here, so a missing one doesn't mean the value lives on.
+bool RegAllocFastImpl::tiedUseNeedsOwnReg(const MachineInstr &MI,
+                                          unsigned UseIdx) const {
+  const MachineOperand &MO = MI.getOperand(UseIdx);
+  const MachineOperand &DefMO = MI.getOperand(MI.findTiedOperandIdx(UseIdx));
+  Register Reg = MO.getReg();
+  if (MO.getSubReg() ||
+      !MRI->getRegClass(Reg)->hasSubClassEq(MRI->getRegClass(DefMO.getReg())))
+    return true;
+
+  // Another read of the value by MI may get it a register first.
+  if (any_of(MI.all_uses(), [&](const MachineOperand &O) {
+        return &O != &MO && O.getReg() == Reg;
+      }))
+    return true;
+
+  if (MO.isKill())
+    return false;
+
+  // Only scan the rest of the block if the value is read elsewhere in it,
+  // which is rare for the input of a tied operand.
+  const MachineBasicBlock *MBB = MI.getParent();
+  if (none_of(MRI->use_nodbg_instructions(Reg), [&](const MachineInstr &UseMI) {
+        return &UseMI != &MI && UseMI.getParent() == MBB;
+      }))
+    return false;
+
+  return any_of(
+      make_range(std::next(MachineBasicBlock::const_iterator(MI)), MBB->end()),
+      [&](const MachineInstr &Other) {
+        return !Other.isDebugInstr() && Other.readsVirtualRegister(Reg);
+      });
+}
+
+/// 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 (see tiedUseNeedsOwnReg()), and always
+    // does when 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 (tiedUseNeedsOwnReg(MI, I))
+          ++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);
@@ -1969,6 +2284,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 00000000000000..87f7239f56bdb2
--- /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_NOREX2, killed renamable $r9, reguse tiedto:$0, renamable $rcx(tied-def 3), 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: MOV64mr %stack.1, 1, $noreg, 0, $noreg, $rcx :: (store (s64) into %stack.1)
+    ; CHECK-NEXT: $rax = MOV64rm %stack.1, 1, $noreg, 0, $noreg :: (load (s64) from %stack.1)
+    ; 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
+    %14:gr64 = MOV64rm %fixed-stack.0, 1, $noreg, 0, $noreg :: (load (s64) from %fixed-stack.0, align 16)
+    %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
+    %13:gr64_norex2 = COPY %14
+    %6:gr64_norex2 = COPY %13
+    INLINEASM &"# $0 $1 $2 $3 $4 $5 $6 $7", sideeffect attdialect, regdef:GR64_NOREX2, def %6, reguse:GR64_NOREX2 foldable, %7, reguse:GR64_NOREX2, %8, reguse:GR64_NOREX2, %9, reguse:GR64_NOREX2, %10, reguse:GR64_NOREX2, %11, reguse:GR64_NOREX2, %12, reguse tiedto:$0, %6(tied-def 3), 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
+...
+---
+# Two read-write "+rm" operands: folding one tied pair must keep the other
+# pair tied.
+name:            test_two_tied_rm_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_norex2 }
+  - { id: 15, 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_two_tied_rm_pressure
+    ; CHECK: liveins: $rdi, $rsi, $rdx, $rcx, $r8, $r9
+    ; CHECK-NEXT: {{  $}}
+    ; CHECK-NEXT: MOV64mr %stack.1, 1, $noreg, 0, $noreg, $rcx :: (store (s64) into %stack.1)
+    ; CHECK-NEXT: renamable $rcx = COPY killed $rdx
+    ; CHECK-NEXT: renamable $rdx = COPY $rsi
+    ; CHECK-NEXT: $rsi = MOV64rm %stack.1, 1, $noreg, 0, $noreg :: (load (s64) from %stack.1)
+    ; 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, renamable $rax :: (store (s64) into %stack.0)
+    ; CHECK-NEXT: INLINEASM &"# $0 $1 $2 $3 $4 $5 $6", sideeffect mayload maystore attdialect, mem:m, %stack.0, 1, $noreg, 0, $noreg, regdef:GR64_NOREX2 foldable, def renamable $rdx, reguse:GR64_NOREX2, killed renamable $rcx, reguse:GR64_NOREX2, killed renamable $rsi, reguse:GR64_NOREX2, killed renamable $r8, reguse:GR64_NOREX2, killed renamable $r9, reguse:GR64_NOREX2, killed renamable $rdi, mem:m, %stack.0, 1, $noreg, 0, $noreg, reguse tiedto:$1, renamable $rdx(tied-def 9), 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 store (s64) on %stack.0)
+    ; CHECK-NEXT: renamable $rax = MOV64rm %stack.0, 1, $noreg, 0, $noreg :: (load (s64) from %stack.0)
+    ; CHECK-NEXT: RET 0, killed $rax, killed $rdx
+    %5:gr64 = COPY $r9
+    %4:gr64 = COPY $r8
+    %3:gr64 = COPY $rcx
+    %2:gr64 = COPY $rdx
+    %1:gr64 = COPY $rsi
+    %0:gr64 = COPY $rdi
+    %15:gr64 = MOV64rm %fixed-stack.0, 1, $noreg, 0, $noreg :: (load (s64) from %fixed-stack.0, align 16)
+    %8:gr64_norex2 = COPY %2
+    %9:gr64_norex2 = COPY %3
+    %10:gr64_norex2 = COPY %4
+    %11:gr64_norex2 = COPY %5
+    %12:gr64_norex2 = COPY %15
+    %13:gr64_norex2 = COPY %0
+    %14:gr64_norex2 = COPY %1
+    %6:gr64_norex2 = COPY %13
+    %7:gr64_norex2 = COPY %14
+    INLINEASM &"# $0 $1 $2 $3 $4 $5 $6", sideeffect attdialect, regdef:GR64_NOREX2 foldable, def %6, regdef:GR64_NOREX2 foldable, def %7, reguse:GR64_NOREX2, %8, reguse:GR64_NOREX2, %9, reguse:GR64_NOREX2, %10, reguse:GR64_NOREX2, %11, reguse:GR64_NOREX2, %12, reguse tiedto:$0, %6(tied-def 3), reguse tiedto:$1, %7(tied-def 5), 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
+    $rdx = COPY %7
+    RET 0, $rax, $rdx
+...
+---
+# Like test_rm_output_pressure, but the output isn't early-clobber, so it can
+# share a register with an input and nothing needs to be folded.
+name:            test_rm_output_shares_input_register
+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_shares_input_register
+    ; CHECK: liveins: $rdi, $rsi, $rdx, $rcx, $r8, $r9
+    ; CHECK-NEXT: {{  $}}
+    ; CHECK-NEXT: INLINEASM &"# $0 $1 $2 $3 $4 $5 $6", sideeffect attdialect, regdef:GR64_NOREX2 foldable, def renamable $rcx, 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
+    ; CHECK-NEXT: MOV64mr %stack.0, 1, $noreg, 0, $noreg, $rcx :: (store (s64) into %stack.0)
+    ; CHECK-NEXT: $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:GR64_NOREX2 foldable, def %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
+...
+---
+# Eleven inputs for the ten GPRs left after the clobbers: one "rm" input has
+# to be folded even though the 32-bit inputs alone would fit, because each
+# 64-bit input takes a 32-bit register too.
+name:            test_rm_mixed_widths_pressure
+tracksRegLiveness: true
+registers:
+  - { id: 0, class: gr32 }
+  - { id: 1, class: gr32 }
+  - { id: 2, class: gr32 }
+  - { id: 3, class: gr32 }
+  - { id: 4, class: gr32 }
+  - { id: 5, class: gr64 }
+  - { id: 6, class: gr32_norex2 }
+  - { id: 7, class: gr32_norex2 }
+  - { id: 8, class: gr32_norex2 }
+  - { id: 9, class: gr32_norex2 }
+  - { id: 10, class: gr32_norex2 }
+  - { id: 11, class: gr64_norex2 }
+  - { id: 12, class: gr64_norex2 }
+  - { id: 13, class: gr64_norex2 }
+  - { id: 14, class: gr64_norex2 }
+  - { id: 15, class: gr64_norex2 }
+  - { id: 16, class: gr64_norex2 }
+  - { id: 17, class: gr64 }
+  - { id: 18, class: gr64 }
+  - { id: 19, class: gr64 }
+  - { id: 20, class: gr64 }
+  - { id: 21, class: gr64 }
+fixedStack:
+  - { id: 0, offset: 32, size: 8, alignment: 16, isImmutable: true }
+  - { id: 1, offset: 24, size: 8, alignment: 8, isImmutable: true }
+  - { id: 2, offset: 16, size: 8, alignment: 16, isImmutable: true }
+  - { id: 3, offset: 8, size: 8, alignment: 8, isImmutable: true }
+  - { id: 4, size: 8, alignment: 16, isImmutable: true }
+body:             |
+  bb.0:
+    liveins: $edi, $esi, $edx, $ecx, $r8d, $r9
+
+    ; CHECK-LABEL: name: test_rm_mixed_widths_pressure
+    ; CHECK: liveins: $edi, $esi, $edx, $ecx, $r8d, $r9
+    ; CHECK-NEXT: {{  $}}
+    ; CHECK-NEXT: renamable $ebx = COPY killed $edi
+    ; CHECK-NEXT: renamable $rax = MOV64rm %fixed-stack.0, 1, $noreg, 0, $noreg :: (load (s64) from %fixed-stack.0, align 16)
+    ; CHECK-NEXT: renamable $rdi = MOV64rm %fixed-stack.1, 1, $noreg, 0, $noreg :: (load (s64) from %fixed-stack.1)
+    ; CHECK-NEXT: renamable $r10 = MOV64rm %fixed-stack.2, 1, $noreg, 0, $noreg :: (load (s64) from %fixed-stack.2, align 16)
+    ; CHECK-NEXT: renamable $r11 = MOV64rm %fixed-stack.3, 1, $noreg, 0, $noreg :: (load (s64) from %fixed-stack.3)
+    ; CHECK-NEXT: renamable $r15 = MOV64rm %fixed-stack.4, 1, $noreg, 0, $noreg :: (load (s64) from %fixed-stack.4, align 16)
+    ; CHECK-NEXT: MOV32mr %stack.0, 1, $noreg, 0, $noreg, killed renamable $ebx :: (store (s32) into %stack.0)
+    ; CHECK-NEXT: INLINEASM &"# $0 $1 $2 $3 $4 $5 $6 $7 $8 $9 $10", sideeffect mayload attdialect, mem:m, %stack.0, 1, $noreg, 0, $noreg, reguse:GR32_NOREX2 foldable, killed renamable $esi, reguse:GR32_NOREX2 foldable, killed renamable $edx, reguse:GR32_NOREX2 foldable, killed renamable $ecx, reguse:GR32_NOREX2 foldable, killed renamable $r8d, reguse:GR64_NOREX2, killed renamable $r9, reguse:GR64_NOREX2, killed renamable $rax, reguse:GR64_NOREX2, killed renamable $rdi, reguse:GR64_NOREX2, killed renamable $r10, reguse:GR64_NOREX2, killed renamable $r11, reguse:GR64_NOREX2, killed renamable $r15, clobber, implicit-def dead early-clobber $rbx, clobber, implicit-def dead early-clobber $rbp, clobber, implicit-def dead early-clobber $r12, clobber, implicit-def dead early-clobber $r13, clobber, implicit-def dead early-clobber $r14 :: (load (s32) from %stack.0)
+    ; CHECK-NEXT: RET 0
+    %5:gr64 = COPY $r9
+    %4:gr32 = COPY $r8d
+    %3:gr32 = COPY $ecx
+    %2:gr32 = COPY $edx
+    %1:gr32 = COPY $esi
+    %0:gr32 = COPY $edi
+    %17:gr64 = MOV64rm %fixed-stack.4, 1, $noreg, 0, $noreg :: (load (s64) from %fixed-stack.4, align 16)
+    %18:gr64 = MOV64rm %fixed-stack.3, 1, $noreg, 0, $noreg :: (load (s64) from %fixed-stack.3)
+    %19:gr64 = MOV64rm %fixed-stack.2, 1, $noreg, 0, $noreg :: (load (s64) from %fixed-stack.2, align 16)
+    %20:gr64 = MOV64rm %fixed-stack.1, 1, $noreg, 0, $noreg :: (load (s64) from %fixed-stack.1)
+    %21:gr64 = MOV64rm %fixed-stack.0, 1, $noreg, 0, $noreg :: (load (s64) from %fixed-stack.0, align 16)
+    %6:gr32_norex2 = COPY %0
+    %7:gr32_norex2 = COPY %1
+    %8:gr32_norex2 = COPY %2
+    %9:gr32_norex2 = COPY %3
+    %10:gr32_norex2 = COPY %4
+    %11:gr64_norex2 = COPY %5
+    %12:gr64_norex2 = COPY %17
+    %13:gr64_norex2 = COPY %18
+    %14:gr64_norex2 = COPY %19
+    %15:gr64_norex2 = COPY %20
+    %16:gr64_norex2 = COPY %21
+    INLINEASM &"# $0 $1 $2 $3 $4 $5 $6 $7 $8 $9 $10", sideeffect attdialect, reguse:GR32_NOREX2 foldable, %6, reguse:GR32_NOREX2 foldable, %7, reguse:GR32_NOREX2 foldable, %8, reguse:GR32_NOREX2 foldable, %9, reguse:GR32_NOREX2 foldable, %10, reguse:GR64_NOREX2, %11, reguse:GR64_NOREX2, %12, reguse:GR64_NOREX2, %13, reguse:GR64_NOREX2, %14, reguse:GR64_NOREX2, %15, reguse:GR64_NOREX2, %16, clobber, implicit-def dead early-clobber $rbx, clobber, implicit-def dead early-clobber $rbp, clobber, implicit-def dead early-clobber $r12, clobber, implicit-def dead early-clobber $r13, clobber, implicit-def dead early-clobber $r14
+    RET 0
+...
+---
+# Seven values for six GPRs. Folding the "rm" operand of %6 wouldn't free a
+# register, since the "r" operand needs %6 in one anyway, so fold %8's.
+name:            test_rm_and_r_same_value_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_and_r_same_value_pressure
+    ; CHECK: liveins: $rdi, $rsi, $rdx, $rcx, $r8, $r9
+    ; CHECK-NEXT: {{  $}}
+    ; CHECK-NEXT: renamable $rax = COPY killed $rsi
+    ; CHECK-NEXT: renamable $rsi = 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, reguse:GR64_NOREX2 foldable, killed renamable $rdi, reguse:GR64_NOREX2, renamable $rdi, mem:m, %stack.0, 1, $noreg, 0, $noreg, 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 $rsi, 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, %6, reguse:GR64_NOREX2 foldable, %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
+...
+---
+# Like test_rm_output_pressure, with an INLINEASM_BR. The reload of %7 after
+# the asm only covers the fallthrough path: on the indirect path, %7 must come
+# from the slot the asm wrote, which has to be %7's own spill slot, so that
+# the indirect block's ordinary reload of %7 finds it.
+name:            test_callbr_rm_indirect_use
+tracksRegLiveness: true
+registers:
+  - { id: 0, class: gr64 }
+  - { id: 1, class: gr64_nosp }
+  - { id: 2, class: gr64 }
+  - { id: 3, class: gr64 }
+  - { id: 4, class: gr64 }
+  - { id: 5, class: gr64 }
+  - { id: 6, class: gr64 }
+  - { 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_norex2 }
+  - { id: 15, class: gr64 }
+  - { id: 16, class: gr32 }
+  - { id: 17, class: gr64 }
+body:             |
+  ; CHECK-LABEL: name: test_callbr_rm_indirect_use
+  ; CHECK: bb.0:
+  ; CHECK-NEXT:   successors: %bb.1(0x80000000), %bb.2(0x00000000)
+  ; CHECK-NEXT:   liveins: $rdi, $rsi, $rdx, $rcx, $r8, $r9
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT:   MOV64mr %stack.1, 1, $noreg, 0, $noreg, $rdi :: (store (s64) into %stack.1)
+  ; CHECK-NEXT:   INLINEASM_BR &"# $0 $1 $2 $3 $4 $5 $6 ${7:l}", 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, imm, %bb.2, 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:   MOV64mr %stack.0, 1, $noreg, 0, $noreg, killed $rax :: (store (s64) into %stack.0)
+  ; CHECK-NEXT:   JMP_1 %bb.1
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT: bb.1:
+  ; CHECK-NEXT:   renamable $eax = MOV32r0 implicit-def dead $eflags
+  ; CHECK-NEXT:   renamable $rax = SUBREG_TO_REG killed renamable $eax, %subreg.sub_32bit
+  ; CHECK-NEXT:   RET 0, killed $rax
+  ; CHECK-NEXT: {{  $}}
+  ; CHECK-NEXT: bb.2 (inlineasm-br-indirect-target):
+  ; CHECK-NEXT:   $rax = MOV64rm %stack.0, 1, $noreg, 0, $noreg :: (load (s64) from %stack.0)
+  ; CHECK-NEXT:   $rcx = MOV64rm %stack.1, 1, $noreg, 0, $noreg :: (load (s64) from %stack.1)
+  ; CHECK-NEXT:   renamable $rax = LEA64r killed renamable $rax, 1, killed renamable $rcx, 0, $noreg
+  ; CHECK-NEXT:   RET 0, killed $rax
+  bb.0:
+    successors: %bb.1(0x80000000), %bb.2(0x00000000)
+    liveins: $rdi, $rsi, $rdx, $rcx, $r8, $r9
+
+    %6:gr64 = COPY $r9
+    %5:gr64 = COPY $r8
+    %4:gr64 = COPY $rcx
+    %3:gr64 = COPY $rdx
+    %2:gr64 = COPY $rsi
+    %1:gr64_nosp = COPY $rdi
+    %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 %6
+    INLINEASM_BR &"# $0 $1 $2 $3 $4 $5 $6 ${7:l}", sideeffect attdialect, regdef-ec:GR64_NOREX2 foldable, def early-clobber %7, reguse:GR64_NOREX2, %8, reguse:GR64_NOREX2, %9, reguse:GR64_NOREX2, %10, reguse:GR64_NOREX2, %11, reguse:GR64_NOREX2, %12, reguse:GR64_NOREX2, %13, imm, %bb.2, 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
+    JMP_1 %bb.1
+
+  bb.1:
+    %16:gr32 = MOV32r0 implicit-def dead $eflags
+    %17:gr64 = SUBREG_TO_REG killed %16, %subreg.sub_32bit
+    $rax = COPY %17
+    RET 0, $rax
+
+  bb.2 (inlineasm-br-indirect-target):
+    %15:gr64 = LEA64r %7, 1, %1, 0, $noreg
+    $rax = COPY %15
+    RET 0, $rax
+...
+
diff --git a/llvm/test/CodeGen/X86/regallocfast-inline-asm-fold.mir b/llvm/test/CodeGen/X86/regallocfast-inline-asm-fold.mir
new file mode 100644
index 00000000000000..870277e0fe2f19
--- /dev/null
+++ b/llvm/test/CodeGen/X86/regallocfast-inline-asm-fold.mir
@@ -0,0 +1,80 @@
+# RUN: llc -mtriple=x86_64-unknown-linux-gnu -run-pass=regallocfast \
+# RUN:   -verify-machineinstrs %s -o - | FileCheck %s
+
+# The fast register allocator folds a foldable ("rm") inline asm operand to a
+# stack slot when the asm's register operands wouldn't fit otherwise. The
+# clobbers below leave six GPRs. The tied operands haven't been rewritten, so
+# a tied def and its input are different virtual registers: the allocator
+# lowers the tie itself, copying the input into the def's register if the
+# input stays live.
+
+---
+# Five inputs plus the tied pair fit: the input dies, so the def takes over its
+# register.
+name: tied_input_dies
+tracksRegLiveness: true
+body: |
+  bb.0:
+    liveins: $rdi, $rsi, $rdx, $rcx, $r8, $r9
+
+    ; CHECK-LABEL: name: tied_input_dies
+    ; CHECK-NOT: MOV64mr
+    ; CHECK: INLINEASM &"", sideeffect attdialect, regdef:GR64 foldable, def renamable [[REG:\$[a-z0-9]+]], {{.*}}, reguse tiedto:$0, killed renamable [[REG]](tied-def 3)
+    %0:gr64 = COPY $rdi
+    %1:gr64 = COPY $rsi
+    %2:gr64 = COPY $rdx
+    %3:gr64 = COPY $rcx
+    %4:gr64 = COPY $r8
+    %5:gr64 = COPY $r9
+    INLINEASM &"", sideeffect attdialect, regdef:GR64 foldable, def %6:gr64, reguse:GR64, killed %1, reguse:GR64, killed %2, reguse:GR64, killed %3, reguse:GR64, killed %4, reguse:GR64, killed %5, reguse tiedto:$0, killed %0(tied-def 3), clobber, implicit-def early-clobber $rax, clobber, implicit-def early-clobber $rbx, clobber, implicit-def early-clobber $rbp, clobber, implicit-def early-clobber $r10, clobber, implicit-def early-clobber $r11, clobber, implicit-def early-clobber $r12, clobber, implicit-def early-clobber $r13, clobber, implicit-def early-clobber $r14, clobber, implicit-def early-clobber $r15
+    $rax = COPY %6
+    RET 0, $rax
+...
+---
+# The same without kill flags, which are optional: nothing reads the input
+# after the asm, so it still dies there.
+name: tied_input_dies_no_kill_flag
+tracksRegLiveness: true
+body: |
+  bb.0:
+    liveins: $rdi, $rsi, $rdx, $rcx, $r8, $r9
+
+    ; CHECK-LABEL: name: tied_input_dies_no_kill_flag
+    ; CHECK-NOT: MOV64mr
+    ; CHECK: INLINEASM &"", sideeffect attdialect, regdef:GR64 foldable, def renamable [[REG:\$[a-z0-9]+]], {{.*}}, reguse tiedto:$0, killed renamable [[REG]](tied-def 3)
+    %0:gr64 = COPY $rdi
+    %1:gr64 = COPY $rsi
+    %2:gr64 = COPY $rdx
+    %3:gr64 = COPY $rcx
+    %4:gr64 = COPY $r8
+    %5:gr64 = COPY $r9
+    INLINEASM &"", sideeffect attdialect, regdef:GR64 foldable, def %6:gr64, reguse:GR64, %1, reguse:GR64, %2, reguse:GR64, %3, reguse:GR64, %4, reguse:GR64, %5, reguse tiedto:$0, %0(tied-def 3), clobber, implicit-def early-clobber $rax, clobber, implicit-def early-clobber $rbx, clobber, implicit-def early-clobber $rbp, clobber, implicit-def early-clobber $r10, clobber, implicit-def early-clobber $r11, clobber, implicit-def early-clobber $r12, clobber, implicit-def early-clobber $r13, clobber, implicit-def early-clobber $r14, clobber, implicit-def early-clobber $r15
+    $rax = COPY %6
+    RET 0, $rax
+...
+---
+# The same, except that the input is used after the asm, so it keeps its own
+# register while it's copied into the def's: seven registers. Fold the tied
+# pair instead, storing the input's value to the def's stack slot first.
+name: tied_input_lives
+tracksRegLiveness: true
+body: |
+  bb.0:
+    liveins: $rdi, $rsi, $rdx, $rcx, $r8, $r9
+
+    ; CHECK-LABEL: name: tied_input_lives
+    ; CHECK: MOV64mr %stack.[[SLOT:[0-9]+]], 1, $noreg, 0, $noreg, renamable [[INPUT:\$[a-z0-9]+]]
+    ; CHECK-NEXT: INLINEASM &"", sideeffect mayload maystore attdialect, mem:m, %stack.[[SLOT]], 1, $noreg, 0, $noreg, {{.*}}, mem:m, %stack.[[SLOT]], 1, $noreg, 0, $noreg, clobber,
+    ; CHECK-NEXT: renamable [[DEF:\$[a-z0-9]+]] = MOV64rm %stack.[[SLOT]], 1, $noreg, 0, $noreg
+    ; CHECK-NEXT: ADD64rr killed renamable [[DEF]], killed renamable [[INPUT]]
+    %0:gr64 = COPY $rdi
+    %1:gr64 = COPY $rsi
+    %2:gr64 = COPY $rdx
+    %3:gr64 = COPY $rcx
+    %4:gr64 = COPY $r8
+    %5:gr64 = COPY $r9
+    INLINEASM &"", sideeffect attdialect, regdef:GR64 foldable, def %6:gr64, reguse:GR64, killed %1, reguse:GR64, killed %2, reguse:GR64, killed %3, reguse:GR64, killed %4, reguse:GR64, killed %5, reguse tiedto:$0, %0(tied-def 3), clobber, implicit-def early-clobber $rax, clobber, implicit-def early-clobber $rbx, clobber, implicit-def early-clobber $rbp, clobber, implicit-def early-clobber $r10, clobber, implicit-def early-clobber $r11, clobber, implicit-def early-clobber $r12, clobber, implicit-def early-clobber $r13, clobber, implicit-def early-clobber $r14, clobber, implicit-def early-clobber $r15
+    %7:gr64 = ADD64rr %6, killed %0, implicit-def dead $eflags
+    $rax = COPY %7
+    RET 0, $rax
+...



More information about the llvm-branch-commits mailing list