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

Clang emitted "=*r" for _Complex register outputs. On a callbr, the 
SelectionDAG stores that output only on the fallthrough path, which would 
silently skip adding a "store" on the indirect paths (#235940 turns that into 
an error). The HandleOutputConstraints method acceps scalar and aggregate 
kinds, but not _Complex.

We fix this by treating a _Complex as an aggregate for this specific case. The 
infrastructure already exists to handle {T, T} structs, so the asm goto 
successor stores come for "free."

Fixes: 235946

Assisted-by: Claude for generating testcases

>From 5f665852cfd45159ce7b9b51daefeb74ea8faa20 Mon Sep 17 00:00:00 2001
From: Bill Wendling <[email protected]>
Date: Thu, 29 Oct 2026 01:49:13 -0700
Subject: [PATCH] [Clang][CodeGen] Add support for _Complex arguments to "asm
 goto"

Clang emitted "=*r" for _Complex register outputs. On a callbr, the
SelectionDAG stores that output only on the fallthrough path, which
would silently skip adding a "store" on the indirect paths (#235940
turns that into an error). The HandleOutputConstraints method acceps
scalar and aggregate kinds, but not _Complex.

We fix this by treating a _Complex as an aggregate for this specific
case. The infrastructure already exists to handle {T, T} structs, so the
asm goto successor stores come for "free."

Fixes: 235946

Assisted-by: Claude for generating testcases
---
 clang/lib/CodeGen/CGStmt.cpp                  |   3 +-
 clang/lib/CodeGen/CodeGenFunction.h           |   4 +
 .../CodeGen/X86/inline-asm-complex-output.c   | 214 ++++++++++++++++++
 3 files changed, 220 insertions(+), 1 deletion(-)
 create mode 100644 clang/test/CodeGen/X86/inline-asm-complex-output.c

diff --git a/clang/lib/CodeGen/CGStmt.cpp b/clang/lib/CodeGen/CGStmt.cpp
index 05737ce071647d..9d744867f81b0a 100644
--- a/clang/lib/CodeGen/CGStmt.cpp
+++ b/clang/lib/CodeGen/CGStmt.cpp
@@ -2941,7 +2941,8 @@ void AsmConstraintsInfo::HandleOutputConstraints() {
     QualType QTy = OutExpr->getType();
     const bool IsScalarOrAggregate =
         CodeGenFunction::hasScalarEvaluationKind(QTy) ||
-        CodeGenFunction::hasAggregateEvaluationKind(QTy);
+        CodeGenFunction::hasAggregateEvaluationKind(QTy) ||
+        CodeGenFunction::hasComplexEvaluationKind(QTy);
 
     if (!Info.allowsMemory() && IsScalarOrAggregate) {
       Constraints += "=" + OutputConstraint;
diff --git a/clang/lib/CodeGen/CodeGenFunction.h 
b/clang/lib/CodeGen/CodeGenFunction.h
index 4cc0a45067a017..f3f4b0d4f1ebfc 100644
--- a/clang/lib/CodeGen/CodeGenFunction.h
+++ b/clang/lib/CodeGen/CodeGenFunction.h
@@ -2746,6 +2746,10 @@ class CodeGenFunction : public CodeGenTypeCache {
     return getEvaluationKind(T) == TEK_Aggregate;
   }
 
+  static bool hasComplexEvaluationKind(QualType T) {
+    return getEvaluationKind(T) == TEK_Complex;
+  }
+
   /// createBasicBlock - Create an LLVM basic block.
   llvm::BasicBlock *createBasicBlock(const Twine &name = "",
                                      llvm::Function *parent = nullptr,
diff --git a/clang/test/CodeGen/X86/inline-asm-complex-output.c 
b/clang/test/CodeGen/X86/inline-asm-complex-output.c
new file mode 100644
index 00000000000000..78ee1f82166cb2
--- /dev/null
+++ b/clang/test/CodeGen/X86/inline-asm-complex-output.c
@@ -0,0 +1,214 @@
+// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py 
UTC_ARGS: --check-globals none --version 6
+// REQUIRES: x86-registered-target
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm %s -o - | 
FileCheck %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -emit-llvm -DERROR -verify 
%s -o /dev/null
+
+// A _Complex output with a register constraint is returned by value as an
+// integer of the same size, like a struct, and not stored through a pointer
+// ("=*r"). An asm goto then stores it on every path, not only the fallthrough.
+
+#ifndef ERROR
+
+// CHECK-LABEL: define dso_local void @test_complex_float(
+// CHECK-SAME: ) #[[ATTR0:[0-9]+]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK-NEXT:    [[C:%.*]] = alloca { float, float }, align 4
+// CHECK-NEXT:    [[TMP0:%.*]] = call i64 asm "", 
"=r,~{dirflag},~{fpsr},~{flags}"() #[[ATTR2:[0-9]+]], !srcloc [[META1:![0-9]+]]
+// CHECK-NEXT:    store i64 [[TMP0]], ptr [[C]], align 4
+// CHECK-NEXT:    ret void
+//
+void test_complex_float(void) {
+  _Complex float c;
+  asm("" : "=r"(c));
+}
+
+// CHECK-LABEL: define dso_local void @test_complex_double(
+// CHECK-SAME: ) #[[ATTR0]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK-NEXT:    [[C:%.*]] = alloca { double, double }, align 8
+// CHECK-NEXT:    [[TMP0:%.*]] = call i128 asm "", 
"=r,~{dirflag},~{fpsr},~{flags}"() #[[ATTR2]], !srcloc [[META2:![0-9]+]]
+// CHECK-NEXT:    store i128 [[TMP0]], ptr [[C]], align 8
+// CHECK-NEXT:    ret void
+//
+void test_complex_double(void) {
+  _Complex double c;
+  asm("" : "=r"(c));
+}
+
+// CHECK-LABEL: define dso_local void @test_complex_char(
+// CHECK-SAME: ) #[[ATTR0]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK-NEXT:    [[C:%.*]] = alloca { i8, i8 }, align 1
+// CHECK-NEXT:    [[TMP0:%.*]] = call i16 asm "", 
"=r,~{dirflag},~{fpsr},~{flags}"() #[[ATTR2]], !srcloc [[META3:![0-9]+]]
+// CHECK-NEXT:    store i16 [[TMP0]], ptr [[C]], align 1
+// CHECK-NEXT:    ret void
+//
+void test_complex_char(void) {
+  _Complex char c;
+  asm("" : "=r"(c));
+}
+
+// CHECK-LABEL: define dso_local void @test_complex_int(
+// CHECK-SAME: ) #[[ATTR0]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK-NEXT:    [[C:%.*]] = alloca { i32, i32 }, align 4
+// CHECK-NEXT:    [[TMP0:%.*]] = call i64 asm "", 
"=r,~{dirflag},~{fpsr},~{flags}"() #[[ATTR2]], !srcloc [[META4:![0-9]+]]
+// CHECK-NEXT:    store i64 [[TMP0]], ptr [[C]], align 4
+// CHECK-NEXT:    ret void
+//
+void test_complex_int(void) {
+  _Complex int c;
+  asm("" : "=r"(c));
+}
+
+// CHECK-LABEL: define dso_local void @test_complex_float_sse(
+// CHECK-SAME: ) #[[ATTR0]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK-NEXT:    [[C:%.*]] = alloca { float, float }, align 4
+// CHECK-NEXT:    [[TMP0:%.*]] = call i64 asm "", 
"=x,~{dirflag},~{fpsr},~{flags}"() #[[ATTR2]], !srcloc [[META5:![0-9]+]]
+// CHECK-NEXT:    store i64 [[TMP0]], ptr [[C]], align 4
+// CHECK-NEXT:    ret void
+//
+void test_complex_float_sse(void) {
+  _Complex float c;
+  asm("" : "=x"(c));
+}
+
+// CHECK-LABEL: define dso_local void @test_complex_float_inout(
+// CHECK-SAME: ) #[[ATTR0]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK-NEXT:    [[C:%.*]] = alloca { float, float }, align 4
+// CHECK-NEXT:    [[C_REALP:%.*]] = getelementptr inbounds nuw { float, float 
}, ptr [[C]], i32 0, i32 0
+// CHECK-NEXT:    [[C_IMAGP:%.*]] = getelementptr inbounds nuw { float, float 
}, ptr [[C]], i32 0, i32 1
+// CHECK-NEXT:    store float 1.000000e+00, ptr [[C_REALP]], align 4
+// CHECK-NEXT:    store float 0.000000e+00, ptr [[C_IMAGP]], align 4
+// CHECK-NEXT:    [[TMP0:%.*]] = load i64, ptr [[C]], align 4
+// CHECK-NEXT:    [[TMP1:%.*]] = call i64 asm "", 
"=r,0,~{dirflag},~{fpsr},~{flags}"(i64 [[TMP0]]) #[[ATTR2]], !srcloc 
[[META6:![0-9]+]]
+// CHECK-NEXT:    store i64 [[TMP1]], ptr [[C]], align 4
+// CHECK-NEXT:    ret void
+//
+void test_complex_float_inout(void) {
+  _Complex float c = 1;
+  asm("" : "+r"(c));
+}
+
+// A memory constraint still passes a pointer.
+// CHECK-LABEL: define dso_local void @test_complex_float_mem(
+// CHECK-SAME: ) #[[ATTR0]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK-NEXT:    [[C:%.*]] = alloca { float, float }, align 4
+// CHECK-NEXT:    call void asm "", "=*m,~{dirflag},~{fpsr},~{flags}"(ptr 
elementtype({ float, float }) [[C]]) #[[ATTR3:[0-9]+]], !srcloc 
[[META7:![0-9]+]]
+// CHECK-NEXT:    ret void
+//
+void test_complex_float_mem(void) {
+  _Complex float c;
+  asm("" : "=m"(c));
+}
+
+// CHECK-LABEL: define dso_local <2 x float> @test_complex_float_goto(
+// CHECK-SAME: ) #[[ATTR1:[0-9]+]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK-NEXT:    [[RETVAL:%.*]] = alloca { float, float }, align 4
+// CHECK-NEXT:    [[C:%.*]] = alloca { float, float }, align 4
+// CHECK-NEXT:    [[C_REALP:%.*]] = getelementptr inbounds nuw { float, float 
}, ptr [[C]], i32 0, i32 0
+// CHECK-NEXT:    [[C_IMAGP:%.*]] = getelementptr inbounds nuw { float, float 
}, ptr [[C]], i32 0, i32 1
+// CHECK-NEXT:    store float 0.000000e+00, ptr [[C_REALP]], align 4
+// CHECK-NEXT:    store float 0.000000e+00, ptr [[C_IMAGP]], align 4
+// CHECK-NEXT:    [[TMP0:%.*]] = callbr i64 asm "", 
"=r,!i,~{dirflag},~{fpsr},~{flags}"() #[[ATTR2]]
+// CHECK-NEXT:            to label %[[ASM_FALLTHROUGH:.*]] [label 
%[[LABEL_SPLIT:.*]]], !srcloc [[META8:![0-9]+]]
+// CHECK:       [[ASM_FALLTHROUGH]]:
+// CHECK-NEXT:    store i64 [[TMP0]], ptr [[C]], align 4
+// CHECK-NEXT:    [[C_REALP1:%.*]] = getelementptr inbounds nuw { float, float 
}, ptr [[C]], i32 0, i32 0
+// CHECK-NEXT:    [[C_REAL:%.*]] = load float, ptr [[C_REALP1]], align 4
+// CHECK-NEXT:    [[C_IMAGP2:%.*]] = getelementptr inbounds nuw { float, float 
}, ptr [[C]], i32 0, i32 1
+// CHECK-NEXT:    [[C_IMAG:%.*]] = load float, ptr [[C_IMAGP2]], align 4
+// CHECK-NEXT:    [[RETVAL_REALP:%.*]] = getelementptr inbounds nuw { float, 
float }, ptr [[RETVAL]], i32 0, i32 0
+// CHECK-NEXT:    [[RETVAL_IMAGP:%.*]] = getelementptr inbounds nuw { float, 
float }, ptr [[RETVAL]], i32 0, i32 1
+// CHECK-NEXT:    store float [[C_REAL]], ptr [[RETVAL_REALP]], align 4
+// CHECK-NEXT:    store float [[C_IMAG]], ptr [[RETVAL_IMAGP]], align 4
+// CHECK-NEXT:    br label %[[RETURN:.*]]
+// CHECK:       [[LABEL_SPLIT]]:
+// CHECK-NEXT:    store i64 [[TMP0]], ptr [[C]], align 4
+// CHECK-NEXT:    br label %[[LABEL:.*]]
+// CHECK:       [[LABEL]]:
+// CHECK-NEXT:    [[C_REALP3:%.*]] = getelementptr inbounds nuw { float, float 
}, ptr [[C]], i32 0, i32 0
+// CHECK-NEXT:    [[C_REAL4:%.*]] = load float, ptr [[C_REALP3]], align 4
+// CHECK-NEXT:    [[C_IMAGP5:%.*]] = getelementptr inbounds nuw { float, float 
}, ptr [[C]], i32 0, i32 1
+// CHECK-NEXT:    [[C_IMAG6:%.*]] = load float, ptr [[C_IMAGP5]], align 4
+// CHECK-NEXT:    [[NEG_R:%.*]] = fneg float [[C_REAL4]]
+// CHECK-NEXT:    [[NEG_I:%.*]] = fneg float [[C_IMAG6]]
+// CHECK-NEXT:    [[RETVAL_REALP7:%.*]] = getelementptr inbounds nuw { float, 
float }, ptr [[RETVAL]], i32 0, i32 0
+// CHECK-NEXT:    [[RETVAL_IMAGP8:%.*]] = getelementptr inbounds nuw { float, 
float }, ptr [[RETVAL]], i32 0, i32 1
+// CHECK-NEXT:    store float [[NEG_R]], ptr [[RETVAL_REALP7]], align 4
+// CHECK-NEXT:    store float [[NEG_I]], ptr [[RETVAL_IMAGP8]], align 4
+// CHECK-NEXT:    br label %[[RETURN]]
+// CHECK:       [[RETURN]]:
+// CHECK-NEXT:    [[TMP1:%.*]] = load <2 x float>, ptr [[RETVAL]], align 4
+// CHECK-NEXT:    ret <2 x float> [[TMP1]]
+//
+_Complex float test_complex_float_goto(void) {
+  _Complex float c = 0;
+  asm goto("" : "=r"(c) : : : label);
+  return c;
+label:
+  return -c;
+}
+
+// CHECK-LABEL: define dso_local { double, double } @test_complex_double_goto(
+// CHECK-SAME: ) #[[ATTR0]] {
+// CHECK-NEXT:  [[ENTRY:.*:]]
+// CHECK-NEXT:    [[RETVAL:%.*]] = alloca { double, double }, align 8
+// CHECK-NEXT:    [[C:%.*]] = alloca { double, double }, align 8
+// CHECK-NEXT:    [[C_REALP:%.*]] = getelementptr inbounds nuw { double, 
double }, ptr [[C]], i32 0, i32 0
+// CHECK-NEXT:    [[C_IMAGP:%.*]] = getelementptr inbounds nuw { double, 
double }, ptr [[C]], i32 0, i32 1
+// CHECK-NEXT:    store double 0.000000e+00, ptr [[C_REALP]], align 8
+// CHECK-NEXT:    store double 0.000000e+00, ptr [[C_IMAGP]], align 8
+// CHECK-NEXT:    [[TMP0:%.*]] = callbr i128 asm "", 
"=r,!i,~{dirflag},~{fpsr},~{flags}"() #[[ATTR2]]
+// CHECK-NEXT:            to label %[[ASM_FALLTHROUGH:.*]] [label 
%[[LABEL_SPLIT:.*]]], !srcloc [[META9:![0-9]+]]
+// CHECK:       [[ASM_FALLTHROUGH]]:
+// CHECK-NEXT:    store i128 [[TMP0]], ptr [[C]], align 8
+// CHECK-NEXT:    [[C_REALP1:%.*]] = getelementptr inbounds nuw { double, 
double }, ptr [[C]], i32 0, i32 0
+// CHECK-NEXT:    [[C_REAL:%.*]] = load double, ptr [[C_REALP1]], align 8
+// CHECK-NEXT:    [[C_IMAGP2:%.*]] = getelementptr inbounds nuw { double, 
double }, ptr [[C]], i32 0, i32 1
+// CHECK-NEXT:    [[C_IMAG:%.*]] = load double, ptr [[C_IMAGP2]], align 8
+// CHECK-NEXT:    [[RETVAL_REALP:%.*]] = getelementptr inbounds nuw { double, 
double }, ptr [[RETVAL]], i32 0, i32 0
+// CHECK-NEXT:    [[RETVAL_IMAGP:%.*]] = getelementptr inbounds nuw { double, 
double }, ptr [[RETVAL]], i32 0, i32 1
+// CHECK-NEXT:    store double [[C_REAL]], ptr [[RETVAL_REALP]], align 8
+// CHECK-NEXT:    store double [[C_IMAG]], ptr [[RETVAL_IMAGP]], align 8
+// CHECK-NEXT:    br label %[[RETURN:.*]]
+// CHECK:       [[LABEL_SPLIT]]:
+// CHECK-NEXT:    store i128 [[TMP0]], ptr [[C]], align 8
+// CHECK-NEXT:    br label %[[LABEL:.*]]
+// CHECK:       [[LABEL]]:
+// CHECK-NEXT:    [[C_REALP3:%.*]] = getelementptr inbounds nuw { double, 
double }, ptr [[C]], i32 0, i32 0
+// CHECK-NEXT:    [[C_REAL4:%.*]] = load double, ptr [[C_REALP3]], align 8
+// CHECK-NEXT:    [[C_IMAGP5:%.*]] = getelementptr inbounds nuw { double, 
double }, ptr [[C]], i32 0, i32 1
+// CHECK-NEXT:    [[C_IMAG6:%.*]] = load double, ptr [[C_IMAGP5]], align 8
+// CHECK-NEXT:    [[NEG_R:%.*]] = fneg double [[C_REAL4]]
+// CHECK-NEXT:    [[NEG_I:%.*]] = fneg double [[C_IMAG6]]
+// CHECK-NEXT:    [[RETVAL_REALP7:%.*]] = getelementptr inbounds nuw { double, 
double }, ptr [[RETVAL]], i32 0, i32 0
+// CHECK-NEXT:    [[RETVAL_IMAGP8:%.*]] = getelementptr inbounds nuw { double, 
double }, ptr [[RETVAL]], i32 0, i32 1
+// CHECK-NEXT:    store double [[NEG_R]], ptr [[RETVAL_REALP7]], align 8
+// CHECK-NEXT:    store double [[NEG_I]], ptr [[RETVAL_IMAGP8]], align 8
+// CHECK-NEXT:    br label %[[RETURN]]
+// CHECK:       [[RETURN]]:
+// CHECK-NEXT:    [[TMP1:%.*]] = load { double, double }, ptr [[RETVAL]], 
align 8
+// CHECK-NEXT:    ret { double, double } [[TMP1]]
+//
+_Complex double test_complex_double_goto(void) {
+  _Complex double c = 0;
+  asm goto("" : "=r"(c) : : : label);
+  return c;
+label:
+  return -c;
+}
+
+#else
+
+// No integer type is 256 bits wide.
+void test_complex_long_double(void) {
+  _Complex long double c;
+  asm("" : "=r"(c)); // expected-error {{impossible constraint in asm: cannot 
store value into a register}}
+}
+
+#endif

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to