Skip to content
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

[clang] Implement constexpr evaluation for __builtin_{add,sub}c #66005

Closed
wants to merge 1 commit into from

Conversation

BertalanD
Copy link
Member

GCC has gained support for these multiprecision arithmetic builtins in r14-1896-g2b4e0415ad6, and although they aren't explicitly specified as such in the documentation, they are usable in a constexpr context.

This commit adds constexpr evaluation support to Clang to match GCC's behavior. The implementation mirrors how the builtins are lowered to a pair of u{add,sub}.with.overflow operations and the carryout is set to 1 if either of those result in an overflow.

@BertalanD BertalanD requested a review from a team as a code owner September 11, 2023 20:22
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Sep 11, 2023
@llvmbot
Copy link
Collaborator

llvmbot commented Sep 11, 2023

@llvm/pr-subscribers-clang

Changes

GCC has gained support for these multiprecision arithmetic builtins in r14-1896-g2b4e0415ad6, and although they aren't explicitly specified as such in the documentation, they are usable in a constexpr context.

This commit adds constexpr evaluation support to Clang to match GCC's behavior. The implementation mirrors how the builtins are lowered to a pair of u{add,sub}.with.overflow operations and the carryout is set to 1 if either of those result in an overflow.

Full diff: https://github.com/llvm/llvm-project/pull/66005.diff

3 Files Affected:

  • (modified) clang/include/clang/Basic/Builtins.def (+10-10)
  • (modified) clang/lib/AST/ExprConstant.cpp (+50)
  • (added) clang/test/SemaCXX/builtins-multiprecision.cpp (+105)
diff --git a/clang/include/clang/Basic/Builtins.def b/clang/include/clang/Basic/Builtins.def
index 586dcf05170eb58..8a54850cca672ba 100644
--- a/clang/include/clang/Basic/Builtins.def
+++ b/clang/include/clang/Basic/Builtins.def
@@ -1619,16 +1619,16 @@ BUILTIN(__builtin_assume, "vb", "nE")
 BUILTIN(__builtin_assume_separate_storage, "vvCD*vCD*", "nE")
 
 // Multiprecision Arithmetic Builtins.
-BUILTIN(__builtin_addcb, "UcUcCUcCUcCUc*", "n")
-BUILTIN(__builtin_addcs, "UsUsCUsCUsCUs*", "n")
-BUILTIN(__builtin_addc, "UiUiCUiCUiCUi*", "n")
-BUILTIN(__builtin_addcl, "ULiULiCULiCULiCULi*", "n")
-BUILTIN(__builtin_addcll, "ULLiULLiCULLiCULLiCULLi*", "n")
-BUILTIN(__builtin_subcb, "UcUcCUcCUcCUc*", "n")
-BUILTIN(__builtin_subcs, "UsUsCUsCUsCUs*", "n")
-BUILTIN(__builtin_subc, "UiUiCUiCUiCUi*", "n")
-BUILTIN(__builtin_subcl, "ULiULiCULiCULiCULi*", "n")
-BUILTIN(__builtin_subcll, "ULLiULLiCULLiCULLiCULLi*", "n")
+BUILTIN(__builtin_addcb, "UcUcCUcCUcCUc*", "nE")
+BUILTIN(__builtin_addcs, "UsUsCUsCUsCUs*", "nE")
+BUILTIN(__builtin_addc, "UiUiCUiCUiCUi*", "nE")
+BUILTIN(__builtin_addcl, "ULiULiCULiCULiCULi*", "nE")
+BUILTIN(__builtin_addcll, "ULLiULLiCULLiCULLiCULLi*", "nE")
+BUILTIN(__builtin_subcb, "UcUcCUcCUcCUc*", "nE")
+BUILTIN(__builtin_subcs, "UsUsCUsCUsCUs*", "nE")
+BUILTIN(__builtin_subc, "UiUiCUiCUiCUi*", "nE")
+BUILTIN(__builtin_subcl, "ULiULiCULiCULiCULi*", "nE")
+BUILTIN(__builtin_subcll, "ULLiULLiCULLiCULLiCULLi*", "nE")
 
 // Checked Arithmetic Builtins for Security.
 BUILTIN(__builtin_add_overflow, "b.", "ntE")
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index dfa48e9c030b6a3..c8f837f7fc1f688 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -12630,6 +12630,56 @@ bool IntExprEvaluator::VisitBuiltinCallExpr(const CallExpr *E,
       return false;
     return Success(DidOverflow, 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: {
+    APSInt X, Y, CarryIn;
+    LValue CarryOut;
+
+    QualType ResultType = E->getArg(3)->getType()->getPointeeType();
+    if (!EvaluateInteger(E->getArg(0), X, Info) ||
+        !EvaluateInteger(E->getArg(1), Y, Info) ||
+        !EvaluateInteger(E->getArg(2), CarryIn, Info) ||
+        !EvaluatePointer(E->getArg(3), CarryOut, Info))
+      return false;
+
+    APInt Result;
+    bool DidOverflow1 = false;
+    bool DidOverflow2 = 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 = X.uadd_ov(Y, DidOverflow1).uadd_ov(CarryIn, DidOverflow2);
+      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 = X.usub_ov(Y, DidOverflow1).usub_ov(CarryIn, DidOverflow2);
+      break;
+    }
+
+    APSInt DidOverflow(
+        APInt(Result.getBitWidth(), DidOverflow1 || DidOverflow2));
+    APValue DidOverflowVal(DidOverflow);
+    if (!handleAssignment(Info, E, CarryOut, ResultType, DidOverflowVal))
+      return false;
+    return Success(Result, E);
+  }
   }
 }
 
diff --git a/clang/test/SemaCXX/builtins-multiprecision.cpp b/clang/test/SemaCXX/builtins-multiprecision.cpp
new file mode 100644
index 000000000000000..bad20242a6e82cf
--- /dev/null
+++ b/clang/test/SemaCXX/builtins-multiprecision.cpp
@@ -0,0 +1,105 @@
+// RUN: %clang_cc1 -fsyntax-only -std=c++17 -verify %s
+// expected-no-diagnostics
+
+#include 
+
+template
+struct Result {
+  T value;
+  T carry;
+  constexpr bool operator==(const Result &Other) {
+    return value == Other.value && carry == Other.carry;
+  }
+};
+
+template
+constexpr Result add(T Lhs, T Rhs, T Carryin)
+{
+  T Carryout = 0;
+  if constexpr(__is_same(T, unsigned char))
+    return { __builtin_addcb(Lhs, Rhs, Carryin, &Carryout), Carryout };
+  else if constexpr(__is_same(T, unsigned short))
+    return { __builtin_addcs(Lhs, Rhs, Carryin, &Carryout), Carryout };
+  else if constexpr(__is_same(T, unsigned int))
+    return { __builtin_addc(Lhs, Rhs, Carryin, &Carryout), Carryout };
+  else if constexpr(__is_same(T, unsigned long))
+    return { __builtin_addcl(Lhs, Rhs, Carryin, &Carryout), Carryout };
+  else if constexpr(__is_same(T, unsigned long long))
+    return { __builtin_addcll(Lhs, Rhs, Carryin, &Carryout), Carryout };
+}
+
+static_assert(add(0, 0, 0) == Result{0, 0});
+static_assert(add(0, 0, 1) == Result{1, 0});
+static_assert(add(UCHAR_MAX - 1, 1, 1) == Result{0, 1});
+static_assert(add(UCHAR_MAX, 1, 0) == Result{0, 1});
+static_assert(add(UCHAR_MAX, 1, 1) == Result{1, 1});
+
+static_assert(add(0, 0, 0) == Result{0, 0});
+static_assert(add(0, 0, 1) == Result{1, 0});
+static_assert(add(USHRT_MAX - 1, 1, 1) == Result{0, 1});
+static_assert(add(USHRT_MAX, 1, 0) == Result{0, 1});
+static_assert(add(USHRT_MAX, 1, 1) == Result{1, 1});
+
+static_assert(add(0, 0, 0) == Result{0, 0});
+static_assert(add(0, 0, 1) == Result{1, 0});
+static_assert(add(UINT_MAX - 1, 1, 1) == Result{0, 1});
+static_assert(add(UINT_MAX, 1, 0) == Result{0, 1});
+static_assert(add(UINT_MAX, 1, 1) == Result{1, 1});
+
+static_assert(add(0, 0, 0) == Result{0, 0});
+static_assert(add(0, 0, 1) == Result{1, 0});
+static_assert(add(ULONG_MAX - 1, 1, 1) == Result{0, 1});
+static_assert(add(ULONG_MAX, 1, 0) == Result{0, 1});
+static_assert(add(ULONG_MAX, 1, 1) == Result{1, 1});
+
+static_assert(add(0, 0, 0) == Result{0, 0});
+static_assert(add(0, 0, 1) == Result{1, 0});
+static_assert(add(ULLONG_MAX - 1, 1, 1) == Result{0, 1});
+static_assert(add(ULLONG_MAX, 1, 0) == Result{0, 1});
+static_assert(add(ULLONG_MAX, 1, 1) == Result{1, 1});
+
+template
+constexpr Result sub(T Lhs, T Rhs, T Carryin)
+{
+  T Carryout = 0;
+  if constexpr(__is_same(T, unsigned char))
+    return { __builtin_subcb(Lhs, Rhs, Carryin, &Carryout), Carryout };
+  else if constexpr(__is_same(T, unsigned short))
+    return { __builtin_subcs(Lhs, Rhs, Carryin, &Carryout), Carryout };
+  else if constexpr(__is_same(T, unsigned int))
+    return { __builtin_subc(Lhs, Rhs, Carryin, &Carryout), Carryout };
+  else if constexpr(__is_same(T, unsigned long))
+    return { __builtin_subcl(Lhs, Rhs, Carryin, &Carryout), Carryout };
+  else if constexpr(__is_same(T, unsigned long long))
+    return { __builtin_subcll(Lhs, Rhs, Carryin, &Carryout), Carryout };
+}
+
+static_assert(sub(0, 0, 0) == Result{0, 0});
+static_assert(sub(0, 0, 1) == Result{UCHAR_MAX, 1});
+static_assert(sub(0, 1, 0) == Result{UCHAR_MAX, 1});
+static_assert(sub(0, 1, 1) == Result{UCHAR_MAX - 1, 1});
+static_assert(sub(1, 0, 0) == Result{1, 0});
+
+static_assert(sub(0, 0, 0) == Result{0, 0});
+static_assert(sub(0, 0, 1) == Result{USHRT_MAX, 1});
+static_assert(sub(0, 1, 0) == Result{USHRT_MAX, 1});
+static_assert(sub(0, 1, 1) == Result{USHRT_MAX - 1, 1});
+static_assert(sub(1, 0, 0) == Result{1, 0});
+
+static_assert(sub(0, 0, 0) == Result{0, 0});
+static_assert(sub(0, 0, 1) == Result{UINT_MAX, 1});
+static_assert(sub(0, 1, 0) == Result{UINT_MAX, 1});
+static_assert(sub(0, 1, 1) == Result{UINT_MAX - 1, 1});
+static_assert(sub(1, 0, 0) == Result{1, 0});
+
+static_assert(sub(0, 0, 0) == Result{0, 0});
+static_assert(sub(0, 0, 1) == Result{ULONG_MAX, 1});
+static_assert(sub(0, 1, 0) == Result{ULONG_MAX, 1});
+static_assert(sub(0, 1, 1) == Result{ULONG_MAX - 1, 1});
+static_assert(sub(1, 0, 0) == Result{1, 0});
+
+static_assert(sub(0, 0, 0) == Result{0, 0});
+static_assert(sub(0, 0, 1) == Result{ULLONG_MAX, 1});
+static_assert(sub(0, 1, 0) == Result{ULLONG_MAX, 1});
+static_assert(sub(0, 1, 1) == Result{ULLONG_MAX - 1, 1});
+static_assert(sub(1, 0, 0) == Result{1, 0});

GCC has gained support for these multiprecision arithmetic builtins in
`r14-1896-g2b4e0415ad6`, and although they aren't explicitly specified
as such in the documentation, they are usable in a constexpr context.

This commit adds constexpr evaluation support to Clang to match GCC's
behavior. The implementation mirrors how the builtins are lowered to a
pair of `u{add,sub}.with.overflow` operations and the carryout is set to
1 if either of those result in an overflow.
@cor3ntin
Copy link
Contributor

cor3ntin commented Oct 3, 2023

@cjdb wanna look at this one?

@BertalanD
Copy link
Member Author

Obsoleted by #81656

@BertalanD BertalanD closed this Apr 26, 2024
@BertalanD BertalanD deleted the constexpr-multiprecision branch April 26, 2024 08:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants