[clang] [C99] Claim conformance for _Complex support (PR #88161)

Aaron Ballman via cfe-commits cfe-commits at lists.llvm.org
Thu Apr 11 09:25:40 PDT 2024


https://github.com/AaronBallman updated https://github.com/llvm/llvm-project/pull/88161

>From 9639b31b6c9240a944fbc6b616bc9053a35270f2 Mon Sep 17 00:00:00 2001
From: Aaron Ballman <aaron at aaronballman.com>
Date: Tue, 9 Apr 2024 13:30:14 -0400
Subject: [PATCH 1/7] [C99] Claim conformance for _Complex support

There's so much overlap between the cited papers so this condenses the
status page into a single entry rather than trying to test conformance
against multiple papers doing conflicting things.
---
 clang/test/C/C99/n809.c | 97 +++++++++++++++++++++++++++++++++++++++++
 clang/www/c_status.html | 30 ++++---------
 2 files changed, 105 insertions(+), 22 deletions(-)
 create mode 100644 clang/test/C/C99/n809.c

diff --git a/clang/test/C/C99/n809.c b/clang/test/C/C99/n809.c
new file mode 100644
index 00000000000000..8d26744443eec4
--- /dev/null
+++ b/clang/test/C/C99/n809.c
@@ -0,0 +1,97 @@
+// RUN: %clang_cc1 -verify -std=c99 %s
+
+/* WG14 N620, N638, N657, N694, N809: Yes*
+ * Complex and imaginary support in <complex.h>
+ *
+ * NB: Clang supports _Complex but not _Imaginary. In C99, _Complex support is
+ * required outside of freestanding, but _Imaginary support is fully optional.
+ * In C11, both are made fully optional. We claim full conformance because we
+ * are actually conforming, but this gets an asterisk because it's also only
+ * partially implemented in a way and users should know about that.
+ *
+ * Because the functionality is so intertwined between the various papers,
+ * we're testing all of the functionality in one file.
+ */
+
+// Demonstrate that we support spelling complex floating-point objects.
+float _Complex f1;
+_Complex float f2;
+
+double _Complex d1;
+_Complex double d2;
+
+long double _Complex ld1;
+_Complex long double ld2;
+
+// Show that we don't support spelling imaginary types.
+float _Imaginary fi1; // expected-error {{imaginary types are not supported}}
+_Imaginary float fi2; // expected-error {{imaginary types are not supported}}
+
+double _Imaginary di1; // expected-error {{imaginary types are not supported}}
+_Imaginary double di2; // expected-error {{imaginary types are not supported}}
+
+long double _Imaginary ldi1; // expected-error {{imaginary types are not supported}}
+_Imaginary long double ldi2; // expected-error {{imaginary types are not supported}}
+
+// Each complex type has the same representation and alignment as an array
+// containing two elements of the corresponding real type.
+_Static_assert(sizeof(float _Complex) == sizeof(struct { float mem[2]; }), "");
+_Static_assert(_Alignof(float _Complex) == _Alignof(struct { float mem[2]; }), "");
+
+_Static_assert(sizeof(double _Complex) == sizeof(struct { double mem[2]; }), "");
+_Static_assert(_Alignof(double _Complex) == _Alignof(struct { double mem[2]; }), "");
+
+_Static_assert(sizeof(long double _Complex) == sizeof(struct { long double mem[2]; }), "");
+_Static_assert(_Alignof(long double _Complex) == _Alignof(struct { long double mem[2]; }), "");
+
+// The first element corresponds to the real part and the second element
+// corresponds to the imaginary part.
+_Static_assert(__real((float _Complex){ 1.0f, 2.0f }) == 1.0f, "");
+_Static_assert(__imag((float _Complex){ 1.0f, 2.0f }) == 2.0f, "");
+
+_Static_assert(__real((double _Complex){ 1.0, 2.0 }) == 1.0, "");
+_Static_assert(__imag((double _Complex){ 1.0, 2.0 }) == 2.0, "");
+
+_Static_assert(__real((long double _Complex){ 1.0L, 2.0L }) == 1.0L, "");
+_Static_assert(__imag((long double _Complex){ 1.0L, 2.0L }) == 2.0L, "");
+
+// When a real value is converted to a complex value, the real part follows the
+// usual conversion rules and the imaginary part should be zero.
+_Static_assert(__real((float _Complex)1.0f) == 1.0f, "");
+_Static_assert(__imag((float _Complex)1.0f) == 0.0f, "");
+
+_Static_assert(__real((double _Complex)1.0f) == 1.0, "");
+_Static_assert(__imag((double _Complex)1.0f) == 0.0, "");
+
+_Static_assert(__real((long double _Complex)1.0f) == 1.0L, "");
+_Static_assert(__imag((long double _Complex)1.0f) == 0.0L, "");
+
+// When a complex value is converted to a real value, the real part follows the
+// usual conversion rules and the imaginary part is discarded.
+_Static_assert((float)(float _Complex){ 1.0f, 2.0f } == 1.0f, "");
+_Static_assert((double)(float _Complex){ 1.0f, 2.0f } == 1.0, "");
+_Static_assert((long double)(float _Complex){ 1.0f, 2.0f } == 1.0L, "");
+
+// Complex values are only equal if both the real and imaginary parts are equal.
+_Static_assert((float _Complex){ 1.0f, 2.0f } == (float _Complex){ 1.0f, 2.0f }, "");
+_Static_assert((double _Complex){ 1.0, 2.0 } == (double _Complex){ 1.0, 2.0 }, "");
+_Static_assert((long double _Complex){ 1.0L, 2.0L } == (long double _Complex){ 1.0L, 2.0L }, "");
+
+_Static_assert((float _Complex){ 1.0f, 2.0f } != (float _Complex){ 2.0f, 0.0f }, "");
+_Static_assert((double _Complex){ 1.0, 2.0 } != (double _Complex){ 2.0, 0.0 }, "");
+_Static_assert((long double _Complex){ 1.0L, 2.0L } != (long double _Complex){ 2.0L, 0.0L }, "");
+
+// You cannot use relational operator on complex values.
+int i1 = (float _Complex){ 1.0f, 2.0f } < 10;        // expected-error {{invalid operands to binary expression}}
+int i2 = (double _Complex){ 1.0f, 2.0f } > 10;       // expected-error {{invalid operands to binary expression}}
+int i3 = (long double _Complex){ 1.0f, 2.0f } <= 10; // expected-error {{invalid operands to binary expression}}
+int i4 = (float _Complex){ 1.0f, 2.0f } >= 10;       // expected-error {{invalid operands to binary expression}}
+
+// As a type specifier, _Complex cannot appear alone; however, we support it as
+// an extension by assuming _Complex double.
+_Complex c = 1.0f; // expected-warning {{plain '_Complex' requires a type specifier; assuming '_Complex double'}}
+// Because we don't support imaginary types, we don't extend the extension to
+// that type specifier.
+// FIXME: the warning diagnostic here is incorrect and should not be emitted.
+_Imaginary i = 1.0f; // expected-warning {{plain '_Complex' requires a type specifier; assuming '_Complex double'}} \
+                        expected-error {{imaginary types are not supported}}
diff --git a/clang/www/c_status.html b/clang/www/c_status.html
index 7ee1d2b507e88c..99a65bfb0a48e5 100644
--- a/clang/www/c_status.html
+++ b/clang/www/c_status.html
@@ -132,29 +132,11 @@ <h2 id="c99">C99 implementation status</h2>
       <td>Unknown</td>
       <td class="full" align="center">Yes</td>
     </tr>
-    <tr id="complex">
-      <td rowspan="6">complex and imaginary support in <complex.h></td>
+    <tr>
+      <td>complex and imaginary support in <complex.h></td>
+      <td><a href="https://www.open-std.org/jtc1/sc22/wg14/www/docs/n693.ps">N693</a></td>
+      <td class="full" align="center">Yes <a href="#complex">(1)</a></td>
     </tr>
-      <tr>
-        <td><a href="https://www.open-std.org/jtc1/sc22/wg14/www/docs/n620.ps">N620</a></td>
-        <td class="unknown" align="center">Unknown</td>
-      </tr>
-      <tr>
-        <td><a href="https://www.open-std.org/jtc1/sc22/wg14/www/docs/n638.ps">N638</a></td>
-        <td class="unknown" align="center">Unknown</td>
-      </tr>
-      <tr>
-        <td><a href="https://www.open-std.org/jtc1/sc22/wg14/www/docs/n657.ps">N657</a></td>
-        <td class="unknown" align="center">Unknown</td>
-      </tr>
-      <tr>
-        <td><a href="https://www.open-std.org/jtc1/sc22/wg14/www/docs/n694.ps">N694</a></td>
-        <td class="unknown" align="center">Unknown</td>
-      </tr>
-      <tr>
-        <td><a href="https://www.open-std.org/jtc1/sc22/wg14/www/docs/n809.ps">N809</a></td>
-        <td class="unknown" align="center">Unknown</td>
-      </tr>
     <tr>
       <td>type-generic math macros in <tgmath.h></td>
       <td><a href="https://www.open-std.org/jtc1/sc22/wg14/www/docs/n693.ps">N693</a></td>
@@ -373,6 +355,10 @@ <h2 id="c99">C99 implementation status</h2>
       <td class="full" align="center">Yes</td>
     </tr>
 </table>
+<span id="complex">(1): Clang supports <code>_Complex</code> type specifiers but
+does not support <code>_Imaginary</code> type specifiers. Support for
+<code>_Imaginary</code> is optional in C99 which is why Clang is fully conforming.
+</span>
 </details>
 
 <h2 id="c11">C11 implementation status</h2>

>From 325afe92fe7f4d25dd6bf17b8c9c9085c80950f8 Mon Sep 17 00:00:00 2001
From: Aaron Ballman <aaron at aaronballman.com>
Date: Tue, 9 Apr 2024 14:01:20 -0400
Subject: [PATCH 2/7] Switch footnote from (1) to (2); NFC

There's already a (1) in the status file, so this reduces confusion.
---
 clang/www/c_status.html | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/clang/www/c_status.html b/clang/www/c_status.html
index 3eae0ab665f84f..6a46ca5deae808 100644
--- a/clang/www/c_status.html
+++ b/clang/www/c_status.html
@@ -135,7 +135,7 @@ <h2 id="c99">C99 implementation status</h2>
     <tr>
       <td>complex and imaginary support in <complex.h></td>
       <td><a href="https://www.open-std.org/jtc1/sc22/wg14/www/docs/n693.ps">N693</a></td>
-      <td class="full" align="center">Yes <a href="#complex">(1)</a></td>
+      <td class="full" align="center">Yes <a href="#complex">(2)</a></td>
     </tr>
     <tr>
       <td>type-generic math macros in <tgmath.h></td>
@@ -355,7 +355,7 @@ <h2 id="c99">C99 implementation status</h2>
       <td class="full" align="center">Yes</td>
     </tr>
 </table>
-<span id="complex">(1): Clang supports <code>_Complex</code> type specifiers but
+<span id="complex">(2): Clang supports <code>_Complex</code> type specifiers but
 does not support <code>_Imaginary</code> type specifiers. Support for
 <code>_Imaginary</code> is optional in C99 which is why Clang is fully conforming.
 </span>

>From 66cbde9cdda60f3d6a2d7e71d2c08c1f0cb90ac9 Mon Sep 17 00:00:00 2001
From: Aaron Ballman <aaron at aaronballman.com>
Date: Thu, 11 Apr 2024 08:44:04 -0400
Subject: [PATCH 3/7] Update status page to mention Annex G

---
 clang/www/c_status.html | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/clang/www/c_status.html b/clang/www/c_status.html
index 6a46ca5deae808..248881b9733597 100644
--- a/clang/www/c_status.html
+++ b/clang/www/c_status.html
@@ -357,7 +357,8 @@ <h2 id="c99">C99 implementation status</h2>
 </table>
 <span id="complex">(2): Clang supports <code>_Complex</code> type specifiers but
 does not support <code>_Imaginary</code> type specifiers. Support for
-<code>_Imaginary</code> is optional in C99 which is why Clang is fully conforming.
+<code>_Imaginary</code> is optional in C99 and Clang does not claim conformance
+to Annex G.
 </span>
 </details>
 

>From 7fea62315e996904438d6fc25da1d2aac4139af1 Mon Sep 17 00:00:00 2001
From: Aaron Ballman <aaron at aaronballman.com>
Date: Thu, 11 Apr 2024 08:46:50 -0400
Subject: [PATCH 4/7] Add further comments explaining array alignment

---
 clang/test/C/C99/n809.c | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/clang/test/C/C99/n809.c b/clang/test/C/C99/n809.c
index 8d26744443eec4..1632346f0707b8 100644
--- a/clang/test/C/C99/n809.c
+++ b/clang/test/C/C99/n809.c
@@ -34,7 +34,10 @@ long double _Imaginary ldi1; // expected-error {{imaginary types are not support
 _Imaginary long double ldi2; // expected-error {{imaginary types are not supported}}
 
 // Each complex type has the same representation and alignment as an array
-// containing two elements of the corresponding real type.
+// containing two elements of the corresponding real type. Note, it is not
+// mandatory that the alignment of a structure containing an array of two
+// elements has the same alignment as an array of two elements outside of a
+// structure, but this is a property Clang supports.
 _Static_assert(sizeof(float _Complex) == sizeof(struct { float mem[2]; }), "");
 _Static_assert(_Alignof(float _Complex) == _Alignof(struct { float mem[2]; }), "");
 

>From c3f7d6efce69552a0af53f4ecf06648878f91f0e Mon Sep 17 00:00:00 2001
From: Aaron Ballman <aaron at aaronballman.com>
Date: Thu, 11 Apr 2024 11:00:56 -0400
Subject: [PATCH 5/7] Add additional test coverage

---
 clang/test/C/C99/n809.c   | 22 +++++++++++++++++
 clang/test/C/C99/n809_2.c | 52 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 74 insertions(+)
 create mode 100644 clang/test/C/C99/n809_2.c

diff --git a/clang/test/C/C99/n809.c b/clang/test/C/C99/n809.c
index 1632346f0707b8..cf2f1a3d974426 100644
--- a/clang/test/C/C99/n809.c
+++ b/clang/test/C/C99/n809.c
@@ -98,3 +98,25 @@ _Complex c = 1.0f; // expected-warning {{plain '_Complex' requires a type specif
 // FIXME: the warning diagnostic here is incorrect and should not be emitted.
 _Imaginary i = 1.0f; // expected-warning {{plain '_Complex' requires a type specifier; assuming '_Complex double'}} \
                         expected-error {{imaginary types are not supported}}
+
+void func(void) {
+#pragma clang diagnostic push
+#pragma clang diagnostic warning "-Wpedantic"
+  // Increment and decrement operators have a constraint that their operand be
+  // a real type; Clang supports this as an extension on complex types as well.
+  // FIXME: the diagnostic message says "on complex integer type" but then
+  // specifies the type as _Complex float. Oops.
+  _Complex float cf = 0.0f;
+
+  cf++; // expected-warning {{ISO C does not support '++'/'--' on complex integer type '_Complex float'}}
+  ++cf; // expected-warning {{ISO C does not support '++'/'--' on complex integer type '_Complex float'}}
+
+  cf--; // expected-warning {{ISO C does not support '++'/'--' on complex integer type '_Complex float'}}
+  --cf; // expected-warning {{ISO C does not support '++'/'--' on complex integer type '_Complex float'}}
+
+  // However, unary + and - are fine, as is += 1.
+  (void)-cf;
+  (void)+cf;
+  cf += 1;
+#pragma clang diagnostic pop
+}
diff --git a/clang/test/C/C99/n809_2.c b/clang/test/C/C99/n809_2.c
new file mode 100644
index 00000000000000..b44386c41b38f7
--- /dev/null
+++ b/clang/test/C/C99/n809_2.c
@@ -0,0 +1,52 @@
+// RUN: %clang_cc1 -ast-dump -std=c99 %s | FileCheck %s
+
+void func(void) {
+  // CHECK: FunctionDecl {{.*}} func 'void (void)'
+
+  // Show that we correctly convert between two complex domains.
+  _Complex float cf = 1.0f;
+  _Complex double cd;
+
+  cd = cf;
+  // CHECK: BinaryOperator {{.*}} '_Complex double' '='
+  // CHECK-NEXT: DeclRefExpr {{.*}} 'cd'
+  // CHECK-NEXT: ImplicitCastExpr {{.*}} '_Complex double' <FloatingComplexCast>
+  // CHECK-NEXT: ImplicitCastExpr {{.*}} '_Complex float' <LValueToRValue>
+  // CHECK-NEXT: DeclRefExpr {{.*}} 'cf'
+
+  cf = cd;
+  // CHECK: BinaryOperator {{.*}} '_Complex float' '='
+  // CHECK-NEXT: DeclRefExpr {{.*}} 'cf'
+  // CHECK-NEXT: ImplicitCastExpr {{.*}} '_Complex float' <FloatingComplexCast>
+  // CHECK-NEXT: ImplicitCastExpr {{.*}} '_Complex double' <LValueToRValue>
+  // CHECK-NEXT: DeclRefExpr {{.*}} 'cd'
+
+  // Show that we correctly convert to the common type of a complex and real.
+  // This should convert the _Complex float to a _Complex double ("without
+  // change of domain" c.f. C99 6.3.1.8p1).
+  (void)(cf + 1.0);
+  // CHECK: BinaryOperator {{.*}} '_Complex double' '+'
+  // CHECK-NEXT: ImplicitCastExpr {{.*}} '_Complex double' <FloatingComplexCast>
+  // CHECK-NEXT: ImplicitCastExpr {{.*}} '_Complex float' <LValueToRValue>
+  // CHECK-NEXT: DeclRefExpr {{.*}} 'cf'
+  // CHECK-NEXT: FloatingLiteral {{.*}} 'double' 1.0
+
+  // This should convert the float constant to double, then produce a
+  // _Complex double.
+  (void)(cd + 1.0f);
+  // CHECK: BinaryOperator {{.*}} '_Complex double' '+'
+  // CHECK-NEXT: ImplicitCastExpr {{.*}} '_Complex double' <LValueToRValue>
+  // CHECK-NEXT: DeclRefExpr {{.*}} 'cd'
+  // CHECK-NEXT: ImplicitCastExpr {{.*}} 'double' <FloatingCast>
+  // CHECK-NEXT: FloatingLiteral {{.*}} 'float' 1.0
+
+  // This should convert the int constant to float, then produce a
+  // _Complex float.
+  (void)(cf + 1);
+  // CHECK: BinaryOperator {{.*}} '_Complex float' '+'
+  // CHECK-NEXT: ImplicitCastExpr {{.*}} '_Complex float' <LValueToRValue>
+  // CHECK-NEXT: DeclRefExpr {{.*}} 'cf'
+  // CHECK-NEXT: ImplicitCastExpr {{.*}} 'float' <IntegralToFloating>
+  // CHECK-NEXT: IntegerLiteral {{.*}} 'int' 1
+}
+

>From 9453c35fd28f8f6d1f63bc852691d2154d1df6c4 Mon Sep 17 00:00:00 2001
From: Aaron Ballman <aaron at aaronballman.com>
Date: Thu, 11 Apr 2024 12:03:14 -0400
Subject: [PATCH 6/7] Add a test for default argument promotion behavior

---
 clang/test/C/C99/n809_2.c | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/clang/test/C/C99/n809_2.c b/clang/test/C/C99/n809_2.c
index b44386c41b38f7..3bf163126521a6 100644
--- a/clang/test/C/C99/n809_2.c
+++ b/clang/test/C/C99/n809_2.c
@@ -1,5 +1,7 @@
 // RUN: %clang_cc1 -ast-dump -std=c99 %s | FileCheck %s
 
+void variadic(int i, ...);
+
 void func(void) {
   // CHECK: FunctionDecl {{.*}} func 'void (void)'
 
@@ -48,5 +50,15 @@ void func(void) {
   // CHECK-NEXT: DeclRefExpr {{.*}} 'cf'
   // CHECK-NEXT: ImplicitCastExpr {{.*}} 'float' <IntegralToFloating>
   // CHECK-NEXT: IntegerLiteral {{.*}} 'int' 1
+
+  // Show that we do not promote a _Complex float to _Complex double as part of
+  // the default argument promotions when passing to a variadic function.
+  variadic(1, cf);
+  // CHECK: CallExpr
+  // CHECK-NEXT: ImplicitCastExpr {{.*}} <FunctionToPointerDecay>
+  // CHECK-NEXT: DeclRefExpr {{.*}} 'variadic'
+  // CHECK-NEXT: IntegerLiteral {{.*}} 'int' 1
+  // CHECK-NEXT: ImplicitCastExpr {{.*}} '_Complex float' <LValueToRValue>
+  // CHECK-NEXT: DeclRefExpr {{.*}} 'cf'
 }
 

>From 98ad653dd8b0387af575d43d5fc2e5c0cb18ddc1 Mon Sep 17 00:00:00 2001
From: Aaron Ballman <aaron at aaronballman.com>
Date: Thu, 11 Apr 2024 12:25:07 -0400
Subject: [PATCH 7/7] Add a test for static initialization

---
 clang/test/C/C99/n809_3.c | 12 ++++++++++++
 1 file changed, 12 insertions(+)
 create mode 100644 clang/test/C/C99/n809_3.c

diff --git a/clang/test/C/C99/n809_3.c b/clang/test/C/C99/n809_3.c
new file mode 100644
index 00000000000000..f1283f5fe1632c
--- /dev/null
+++ b/clang/test/C/C99/n809_3.c
@@ -0,0 +1,12 @@
+// RUN: %clang_cc1 -emit-llvm -std=c99 %s -o - | FileCheck %s
+
+// Demonstrate that statics are properly zero initialized.
+static _Complex float f_global;
+void func(void) {
+  static _Complex double d_local;
+  d_local = f_global;
+}
+
+// CHECK-DAG: @func.d_local = internal global { double, double } zeroinitializer
+// CHECK-DAG: @f_global = internal global { float, float } zeroinitializer
+



More information about the cfe-commits mailing list