Unverified Commit b45236f1 authored by DaPorkchop_'s avatar DaPorkchop_ Committed by GitHub
Browse files

[clang] Implement constexpr bit_cast for vectors (#66894)

This makes __builtin_bit_cast support converting to and from vector
types in a constexpr context.
parent 0f8615f4
Loading
Loading
Loading
Loading
+4 −1
Original line number Diff line number Diff line
@@ -317,7 +317,7 @@ def note_constexpr_memcpy_unsupported : Note<
  "source is not a contiguous array of at least %4 elements of type %3|"
  "destination is not a contiguous array of at least %4 elements of type %3}2">;
def note_constexpr_bit_cast_unsupported_type : Note<
  "constexpr bit_cast involving type %0 is not yet supported">;
  "constexpr bit cast involving type %0 is not yet supported">;
def note_constexpr_bit_cast_unsupported_bitfield : Note<
  "constexpr bit_cast involving bit-field is not yet supported">;
def note_constexpr_bit_cast_invalid_type : Note<
@@ -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">;
+175 −94
Original line number Diff line number Diff line
@@ -2737,53 +2737,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).
@@ -7023,10 +6976,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.
@@ -7113,6 +7067,72 @@ 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 (EltTy->isRealFloatingType() && &Info.Ctx.getFloatTypeSemantics(EltTy) ==
                                           &APFloat::x87DoubleExtended()) {
      // The layout for x86_fp80 vectors seems to be handled very inconsistently
      // by both clang and LLVM, so for now we won't allow bit_casts involving
      // it in a constexpr context.
      Info.FFDiag(BCE->getBeginLoc(),
                  diag::note_constexpr_bit_cast_unsupported_type)
          << EltTy;
      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();
@@ -7121,7 +7141,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;
@@ -7322,6 +7342,77 @@ 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;
    }
    if (EltTy->isRealFloatingType() && &Info.Ctx.getFloatTypeSemantics(EltTy) ==
                                           &APFloat::x87DoubleExtended()) {
      // The layout for x86_fp80 vectors seems to be handled very inconsistently
      // by both clang and LLVM, so for now we won't allow bit_casts involving
      // it in a constexpr context.
      Info.FFDiag(BCE->getBeginLoc(),
                  diag::note_constexpr_bit_cast_unsupported_type)
          << EltTy;
      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));
  }
@@ -7421,25 +7512,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);
@@ -7456,6 +7537,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> {
@@ -10540,41 +10640,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);
+3 −0
Original line number Diff line number Diff line
@@ -2152,6 +2152,9 @@ llvm::Constant *ConstantEmitter::tryEmitPrivate(const APValue &Value,
        Inits[I] = llvm::ConstantInt::get(CGM.getLLVMContext(), Elt.getInt());
      else if (Elt.isFloat())
        Inits[I] = llvm::ConstantFP::get(CGM.getLLVMContext(), Elt.getFloat());
      else if (Elt.isIndeterminate())
        Inits[I] = llvm::UndefValue::get(CGM.getTypes().ConvertType(
            DestType->castAs<VectorType>()->getElementType()));
      else
        llvm_unreachable("unsupported vector element type");
    }
+5 −4
Original line number Diff line number Diff line
@@ -140,11 +140,12 @@ void g28(void) {
  typedef short v12i16 __attribute((vector_size(24)));
  typedef long double v2f80 __attribute((vector_size(24)));
  // CHECK: @g28.a = internal global <1 x i64> <i64 10>
  // CHECK: @g28.b = internal global <12 x i16> <i16 0, i16 0, i16 0, i16 -32768, i16 16383, i16 0, i16 0, i16 0, i16 0, i16 -32768, i16 16384, i16 0>
  // CHECK: @g28.c = internal global <2 x x86_fp80> <x86_fp80 0xK3FFF8000000000000000, x86_fp80 0xK40008000000000000000>, align 32
  // @g28.b = internal global <12 x i16> <i16 0, i16 0, i16 0, i16 -32768, i16 16383, i16 0, i16 0, i16 0, i16 0, i16 -32768, i16 16384, i16 0>
  // @g28.c = internal global <2 x x86_fp80> <x86_fp80 0xK3FFF8000000000000000, x86_fp80 0xK40008000000000000000>, align 32
  static v1i64 a = (v1i64)10LL;
  static v12i16 b = (v12i16)(v2f80){1,2};
  static v2f80 c = (v2f80)(v12i16){0,0,0,-32768,16383,0,0,0,0,-32768,16384,0};
  //FIXME: support constant bitcast between vectors of x86_fp80
  //static v12i16 b = (v12i16)(v2f80){1,2};
  //static v2f80 c = (v2f80)(v12i16){0,0,0,-32768,16383,0,0,0,0,-32768,16384,0};
}

// PR13643
+66 −0
Original line number Diff line number Diff line
// RUN: %clang_cc1 -verify -std=c++2a -fsyntax-only -triple i386-pc-linux-gnu %s

// This is separate from constexpr-builtin-bit-cast.cpp because we want to
// compile for i386 so that sizeof(long double) is 12.

typedef long double fp80x2_v __attribute__((ext_vector_type(2)));

static_assert(sizeof(long double) == 12, "");
static_assert(sizeof(fp80x2_v) == 32, "");

struct fp80x2_s {
  char _data[2 * 10];
  unsigned char _pad[sizeof(fp80x2_v) - 2 * 10];

  constexpr bool operator==(const fp80x2_s& rhs) const {
    for (int i = 0; i < 2 * 10; ++i)
      if (_data[i] != rhs._data[i])
        return false;
    return true;
  }
};

namespace builtin_bit_cast {
  constexpr static fp80x2_v test_vec_fp80 = { 1, 2 };
  constexpr static fp80x2_s test_str_fp80 = { { 0, 0, 0, 0, 0, 0, 0, -128, -1, 63, 0, 0, 0, 0, 0, 0, 0, -128, 0, 64 }, {} };

  // expected-error@+2 {{static assertion expression is not an integral constant expression}}
  // expected-note@+1 {{constexpr bit cast involving type 'long double' is not yet supported}}
  static_assert(__builtin_bit_cast(fp80x2_s, test_vec_fp80) == test_str_fp80, "");

  // expected-error@+2 {{static assertion expression is not an integral constant expression}}
  // expected-note@+1 {{constexpr bit cast involving type 'long double' is not yet supported}}
  static_assert(__builtin_bit_cast(fp80x2_s, __builtin_bit_cast(fp80x2_v, test_str_fp80)) == test_str_fp80, "");

  // expected-error@+2 {{constexpr variable 'bad_str_fp80_0' must be initialized by a constant expression}}
  // expected-note@+1 {{constexpr bit cast involving type 'long double' is not yet supported}}
  constexpr static char bad_str_fp80_0 = __builtin_bit_cast(fp80x2_s, test_vec_fp80)._pad[0];

  // expected-error@+2 {{constexpr variable 'bad_str_fp80_1' must be initialized by a constant expression}}
  // expected-note@+1 {{constexpr bit cast involving type 'long double' is not yet supported}}
  constexpr static char bad_str_fp80_1 = __builtin_bit_cast(fp80x2_s, test_vec_fp80)._pad[1];

  // expected-error@+2 {{constexpr variable 'bad_str_fp80_11' must be initialized by a constant expression}}
  // expected-note@+1 {{constexpr bit cast involving type 'long double' is not yet supported}}
  constexpr static char bad_str_fp80_11 = __builtin_bit_cast(fp80x2_s, test_vec_fp80)._pad[11];

  // expected-error@+2 {{constexpr variable 'struct2v' must be initialized by a constant expression}}
  // expected-note@+1 {{constexpr bit cast involving type 'long double' is not yet supported}}
  constexpr static fp80x2_v struct2v = __builtin_bit_cast(fp80x2_v, test_str_fp80);
}

namespace c_cast {
  typedef short v12i16 __attribute((vector_size(24)));
  typedef long double v2f80 __attribute((vector_size(24)));

  // FIXME: re-enable the corresponding test cases in CodeGen/const-init.c when
  //  constexpr bitcast with x86_fp80 is supported

  // expected-error@+2 {{constexpr variable 'b' must be initialized by a constant expression}}
  // expected-note@+1 {{constexpr bit cast involving type 'long double' is not yet supported}}
  constexpr static v12i16 b = (v12i16)(v2f80){1,2};

  // expected-error@+2 {{constexpr variable 'c' must be initialized by a constant expression}}
  // expected-note@+1 {{constexpr bit cast involving type 'long double' is not yet supported}}
  constexpr static v2f80 c = (v2f80)(v12i16){0,0,0,-32768,16383,0,0,0,0,-32768,16384,0};
}
Loading