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][Interp] Add basic support for _BitInt #68069

Merged
merged 2 commits into from
Oct 11, 2023
Merged

Conversation

tbaederr
Copy link
Contributor

@tbaederr tbaederr commented Oct 3, 2023

Make sure we pass the expected bitwidth around when casting to IntAP/IntAPS and move the tests to their own file.

This makes it easier to test the IntegralAP code for different bit widths than 128.

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Oct 3, 2023
@llvmbot
Copy link
Collaborator

llvmbot commented Oct 3, 2023

@llvm/pr-subscribers-clang

Changes

Make sure we pass the expected bitwidth around when casting to IntAP/IntAPS and move the tests to their own file.

This makes it easier to test the IntegralAP code for different bit widths than 128.


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

8 Files Affected:

  • (modified) clang/lib/AST/Interp/ByteCodeExprGen.cpp (+12)
  • (modified) clang/lib/AST/Interp/Context.h (+2)
  • (modified) clang/lib/AST/Interp/IntegralAP.h (+9-7)
  • (modified) clang/lib/AST/Interp/Interp.h (+57-1)
  • (modified) clang/lib/AST/Interp/InterpBuiltin.cpp (+2-1)
  • (modified) clang/lib/AST/Interp/Opcodes.td (+28-2)
  • (added) clang/test/AST/Interp/intap.cpp (+83)
  • (modified) clang/test/AST/Interp/literals.cpp (-75)
diff --git a/clang/lib/AST/Interp/ByteCodeExprGen.cpp b/clang/lib/AST/Interp/ByteCodeExprGen.cpp
index e266804a4e75dea..f899c2fe22ecd8f 100644
--- a/clang/lib/AST/Interp/ByteCodeExprGen.cpp
+++ b/clang/lib/AST/Interp/ByteCodeExprGen.cpp
@@ -138,6 +138,13 @@ bool ByteCodeExprGen<Emitter>::VisitCastExpr(const CastExpr *CE) {
     if (!this->visit(SubExpr))
       return false;
 
+    if (ToT == PT_IntAP)
+      return this->emitCastFloatingIntegralAP(Ctx.getBitWidth(CE->getType()),
+                                              CE);
+    if (ToT == PT_IntAPS)
+      return this->emitCastFloatingIntegralAPS(Ctx.getBitWidth(CE->getType()),
+                                               CE);
+
     return this->emitCastFloatingIntegral(*ToT, CE);
   }
 
@@ -183,6 +190,11 @@ bool ByteCodeExprGen<Emitter>::VisitCastExpr(const CastExpr *CE) {
       return true;
     }
 
+    if (ToT == PT_IntAP)
+      return this->emitCastAP(*FromT, Ctx.getBitWidth(CE->getType()), CE);
+    if (ToT == PT_IntAPS)
+      return this->emitCastAPS(*FromT, Ctx.getBitWidth(CE->getType()), CE);
+
     return this->emitCast(*FromT, *ToT, CE);
   }
 
diff --git a/clang/lib/AST/Interp/Context.h b/clang/lib/AST/Interp/Context.h
index 958b50b1615ad18..6df61e93ad83abc 100644
--- a/clang/lib/AST/Interp/Context.h
+++ b/clang/lib/AST/Interp/Context.h
@@ -64,6 +64,8 @@ class Context final {
   unsigned getCharBit() const;
   /// Return the floating-point semantics for T.
   const llvm::fltSemantics &getFloatSemantics(QualType T) const;
+  /// Return the size of T in bits.
+  uint32_t getBitWidth(QualType T) const { return Ctx.getIntWidth(T); }
 
   /// Classifies an expression.
   std::optional<PrimType> classify(QualType T) const;
diff --git a/clang/lib/AST/Interp/IntegralAP.h b/clang/lib/AST/Interp/IntegralAP.h
index a8df431bef11784..f9a33bbcd7bd7fa 100644
--- a/clang/lib/AST/Interp/IntegralAP.h
+++ b/clang/lib/AST/Interp/IntegralAP.h
@@ -37,8 +37,12 @@ template <bool Signed> class IntegralAP final {
   APSInt V;
 
   template <typename T> static T truncateCast(const APSInt &V) {
-    return std::is_signed_v<T> ? V.trunc(sizeof(T) * 8).getSExtValue()
-                               : V.trunc(sizeof(T) * 8).getZExtValue();
+    constexpr unsigned BitSize = sizeof(T) * 8;
+    if (BitSize >= V.getBitWidth())
+      return std::is_signed_v<T> ? V.getSExtValue() : V.getZExtValue();
+
+    return std::is_signed_v<T> ? V.trunc(BitSize).getSExtValue()
+                               : V.trunc(BitSize).getZExtValue();
   }
 
 public:
@@ -89,10 +93,9 @@ template <bool Signed> class IntegralAP final {
   }
 
   template <unsigned Bits, bool InputSigned>
-  static IntegralAP from(Integral<Bits, InputSigned> I) {
-    // FIXME: Take bits parameter.
+  static IntegralAP from(Integral<Bits, InputSigned> I, unsigned BitWidth) {
     APSInt Copy =
-        APSInt(APInt(128, static_cast<int64_t>(I), InputSigned), !Signed);
+        APSInt(APInt(BitWidth, static_cast<int64_t>(I), InputSigned), !Signed);
     Copy.setIsSigned(Signed);
 
     assert(Copy.isSigned() == Signed);
@@ -108,8 +111,7 @@ template <bool Signed> class IntegralAP final {
     return IntegralAP(0);
   }
 
-  // FIXME: This can't be static if the bitwidth depends on V.
-  static constexpr unsigned bitWidth() { return 128; }
+  constexpr unsigned bitWidth() const { return V.getBitWidth(); }
 
   APSInt toAPSInt(unsigned Bits = 0) const { return V; }
   APValue toAPValue() const { return APValue(V); }
diff --git a/clang/lib/AST/Interp/Interp.h b/clang/lib/AST/Interp/Interp.h
index 9d5ec3315415cf7..d3ee28c0315cda8 100644
--- a/clang/lib/AST/Interp/Interp.h
+++ b/clang/lib/AST/Interp/Interp.h
@@ -1561,6 +1561,22 @@ inline bool CastFP(InterpState &S, CodePtr OpPC, const llvm::fltSemantics *Sem,
   return true;
 }
 
+/// Like Cast(), but we cast to an arbitrary-bitwidth integral, so we need
+/// to know what bitwidth the result should be.
+template <PrimType Name, class T = typename PrimConv<Name>::T>
+bool CastAP(InterpState &S, CodePtr OpPC, uint32_t BitWidth) {
+  S.Stk.push<IntegralAP<false>>(
+      IntegralAP<false>::from(S.Stk.pop<T>(), BitWidth));
+  return true;
+}
+
+template <PrimType Name, class T = typename PrimConv<Name>::T>
+bool CastAPS(InterpState &S, CodePtr OpPC, uint32_t BitWidth) {
+  S.Stk.push<IntegralAP<true>>(
+      IntegralAP<true>::from(S.Stk.pop<T>(), BitWidth));
+  return true;
+}
+
 template <PrimType Name, class T = typename PrimConv<Name>::T>
 bool CastIntegralFloating(InterpState &S, CodePtr OpPC,
                           const llvm::fltSemantics *Sem,
@@ -1601,6 +1617,46 @@ bool CastFloatingIntegral(InterpState &S, CodePtr OpPC) {
   }
 }
 
+static inline bool CastFloatingIntegralAP(InterpState &S, CodePtr OpPC,
+                                          uint32_t BitWidth) {
+  const Floating &F = S.Stk.pop<Floating>();
+
+  APSInt Result(BitWidth, /*IsUnsigned=*/true);
+  auto Status = F.convertToInteger(Result);
+
+  // Float-to-Integral overflow check.
+  if ((Status & APFloat::opStatus::opInvalidOp) && F.isFinite()) {
+    const Expr *E = S.Current->getExpr(OpPC);
+    QualType Type = E->getType();
+
+    S.CCEDiag(E, diag::note_constexpr_overflow) << F.getAPFloat() << Type;
+    return S.noteUndefinedBehavior();
+  }
+
+  S.Stk.push<IntegralAP<true>>(IntegralAP<true>(Result));
+  return CheckFloatResult(S, OpPC, F, Status);
+}
+
+static inline bool CastFloatingIntegralAPS(InterpState &S, CodePtr OpPC,
+                                           uint32_t BitWidth) {
+  const Floating &F = S.Stk.pop<Floating>();
+
+  APSInt Result(BitWidth, /*IsUnsigned=*/false);
+  auto Status = F.convertToInteger(Result);
+
+  // Float-to-Integral overflow check.
+  if ((Status & APFloat::opStatus::opInvalidOp) && F.isFinite()) {
+    const Expr *E = S.Current->getExpr(OpPC);
+    QualType Type = E->getType();
+
+    S.CCEDiag(E, diag::note_constexpr_overflow) << F.getAPFloat() << Type;
+    return S.noteUndefinedBehavior();
+  }
+
+  S.Stk.push<IntegralAP<true>>(IntegralAP<true>(Result));
+  return CheckFloatResult(S, OpPC, F, Status);
+}
+
 template <PrimType Name, class T = typename PrimConv<Name>::T>
 bool CastPointerIntegral(InterpState &S, CodePtr OpPC) {
   const Pointer &Ptr = S.Stk.pop<Pointer>();
@@ -1690,7 +1746,7 @@ inline bool Shl(InterpState &S, CodePtr OpPC) {
 
   typename LT::AsUnsigned R;
   LT::AsUnsigned::shiftLeft(LT::AsUnsigned::from(LHS),
-                            LT::AsUnsigned::from(RHS), Bits, &R);
+                            LT::AsUnsigned::from(RHS, Bits), Bits, &R);
   S.Stk.push<LT>(LT::from(R));
   return true;
 }
diff --git a/clang/lib/AST/Interp/InterpBuiltin.cpp b/clang/lib/AST/Interp/InterpBuiltin.cpp
index bba0255219bc0d7..7552c1b88cff60c 100644
--- a/clang/lib/AST/Interp/InterpBuiltin.cpp
+++ b/clang/lib/AST/Interp/InterpBuiltin.cpp
@@ -41,7 +41,8 @@ static APSInt peekToAPSInt(InterpStack &Stk, PrimType T, size_t Offset = 0) {
   APSInt R;
   INT_TYPE_SWITCH(T, {
     T Val = Stk.peek<T>(Offset);
-    R = APSInt(APInt(T::bitWidth(), static_cast<uint64_t>(Val), T::isSigned()));
+    R = APSInt(
+        APInt(Val.bitWidth(), static_cast<uint64_t>(Val), T::isSigned()));
   });
 
   return R;
diff --git a/clang/lib/AST/Interp/Opcodes.td b/clang/lib/AST/Interp/Opcodes.td
index 9fc4938bb37bde8..ef6f730a21f027f 100644
--- a/clang/lib/AST/Interp/Opcodes.td
+++ b/clang/lib/AST/Interp/Opcodes.td
@@ -560,7 +560,7 @@ def FromCastTypeClass : TypeClass {
 }
 
 def ToCastTypeClass : TypeClass {
-  let Types = [Uint8, Sint8, Uint16, Sint16, Uint32, Sint32, Uint64, Sint64, Bool, IntAP, IntAPS];
+  let Types = [Uint8, Sint8, Uint16, Sint16, Uint32, Sint32, Uint64, Sint64, Bool];
 }
 
 def Cast: Opcode {
@@ -573,6 +573,22 @@ def CastFP : Opcode {
   let Args = [ArgFltSemantics, ArgRoundingMode];
 }
 
+def FixedSizeIntegralTypes : TypeClass {
+  let Types = [Uint8, Sint8, Uint16, Sint16, Uint32, Sint32, Uint64, Sint64, Bool];
+}
+
+def CastAP : Opcode {
+  let Types = [AluTypeClass];
+  let Args = [ArgUint32];
+  let HasGroup = 1;
+}
+
+def CastAPS : Opcode {
+  let Types = [AluTypeClass];
+  let Args = [ArgUint32];
+  let HasGroup = 1;
+}
+
 // Cast an integer to a floating type
 def CastIntegralFloating : Opcode {
   let Types = [AluTypeClass];
@@ -582,11 +598,21 @@ def CastIntegralFloating : Opcode {
 
 // Cast a floating to an integer type
 def CastFloatingIntegral : Opcode {
-  let Types = [AluTypeClass];
+  let Types = [FixedSizeIntegralTypes];
   let Args = [];
   let HasGroup = 1;
 }
 
+def CastFloatingIntegralAP : Opcode {
+  let Types = [];
+  let Args = [ArgUint32];
+}
+
+def CastFloatingIntegralAPS : Opcode {
+  let Types = [];
+  let Args = [ArgUint32];
+}
+
 def CastPointerIntegral : Opcode {
   let Types = [AluTypeClass];
   let Args = [];
diff --git a/clang/test/AST/Interp/intap.cpp b/clang/test/AST/Interp/intap.cpp
new file mode 100644
index 000000000000000..8d8f3f27fc01672
--- /dev/null
+++ b/clang/test/AST/Interp/intap.cpp
@@ -0,0 +1,83 @@
+// RUN: %clang_cc1 -verify=ref %s
+// RUN: %clang_cc1 -fexperimental-new-constant-interpreter -verify %s
+
+constexpr _BitInt(2) A = 0;
+constexpr _BitInt(2) B = A + 1;
+constexpr _BitInt(2) C = B + 1; // expected-warning {{from 2 to -2}} \
+                                // ref-warning {{from 2 to -2}}
+static_assert(C == -2, "");
+
+
+#ifdef __SIZEOF_INT128__
+namespace i128 {
+  typedef __int128 int128_t;
+  typedef unsigned __int128 uint128_t;
+  constexpr int128_t I128_1 = 12;
+  static_assert(I128_1 == 12, "");
+  static_assert(I128_1 != 10, "");
+  static_assert(I128_1 != 12, ""); // expected-error{{failed}} \
+                                   // ref-error{{failed}} \
+                                   // expected-note{{evaluates to}} \
+                                   // ref-note{{evaluates to}}
+
+  static const __uint128_t UINT128_MAX =__uint128_t(__int128_t(-1L));
+  static_assert(UINT128_MAX == -1, "");
+
+  static const __int128_t INT128_MAX = UINT128_MAX >> (__int128_t)1;
+  static_assert(INT128_MAX != 0, "");
+  static const __int128_t INT128_MIN = -INT128_MAX - 1;
+  constexpr __int128 A = INT128_MAX + 1; // expected-error {{must be initialized by a constant expression}} \
+                                         // expected-note {{outside the range}} \
+                                         // ref-error {{must be initialized by a constant expression}} \
+                                         // ref-note {{outside the range}}
+  constexpr int128_t Two = (int128_t)1 << 1ul;
+  static_assert(Two == 2, "");
+
+  constexpr uint128_t AllOnes = ~static_cast<uint128_t>(0);
+  static_assert(AllOnes == UINT128_MAX, "");
+
+#if __cplusplus >= 201402L
+  template <typename T>
+  constexpr T CastFrom(__int128_t A) {
+    T B = (T)A;
+    return B;
+  }
+  static_assert(CastFrom<char>(12) == 12, "");
+  static_assert(CastFrom<unsigned char>(12) == 12, "");
+  static_assert(CastFrom<long>(12) == 12, "");
+  static_assert(CastFrom<unsigned short>(12) == 12, "");
+  static_assert(CastFrom<int128_t>(12) == 12, "");
+  static_assert(CastFrom<float>(12) == 12, "");
+  static_assert(CastFrom<double>(12) == 12, "");
+  static_assert(CastFrom<long double>(12) == 12, "");
+
+  static_assert(CastFrom<char>(AllOnes) == -1, "");
+  static_assert(CastFrom<unsigned char>(AllOnes) == 0xFF, "");
+  static_assert(CastFrom<long>(AllOnes) == -1, "");
+  static_assert(CastFrom<unsigned short>(AllOnes) == 0xFFFF, "");
+  static_assert(CastFrom<int>(AllOnes) == -1, "");
+  static_assert(CastFrom<int128_t>(AllOnes) == -1, "");
+  static_assert(CastFrom<uint128_t>(AllOnes) == AllOnes, "");
+
+  template <typename T>
+  constexpr __int128 CastTo(T A) {
+    int128_t B = (int128_t)A;
+    return B;
+  }
+  static_assert(CastTo<char>(12) == 12, "");
+  static_assert(CastTo<unsigned char>(12) == 12, "");
+  static_assert(CastTo<long>(12) == 12, "");
+  static_assert(CastTo<unsigned long long>(12) == 12, "");
+  static_assert(CastTo<float>(12) == 12, "");
+  static_assert(CastTo<double>(12) == 12, "");
+  static_assert(CastTo<long double>(12) == 12, "");
+#endif
+
+  constexpr int128_t Error = __LDBL_MAX__; // ref-warning {{implicit conversion of out of range value}} \
+                                           // ref-error {{must be initialized by a constant expression}} \
+                                           // ref-note {{is outside the range of representable values of type}} \
+                                           // expected-warning {{implicit conversion of out of range value}} \
+                                           // expected-error {{must be initialized by a constant expression}} \
+                                           // expected-note {{is outside the range of representable values of type}}
+}
+#endif
diff --git a/clang/test/AST/Interp/literals.cpp b/clang/test/AST/Interp/literals.cpp
index 00875bcf44dc8e6..ceda59405ea9105 100644
--- a/clang/test/AST/Interp/literals.cpp
+++ b/clang/test/AST/Interp/literals.cpp
@@ -26,81 +26,6 @@ static_assert(number != 10, ""); // expected-error{{failed}} \
                                  // expected-note{{evaluates to}} \
                                  // ref-note{{evaluates to}}
 
-
-#ifdef __SIZEOF_INT128__
-namespace i128 {
-  typedef __int128 int128_t;
-  typedef unsigned __int128 uint128_t;
-  constexpr int128_t I128_1 = 12;
-  static_assert(I128_1 == 12, "");
-  static_assert(I128_1 != 10, "");
-  static_assert(I128_1 != 12, ""); // expected-error{{failed}} \
-                                   // ref-error{{failed}} \
-                                   // expected-note{{evaluates to}} \
-                                   // ref-note{{evaluates to}}
-
-  static const __uint128_t UINT128_MAX =__uint128_t(__int128_t(-1L));
-  static_assert(UINT128_MAX == -1, "");
-
-  static const __int128_t INT128_MAX = UINT128_MAX >> (__int128_t)1;
-  static_assert(INT128_MAX != 0, "");
-  static const __int128_t INT128_MIN = -INT128_MAX - 1;
-  constexpr __int128 A = INT128_MAX + 1; // expected-error {{must be initialized by a constant expression}} \
-                                         // expected-note {{outside the range}} \
-                                         // ref-error {{must be initialized by a constant expression}} \
-                                         // ref-note {{outside the range}}
-  constexpr int128_t Two = (int128_t)1 << 1ul;
-  static_assert(Two == 2, "");
-
-  constexpr uint128_t AllOnes = ~static_cast<uint128_t>(0);
-  static_assert(AllOnes == UINT128_MAX, "");
-
-#if __cplusplus >= 201402L
-  template <typename T>
-  constexpr T CastFrom(__int128_t A) {
-    T B = (T)A;
-    return B;
-  }
-  static_assert(CastFrom<char>(12) == 12, "");
-  static_assert(CastFrom<unsigned char>(12) == 12, "");
-  static_assert(CastFrom<long>(12) == 12, "");
-  static_assert(CastFrom<unsigned short>(12) == 12, "");
-  static_assert(CastFrom<int128_t>(12) == 12, "");
-  static_assert(CastFrom<float>(12) == 12, "");
-  static_assert(CastFrom<double>(12) == 12, "");
-  static_assert(CastFrom<long double>(12) == 12, "");
-
-  static_assert(CastFrom<char>(AllOnes) == -1, "");
-  static_assert(CastFrom<unsigned char>(AllOnes) == 0xFF, "");
-  static_assert(CastFrom<long>(AllOnes) == -1, "");
-  static_assert(CastFrom<unsigned short>(AllOnes) == 0xFFFF, "");
-  static_assert(CastFrom<int>(AllOnes) == -1, "");
-  static_assert(CastFrom<int128_t>(AllOnes) == -1, "");
-  static_assert(CastFrom<uint128_t>(AllOnes) == AllOnes, "");
-
-  template <typename T>
-  constexpr __int128 CastTo(T A) {
-    int128_t B = (int128_t)A;
-    return B;
-  }
-  static_assert(CastTo<char>(12) == 12, "");
-  static_assert(CastTo<unsigned char>(12) == 12, "");
-  static_assert(CastTo<long>(12) == 12, "");
-  static_assert(CastTo<unsigned long long>(12) == 12, "");
-  static_assert(CastTo<float>(12) == 12, "");
-  static_assert(CastTo<double>(12) == 12, "");
-  static_assert(CastTo<long double>(12) == 12, "");
-#endif
-
-constexpr int128_t Error = __LDBL_MAX__; // ref-warning {{implicit conversion of out of range value}} \
-                                         // ref-error {{must be initialized by a constant expression}} \
-                                         // ref-note {{is outside the range of representable values of type}} \
-                                         // expected-warning {{implicit conversion of out of range value}} \
-                                         // expected-error {{must be initialized by a constant expression}} \
-                                         // expected-note {{is outside the range of representable values of type}}
-}
-#endif
-
 constexpr bool b = number;
 static_assert(b, "");
 constexpr int one = true;

S.Stk.push<IntegralAP<true>>(IntegralAP<true>(Result));
return CheckFloatResult(S, OpPC, F, Status);
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

CastFloatingIntegralAP(S) needs a helper function to get rid of the code duplication.

@@ -582,11 +598,21 @@ def CastIntegralFloating : Opcode {

// Cast a floating to an integer type
def CastFloatingIntegral : Opcode {
let Types = [AluTypeClass];
let Types = [FixedSizeIntegralTypes];
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The type classes for these get kinda confusing... but I just can't come up with the correct syntax to define them inline.

Make sure we pass the expected bitwidth around when casting to
IntAP/IntAPS and move the tests to their own file.

constexpr _BitInt(2) A = 0;
constexpr _BitInt(2) B = A + 1;
constexpr _BitInt(2) C = B + 1; // expected-warning {{from 2 to -2}} \
Copy link
Collaborator

Choose a reason for hiding this comment

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

Since _BitInt can end up being pretty sizable (I think we removed the size limit?), can we test something like a 1024 sized one?

@tbaederr tbaederr merged commit 26d9f85 into llvm:main Oct 11, 2023
3 checks passed
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