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