[llvm-branch-commits] [llvm] [CodeGen] Prefer spilling a register an inline asm can fold (PR #235633)

Bill Wendling via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Mon Oct 26 00:34:07 PDT 2026


https://github.com/isanbard created https://github.com/llvm/llvm-project/pull/235633

InlineSpiller rematerialized a value passed to a foldable inline asm operand (e.g. a constant) into a register right before the asm, which the asm may not have had, since registers running out is why the value was spilled. It's now spilled and folded instead.

An asm that can fold a register also no longer adds to the register's spill weight, since it needs no reload or spill for it. The allocator then spills that register before the asm's other operands, which need registers, which saves spills in two_tied_rm and tied_rm_undef.

Assisted-by: Claude Opus 5.5


>From 5f2e80314563582d1cb37619e2e0bf500e39d7e3 Mon Sep 17 00:00:00 2001
From: Bill Wendling <isanbard at gmail.com>
Date: Mon, 26 Oct 2026 00:19:57 -0700
Subject: [PATCH] [CodeGen] Prefer spilling a register an inline asm can fold

InlineSpiller rematerialized a value passed to a foldable inline asm
operand (e.g. a constant) into a register right before the asm, which
the asm may not have had, since registers running out is why the value
was spilled. It's now spilled and folded instead.

An asm that can fold a register also no longer adds to the register's
spill weight, since it needs no reload or spill for it. The allocator
then spills that register before the asm's other operands, which need
registers, which saves spills in two_tied_rm and tied_rm_undef.

Assisted-by: Claude Opus 5.5
---
 llvm/lib/CodeGen/CalcSpillWeights.cpp         |  6 +-
 llvm/lib/CodeGen/InlineSpiller.cpp            |  6 ++
 .../CodeGen/X86/greedy-inline-asm-fold.mir    | 70 +++++++++++++------
 3 files changed, 61 insertions(+), 21 deletions(-)

diff --git a/llvm/lib/CodeGen/CalcSpillWeights.cpp b/llvm/lib/CodeGen/CalcSpillWeights.cpp
index 26d265c2a986e8..81bf22243b06ec 100644
--- a/llvm/lib/CodeGen/CalcSpillWeights.cpp
+++ b/llvm/lib/CodeGen/CalcSpillWeights.cpp
@@ -319,7 +319,11 @@ float VirtRegAuxInfo::weightCalcHelper(LiveInterval &LI) {
 
     // Force Weight onto the stack so that x86 doesn't add hidden precision.
     stack_float_t Weight = 1.0f;
-    if (IsSpillable) {
+    // An inline asm that can access the register in its stack slot needs no
+    // reload or spill for it, so it doesn't add to the spill weight. That makes
+    // the register a better choice to spill than the asm's other operands,
+    // which need registers.
+    if (IsSpillable && !canMemFoldInlineAsm(*MI, LI.reg())) {
       // Get loop info for mi.
       if (MI->getParent() != MBB) {
         MBB = MI->getParent();
diff --git a/llvm/lib/CodeGen/InlineSpiller.cpp b/llvm/lib/CodeGen/InlineSpiller.cpp
index bdcf13266ae5a3..a313852443368c 100644
--- a/llvm/lib/CodeGen/InlineSpiller.cpp
+++ b/llvm/lib/CodeGen/InlineSpiller.cpp
@@ -621,6 +621,12 @@ void InlineSpiller::markValueUsed(LiveInterval *LI, VNInfo *VNI) {
 
 bool llvm::canGuaranteeAssignmentAfterRemat(Register VReg,
                                             const MachineInstr &MI) {
+  // An inline asm that can access VReg in its stack slot (an "rm" operand)
+  // falls back on that when registers run out, which is why VReg is being
+  // spilled. A register rematerialized for it may not be available.
+  if (MI.isInlineAsm() && MI.mayFoldInlineAsmReg(VReg))
+    return false;
+
   if (!RestrictStatepointRemat)
     return true;
   // Here's a quick explanation of the problem we're trying to handle here:
diff --git a/llvm/test/CodeGen/X86/greedy-inline-asm-fold.mir b/llvm/test/CodeGen/X86/greedy-inline-asm-fold.mir
index 311b969a71a3c9..1f870984228c17 100644
--- a/llvm/test/CodeGen/X86/greedy-inline-asm-fold.mir
+++ b/llvm/test/CodeGen/X86/greedy-inline-asm-fold.mir
@@ -117,20 +117,19 @@ body: |
     ; CHECK: liveins: $rdi, $rsi, $rdx, $rcx, $r8, $r9
     ; CHECK-NEXT: {{  $}}
     ; CHECK-NEXT: [[COPY:%[0-9]+]]:gr64 = COPY $rdi
-    ; CHECK-NEXT: MOV64mr %stack.4, 1, $noreg, 0, $noreg, $rsi :: (store (s64) into %stack.4)
+    ; CHECK-NEXT: MOV64mr %stack.3, 1, $noreg, 0, $noreg, $rsi :: (store (s64) into %stack.3)
     ; CHECK-NEXT: MOV64mr %stack.0, 1, $noreg, 0, $noreg, $rdx :: (store (s64) into %stack.0)
     ; CHECK-NEXT: MOV64mr %stack.1, 1, $noreg, 0, $noreg, $rcx :: (store (s64) into %stack.1)
     ; CHECK-NEXT: MOV64mr %stack.2, 1, $noreg, 0, $noreg, $r8 :: (store (s64) into %stack.2)
-    ; CHECK-NEXT: MOV64mr %stack.3, 1, $noreg, 0, $noreg, $r9 :: (store (s64) into %stack.3)
+    ; CHECK-NEXT: [[COPY1:%[0-9]+]]:gr64 = COPY $r9
     ; CHECK-NEXT: [[MOV64rm:%[0-9]+]]:gr64 = MOV64rm %fixed-stack.0, 1, $noreg, 0, $noreg :: (load (s64) from %fixed-stack.0, align 16)
-    ; CHECK-NEXT: [[COPY1:%[0-9]+]]:gr64 = lr-split COPY [[COPY]]
+    ; CHECK-NEXT: [[COPY2:%[0-9]+]]:gr64 = lr-split COPY [[COPY]]
     ; CHECK-NEXT: [[MOV64rm1:%[0-9]+]]:gr64 = MOV64rm %stack.0, 1, $noreg, 0, $noreg :: (load (s64) from %stack.0)
     ; CHECK-NEXT: [[MOV64rm2:%[0-9]+]]:gr64 = MOV64rm %stack.1, 1, $noreg, 0, $noreg :: (load (s64) from %stack.1)
     ; CHECK-NEXT: [[MOV64rm3:%[0-9]+]]:gr64 = MOV64rm %stack.2, 1, $noreg, 0, $noreg :: (load (s64) from %stack.2)
-    ; CHECK-NEXT: [[MOV64rm4:%[0-9]+]]:gr64 = MOV64rm %stack.3, 1, $noreg, 0, $noreg :: (load (s64) from %stack.3)
-    ; CHECK-NEXT: INLINEASM &"", sideeffect mayload maystore attdialect, regdef:GR64 foldable, def [[COPY1]], mem:m ops:5, %stack.4, 1, $noreg, 0, $noreg, reguse:GR64, [[MOV64rm1]], reguse:GR64, [[MOV64rm2]], reguse:GR64, [[MOV64rm3]], reguse:GR64, [[MOV64rm4]], reguse:GR64, [[MOV64rm]], reguse tiedto:$0, [[COPY1]](tied-def 3), mem:m ops:5, %stack.4, 1, $noreg, 0, $noreg, 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.4)
-    ; CHECK-NEXT: $rax = COPY [[COPY1]]
-    ; CHECK-NEXT: $rdx = MOV64rm %stack.4, 1, $noreg, 0, $noreg :: (load (s64) from %stack.4)
+    ; CHECK-NEXT: INLINEASM &"", sideeffect mayload maystore attdialect, regdef:GR64 foldable, def [[COPY2]], mem:m ops:5, %stack.3, 1, $noreg, 0, $noreg, reguse:GR64, [[MOV64rm1]], reguse:GR64, [[MOV64rm2]], reguse:GR64, [[MOV64rm3]], reguse:GR64, [[COPY1]], reguse:GR64, [[MOV64rm]], reguse tiedto:$0, [[COPY2]](tied-def 3), mem:m ops:5, %stack.3, 1, $noreg, 0, $noreg, 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.3)
+    ; CHECK-NEXT: $rax = COPY [[COPY2]]
+    ; CHECK-NEXT: $rdx = MOV64rm %stack.3, 1, $noreg, 0, $noreg :: (load (s64) from %stack.3)
     ; CHECK-NEXT: RET 0, $rax, $rdx
     %0:gr64 = COPY $rdi
     %1:gr64 = COPY $rsi
@@ -167,19 +166,14 @@ body: |
     ; CHECK-LABEL: name: tied_rm_undef
     ; CHECK: liveins: $rdi, $rsi, $rdx, $rcx, $r8, $r9
     ; CHECK-NEXT: {{  $}}
-    ; CHECK-NEXT: MOV64mr %stack.0, 1, $noreg, 0, $noreg, $rdi :: (store (s64) into %stack.0)
-    ; CHECK-NEXT: MOV64mr %stack.1, 1, $noreg, 0, $noreg, $rsi :: (store (s64) into %stack.1)
-    ; CHECK-NEXT: MOV64mr %stack.2, 1, $noreg, 0, $noreg, $rdx :: (store (s64) into %stack.2)
-    ; CHECK-NEXT: MOV64mr %stack.3, 1, $noreg, 0, $noreg, $rcx :: (store (s64) into %stack.3)
-    ; CHECK-NEXT: MOV64mr %stack.4, 1, $noreg, 0, $noreg, $r8 :: (store (s64) into %stack.4)
-    ; CHECK-NEXT: [[COPY:%[0-9]+]]:gr64 = COPY $r9
-    ; CHECK-NEXT: [[MOV64rm:%[0-9]+]]:gr64 = MOV64rm %stack.0, 1, $noreg, 0, $noreg :: (load (s64) from %stack.0)
-    ; CHECK-NEXT: [[MOV64rm1:%[0-9]+]]:gr64 = MOV64rm %stack.1, 1, $noreg, 0, $noreg :: (load (s64) from %stack.1)
-    ; CHECK-NEXT: [[MOV64rm2:%[0-9]+]]:gr64 = MOV64rm %stack.2, 1, $noreg, 0, $noreg :: (load (s64) from %stack.2)
-    ; CHECK-NEXT: [[MOV64rm3:%[0-9]+]]:gr64 = MOV64rm %stack.3, 1, $noreg, 0, $noreg :: (load (s64) from %stack.3)
-    ; CHECK-NEXT: [[MOV64rm4:%[0-9]+]]:gr64 = MOV64rm %stack.4, 1, $noreg, 0, $noreg :: (load (s64) from %stack.4)
-    ; CHECK-NEXT: INLINEASM &"", sideeffect maystore attdialect, mem:m ops:5, %stack.5, 1, $noreg, 0, $noreg, reguse:GR64, [[MOV64rm]], reguse:GR64, [[MOV64rm1]], reguse:GR64, [[MOV64rm2]], reguse:GR64, [[MOV64rm3]], reguse:GR64, [[MOV64rm4]], reguse:GR64, [[COPY]], mem:m ops:5, %stack.5, 1, $noreg, 0, $noreg, 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.5)
-    ; CHECK-NEXT: $rax = MOV64rm %stack.5, 1, $noreg, 0, $noreg :: (load (s64) from %stack.5)
+    ; CHECK-NEXT: [[COPY:%[0-9]+]]:gr64 = COPY $rdi
+    ; CHECK-NEXT: [[COPY1:%[0-9]+]]:gr64 = COPY $rsi
+    ; CHECK-NEXT: [[COPY2:%[0-9]+]]:gr64 = COPY $rdx
+    ; CHECK-NEXT: [[COPY3:%[0-9]+]]:gr64 = COPY $rcx
+    ; CHECK-NEXT: [[COPY4:%[0-9]+]]:gr64 = COPY $r8
+    ; CHECK-NEXT: [[COPY5:%[0-9]+]]:gr64 = COPY $r9
+    ; CHECK-NEXT: INLINEASM &"", sideeffect maystore attdialect, mem:m ops:5, %stack.0, 1, $noreg, 0, $noreg, reguse:GR64, [[COPY]], reguse:GR64, [[COPY1]], reguse:GR64, [[COPY2]], reguse:GR64, [[COPY3]], reguse:GR64, [[COPY4]], reguse:GR64, [[COPY5]], mem:m ops:5, %stack.0, 1, $noreg, 0, $noreg, 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: $rax = MOV64rm %stack.0, 1, $noreg, 0, $noreg :: (load (s64) from %stack.0)
     ; CHECK-NEXT: RET 0, $rax
     %1:gr64 = COPY $rdi
     %2:gr64 = COPY $rsi
@@ -452,3 +446,39 @@ body: |
     INLINEASM &"", sideeffect attdialect, reguse:GR64 foldable, %0, reguse:GR64, undef %0
     RET 0
 ...
+---
+# A constant passed to an "rm" operand is spilled and folded rather than
+# rematerialized into a register, which would need a register that the asm
+# doesn't leave.
+#
+#   void rm_constant(long a, long b, long c, long d, long e, long f) {
+#     asm volatile("" : : "rm"(42L), "r"(a), "r"(b), "r"(c), "r"(d), "r"(e),
+#                         "r"(f) : CLOBBERS);
+#   }
+name: rm_constant
+tracksRegLiveness: true
+body: |
+  bb.0:
+    liveins: $rdi, $rsi, $rdx, $rcx, $r8, $r9
+    ; CHECK-LABEL: name: rm_constant
+    ; CHECK: liveins: $rdi, $rsi, $rdx, $rcx, $r8, $r9
+    ; CHECK-NEXT: {{  $}}
+    ; CHECK-NEXT: [[COPY:%[0-9]+]]:gr64 = COPY $rdi
+    ; CHECK-NEXT: [[COPY1:%[0-9]+]]:gr64 = COPY $rsi
+    ; CHECK-NEXT: [[COPY2:%[0-9]+]]:gr64 = COPY $rdx
+    ; CHECK-NEXT: [[COPY3:%[0-9]+]]:gr64 = COPY $rcx
+    ; CHECK-NEXT: [[COPY4:%[0-9]+]]:gr64 = COPY $r8
+    ; CHECK-NEXT: [[COPY5:%[0-9]+]]:gr64 = COPY $r9
+    ; CHECK-NEXT: MOV64mi32 %stack.0, 1, $noreg, 0, $noreg, 42 :: (store (s64) into %stack.0)
+    ; CHECK-NEXT: INLINEASM &"", sideeffect mayload attdialect, mem:m ops:5, %stack.0, 1, $noreg, 0, $noreg, reguse:GR64, [[COPY]], reguse:GR64, [[COPY1]], reguse:GR64, [[COPY2]], reguse:GR64, [[COPY3]], reguse:GR64, [[COPY4]], reguse:GR64, [[COPY5]], 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
+    %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 = MOV64ri32 42
+    INLINEASM &"", sideeffect attdialect, reguse:GR64 foldable, %6, reguse:GR64, %0, reguse:GR64, %1, reguse:GR64, %2, reguse:GR64, %3, reguse:GR64, %4, reguse:GR64, %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
+    RET 0
+...



More information about the llvm-branch-commits mailing list