diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst index 1830480b1285d7ed5c3c17b2d2889fb3224e8461..6d33035aebca33dd0c92fc0b2bfcccaabba4938c 100644 --- a/clang/docs/LanguageExtensions.rst +++ b/clang/docs/LanguageExtensions.rst @@ -5175,6 +5175,7 @@ The following builtin intrinsics can be used in constant expressions: * ``__builtin_ctzl`` * ``__builtin_ctzll`` * ``__builtin_ctzs`` +* ``__builtin_ctzg`` * ``__builtin_ffs`` * ``__builtin_ffsl`` * ``__builtin_ffsll`` diff --git a/clang/include/clang/Basic/Builtins.def b/clang/include/clang/Basic/Builtins.def index 3f2cbcedc4b51b1f3edb5d97ed566d4b08e2adca..9d0fefcda301efebc4ba14dfb7dcd4168271105f 100644 --- a/clang/include/clang/Basic/Builtins.def +++ b/clang/include/clang/Basic/Builtins.def @@ -512,6 +512,7 @@ BUILTIN(__builtin_ctzs , "iUs" , "ncE") BUILTIN(__builtin_ctz , "iUi" , "ncE") BUILTIN(__builtin_ctzl , "iULi" , "ncE") BUILTIN(__builtin_ctzll, "iULLi", "ncE") +BUILTIN(__builtin_ctzg, "i.", "nctE") // TODO: int ctzimax(uintmax_t) BUILTIN(__builtin_ffs , "ii" , "FncE") BUILTIN(__builtin_ffsl , "iLi" , "FncE") diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index bde46171dde1658b0356860c1ac2cb178685c1b9..a3a59357a2ec5c6499b4766ca7c7d45150b8f6ea 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -11737,12 +11737,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}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">; diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 99ae88a6cd692418dabcb37265aa901c7b96859c..4441da51cecdac3983c049dea7b46122b16a7339 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -12138,12 +12138,26 @@ bool IntExprEvaluator::VisitBuiltinCallExpr(const CallExpr *E, case Builtin::BI__builtin_ctz: case Builtin::BI__builtin_ctzl: case Builtin::BI__builtin_ctzll: - case Builtin::BI__builtin_ctzs: { + case Builtin::BI__builtin_ctzs: + case Builtin::BI__builtin_ctzg: { APSInt Val; if (!EvaluateInteger(E->getArg(0), Val, Info)) return false; - if (!Val) + + std::optional Fallback; + if (BuiltinOp == Builtin::BI__builtin_ctzg && E->getNumArgs() > 1) { + APSInt FallbackTemp; + if (!EvaluateInteger(E->getArg(1), FallbackTemp, Info)) + return false; + Fallback = FallbackTemp; + } + + if (!Val) { + if (Fallback) + return Success(*Fallback, E); + return Error(E); + } return Success(Val.countr_zero(), E); } diff --git a/clang/lib/AST/Interp/ByteCodeEmitter.cpp b/clang/lib/AST/Interp/ByteCodeEmitter.cpp index f2072f974c40840d2cf077b1855580c594406323..746fce9fde2b326ea71a03a502464248d79089a9 100644 --- a/clang/lib/AST/Interp/ByteCodeEmitter.cpp +++ b/clang/lib/AST/Interp/ByteCodeEmitter.cpp @@ -9,6 +9,7 @@ #include "ByteCodeEmitter.h" #include "Context.h" #include "Floating.h" +#include "IntegralAP.h" #include "Opcode.h" #include "Program.h" #include "clang/AST/ASTLambda.h" diff --git a/clang/lib/AST/Interp/ByteCodeExprGen.cpp b/clang/lib/AST/Interp/ByteCodeExprGen.cpp index 9f3eb158576fd8b228c26a4c02fee5fba63d5c2e..878d6ea9195f9d51d311b95c050bbf23bfba6393 100644 --- a/clang/lib/AST/Interp/ByteCodeExprGen.cpp +++ b/clang/lib/AST/Interp/ByteCodeExprGen.cpp @@ -146,8 +146,10 @@ bool ByteCodeExprGen::VisitCastExpr(const CastExpr *CE) { if (!this->visit(SubExpr)) return false; - if (FromT == ToT) + if (FromT == ToT) { + assert(ToT != PT_IntAP && ToT != PT_IntAPS); return true; + } return this->emitCast(*FromT, *ToT, CE); } @@ -1059,6 +1061,10 @@ bool ByteCodeExprGen::visitZeroInitializer(QualType QT, return this->emitZeroSint64(E); case PT_Uint64: return this->emitZeroUint64(E); + case PT_IntAP: + case PT_IntAPS: + assert(false); + return false; case PT_Ptr: return this->emitNullPtr(E); case PT_FnPtr: @@ -1227,6 +1233,10 @@ bool ByteCodeExprGen::emitConst(T Value, const Expr *E) { return this->emitConstSint64(Value, E); case PT_Uint64: return this->emitConstUint64(Value, E); + case PT_IntAP: + case PT_IntAPS: + assert(false); + return false; case PT_Bool: return this->emitConstBool(Value, E); case PT_Ptr: @@ -1729,7 +1739,7 @@ bool ByteCodeExprGen::VisitBuiltinCallExpr(const CallExpr *E) { return false; } - if (!this->emitCallBI(Func, E)) + if (!this->emitCallBI(Func, E, E)) return false; QualType ReturnType = E->getCallReturnType(Ctx.getASTContext()); diff --git a/clang/lib/AST/Interp/Context.cpp b/clang/lib/AST/Interp/Context.cpp index eeb7fa9379f5ccd7ab8639ca0a71d9ab287e1955..ef6409274aad4b67adf658706c5532e00f901b29 100644 --- a/clang/lib/AST/Interp/Context.cpp +++ b/clang/lib/AST/Interp/Context.cpp @@ -108,7 +108,7 @@ std::optional Context::classify(QualType T) const { case 8: return PT_Sint8; default: - return {}; + return PT_IntAPS; } } @@ -123,7 +123,7 @@ std::optional Context::classify(QualType T) const { case 8: return PT_Uint8; default: - return {}; + return PT_IntAP; } } diff --git a/clang/lib/AST/Interp/Context.h b/clang/lib/AST/Interp/Context.h index 19d480d912116b3145eeaf70611dd30a7c7abaaa..6547953981e23b2cb0041403e0719b7f15e0894e 100644 --- a/clang/lib/AST/Interp/Context.h +++ b/clang/lib/AST/Interp/Context.h @@ -18,12 +18,14 @@ #include "InterpStack.h" #include "clang/AST/APValue.h" +#include "clang/AST/ASTContext.h" namespace clang { class ASTContext; class LangOptions; class FunctionDecl; class VarDecl; +class APValue; namespace interp { class Function; @@ -60,9 +62,23 @@ public: /// Return the floating-point semantics for T. const llvm::fltSemantics &getFloatSemantics(QualType T) const; + uint32_t getBitWidth(QualType T) const { return Ctx.getIntWidth(T); } + /// Classifies an expression. std::optional classify(QualType T) const; + /// Classifies an expression. + std::optional classify(const Expr *E) const { + assert(E); + if (E->isGLValue()) { + if (E->getType()->isFunctionType()) + return PT_FnPtr; + return PT_Ptr; + } + + return classify(E->getType()); + } + const CXXMethodDecl * getOverridingFunction(const CXXRecordDecl *DynamicDecl, const CXXRecordDecl *StaticDecl, diff --git a/clang/lib/AST/Interp/Descriptor.cpp b/clang/lib/AST/Interp/Descriptor.cpp index ccd2a993e9f7d3bb0465ee22608aee62440c1549..9453a8c3b925e3f400f9c065272d333a4aa6c581 100644 --- a/clang/lib/AST/Interp/Descriptor.cpp +++ b/clang/lib/AST/Interp/Descriptor.cpp @@ -10,6 +10,7 @@ #include "Boolean.h" #include "Floating.h" #include "FunctionPointer.h" +#include "IntegralAP.h" #include "Pointer.h" #include "PrimType.h" #include "Record.h" @@ -181,6 +182,10 @@ static BlockCtorFn getCtorPrim(PrimType Type) { // constructor called. if (Type == PT_Float) return ctorTy::T>; + if (Type == PT_IntAP) + return ctorTy::T>; + if (Type == PT_IntAPS) + return ctorTy::T>; COMPOSITE_TYPE_SWITCH(Type, return ctorTy, return nullptr); } @@ -190,6 +195,10 @@ static BlockDtorFn getDtorPrim(PrimType Type) { // destructor called, since they might allocate memory. if (Type == PT_Float) return dtorTy::T>; + if (Type == PT_IntAP) + return dtorTy::T>; + if (Type == PT_IntAPS) + return dtorTy::T>; COMPOSITE_TYPE_SWITCH(Type, return dtorTy, return nullptr); } diff --git a/clang/lib/AST/Interp/Disasm.cpp b/clang/lib/AST/Interp/Disasm.cpp index 35ed5d128697197c28899be1ee28392ebde23a42..bd97312e4f87b3139f24533b88e10b5c41c7e0ad 100644 --- a/clang/lib/AST/Interp/Disasm.cpp +++ b/clang/lib/AST/Interp/Disasm.cpp @@ -12,6 +12,7 @@ #include "Floating.h" #include "Function.h" +#include "IntegralAP.h" #include "Opcode.h" #include "PrimType.h" #include "Program.h" diff --git a/clang/lib/AST/Interp/EvalEmitter.cpp b/clang/lib/AST/Interp/EvalEmitter.cpp index f22cca90d4f4120ca91770835e46e936dc83d864..537afa9513404d102a4594eeff86fc54eeef8589 100644 --- a/clang/lib/AST/Interp/EvalEmitter.cpp +++ b/clang/lib/AST/Interp/EvalEmitter.cpp @@ -7,7 +7,9 @@ //===----------------------------------------------------------------------===// #include "EvalEmitter.h" +#include "ByteCodeGenError.h" #include "Context.h" +#include "IntegralAP.h" #include "Interp.h" #include "Opcode.h" #include "Program.h" diff --git a/clang/lib/AST/Interp/Integral.h b/clang/lib/AST/Interp/Integral.h index de588ab8c9f1915051307f0e26bdb080266707c6..a74a4a717f320d863b7938c0c337528ec8b56d9c 100644 --- a/clang/lib/AST/Interp/Integral.h +++ b/clang/lib/AST/Interp/Integral.h @@ -29,6 +29,8 @@ namespace interp { using APInt = llvm::APInt; using APSInt = llvm::APSInt; +template class IntegralAP; + // Helper structure to select the representation. template struct Repr; template <> struct Repr<8, false> { using Type = uint8_t; }; @@ -61,6 +63,8 @@ private: template explicit Integral(T V) : V(V) {} public: + using AsUnsigned = Integral; + /// Zero-initializes an integral. Integral() : V(0) {} diff --git a/clang/lib/AST/Interp/IntegralAP.h b/clang/lib/AST/Interp/IntegralAP.h new file mode 100644 index 0000000000000000000000000000000000000000..2f0d34b86bf32b4d79937de51b23d2ed3b21ba14 --- /dev/null +++ b/clang/lib/AST/Interp/IntegralAP.h @@ -0,0 +1,264 @@ +//===--- Integral.h - Wrapper for numeric types for the VM ------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// Defines the VM types and helpers operating on types. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_AST_INTERP_INTEGRAL_AP_H +#define LLVM_CLANG_AST_INTERP_INTEGRAL_AP_H + +#include "clang/AST/APValue.h" +#include "clang/AST/ComparisonCategories.h" +#include "llvm/ADT/APSInt.h" +#include "llvm/Support/MathExtras.h" +#include "llvm/Support/raw_ostream.h" +#include +#include + +#include "Primitives.h" + +namespace clang { +namespace interp { + +using APInt = llvm::APInt; +using APSInt = llvm::APSInt; +template class Integral; +class Boolean; + +template class IntegralAP final { +public: + APSInt V; + +public: + using AsUnsigned = IntegralAP; + + template + IntegralAP(T Value) : V(APInt(sizeof(T) * 8, Value, std::is_signed_v)) {} + + IntegralAP(APInt V) : V(V) {} + IntegralAP(APSInt V) : V(V) {} + /// Arbitrary value for uninitialized variables. + IntegralAP() : V(APSInt::getMaxValue(1024, Signed)) {} + + IntegralAP operator-() const { return IntegralAP(-V); } + bool operator>(IntegralAP RHS) const { return V > RHS.V; } + bool operator>=(IntegralAP RHS) const { return V >= RHS.V; } + bool operator<(IntegralAP RHS) const { return V < RHS.V; } + bool operator<=(IntegralAP RHS) const { return V <= RHS.V; } + + explicit operator bool() const { return !V.isZero(); } + explicit operator int8_t() const { return V.getSExtValue(); } + explicit operator uint8_t() const { return V.getZExtValue(); } + explicit operator int16_t() const { return V.getSExtValue(); } + explicit operator uint16_t() const { return V.getZExtValue(); } + explicit operator int32_t() const { return V.getSExtValue(); } + explicit operator uint32_t() const { return V.getZExtValue(); } + explicit operator int64_t() const { return V.getSExtValue(); } + explicit operator uint64_t() const { return V.getZExtValue(); } + + template static IntegralAP from(T Value, unsigned NumBits = 0) { + assert(NumBits > 0); + APSInt Copy = APSInt(APInt(NumBits, Value, Signed), !Signed); + + return IntegralAP(Copy); + } + + template + static IntegralAP from(IntegralAP V, unsigned NumBits = 0) { + if constexpr (Signed == InputSigned) + return V; + + APSInt Copy = V.V; + Copy.setIsSigned(Signed); + + return IntegralAP(Copy); + } + + template + static IntegralAP from(Integral I) { + // FIXME: Take bits parameter. + APSInt Copy = + APSInt(APInt(128, static_cast(I), InputSigned), !Signed); + Copy.setIsSigned(Signed); + + assert(Copy.isSigned() == Signed); + return IntegralAP(Copy); + } + + static IntegralAP from(const Boolean &B) { + assert(false); + return IntegralAP::zero(); + } + + static IntegralAP zero() { + assert(false); + return IntegralAP(0); + } + + // FIXME: This can't be static if the bitwidth depends on V. + static constexpr unsigned bitWidth() { return 128; } + + APSInt toAPSInt(unsigned Bits = 0) const { return V; } + APValue toAPValue() const { return APValue(V); } + + bool isZero() const { return V.isZero(); } + bool isPositive() const { return V.isNonNegative(); } + bool isNegative() const { return !V.isNonNegative(); } + bool isMin() const { return V.isMinValue(); } + bool isMax() const { return V.isMaxValue(); } + static bool isSigned() { return Signed; } + bool isMinusOne() const { return Signed && V == -1; } + + unsigned countLeadingZeros() const { return V.countl_zero(); } + + void print(llvm::raw_ostream &OS) const { OS << V; } + std::string toDiagnosticString(const ASTContext &Ctx) const { + std::string NameStr; + llvm::raw_string_ostream OS(NameStr); + print(OS); + return NameStr; + } + + IntegralAP truncate(unsigned bitWidth) const { + assert(false); + return V; + } + + IntegralAP toUnsigned() const { + APSInt Copy = V; + Copy.setIsSigned(false); + return IntegralAP(Copy); + } + + ComparisonCategoryResult compare(const IntegralAP &RHS) const { + return Compare(V, RHS.V); + } + + static bool increment(IntegralAP A, IntegralAP *R) { + assert(false); + *R = IntegralAP(A.V + 1); + return false; + } + + static bool decrement(IntegralAP A, IntegralAP *R) { + assert(false); + *R = IntegralAP(A.V - 1); + return false; + } + + static bool add(IntegralAP A, IntegralAP B, unsigned OpBits, IntegralAP *R) { + return CheckAddUB(A, B, OpBits, R); + } + + static bool sub(IntegralAP A, IntegralAP B, unsigned OpBits, IntegralAP *R) { + /// FIXME: Gotta check if the result fits into OpBits bits. + return CheckSubUB(A, B, R); + } + + static bool mul(IntegralAP A, IntegralAP B, unsigned OpBits, IntegralAP *R) { + assert(false); + // return CheckMulUB(A.V, B.V, R->V); + return false; + } + + static bool rem(IntegralAP A, IntegralAP B, unsigned OpBits, IntegralAP *R) { + assert(false); + *R = IntegralAP(A.V % B.V); + return false; + } + + static bool div(IntegralAP A, IntegralAP B, unsigned OpBits, IntegralAP *R) { + assert(false); + *R = IntegralAP(A.V / B.V); + return false; + } + + static bool bitAnd(IntegralAP A, IntegralAP B, unsigned OpBits, + IntegralAP *R) { + assert(false); + *R = IntegralAP(A.V & B.V); + return false; + } + + static bool bitOr(IntegralAP A, IntegralAP B, unsigned OpBits, + IntegralAP *R) { + assert(false); + *R = IntegralAP(A.V | B.V); + return false; + } + + static bool bitXor(IntegralAP A, IntegralAP B, unsigned OpBits, + IntegralAP *R) { + assert(false); + *R = IntegralAP(A.V ^ B.V); + return false; + } + + static bool neg(const IntegralAP &A, IntegralAP *R) { + APSInt AI = A.V; + + AI.setIsSigned(Signed); + *R = IntegralAP(AI); + return false; + } + + static bool comp(IntegralAP A, IntegralAP *R) { + assert(false); + *R = IntegralAP(~A.V); + return false; + } + + static void shiftLeft(const IntegralAP A, const IntegralAP B, unsigned OpBits, + IntegralAP *R) { + *R = IntegralAP(A.V << B.V.getZExtValue()); + } + + static void shiftRight(const IntegralAP A, const IntegralAP B, + unsigned OpBits, IntegralAP *R) { + *R = IntegralAP(A.V >> B.V.getZExtValue()); + } + +private: + static bool CheckAddUB(const IntegralAP &A, const IntegralAP &B, + unsigned BitWidth, IntegralAP *R) { + if (!A.isSigned()) { + R->V = A.V + B.V; + return false; + } + + const APSInt &LHS = A.V; + const APSInt &RHS = B.V; + + APSInt Value(LHS.extend(BitWidth) + RHS.extend(BitWidth), false); + APSInt Result = Value.trunc(LHS.getBitWidth()); + if (Result.extend(BitWidth) != Value) + return true; + + R->V = Result; + return false; + } + + static bool CheckSubUB(const IntegralAP &A, const IntegralAP &B, + IntegralAP *R) { + R->V = A.V - B.V; + return false; // Success! + } +}; + +template +inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, + IntegralAP I) { + I.print(OS); + return OS; +} + +} // namespace interp +} // namespace clang + +#endif diff --git a/clang/lib/AST/Interp/Interp.h b/clang/lib/AST/Interp/Interp.h index ff67e873a08445f72494cdf2715294fdfd476f48..4aa60790fbfe8681509dc7bb85651452ecf47f78 100644 --- a/clang/lib/AST/Interp/Interp.h +++ b/clang/lib/AST/Interp/Interp.h @@ -169,7 +169,8 @@ bool CheckFloatResult(InterpState &S, CodePtr OpPC, APFloat::opStatus Status); bool Interpret(InterpState &S, APValue &Result); /// Interpret a builtin function. -bool InterpretBuiltin(InterpState &S, CodePtr OpPC, const Function *F); +bool InterpretBuiltin(InterpState &S, CodePtr OpPC, const Function *F, + const CallExpr *Call); enum class ArithOp { Add, Sub }; @@ -249,7 +250,12 @@ bool AddSubMulHelper(InterpState &S, CodePtr OpPC, unsigned Bits, const T &LHS, return true; } else { S.CCEDiag(E, diag::note_constexpr_overflow) << Value << Type; - return S.noteUndefinedBehavior(); + + if (!S.noteUndefinedBehavior()) { + S.Stk.pop(); + return false; + } + return true; } } @@ -1466,7 +1472,7 @@ bool CastFloatingIntegral(InterpState &S, CodePtr OpPC) { S.Stk.push(T(F.isNonZero())); return true; } else { - APSInt Result(std::max(8u, T::bitWidth() + 1), + APSInt Result(std::max(8u, T::bitWidth()), /*IsUnsigned=*/!T::isSigned()); auto Status = F.convertToInteger(Result); @@ -1541,9 +1547,10 @@ inline bool Shr(InterpState &S, CodePtr OpPC) { if (!CheckShift(S, OpPC, LHS, RHS, Bits)) return false; - Integral R; - Integral::shiftRight(LHS.toUnsigned(), RHS, Bits, &R); - S.Stk.push(R); + typename LT::AsUnsigned R; + LT::AsUnsigned::shiftRight(LT::AsUnsigned::from(LHS), + LT::AsUnsigned::from(RHS), Bits, &R); + S.Stk.push(LT::from(R)); return true; } @@ -1558,9 +1565,10 @@ inline bool Shl(InterpState &S, CodePtr OpPC) { if (!CheckShift(S, OpPC, LHS, RHS, Bits)) return false; - Integral R; - Integral::shiftLeft(LHS.toUnsigned(), RHS, Bits, &R); - S.Stk.push(R); + typename LT::AsUnsigned R; + LT::AsUnsigned::shiftLeft(LT::AsUnsigned::from(LHS), + LT::AsUnsigned::from(RHS), Bits, &R); + S.Stk.push(LT::from(R)); return true; } @@ -1695,13 +1703,14 @@ inline bool CallVirt(InterpState &S, CodePtr OpPC, const Function *Func) { return Call(S, OpPC, Func); } -inline bool CallBI(InterpState &S, CodePtr &PC, const Function *Func) { +inline bool CallBI(InterpState &S, CodePtr &PC, const Function *Func, + const CallExpr *CE) { auto NewFrame = std::make_unique(S, Func, PC); InterpFrame *FrameBefore = S.Current; S.Current = NewFrame.get(); - if (InterpretBuiltin(S, PC, Func)) { + if (InterpretBuiltin(S, PC, Func, CE)) { NewFrame.release(); return true; } diff --git a/clang/lib/AST/Interp/InterpBuiltin.cpp b/clang/lib/AST/Interp/InterpBuiltin.cpp index c11f22aa94cacfef4f1c52629e79dfbbd06f53df..8af3bb706ac6f838a189995c53465de1569b3f2b 100644 --- a/clang/lib/AST/Interp/InterpBuiltin.cpp +++ b/clang/lib/AST/Interp/InterpBuiltin.cpp @@ -8,16 +8,94 @@ #include "Boolean.h" #include "Interp.h" #include "PrimType.h" +#include "clang/AST/RecordLayout.h" #include "clang/Basic/Builtins.h" namespace clang { namespace interp { +static unsigned callArgSize(const InterpState &S, const CallExpr *C) { + unsigned O = 0; + + for (const Expr *E : C->arguments()) { + O += align(primSize(*S.getContext().classify(E))); + } + + return O; +} + template T getParam(InterpFrame *Frame, unsigned Index) { unsigned Offset = Frame->getFunction()->getParamOffset(Index); return Frame->getParam(Offset); } +/// Peek an integer value from the stack into an APSInt. +static APSInt peekToAPSInt(InterpStack &Stk, PrimType T, size_t Offset = 0) { + if (Offset == 0) + Offset = align(primSize(T)); + + APSInt R; + INT_TYPE_SWITCH(T, R = Stk.peek(Offset).toAPSInt()); + + return R; +} + +/// Pushes \p Val on the stack as the type given by \p QT. +static void pushInteger(InterpState &S, const APSInt &Val, QualType QT) { + assert(QT->isSignedIntegerOrEnumerationType() || + QT->isUnsignedIntegerOrEnumerationType()); + std::optional T = S.getContext().classify(QT); + assert(T); + + if (QT->isSignedIntegerOrEnumerationType()) { + int64_t V = Val.getSExtValue(); + INT_TYPE_SWITCH(*T, { S.Stk.push(T::from(V)); }); + } else { + assert(QT->isUnsignedIntegerOrEnumerationType()); + uint64_t V = Val.getZExtValue(); + INT_TYPE_SWITCH(*T, { S.Stk.push(T::from(V)); }); + } +} + +template +static void pushInteger(InterpState &S, T Val, QualType QT) { + if constexpr (std::is_same_v) + pushInteger(S, APSInt(Val, !std::is_signed_v), QT); + else + pushInteger(S, + APSInt(APInt(sizeof(T) * 8, static_cast(Val), + std::is_signed_v), + !std::is_signed_v), + QT); +} + +static bool retPrimValue(InterpState &S, CodePtr OpPC, APValue &Result, + std::optional &T) { + if (!T) + return RetVoid(S, OpPC, Result); + +#define RET_CASE(X) \ + case X: \ + return Ret(S, OpPC, Result); + switch (*T) { + RET_CASE(PT_Ptr); + RET_CASE(PT_FnPtr); + RET_CASE(PT_Float); + RET_CASE(PT_Bool); + RET_CASE(PT_Sint8); + RET_CASE(PT_Uint8); + RET_CASE(PT_Sint16); + RET_CASE(PT_Uint16); + RET_CASE(PT_Sint32); + RET_CASE(PT_Uint32); + RET_CASE(PT_Sint64); + RET_CASE(PT_Uint64); + default: + llvm_unreachable("Unsupported return type for builtin function"); + } +#undef RET_CASE +} + static bool interp__builtin_strcmp(InterpState &S, CodePtr OpPC, InterpFrame *Frame) { const Pointer &A = getParam(Frame, 0); @@ -57,9 +135,36 @@ static bool interp__builtin_strcmp(InterpState &S, CodePtr OpPC, return true; } -bool InterpretBuiltin(InterpState &S, CodePtr OpPC, const Function *F) { +static bool interp__builtin_ctz(InterpState &S, CodePtr OpPC, + InterpFrame *Frame, const Function *Func, + const CallExpr *Call) { + unsigned CallSize = callArgSize(S, Call); + PrimType ValT = *S.getContext().classify(Call->getArg(0)); + + const APSInt &Val = peekToAPSInt(S.Stk, ValT, CallSize); + + if (Val == 0) { + if (Func->getBuiltinID() == Builtin::BI__builtin_ctzg && + Call->getNumArgs() == 2) { + // We have a fallback parameter. + PrimType FallbackT = *S.getContext().classify(Call->getArg(1)); + const APSInt &Fallback = peekToAPSInt(S.Stk, FallbackT); + pushInteger(S, Fallback, Call->getType()); + return true; + } + return false; + } + + unsigned Result = Val.countTrailingZeros(); + pushInteger(S, Result, Call->getType()); + return true; +} + +bool InterpretBuiltin(InterpState &S, CodePtr OpPC, const Function *F, + const CallExpr *Call) { InterpFrame *Frame = S.Current; APValue Dummy; + std::optional ReturnT = S.getContext().classify(Call); switch (F->getBuiltinID()) { case Builtin::BI__builtin_is_constant_evaluated: @@ -71,6 +176,14 @@ bool InterpretBuiltin(InterpState &S, CodePtr OpPC, const Function *F) { if (interp__builtin_strcmp(S, OpPC, Frame)) return Ret(S, OpPC, Dummy); return false; + case Builtin::BI__builtin_ctz: + case Builtin::BI__builtin_ctzl: + case Builtin::BI__builtin_ctzll: + case Builtin::BI__builtin_ctzs: + case Builtin::BI__builtin_ctzg: + if (!interp__builtin_ctz(S, OpPC, Frame, F, Call)) + return false; + return retPrimValue(S, OpPC, Dummy, ReturnT); default: return false; } diff --git a/clang/lib/AST/Interp/InterpStack.h b/clang/lib/AST/Interp/InterpStack.h index ab4351a6dc679fdd699e98b5eda15104cbf6adc0..3fd0f63c781fc70d8623dce40f2b52d1563ab663 100644 --- a/clang/lib/AST/Interp/InterpStack.h +++ b/clang/lib/AST/Interp/InterpStack.h @@ -14,6 +14,7 @@ #define LLVM_CLANG_AST_INTERP_INTERPSTACK_H #include "FunctionPointer.h" +#include "IntegralAP.h" #include "PrimType.h" #include #include @@ -183,6 +184,10 @@ private: return PT_Float; else if constexpr (std::is_same_v) return PT_FnPtr; + else if constexpr (std::is_same_v>) + return PT_IntAP; + else if constexpr (std::is_same_v>) + return PT_IntAP; llvm_unreachable("unknown type push()'ed into InterpStack"); } diff --git a/clang/lib/AST/Interp/Opcodes.td b/clang/lib/AST/Interp/Opcodes.td index 28074a350d05fba83e79c6e59d733130730a3054..0cde4e26bbbf94b75ac5a0ec3429927a21bf5e3c 100644 --- a/clang/lib/AST/Interp/Opcodes.td +++ b/clang/lib/AST/Interp/Opcodes.td @@ -25,6 +25,8 @@ def Sint32 : Type; def Uint32 : Type; def Sint64 : Type; def Uint64 : Type; +def IntAP : Type; +def IntAPS : Type; def Float : Type; def Ptr : Type; def FnPtr : Type; @@ -42,6 +44,8 @@ def ArgSint32 : ArgType { let Name = "int32_t"; } def ArgUint32 : ArgType { let Name = "uint32_t"; } def ArgSint64 : ArgType { let Name = "int64_t"; } def ArgUint64 : ArgType { let Name = "uint64_t"; } +def ArgIntAP : ArgType { let Name = "IntegralAP"; } +def ArgIntAPS : ArgType { let Name = "IntegralAP"; } def ArgFloat : ArgType { let Name = "Floating"; } def ArgBool : ArgType { let Name = "bool"; } @@ -51,6 +55,9 @@ def ArgRecordField : ArgType { let Name = "const Record::Field *"; } def ArgFltSemantics : ArgType { let Name = "const llvm::fltSemantics *"; } def ArgRoundingMode : ArgType { let Name = "llvm::RoundingMode"; } def ArgLETD: ArgType { let Name = "const LifetimeExtendedTemporaryDecl *"; } +def ArgCastKind : ArgType { let Name = "CastKind"; } +def ArgCallExpr : ArgType { let Name = "const CallExpr *"; } +def ArgExpr : ArgType { let Name = "const Expr *"; } //===----------------------------------------------------------------------===// // Classes of types instructions operate on. @@ -62,7 +69,7 @@ class TypeClass { def IntegerTypeClass : TypeClass { let Types = [Sint8, Uint8, Sint16, Uint16, Sint32, - Uint32, Sint64, Uint64]; + Uint32, Sint64, Uint64, IntAP, IntAPS]; } def NumberTypeClass : TypeClass { @@ -187,7 +194,7 @@ def CallVirt : Opcode { } def CallBI : Opcode { - let Args = [ArgFunction]; + let Args = [ArgFunction, ArgCallExpr]; let Types = []; } @@ -533,11 +540,11 @@ def Comp: Opcode { //===----------------------------------------------------------------------===// def FromCastTypeClass : TypeClass { - let Types = [Uint8, Sint8, Uint16, Sint16, Uint32, Sint32, Uint64, Sint64, Bool]; + let Types = [Uint8, Sint8, Uint16, Sint16, Uint32, Sint32, Uint64, Sint64, Bool, IntAP, IntAPS]; } def ToCastTypeClass : TypeClass { - let Types = [Uint8, Sint8, Uint16, Sint16, Uint32, Sint32, Uint64, Sint64, Bool]; + let Types = [Uint8, Sint8, Uint16, Sint16, Uint32, Sint32, Uint64, Sint64, Bool, IntAP, IntAPS]; } def Cast: Opcode { diff --git a/clang/lib/AST/Interp/PrimType.cpp b/clang/lib/AST/Interp/PrimType.cpp index a9b5d8ea8cc8c7c4fa5f1fbc70a70cf7982d9f01..9b96dcfe6a272fc777bb7a2bb8f6c421d775d5bc 100644 --- a/clang/lib/AST/Interp/PrimType.cpp +++ b/clang/lib/AST/Interp/PrimType.cpp @@ -10,6 +10,7 @@ #include "Boolean.h" #include "Floating.h" #include "FunctionPointer.h" +#include "IntegralAP.h" #include "Pointer.h" using namespace clang; diff --git a/clang/lib/AST/Interp/PrimType.h b/clang/lib/AST/Interp/PrimType.h index 693e57210608d9bb241d178f630c63db5f222ce5..730bf4a9488af17bc506e23d19a363a4e403354c 100644 --- a/clang/lib/AST/Interp/PrimType.h +++ b/clang/lib/AST/Interp/PrimType.h @@ -13,6 +13,7 @@ #ifndef LLVM_CLANG_AST_INTERP_TYPE_H #define LLVM_CLANG_AST_INTERP_TYPE_H +#include "llvm/Support/raw_ostream.h" #include #include #include @@ -24,6 +25,7 @@ class Pointer; class Boolean; class Floating; class FunctionPointer; +template class IntegralAP; template class Integral; /// Enumeration of the primitive types of the VM. @@ -36,12 +38,28 @@ enum PrimType : unsigned { PT_Uint32, PT_Sint64, PT_Uint64, + PT_IntAP, + PT_IntAPS, PT_Bool, PT_Float, PT_Ptr, PT_FnPtr, }; +enum class CastKind : uint8_t { + Reinterpret, +}; + +inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, + interp::CastKind CK) { + switch (CK) { + case interp::CastKind::Reinterpret: + OS << "reinterpret_cast"; + break; + } + return OS; +} + constexpr bool isIntegralType(PrimType T) { return T <= PT_Uint64; } /// Mapping from primitive types to their representation. @@ -54,6 +72,12 @@ template <> struct PrimConv { using T = Integral<32, true>; }; template <> struct PrimConv { using T = Integral<32, false>; }; template <> struct PrimConv { using T = Integral<64, true>; }; template <> struct PrimConv { using T = Integral<64, false>; }; +template <> struct PrimConv { + using T = IntegralAP; +}; +template <> struct PrimConv { + using T = IntegralAP; +}; template <> struct PrimConv { using T = Floating; }; template <> struct PrimConv { using T = Boolean; }; template <> struct PrimConv { using T = Pointer; }; @@ -94,12 +118,34 @@ static inline bool aligned(const void *P) { TYPE_SWITCH_CASE(PT_Uint32, B) \ TYPE_SWITCH_CASE(PT_Sint64, B) \ TYPE_SWITCH_CASE(PT_Uint64, B) \ + TYPE_SWITCH_CASE(PT_IntAP, B) \ + TYPE_SWITCH_CASE(PT_IntAPS, B) \ TYPE_SWITCH_CASE(PT_Float, B) \ TYPE_SWITCH_CASE(PT_Bool, B) \ TYPE_SWITCH_CASE(PT_Ptr, B) \ TYPE_SWITCH_CASE(PT_FnPtr, B) \ } \ } while (0) + +#define INT_TYPE_SWITCH(Expr, B) \ + do { \ + switch (Expr) { \ + TYPE_SWITCH_CASE(PT_Sint8, B) \ + TYPE_SWITCH_CASE(PT_Uint8, B) \ + TYPE_SWITCH_CASE(PT_Sint16, B) \ + TYPE_SWITCH_CASE(PT_Uint16, B) \ + TYPE_SWITCH_CASE(PT_Sint32, B) \ + TYPE_SWITCH_CASE(PT_Uint32, B) \ + TYPE_SWITCH_CASE(PT_Sint64, B) \ + TYPE_SWITCH_CASE(PT_Uint64, B) \ + TYPE_SWITCH_CASE(PT_IntAP, B) \ + TYPE_SWITCH_CASE(PT_IntAPS, B) \ + TYPE_SWITCH_CASE(PT_Bool, B) \ + default: \ + llvm_unreachable("Not an integer value"); \ + } \ + } while (0) + #define COMPOSITE_TYPE_SWITCH(Expr, B, D) \ do { \ switch (Expr) { \ diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp index 64861eb852b8ab0ff3ba563a9efe489d94f9972b..f9091d767bb413714958b020eb34844ac1ee44bc 100644 --- a/clang/lib/CodeGen/CGBuiltin.cpp +++ b/clang/lib/CodeGen/CGBuiltin.cpp @@ -2737,19 +2737,34 @@ 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 = llvm::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: diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index 8a7c408e99710f213cf6621cdd0e0710b5cc1e92..82450006e41931fd43e822b96d1ef9572ecf9fc0 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -2064,6 +2064,45 @@ static bool checkFPMathBuiltinElementType(Sema &S, SourceLocation Loc, return false; } +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) { @@ -2796,7 +2835,13 @@ Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID, diag::err_hip_invalid_args_builtin_mangled_name); return ExprError(); } + break; } + + case Builtin::BI__builtin_ctzg: + if (SemaBuiltinCountZeroBitsGeneric(*this, TheCall)) + return ExprError(); + break; } // Since the target specific builtins for each arch overlap, only check those diff --git a/clang/test/AST/Interp/builtin-functions.cpp b/clang/test/AST/Interp/builtin-functions.cpp index eff8f80b7649a18a4753beca30f9837fcecdcb11..f3c0621acdc6fdd23582bc7cc3c7dd231a4ba9ce 100644 --- a/clang/test/AST/Interp/builtin-functions.cpp +++ b/clang/test/AST/Interp/builtin-functions.cpp @@ -34,3 +34,39 @@ namespace strcmp { // ref-error {{not an integral constant}} \ // ref-note {{dereferenced one-past-the-end}} } + +#define BITSIZE(x) (sizeof(x) * 8) + +namespace ctz { + char ctz36[__builtin_ctzg(0x10UL) == 4 ? 1 : -1]; + char ctz37[__builtin_ctzg(0x10UL, 42) == 4 ? 1 : -1]; + char ctz38[__builtin_ctzg(1UL << (BITSIZE(long) - 1)) == BITSIZE(long) - 1 ? 1 : -1]; + char ctz39[__builtin_ctzg(1UL << (BITSIZE(long) - 1), 42) == BITSIZE(long) - 1 ? 1 : -1]; + int ctz40 = __builtin_ctzg(0ULL); + char ctz41[__builtin_ctzg(0ULL, 42) == 42 ? 1 : -1]; + char ctz42[__builtin_ctzg(0x1ULL) == 0 ? 1 : -1]; + char ctz43[__builtin_ctzg(0x1ULL, 42) == 0 ? 1 : -1]; + char ctz44[__builtin_ctzg(0x10ULL) == 4 ? 1 : -1]; + char ctz45[__builtin_ctzg(0x10ULL, 42) == 4 ? 1 : -1]; + char ctz46[__builtin_ctzg(1ULL << (BITSIZE(long long) - 1)) == BITSIZE(long long) - 1 ? 1 : -1]; + char ctz47[__builtin_ctzg(1ULL << (BITSIZE(long long) - 1), 42) == BITSIZE(long long) - 1 ? 1 : -1]; +#ifdef __SIZEOF_INT128__ + char ctz49[__builtin_ctzg((unsigned __int128)0, 42) == 42 ? 1 : -1]; + char ctz50[__builtin_ctzg((unsigned __int128)0x1) == 0 ? 1 : -1]; + char ctz51[__builtin_ctzg((unsigned __int128)0x1, 42) == 0 ? 1 : -1]; + char ctz52[__builtin_ctzg((unsigned __int128)0x10) == 4 ? 1 : -1]; + char ctz53[__builtin_ctzg((unsigned __int128)0x10, 42) == 4 ? 1 : -1]; + char ctz54[__builtin_ctzg((unsigned __int128)1 << (BITSIZE(__int128) - 1)) == BITSIZE(__int128) - 1 ? 1 : -1]; + char ctz55[__builtin_ctzg((unsigned __int128)1 << (BITSIZE(__int128) - 1), 42) == BITSIZE(__int128) - 1 ? 1 : -1]; +#endif +#ifndef __AVR__ + char ctz57[__builtin_ctzg((unsigned _BitInt(128))0, 42) == 42 ? 1 : -1]; + char ctz58[__builtin_ctzg((unsigned _BitInt(128))0x1) == 0 ? 1 : -1]; + char ctz59[__builtin_ctzg((unsigned _BitInt(128))0x1, 42) == 0 ? 1 : -1]; + char ctz60[__builtin_ctzg((unsigned _BitInt(128))0x10) == 4 ? 1 : -1]; + char ctz61[__builtin_ctzg((unsigned _BitInt(128))0x10, 42) == 4 ? 1 : -1]; + 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 +} + diff --git a/clang/test/CodeGen/ubsan-builtin-checks.c b/clang/test/CodeGen/ubsan-builtin-checks.c index 2bc32d8df4850d5fe0dbdc3188cecbc96673e008..47fb7283451b207b509398518610a8f7085c240f 100644 --- a/clang/test/CodeGen/ubsan-builtin-checks.c +++ b/clang/test/CodeGen/ubsan-builtin-checks.c @@ -23,6 +23,9 @@ void check_ctz(int n) { // CHECK: call void @__ubsan_handle_invalid_builtin __builtin_ctzll(n); + + // CHECK: call void @__ubsan_handle_invalid_builtin + __builtin_ctzg((unsigned int)n); } // CHECK: define{{.*}} void @check_clz @@ -45,3 +48,4 @@ void check_clz(int n) { // CHECK: call void @__ubsan_handle_invalid_builtin __builtin_clzll(n); } + diff --git a/clang/test/Sema/constant-builtins-2.c b/clang/test/Sema/constant-builtins-2.c index 93948201c451b495f4e66739d3bab2e5a5f979f0..65bacd39adfd102e7aec23b33dec144e8a05e6a4 100644 --- a/clang/test/Sema/constant-builtins-2.c +++ b/clang/test/Sema/constant-builtins-2.c @@ -187,6 +187,64 @@ int ctz4 = __builtin_ctz(0); // expected-error {{not a compile-time constant}} char ctz5[__builtin_ctzl(0x10L) == 4 ? 1 : -1]; char ctz6[__builtin_ctzll(0x100LL) == 8 ? 1 : -1]; char ctz7[__builtin_ctzs(1 << (BITSIZE(short) - 1)) == BITSIZE(short) - 1 ? 1 : -1]; +int ctz8 = __builtin_ctzg((unsigned char)0); // expected-error {{not a compile-time constant}} +char ctz9[__builtin_ctzg((unsigned char)0, 42) == 42 ? 1 : -1]; +char ctz10[__builtin_ctzg((unsigned char)0x1) == 0 ? 1 : -1]; +char ctz11[__builtin_ctzg((unsigned char)0x1, 42) == 0 ? 1 : -1]; +char ctz12[__builtin_ctzg((unsigned char)0x10) == 4 ? 1 : -1]; +char ctz13[__builtin_ctzg((unsigned char)0x10, 42) == 4 ? 1 : -1]; +char ctz14[__builtin_ctzg((unsigned char)(1 << (BITSIZE(char) - 1))) == BITSIZE(char) - 1 ? 1 : -1]; +char ctz15[__builtin_ctzg((unsigned char)(1 << (BITSIZE(char) - 1)), 42) == BITSIZE(char) - 1 ? 1 : -1]; +int ctz16 = __builtin_ctzg((unsigned short)0); // expected-error {{not a compile-time constant}} +char ctz17[__builtin_ctzg((unsigned short)0, 42) == 42 ? 1 : -1]; +char ctz18[__builtin_ctzg((unsigned short)0x1) == 0 ? 1 : -1]; +char ctz19[__builtin_ctzg((unsigned short)0x1, 42) == 0 ? 1 : -1]; +char ctz20[__builtin_ctzg((unsigned short)0x10) == 4 ? 1 : -1]; +char ctz21[__builtin_ctzg((unsigned short)0x10, 42) == 4 ? 1 : -1]; +char ctz22[__builtin_ctzg((unsigned short)(1 << (BITSIZE(short) - 1))) == BITSIZE(short) - 1 ? 1 : -1]; +char ctz23[__builtin_ctzg((unsigned short)(1 << (BITSIZE(short) - 1)), 42) == BITSIZE(short) - 1 ? 1 : -1]; +int ctz24 = __builtin_ctzg(0U); // expected-error {{not a compile-time constant}} +char ctz25[__builtin_ctzg(0U, 42) == 42 ? 1 : -1]; +char ctz26[__builtin_ctzg(0x1U) == 0 ? 1 : -1]; +char ctz27[__builtin_ctzg(0x1U, 42) == 0 ? 1 : -1]; +char ctz28[__builtin_ctzg(0x10U) == 4 ? 1 : -1]; +char ctz29[__builtin_ctzg(0x10U, 42) == 4 ? 1 : -1]; +char ctz30[__builtin_ctzg(1U << (BITSIZE(int) - 1)) == BITSIZE(int) - 1 ? 1 : -1]; +char ctz31[__builtin_ctzg(1U << (BITSIZE(int) - 1), 42) == BITSIZE(int) - 1 ? 1 : -1]; +int ctz32 = __builtin_ctzg(0UL); // expected-error {{not a compile-time constant}} +char ctz33[__builtin_ctzg(0UL, 42) == 42 ? 1 : -1]; +char ctz34[__builtin_ctzg(0x1UL) == 0 ? 1 : -1]; +char ctz35[__builtin_ctzg(0x1UL, 42) == 0 ? 1 : -1]; +char ctz36[__builtin_ctzg(0x10UL) == 4 ? 1 : -1]; +char ctz37[__builtin_ctzg(0x10UL, 42) == 4 ? 1 : -1]; +char ctz38[__builtin_ctzg(1UL << (BITSIZE(long) - 1)) == BITSIZE(long) - 1 ? 1 : -1]; +char ctz39[__builtin_ctzg(1UL << (BITSIZE(long) - 1), 42) == BITSIZE(long) - 1 ? 1 : -1]; +int ctz40 = __builtin_ctzg(0ULL); // expected-error {{not a compile-time constant}} +char ctz41[__builtin_ctzg(0ULL, 42) == 42 ? 1 : -1]; +char ctz42[__builtin_ctzg(0x1ULL) == 0 ? 1 : -1]; +char ctz43[__builtin_ctzg(0x1ULL, 42) == 0 ? 1 : -1]; +char ctz44[__builtin_ctzg(0x10ULL) == 4 ? 1 : -1]; +char ctz45[__builtin_ctzg(0x10ULL, 42) == 4 ? 1 : -1]; +char ctz46[__builtin_ctzg(1ULL << (BITSIZE(long long) - 1)) == BITSIZE(long long) - 1 ? 1 : -1]; +char ctz47[__builtin_ctzg(1ULL << (BITSIZE(long long) - 1), 42) == BITSIZE(long long) - 1 ? 1 : -1]; +#ifdef __SIZEOF_INT128__ +int ctz48 = __builtin_ctzg((unsigned __int128)0); // expected-error {{not a compile-time constant}} +char ctz49[__builtin_ctzg((unsigned __int128)0, 42) == 42 ? 1 : -1]; +char ctz50[__builtin_ctzg((unsigned __int128)0x1) == 0 ? 1 : -1]; +char ctz51[__builtin_ctzg((unsigned __int128)0x1, 42) == 0 ? 1 : -1]; +char ctz52[__builtin_ctzg((unsigned __int128)0x10) == 4 ? 1 : -1]; +char ctz53[__builtin_ctzg((unsigned __int128)0x10, 42) == 4 ? 1 : -1]; +char ctz54[__builtin_ctzg((unsigned __int128)1 << (BITSIZE(__int128) - 1)) == BITSIZE(__int128) - 1 ? 1 : -1]; +char ctz55[__builtin_ctzg((unsigned __int128)1 << (BITSIZE(__int128) - 1), 42) == BITSIZE(__int128) - 1 ? 1 : -1]; +#endif +int ctz56 = __builtin_ctzg((unsigned _BitInt(128))0); // expected-error {{not a compile-time constant}} +char ctz57[__builtin_ctzg((unsigned _BitInt(128))0, 42) == 42 ? 1 : -1]; +char ctz58[__builtin_ctzg((unsigned _BitInt(128))0x1) == 0 ? 1 : -1]; +char ctz59[__builtin_ctzg((unsigned _BitInt(128))0x1, 42) == 0 ? 1 : -1]; +char ctz60[__builtin_ctzg((unsigned _BitInt(128))0x10) == 4 ? 1 : -1]; +char ctz61[__builtin_ctzg((unsigned _BitInt(128))0x10, 42) == 4 ? 1 : -1]; +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]; char popcount1[__builtin_popcount(0) == 0 ? 1 : -1]; char popcount2[__builtin_popcount(0xF0F0) == 8 ? 1 : -1]; diff --git a/clang/test/Sema/constant-builtins-all-args-evaluated.cpp b/clang/test/Sema/constant-builtins-all-args-evaluated.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d37af0c1386d824010b1fd868b2fe80f64f06d2d --- /dev/null +++ b/clang/test/Sema/constant-builtins-all-args-evaluated.cpp @@ -0,0 +1,19 @@ +// RUN: %clang_cc1 -fsyntax-only -verify %s +// expected-no-diagnostics + +constexpr int test_ctzg_0() { + int x = 0; + (void)__builtin_ctzg(0U, ++x); + return x; +} + +static_assert(test_ctzg_0() == 1); + +constexpr int test_ctzg_1() { + int x = 0; + (void)__builtin_ctzg(1U, ++x); + return x; +} + +static_assert(test_ctzg_1() == 1); + diff --git a/clang/test/Sema/count-builtins.c b/clang/test/Sema/count-builtins.c new file mode 100644 index 0000000000000000000000000000000000000000..34ed718f3d3b83de5746cc5ee48be4f0e4e01c95 --- /dev/null +++ b/clang/test/Sema/count-builtins.c @@ -0,0 +1,34 @@ +// RUN: %clang_cc1 -std=c2x -triple=x86_64-pc-linux-gnu -fsyntax-only -verify -Wpedantic %s + +typedef int int2 __attribute__((ext_vector_type(2))); + +void test_builtin_ctzg(short s, int i, unsigned int ui, __int128 i128, + _BitInt(128) bi128, double d, int2 i2) { + __builtin_ctzg(); + // expected-error@-1 {{too few arguments to function call, expected 1, have 0}} + __builtin_ctzg(i, i, i); + // expected-error@-1 {{too many arguments to function call, expected at most 2, have 3}} + __builtin_ctzg(s); + // expected-error@-1 {{1st argument must be an unsigned integer (was 'short')}} + __builtin_ctzg(i); + // expected-error@-1 {{1st argument must be an unsigned integer (was 'int')}} + __builtin_ctzg(i128); + // expected-error@-1 {{1st argument must be an unsigned integer (was '__int128')}} + __builtin_ctzg(bi128); + // expected-error@-1 {{1st argument must be an unsigned integer (was '_BitInt(128)')}} + __builtin_ctzg(d); + // expected-error@-1 {{1st argument must be an unsigned integer (was 'double')}} + __builtin_ctzg(i2); + // expected-error@-1 {{1st argument must be an unsigned integer (was 'int2' (vector of 2 'int' values))}} + __builtin_ctzg(ui, ui); + // expected-error@-1 {{2nd argument must be an 'int' (was 'unsigned int')}} + __builtin_ctzg(ui, i128); + // expected-error@-1 {{2nd argument must be an 'int' (was '__int128')}} + __builtin_ctzg(ui, bi128); + // expected-error@-1 {{2nd argument must be an 'int' (was '_BitInt(128)')}} + __builtin_ctzg(ui, d); + // expected-error@-1 {{2nd argument must be an 'int' (was 'double')}} + __builtin_ctzg(ui, i2); + // expected-error@-1 {{2nd argument must be an 'int' (was 'int2' (vector of 2 'int' values))}} +} + diff --git a/libc/src/__support/CPP/bit.h b/libc/src/__support/CPP/bit.h index 4241f2b52b543cf6430342f87c75851cf7fa7ae2..d70e0180b50ed5675d35e909e91296d34433d1c9 100644 --- a/libc/src/__support/CPP/bit.h +++ b/libc/src/__support/CPP/bit.h @@ -9,7 +9,9 @@ #ifndef LLVM_LIBC_SUPPORT_CPP_BIT_H #define LLVM_LIBC_SUPPORT_CPP_BIT_H +#include "src/__support/CPP/limits.h" // numeric_limits #include "src/__support/CPP/type_traits.h" +#include "src/__support/macros/attributes.h" #include "src/__support/macros/config.h" // LIBC_HAS_BUILTIN namespace __llvm_libc::cpp { @@ -48,6 +50,62 @@ template constexpr To bit_cast(const From &from) { #endif // defined(LLVM_LIBC_HAS_BUILTIN_BIT_CAST) } +template +[[nodiscard]] LIBC_INLINE constexpr cpp::enable_if_t, bool> +has_single_bit(T value) { + return (value != 0) && ((value & (value - 1)) == 0); +} + +// A temporary macro to add template function specialization when compiler +// builtin is available. +#define ADD_SPECIALIZATION(NAME, TYPE, BUILTIN) \ + template <> [[nodiscard]] LIBC_INLINE constexpr int NAME(TYPE value) { \ + static_assert(cpp::is_unsigned_v); \ + return value == 0 ? cpp::numeric_limits::digits : BUILTIN(value); \ + } + +#if LIBC_HAS_BUILTIN(__builtin_ctzg) +template +[[nodiscard]] LIBC_INLINE constexpr cpp::enable_if_t, int> +countr_zero(T value) { + return __builtin_ctzg(value, cpp::numeric_limits::digits); +} +#else +template +[[nodiscard]] LIBC_INLINE constexpr cpp::enable_if_t, int> +countr_zero(T value) { + if (!value) + return cpp::numeric_limits::digits; + if (value & 0x1) + return 0; + // Bisection method. + unsigned zero_bits = 0; + unsigned shift = cpp::numeric_limits::digits >> 1; + T mask = cpp::numeric_limits::max() >> shift; + while (shift) { + if ((value & mask) == 0) { + value >>= shift; + zero_bits |= shift; + } + shift >>= 1; + mask >>= shift; + } + return zero_bits; +} +#if LIBC_HAS_BUILTIN(__builtin_ctzs) +ADD_SPECIALIZATION(countr_zero, unsigned short, __builtin_ctzs) +#endif +#if LIBC_HAS_BUILTIN(__builtin_ctz) +ADD_SPECIALIZATION(countr_zero, unsigned int, __builtin_ctz) +#endif +#if LIBC_HAS_BUILTIN(__builtin_ctzl) +ADD_SPECIALIZATION(countr_zero, unsigned long, __builtin_ctzl) +#endif +#if LIBC_HAS_BUILTIN(__builtin_ctzll) +ADD_SPECIALIZATION(countr_zero, unsigned long long, __builtin_ctzll) +#endif +#endif + } // namespace __llvm_libc::cpp #endif // LLVM_LIBC_SUPPORT_CPP_BIT_H diff --git a/libcxx/include/__bit/countr.h b/libcxx/include/__bit/countr.h index 66ca5e7e66f2b0d95ab84ff7348e03931cdb87f1..c66fa29c2b4b1e3fbac41a351811d35706c9efd3 100644 --- a/libcxx/include/__bit/countr.h +++ b/libcxx/include/__bit/countr.h @@ -32,10 +32,11 @@ int __libcpp_ctz(unsigned long __x) _NOEXCEPT { return __builtin_ctzl(__x); _LIBCPP_NODISCARD inline _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR int __libcpp_ctz(unsigned long long __x) _NOEXCEPT { return __builtin_ctzll(__x); } -#if _LIBCPP_STD_VER >= 20 - -template <__libcpp_unsigned_integer _Tp> -_LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI constexpr int countr_zero(_Tp __t) noexcept { +template +_LIBCPP_NODISCARD _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR int __countr_zero(_Tp __t) _NOEXCEPT { +#if __has_builtin(__builtin_ctzg) + return __builtin_ctzg(__t, numeric_limits<_Tp>::digits); +#else // __has_builtin(__builtin_ctzg) if (__t == 0) return numeric_limits<_Tp>::digits; @@ -54,6 +55,14 @@ _LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI constexpr int countr_zero(_Tp __t) n } return __ret + std::__libcpp_ctz(static_cast(__t)); } +#endif // __has_builtin(__builtin_ctzg) +} + +#if _LIBCPP_STD_VER >= 20 + +template <__libcpp_unsigned_integer _Tp> +_LIBCPP_NODISCARD_EXT _LIBCPP_HIDE_FROM_ABI constexpr int countr_zero(_Tp __t) noexcept { + return std::__countr_zero(__t); } template <__libcpp_unsigned_integer _Tp>