[clang] [clang][bytecode] Start implementing __builtin_bit_cast (PR #112126)
Aaron Ballman via cfe-commits
cfe-commits at lists.llvm.org
Thu Oct 31 05:46:34 PDT 2024
Timm =?utf-8?q?Bäder?= <tbaeder at redhat.com>,
Timm =?utf-8?q?Bäder?= <tbaeder at redhat.com>,
Timm =?utf-8?q?Bäder?= <tbaeder at redhat.com>,
Timm =?utf-8?q?Bäder?= <tbaeder at redhat.com>
Message-ID:
In-Reply-To: <llvm.org/llvm/llvm-project/pull/112126 at github.com>
================
@@ -0,0 +1,399 @@
+// RUN: %clang_cc1 -verify=ref,both -std=c++2a -fsyntax-only %s
+// RUN: %clang_cc1 -verify=ref,both -std=c++2a -fsyntax-only -triple aarch64_be-linux-gnu %s
+// RUN: %clang_cc1 -verify=ref,both -std=c++2a -fsyntax-only -triple powerpc64le-unknown-unknown -mabi=ieeelongdouble %s
+// RUN: %clang_cc1 -verify=ref,both -std=c++2a -fsyntax-only -triple powerpc64-unknown-unknown -mabi=ieeelongdouble %s
+
+// RUN: %clang_cc1 -verify=expected,both -std=c++2a -fsyntax-only -fexperimental-new-constant-interpreter %s
+// RUN: %clang_cc1 -verify=expected,both -std=c++2a -fsyntax-only -triple aarch64_be-linux-gnu -fexperimental-new-constant-interpreter %s
+// RUN: %clang_cc1 -verify=expected,both -std=c++2a -fsyntax-only -fexperimental-new-constant-interpreter -triple powerpc64le-unknown-unknown -mabi=ieeelongdouble %s
+// RUN: %clang_cc1 -verify=expected,both -std=c++2a -fsyntax-only -fexperimental-new-constant-interpreter -triple powerpc64-unknown-unknown -mabi=ieeelongdouble %s
+
+#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
+# define LITTLE_END 1
+#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+# define LITTLE_END 0
+#else
+# error "huh?"
+#endif
+
+typedef decltype(nullptr) nullptr_t;
+typedef __INTPTR_TYPE__ intptr_t;
+
+static_assert(sizeof(int) == 4);
+static_assert(sizeof(long long) == 8);
+
+template <class To, class From>
+constexpr To bit_cast(const From &from) {
+ static_assert(sizeof(To) == sizeof(From));
+ return __builtin_bit_cast(To, from);
+}
+
+template <class Intermediate, class Init>
+constexpr bool check_round_trip(const Init &init) {
+ return bit_cast<Init>(bit_cast<Intermediate>(init)) == init;
+}
+
+template <class Intermediate, class Init>
+constexpr Init round_trip(const Init &init) {
+ return bit_cast<Init>(bit_cast<Intermediate>(init));
+}
+
+namespace std {
+enum byte : unsigned char {};
+} // namespace std
+
+using uint8_t = unsigned char;
+
+template<int N>
+struct bytes {
+ using size_t = unsigned int;
+ unsigned char d[N];
+
+ constexpr unsigned char &operator[](size_t index) {
+ if (index < N)
+ return d[index];
+ }
+};
+
+
+template <int N, typename T = unsigned char, int Pad = 0>
+struct bits {
+ T : Pad;
+ T bits : N;
+
+ constexpr bool operator==(const T& rhs) const {
+ return bits == rhs;
+ }
+};
+
+template <int N, typename T, int P>
+constexpr bool operator==(const struct bits<N, T, P>& lhs, const struct bits<N, T, P>& rhs) {
+ return lhs.bits == rhs.bits;
+}
+
+
+namespace simple {
+ constexpr int A = __builtin_bit_cast(int, 10);
+ static_assert(A == 10);
+
+ static_assert(__builtin_bit_cast(unsigned, 1.0F) == 1065353216);
+
+ struct Bytes {
+ char a, b, c, d;
+ };
+ constexpr unsigned B = __builtin_bit_cast(unsigned, Bytes{10, 12, 13, 14});
+ static_assert(B == (LITTLE_END ? 235736074 : 168561934));
+
+
+ constexpr unsigned C = __builtin_bit_cast(unsigned, (_BitInt(32))12);
+ static_assert(C == 12);
+
+ struct BitInts {
+ _BitInt(16) a;
+ _BitInt(16) b;
+ };
+ constexpr unsigned D = __builtin_bit_cast(unsigned, BitInts{12, 13});
+ static_assert(D == (LITTLE_END ? 851980 : 786445));
+
+
+
+ static_assert(__builtin_bit_cast(char, true) == 1);
+
+ static_assert(check_round_trip<unsigned>((int)-1));
+ static_assert(check_round_trip<unsigned>((int)0x12345678));
+ static_assert(check_round_trip<unsigned>((int)0x87654321));
+ static_assert(check_round_trip<unsigned>((int)0x0C05FEFE));
+ // static_assert(round_trip<float>((int)0x0C05FEFE));
+
+
+ /// This works in GCC and in the bytecode interpreter, but the current interpreter
+ /// diagnoses it.
+ static_assert(__builtin_bit_cast(intptr_t, nullptr) == 0); // ref-error {{not an integral constant expression}} \
+ // ref-note {{indeterminate value can only initialize an object}}
+}
+
+namespace Fail {
+ constexpr int a = 1/0; // both-error {{must be initialized by a constant expression}} \
+ // both-note {{division by zero}} \
+ // both-note {{declared here}}
+ constexpr int b = __builtin_bit_cast(int, a); // both-error {{must be initialized by a constant expression}} \
+ // both-note {{initializer of 'a' is not a constant expression}}
+}
+
+namespace NullPtr {
+ constexpr nullptr_t N = __builtin_bit_cast(nullptr_t, (intptr_t)1u);
+ static_assert(N == nullptr);
+ static_assert(__builtin_bit_cast(nullptr_t, (_BitInt(sizeof(void*) * 8))12) == __builtin_bit_cast(nullptr_t, (unsigned _BitInt(sizeof(void*) * 8))0));
+ static_assert(__builtin_bit_cast(nullptr_t, nullptr) == nullptr);
+}
+
+namespace bitint {
+ constexpr _BitInt(sizeof(int) * 8) BI = ~0;
+ constexpr unsigned int I = __builtin_bit_cast(unsigned int, BI);
+ static_assert(I == ~0u, "");
+
+ constexpr _BitInt(sizeof(int) * 8) IB = __builtin_bit_cast(_BitInt(sizeof(int) * 8), I); // ref-error {{must be initialized by a constant expression}} \
+ // ref-note {{constexpr bit cast involving type '_BitInt(32)' is not yet supported}} \
+ // ref-note {{declared here}}
+ static_assert(IB == ~0u, ""); // ref-error {{not an integral constant expression}} \
+ // ref-note {{initializer of 'IB' is not a constant expression}}
+}
+
+namespace BitFields {
+ struct BitFields {
+ unsigned a : 2;
+ unsigned b : 30;
+ };
+
+ constexpr unsigned A = __builtin_bit_cast(unsigned, BitFields{3, 16}); // ref-error {{must be initialized by a constant expression}} \
+ // ref-note {{not yet supported}} \
+ // ref-note {{declared here}}
+ static_assert(A == (LITTLE_END ? 67 : 3221225488)); // ref-error {{not an integral constant expression}} \
+ // ref-note {{initializer of 'A'}}
+
+
+ void bitfield_indeterminate() {
+ struct BF { unsigned char z : 2; };
+ enum byte : unsigned char {};
+
+ constexpr BF bf = {0x3};
+ /// Requires bitcasts to composite types.
+ // static_assert(bit_cast<bits<2>>(bf).bits == bf.z);
+ // static_assert(bit_cast<unsigned char>(bf));
+
+#if 0
+ // static_assert(__builtin_bit_cast(byte, bf));
+
+ struct M {
+ // expected-note at +1 {{subobject declared here}}
+ unsigned char mem[sizeof(BF)];
+ };
+ // expected-error at +2 {{initialized by a constant expression}}
+ // expected-note at +1 {{not initialized}}
+ constexpr M m = bit_cast<M>(bf);
+
+ constexpr auto f = []() constexpr {
+ // bits<24, unsigned int, LITTLE_END ? 0 : 8> B = {0xc0ffee};
+ constexpr struct { unsigned short b1; unsigned char b0; } B = {0xc0ff, 0xee};
+ return bit_cast<bytes<4>>(B);
+ };
+
+ static_assert(f()[0] + f()[1] + f()[2] == 0xc0 + 0xff + 0xee);
+ {
+ // expected-error at +2 {{initialized by a constant expression}}
+ // expected-note at +1 {{read of uninitialized object is not allowed in a constant expression}}
+ constexpr auto _bad = f()[3];
+ }
+
+ struct B {
+ unsigned short s0 : 8;
+ unsigned short s1 : 8;
+ std::byte b0 : 4;
+ std::byte b1 : 4;
+ std::byte b2 : 4;
+ };
+ constexpr auto g = [f]() constexpr {
+ return bit_cast<B>(f());
+ };
+ static_assert(g().s0 + g().s1 + g().b0 + g().b1 == 0xc0 + 0xff + 0xe + 0xe);
+ {
+ // expected-error at +2 {{initialized by a constant expression}}
+ // expected-note at +1 {{read of uninitialized object is not allowed in a constant expression}}
+ constexpr auto _bad = g().b2;
+ }
+#endif
+ }
+}
+
+struct int_splicer {
+ unsigned x;
+ unsigned y;
+
+ constexpr int_splicer() : x(1), y(2) {}
+ constexpr int_splicer(unsigned x, unsigned y) : x(x), y(y) {}
+
+ constexpr bool operator==(const int_splicer &other) const {
+ return other.x == x && other.y == y;
+ }
+};
+
+constexpr int_splicer splice(0x0C05FEFE, 0xCAFEBABE);
+
+#if 0
+static_assert(bit_cast<unsigned long long>(splice) == (LITTLE_END
+ ? 0xCAFEBABE0C05FEFE
+ : 0x0C05FEFECAFEBABE));
+
+constexpr int_splicer IS = bit_cast<int_splicer>(0xCAFEBABE0C05FEFE);
+static_assert(bit_cast<int_splicer>(0xCAFEBABE0C05FEFE).x == (LITTLE_END
+ ? 0x0C05FEFE
+ : 0xCAFEBABE));
+
+static_assert(round_trip<unsigned long long>(splice));
+static_assert(round_trip<long long>(splice));
+#endif
+
+
+
+/// ---------------------------------------------------------------------------
+/// From here on, it's things copied from test/SemaCXX/constexpr-builtin-bit.cast.cpp
+
+void test_int() {
+ static_assert(round_trip<unsigned>((int)-1));
+ static_assert(round_trip<unsigned>((int)0x12345678));
+ static_assert(round_trip<unsigned>((int)0x87654321));
+ static_assert(round_trip<unsigned>((int)0x0C05FEFE));
+}
+
+void test_array() {
+ constexpr unsigned char input[] = {0xCA, 0xFE, 0xBA, 0xBE};
+ constexpr unsigned expected = LITTLE_END ? 0xBEBAFECA : 0xCAFEBABE;
+ static_assert(bit_cast<unsigned>(input) == expected);
+
+ /// Same things but with a composite array.
+ struct US { unsigned char I; };
+ constexpr US input2[] = {{0xCA}, {0xFE}, {0xBA}, {0xBE}};
+ static_assert(bit_cast<unsigned>(input2) == expected);
+}
+
+void test_record() {
+ struct int_splicer {
+ unsigned x;
+ unsigned y;
+
+ constexpr bool operator==(const int_splicer &other) const {
+ return other.x == x && other.y == y;
+ }
+ };
+
+ constexpr int_splicer splice{0x0C05FEFE, 0xCAFEBABE};
+
+ static_assert(bit_cast<unsigned long long>(splice) == (LITTLE_END
+ ? 0xCAFEBABE0C05FEFE
+ : 0x0C05FEFECAFEBABE));
+
+ /// FIXME: Bit casts to composite types.
+ // static_assert(bit_cast<int_splicer>(0xCAFEBABE0C05FEFE).x == (LITTLE_END
+ // ? 0x0C05FEFE
+ // : 0xCAFEBABE));
+
+ // static_assert(check_round_trip<unsigned long long>(splice));
+ // static_assert(check_round_trip<long long>(splice));
+
+ struct base2 {
+ };
+
+ struct base3 {
+ unsigned z;
+ };
+
+ struct bases : int_splicer, base2, base3 {
+ unsigned doublez;
+ };
+
+ struct tuple4 {
+ unsigned x, y, z, doublez;
+
+ bool operator==(tuple4 const &other) const = default;
+ constexpr bool operator==(bases const &other) const {
+ return x == other.x && y == other.y &&
+ z == other.z && doublez == other.doublez;
+ }
+ };
+ // constexpr bases b = {{1, 2}, {}, {3}, 4};
+ // constexpr tuple4 t4 = bit_cast<tuple4>(b);
+ // static_assert(t4 == tuple4{1, 2, 3, 4});
+ // static_assert(round_trip<tuple4>(b));
+
+ // constexpr auto b2 = bit_cast<bases>(t4);
+ // static_assert(t4 == b2);
+}
+
+void test_partially_initialized() {
+ struct pad {
+ signed char x;
+ int y;
+ };
+
+ struct no_pad {
+ signed char x;
+ signed char p1, p2, p3;
+ int y;
+ };
+
+ static_assert(sizeof(pad) == sizeof(no_pad));
+
+#if 0
+ constexpr pad pir{4, 4};
+ constexpr int piw = bit_cast<no_pad>(pir).x; // both-error {{constexpr variable 'piw' must be initialized by a constant expression}} \
+ // both-note {{in call to 'bit_cast<no_pad, pad>(pir)'}}
+
+
+ constexpr no_pad bad = bit_cast<no_pad>(pir); // both-error {{constexpr variable 'bad' must be initialized by a constant expression}} \
+ // both-note {{in call to 'bit_cast<no_pad, pad>(pir)'}}
+ // constexpr pad fine = bit_cast<pad>(no_pad{1, 2, 3, 4, 5});
+ // static_assert(fine.x == 1 && fine.y == 5);
+#endif
+}
+
+
+void bad_types() {
+#if 0
+ union X {
+ int x;
+ };
+
+ struct G {
+ int g;
+ };
+ // expected-error at +2 {{constexpr variable 'g' must be initialized by a constant expression}}
+ // expected-note at +1 {{bit_cast from a union type is not allowed in a constant expression}}
+ constexpr G g = __builtin_bit_cast(G, X{0});
----------------
AaronBallman wrote:
Ah! That makes sense -- can you add a test that shows bitcasting from a union to a primitive type is caught? e.g.,
```
constexpr int a = __builtin_bit_cast(int, X{0});
```
https://github.com/llvm/llvm-project/pull/112126
More information about the cfe-commits
mailing list