[clang] [clang] Implement constexpr bit_cast for vectors (PR #66894)
via cfe-commits
cfe-commits at lists.llvm.org
Mon Oct 9 01:07:23 PDT 2023
https://github.com/DaMatrix updated https://github.com/llvm/llvm-project/pull/66894
>From f05f52ed4e74cf857bf07aef5dbd5d0861591a14 Mon Sep 17 00:00:00 2001
From: DaPorkchop_ <daporkchop at daporkchop.net>
Date: Sun, 13 Aug 2023 22:39:12 +0200
Subject: [PATCH] [clang] Implement constexpr bit_cast for vectors
---
.../include/clang/Basic/DiagnosticASTKinds.td | 3 +
clang/lib/AST/ExprConstant.cpp | 247 +++++++++++-------
.../SemaCXX/constexpr-builtin-bit-cast.cpp | 61 +++++
3 files changed, 217 insertions(+), 94 deletions(-)
diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td
index d2656310e79c9b8..3f06e18783dd558 100644
--- a/clang/include/clang/Basic/DiagnosticASTKinds.td
+++ b/clang/include/clang/Basic/DiagnosticASTKinds.td
@@ -326,6 +326,9 @@ def note_constexpr_bit_cast_invalid_type : Note<
"%select{type|member}1 is not allowed in a constant expression">;
def note_constexpr_bit_cast_invalid_subtype : Note<
"invalid type %0 is a %select{member|base}1 of %2">;
+def note_constexpr_bit_cast_invalid_vector : Note<
+ "bit_cast involving type %0 is not allowed in a constant expression; "
+ "element size %1 * element count %2 is not a multiple of the byte size %3">;
def note_constexpr_bit_cast_indet_dest : Note<
"indeterminate value can only initialize an object of type 'unsigned char'"
"%select{, 'char',|}1 or 'std::byte'; %0 is invalid">;
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index a142ea7c47a4730..ad4d1dd12edb2e8 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -2732,53 +2732,6 @@ static bool truncateBitfieldValue(EvalInfo &Info, const Expr *E,
return true;
}
-static bool EvalAndBitcastToAPInt(EvalInfo &Info, const Expr *E,
- llvm::APInt &Res) {
- APValue SVal;
- if (!Evaluate(SVal, Info, E))
- return false;
- if (SVal.isInt()) {
- Res = SVal.getInt();
- return true;
- }
- if (SVal.isFloat()) {
- Res = SVal.getFloat().bitcastToAPInt();
- return true;
- }
- if (SVal.isVector()) {
- QualType VecTy = E->getType();
- unsigned VecSize = Info.Ctx.getTypeSize(VecTy);
- QualType EltTy = VecTy->castAs<VectorType>()->getElementType();
- unsigned EltSize = Info.Ctx.getTypeSize(EltTy);
- bool BigEndian = Info.Ctx.getTargetInfo().isBigEndian();
- Res = llvm::APInt::getZero(VecSize);
- for (unsigned i = 0; i < SVal.getVectorLength(); i++) {
- APValue &Elt = SVal.getVectorElt(i);
- llvm::APInt EltAsInt;
- if (Elt.isInt()) {
- EltAsInt = Elt.getInt();
- } else if (Elt.isFloat()) {
- EltAsInt = Elt.getFloat().bitcastToAPInt();
- } else {
- // Don't try to handle vectors of anything other than int or float
- // (not sure if it's possible to hit this case).
- Info.FFDiag(E, diag::note_invalid_subexpr_in_const_expr);
- return false;
- }
- unsigned BaseEltSize = EltAsInt.getBitWidth();
- if (BigEndian)
- Res |= EltAsInt.zextOrTrunc(VecSize).rotr(i*EltSize+BaseEltSize);
- else
- Res |= EltAsInt.zextOrTrunc(VecSize).rotl(i*EltSize);
- }
- return true;
- }
- // Give up if the input isn't an int, float, or vector. For example, we
- // reject "(v4i16)(intptr_t)&a".
- Info.FFDiag(E, diag::note_invalid_subexpr_in_const_expr);
- return false;
-}
-
/// Perform the given integer operation, which is known to need at most BitWidth
/// bits, and check for overflow in the original type (if that type was not an
/// unsigned type).
@@ -7008,10 +6961,11 @@ class APValueToBufferConverter {
return visitArray(Val, Ty, Offset);
case APValue::Struct:
return visitRecord(Val, Ty, Offset);
+ case APValue::Vector:
+ return visitVector(Val, Ty, Offset);
case APValue::ComplexInt:
case APValue::ComplexFloat:
- case APValue::Vector:
case APValue::FixedPoint:
// FIXME: We should support these.
@@ -7098,6 +7052,61 @@ class APValueToBufferConverter {
return true;
}
+ bool visitVector(const APValue &Val, QualType Ty, CharUnits Offset) {
+ const VectorType *VTy = Ty->castAs<VectorType>();
+ QualType EltTy = VTy->getElementType();
+ unsigned NElts = VTy->getNumElements();
+ unsigned EltSize =
+ VTy->isExtVectorBoolType() ? 1 : Info.Ctx.getTypeSize(EltTy);
+
+ if ((NElts * EltSize) % Info.Ctx.getCharWidth() != 0) {
+ // The vector's size in bits is not a multiple of the target's byte size,
+ // so its layout is unspecified. For now, we'll simply treat these cases
+ // as unsupported (this should only be possible with OpenCL bool vectors
+ // whose element count isn't a multiple of the byte size).
+ Info.FFDiag(BCE->getBeginLoc(),
+ diag::note_constexpr_bit_cast_invalid_vector)
+ << Ty.getCanonicalType() << EltSize << NElts
+ << Info.Ctx.getCharWidth();
+ return false;
+ }
+
+ if (VTy->isExtVectorBoolType()) {
+ // Special handling for OpenCL bool vectors:
+ // Since these vectors are stored as packed bits, but we can't write
+ // individual bits to the BitCastBuffer, we'll buffer all of the elements
+ // together into an appropriately sized APInt and write them all out at
+ // once. Because we don't accept vectors where NElts * EltSize isn't a
+ // multiple of the char size, there will be no padding space, so we don't
+ // have to worry about writing data which should have been left
+ // uninitialized.
+ bool BigEndian = Info.Ctx.getTargetInfo().isBigEndian();
+
+ llvm::APInt Res = llvm::APInt::getZero(NElts);
+ for (unsigned I = 0; I < NElts; ++I) {
+ const llvm::APSInt &EltAsInt = Val.getVectorElt(I).getInt();
+ assert(EltAsInt.isUnsigned() && EltAsInt.getBitWidth() == 1 &&
+ "bool vector element must be 1-bit unsigned integer!");
+
+ Res.insertBits(EltAsInt, BigEndian ? (NElts - I - 1) : I);
+ }
+
+ SmallVector<uint8_t, 8> Bytes(NElts / 8);
+ llvm::StoreIntToMemory(Res, &*Bytes.begin(), NElts / 8);
+ Buffer.writeObject(Offset, Bytes);
+ } else {
+ // Iterate over each of the elements and write them out to the buffer at
+ // the appropriate offset.
+ CharUnits EltSizeChars = Info.Ctx.getTypeSizeInChars(EltTy);
+ for (unsigned I = 0; I < NElts; ++I) {
+ if (!visit(Val.getVectorElt(I), EltTy, Offset + I * EltSizeChars))
+ return false;
+ }
+ }
+
+ return true;
+ }
+
bool visitInt(const APSInt &Val, QualType Ty, CharUnits Offset) {
APSInt AdjustedVal = Val;
unsigned Width = AdjustedVal.getBitWidth();
@@ -7106,7 +7115,7 @@ class APValueToBufferConverter {
AdjustedVal = AdjustedVal.extend(Width);
}
- SmallVector<unsigned char, 8> Bytes(Width / 8);
+ SmallVector<uint8_t, 8> Bytes(Width / 8);
llvm::StoreIntToMemory(AdjustedVal, &*Bytes.begin(), Width / 8);
Buffer.writeObject(Offset, Bytes);
return true;
@@ -7307,6 +7316,66 @@ class BufferToAPValueConverter {
return ArrayValue;
}
+ std::optional<APValue> visit(const VectorType *VTy, CharUnits Offset) {
+ QualType EltTy = VTy->getElementType();
+ unsigned NElts = VTy->getNumElements();
+ unsigned EltSize =
+ VTy->isExtVectorBoolType() ? 1 : Info.Ctx.getTypeSize(EltTy);
+
+ if ((NElts * EltSize) % Info.Ctx.getCharWidth() != 0) {
+ // The vector's size in bits is not a multiple of the target's byte size,
+ // so its layout is unspecified. For now, we'll simply treat these cases
+ // as unsupported (this should only be possible with OpenCL bool vectors
+ // whose element count isn't a multiple of the byte size).
+ Info.FFDiag(BCE->getBeginLoc(),
+ diag::note_constexpr_bit_cast_invalid_vector)
+ << QualType(VTy, 0) << EltSize << NElts << Info.Ctx.getCharWidth();
+ return std::nullopt;
+ }
+
+ SmallVector<APValue, 4> Elts;
+ Elts.reserve(NElts);
+ if (VTy->isExtVectorBoolType()) {
+ // Special handling for OpenCL bool vectors:
+ // Since these vectors are stored as packed bits, but we can't read
+ // individual bits from the BitCastBuffer, we'll buffer all of the
+ // elements together into an appropriately sized APInt and write them all
+ // out at once. Because we don't accept vectors where NElts * EltSize
+ // isn't a multiple of the char size, there will be no padding space, so
+ // we don't have to worry about reading any padding data which didn't
+ // actually need to be accessed.
+ bool BigEndian = Info.Ctx.getTargetInfo().isBigEndian();
+
+ SmallVector<uint8_t, 8> Bytes;
+ Bytes.reserve(NElts / 8);
+ if (!Buffer.readObject(Offset, CharUnits::fromQuantity(NElts / 8), Bytes))
+ return std::nullopt;
+
+ APSInt SValInt(NElts, true);
+ llvm::LoadIntFromMemory(SValInt, &*Bytes.begin(), Bytes.size());
+
+ for (unsigned I = 0; I < NElts; ++I) {
+ llvm::APInt Elt =
+ SValInt.extractBits(1, (BigEndian ? NElts - I - 1 : I) * EltSize);
+ Elts.emplace_back(
+ APSInt(std::move(Elt), !EltTy->isSignedIntegerType()));
+ }
+ } else {
+ // Iterate over each of the elements and read them from the buffer at
+ // the appropriate offset.
+ CharUnits EltSizeChars = Info.Ctx.getTypeSizeInChars(EltTy);
+ for (unsigned I = 0; I < NElts; ++I) {
+ std::optional<APValue> EltValue =
+ visitType(EltTy, Offset + I * EltSizeChars);
+ if (!EltValue)
+ return std::nullopt;
+ Elts.push_back(std::move(*EltValue));
+ }
+ }
+
+ return APValue(Elts.data(), Elts.size());
+ }
+
std::optional<APValue> visit(const Type *Ty, CharUnits Offset) {
return unsupportedType(QualType(Ty, 0));
}
@@ -7406,25 +7475,15 @@ static bool checkBitCastConstexprEligibility(EvalInfo *Info,
return SourceOK;
}
-static bool handleLValueToRValueBitCast(EvalInfo &Info, APValue &DestValue,
- APValue &SourceValue,
+static bool handleRValueToRValueBitCast(EvalInfo &Info, APValue &DestValue,
+ const APValue &SourceRValue,
const CastExpr *BCE) {
assert(CHAR_BIT == 8 && Info.Ctx.getTargetInfo().getCharWidth() == 8 &&
"no host or target supports non 8-bit chars");
- assert(SourceValue.isLValue() &&
- "LValueToRValueBitcast requires an lvalue operand!");
if (!checkBitCastConstexprEligibility(&Info, Info.Ctx, BCE))
return false;
- LValue SourceLValue;
- APValue SourceRValue;
- SourceLValue.setFrom(Info.Ctx, SourceValue);
- if (!handleLValueToRValueConversion(
- Info, BCE, BCE->getSubExpr()->getType().withConst(), SourceLValue,
- SourceRValue, /*WantObjectRepresentation=*/true))
- return false;
-
// Read out SourceValue into a char buffer.
std::optional<BitCastBuffer> Buffer =
APValueToBufferConverter::convert(Info, SourceRValue, BCE);
@@ -7441,6 +7500,25 @@ static bool handleLValueToRValueBitCast(EvalInfo &Info, APValue &DestValue,
return true;
}
+static bool handleLValueToRValueBitCast(EvalInfo &Info, APValue &DestValue,
+ APValue &SourceValue,
+ const CastExpr *BCE) {
+ assert(CHAR_BIT == 8 && Info.Ctx.getTargetInfo().getCharWidth() == 8 &&
+ "no host or target supports non 8-bit chars");
+ assert(SourceValue.isLValue() &&
+ "LValueToRValueBitcast requires an lvalue operand!");
+
+ LValue SourceLValue;
+ APValue SourceRValue;
+ SourceLValue.setFrom(Info.Ctx, SourceValue);
+ if (!handleLValueToRValueConversion(
+ Info, BCE, BCE->getSubExpr()->getType().withConst(), SourceLValue,
+ SourceRValue, /*WantObjectRepresentation=*/true))
+ return false;
+
+ return handleRValueToRValueBitCast(Info, DestValue, SourceRValue, BCE);
+}
+
template <class Derived>
class ExprEvaluatorBase
: public ConstStmtVisitor<Derived, bool> {
@@ -10517,41 +10595,22 @@ bool VectorExprEvaluator::VisitCastExpr(const CastExpr *E) {
return Success(Elts, E);
}
case CK_BitCast: {
- // Evaluate the operand into an APInt we can extract from.
- llvm::APInt SValInt;
- if (!EvalAndBitcastToAPInt(Info, SE, SValInt))
+ APValue SVal;
+ if (!Evaluate(SVal, Info, SE))
+ return false;
+
+ if (!SVal.isInt() && !SVal.isFloat() && !SVal.isVector()) {
+ // Give up if the input isn't an int, float, or vector. For example, we
+ // reject "(v4i16)(intptr_t)&a".
+ Info.FFDiag(E, diag::note_constexpr_invalid_cast)
+ << 2 << Info.Ctx.getLangOpts().CPlusPlus;
return false;
- // Extract the elements
- QualType EltTy = VTy->getElementType();
- unsigned EltSize = Info.Ctx.getTypeSize(EltTy);
- bool BigEndian = Info.Ctx.getTargetInfo().isBigEndian();
- SmallVector<APValue, 4> Elts;
- if (EltTy->isRealFloatingType()) {
- const llvm::fltSemantics &Sem = Info.Ctx.getFloatTypeSemantics(EltTy);
- unsigned FloatEltSize = EltSize;
- if (&Sem == &APFloat::x87DoubleExtended())
- FloatEltSize = 80;
- for (unsigned i = 0; i < NElts; i++) {
- llvm::APInt Elt;
- if (BigEndian)
- Elt = SValInt.rotl(i * EltSize + FloatEltSize).trunc(FloatEltSize);
- else
- Elt = SValInt.rotr(i * EltSize).trunc(FloatEltSize);
- Elts.push_back(APValue(APFloat(Sem, Elt)));
- }
- } else if (EltTy->isIntegerType()) {
- for (unsigned i = 0; i < NElts; i++) {
- llvm::APInt Elt;
- if (BigEndian)
- Elt = SValInt.rotl(i*EltSize+EltSize).zextOrTrunc(EltSize);
- else
- Elt = SValInt.rotr(i*EltSize).zextOrTrunc(EltSize);
- Elts.push_back(APValue(APSInt(Elt, !EltTy->isSignedIntegerType())));
- }
- } else {
- return Error(E);
}
- return Success(Elts, E);
+
+ if (!handleRValueToRValueBitCast(Info, Result, SVal, E))
+ return false;
+
+ return true;
}
default:
return ExprEvaluatorBaseTy::VisitCastExpr(E);
diff --git a/clang/test/SemaCXX/constexpr-builtin-bit-cast.cpp b/clang/test/SemaCXX/constexpr-builtin-bit-cast.cpp
index a6ebe0572d063bb..48cdd31d5a00335 100644
--- a/clang/test/SemaCXX/constexpr-builtin-bit-cast.cpp
+++ b/clang/test/SemaCXX/constexpr-builtin-bit-cast.cpp
@@ -463,3 +463,64 @@ static_assert(bit_cast<long double>(ld539) == fivehundredandthirtynine, "");
static_assert(round_trip<__int128_t>(34.0L));
#endif
}
+
+namespace test_vector {
+
+typedef unsigned uint2 __attribute__((vector_size(2 * sizeof(unsigned))));
+typedef char byte8 __attribute__((vector_size(sizeof(unsigned long long))));
+
+constexpr uint2 test_vector = { 0x0C05FEFE, 0xCAFEBABE };
+
+static_assert(bit_cast<unsigned long long>(test_vector) == (LITTLE_END
+ ? 0xCAFEBABE0C05FEFE
+ : 0x0C05FEFECAFEBABE), "");
+
+static_assert(round_trip<uint2>(0xCAFEBABE0C05FEFEULL), "");
+static_assert(round_trip<byte8>(0xCAFEBABE0C05FEFEULL), "");
+
+typedef bool bool8 __attribute__((ext_vector_type(8)));
+typedef bool bool9 __attribute__((ext_vector_type(9)));
+typedef bool bool16 __attribute__((ext_vector_type(16)));
+typedef bool bool17 __attribute__((ext_vector_type(17)));
+typedef bool bool32 __attribute__((ext_vector_type(32)));
+typedef bool bool128 __attribute__((ext_vector_type(128)));
+
+static_assert(bit_cast<unsigned char>(bool8{1,0,1,0,1,0,1,0}) == (LITTLE_END ? 0x55 : 0xAA), "");
+static_assert(round_trip<bool8>(static_cast<unsigned char>(0)), "");
+static_assert(round_trip<bool8>(static_cast<unsigned char>(1)), "");
+static_assert(round_trip<bool8>(static_cast<unsigned char>(0x55)), "");
+
+static_assert(bit_cast<unsigned short>(bool16{1,1,1,1,1,0,0,0, 1,1,1,1,0,1,0,0}) == (LITTLE_END ? 0x2F1F : 0xF8F4), "");
+
+static_assert(round_trip<bool16>(static_cast<short>(0xCAFE)), "");
+static_assert(round_trip<bool32>(static_cast<int>(0xCAFEBABE)), "");
+static_assert(round_trip<bool128>(static_cast<__int128_t>(0xCAFEBABE0C05FEFEULL)), "");
+
+// expected-error at +2 {{constexpr variable 'bad_bool9_to_short' must be initialized by a constant expression}}
+// expected-note at +1 {{bit_cast involving type 'bool __attribute__((ext_vector_type(9)))' (vector of 9 'bool' values) is not allowed in a constant expression; element size 1 * element count 9 is not a multiple of the byte size 8}}
+constexpr unsigned short bad_bool9_to_short = __builtin_bit_cast(unsigned short, bool9{1,1,0,1,0,1,0,1,0});
+// expected-error at +2 {{constexpr variable 'bad_short_to_bool9' must be initialized by a constant expression}}
+// expected-note at +1 {{bit_cast involving type 'bool __attribute__((ext_vector_type(9)))' (vector of 9 'bool' values) is not allowed in a constant expression; element size 1 * element count 9 is not a multiple of the byte size 8}}
+constexpr bool9 bad_short_to_bool9 = __builtin_bit_cast(bool9, static_cast<unsigned short>(0));
+// expected-error at +2 {{constexpr variable 'bad_int_to_bool17' must be initialized by a constant expression}}
+// expected-note at +1 {{bit_cast involving type 'bool __attribute__((ext_vector_type(17)))' (vector of 17 'bool' values) is not allowed in a constant expression; element size 1 * element count 17 is not a multiple of the byte size 8}}
+constexpr bool17 bad_int_to_bool17 = __builtin_bit_cast(bool17, 0x0001CAFEU);
+
+struct fp80x4_s {
+ long double a, b, c, d;
+
+ constexpr bool operator==(const fp80x4_s& rhs) const {
+ return a == rhs.a && b == rhs.b && c == rhs.c && d == rhs.d;
+ }
+};
+typedef long double fp80x4_v __attribute__((ext_vector_type(4)));
+constexpr fp80x4_v test_vec_fp80 = { 0, 1, 2, 3 };
+constexpr fp80x4_s test_str_fp80 = { 0, 1, 2, 3 };
+
+constexpr static fp80x4_v struct2v = bit_cast<fp80x4_v>(test_str_fp80);
+
+static_assert(round_trip<fp80x4_v>(test_str_fp80), "");
+static_assert(bit_cast<fp80x4_s>(test_vec_fp80) == test_str_fp80, "");
+static_assert(bit_cast<fp80x4_s>(bit_cast<fp80x4_v>(test_str_fp80)) == test_str_fp80, "");
+
+}
More information about the cfe-commits
mailing list