Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions clang/docs/LanguageExtensions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4182,7 +4182,7 @@ builtin, the mangler emits their usual pattern without any special treatment.
-----------------------

``__builtin_popcountg`` returns the number of 1 bits in the argument. The
argument can be of any unsigned integer type.
argument can be of any unsigned integer type or fixed boolean vector.

**Syntax**:

Expand Down Expand Up @@ -4214,7 +4214,13 @@ such as ``unsigned __int128`` and C23 ``unsigned _BitInt(N)``.

``__builtin_clzg`` (respectively ``__builtin_ctzg``) returns the number of
leading (respectively trailing) 0 bits in the first argument. The first argument
can be of any unsigned integer type.
can be of any unsigned integer type or fixed boolean vector.

For boolean vectors, these builtins interpret the vector like a bit-field where
the ith element of the vector is bit i of the bit-field, counting from the
least significant end. ``__builtin_clzg`` returns the number of zero elements at
the end of the vector, while ``__builtin_ctzg`` returns the number of zero
elements at the start of the vector.

If the first argument is 0 and an optional second argument of ``int`` type is
provided, then the second argument is returned. If the first argument is 0, but
Expand Down
4 changes: 3 additions & 1 deletion clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -143,10 +143,12 @@ Non-comprehensive list of changes in this release
- Added ``__builtin_masked_load`` and ``__builtin_masked_store`` for conditional
memory loads from vectors. Binds to the LLVM intrinsic of the same name.

- The ``__builtin_popcountg``, ``__builtin_ctzg``, and ``__builtin_clzg``
functions now accept fixed-size boolean vectors.

- Use of ``__has_feature`` to detect the ``ptrauth_qualifier`` and ``ptrauth_intrinsics``
features has been deprecated, and is restricted to the arm64e target only. The
correct method to check for these features is to test for the ``__PTRAUTH__``
macro.

- Added a new builtin, ``__builtin_dedup_pack``, to remove duplicate types from a parameter pack.
This feature is particularly useful in template metaprogramming for normalizing type lists.
Expand Down
46 changes: 40 additions & 6 deletions clang/lib/AST/ByteCode/InterpBuiltin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,22 @@ static void diagnoseNonConstexprBuiltin(InterpState &S, CodePtr OpPC,
S.CCEDiag(Loc, diag::note_invalid_subexpr_in_const_expr);
}

static llvm::APSInt convertBoolVectorToInt(const Pointer &Val) {
assert(Val.getFieldDesc()->isPrimitiveArray() &&
Val.getFieldDesc()->getElemQualType()->isBooleanType() &&
"Not a boolean vector");
unsigned NumElts = Val.getNumElems();

// Each element is one bit, so create an integer with NumElts bits.
llvm::APSInt Result(NumElts, 0);
for (unsigned I = 0; I < NumElts; ++I) {
if (Val.elem<bool>(I))
Result.setBit(I);
}

return Result;
}

static bool interp__builtin_is_constant_evaluated(InterpState &S, CodePtr OpPC,
const InterpFrame *Frame,
const CallExpr *Call) {
Expand Down Expand Up @@ -638,8 +654,14 @@ static bool interp__builtin_abs(InterpState &S, CodePtr OpPC,
static bool interp__builtin_popcount(InterpState &S, CodePtr OpPC,
const InterpFrame *Frame,
const CallExpr *Call) {
PrimType ArgT = *S.getContext().classify(Call->getArg(0)->getType());
APSInt Val = popToAPSInt(S.Stk, ArgT);
APSInt Val;
if (Call->getArg(0)->getType()->isExtVectorBoolType()) {
const Pointer &Arg = S.Stk.pop<Pointer>();
Val = convertBoolVectorToInt(Arg);
} else {
PrimType ArgT = *S.getContext().classify(Call->getArg(0)->getType());
Val = popToAPSInt(S.Stk, ArgT);
}
pushInteger(S, Val.popcount(), Call->getType());
return true;
}
Expand Down Expand Up @@ -935,8 +957,14 @@ static bool interp__builtin_clz(InterpState &S, CodePtr OpPC,
PrimType FallbackT = *S.getContext().classify(Call->getArg(1));
Fallback = popToAPSInt(S.Stk, FallbackT);
}
PrimType ValT = *S.getContext().classify(Call->getArg(0));
const APSInt &Val = popToAPSInt(S.Stk, ValT);
APSInt Val;
if (Call->getArg(0)->getType()->isExtVectorBoolType()) {
const Pointer &Arg = S.Stk.pop<Pointer>();
Val = convertBoolVectorToInt(Arg);
} else {
PrimType ValT = *S.getContext().classify(Call->getArg(0));
Val = popToAPSInt(S.Stk, ValT);
}

// When the argument is 0, the result of GCC builtins is undefined, whereas
// for Microsoft intrinsics, the result is the bit-width of the argument.
Expand Down Expand Up @@ -966,8 +994,14 @@ static bool interp__builtin_ctz(InterpState &S, CodePtr OpPC,
PrimType FallbackT = *S.getContext().classify(Call->getArg(1));
Fallback = popToAPSInt(S.Stk, FallbackT);
}
PrimType ValT = *S.getContext().classify(Call->getArg(0));
const APSInt &Val = popToAPSInt(S.Stk, ValT);
APSInt Val;
if (Call->getArg(0)->getType()->isExtVectorBoolType()) {
const Pointer &Arg = S.Stk.pop<Pointer>();
Val = convertBoolVectorToInt(Arg);
} else {
PrimType ValT = *S.getContext().classify(Call->getArg(0));
Val = popToAPSInt(S.Stk, ValT);
}

if (Val == 0) {
if (Fallback) {
Expand Down
42 changes: 39 additions & 3 deletions clang/lib/AST/ExprConstant.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11314,6 +11314,24 @@ static bool EvaluateVector(const Expr* E, APValue& Result, EvalInfo &Info) {
return VectorExprEvaluator(Info, Result).Visit(E);
}

static llvm::APInt ConvertBoolVectorToInt(const APValue &Val) {
assert(Val.isVector() && "expected vector APValue");
unsigned NumElts = Val.getVectorLength();

// Each element is one bit, so create an integer with NumElts bits.
llvm::APInt Result(NumElts, 0);

for (unsigned I = 0; I < NumElts; ++I) {
const APValue &Elt = Val.getVectorElt(I);
assert(Elt.isInt() && "expected integer element in bool vector");

if (Elt.getInt().getBoolValue())
Result.setBit(I);
}

return Result;
}

bool VectorExprEvaluator::VisitCastExpr(const CastExpr *E) {
const VectorType *VTy = E->getType()->castAs<VectorType>();
unsigned NElts = VTy->getNumElements();
Expand Down Expand Up @@ -13456,8 +13474,14 @@ bool IntExprEvaluator::VisitBuiltinCallExpr(const CallExpr *E,
case Builtin::BI__lzcnt:
case Builtin::BI__lzcnt64: {
APSInt Val;
if (!EvaluateInteger(E->getArg(0), Val, Info))
if (E->getArg(0)->getType()->isExtVectorBoolType()) {
APValue Vec;
if (!EvaluateVector(E->getArg(0), Vec, Info))
return false;
Val = ConvertBoolVectorToInt(Vec);
} else if (!EvaluateInteger(E->getArg(0), Val, Info)) {
return false;
}

std::optional<APSInt> Fallback;
if ((BuiltinOp == Builtin::BI__builtin_clzg ||
Expand Down Expand Up @@ -13542,8 +13566,14 @@ bool IntExprEvaluator::VisitBuiltinCallExpr(const CallExpr *E,
case Builtin::BI__builtin_ctzg:
case Builtin::BI__builtin_elementwise_cttz: {
APSInt Val;
if (!EvaluateInteger(E->getArg(0), Val, Info))
if (E->getArg(0)->getType()->isExtVectorBoolType()) {
APValue Vec;
if (!EvaluateVector(E->getArg(0), Vec, Info))
return false;
Val = ConvertBoolVectorToInt(Vec);
} else if (!EvaluateInteger(E->getArg(0), Val, Info)) {
return false;
}

std::optional<APSInt> Fallback;
if ((BuiltinOp == Builtin::BI__builtin_ctzg ||
Expand Down Expand Up @@ -13758,8 +13788,14 @@ bool IntExprEvaluator::VisitBuiltinCallExpr(const CallExpr *E,
case Builtin::BI__popcnt:
case Builtin::BI__popcnt64: {
APSInt Val;
if (!EvaluateInteger(E->getArg(0), Val, Info))
if (E->getArg(0)->getType()->isExtVectorBoolType()) {
APValue Vec;
if (!EvaluateVector(E->getArg(0), Vec, Info))
return false;
Val = ConvertBoolVectorToInt(Vec);
} else if (!EvaluateInteger(E->getArg(0), Val, Info)) {
return false;
}

return Success(Val.popcount(), E);
}
Expand Down
25 changes: 21 additions & 4 deletions clang/lib/CodeGen/CGBuiltin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1693,6 +1693,23 @@ getBitTestAtomicOrdering(BitTest::InterlockingKind I) {
llvm_unreachable("invalid interlocking");
}

static llvm::Value *EmitBitCountExpr(CodeGenFunction &CGF, const Expr *E) {
llvm::Value *ArgValue = CGF.EmitScalarExpr(E);
llvm::Type *ArgType = ArgValue->getType();

// Boolean vectors can be casted directly to its bitfield representation. We
// intentionally do not round up to the next power of two size and let LLVM
// handle the trailing bits.
if (auto *VT = dyn_cast<llvm::FixedVectorType>(ArgType);
VT && VT->getElementType()->isIntegerTy(1)) {
llvm::Type *StorageType =
llvm::Type::getIntNTy(CGF.getLLVMContext(), VT->getNumElements());
ArgValue = CGF.Builder.CreateBitCast(ArgValue, StorageType);
}

return ArgValue;
}

/// Emit a _bittest* intrinsic. These intrinsics take a pointer to an array of
/// bits and a bit position and read and optionally modify the bit at that
/// position. The position index can be arbitrarily large, i.e. it can be larger
Expand Down Expand Up @@ -2020,7 +2037,7 @@ Value *CodeGenFunction::EmitCheckedArgForBuiltin(const Expr *E,
assert((Kind == BCK_CLZPassedZero || Kind == BCK_CTZPassedZero) &&
"Unsupported builtin check kind");

Value *ArgValue = EmitScalarExpr(E);
Value *ArgValue = EmitBitCountExpr(*this, E);
if (!SanOpts.has(SanitizerKind::Builtin))
return ArgValue;

Expand Down Expand Up @@ -3334,7 +3351,7 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
E->getNumArgs() > 1;

Value *ArgValue =
HasFallback ? EmitScalarExpr(E->getArg(0))
HasFallback ? EmitBitCountExpr(*this, E->getArg(0))
: EmitCheckedArgForBuiltin(E->getArg(0), BCK_CTZPassedZero);

llvm::Type *ArgType = ArgValue->getType();
Expand Down Expand Up @@ -3371,7 +3388,7 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
E->getNumArgs() > 1;

Value *ArgValue =
HasFallback ? EmitScalarExpr(E->getArg(0))
HasFallback ? EmitBitCountExpr(*this, E->getArg(0))
: EmitCheckedArgForBuiltin(E->getArg(0), BCK_CLZPassedZero);

llvm::Type *ArgType = ArgValue->getType();
Expand Down Expand Up @@ -3456,7 +3473,7 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
case Builtin::BI__builtin_popcountl:
case Builtin::BI__builtin_popcountll:
case Builtin::BI__builtin_popcountg: {
Value *ArgValue = EmitScalarExpr(E->getArg(0));
Value *ArgValue = EmitBitCountExpr(*this, E->getArg(0));

llvm::Type *ArgType = ArgValue->getType();
Function *F = CGM.getIntrinsic(Intrinsic::ctpop, ArgType);
Expand Down
4 changes: 2 additions & 2 deletions clang/lib/Sema/SemaChecking.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2214,7 +2214,7 @@ static bool BuiltinPopcountg(Sema &S, CallExpr *TheCall) {

QualType ArgTy = Arg->getType();

if (!ArgTy->isUnsignedIntegerType()) {
if (!ArgTy->isUnsignedIntegerType() && !ArgTy->isExtVectorBoolType()) {
S.Diag(Arg->getBeginLoc(), diag::err_builtin_invalid_arg_type)
<< 1 << /* scalar */ 1 << /* unsigned integer ty */ 3 << /* no fp */ 0
<< ArgTy;
Expand All @@ -2239,7 +2239,7 @@ static bool BuiltinCountZeroBitsGeneric(Sema &S, CallExpr *TheCall) {

QualType Arg0Ty = Arg0->getType();

if (!Arg0Ty->isUnsignedIntegerType()) {
if (!Arg0Ty->isUnsignedIntegerType() && !Arg0Ty->isExtVectorBoolType()) {
S.Diag(Arg0->getBeginLoc(), diag::err_builtin_invalid_arg_type)
<< 1 << /* scalar */ 1 << /* unsigned integer ty */ 3 << /* no fp */ 0
<< Arg0Ty;
Expand Down
4 changes: 4 additions & 0 deletions clang/test/AST/ByteCode/builtin-functions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,7 @@ namespace SourceLocation {
}

#define BITSIZE(x) (sizeof(x) * 8)
constexpr bool __attribute__((ext_vector_type(4))) v4b{};
namespace popcount {
static_assert(__builtin_popcount(~0u) == __CHAR_BIT__ * sizeof(unsigned int), "");
static_assert(__builtin_popcount(0) == 0, "");
Expand All @@ -471,6 +472,7 @@ namespace popcount {
static_assert(__builtin_popcountg(0ul) == 0, "");
static_assert(__builtin_popcountg(~0ull) == __CHAR_BIT__ * sizeof(unsigned long long), "");
static_assert(__builtin_popcountg(0ull) == 0, "");
static_assert(__builtin_popcountg(v4b) == 0, "");
#ifdef __SIZEOF_INT128__
static_assert(__builtin_popcountg(~(unsigned __int128)0) == __CHAR_BIT__ * sizeof(unsigned __int128), "");
static_assert(__builtin_popcountg((unsigned __int128)0) == 0, "");
Expand Down Expand Up @@ -743,6 +745,7 @@ namespace clz {
char clz62[__builtin_clzg((unsigned _BitInt(128))0xf) == BITSIZE(_BitInt(128)) - 4 ? 1 : -1];
char clz63[__builtin_clzg((unsigned _BitInt(128))0xf, 42) == BITSIZE(_BitInt(128)) - 4 ? 1 : -1];
#endif
char clz64[__builtin_clzg(v4b, 0) == 0 ? 1 : -1];
}

namespace ctz {
Expand Down Expand Up @@ -813,6 +816,7 @@ namespace ctz {
char ctz62[__builtin_ctzg((unsigned _BitInt(128))1 << (BITSIZE(_BitInt(128)) - 1)) == BITSIZE(_BitInt(128)) - 1 ? 1 : -1];
char ctz63[__builtin_ctzg((unsigned _BitInt(128))1 << (BITSIZE(_BitInt(128)) - 1), 42) == BITSIZE(_BitInt(128)) - 1 ? 1 : -1];
#endif
char clz64[__builtin_ctzg(v4b, 0) == 0 ? 1 : -1];
}

namespace bswap {
Expand Down
Loading
Loading