[llvm] [CodeGen][ISel] Fix inline assembly crash during instruction selection with direct memory output constraint (PR #209439)

Chibuoyim Ogbonna via llvm-commits llvm-commits at lists.llvm.org
Tue Jul 14 04:01:10 PDT 2026


https://github.com/bruteforceboy created https://github.com/llvm/llvm-project/pull/209439

This attempts to close [#207440](https://github.com/llvm/llvm-project/issues/207440).

A direct output is made available as the asm's return value and consumes no call argument, but a memory output writes through an address supplied as an input argument, and so must be spelled indirectly (`"=*m"`) ([LangRef](https://llvm.org/docs/LangRef.html#indirect-inputs-and-outputs)).

So, a memory constraint on a direct output has no operand naming the memory to write through. This wasn't enforced, so a direct output ended up with a memory constraint which caused both GlobalISel and SelectionDAG to crash.

For a direct memory output, this patch:
- `"=rm"`: drops the memory/address alternatives, so the register alternative is selected and the asm compiles.
- `"=m"`: memory is the only alternative, so there is nothing to select. It is kept so that lowering can report an error instead of crashing.

Note: clang doesn't emit a direct memory output, so this mostly only affects IR that previously crashed.

Assisted-by: Claude Opus 4.8

>From 3e9770841315eb184b4e5bf65a2322d5ea7d5857 Mon Sep 17 00:00:00 2001
From: workwilson <ogbonnachibuoyim12 at gmail.com>
Date: Tue, 14 Jul 2026 18:49:31 +0800
Subject: [PATCH] [CodeGen][ISel] Fix inline assembly crash during instruction
 selection with direct memory output constraint

---
 .../CodeGen/GlobalISel/InlineAsmLowering.cpp  | 13 +++++
 .../SelectionDAG/SelectionDAGBuilder.cpp      | 24 +++++++---
 .../CodeGen/SelectionDAG/TargetLowering.cpp   | 15 ++++++
 .../inline-asm-direct-mem-output-error.ll     | 14 ++++++
 .../AArch64/inline-asm-direct-mem-output.ll   | 48 +++++++++++++++++++
 .../X86/inline-asm-direct-mem-output-error.ll | 13 +++++
 .../X86/inline-asm-direct-mem-output.ll       | 36 ++++++++++++++
 7 files changed, 157 insertions(+), 6 deletions(-)
 create mode 100644 llvm/test/CodeGen/AArch64/inline-asm-direct-mem-output-error.ll
 create mode 100644 llvm/test/CodeGen/AArch64/inline-asm-direct-mem-output.ll
 create mode 100644 llvm/test/CodeGen/X86/inline-asm-direct-mem-output-error.ll
 create mode 100644 llvm/test/CodeGen/X86/inline-asm-direct-mem-output.ll

diff --git a/llvm/lib/CodeGen/GlobalISel/InlineAsmLowering.cpp b/llvm/lib/CodeGen/GlobalISel/InlineAsmLowering.cpp
index b5bbcc193b6b7..9c939e24db1dc 100644
--- a/llvm/lib/CodeGen/GlobalISel/InlineAsmLowering.cpp
+++ b/llvm/lib/CodeGen/GlobalISel/InlineAsmLowering.cpp
@@ -333,6 +333,19 @@ bool InlineAsmLowering::lowerInlineAsm(
     switch (OpInfo.Type) {
     case InlineAsm::isOutput:
       if (OpInfo.ConstraintType == TargetLowering::C_Memory) {
+        // A memory output writes through an address passed to the asm, so it
+        // only has somewhere to write to if it is indirect (e.g. "=*m"). A
+        // direct memory output has no operand to name the memory, and no
+        // register alternative to fall back on.
+        if (!OpInfo.isIndirect) {
+          emitInlineAsmError(MIRBuilder, Call,
+                             "memory output constraint '" +
+                                 Twine(OpInfo.ConstraintCode) +
+                                 "' must be indirect",
+                             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 98c91e65b4752..94cb06cb52f97 100644
--- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
@@ -10386,8 +10386,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 if an
+/// error was encountered, in which case Info.ErrorMsg describes it.
+static bool
 computeConstraintToUse(ConstraintDecisionInfo &Info, const CallBase &Call,
                        TargetLowering::AsmOperandInfoVector &TargetConstraints,
                        SelectionDAGBuilder &Builder, const TargetLowering &TLI,
@@ -10449,9 +10450,16 @@ 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 can be indirectified: it has a value whose address we can
+      // take. A direct output becomes the result of the asm and has no operand
+      // naming memory to write through. Constraint selection already avoids
+      // memory for a direct output when a register alternative exists (e.g.
+      // "=rm"), so getting here means memory was the only choice (e.g. "=m").
+      if (OpInfo.Type != InlineAsm::isInput) {
+        Info.ErrorMsg << "memory output constraint '" << OpInfo.ConstraintCode
+                      << "' must be indirect";
+        return true;
+      }
 
       // Memory operands really want the address of the value.
       Info.Chain = getAddressForMemoryInput(Info.Chain, Builder.getCurSDLoc(),
@@ -10464,6 +10472,8 @@ computeConstraintToUse(ConstraintDecisionInfo &Info, const CallBase &Call,
       OpInfo.isIndirect = true;
     }
   }
+
+  return false;
 }
 
 /// Prepare DAG-level operands. As part of this, assign virtual and physical
@@ -10757,7 +10767,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/lib/CodeGen/SelectionDAG/TargetLowering.cpp b/llvm/lib/CodeGen/SelectionDAG/TargetLowering.cpp
index bca34c5c347ee..5cada45bd7d2f 100644
--- a/llvm/lib/CodeGen/SelectionDAG/TargetLowering.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/TargetLowering.cpp
@@ -6416,6 +6416,21 @@ TargetLowering::ConstraintGroup TargetLowering::getConstraintPreferences(
     Ret.emplace_back(Code, CType);
   }
 
+  // A direct output becomes the result of the asm rather than being written
+  // through an address supplied by the caller, so it has to live in a
+  // register. Prefer the other alternatives, if any (e.g. "r" in "=rm").
+  // If memory is the only option (e.g. "=m"), keep it so that lowering can
+  // diagnose the constraint; memory outputs must be spelled indirectly
+  // (e.g. "=*m").
+  if (OpInfo.Type == InlineAsm::isOutput && !OpInfo.isIndirect) {
+    auto IsMemOrAddr = [](const ConstraintPair &P) {
+      return P.second == TargetLowering::C_Memory ||
+             P.second == TargetLowering::C_Address;
+    };
+    if (!llvm::all_of(Ret, IsMemOrAddr))
+      llvm::erase_if(Ret, IsMemOrAddr);
+  }
+
   llvm::stable_sort(Ret, [](ConstraintPair a, ConstraintPair b) {
     return getConstraintPiority(a.second) > getConstraintPiority(b.second);
   });
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 0000000000000..4f292d2d4105c
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/inline-asm-direct-mem-output-error.ll
@@ -0,0 +1,14 @@
+; RUN: not llc -mtriple=aarch64-unknown-linux-gnu < %s 2>&1 | FileCheck %s
+; RUN: not llc -mtriple=aarch64-unknown-linux-gnu -global-isel < %s 2>&1 | FileCheck %s
+
+; CHECK: error: memory output constraint 'm' must be indirect
+define i64 @direct_m_output() {
+  %v = call i64 asm "", "=m"()
+  ret i64 %v
+}
+
+; CHECK: error: memory output constraint '{{[mo]}}' must be indirect
+define i64 @direct_mo_output() {
+  %v = call i64 asm "", "=mo"()
+  ret i64 %v
+}
diff --git a/llvm/test/CodeGen/AArch64/inline-asm-direct-mem-output.ll b/llvm/test/CodeGen/AArch64/inline-asm-direct-mem-output.ll
new file mode 100644
index 0000000000000..91baf51047b2e
--- /dev/null
+++ b/llvm/test/CodeGen/AArch64/inline-asm-direct-mem-output.ll
@@ -0,0 +1,48 @@
+; RUN: llc -mtriple=aarch64-unknown-linux-gnu < %s | FileCheck %s
+; RUN: llc -mtriple=aarch64-unknown-linux-gnu -global-isel < %s | FileCheck %s
+
+define i64 @direct_rm_output() {
+; CHECK-LABEL: direct_rm_output:
+; CHECK:       // %bb.0:
+; CHECK-NEXT:    //APP
+; CHECK-NEXT:    mov x0, #42 // =0x2a
+; CHECK-NEXT:    //NO_APP
+; CHECK-NEXT:    ret
+  %v = call i64 asm "mov $0, #42", "=rm"()
+  ret i64 %v
+}
+
+define i64 @direct_rm_output_used(i64 %x) {
+; CHECK-LABEL: direct_rm_output_used:
+; CHECK:       // %bb.0:
+; CHECK-NEXT:    //APP
+; CHECK-NEXT:    mov x8, #42 // =0x2a
+; CHECK-NEXT:    //NO_APP
+; CHECK-NEXT:    add x0, x8, x0
+; CHECK-NEXT:    ret
+  %v = call i64 asm "mov $0, #42", "=rm"()
+  %s = add i64 %v, %x
+  ret i64 %s
+}
+
+define i64 @direct_rm_output_tied(i64 %x) {
+; CHECK-LABEL: direct_rm_output_tied:
+; CHECK:       // %bb.0:
+; CHECK-NEXT:    //APP
+; CHECK-NEXT:    add x0, x0, #1
+; CHECK-NEXT:    //NO_APP
+; CHECK-NEXT:    ret
+  %v = call i64 asm "add $0, $0, #1", "=rm,0"(i64 %x)
+  ret i64 %v
+}
+
+define void @indirect_m_output(ptr %p) {
+; CHECK-LABEL: indirect_m_output:
+; CHECK:       // %bb.0:
+; CHECK-NEXT:    //APP
+; CHECK-NEXT:    str xzr, [x0]
+; CHECK-NEXT:    //NO_APP
+; CHECK-NEXT:    ret
+  call void asm "str xzr, $0", "=*m"(ptr elementtype(i64) %p)
+  ret void
+}
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 0000000000000..121fa8729e4c8
--- /dev/null
+++ b/llvm/test/CodeGen/X86/inline-asm-direct-mem-output-error.ll
@@ -0,0 +1,13 @@
+; RUN: not llc -mtriple=x86_64-unknown-linux-gnu < %s 2>&1 | FileCheck %s
+
+; CHECK: error: memory output constraint 'm' must be indirect
+define i64 @direct_m_output() {
+  %v = call i64 asm "", "=m"()
+  ret i64 %v
+}
+
+; CHECK: error: memory output constraint '{{[mo]}}' must be indirect
+define i64 @direct_mo_output() {
+  %v = call i64 asm "", "=mo"()
+  ret i64 %v
+}
diff --git a/llvm/test/CodeGen/X86/inline-asm-direct-mem-output.ll b/llvm/test/CodeGen/X86/inline-asm-direct-mem-output.ll
new file mode 100644
index 0000000000000..e4bf1eba56e4b
--- /dev/null
+++ b/llvm/test/CodeGen/X86/inline-asm-direct-mem-output.ll
@@ -0,0 +1,36 @@
+; RUN: llc -mtriple=x86_64-unknown-linux-gnu < %s | FileCheck %s
+
+define i64 @direct_rm_output() {
+; CHECK-LABEL: direct_rm_output:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    #APP
+; CHECK-NEXT:    movq $42, %rax
+; CHECK-NEXT:    #NO_APP
+; CHECK-NEXT:    retq
+  %v = call i64 asm "movq $$42, $0", "=rm"()
+  ret i64 %v
+}
+
+define i64 @direct_rm_output_used(i64 %x) {
+; CHECK-LABEL: direct_rm_output_used:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    #APP
+; CHECK-NEXT:    movq $42, %rax
+; CHECK-NEXT:    #NO_APP
+; CHECK-NEXT:    addq %rdi, %rax
+; CHECK-NEXT:    retq
+  %v = call i64 asm "movq $$42, $0", "=rm"()
+  %s = add i64 %v, %x
+  ret i64 %s
+}
+
+define void @indirect_m_output(ptr %p) {
+; CHECK-LABEL: indirect_m_output:
+; CHECK:       # %bb.0:
+; CHECK-NEXT:    #APP
+; CHECK-NEXT:    movq $0, (%rdi)
+; CHECK-NEXT:    #NO_APP
+; CHECK-NEXT:    retq
+  call void asm "movq $$0, $0", "=*m"(ptr elementtype(i64) %p)
+  ret void
+}



More information about the llvm-commits mailing list