[clang] [clang][bytecode] Start implementing __builtin_bit_cast (PR #112126)

Aaron Ballman via cfe-commits cfe-commits at lists.llvm.org
Wed Oct 30 06:39:50 PDT 2024


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:

Why is this test disabled? I would have assumed this worked?

https://github.com/llvm/llvm-project/pull/112126


More information about the cfe-commits mailing list