[clang] [clang][Interp] Integral pointers (PR #84159)

Timm Baeder via cfe-commits cfe-commits at lists.llvm.org
Wed Mar 6 07:11:53 PST 2024


Timm =?utf-8?q?Bäder?= <tbaeder at redhat.com>
Message-ID:
In-Reply-To: <llvm.org/llvm/llvm-project/pull/84159 at github.com>


https://github.com/tbaederr updated https://github.com/llvm/llvm-project/pull/84159

>From 5eacd7478d3db7472d5ab983468a441b1f61ad7a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbaeder at redhat.com>
Date: Wed, 6 Mar 2024 08:36:52 +0100
Subject: [PATCH 1/2] Integral pointers

---
 clang/lib/AST/Interp/ByteCodeExprGen.cpp |  74 ++++-
 clang/lib/AST/Interp/ByteCodeStmtGen.cpp |   2 +-
 clang/lib/AST/Interp/Descriptor.h        |   1 +
 clang/lib/AST/Interp/FunctionPointer.h   |  10 +-
 clang/lib/AST/Interp/Interp.cpp          |   5 +
 clang/lib/AST/Interp/Interp.h            |  89 ++++--
 clang/lib/AST/Interp/InterpBlock.cpp     |   4 +-
 clang/lib/AST/Interp/Opcodes.td          |  12 +
 clang/lib/AST/Interp/Pointer.cpp         | 176 ++++++++---
 clang/lib/AST/Interp/Pointer.h           | 366 ++++++++++++++++-------
 clang/lib/AST/Interp/PrimType.h          |   4 +
 clang/test/AST/Interp/c.c                |  18 ++
 clang/test/AST/Interp/const-eval.c       | 194 ++++++++++++
 13 files changed, 775 insertions(+), 180 deletions(-)
 create mode 100644 clang/test/AST/Interp/const-eval.c

diff --git a/clang/lib/AST/Interp/ByteCodeExprGen.cpp b/clang/lib/AST/Interp/ByteCodeExprGen.cpp
index d887170cbc5d2d..67e7d89c033d63 100644
--- a/clang/lib/AST/Interp/ByteCodeExprGen.cpp
+++ b/clang/lib/AST/Interp/ByteCodeExprGen.cpp
@@ -161,10 +161,18 @@ bool ByteCodeExprGen<Emitter>::VisitCastExpr(const CastExpr *CE) {
     return this->emitCastFloatingIntegral(*ToT, CE);
   }
 
-  case CK_NullToPointer:
+  case CK_NullToPointer: {
     if (DiscardResult)
       return true;
-    return this->emitNull(classifyPrim(CE->getType()), CE);
+
+    const Descriptor *Desc = nullptr;
+    const QualType PointeeType = CE->getType()->getPointeeType();
+    if (!PointeeType.isNull()) {
+      if (std::optional<PrimType> T = classify(PointeeType))
+        Desc = P.createDescriptor(SubExpr, *T);
+    }
+    return this->emitNull(classifyPrim(CE->getType()), Desc, CE);
+  }
 
   case CK_PointerToIntegral: {
     if (DiscardResult)
@@ -187,6 +195,34 @@ bool ByteCodeExprGen<Emitter>::VisitCastExpr(const CastExpr *CE) {
     return true;
   }
 
+  case CK_IntegralToPointer: {
+    QualType IntType = SubExpr->getType();
+    assert(IntType->isIntegralOrEnumerationType());
+    if (!this->visit(SubExpr))
+      return false;
+    // FIXME: I think the discard is wrong since the int->ptr cast might cause a
+    // diagnostic.
+    PrimType T = classifyPrim(IntType);
+    if (DiscardResult)
+      return this->emitPop(T, CE);
+
+    QualType PtrType = CE->getType();
+    assert(PtrType->isPointerType());
+
+    const Descriptor *Desc;
+    if (std::optional<PrimType> T = classify(PtrType->getPointeeType())) {
+      Desc = P.createDescriptor(SubExpr, *T);
+    } else if (PtrType->getPointeeType()->isVoidType())
+      Desc = nullptr;
+    else {
+      Desc = P.createDescriptor(CE, PtrType->getPointeeType().getTypePtr(),
+                                Descriptor::InlineDescMD, true, false,
+                                /*IsMutable=*/false, nullptr);
+    }
+
+    return this->emitGetIntPtr(T, Desc, CE);
+  }
+
   case CK_AtomicToNonAtomic:
   case CK_ConstructorConversion:
   case CK_FunctionToPointerDecay:
@@ -195,13 +231,31 @@ bool ByteCodeExprGen<Emitter>::VisitCastExpr(const CastExpr *CE) {
   case CK_UserDefinedConversion:
     return this->delegate(SubExpr);
 
-  case CK_BitCast:
+  case CK_BitCast: {
+    // Reject bitcasts to atomic types.
     if (CE->getType()->isAtomicType()) {
       if (!this->discard(SubExpr))
         return false;
       return this->emitInvalidCast(CastKind::Reinterpret, CE);
     }
-    return this->delegate(SubExpr);
+
+    if (DiscardResult)
+      return this->discard(SubExpr);
+
+    std::optional<PrimType> FromT = classify(SubExpr->getType());
+    std::optional<PrimType> ToT = classifyPrim(CE->getType());
+    if (!FromT || !ToT)
+      return false;
+
+    assert(isPtrType(*FromT));
+    assert(isPtrType(*ToT));
+    if (FromT == ToT)
+      return this->delegate(SubExpr);
+
+    if (!this->visit(SubExpr))
+      return false;
+    return this->emitDecayPtr(*FromT, *ToT, CE);
+  }
 
   case CK_IntegralToBoolean:
   case CK_IntegralCast: {
@@ -233,7 +287,7 @@ bool ByteCodeExprGen<Emitter>::VisitCastExpr(const CastExpr *CE) {
     if (!this->visit(SubExpr))
       return false;
 
-    if (!this->emitNull(PtrT, CE))
+    if (!this->emitNull(PtrT, nullptr, CE))
       return false;
 
     return this->emitNE(PtrT, CE);
@@ -445,7 +499,7 @@ bool ByteCodeExprGen<Emitter>::VisitBinaryOperator(const BinaryOperator *BO) {
 
   // Pointer arithmetic special case.
   if (BO->getOpcode() == BO_Add || BO->getOpcode() == BO_Sub) {
-    if (T == PT_Ptr || (LT == PT_Ptr && RT == PT_Ptr))
+    if (isPtrType(*T) || (isPtrType(*LT) && isPtrType(*RT)))
       return this->VisitPointerArithBinOp(BO);
   }
 
@@ -2296,7 +2350,7 @@ bool ByteCodeExprGen<Emitter>::visitBool(const Expr *E) {
 
   // Convert pointers to bool.
   if (T == PT_Ptr || T == PT_FnPtr) {
-    if (!this->emitNull(*T, E))
+    if (!this->emitNull(*T, nullptr, E))
       return false;
     return this->emitNE(*T, E);
   }
@@ -2336,9 +2390,9 @@ bool ByteCodeExprGen<Emitter>::visitZeroInitializer(PrimType T, QualType QT,
   case PT_IntAPS:
     return this->emitZeroIntAPS(Ctx.getBitWidth(QT), E);
   case PT_Ptr:
-    return this->emitNullPtr(E);
+    return this->emitNullPtr(nullptr, E);
   case PT_FnPtr:
-    return this->emitNullFnPtr(E);
+    return this->emitNullFnPtr(nullptr, E);
   case PT_Float: {
     return this->emitConstFloat(APFloat::getZero(Ctx.getFloatSemantics(QT)), E);
   }
@@ -2930,7 +2984,7 @@ bool ByteCodeExprGen<Emitter>::VisitCXXNullPtrLiteralExpr(
   if (DiscardResult)
     return true;
 
-  return this->emitNullPtr(E);
+  return this->emitNullPtr(nullptr, E);
 }
 
 template <class Emitter>
diff --git a/clang/lib/AST/Interp/ByteCodeStmtGen.cpp b/clang/lib/AST/Interp/ByteCodeStmtGen.cpp
index 6da3860f98d8c0..95b482c9464654 100644
--- a/clang/lib/AST/Interp/ByteCodeStmtGen.cpp
+++ b/clang/lib/AST/Interp/ByteCodeStmtGen.cpp
@@ -110,7 +110,7 @@ bool ByteCodeStmtGen<Emitter>::emitLambdaStaticInvokerBody(
   // one here, and we don't need one either because the lambda cannot have
   // any captures, as verified above. Emit a null pointer. This is then
   // special-cased when interpreting to not emit any misleading diagnostics.
-  if (!this->emitNullPtr(MD))
+  if (!this->emitNullPtr(nullptr, MD))
     return false;
 
   // Forward all arguments from the static invoker to the lambda call operator.
diff --git a/clang/lib/AST/Interp/Descriptor.h b/clang/lib/AST/Interp/Descriptor.h
index 0f64d678f3ef6b..fa170e328325ac 100644
--- a/clang/lib/AST/Interp/Descriptor.h
+++ b/clang/lib/AST/Interp/Descriptor.h
@@ -160,6 +160,7 @@ struct Descriptor final {
 
   const Decl *asDecl() const { return Source.dyn_cast<const Decl *>(); }
   const Expr *asExpr() const { return Source.dyn_cast<const Expr *>(); }
+  const DeclTy &getSource() const { return Source; }
 
   const ValueDecl *asValueDecl() const {
     return dyn_cast_if_present<ValueDecl>(asDecl());
diff --git a/clang/lib/AST/Interp/FunctionPointer.h b/clang/lib/AST/Interp/FunctionPointer.h
index bb3da9b50aa552..717d385e6307ca 100644
--- a/clang/lib/AST/Interp/FunctionPointer.h
+++ b/clang/lib/AST/Interp/FunctionPointer.h
@@ -22,7 +22,11 @@ class FunctionPointer final {
   const Function *Func;
 
 public:
-  FunctionPointer() : Func(nullptr) {}
+  // FIXME: We might want to track the fact that the Function pointer
+  // has been created from an integer and is most likely garbage anyway.
+  FunctionPointer(int IntVal = 0, const Descriptor *Desc = nullptr)
+      : Func(reinterpret_cast<const Function *>(IntVal)) {}
+
   FunctionPointer(const Function *Func) : Func(Func) { assert(Func); }
 
   const Function *getFunction() const { return Func; }
@@ -53,6 +57,10 @@ class FunctionPointer final {
     return toAPValue().getAsString(Ctx, Func->getDecl()->getType());
   }
 
+  uint32_t getIntegerRepresentation() const {
+    return static_cast<uint32_t>(reinterpret_cast<uintptr_t>(Func));
+  }
+
   ComparisonCategoryResult compare(const FunctionPointer &RHS) const {
     if (Func == RHS.Func)
       return ComparisonCategoryResult::Equal;
diff --git a/clang/lib/AST/Interp/Interp.cpp b/clang/lib/AST/Interp/Interp.cpp
index 4f3cd6cd21a151..e4d421839bbbc4 100644
--- a/clang/lib/AST/Interp/Interp.cpp
+++ b/clang/lib/AST/Interp/Interp.cpp
@@ -282,6 +282,8 @@ bool CheckConstant(InterpState &S, CodePtr OpPC, const Descriptor *Desc) {
 }
 
 static bool CheckConstant(InterpState &S, CodePtr OpPC, const Pointer &Ptr) {
+  if (Ptr.isIntegralPointer())
+    return true;
   return CheckConstant(S, OpPC, Ptr.getDeclDesc());
 }
 
@@ -335,6 +337,9 @@ bool CheckConst(InterpState &S, CodePtr OpPC, const Pointer &Ptr) {
     return true;
   }
 
+  if (!Ptr.isBlockPointer())
+    return false;
+
   const QualType Ty = Ptr.getType();
   const SourceInfo &Loc = S.Current->getSource(OpPC);
   S.FFDiag(Loc, diag::note_constexpr_modify_const_type) << Ty;
diff --git a/clang/lib/AST/Interp/Interp.h b/clang/lib/AST/Interp/Interp.h
index 9c3d58d5619c46..6dcfa3f8c354c4 100644
--- a/clang/lib/AST/Interp/Interp.h
+++ b/clang/lib/AST/Interp/Interp.h
@@ -809,9 +809,9 @@ inline bool CmpHelperEQ<Pointer>(InterpState &S, CodePtr OpPC, CompareFn Fn) {
     // element in the same array are NOT equal. They have the same Base value,
     // but a different Offset. This is a pretty rare case, so we fix this here
     // by comparing pointers to the first elements.
-    if (!LHS.isDummy() && LHS.isArrayRoot())
+    if (!LHS.isZero() && !LHS.isDummy() && LHS.isArrayRoot())
       VL = LHS.atIndex(0).getByteOffset();
-    if (!RHS.isDummy() && RHS.isArrayRoot())
+    if (!RHS.isDummy() && !RHS.isDummy() && RHS.isArrayRoot())
       VR = RHS.atIndex(0).getByteOffset();
 
     S.Stk.push<BoolT>(BoolT::from(Fn(Compare(VL, VR))));
@@ -1368,6 +1368,8 @@ bool Load(InterpState &S, CodePtr OpPC) {
   const Pointer &Ptr = S.Stk.peek<Pointer>();
   if (!CheckLoad(S, OpPC, Ptr))
     return false;
+  if (!Ptr.isBlockPointer())
+    return false;
   S.Stk.push<T>(Ptr.deref<T>());
   return true;
 }
@@ -1377,6 +1379,8 @@ bool LoadPop(InterpState &S, CodePtr OpPC) {
   const Pointer &Ptr = S.Stk.pop<Pointer>();
   if (!CheckLoad(S, OpPC, Ptr))
     return false;
+  if (!Ptr.isBlockPointer())
+    return false;
   S.Stk.push<T>(Ptr.deref<T>());
   return true;
 }
@@ -1505,12 +1509,17 @@ bool OffsetHelper(InterpState &S, CodePtr OpPC, const T &Offset,
     return true;
   }
 
-  if (!CheckNull(S, OpPC, Ptr, CSK_ArrayIndex))
-    return false;
+  if (!CheckNull(S, OpPC, Ptr, CSK_ArrayIndex)) {
+    // The CheckNul will have emitted a note already, but we only
+    // abort in C++, since this is fine in C.
+    if (S.getLangOpts().CPlusPlus)
+      return false;
+  }
 
   // Arrays of unknown bounds cannot have pointers into them.
-  if (!CheckArray(S, OpPC, Ptr))
+  if (!CheckArray(S, OpPC, Ptr)) {
     return false;
+  }
 
   // Get a version of the index comparable to the type.
   T Index = T::from(Ptr.getIndex(), Offset.bitWidth());
@@ -1532,23 +1541,25 @@ bool OffsetHelper(InterpState &S, CodePtr OpPC, const T &Offset,
     Invalid = true;
   };
 
-  T MaxOffset = T::from(MaxIndex - Index, Offset.bitWidth());
-  if constexpr (Op == ArithOp::Add) {
-    // If the new offset would be negative, bail out.
-    if (Offset.isNegative() && (Offset.isMin() || -Offset > Index))
-      DiagInvalidOffset();
-
-    // If the new offset would be out of bounds, bail out.
-    if (Offset.isPositive() && Offset > MaxOffset)
-      DiagInvalidOffset();
-  } else {
-    // If the new offset would be negative, bail out.
-    if (Offset.isPositive() && Index < Offset)
-      DiagInvalidOffset();
-
-    // If the new offset would be out of bounds, bail out.
-    if (Offset.isNegative() && (Offset.isMin() || -Offset > MaxOffset))
-      DiagInvalidOffset();
+  if (Ptr.isBlockPointer()) {
+    T MaxOffset = T::from(MaxIndex - Index, Offset.bitWidth());
+    if constexpr (Op == ArithOp::Add) {
+      // If the new offset would be negative, bail out.
+      if (Offset.isNegative() && (Offset.isMin() || -Offset > Index))
+        DiagInvalidOffset();
+
+      // If the new offset would be out of bounds, bail out.
+      if (Offset.isPositive() && Offset > MaxOffset)
+        DiagInvalidOffset();
+    } else {
+      // If the new offset would be negative, bail out.
+      if (Offset.isPositive() && Index < Offset)
+        DiagInvalidOffset();
+
+      // If the new offset would be out of bounds, bail out.
+      if (Offset.isNegative() && (Offset.isMin() || -Offset > MaxOffset))
+        DiagInvalidOffset();
+    }
   }
 
   if (Invalid && !Ptr.isDummy() && S.getLangOpts().CPlusPlus)
@@ -1632,6 +1643,11 @@ inline bool SubPtr(InterpState &S, CodePtr OpPC) {
   const Pointer &LHS = S.Stk.pop<Pointer>();
   const Pointer &RHS = S.Stk.pop<Pointer>();
 
+  if (RHS.isZero()) {
+    S.Stk.push<T>(T::from(LHS.getIndex()));
+    return true;
+  }
+
   if (!Pointer::hasSameBase(LHS, RHS) && S.getLangOpts().CPlusPlus) {
     // TODO: Diagnose.
     return false;
@@ -1810,8 +1826,9 @@ static inline bool ZeroIntAPS(InterpState &S, CodePtr OpPC, uint32_t BitWidth) {
 }
 
 template <PrimType Name, class T = typename PrimConv<Name>::T>
-inline bool Null(InterpState &S, CodePtr OpPC) {
-  S.Stk.push<T>();
+inline bool Null(InterpState &S, CodePtr OpPC, const Descriptor *Desc) {
+  // Note: Desc can be null.
+  S.Stk.push<T>(0, Desc);
   return true;
 }
 
@@ -1912,6 +1929,10 @@ inline bool NoRet(InterpState &S, CodePtr OpPC) {
 
 inline bool NarrowPtr(InterpState &S, CodePtr OpPC) {
   const Pointer &Ptr = S.Stk.pop<Pointer>();
+  if (!S.getLangOpts().CPlusPlus) {
+    S.Stk.push<Pointer>(Ptr);
+    return true;
+  }
   S.Stk.push<Pointer>(Ptr.narrow());
   return true;
 }
@@ -2180,6 +2201,14 @@ inline bool GetFnPtr(InterpState &S, CodePtr OpPC, const Function *Func) {
   return true;
 }
 
+template <PrimType Name, class T = typename PrimConv<Name>::T>
+inline bool GetIntPtr(InterpState &S, CodePtr OpPC, const Descriptor *Desc) {
+  const T &IntVal = S.Stk.pop<T>();
+
+  S.Stk.push<Pointer>(static_cast<int64_t>(IntVal), Desc);
+  return true;
+}
+
 /// Just emit a diagnostic. The expression that caused emission of this
 /// op is not valid in a constant context.
 inline bool Invalid(InterpState &S, CodePtr OpPC) {
@@ -2233,6 +2262,18 @@ inline bool CheckNonNullArg(InterpState &S, CodePtr OpPC) {
   return false;
 }
 
+/// OldPtr -> Integer -> NewPtr.
+template <PrimType TIn, PrimType TOut>
+inline bool DecayPtr(InterpState &S, CodePtr OpPC) {
+  static_assert(isPtrType(TIn) && isPtrType(TOut));
+  using FromT = typename PrimConv<TIn>::T;
+  using ToT = typename PrimConv<TOut>::T;
+
+  const FromT &OldPtr = S.Stk.pop<FromT>();
+  S.Stk.push<ToT>(ToT(OldPtr.getIntegerRepresentation(), nullptr));
+  return true;
+}
+
 //===----------------------------------------------------------------------===//
 // Read opcode arguments
 //===----------------------------------------------------------------------===//
diff --git a/clang/lib/AST/Interp/InterpBlock.cpp b/clang/lib/AST/Interp/InterpBlock.cpp
index a62128d9cfaedd..9b33d1b778fb2c 100644
--- a/clang/lib/AST/Interp/InterpBlock.cpp
+++ b/clang/lib/AST/Interp/InterpBlock.cpp
@@ -73,7 +73,7 @@ void Block::replacePointer(Pointer *Old, Pointer *New) {
   removePointer(Old);
   addPointer(New);
 
-  Old->Pointee = nullptr;
+  Old->PointeeStorage.BS.Pointee = nullptr;
 
 #ifndef NDEBUG
   assert(!hasPointer(Old));
@@ -104,7 +104,7 @@ DeadBlock::DeadBlock(DeadBlock *&Root, Block *Blk)
   // Transfer pointers.
   B.Pointers = Blk->Pointers;
   for (Pointer *P = Blk->Pointers; P; P = P->Next)
-    P->Pointee = &B;
+    P->PointeeStorage.BS.Pointee = &B;
 }
 
 void DeadBlock::free() {
diff --git a/clang/lib/AST/Interp/Opcodes.td b/clang/lib/AST/Interp/Opcodes.td
index ffc54646f0279e..68bd2218a2d6bb 100644
--- a/clang/lib/AST/Interp/Opcodes.td
+++ b/clang/lib/AST/Interp/Opcodes.td
@@ -59,6 +59,7 @@ def ArgCastKind : ArgType { let Name = "CastKind"; }
 def ArgCallExpr : ArgType { let Name = "const CallExpr *"; }
 def ArgOffsetOfExpr : ArgType { let Name = "const OffsetOfExpr *"; }
 def ArgDeclRef : ArgType { let Name = "const DeclRefExpr *"; }
+def ArgDesc : ArgType { let Name = "const Descriptor *"; }
 def ArgCCI : ArgType { let Name = "const ComparisonCategoryInfo *"; }
 
 //===----------------------------------------------------------------------===//
@@ -272,6 +273,7 @@ def ZeroIntAPS : Opcode {
 // [] -> [Pointer]
 def Null : Opcode {
   let Types = [PtrTypeClass];
+  let Args = [ArgDesc];
   let HasGroup = 1;
 }
 
@@ -522,6 +524,11 @@ def GetFnPtr : Opcode {
   let Args = [ArgFunction];
 }
 
+def GetIntPtr : Opcode {
+  let Types = [IntegerTypeClass];
+  let Args = [ArgDesc];
+  let HasGroup = 1;
+}
 
 //===----------------------------------------------------------------------===//
 // Binary operators.
@@ -654,6 +661,11 @@ def CastPointerIntegral : Opcode {
   let HasGroup = 1;
 }
 
+def DecayPtr : Opcode {
+  let Types = [PtrTypeClass, PtrTypeClass];
+  let HasGroup = 1;
+}
+
 //===----------------------------------------------------------------------===//
 // Comparison opcodes.
 //===----------------------------------------------------------------------===//
diff --git a/clang/lib/AST/Interp/Pointer.cpp b/clang/lib/AST/Interp/Pointer.cpp
index 3f85635f43674d..8659d8424e8502 100644
--- a/clang/lib/AST/Interp/Pointer.cpp
+++ b/clang/lib/AST/Interp/Pointer.cpp
@@ -26,60 +26,98 @@ Pointer::Pointer(Block *Pointee)
 Pointer::Pointer(Block *Pointee, unsigned BaseAndOffset)
     : Pointer(Pointee, BaseAndOffset, BaseAndOffset) {}
 
-Pointer::Pointer(const Pointer &P) : Pointer(P.Pointee, P.Base, P.Offset) {}
+Pointer::Pointer(const Pointer &P)
+    : Offset(P.Offset), PointeeStorage(P.PointeeStorage),
+      StorageKind(P.StorageKind) {
 
-Pointer::Pointer(Pointer &&P)
-    : Pointee(P.Pointee), Base(P.Base), Offset(P.Offset) {
-  if (Pointee)
-    Pointee->replacePointer(&P, this);
+  if (isBlockPointer() && PointeeStorage.BS.Pointee)
+    PointeeStorage.BS.Pointee->addPointer(this);
 }
 
 Pointer::Pointer(Block *Pointee, unsigned Base, unsigned Offset)
-    : Pointee(Pointee), Base(Base), Offset(Offset) {
+    : Offset(Offset), StorageKind(Storage::Block) {
   assert((Base == RootPtrMark || Base % alignof(void *) == 0) && "wrong base");
+
+  PointeeStorage.BS = {Pointee, Base};
+
   if (Pointee)
     Pointee->addPointer(this);
 }
 
+Pointer::Pointer(Pointer &&P)
+    : Offset(P.Offset), PointeeStorage(P.PointeeStorage),
+      StorageKind(P.StorageKind) {
+
+  if (StorageKind == Storage::Block && PointeeStorage.BS.Pointee) {
+    PointeeStorage.BS.Pointee->replacePointer(&P, this);
+  }
+}
+
 Pointer::~Pointer() {
-  if (Pointee) {
-    Pointee->removePointer(this);
-    Pointee->cleanup();
+  if (isIntegralPointer())
+    return;
+
+  if (PointeeStorage.BS.Pointee) {
+    PointeeStorage.BS.Pointee->removePointer(this);
+    PointeeStorage.BS.Pointee->cleanup();
   }
 }
 
 void Pointer::operator=(const Pointer &P) {
-  Block *Old = Pointee;
 
-  if (Pointee)
-    Pointee->removePointer(this);
+  if (this->isIntegralPointer() && P.isBlockPointer()) {
+  } else {
+    assert(P.StorageKind == StorageKind);
+  }
 
-  Offset = P.Offset;
-  Base = P.Base;
+  bool WasBlockPointer = isBlockPointer();
+  StorageKind = P.StorageKind;
+  if (StorageKind == Storage::Block) {
+    Block *Old = PointeeStorage.BS.Pointee;
+    if (WasBlockPointer && PointeeStorage.BS.Pointee)
+      PointeeStorage.BS.Pointee->removePointer(this);
 
-  Pointee = P.Pointee;
-  if (Pointee)
-    Pointee->addPointer(this);
+    Offset = P.Offset;
+    PointeeStorage.BS = P.PointeeStorage.BS;
+
+    if (PointeeStorage.BS.Pointee)
+      PointeeStorage.BS.Pointee->addPointer(this);
 
-  if (Old)
-    Old->cleanup();
+    if (WasBlockPointer && Old)
+      Old->cleanup();
+
+  } else if (StorageKind == Storage::Int) {
+    PointeeStorage.Int = P.PointeeStorage.Int;
+  } else
+    assert(false);
 }
 
 void Pointer::operator=(Pointer &&P) {
-  Block *Old = Pointee;
+  if (this->isIntegralPointer() && P.isBlockPointer()) {
+  } else {
+    assert(P.StorageKind == StorageKind);
+  }
 
-  if (Pointee)
-    Pointee->removePointer(this);
+  bool WasBlockPointer = isBlockPointer();
+  StorageKind = P.StorageKind;
+  if (StorageKind == Storage::Block) {
+    Block *Old = PointeeStorage.BS.Pointee;
+    if (WasBlockPointer && PointeeStorage.BS.Pointee)
+      PointeeStorage.BS.Pointee->removePointer(this);
 
-  Offset = P.Offset;
-  Base = P.Base;
+    Offset = P.Offset;
+    PointeeStorage.BS = P.PointeeStorage.BS;
 
-  Pointee = P.Pointee;
-  if (Pointee)
-    Pointee->replacePointer(&P, this);
+    if (PointeeStorage.BS.Pointee)
+      PointeeStorage.BS.Pointee->addPointer(this);
+
+    if (WasBlockPointer && Old)
+      Old->cleanup();
 
-  if (Old)
-    Old->cleanup();
+  } else if (StorageKind == Storage::Int) {
+    PointeeStorage.Int = P.PointeeStorage.Int;
+  } else
+    assert(false);
 }
 
 APValue Pointer::toAPValue() const {
@@ -88,6 +126,12 @@ APValue Pointer::toAPValue() const {
   if (isZero())
     return APValue(static_cast<const Expr *>(nullptr), CharUnits::Zero(), Path,
                    /*IsOnePastEnd=*/false, /*IsNullPtr=*/true);
+  if (isIntegralPointer()) {
+    return APValue(static_cast<const Expr *>(nullptr),
+                   CharUnits::fromQuantity(asIntPointer().Value + this->Offset),
+                   Path,
+                   /*IsOnePastEnd=*/false, /*IsNullPtr=*/false);
+  }
 
   // Build the lvalue base from the block.
   const Descriptor *Desc = getDeclDesc();
@@ -123,8 +167,9 @@ APValue Pointer::toAPValue() const {
         Path.push_back(APValue::LValuePathEntry({BaseOrMember, IsVirtual}));
         Ptr = Ptr.getBase();
         continue;
+
+        llvm_unreachable("Invalid field type");
       }
-      llvm_unreachable("Invalid field type");
     }
   }
 
@@ -137,19 +182,53 @@ APValue Pointer::toAPValue() const {
   return APValue(Base, Offset, Path, IsOnePastEnd, /*IsNullPtr=*/false);
 }
 
+void Pointer::print(llvm::raw_ostream &OS) const {
+  OS << PointeeStorage.BS.Pointee << " (";
+  if (isBlockPointer()) {
+    OS << "Block) {";
+
+    if (PointeeStorage.BS.Base == RootPtrMark)
+      OS << "rootptr, ";
+    else
+      OS << PointeeStorage.BS.Base << ", ";
+
+    if (Offset == PastEndMark)
+      OS << "pastend, ";
+    else
+      OS << Offset << ", ";
+
+    if (isBlockPointer() && PointeeStorage.BS.Pointee)
+      OS << PointeeStorage.BS.Pointee->getSize();
+    else
+      OS << "nullptr";
+  } else {
+    OS << "Int) {";
+    OS << PointeeStorage.Int.Value << ", " << PointeeStorage.Int.Desc;
+  }
+  OS << "}";
+}
+
 std::string Pointer::toDiagnosticString(const ASTContext &Ctx) const {
-  if (!Pointee)
+  if (isZero())
     return "nullptr";
 
+  if (isIntegralPointer())
+    return std::string("&(") + std::to_string(asIntPointer().Value + Offset) +
+           ")";
+
   return toAPValue().getAsString(Ctx, getType());
 }
 
 bool Pointer::isInitialized() const {
-  assert(Pointee && "Cannot check if null pointer was initialized");
+  if (isIntegralPointer())
+    return true;
+
+  assert(PointeeStorage.BS.Pointee &&
+         "Cannot check if null pointer was initialized");
   const Descriptor *Desc = getFieldDesc();
   assert(Desc);
   if (Desc->isPrimitiveArray()) {
-    if (isStatic() && Base == 0)
+    if (isStatic() && PointeeStorage.BS.Base == 0)
       return true;
 
     InitMapPtr &IM = getInitMap();
@@ -164,17 +243,20 @@ bool Pointer::isInitialized() const {
   }
 
   // Field has its bit in an inline descriptor.
-  return Base == 0 || getInlineDesc()->IsInitialized;
+  return PointeeStorage.BS.Base == 0 || getInlineDesc()->IsInitialized;
 }
 
 void Pointer::initialize() const {
-  assert(Pointee && "Cannot initialize null pointer");
+  if (isIntegralPointer())
+    return;
+
+  assert(PointeeStorage.BS.Pointee && "Cannot initialize null pointer");
   const Descriptor *Desc = getFieldDesc();
 
   assert(Desc);
   if (Desc->isPrimitiveArray()) {
     // Primitive global arrays don't have an initmap.
-    if (isStatic() && Base == 0)
+    if (isStatic() && PointeeStorage.BS.Base == 0)
       return;
 
     InitMapPtr &IM = getInitMap();
@@ -196,13 +278,15 @@ void Pointer::initialize() const {
   }
 
   // Field has its bit in an inline descriptor.
-  assert(Base != 0 && "Only composite fields can be initialised");
+  assert(PointeeStorage.BS.Base != 0 &&
+         "Only composite fields can be initialised");
   getInlineDesc()->IsInitialized = true;
 }
 
 void Pointer::activate() const {
   // Field has its bit in an inline descriptor.
-  assert(Base != 0 && "Only composite fields can be initialised");
+  assert(PointeeStorage.BS.Base != 0 &&
+         "Only composite fields can be initialised");
   getInlineDesc()->IsActive = true;
 }
 
@@ -211,11 +295,23 @@ void Pointer::deactivate() const {
 }
 
 bool Pointer::hasSameBase(const Pointer &A, const Pointer &B) {
-  return A.Pointee == B.Pointee;
+  // Two null pointers always have the same base.
+  if (A.isZero() && B.isZero())
+    return true;
+
+  if (A.isIntegralPointer() && B.isIntegralPointer())
+    return true;
+
+  if (A.isIntegralPointer() || B.isIntegralPointer())
+    return A.getSource() == B.getSource();
+
+  return A.asBlockPointer().Pointee == B.asBlockPointer().Pointee;
 }
 
 bool Pointer::hasSameArray(const Pointer &A, const Pointer &B) {
-  return hasSameBase(A, B) && A.Base == B.Base && A.getFieldDesc()->IsArray;
+  return hasSameBase(A, B) &&
+         A.PointeeStorage.BS.Base == B.PointeeStorage.BS.Base &&
+         A.getFieldDesc()->IsArray;
 }
 
 std::optional<APValue> Pointer::toRValue(const Context &Ctx) const {
diff --git a/clang/lib/AST/Interp/Pointer.h b/clang/lib/AST/Interp/Pointer.h
index 34ecdb967960d5..0c861c11a2b25b 100644
--- a/clang/lib/AST/Interp/Pointer.h
+++ b/clang/lib/AST/Interp/Pointer.h
@@ -28,11 +28,26 @@ class Block;
 class DeadBlock;
 class Pointer;
 class Context;
+template <unsigned A, bool B> class Integral;
 enum PrimType : unsigned;
 
 class Pointer;
 inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Pointer &P);
 
+struct BlockPointer {
+  /// The block the pointer is pointing to.
+  Block *Pointee;
+  /// Start of the current subfield.
+  unsigned Base;
+};
+
+struct IntPointer {
+  const Descriptor *Desc;
+  int Value;
+};
+
+enum class Storage { Block, Int };
+
 /// A pointer to a memory block, live or dead.
 ///
 /// This object can be allocated into interpreter stack frames. If pointing to
@@ -68,11 +83,20 @@ class Pointer {
   static constexpr unsigned RootPtrMark = ~0u;
 
 public:
-  Pointer() {}
+  Pointer() {
+    StorageKind = Storage::Int;
+    PointeeStorage.Int.Value = 0;
+    PointeeStorage.Int.Desc = nullptr;
+  }
   Pointer(Block *B);
   Pointer(Block *B, unsigned BaseAndOffset);
   Pointer(const Pointer &P);
   Pointer(Pointer &&P);
+  Pointer(uint64_t Address, const Descriptor *Desc, unsigned Offset = 0)
+      : Offset(Offset), StorageKind(Storage::Int) {
+    PointeeStorage.Int.Value = Address;
+    PointeeStorage.Int.Desc = Desc;
+  }
   ~Pointer();
 
   void operator=(const Pointer &P);
@@ -80,13 +104,20 @@ class Pointer {
 
   /// Equality operators are just for tests.
   bool operator==(const Pointer &P) const {
-    return Pointee == P.Pointee && Base == P.Base && Offset == P.Offset;
-  }
+    if (P.StorageKind != StorageKind)
+      return false;
+    if (isIntegralPointer())
+      return P.asIntPointer().Value == asIntPointer().Value &&
+             Offset == P.Offset;
 
-  bool operator!=(const Pointer &P) const {
-    return Pointee != P.Pointee || Base != P.Base || Offset != P.Offset;
+    assert(isBlockPointer());
+    return P.asBlockPointer().Pointee == asBlockPointer().Pointee &&
+           P.asBlockPointer().Base == asBlockPointer().Base &&
+           Offset == P.Offset;
   }
 
+  bool operator!=(const Pointer &P) const { return !(P == *this); }
+
   /// Converts the pointer to an APValue.
   APValue toAPValue() const;
 
@@ -94,7 +125,9 @@ class Pointer {
   std::string toDiagnosticString(const ASTContext &Ctx) const;
 
   unsigned getIntegerRepresentation() const {
-    return reinterpret_cast<uintptr_t>(Pointee) + Offset;
+    if (isIntegralPointer())
+      return asIntPointer().Value + (Offset * elemSize());
+    return reinterpret_cast<uintptr_t>(asBlockPointer().Pointee) + Offset;
   }
 
   /// Converts the pointer to an APValue that is an rvalue.
@@ -102,20 +135,27 @@ class Pointer {
 
   /// Offsets a pointer inside an array.
   [[nodiscard]] Pointer atIndex(unsigned Idx) const {
-    if (Base == RootPtrMark)
-      return Pointer(Pointee, RootPtrMark, getDeclDesc()->getSize());
+    if (isIntegralPointer())
+      return Pointer(asIntPointer().Value, asIntPointer().Desc, Idx);
+
+    if (asBlockPointer().Base == RootPtrMark)
+      return Pointer(asBlockPointer().Pointee, RootPtrMark,
+                     getDeclDesc()->getSize());
     unsigned Off = Idx * elemSize();
     if (getFieldDesc()->ElemDesc)
       Off += sizeof(InlineDescriptor);
     else
       Off += sizeof(InitMapPtr);
-    return Pointer(Pointee, Base, Base + Off);
+    return Pointer(asBlockPointer().Pointee, asBlockPointer().Base,
+                   asBlockPointer().Base + Off);
   }
 
   /// Creates a pointer to a field.
   [[nodiscard]] Pointer atField(unsigned Off) const {
     unsigned Field = Offset + Off;
-    return Pointer(Pointee, Field, Field);
+    if (isIntegralPointer())
+      return Pointer(asIntPointer().Value + Field, asIntPointer().Desc);
+    return Pointer(asBlockPointer().Pointee, Field, Field);
   }
 
   /// Subtract the given offset from the current Base and Offset
@@ -123,44 +163,49 @@ class Pointer {
   [[nodiscard]]  Pointer atFieldSub(unsigned Off) const {
     assert(Offset >= Off);
     unsigned O = Offset - Off;
-    return Pointer(Pointee, O, O);
+    return Pointer(asBlockPointer().Pointee, O, O);
   }
 
   /// Restricts the scope of an array element pointer.
   [[nodiscard]] Pointer narrow() const {
+    if (!isBlockPointer())
+      return *this;
+    assert(isBlockPointer());
     // Null pointers cannot be narrowed.
     if (isZero() || isUnknownSizeArray())
       return *this;
 
     // Pointer to an array of base types - enter block.
-    if (Base == RootPtrMark)
-      return Pointer(Pointee, sizeof(InlineDescriptor),
+    if (asBlockPointer().Base == RootPtrMark)
+      return Pointer(asBlockPointer().Pointee, sizeof(InlineDescriptor),
                      Offset == 0 ? Offset : PastEndMark);
 
     // Pointer is one past end - magic offset marks that.
     if (isOnePastEnd())
-      return Pointer(Pointee, Base, PastEndMark);
+      return Pointer(asBlockPointer().Pointee, asBlockPointer().Base,
+                     PastEndMark);
 
     // Primitive arrays are a bit special since they do not have inline
     // descriptors. If Offset != Base, then the pointer already points to
     // an element and there is nothing to do. Otherwise, the pointer is
     // adjusted to the first element of the array.
     if (inPrimitiveArray()) {
-      if (Offset != Base)
+      if (Offset != asBlockPointer().Base)
         return *this;
-      return Pointer(Pointee, Base, Offset + sizeof(InitMapPtr));
+      return Pointer(asBlockPointer().Pointee, asBlockPointer().Base,
+                     Offset + sizeof(InitMapPtr));
     }
 
     // Pointer is to a field or array element - enter it.
-    if (Offset != Base)
-      return Pointer(Pointee, Offset, Offset);
+    if (Offset != asBlockPointer().Base)
+      return Pointer(asBlockPointer().Pointee, Offset, Offset);
 
     // Enter the first element of an array.
     if (!getFieldDesc()->isArray())
       return *this;
 
-    const unsigned NewBase = Base + sizeof(InlineDescriptor);
-    return Pointer(Pointee, NewBase, NewBase);
+    const unsigned NewBase = asBlockPointer().Base + sizeof(InlineDescriptor);
+    return Pointer(asBlockPointer().Pointee, NewBase, NewBase);
   }
 
   /// Expands a pointer to the containing array, undoing narrowing.
@@ -172,72 +217,107 @@ class Pointer {
         Adjust = sizeof(InitMapPtr);
       else
         Adjust = sizeof(InlineDescriptor);
-      return Pointer(Pointee, Base, Base + getSize() + Adjust);
+      return Pointer(asBlockPointer().Pointee, asBlockPointer().Base,
+                     asBlockPointer().Base + getSize() + Adjust);
     }
 
     // Do not step out of array elements.
-    if (Base != Offset)
+    if (asBlockPointer().Base != Offset)
       return *this;
 
     // If at base, point to an array of base types.
-    if (Base == 0 || Base == sizeof(InlineDescriptor))
-      return Pointer(Pointee, RootPtrMark, 0);
+    if (asBlockPointer().Base == 0 ||
+        asBlockPointer().Base == sizeof(InlineDescriptor))
+      return Pointer(asBlockPointer().Pointee, RootPtrMark, 0);
 
     // Step into the containing array, if inside one.
-    unsigned Next = Base - getInlineDesc()->Offset;
+    unsigned Next = asBlockPointer().Base - getInlineDesc()->Offset;
     const Descriptor *Desc =
         Next == 0 ? getDeclDesc() : getDescriptor(Next)->Desc;
     if (!Desc->IsArray)
       return *this;
-    return Pointer(Pointee, Next, Offset);
+    return Pointer(asBlockPointer().Pointee, Next, Offset);
   }
 
   /// Checks if the pointer is null.
-  bool isZero() const { return Pointee == nullptr; }
+  bool isZero() const {
+    if (Offset != 0)
+      return false;
+
+    if (isBlockPointer())
+      return asBlockPointer().Pointee == nullptr;
+    else if (isIntegralPointer())
+      return asIntPointer().Value == 0;
+    llvm_unreachable("zomg");
+  }
   /// Checks if the pointer is live.
-  bool isLive() const { return Pointee && !Pointee->IsDead; }
+  bool isLive() const {
+    if (isIntegralPointer())
+      return true;
+    return asBlockPointer().Pointee && !asBlockPointer().Pointee->IsDead;
+  }
   /// Checks if the item is a field in an object.
   bool isField() const {
+    if (isIntegralPointer())
+      return false;
+
+    unsigned Base = asBlockPointer().Base;
     return Base != 0 && Base != sizeof(InlineDescriptor) &&
            Base != RootPtrMark && getFieldDesc()->asDecl();
   }
 
   /// Accessor for information about the declaration site.
   const Descriptor *getDeclDesc() const {
-    assert(Pointee);
-    return Pointee->Desc;
+    assert(isBlockPointer());
+    assert(asBlockPointer().Pointee);
+    return asBlockPointer().Pointee->Desc;
   }
   SourceLocation getDeclLoc() const { return getDeclDesc()->getLocation(); }
 
+  /// Returns the expression or declaration the pointer has been created for.
+  DeclTy getSource() const {
+    if (isBlockPointer())
+      return getDeclDesc()->getSource();
+
+    assert(isIntegralPointer());
+    return asIntPointer().Desc ? asIntPointer().Desc->getSource() : DeclTy();
+  }
+
   /// Returns a pointer to the object of which this pointer is a field.
   [[nodiscard]] Pointer getBase() const {
-    if (Base == RootPtrMark) {
+    if (asBlockPointer().Base == RootPtrMark) {
       assert(Offset == PastEndMark && "cannot get base of a block");
-      return Pointer(Pointee, Base, 0);
+      return Pointer(asBlockPointer().Pointee, asBlockPointer().Base, 0);
     }
-    unsigned NewBase = Base - getInlineDesc()->Offset;
-    return Pointer(Pointee, NewBase, NewBase);
+    unsigned NewBase = asBlockPointer().Base - getInlineDesc()->Offset;
+    return Pointer(asBlockPointer().Pointee, NewBase, NewBase);
   }
   /// Returns the parent array.
   [[nodiscard]] Pointer getArray() const {
-    if (Base == RootPtrMark) {
+    if (asBlockPointer().Base == RootPtrMark) {
       assert(Offset != 0 && Offset != PastEndMark && "not an array element");
-      return Pointer(Pointee, Base, 0);
+      return Pointer(asBlockPointer().Pointee, asBlockPointer().Base, 0);
     }
-    assert(Offset != Base && "not an array element");
-    return Pointer(Pointee, Base, Base);
+    assert(Offset != asBlockPointer().Base && "not an array element");
+    return Pointer(asBlockPointer().Pointee, asBlockPointer().Base,
+                   asBlockPointer().Base);
   }
 
   /// Accessors for information about the innermost field.
   const Descriptor *getFieldDesc() const {
-    if (Base == 0 || Base == sizeof(InlineDescriptor) || Base == RootPtrMark)
+    if (isIntegralPointer())
+      return asIntPointer().Desc;
+    if (isBlockPointer() &&
+        (asBlockPointer().Base == 0 ||
+         asBlockPointer().Base == sizeof(InlineDescriptor) ||
+         asBlockPointer().Base == RootPtrMark))
       return getDeclDesc();
     return getInlineDesc()->Desc;
   }
 
   /// Returns the type of the innermost field.
   QualType getType() const {
-    if (inPrimitiveArray() && Offset != Base) {
+    if (inPrimitiveArray() && Offset != asBlockPointer().Base) {
       // Unfortunately, complex types are not array types in clang, but they are
       // for us.
       if (const auto *AT = getFieldDesc()->getType()->getAsArrayTypeUnsafe())
@@ -248,53 +328,98 @@ class Pointer {
     return getFieldDesc()->getType();
   }
 
-  [[nodiscard]] Pointer getDeclPtr() const { return Pointer(Pointee); }
+  [[nodiscard]] Pointer getDeclPtr() const {
+    return Pointer(asBlockPointer().Pointee);
+  }
 
   /// Returns the element size of the innermost field.
   size_t elemSize() const {
-    if (Base == RootPtrMark)
+    if (isIntegralPointer()) {
+      if (!asIntPointer().Desc)
+        return 1;
+      return asIntPointer().Desc->getElemSize();
+    }
+
+    if (asBlockPointer().Base == RootPtrMark)
       return getDeclDesc()->getSize();
     return getFieldDesc()->getElemSize();
   }
   /// Returns the total size of the innermost field.
-  size_t getSize() const { return getFieldDesc()->getSize(); }
+  size_t getSize() const {
+    assert(isBlockPointer());
+    return getFieldDesc()->getSize();
+  }
 
   /// Returns the offset into an array.
   unsigned getOffset() const {
     assert(Offset != PastEndMark && "invalid offset");
-    if (Base == RootPtrMark)
+    if (asBlockPointer().Base == RootPtrMark)
       return Offset;
 
     unsigned Adjust = 0;
-    if (Offset != Base) {
+    if (Offset != asBlockPointer().Base) {
       if (getFieldDesc()->ElemDesc)
         Adjust = sizeof(InlineDescriptor);
       else
         Adjust = sizeof(InitMapPtr);
     }
-    return Offset - Base - Adjust;
+    return Offset - asBlockPointer().Base - Adjust;
   }
 
   /// Whether this array refers to an array, but not
   /// to the first element.
-  bool isArrayRoot() const { return inArray() && Offset == Base; }
+  bool isArrayRoot() const {
+    return inArray() && Offset == asBlockPointer().Base;
+  }
 
   /// Checks if the innermost field is an array.
-  bool inArray() const { return getFieldDesc()->IsArray; }
+  bool inArray() const {
+    if (isBlockPointer())
+      return getFieldDesc()->IsArray;
+    return false;
+  }
   /// Checks if the structure is a primitive array.
-  bool inPrimitiveArray() const { return getFieldDesc()->isPrimitiveArray(); }
+  bool inPrimitiveArray() const {
+    if (isBlockPointer())
+      return getFieldDesc()->isPrimitiveArray();
+    return false;
+  }
   /// Checks if the structure is an array of unknown size.
   bool isUnknownSizeArray() const {
+    if (!isBlockPointer())
+      return false;
     return getFieldDesc()->isUnknownSizeArray();
   }
   /// Checks if the pointer points to an array.
-  bool isArrayElement() const { return inArray() && Base != Offset; }
+  bool isArrayElement() const {
+    if (isBlockPointer())
+      return inArray() && asBlockPointer().Base != Offset;
+    return false;
+  }
   /// Pointer points directly to a block.
   bool isRoot() const {
-    return (Base == 0 || Base == RootPtrMark) && Offset == 0;
+    return (asBlockPointer().Base == 0 ||
+            asBlockPointer().Base == RootPtrMark) &&
+           Offset == 0;
   }
   /// If this pointer has an InlineDescriptor we can use to initialize.
-  bool canBeInitialized() const { return Pointee && Base > 0; }
+  bool canBeInitialized() const {
+    if (!isBlockPointer())
+      return false;
+
+    return asBlockPointer().Pointee && asBlockPointer().Base > 0;
+  }
+
+  [[nodiscard]] const BlockPointer &asBlockPointer() const {
+    assert(isBlockPointer());
+    return PointeeStorage.BS;
+  }
+  [[nodiscard]] const IntPointer &asIntPointer() const {
+    assert(isIntegralPointer());
+    return PointeeStorage.Int;
+  }
+  bool isBlockPointer() const { return StorageKind == Storage::Block; }
+  bool isIntegralPointer() const { return StorageKind == Storage::Int; }
 
   /// Returns the record descriptor of a class.
   const Record *getRecord() const { return getFieldDesc()->ElemRecord; }
@@ -310,71 +435,109 @@ class Pointer {
   bool isUnion() const;
 
   /// Checks if the storage is extern.
-  bool isExtern() const { return Pointee && Pointee->isExtern(); }
+  bool isExtern() const {
+    if (isBlockPointer())
+      return asBlockPointer().Pointee && asBlockPointer().Pointee->isExtern();
+    return false;
+  }
   /// Checks if the storage is static.
   bool isStatic() const {
-    assert(Pointee);
-    return Pointee->isStatic();
+    if (isIntegralPointer())
+      return true;
+    assert(asBlockPointer().Pointee);
+    return asBlockPointer().Pointee->isStatic();
   }
   /// Checks if the storage is temporary.
   bool isTemporary() const {
-    assert(Pointee);
-    return Pointee->isTemporary();
+    if (isBlockPointer()) {
+      assert(asBlockPointer().Pointee);
+      return asBlockPointer().Pointee->isTemporary();
+    }
+    return false;
   }
   /// Checks if the storage is a static temporary.
   bool isStaticTemporary() const { return isStatic() && isTemporary(); }
 
   /// Checks if the field is mutable.
   bool isMutable() const {
-    return Base != 0 && Base != sizeof(InlineDescriptor) &&
+    if (!isBlockPointer())
+      return false;
+    return asBlockPointer().Base != 0 &&
+           asBlockPointer().Base != sizeof(InlineDescriptor) &&
            getInlineDesc()->IsFieldMutable;
   }
   /// Checks if an object was initialized.
   bool isInitialized() const;
   /// Checks if the object is active.
   bool isActive() const {
-    return Base == 0 || Base == sizeof(InlineDescriptor) ||
+    if (!isBlockPointer())
+      return true;
+    return asBlockPointer().Base == 0 ||
+           asBlockPointer().Base == sizeof(InlineDescriptor) ||
            getInlineDesc()->IsActive;
   }
   /// Checks if a structure is a base class.
   bool isBaseClass() const { return isField() && getInlineDesc()->IsBase; }
   /// Checks if the pointer points to a dummy value.
   bool isDummy() const {
-    if (!Pointee)
+    if (!isBlockPointer())
+      return false;
+
+    if (!asBlockPointer().Pointee)
       return false;
+
     return getDeclDesc()->isDummy();
   }
 
   /// Checks if an object or a subfield is mutable.
   bool isConst() const {
-    return (Base == 0 || Base == sizeof(InlineDescriptor))
+    if (isIntegralPointer())
+      return true;
+    return (asBlockPointer().Base == 0 ||
+            asBlockPointer().Base == sizeof(InlineDescriptor))
                ? getDeclDesc()->IsConst
                : getInlineDesc()->IsConst;
   }
 
   /// Returns the declaration ID.
   std::optional<unsigned> getDeclID() const {
-    assert(Pointee);
-    return Pointee->getDeclID();
+    if (isBlockPointer()) {
+      assert(asBlockPointer().Pointee);
+      return asBlockPointer().Pointee->getDeclID();
+    }
+    return std::nullopt;
   }
 
   /// Returns the byte offset from the start.
   unsigned getByteOffset() const {
+    if (isIntegralPointer())
+      return asIntPointer().Value + Offset;
     return Offset;
   }
 
   /// Returns the number of elements.
-  unsigned getNumElems() const { return getSize() / elemSize(); }
+  unsigned getNumElems() const {
+    if (isIntegralPointer())
+      return ~unsigned(0);
+    return getSize() / elemSize();
+  }
 
-  const Block *block() const { return Pointee; }
+  const Block *block() const { return asBlockPointer().Pointee; }
 
   /// Returns the index into an array.
   int64_t getIndex() const {
+    if (!isBlockPointer())
+      return 0;
+
+    if (isZero())
+      return 0;
+
     if (isElementPastEnd())
       return 1;
 
     // narrow()ed element in a composite array.
-    if (Base > sizeof(InlineDescriptor) && Base == Offset)
+    if (asBlockPointer().Base > sizeof(InlineDescriptor) &&
+        asBlockPointer().Base == Offset)
       return 0;
 
     if (auto ElemSize = elemSize())
@@ -384,7 +547,10 @@ class Pointer {
 
   /// Checks if the index is one past end.
   bool isOnePastEnd() const {
-    if (!Pointee)
+    if (isIntegralPointer())
+      return false;
+
+    if (!asBlockPointer().Pointee)
       return false;
     return isElementPastEnd() || getSize() == getOffset();
   }
@@ -395,20 +561,25 @@ class Pointer {
   /// Dereferences the pointer, if it's live.
   template <typename T> T &deref() const {
     assert(isLive() && "Invalid pointer");
-    assert(Pointee);
+    assert(isBlockPointer());
+    assert(asBlockPointer().Pointee);
+    assert(Offset + sizeof(T) <=
+           asBlockPointer().Pointee->getDescriptor()->getAllocSize());
+
     if (isArrayRoot())
-      return *reinterpret_cast<T *>(Pointee->rawData() + Base +
-                                    sizeof(InitMapPtr));
+      return *reinterpret_cast<T *>(asBlockPointer().Pointee->rawData() +
+                                    asBlockPointer().Base + sizeof(InitMapPtr));
 
-    assert(Offset + sizeof(T) <= Pointee->getDescriptor()->getAllocSize());
-    return *reinterpret_cast<T *>(Pointee->rawData() + Offset);
+    return *reinterpret_cast<T *>(asBlockPointer().Pointee->rawData() + Offset);
   }
 
   /// Dereferences a primitive element.
   template <typename T> T &elem(unsigned I) const {
     assert(I < getNumElems());
-    assert(Pointee);
-    return reinterpret_cast<T *>(Pointee->data() + sizeof(InitMapPtr))[I];
+    assert(isBlockPointer());
+    assert(asBlockPointer().Pointee);
+    return reinterpret_cast<T *>(asBlockPointer().Pointee->data() +
+                                 sizeof(InitMapPtr))[I];
   }
 
   /// Initializes a field.
@@ -437,24 +608,7 @@ class Pointer {
   static bool hasSameArray(const Pointer &A, const Pointer &B);
 
   /// Prints the pointer.
-  void print(llvm::raw_ostream &OS) const {
-    OS << Pointee << " {";
-    if (Base == RootPtrMark)
-      OS << "rootptr, ";
-    else
-      OS << Base << ", ";
-
-    if (Offset == PastEndMark)
-      OS << "pastend, ";
-    else
-      OS << Offset << ", ";
-
-    if (Pointee)
-      OS << Pointee->getSize();
-    else
-      OS << "nullptr";
-    OS << "}";
-  }
+  void print(llvm::raw_ostream &OS) const;
 
 private:
   friend class Block;
@@ -464,33 +618,41 @@ class Pointer {
   Pointer(Block *Pointee, unsigned Base, unsigned Offset);
 
   /// Returns the embedded descriptor preceding a field.
-  InlineDescriptor *getInlineDesc() const { return getDescriptor(Base); }
+  InlineDescriptor *getInlineDesc() const {
+    return getDescriptor(asBlockPointer().Base);
+  }
 
   /// Returns a descriptor at a given offset.
   InlineDescriptor *getDescriptor(unsigned Offset) const {
     assert(Offset != 0 && "Not a nested pointer");
-    assert(Pointee);
-    return reinterpret_cast<InlineDescriptor *>(Pointee->rawData() + Offset) -
+    assert(isBlockPointer());
+    assert(!isZero());
+    return reinterpret_cast<InlineDescriptor *>(
+               asBlockPointer().Pointee->rawData() + Offset) -
            1;
   }
 
   /// Returns a reference to the InitMapPtr which stores the initialization map.
   InitMapPtr &getInitMap() const {
-    assert(Pointee);
-    return *reinterpret_cast<InitMapPtr *>(Pointee->rawData() + Base);
+    assert(isBlockPointer());
+    assert(!isZero());
+    return *reinterpret_cast<InitMapPtr *>(asBlockPointer().Pointee->rawData() +
+                                           asBlockPointer().Base);
   }
 
-  /// The block the pointer is pointing to.
-  Block *Pointee = nullptr;
-  /// Start of the current subfield.
-  unsigned Base = 0;
-  /// Offset into the block.
+  /// Offset into the storage.
   unsigned Offset = 0;
 
   /// Previous link in the pointer chain.
   Pointer *Prev = nullptr;
   /// Next link in the pointer chain.
   Pointer *Next = nullptr;
+
+  union {
+    BlockPointer BS;
+    IntPointer Int;
+  } PointeeStorage;
+  Storage StorageKind = Storage::Int;
 };
 
 inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, const Pointer &P) {
diff --git a/clang/lib/AST/Interp/PrimType.h b/clang/lib/AST/Interp/PrimType.h
index 24a24a71a07b57..dd2055d951660e 100644
--- a/clang/lib/AST/Interp/PrimType.h
+++ b/clang/lib/AST/Interp/PrimType.h
@@ -46,6 +46,10 @@ enum PrimType : unsigned {
   PT_FnPtr,
 };
 
+inline constexpr bool isPtrType(PrimType T) {
+  return T == PT_Ptr || T == PT_FnPtr;
+}
+
 enum class CastKind : uint8_t {
   Reinterpret,
   Atomic,
diff --git a/clang/test/AST/Interp/c.c b/clang/test/AST/Interp/c.c
index 8de6139efbea09..531e02aafbbbb6 100644
--- a/clang/test/AST/Interp/c.c
+++ b/clang/test/AST/Interp/c.c
@@ -201,3 +201,21 @@ void localCompoundLiteral(void) {
        // pedantic-ref-warning {{use of an empty initializer}}
   };
 }
+
+_Static_assert(((void*)0 + 1) != (void*)0, ""); // pedantic-expected-warning {{arithmetic on a pointer to void is a GNU extension}} \
+                                                // pedantic-expected-warning {{not an integer constant expression}} \
+                                                // pedantic-expected-note {{cannot perform pointer arithmetic on null pointer}} \
+                                                // pedantic-ref-warning {{arithmetic on a pointer to void is a GNU extension}} \
+                                                // pedantic-ref-warning {{not an integer constant expression}} \
+                                                // pedantic-ref-note {{cannot perform pointer arithmetic on null pointer}}
+
+typedef __INTPTR_TYPE__ intptr_t;
+int array[(intptr_t)(int*)1]; // ref-warning {{variable length array folded to constant array}} \
+                              // pedantic-ref-warning {{variable length array folded to constant array}} \
+                              // expected-warning {{variable length array folded to constant array}} \
+                              // pedantic-expected-warning {{variable length array folded to constant array}}
+
+int castViaInt[*(int*)(unsigned long)"test"]; // ref-error {{variable length array}} \
+                                              // pedantic-ref-error {{variable length array}} \
+                                              // expected-error {{variable length array}} \
+                                              // pedantic-expected-error {{variable length array}}
diff --git a/clang/test/AST/Interp/const-eval.c b/clang/test/AST/Interp/const-eval.c
new file mode 100644
index 00000000000000..d3096611a47fa7
--- /dev/null
+++ b/clang/test/AST/Interp/const-eval.c
@@ -0,0 +1,194 @@
+// RUN: %clang_cc1 -fsyntax-only -verify=both,ref      -triple x86_64-linux %s -Wno-tautological-pointer-compare -Wno-pointer-to-int-cast
+// RUN: %clang_cc1 -fsyntax-only -verify=both,expected -triple x86_64-linux %s -Wno-tautological-pointer-compare -Wno-pointer-to-int-cast -fexperimental-new-constant-interpreter -DNEW_INTERP
+// RUN: %clang_cc1 -fsyntax-only -verify=both,ref      -triple powerpc64-ibm-aix-xcoff %s -Wno-tautological-pointer-compare -Wno-pointer-to-int-cast
+// RUN: %clang_cc1 -fsyntax-only -verify=both,expected -triple powerpc64-ibm-aix-xcoff %s -Wno-tautological-pointer-compare -Wno-pointer-to-int-cast -fexperimental-new-constant-interpreter -DNEW_INTERP
+
+/// This is a version of test/Sema/const-eval.c with the
+/// tests commented out that the new constant expression interpreter does
+/// not support yet. They are all marked with the NEW_INTERP define:
+///
+///   - builtin_constant_p
+///   - unions
+
+
+#define EVAL_EXPR(testno, expr) enum { test##testno = (expr) }; struct check_positive##testno { int a[test##testno]; };
+int x;
+EVAL_EXPR(1, (_Bool)&x)
+EVAL_EXPR(2, (int)(1.0+(double)4))
+EVAL_EXPR(3, (int)(1.0+(float)4.0))
+EVAL_EXPR(4, (_Bool)(1 ? (void*)&x : 0))
+EVAL_EXPR(5, (_Bool)(int[]){0})
+struct y {int x,y;};
+EVAL_EXPR(6, (int)(1+(struct y*)0))
+_Static_assert((long)&((struct y*)0)->y > 0, "");
+EVAL_EXPR(7, (int)&((struct y*)0)->y)
+EVAL_EXPR(8, (_Bool)"asdf")
+EVAL_EXPR(9, !!&x)
+EVAL_EXPR(10, ((void)1, 12))
+void g0(void);
+EVAL_EXPR(11, (g0(), 12)) // both-error {{not an integer constant expression}}
+EVAL_EXPR(12, 1.0&&2.0)
+EVAL_EXPR(13, x || 3.0) // both-error {{not an integer constant expression}}
+
+unsigned int l_19 = 1;
+EVAL_EXPR(14, (1 ^ l_19) && 1); // both-error {{not an integer constant expression}}
+
+void f(void)
+{
+  int a;
+  EVAL_EXPR(15, (_Bool)&a);
+}
+
+_Complex float g16 = (1.0f + 1.0fi);
+
+// ?: in constant expressions.
+int g17[(3?:1) - 2];
+
+EVAL_EXPR(18, ((int)((void*)10 + 10)) == 20 ? 1 : -1);
+
+struct s {
+  int a[(int)-1.0f]; // both-error {{array size is negative}}
+};
+
+EVAL_EXPR(19, ((int)&*(char*)10 == 10 ? 1 : -1));
+
+#ifndef NEW_INTERP
+EVAL_EXPR(20, __builtin_constant_p(*((int*) 10)));
+#endif
+
+EVAL_EXPR(21, (__imag__ 2i) == 2 ? 1 : -1);
+
+EVAL_EXPR(22, (__real__ (2i+3)) == 3 ? 1 : -1);
+
+int g23[(int)(1.0 / 1.0)] = { 1 }; // both-warning {{folded to constant array}}
+int g24[(int)(1.0 / 1.0)] = { 1 , 2 }; // both-warning {{folded to constant array}} \
+                                       // both-warning {{excess elements in array initializer}}
+int g25[(int)(1.0 + 1.0)], g26 = sizeof(g25); // both-warning {{folded to constant array}}
+
+EVAL_EXPR(26, (_Complex double)0 ? -1 : 1)
+EVAL_EXPR(27, (_Complex int)0 ? -1 : 1)
+EVAL_EXPR(28, (_Complex double)1 ? 1 : -1)
+EVAL_EXPR(29, (_Complex int)1 ? 1 : -1)
+
+// PR4027
+struct a { int x, y; };
+static struct a V2 = (struct a)(struct a){ 1, 2};
+static const struct a V1 = (struct a){ 1, 2};
+
+EVAL_EXPR(30, (int)(_Complex float)((1<<30)-1) == (1<<30) ? 1 : -1)
+EVAL_EXPR(31, (int*)0 == (int*)0 ? 1 : -1)
+EVAL_EXPR(32, (int*)0 != (int*)0 ? -1 : 1)
+EVAL_EXPR(33, (void*)0 - (void*)0 == 0 ? 1 : -1)
+
+void foo(void) {}
+EVAL_EXPR(34, (foo == (void *)0) ? -1 : 1)
+
+// No PR. Mismatched bitwidths lead to a crash on second evaluation.
+const _Bool constbool = 0;
+EVAL_EXPR(35, constbool)
+EVAL_EXPR(36, constbool)
+
+EVAL_EXPR(37, ((void)1,2.0) == 2.0 ? 1 : -1)
+EVAL_EXPR(38, __builtin_expect(1,1) == 1 ? 1 : -1)
+
+// PR7884
+EVAL_EXPR(39, __real__(1.f) == 1 ? 1 : -1)
+EVAL_EXPR(40, __imag__(1.f) == 0 ? 1 : -1)
+
+// From gcc testsuite
+EVAL_EXPR(41, (int)(1+(_Complex unsigned)2))
+
+void rdar8875946(void) {
+  double _Complex  P;
+  float _Complex  P2 = 3.3f + P;
+}
+
+double d = (d = 0.0); // both-error {{not a compile-time constant}}
+double d2 = ++d; // both-error {{not a compile-time constant}}
+
+int n = 2;
+int intLvalue[*(int*)((long)&n ?: 1)] = { 1, 2 }; // both-error {{variable length array}}
+
+union u { int a; char b[4]; };
+char c = ((union u)(123456)).b[0]; // both-error {{not a compile-time constant}}
+
+#ifndef NEW_INTERP
+extern const int weak_int __attribute__((weak));
+const int weak_int = 42;
+int weak_int_test = weak_int; // both-error {{not a compile-time constant}}
+#endif
+
+int literalVsNull1 = "foo" == 0;
+int literalVsNull2 = 0 == "foo";
+
+// PR11385.
+int castViaInt[*(int*)(unsigned long)"test"]; // both-error {{variable length array}}
+
+// PR11391.
+#ifndef NEW_INTERP
+struct PR11391 { _Complex float f; } pr11391;
+EVAL_EXPR(42, __builtin_constant_p(pr11391.f = 1))
+#endif
+
+// PR12043
+float varfloat;
+const float constfloat = 0;
+EVAL_EXPR(43, varfloat && constfloat) // both-error {{not an integer constant expression}}
+EVAL_EXPR(45, ((char*)-1) + 1 == 0 ? 1 : -1)
+EVAL_EXPR(46, ((char*)-1) + 1 < (char*) -1 ? 1 : -1)
+EVAL_EXPR(47, &x < &x + 1 ? 1 : -1)
+EVAL_EXPR(48, &x != &x - 1 ? 1 : -1)
+EVAL_EXPR(49, &x < &x - 100 ? 1 : -1) // ref-error {{not an integer constant expression}}
+
+/// FIXME: Rejecting this is correct, BUT when converting the innermost pointer
+/// to an integer, we do not preserve the information where it came from. So when we later
+/// create a pointer from it, it also doesn't have that information, which means
+/// hasSameBase() for those two pointers will return false. And in those cases, we emit
+/// the diagnostic:
+///   comparison between '&Test50' and '&(631578)' has unspecified value
+extern struct Test50S Test50;
+EVAL_EXPR(50, &Test50 < (struct Test50S*)((unsigned long)&Test50 + 10)) // both-error {{not an integer constant expression}} \
+                                                                        // expected-note {{comparison between}}
+
+EVAL_EXPR(51, 0 != (float)1e99)
+
+// PR21945
+void PR21945(void) { int i = (({}), 0l); }
+
+#ifndef NEW_INTERP
+void PR24622(void);
+struct PR24622 {} pr24622;
+EVAL_EXPR(52, &pr24622 == (void *)&PR24622); // ref-error {{not an integer constant expression}}
+                                             // ref-note at -1 {{past the end}}
+#endif
+
+// We evaluate these by providing 2s' complement semantics in constant
+// expressions, like we do for integers.
+void *PR28739a = (__int128)(unsigned long)-1 + &PR28739a;                  // both-warning {{the pointer incremented by 18446744073709551615 refers past the last possible element for an array in 64-bit address space containing 64-bit (8-byte) elements (max possible 2305843009213693952 elements)}}
+
+void *PR28739b = &PR28739b + (__int128)(unsigned long)-1;                  // both-warning {{refers past the last possible element}}
+__int128 PR28739c = (&PR28739c + (__int128)(unsigned long)-1) - &PR28739c; // both-warning {{refers past the last possible element}}
+void *PR28739d = &(&PR28739d)[(__int128)(unsigned long)-1];                // both-warning {{refers past the last possible element}}
+
+struct PR35214_X {
+  int k;
+  int arr[];
+};
+int PR35214_x;
+int PR35214_y = ((struct PR35214_X *)&PR35214_x)->arr[1]; // both-error {{not a compile-time constant}}
+int *PR35214_z = &((struct PR35214_X *)&PR35214_x)->arr[1]; // ok, &PR35214_x + 2
+
+
+/// From const-eval-64.c
+EVAL_EXPR(53, ((char*)-1LL) + 1 == 0 ? 1 : -1)
+EVAL_EXPR(54, ((char*)-1LL) + 1 < (char*) -1 ? 1 : -1)
+
+/// === Additions ===
+#if __SIZEOF_INT__ == 4
+typedef __INTPTR_TYPE__ intptr_t;
+const intptr_t A = (intptr_t)(((int*) 0) + 1);
+const intptr_t B = (intptr_t)(((char*)0) + 3);
+_Static_assert(A > B, "");
+#else
+#error :(
+#endif

>From 398251e71e2ac3001daf09d64a15309bfb07bf70 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbaeder at redhat.com>
Date: Wed, 6 Mar 2024 16:11:17 +0100
Subject: [PATCH 2/2] Allow converting bool to pointers as well.

---
 clang/lib/AST/Interp/ByteCodeExprGen.cpp | 10 +++++++++-
 clang/lib/AST/Interp/Opcodes.td          |  2 +-
 clang/test/AST/Interp/functions.cpp      | 15 +++++++++++++++
 3 files changed, 25 insertions(+), 2 deletions(-)

diff --git a/clang/lib/AST/Interp/ByteCodeExprGen.cpp b/clang/lib/AST/Interp/ByteCodeExprGen.cpp
index 67e7d89c033d63..5b54e581eff6eb 100644
--- a/clang/lib/AST/Interp/ByteCodeExprGen.cpp
+++ b/clang/lib/AST/Interp/ByteCodeExprGen.cpp
@@ -220,7 +220,15 @@ bool ByteCodeExprGen<Emitter>::VisitCastExpr(const CastExpr *CE) {
                                 /*IsMutable=*/false, nullptr);
     }
 
-    return this->emitGetIntPtr(T, Desc, CE);
+    if (!this->emitGetIntPtr(T, Desc, CE))
+      return false;
+
+    PrimType DestPtrT = classifyPrim(PtrType);
+    if (DestPtrT == PT_Ptr)
+      return true;
+
+    // // In case we're converting the integer to a non-Pointer.
+    return this->emitDecayPtr(PT_Ptr, DestPtrT, CE);
   }
 
   case CK_AtomicToNonAtomic:
diff --git a/clang/lib/AST/Interp/Opcodes.td b/clang/lib/AST/Interp/Opcodes.td
index 68bd2218a2d6bb..c7c32149bbb813 100644
--- a/clang/lib/AST/Interp/Opcodes.td
+++ b/clang/lib/AST/Interp/Opcodes.td
@@ -525,7 +525,7 @@ def GetFnPtr : Opcode {
 }
 
 def GetIntPtr : Opcode {
-  let Types = [IntegerTypeClass];
+  let Types = [AluTypeClass];
   let Args = [ArgDesc];
   let HasGroup = 1;
 }
diff --git a/clang/test/AST/Interp/functions.cpp b/clang/test/AST/Interp/functions.cpp
index 67fd9036d81e76..4fb3c816000ab8 100644
--- a/clang/test/AST/Interp/functions.cpp
+++ b/clang/test/AST/Interp/functions.cpp
@@ -185,6 +185,21 @@ namespace FunctionReturnType {
   constexpr int (*invalidFnPtr)() = m;
   static_assert(invalidFnPtr() == 5, ""); // both-error {{not an integral constant expression}} \
                                           // both-note {{non-constexpr function 'm'}}
+
+
+namespace ToBool {
+  void mismatched(int x) {}
+  typedef void (*callback_t)(int);
+  void foo() {
+    callback_t callback = (callback_t)mismatched; // warns
+    /// Casts a function pointer to a boolean and then back to a function pointer.
+    /// This is extracted from test/Sema/callingconv-cast.c
+    callback = (callback_t)!mismatched; // both-warning {{address of function 'mismatched' will always evaluate to 'true'}} \
+                                        // both-note {{prefix with the address-of operator to silence this warning}}
+  }
+}
+
+
 }
 
 namespace Comparison {



More information about the cfe-commits mailing list