diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst index 1db80262b8fb8..fd43966277342 100644 --- a/clang/docs/LanguageExtensions.rst +++ b/clang/docs/LanguageExtensions.rst @@ -5274,6 +5274,11 @@ Intrinsics Support within Constant Expressions The following builtin intrinsics can be used in constant expressions: +* ``__builtin_addcb`` +* ``__builtin_addcs`` +* ``__builtin_addc`` +* ``__builtin_addcl`` +* ``__builtin_addcll`` * ``__builtin_bitreverse8`` * ``__builtin_bitreverse16`` * ``__builtin_bitreverse32`` @@ -5320,6 +5325,11 @@ The following builtin intrinsics can be used in constant expressions: * ``__builtin_rotateright16`` * ``__builtin_rotateright32`` * ``__builtin_rotateright64`` +* ``__builtin_subcb`` +* ``__builtin_subcs`` +* ``__builtin_subc`` +* ``__builtin_subcl`` +* ``__builtin_subcll`` The following x86-specific intrinsics can be used in constant expressions: diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 5a19c2ea36bdf..608d855abf5d0 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -141,6 +141,9 @@ Non-comprehensive list of changes in this release - Added ``__builtin_readsteadycounter`` for reading fixed frequency hardware counters. +- ``__builtin_addc``, ``__builtin_subc``, and the other sizes of those + builtins are now constexpr and may be used in constant expressions. + New Compiler Flags ------------------ diff --git a/clang/include/clang/Basic/Builtins.td b/clang/include/clang/Basic/Builtins.td index 193d5851f9f29..df74026c5d2d5 100644 --- a/clang/include/clang/Basic/Builtins.td +++ b/clang/include/clang/Basic/Builtins.td @@ -4067,14 +4067,14 @@ class MPATemplate : Template< def Addc : Builtin, MPATemplate { let Spellings = ["__builtin_addc"]; - let Attributes = [NoThrow]; + let Attributes = [NoThrow, Constexpr]; // FIXME: Why are these argumentes marked const? let Prototype = "T(T const, T const, T const, T*)"; } def Subc : Builtin, MPATemplate { let Spellings = ["__builtin_subc"]; - let Attributes = [NoThrow]; + let Attributes = [NoThrow, Constexpr]; // FIXME: Why are these argumentes marked const? let Prototype = "T(T const, T const, T const, T*)"; } diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 33ad94e6795c8..010010120cf6d 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -12696,6 +12696,59 @@ bool IntExprEvaluator::VisitBuiltinCallExpr(const CallExpr *E, return BuiltinOp == Builtin::BI__atomic_always_lock_free ? Success(0, E) : Error(E); } + case Builtin::BI__builtin_addcb: + case Builtin::BI__builtin_addcs: + case Builtin::BI__builtin_addc: + case Builtin::BI__builtin_addcl: + case Builtin::BI__builtin_addcll: + case Builtin::BI__builtin_subcb: + case Builtin::BI__builtin_subcs: + case Builtin::BI__builtin_subc: + case Builtin::BI__builtin_subcl: + case Builtin::BI__builtin_subcll: { + LValue CarryOutLValue; + APSInt LHS, RHS, CarryIn, CarryOut, Result; + QualType ResultType = E->getArg(0)->getType(); + if (!EvaluateInteger(E->getArg(0), LHS, Info) || + !EvaluateInteger(E->getArg(1), RHS, Info) || + !EvaluateInteger(E->getArg(2), CarryIn, Info) || + !EvaluatePointer(E->getArg(3), CarryOutLValue, Info)) + return false; + // Copy the number of bits and sign. + Result = LHS; + CarryOut = LHS; + + bool FirstOverflowed = false; + bool SecondOverflowed = false; + switch (BuiltinOp) { + default: + llvm_unreachable("Invalid value for BuiltinOp"); + case Builtin::BI__builtin_addcb: + case Builtin::BI__builtin_addcs: + case Builtin::BI__builtin_addc: + case Builtin::BI__builtin_addcl: + case Builtin::BI__builtin_addcll: + Result = + LHS.uadd_ov(RHS, FirstOverflowed).uadd_ov(CarryIn, SecondOverflowed); + break; + case Builtin::BI__builtin_subcb: + case Builtin::BI__builtin_subcs: + case Builtin::BI__builtin_subc: + case Builtin::BI__builtin_subcl: + case Builtin::BI__builtin_subcll: + Result = + LHS.usub_ov(RHS, FirstOverflowed).usub_ov(CarryIn, SecondOverflowed); + break; + } + + // It is possible for both overflows to happen but CGBuiltin uses an OR so + // this is consistent. + CarryOut = (uint64_t)(FirstOverflowed | SecondOverflowed); + APValue APV{CarryOut}; + if (!handleAssignment(Info, E, CarryOutLValue, ResultType, APV)) + return false; + return Success(Result, E); + } case Builtin::BI__builtin_add_overflow: case Builtin::BI__builtin_sub_overflow: case Builtin::BI__builtin_mul_overflow: diff --git a/clang/test/SemaCXX/builtins-overflow.cpp b/clang/test/SemaCXX/builtins-overflow.cpp index c84b7da00b543..1b1e46ae75132 100644 --- a/clang/test/SemaCXX/builtins-overflow.cpp +++ b/clang/test/SemaCXX/builtins-overflow.cpp @@ -94,3 +94,52 @@ static_assert(smul(17,22) == Result{false, 374}); static_assert(smul(INT_MAX / 22, 23) == Result{true, -2049870757}); static_assert(smul(INT_MIN / 22, -23) == Result{true, -2049870757}); +template +struct CarryResult { + T CarryOut; + T Value; + constexpr bool operator==(const CarryResult &Other) { + return CarryOut == Other.CarryOut && Value == Other.Value; + } +}; + +constexpr CarryResult addcb(unsigned char lhs, unsigned char rhs, unsigned char carry) { + unsigned char carry_out{}; + unsigned char sum{}; + sum = __builtin_addcb(lhs, rhs, carry, &carry_out); + return {carry_out, sum}; +} + +static_assert(addcb(120, 10, 0) == CarryResult{0, 130}); +static_assert(addcb(250, 10, 0) == CarryResult{1, 4}); +static_assert(addcb(255, 255, 0) == CarryResult{1, 254}); +static_assert(addcb(255, 255, 1) == CarryResult{1, 255}); +static_assert(addcb(255, 0, 1) == CarryResult{1, 0}); +static_assert(addcb(255, 1, 0) == CarryResult{1, 0}); +static_assert(addcb(255, 1, 1) == CarryResult{1, 1}); +// This is currently supported with the carry still producing a value of 1. +// If support for carry outside of 0-1 is removed, change this test to check +// that it is not supported. +static_assert(addcb(255, 255, 2) == CarryResult{1, 0}); + +constexpr CarryResult subcb(unsigned char lhs, unsigned char rhs, unsigned char carry) { + unsigned char carry_out{}; + unsigned char sum{}; + sum = __builtin_subcb(lhs, rhs, carry, &carry_out); + return {carry_out, sum}; +} + +static_assert(subcb(20, 10, 0) == CarryResult{0, 10}); +static_assert(subcb(10, 10, 0) == CarryResult{0, 0}); +static_assert(subcb(10, 15, 0) == CarryResult{1, 251}); +// The carry is subtracted from the result +static_assert(subcb(10, 15, 1) == CarryResult{1, 250}); +static_assert(subcb(0, 0, 1) == CarryResult{1, 255}); +static_assert(subcb(0, 1, 0) == CarryResult{1, 255}); +static_assert(subcb(0, 1, 1) == CarryResult{1, 254}); +static_assert(subcb(0, 255, 0) == CarryResult{1, 1}); +static_assert(subcb(0, 255, 1) == CarryResult{1, 0}); +// This is currently supported with the carry still producing a value of 1. +// If support for carry outside of 0-1 is removed, change this test to check +// that it is not supported. +static_assert(subcb(0, 255, 2) == CarryResult{1, 255});