[llvm-branch-commits] [llvm] [CodeGen] Report an error for a direct inline asm output in memory (PR #229628)

Bill Wendling via llvm-branch-commits llvm-branch-commits at lists.llvm.org
Fri Oct 16 01:55:52 PDT 2026


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

>From 095f83b8a7422ee2bd8869dea9fac1ab0e3bf8ce 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 1/2] [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 b595de3734bb9f..e994b6af23eb04 100644
--- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
@@ -10504,8 +10504,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,
@@ -10567,9 +10568,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(),
@@ -10582,6 +10588,8 @@ computeConstraintToUse(ConstraintDecisionInfo &Info, const CallBase &Call,
       OpInfo.isIndirect = true;
     }
   }
+
+  return false;
 }
 
 /// Prepare DAG-level operands. As part of this, assign virtual and physical
@@ -10875,7 +10883,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 8a0c12bc816aca775c88d485dec185c1c3106a06 Mon Sep 17 00:00:00 2001
From: Bill Wendling <isanbard at gmail.com>
Date: Thu, 15 Oct 2026 18:20:52 -0700
Subject: [PATCH 2/2] [CodeGen] Address review: error for "=m|r" direct outputs
 too

A direct output whose chosen alternative is memory, e.g. "=m|r", still
crashed SelectionDAG, which exempted multiple-alternative operands from the
check and then asked for the address of an output with no value. Drop the
exemption and test "=m|r" with both instruction selectors. Name the
selector explicitly in the RUN lines rather than relying on -O levels,
since X86 GlobalISel can't lower inline asm and both X86 lines ran
SelectionDAG anyway. Drop "yet" from the error: Clang never emits a
direct memory output, so there are no plans to support one.

Assisted-by: Claude Opus 5.5
---
 .../CodeGen/GlobalISel/InlineAsmLowering.cpp  |  2 +-
 .../SelectionDAG/SelectionDAGBuilder.cpp      |  9 +++----
 .../inline-asm-direct-mem-output-error.ll     | 14 ++++++++---
 .../X86/inline-asm-direct-mem-output-error.ll | 24 +++++++++++--------
 4 files changed, 31 insertions(+), 18 deletions(-)

diff --git a/llvm/lib/CodeGen/GlobalISel/InlineAsmLowering.cpp b/llvm/lib/CodeGen/GlobalISel/InlineAsmLowering.cpp
index 5d66cb5e332276..210f693f7bf583 100644
--- a/llvm/lib/CodeGen/GlobalISel/InlineAsmLowering.cpp
+++ b/llvm/lib/CodeGen/GlobalISel/InlineAsmLowering.cpp
@@ -340,7 +340,7 @@ bool InlineAsmLowering::lowerInlineAsm(
         // 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 "
+                             "cannot handle direct memory outputs for "
                              "constraint '" +
                                  Twine(OpInfo.ConstraintCode) + "'",
                              GetOrCreateVRegs(Call));
diff --git a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
index e994b6af23eb04..c15e96738752cc 100644
--- a/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
+++ b/llvm/lib/CodeGen/SelectionDAG/SelectionDAGBuilder.cpp
@@ -10570,10 +10570,11 @@ computeConstraintToUse(ConstraintDecisionInfo &Info, const CallBase &Call,
         !OpInfo.isIndirect) {
       // 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 << "'";
+      // need a stack slot to be reloaded after the asm. That includes an
+      // output whose chosen alternative is memory, e.g. "=m|r".
+      if (OpInfo.Type != InlineAsm::isInput) {
+        Info.ErrorMsg << "cannot handle direct memory outputs for constraint '"
+                      << OpInfo.ConstraintCode << "'";
         return true;
       }
 
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
index 7ebe9f006943f9..12aa4a46da9989 100644
--- a/llvm/test/CodeGen/AArch64/inline-asm-direct-mem-output-error.ll
+++ b/llvm/test/CodeGen/AArch64/inline-asm-direct-mem-output-error.ll
@@ -1,13 +1,21 @@
-; RUN: not llc -mtriple=aarch64 -filetype=null < %s 2>&1 | FileCheck %s
+; RUN: not llc -mtriple=aarch64 -global-isel=0 -filetype=null < %s 2>&1 \
+; RUN:   | FileCheck --implicit-check-not=error: %s
 ; RUN: not llc -mtriple=aarch64 -global-isel -global-isel-abort=1 \
-; RUN:   -filetype=null < %s 2>&1 | FileCheck %s
+; RUN:   -filetype=null < %s 2>&1 | FileCheck --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, with either
 ; instruction selector.
 
-; CHECK: error: cannot handle direct memory outputs yet for constraint 'm'
+; CHECK: error: cannot handle direct memory outputs for constraint 'm'
 define i64 @rm_output() {
   %r = call i64 asm "# $0", "=rm"()
   ret i64 %r
 }
+
+; With multiple alternatives, the memory one is chosen here, too.
+; CHECK: error: cannot handle direct memory outputs for constraint 'm'
+define i64 @multi_alt() {
+  %r = call i64 asm "# $0", "=m|r"()
+  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
index 3f3a9168de38f8..00135acfdc4769 100644
--- a/llvm/test/CodeGen/X86/inline-asm-direct-mem-output-error.ll
+++ b/llvm/test/CodeGen/X86/inline-asm-direct-mem-output-error.ll
@@ -1,31 +1,35 @@
-; 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
+; X86 GlobalISel can't lower inline asm, so only SelectionDAG is tested here;
+; see the AArch64 test for both instruction selectors.
+; RUN: not llc -mtriple=x86_64-unknown-linux-gnu -global-isel=0 \
+; RUN:   -filetype=null < %s 2>&1 | FileCheck --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'
+; CHECK: error: cannot handle direct memory outputs 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'
+; CHECK: error: cannot handle direct memory outputs 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'
+; CHECK: error: cannot handle direct memory outputs for constraint 'm'
 define i32 @m_i32() {
   %r = call i32 asm "# $0", "=m"()
   ret i32 %r
 }
+
+; With multiple alternatives, the memory one is chosen here, too.
+; CHECK: error: cannot handle direct memory outputs for constraint 'm'
+define i32 @multi_alt() {
+  %r = call i32 asm "# $0", "=m|r"()
+  ret i32 %r
+}



More information about the llvm-branch-commits mailing list