Skip to content

[clang] Implement __builtin_{clzg,ctzg} #83431

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 21, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
41 changes: 41 additions & 0 deletions clang/docs/LanguageExtensions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3504,6 +3504,47 @@ argument can be of any unsigned integer type.
``__builtin_popcount{,l,ll}`` builtins, with support for other integer types,
such as ``unsigned __int128`` and C23 ``unsigned _BitInt(N)``.

``__builtin_clzg`` and ``__builtin_ctzg``
-----------------------------------------

``__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.

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
only one argument is provided, then the behavior is undefined.

**Syntax**:

.. code-block:: c++

int __builtin_clzg(type x[, int fallback])
int __builtin_ctzg(type x[, int fallback])

**Examples**:

.. code-block:: c++

unsigned int x = 1;
int x_lz = __builtin_clzg(x);
int x_tz = __builtin_ctzg(x);

unsigned long y = 2;
int y_lz = __builtin_clzg(y);
int y_tz = __builtin_ctzg(y);

unsigned _BitInt(128) z = 4;
int z_lz = __builtin_clzg(z);
int z_tz = __builtin_ctzg(z);

**Description**:

``__builtin_clzg`` (respectively ``__builtin_ctzg``) is meant to be a
type-generic alternative to the ``__builtin_clz{,l,ll}`` (respectively
``__builtin_ctz{,l,ll}``) builtins, with support for other integer types, such
as ``unsigned __int128`` and C23 ``unsigned _BitInt(N)``.

Multiprecision Arithmetic Builtins
----------------------------------

Expand Down
12 changes: 10 additions & 2 deletions clang/include/clang/Basic/Builtins.td
Original file line number Diff line number Diff line change
Expand Up @@ -660,15 +660,23 @@ def Clz : Builtin, BitShort_Int_Long_LongLongTemplate {
let Prototype = "int(unsigned T)";
}

// FIXME: Add int clzimax(uintmax_t)
def Clzg : Builtin {
let Spellings = ["__builtin_clzg"];
let Attributes = [NoThrow, Const, CustomTypeChecking];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should Constexpr be in this list of attributes?

https://godbolt.org/z/Efd3EW5xE

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will implement constexpr support. It requires adding a few lines in clang/lib/AST/ExprConstant.cpp and clang/lib/AST/Interp/InterpBuiltin.cpp.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Filed #86549

let Prototype = "int(...)";
}

def Ctz : Builtin, BitShort_Int_Long_LongLongTemplate {
let Spellings = ["__builtin_ctz"];
let Attributes = [NoThrow, Const, Constexpr];
let Prototype = "int(unsigned T)";
}

// FIXME: Add int ctzimax(uintmax_t)
def Ctzg : Builtin {
let Spellings = ["__builtin_ctzg"];
let Attributes = [NoThrow, Const, CustomTypeChecking];
let Prototype = "int(...)";
}

def FFS : Builtin, BitInt_Long_LongLongTemplate {
let Spellings = ["__builtin_ffs"];
Expand Down
15 changes: 8 additions & 7 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -11978,13 +11978,14 @@ def err_builtin_launder_invalid_arg : Error<
"'__builtin_launder' is not allowed">;

def err_builtin_invalid_arg_type: Error <
"%ordinal0 argument must be a "
"%select{vector, integer or floating point type|matrix|"
"pointer to a valid matrix element type|"
"signed integer or floating point type|vector type|"
"floating point type|"
"vector of integers|"
"type of unsigned integer}1 (was %2)">;
"%ordinal0 argument must be "
"%select{a vector, integer or floating point type|a matrix|"
"a pointer to a valid matrix element type|"
"a signed integer or floating point type|a vector type|"
"a floating point type|"
"a vector of integers|"
"an unsigned integer|"
"an 'int'}1 (was %2)">;

def err_builtin_matrix_disabled: Error<
"matrix types extension is disabled. Pass -fenable-matrix to enable it">;
Expand Down
46 changes: 38 additions & 8 deletions clang/lib/CodeGen/CGBuiltin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3128,36 +3128,66 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
case Builtin::BI__builtin_ctzs:
case Builtin::BI__builtin_ctz:
case Builtin::BI__builtin_ctzl:
case Builtin::BI__builtin_ctzll: {
Value *ArgValue = EmitCheckedArgForBuiltin(E->getArg(0), BCK_CTZPassedZero);
case Builtin::BI__builtin_ctzll:
case Builtin::BI__builtin_ctzg: {
bool HasFallback = BuiltinIDIfNoAsmLabel == Builtin::BI__builtin_ctzg &&
E->getNumArgs() > 1;

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

llvm::Type *ArgType = ArgValue->getType();
Function *F = CGM.getIntrinsic(Intrinsic::cttz, ArgType);

llvm::Type *ResultType = ConvertType(E->getType());
Value *ZeroUndef = Builder.getInt1(getTarget().isCLZForZeroUndef());
Value *ZeroUndef =
Builder.getInt1(HasFallback || getTarget().isCLZForZeroUndef());
Value *Result = Builder.CreateCall(F, {ArgValue, ZeroUndef});
if (Result->getType() != ResultType)
Result = Builder.CreateIntCast(Result, ResultType, /*isSigned*/true,
"cast");
return RValue::get(Result);
if (!HasFallback)
return RValue::get(Result);

Value *Zero = Constant::getNullValue(ArgType);
Value *IsZero = Builder.CreateICmpEQ(ArgValue, Zero, "iszero");
Value *FallbackValue = EmitScalarExpr(E->getArg(1));
Value *ResultOrFallback =
Builder.CreateSelect(IsZero, FallbackValue, Result, "ctzg");
return RValue::get(ResultOrFallback);
}
case Builtin::BI__builtin_clzs:
case Builtin::BI__builtin_clz:
case Builtin::BI__builtin_clzl:
case Builtin::BI__builtin_clzll: {
Value *ArgValue = EmitCheckedArgForBuiltin(E->getArg(0), BCK_CLZPassedZero);
case Builtin::BI__builtin_clzll:
case Builtin::BI__builtin_clzg: {
bool HasFallback = BuiltinIDIfNoAsmLabel == Builtin::BI__builtin_clzg &&
E->getNumArgs() > 1;

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

llvm::Type *ArgType = ArgValue->getType();
Function *F = CGM.getIntrinsic(Intrinsic::ctlz, ArgType);

llvm::Type *ResultType = ConvertType(E->getType());
Value *ZeroUndef = Builder.getInt1(getTarget().isCLZForZeroUndef());
Value *ZeroUndef =
Builder.getInt1(HasFallback || getTarget().isCLZForZeroUndef());
Value *Result = Builder.CreateCall(F, {ArgValue, ZeroUndef});
if (Result->getType() != ResultType)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if the way this is handling isCLZForZeroUndef() is what you want?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since in the additions below I check if the argument itself is zero instead of checking the result of ctlz, I don't see why the ctlz result for zero being undefined would be a problem.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was more thinking of the opposite: we don't need to make the result of the clz defined if we're not using it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. Is it worth making tests for this with a target where isCLZForZeroUndef() is false?

Result = Builder.CreateIntCast(Result, ResultType, /*isSigned*/true,
"cast");
return RValue::get(Result);
if (!HasFallback)
return RValue::get(Result);

Value *Zero = Constant::getNullValue(ArgType);
Value *IsZero = Builder.CreateICmpEQ(ArgValue, Zero, "iszero");
Value *FallbackValue = EmitScalarExpr(E->getArg(1));
Value *ResultOrFallback =
Builder.CreateSelect(IsZero, FallbackValue, Result, "clzg");
return RValue::get(ResultOrFallback);
}
case Builtin::BI__builtin_ffs:
case Builtin::BI__builtin_ffsl:
Expand Down
47 changes: 47 additions & 0 deletions clang/lib/Sema/SemaChecking.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2214,6 +2214,48 @@ static bool SemaBuiltinPopcountg(Sema &S, CallExpr *TheCall) {
return false;
}

/// Checks that __builtin_{clzg,ctzg} was called with a first argument, which is
/// an unsigned integer, and an optional second argument, which is promoted to
/// an 'int'.
static bool SemaBuiltinCountZeroBitsGeneric(Sema &S, CallExpr *TheCall) {
if (checkArgCountRange(S, TheCall, 1, 2))
return true;

ExprResult Arg0Res = S.DefaultLvalueConversion(TheCall->getArg(0));
if (Arg0Res.isInvalid())
return true;

Expr *Arg0 = Arg0Res.get();
TheCall->setArg(0, Arg0);

QualType Arg0Ty = Arg0->getType();

if (!Arg0Ty->isUnsignedIntegerType()) {
S.Diag(Arg0->getBeginLoc(), diag::err_builtin_invalid_arg_type)
<< 1 << /*unsigned integer ty*/ 7 << Arg0Ty;
return true;
}

if (TheCall->getNumArgs() > 1) {
ExprResult Arg1Res = S.UsualUnaryConversions(TheCall->getArg(1));
if (Arg1Res.isInvalid())
return true;

Expr *Arg1 = Arg1Res.get();
TheCall->setArg(1, Arg1);

QualType Arg1Ty = Arg1->getType();

if (!Arg1Ty->isSpecificBuiltinType(BuiltinType::Int)) {
S.Diag(Arg1->getBeginLoc(), diag::err_builtin_invalid_arg_type)
<< 2 << /*'int' ty*/ 8 << Arg1Ty;
return true;
}
}

return false;
}

ExprResult
Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID,
CallExpr *TheCall) {
Expand Down Expand Up @@ -2990,6 +3032,11 @@ Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID,
if (SemaBuiltinPopcountg(*this, TheCall))
return ExprError();
break;
case Builtin::BI__builtin_clzg:
case Builtin::BI__builtin_ctzg:
if (SemaBuiltinCountZeroBitsGeneric(*this, TheCall))
return ExprError();
break;
}

if (getLangOpts().HLSL && CheckHLSLBuiltinFunctionCall(BuiltinID, TheCall))
Expand Down
Loading