Skip to content

Commit

Permalink
[clang] Allow builtin addc/subc to be constant evaluated (#81656)
Browse files Browse the repository at this point in the history
[clang] Allow builtin addc/subc to be constant evaluated
  • Loading branch information
Bryce-MW committed Feb 15, 2024
1 parent 6e11ed2 commit f209352
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 2 deletions.
10 changes: 10 additions & 0 deletions clang/docs/LanguageExtensions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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``
Expand Down Expand Up @@ -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:
Expand Down
3 changes: 3 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
------------------

Expand Down
4 changes: 2 additions & 2 deletions clang/include/clang/Basic/Builtins.td
Original file line number Diff line number Diff line change
Expand Up @@ -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*)";
}
Expand Down
53 changes: 53 additions & 0 deletions clang/lib/AST/ExprConstant.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
49 changes: 49 additions & 0 deletions clang/test/SemaCXX/builtins-overflow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,3 +94,52 @@ static_assert(smul(17,22) == Result<int>{false, 374});
static_assert(smul(INT_MAX / 22, 23) == Result<int>{true, -2049870757});
static_assert(smul(INT_MIN / 22, -23) == Result<int>{true, -2049870757});

template<typename T>
struct CarryResult {
T CarryOut;
T Value;
constexpr bool operator==(const CarryResult<T> &Other) {
return CarryOut == Other.CarryOut && Value == Other.Value;
}
};

constexpr CarryResult<unsigned char> 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<unsigned char>{0, 130});
static_assert(addcb(250, 10, 0) == CarryResult<unsigned char>{1, 4});
static_assert(addcb(255, 255, 0) == CarryResult<unsigned char>{1, 254});
static_assert(addcb(255, 255, 1) == CarryResult<unsigned char>{1, 255});
static_assert(addcb(255, 0, 1) == CarryResult<unsigned char>{1, 0});
static_assert(addcb(255, 1, 0) == CarryResult<unsigned char>{1, 0});
static_assert(addcb(255, 1, 1) == CarryResult<unsigned char>{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<unsigned char>{1, 0});

constexpr CarryResult<unsigned char> 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<unsigned char>{0, 10});
static_assert(subcb(10, 10, 0) == CarryResult<unsigned char>{0, 0});
static_assert(subcb(10, 15, 0) == CarryResult<unsigned char>{1, 251});
// The carry is subtracted from the result
static_assert(subcb(10, 15, 1) == CarryResult<unsigned char>{1, 250});
static_assert(subcb(0, 0, 1) == CarryResult<unsigned char>{1, 255});
static_assert(subcb(0, 1, 0) == CarryResult<unsigned char>{1, 255});
static_assert(subcb(0, 1, 1) == CarryResult<unsigned char>{1, 254});
static_assert(subcb(0, 255, 0) == CarryResult<unsigned char>{1, 1});
static_assert(subcb(0, 255, 1) == CarryResult<unsigned char>{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<unsigned char>{1, 255});

0 comments on commit f209352

Please sign in to comment.