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

>From e3f1fa9a6b0335ea7183c58d636c567e9132886f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <[email protected]>
Date: Thu, 30 Jul 2026 14:53:12 +0200
Subject: [PATCH] opaque pointers

---
 clang/lib/AST/ByteCode/Compiler.cpp           |  18 +-
 clang/lib/AST/ByteCode/Context.cpp            |  16 +-
 clang/lib/AST/ByteCode/Context.h              |   2 +-
 clang/lib/AST/ByteCode/Interp.cpp             | 177 ++++++
 clang/lib/AST/ByteCode/Interp.h               | 160 ++++--
 clang/lib/AST/ByteCode/InterpBuiltin.cpp      | 236 +-------
 .../AST/ByteCode/InterpBuiltinObjectSize.cpp  | 544 ++++++++++++++++++
 clang/lib/AST/ByteCode/InterpHelpers.h        |   3 +-
 clang/lib/AST/ByteCode/InterpState.h          |  21 +
 clang/lib/AST/ByteCode/Opcodes.td             |   6 +
 clang/lib/AST/ByteCode/Pointer.cpp            | 227 +++++++-
 clang/lib/AST/ByteCode/Pointer.h              | 158 ++++-
 clang/lib/AST/ByteCode/Program.cpp            |   6 +-
 clang/lib/AST/CMakeLists.txt                  |   1 +
 clang/lib/AST/ExprConstShared.h               |   2 +
 clang/lib/AST/ExprConstant.cpp                |   7 +-
 .../builtin-object-size-codegen-cxx23.cpp     |  15 +
 .../ByteCode/builtin-object-size-codegen.c    |  89 +++
 .../ByteCode/builtin-object-size-codegen.cpp  | 188 ++++++
 clang/test/AST/ByteCode/codegen.c             |  22 +
 clang/test/AST/ByteCode/enable_if.c           | 202 -------
 clang/test/AST/ByteCode/literals.cpp          |   3 +
 .../CodeGen/attr-counted-by-with-sanitizers.c |   6 +-
 .../attr-counted-by-without-sanitizers.c      |   8 +-
 clang/test/Sema/enable_if.c                   |   5 +
 clang/test/SemaCXX/new-delete.cpp             |  16 +-
 26 files changed, 1636 insertions(+), 502 deletions(-)
 create mode 100644 clang/lib/AST/ByteCode/InterpBuiltinObjectSize.cpp
 create mode 100644 
clang/test/AST/ByteCode/builtin-object-size-codegen-cxx23.cpp
 delete mode 100644 clang/test/AST/ByteCode/enable_if.c

diff --git a/clang/lib/AST/ByteCode/Compiler.cpp 
b/clang/lib/AST/ByteCode/Compiler.cpp
index 316b2a4f092f9..ee36d11fd1f16 100644
--- a/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/clang/lib/AST/ByteCode/Compiler.cpp
@@ -7,6 +7,7 @@
 
//===----------------------------------------------------------------------===//
 
 #include "Compiler.h"
+#include "../ExprConstShared.h"
 #include "ByteCodeEmitter.h"
 #include "Context.h"
 #include "FixedPoint.h"
@@ -453,8 +454,12 @@ bool Compiler<Emitter>::VisitCastExpr(const CastExpr *E) {
 
   switch (E->getCastKind()) {
   case CK_LValueToRValue: {
-    if (ToLValue && E->getType()->isPointerType())
-      return this->delegate(SubExpr);
+    if (ToLValue && E->getType()->isPointerType()) {
+      assert(!DiscardResult);
+      if (!this->visit(SubExpr))
+        return false;
+      return this->emitLoadPopL(E);
+    }
 
     if (SubExpr->getType().isVolatileQualified())
       return this->emitInvalidCast(CastKind::Volatile, /*Fatal=*/true, E);
@@ -6095,7 +6100,7 @@ bool Compiler<Emitter>::VisitBuiltinCallExpr(const 
CallExpr *E,
         return false;
 
     } else {
-      if (!this->visitAsLValue(Arg0))
+      if (!this->visitAsLValue(ignorePointerCastsAndParens(Arg0)))
         return false;
     }
     if (!this->visit(E->getArg(1)))
@@ -8706,8 +8711,13 @@ bool Compiler<Emitter>::emitDestructionPop(const 
Descriptor *Desc,
 template <class Emitter>
 bool Compiler<Emitter>::emitDummyPtr(DeclOrExpr D, const Expr *E, bool CU) {
   assert(!DiscardResult && "Should've been checked before");
-  unsigned DummyID = P.getOrCreateDummy(D, CU);
 
+  if (ToLValue) {
+    if (const auto *VD = D.asValueDecl())
+      return this->emitGetOpaquePtr(VD, CU, E);
+  }
+
+  unsigned DummyID = P.getOrCreateDummy(D, CU);
   if (!this->emitGetPtrGlobal(DummyID, E))
     return false;
   if (E->getType()->isVoidType())
diff --git a/clang/lib/AST/ByteCode/Context.cpp 
b/clang/lib/AST/ByteCode/Context.cpp
index ce7a95ed49c06..d0b779ed15b46 100644
--- a/clang/lib/AST/ByteCode/Context.cpp
+++ b/clang/lib/AST/ByteCode/Context.cpp
@@ -367,26 +367,24 @@ std::optional<uint64_t> Context::evaluateStrlen(State 
&Parent, const Expr *E) {
   return Result;
 }
 
-std::optional<uint64_t>
-Context::tryEvaluateObjectSize(State &Parent, const Expr *E, unsigned Kind) {
+std::optional<uint64_t> Context::tryEvaluateObjectSize(State &Parent,
+                                                       const Expr *E,
+                                                       unsigned Kind,
+                                                       bool IsDynamic) {
   assert(Stk.empty());
   Compiler<EvalEmitter> C(*this, *P, Parent, Stk);
 
   std::optional<uint64_t> Result;
-
   auto PtrRes = C.interpretAsLValuePointer(E, [&](InterpState &S, CodePtr OpPC,
                                                   const Pointer &Ptr) {
-    const Descriptor *DeclDesc = Ptr.getDeclDesc();
-    if (!DeclDesc)
-      return false;
-
-    QualType T = DeclDesc->getType().getNonReferenceType();
+    QualType T = Ptr.getType().getNonReferenceType();
     if (T->isIncompleteType() || T->isFunctionType() ||
         !T->isConstantSizeType())
       return false;
 
     Pointer P = Ptr;
-    if (auto ObjectSize = evaluateBuiltinObjectSize(getASTContext(), Kind, P)) 
{
+    if (auto ObjectSize =
+            evaluateBuiltinObjectSize(getASTContext(), Kind, P, E, IsDynamic)) 
{
       Result = *ObjectSize;
       return true;
     }
diff --git a/clang/lib/AST/ByteCode/Context.h b/clang/lib/AST/ByteCode/Context.h
index 47821a3e3a7f3..77566d35ad6e0 100644
--- a/clang/lib/AST/ByteCode/Context.h
+++ b/clang/lib/AST/ByteCode/Context.h
@@ -95,7 +95,7 @@ class Context final {
   /// bytes belonging to the same storage (stack, heap allocation,
   /// global variable) are considered.
   std::optional<uint64_t> tryEvaluateObjectSize(State &Parent, const Expr *E,
-                                                unsigned Kind);
+                                                unsigned Kind, bool IsDynamic);
 
   std::optional<bool> evaluateWithSubstitution(State &Parent,
                                                const FunctionDecl *Callee,
diff --git a/clang/lib/AST/ByteCode/Interp.cpp 
b/clang/lib/AST/ByteCode/Interp.cpp
index ece2e71408731..a931e2bb6ae6f 100644
--- a/clang/lib/AST/ByteCode/Interp.cpp
+++ b/clang/lib/AST/ByteCode/Interp.cpp
@@ -1617,6 +1617,29 @@ static bool getField(InterpState &S, CodePtr OpPC, const 
Pointer &Ptr,
     return false;
   }
 
+  if (Ptr.isOpaquePointer()) {
+    const OpaquePointer &OP = Ptr.asOpaquePointer();
+    const RecordDecl *RD = OP.getFieldType()->getAsRecordDecl();
+    if (!RD)
+      return false;
+    const Record *R = S.getContext().getRecord(RD);
+    if (!R)
+      return false;
+
+    const Record::Field *F = R->findField(Off);
+    if (!F)
+      return false;
+
+    PointerPathEntry *NewPath = S.extendPointerPath(
+        OP.PathLength + 1, OP.Path, PointerPathEntry::field(F->Decl));
+
+    S.Stk.push<Pointer>(OP.withPath(NewPath, OP.PathLength + 1,
+                                    F->Decl->getType().getTypePtr()),
+                        Ptr.getByteOffset());
+
+    return true;
+  }
+
   if (!Ptr.isBlockPointer()) {
     // If we're trying to get the field of a TypeId pointer, try to produce a
     // proper diagnostic.
@@ -1651,6 +1674,29 @@ static bool getBase(InterpState &S, CodePtr OpPC, const 
Pointer &Ptr,
   if (!NullOK && !CheckNull(S, OpPC, Ptr, CSK_Base))
     return false;
 
+  if (Ptr.isOpaquePointer()) {
+    const OpaquePointer &OP = Ptr.asOpaquePointer();
+    const RecordDecl *RD = OP.getFieldType()->getAsRecordDecl();
+    if (!RD)
+      return false;
+    const Record *R = S.getContext().getRecord(RD);
+    assert(R);
+
+    const Record::Base *B = R->findBase(Off);
+    if (!B)
+      return false;
+
+    PointerPathEntry *NewPath = S.extendPointerPath(
+        OP.PathLength + 1, OP.Path,
+        PointerPathEntry::base(cast<CXXRecordDecl>(B->Decl)));
+    S.Stk.push<Pointer>(
+        OP.withPath(
+            NewPath, OP.PathLength + 1,
+            S.getASTContext().getCanonicalTagType(B->Decl).getTypePtr()),
+        Ptr.getByteOffset());
+    return true;
+  }
+
   if (!Ptr.isBlockPointer()) {
     if (!Ptr.isIntegralPointer())
       return false;
@@ -1911,6 +1957,7 @@ bool CallVar(InterpState &S, CodePtr OpPC, const Function 
*Func,
   S.Current = FrameBefore;
   return false;
 }
+
 bool Call(InterpState &S, CodePtr OpPC, const Function *Func,
           uint32_t VarArgSize) {
 
@@ -3229,6 +3276,136 @@ bool CastFloatingIntegralAPS(InterpState &S, CodePtr 
OpPC, uint32_t BitWidth,
   return floatAPCast<true>(S, OpPC, F, BitWidth, FPOI);
 }
 
+// Helper to check if a RecordDecl can be passed to
+// ASTContext::getRecordLayout().
+static bool validRecordDecl(const RecordDecl *D) {
+  D = D->getDefinition();
+  return D && !D->isInvalidDecl() && D->isCompleteDefinition();
+}
+// Same but for types.
+static bool validType(QualType T) {
+  if (const RecordDecl *RD = T->getAsRecordDecl())
+    return validRecordDecl(RD);
+  return true;
+}
+
+bool arrayElemPtrOpaque(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
+                        APSInt &&Index, bool AllowReplace) {
+  const OpaquePointer &OP = Ptr.asOpaquePointer();
+  QualType ArrTy = OP.getSurroundingArray();
+
+  if (isa<VariableArrayType>(ArrTy) && OP.PathLength != 0) {
+    return false;
+  }
+
+  QualType ElemType;
+  if (const ArrayType *AT = ArrTy->getAsArrayTypeUnsafe())
+    ElemType = AT->getElementType();
+  else
+    ElemType = ArrTy;
+
+  if (ArrTy->isArrayType()) {
+    unsigned NewPathLength;
+    if (AllowReplace && OP.isArrayElement()) {
+      // This is what happens after an array-to-pointer-decay. We don't enter
+      // the array element but simply change the index in the array we're 
alread
+      // pointing into.
+      NewPathLength = OP.PathLength;
+    } else {
+      NewPathLength = OP.PathLength + 1;
+    }
+
+    PointerPathEntry NewEntry;
+    if (Index.isNonNegative())
+      NewEntry = PointerPathEntry::array(Index.getZExtValue());
+    else
+      NewEntry = PointerPathEntry::negativeArray(Index.getZExtValue());
+
+    PointerPathEntry *NewPath =
+        S.extendPointerPath(NewPathLength, OP.Path, NewEntry);
+    S.Stk.push<Pointer>(
+        OP.withPath(NewPath, NewPathLength, ElemType.getTypePtr()),
+        Ptr.getByteOffset());
+
+  } else {
+    if (!validType(ElemType))
+      return false;
+    unsigned ElemSize =
+        S.getASTContext().getTypeSizeInChars(ElemType).getQuantity();
+    size_t NewOffset = Ptr.getByteOffset() + (Index.getZExtValue() * ElemSize);
+    bool PastEnd = Index != 0;
+
+    S.Stk.push<Pointer>(OP.withFieldType(ElemType.getTypePtr(), PastEnd),
+                        NewOffset);
+  }
+  return true;
+}
+
+std::optional<Pointer> addSubOffsetOpaque(InterpState &S, CodePtr OpPC,
+                                          const Pointer &Ptr, APSInt &&Offset,
+                                          ArithOp Op) {
+  assert(Ptr.isOpaquePointer());
+  if (Offset.isZero())
+    return Ptr;
+
+  const OpaquePointer &OP = Ptr.asOpaquePointer();
+  QualType ArrTy = OP.getSurroundingArray();
+  QualType ElemTy = ArrTy;
+  unsigned NumElems = 1;
+  if (const ArrayType *AT = ArrTy->getAsArrayTypeUnsafe()) {
+    ElemTy = AT->getElementType();
+    if (const auto *CAT = dyn_cast<ConstantArrayType>(AT))
+      NumElems = CAT->getZExtSize();
+  }
+
+  if (isa<IncompleteArrayType>(ArrTy)) {
+    const SourceInfo &E = S.Current->getSource(OpPC);
+    S.FFDiag(E, diag::note_constexpr_unsized_array_indexed);
+    return std::nullopt;
+  }
+
+  if (Offset > NumElems) {
+    if (Op == ArithOp::Add)
+      S.CCEDiag(S.Current->getSource(OpPC), diag::note_constexpr_array_index)
+          << Offset << /*non-array*/ !isa<ArrayType>(ArrTy) << NumElems;
+    else
+      S.CCEDiag(S.Current->getSource(OpPC), diag::note_constexpr_array_index)
+          << -Offset << /*non-array*/ !isa<ArrayType>(ArrTy) << NumElems;
+  }
+
+  if (!validType(ElemTy) || !validType(ArrTy)) {
+    Invalid(S, OpPC);
+    return std::nullopt;
+  }
+
+  if (Offset.getActiveBits() > 64)
+    return std::nullopt;
+
+  // If the pointer is an array element, advance that index.
+  if (OP.isArrayElement()) {
+    unsigned NewPathLength = OP.PathLength;
+    PointerPathEntry *NewPath = S.allocPointerPath(OP.PathLength, OP.Path);
+
+    if (Op == ArithOp::Add)
+      NewPath[NewPathLength - 1].Index += Offset.getZExtValue();
+    else
+      NewPath[NewPathLength - 1].Index -= Offset.getZExtValue();
+    return OP.withPath(NewPath, NewPathLength, OP.FieldType.getPointer());
+  }
+
+  unsigned ElemSize =
+      S.getASTContext().getTypeSizeInChars(ElemTy).getQuantity();
+  unsigned NewOffset;
+  if (Op == ArithOp::Add)
+    NewOffset = Ptr.getByteOffset() + (ElemSize * Offset.getZExtValue());
+  else
+    NewOffset = Ptr.getByteOffset() - (ElemSize * Offset.getZExtValue());
+
+  // We already checked offset != before, so this is a non-array type being
+  // offset by > 0.
+  return Pointer(OP.withPastEnd(true), NewOffset);
+}
+
 // FIXME: Would be nice to generate this instead of hardcoding it here.
 [[maybe_unused]] static constexpr bool OpReturns(Opcode Op) {
   return Op == OP_RetVoid || Op == OP_RetValue || Op == OP_NoRet ||
diff --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h
index 4c8d11a9dd77a..bdec7b7b91db8 100644
--- a/clang/lib/AST/ByteCode/Interp.h
+++ b/clang/lib/AST/ByteCode/Interp.h
@@ -2264,6 +2264,46 @@ bool LoadPop(InterpState &S, CodePtr OpPC) {
   return true;
 }
 
+/// Like LoadPop above, but if any of the checks fail, we
+/// turn the pointer into an opaque pointer of appropriate type.
+inline bool LoadPopL(InterpState &S, CodePtr OpPC) {
+  const Pointer &Ptr = S.Stk.pop<Pointer>();
+  auto *P = S.getEvalStatus().Diag;
+  S.getEvalStatus().Diag = nullptr;
+
+  bool Failed = false;
+  if (!CheckLoad(S, OpPC, Ptr))
+    Failed = true;
+  if (!Ptr.isBlockPointer())
+    Failed = true;
+  if (!Failed && !Ptr.canDeref(PT_Ptr))
+    Failed = true;
+  S.getEvalStatus().Diag = P;
+
+  if (Failed) {
+    if (Ptr.isOpaquePointer()) {
+      const OpaquePointer &OP = Ptr.asOpaquePointer();
+
+      if (!Ptr.asOpaquePointer().Base->getType()->isPointerType())
+        return false;
+
+      QualType T = Ptr.getType();
+      S.Stk.push<Pointer>(OP.withFieldType(T.getTypePtr()),
+                          Ptr.getByteOffset());
+      return true;
+    }
+
+    // Convert the block pointer to an opaque pointer.
+    if (!Ptr.isBlockPointer())
+      return false;
+    // FIXME: I *think* we need more information here than just the base.
+    S.Stk.push<Pointer>(Ptr.getDeclDesc()->asValueDecl());
+  } else {
+    S.Stk.push<Pointer>(Ptr.deref<Pointer>());
+  }
+  return true;
+}
+
 template <PrimType Name, class T = typename PrimConv<Name>::T>
 bool Store(InterpState &S, CodePtr OpPC) {
   const T &Value = S.Stk.pop<T>();
@@ -2658,11 +2698,23 @@ std::optional<Pointer> OffsetHelper(InterpState &S, 
CodePtr OpPC,
   return Ptr.atIndex(static_cast<uint64_t>(Result));
 }
 
+std::optional<Pointer> addSubOffsetOpaque(InterpState &S, CodePtr OpPC,
+                                          const Pointer &Ptr, APSInt &&Offset,
+                                          ArithOp Op);
 template <PrimType Name, class T = typename PrimConv<Name>::T>
 bool AddOffset(InterpState &S, CodePtr OpPC) {
   const T &Offset = S.Stk.pop<T>();
   const Pointer &Ptr = S.Stk.pop<Pointer>().expand();
 
+  if (Ptr.isOpaquePointer()) {
+    if (std::optional<Pointer> Result =
+            addSubOffsetOpaque(S, OpPC, Ptr, Offset.toAPSInt(), ArithOp::Add)) 
{
+      S.Stk.push<Pointer>(*Result);
+      return true;
+    }
+    return false;
+  }
+
   if (std::optional<Pointer> Result = OffsetHelper<T, ArithOp::Add>(
           S, OpPC, Offset, Ptr, /*IsPointerArith=*/true)) {
     S.Stk.push<Pointer>(Result->narrow());
@@ -2676,6 +2728,15 @@ bool SubOffset(InterpState &S, CodePtr OpPC) {
   const T &Offset = S.Stk.pop<T>();
   const Pointer &Ptr = S.Stk.pop<Pointer>().expand();
 
+  if (Ptr.isOpaquePointer()) {
+    if (std::optional<Pointer> Result =
+            addSubOffsetOpaque(S, OpPC, Ptr, Offset.toAPSInt(), ArithOp::Sub)) 
{
+      S.Stk.push<Pointer>(*Result);
+      return true;
+    }
+    return false;
+  }
+
   if (std::optional<Pointer> Result = OffsetHelper<T, ArithOp::Sub>(
           S, OpPC, Offset, Ptr, /*IsPointerArith=*/true)) {
     S.Stk.push<Pointer>(Result->narrow());
@@ -2684,6 +2745,12 @@ bool SubOffset(InterpState &S, CodePtr OpPC) {
   return false;
 }
 
+inline bool GetOpaquePtr(InterpState &S, const ValueDecl *VD,
+                         bool ConstexprUnknown) {
+  S.Stk.push<Pointer>(VD, ConstexprUnknown);
+  return true;
+}
+
 template <ArithOp Op>
 static inline bool IncDecPtrHelper(InterpState &S, CodePtr OpPC,
                                    const Pointer &Ptr) {
@@ -2699,6 +2766,15 @@ static inline bool IncDecPtrHelper(InterpState &S, 
CodePtr OpPC,
   // Get the current value on the stack.
   S.Stk.push<Pointer>(P);
 
+  if (P.isOpaquePointer()) {
+    if (std::optional<Pointer> Result =
+            addSubOffsetOpaque(S, OpPC, P, APSInt(APInt(1, 1), true), Op)) {
+      Ptr.deref<Pointer>() = *Result;
+      return true;
+    }
+    return false;
+  }
+
   // Now the current Ptr again and a constant 1.
   OneT One = OneT::from(1);
   if (std::optional<Pointer> Result =
@@ -3444,18 +3520,15 @@ inline bool ExpandPtr(InterpState &S) {
   return true;
 }
 
-// 1) Pops an integral value from the stack
-// 2) Peeks a pointer
-// 3) Pushes a new pointer that's a narrowed array
-//   element of the peeked pointer with the value
-//   from 1) added as offset.
-//
-// This leaves the original pointer on the stack and pushes a new one
-// with the offset applied and narrowed.
-template <PrimType Name, class T = typename PrimConv<Name>::T>
-inline bool ArrayElemPtr(InterpState &S, CodePtr OpPC) {
-  const T &Offset = S.Stk.pop<T>();
-  const Pointer &Ptr = S.Stk.peek<Pointer>();
+bool arrayElemPtrOpaque(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
+                        APSInt &&Index, bool AllowReplace = true);
+
+// Implementation for ArrayElemPtr and ArrayElemPtrPop ops.
+template <typename T>
+inline bool arrayElemPtr(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
+                         const T &Offset) {
+  if (Ptr.isOpaquePointer())
+    return arrayElemPtrOpaque(S, OpPC, Ptr, Offset.toAPSInt());
 
   if (!Ptr.isZero() && !Offset.isZero()) {
     if (!CheckArray(S, OpPC, Ptr))
@@ -3479,38 +3552,31 @@ inline bool ArrayElemPtr(InterpState &S, CodePtr OpPC) {
     S.Stk.push<Pointer>(Result->narrow());
     return true;
   }
-
   return false;
 }
 
+// 1) Pops an integral value from the stack
+// 2) Peeks a pointer
+// 3) Pushes a new pointer that's a narrowed array
+//   element of the peeked pointer with the value
+//   from 1) added as offset.
+//
+// This leaves the original pointer on the stack and pushes a new one
+// with the offset applied and narrowed.
 template <PrimType Name, class T = typename PrimConv<Name>::T>
-inline bool ArrayElemPtrPop(InterpState &S, CodePtr OpPC) {
+inline bool ArrayElemPtr(InterpState &S, CodePtr OpPC) {
   const T &Offset = S.Stk.pop<T>();
-  const Pointer &Ptr = S.Stk.pop<Pointer>();
-
-  if (!Ptr.isZero() && !Offset.isZero()) {
-    if (!CheckArray(S, OpPC, Ptr))
-      return false;
-  }
+  const Pointer &Ptr = S.Stk.peek<Pointer>();
 
-  if (Offset.isZero()) {
-    if (const Descriptor *Desc = Ptr.getFieldDesc();
-        Desc && Desc->isArray() && Ptr.getIndex() == 0) {
-      S.Stk.push<Pointer>(Ptr.atIndex(0).narrow());
-      return true;
-    }
-    S.Stk.push<Pointer>(Ptr.narrow());
-    return true;
-  }
+  return arrayElemPtr<T>(S, OpPC, Ptr, Offset);
+}
 
-  assert(!Offset.isZero());
+template <PrimType Name, class T = typename PrimConv<Name>::T>
+inline bool ArrayElemPtrPop(InterpState &S, CodePtr OpPC) {
+  const T &Offset = S.Stk.pop<T>();
+  const Pointer &Ptr = S.Stk.pop<Pointer>();
 
-  if (std::optional<Pointer> Result =
-          OffsetHelper<T, ArithOp::Add>(S, OpPC, Offset, Ptr)) {
-    S.Stk.push<Pointer>(Result->narrow());
-    return true;
-  }
-  return false;
+  return arrayElemPtr<T>(S, OpPC, Ptr, Offset);
 }
 
 template <PrimType Name, class T = typename PrimConv<Name>::T>
@@ -3582,9 +3648,25 @@ inline bool ArrayDecay(InterpState &S, CodePtr OpPC) {
       return false;
   }
 
-  if (Ptr.isRoot() || !Ptr.isUnknownSizeArray()) {
-    S.Stk.push<Pointer>(Ptr.atIndex(0).narrow());
-    return true;
+  if (Ptr.isRoot() || !Ptr.isUnknownSizeArray() ||
+      !S.inConstantContext()) { // FIXME
+    if (Ptr.isBlockPointer()) {
+      S.Stk.push<Pointer>(Ptr.atIndex(0).narrow());
+      return true;
+    }
+
+    if (!Ptr.isOpaquePointer()) {
+      S.Stk.push<Pointer>(Ptr);
+      return true;
+    }
+
+    if (!Ptr.getType()->isArrayType()) {
+      S.Stk.push<Pointer>(Ptr);
+      return true;
+    }
+    return arrayElemPtrOpaque(S, OpPC, Ptr,
+                              APSInt(APInt::getZero(1), /*IsUnsigned=*/true),
+                              /*AllowReplace=*/false);
   }
 
   const SourceInfo &E = S.Current->getSource(OpPC);
diff --git a/clang/lib/AST/ByteCode/InterpBuiltin.cpp 
b/clang/lib/AST/ByteCode/InterpBuiltin.cpp
index 0e81059a0f6cf..2d9fae61b91c0 100644
--- a/clang/lib/AST/ByteCode/InterpBuiltin.cpp
+++ b/clang/lib/AST/ByteCode/InterpBuiltin.cpp
@@ -2297,218 +2297,9 @@ static bool interp__builtin_memchr(InterpState &S, 
CodePtr OpPC,
   return true;
 }
 
-static std::optional<unsigned> computeFullDescSize(const ASTContext &ASTCtx,
-                                                   const Descriptor *Desc) {
-  if (Desc->isPrimitive() || Desc->isArray())
-    return ASTCtx.getTypeSizeInChars(Desc->getType()).getQuantity();
-
-  if (Desc->isRecord()) {
-    // Can't use Descriptor::getType() as that may return a pointer type. Look
-    // at the decl directly.
-    return ASTCtx
-        .getTypeSizeInChars(
-            ASTCtx.getCanonicalTagType(Desc->ElemRecord->getDecl()))
-        .getQuantity();
-  }
-
-  return std::nullopt;
-}
-
-/// Compute the byte offset of \p Ptr in the full declaration.
-static unsigned computePointerOffset(const ASTContext &ASTCtx,
-                                     const Pointer &Ptr) {
-  unsigned Result = 0;
-
-  Pointer P = Ptr;
-  while (P.isField() || P.isArrayElement()) {
-    P = P.expand();
-    const Descriptor *D = P.getFieldDesc();
-
-    if (P.isArrayElement()) {
-      unsigned ElemSize =
-          ASTCtx.getTypeSizeInChars(D->getElemQualType()).getQuantity();
-      if (P.isOnePastEnd())
-        Result += ElemSize * P.getNumElems();
-      else
-        Result += ElemSize * P.getIndex();
-      P = P.expand().getArray();
-    } else if (P.isBaseClass()) {
-      const auto *RD = cast<CXXRecordDecl>(D->asDecl());
-      bool IsVirtual = Ptr.isVirtualBaseClass();
-      P = P.getBase();
-      const Record *BaseRecord = P.getRecord();
-
-      const ASTRecordLayout &Layout =
-          
ASTCtx.getASTRecordLayout(cast<CXXRecordDecl>(BaseRecord->getDecl()));
-      if (IsVirtual)
-        Result += Layout.getVBaseClassOffset(RD).getQuantity();
-      else
-        Result += Layout.getBaseClassOffset(RD).getQuantity();
-    } else if (P.isField()) {
-      const FieldDecl *FD = P.getField();
-      const ASTRecordLayout &Layout =
-          ASTCtx.getASTRecordLayout(FD->getParent());
-      unsigned FieldIndex = FD->getFieldIndex();
-      uint64_t FieldOffset =
-          ASTCtx.toCharUnitsFromBits(Layout.getFieldOffset(FieldIndex))
-              .getQuantity();
-      Result += FieldOffset;
-      P = P.getBase();
-    } else
-      llvm_unreachable("Unhandled descriptor type");
-  }
-
-  return Result;
-}
-
-/// Does Ptr point to the last subobject?
-static bool pointsToLastObject(const Pointer &Ptr) {
-  Pointer P = Ptr;
-  while (!P.isRoot()) {
-
-    if (P.isArrayElement()) {
-      P = P.expand().getArray();
-      continue;
-    }
-    if (P.isBaseClass()) {
-      if (P.getRecord()->getNumFields() > 0)
-        return false;
-      P = P.getBase();
-      continue;
-    }
-
-    Pointer Base = P.getBase();
-    if (const Record *R = Base.getRecord()) {
-      assert(P.getField());
-      if (P.getField()->getFieldIndex() != R->getNumFields() - 1)
-        return false;
-    }
-    P = Base;
-  }
-
-  return true;
-}
-
-/// Does Ptr point to the last object AND to a flexible array member?
-static bool isUserWritingOffTheEnd(const ASTContext &Ctx, const Pointer &Ptr,
-                                   bool InvalidBase) {
-  auto isFlexibleArrayMember = [&](const Descriptor *FieldDesc) {
-    using FAMKind = LangOptions::StrictFlexArraysLevelKind;
-    FAMKind StrictFlexArraysLevel =
-        Ctx.getLangOpts().getStrictFlexArraysLevel();
-
-    if (StrictFlexArraysLevel == FAMKind::Default)
-      return true;
-
-    unsigned NumElems = FieldDesc->getNumElems();
-    if (NumElems == 0 && StrictFlexArraysLevel != FAMKind::IncompleteOnly)
-      return true;
-
-    if (NumElems == 1 && StrictFlexArraysLevel == FAMKind::OneZeroOrIncomplete)
-      return true;
-    return false;
-  };
-
-  const Descriptor *FieldDesc = Ptr.getFieldDesc();
-  if (!FieldDesc->isArray())
-    return false;
-
-  return InvalidBase && pointsToLastObject(Ptr) &&
-         isFlexibleArrayMember(FieldDesc);
-}
-
-UnsignedOrNone evaluateBuiltinObjectSize(const ASTContext &ASTCtx,
-                                         unsigned Kind, Pointer &Ptr) {
-  if (Ptr.isZero() || !Ptr.isBlockPointer())
-    return std::nullopt;
-
-  if (Ptr.isDummy() && Ptr.getType()->isPointerType())
-    return std::nullopt;
-
-  bool InvalidBase = false;
-
-  if (Ptr.isDummy()) {
-    if (const VarDecl *VD = Ptr.getDeclDesc()->asVarDecl();
-        VD && VD->getType()->isPointerType())
-      InvalidBase = true;
-  }
-
-  // According to the GCC documentation, we want the size of the subobject
-  // denoted by the pointer. But that's not quite right -- what we actually
-  // want is the size of the immediately-enclosing array, if there is one.
-  if (Ptr.isArrayElement())
-    Ptr = Ptr.expand();
-
-  bool DetermineForCompleteObject = Ptr.getFieldDesc() == Ptr.getDeclDesc();
-  const Descriptor *DeclDesc = Ptr.getDeclDesc();
-  assert(DeclDesc);
-
-  bool UseFieldDesc = (Kind & 1u);
-  bool ReportMinimum = (Kind & 2u);
-  if (!UseFieldDesc || DetermineForCompleteObject) {
-    // Can't read beyond the pointer decl desc.
-    if (!ReportMinimum && DeclDesc->getType()->isPointerType())
-      return std::nullopt;
-
-    if (InvalidBase)
-      return std::nullopt;
-  } else {
-    if (isUserWritingOffTheEnd(ASTCtx, Ptr, InvalidBase)) {
-      // If we cannot determine the size of the initial allocation, then we
-      // can't given an accurate upper-bound. However, we are still able to 
give
-      // conservative lower-bounds for Type=3.
-      if (Kind == 1)
-        return std::nullopt;
-    }
-    // For Type=1, defer to the runtime path on a true incomplete-array
-    // flexible array member (e.g. 'char fam[]') even when the base is a
-    // concrete local/global. Without this, the bytecode interpreter would
-    // happily fold &af.fam to 'NumElems * elemSize = 0' below; the default
-    // const-evaluator avoids the same trap, and CGBuiltin emits
-    // @llvm.objectsize for the correct layout-derived answer (matching
-    // GCC's __bos/__bdos on '&af.fam').
-    if (Kind == 1 && pointsToLastObject(Ptr) && Ptr.getFieldDesc()->isArray() 
&&
-        Ptr.getFieldDesc()->getType()->isIncompleteArrayType())
-      return std::nullopt;
-  }
-
-  // The "closest surrounding subobject" is NOT a base class,
-  // so strip the base class casts.
-  if (UseFieldDesc && Ptr.isBaseClass())
-    Ptr = Ptr.stripBaseCasts();
-
-  const Descriptor *Desc = UseFieldDesc ? Ptr.getFieldDesc() : DeclDesc;
-  assert(Desc);
-
-  std::optional<unsigned> FullSize = computeFullDescSize(ASTCtx, Desc);
-  if (!FullSize)
-    return std::nullopt;
-
-  unsigned ByteOffset;
-  if (UseFieldDesc) {
-    if (Ptr.isBaseClass()) {
-      assert(computePointerOffset(ASTCtx, Ptr.getBase()) <=
-             computePointerOffset(ASTCtx, Ptr));
-      ByteOffset = computePointerOffset(ASTCtx, Ptr.getBase()) -
-                   computePointerOffset(ASTCtx, Ptr);
-    } else {
-      if (Ptr.inArray())
-        ByteOffset =
-            computePointerOffset(ASTCtx, Ptr) -
-            computePointerOffset(ASTCtx, Ptr.expand().atIndex(0).narrow());
-      else
-        ByteOffset = 0;
-    }
-  } else
-    ByteOffset = computePointerOffset(ASTCtx, Ptr);
-
-  assert(ByteOffset <= *FullSize);
-  return *FullSize - ByteOffset;
-}
-
 static bool interp__builtin_object_size(InterpState &S, CodePtr OpPC,
                                         const InterpFrame *Frame,
-                                        const CallExpr *Call) {
+                                        const CallExpr *Call, bool IsDynamic) {
   const ASTContext &ASTCtx = S.getASTContext();
   // From the GCC docs:
   // Kind is an integer constant from 0 to 3. If the least significant bit is
@@ -2521,17 +2312,31 @@ static bool interp__builtin_object_size(InterpState &S, 
CodePtr OpPC,
   assert(Kind <= 3 && "unexpected kind");
   Pointer Ptr = S.Stk.pop<Pointer>();
 
+  if (auto Result = evaluateBuiltinObjectSize(ASTCtx, Kind, Ptr,
+                                              Call->getArg(0), IsDynamic)) {
+    pushInteger(S, *Result, Call->getType());
+    return true;
+  }
+
   if (Call->getArg(0)->HasSideEffects(ASTCtx)) {
     // "If there are any side effects in them, it returns (size_t) -1
     // for type 0 or 1 and (size_t) 0 for type 2 or 3."
-    pushInteger(S, Kind <= 1 ? -1 : 0, Call->getType());
+    pushInteger(S, Kind <= 1 ? (size_t)-1 : (size_t)0, Call->getType());
     return true;
   }
 
-  if (auto Result = evaluateBuiltinObjectSize(ASTCtx, Kind, Ptr)) {
-    pushInteger(S, *Result, Call->getType());
+  switch (S.EvalMode) {
+  case EvaluationMode::ConstantExpression:
+  case EvaluationMode::ConstantFold:
+  case EvaluationMode::IgnoreSideEffects:
+    // Leave it to IR generation.
+    return Invalid(S, OpPC);
+  case EvaluationMode::ConstantExpressionUnevaluated:
+    // Reduce it to a constant now.
+    pushInteger(S, ((Kind & 2u) ? (size_t)0 : (size_t)-1), Call->getType());
     return true;
   }
+
   return false;
 }
 
@@ -5448,8 +5253,11 @@ bool InterpretBuiltin(InterpState &S, CodePtr OpPC, 
const CallExpr *Call,
     return interp__builtin_memchr(S, OpPC, Call, BuiltinID);
 
   case Builtin::BI__builtin_object_size:
+    return interp__builtin_object_size(S, OpPC, Frame, Call,
+                                       /*IsDynamic=*/false);
   case Builtin::BI__builtin_dynamic_object_size:
-    return interp__builtin_object_size(S, OpPC, Frame, Call);
+    return interp__builtin_object_size(S, OpPC, Frame, Call,
+                                       /*IsDynamic=*/true);
 
   case Builtin::BI__builtin_is_within_lifetime:
     return interp__builtin_is_within_lifetime(S, OpPC, Call);
diff --git a/clang/lib/AST/ByteCode/InterpBuiltinObjectSize.cpp 
b/clang/lib/AST/ByteCode/InterpBuiltinObjectSize.cpp
new file mode 100644
index 0000000000000..2e115e432396b
--- /dev/null
+++ b/clang/lib/AST/ByteCode/InterpBuiltinObjectSize.cpp
@@ -0,0 +1,544 @@
+//===------------- InterpBuiltinObjectSize.cpp ------------------*- C++ 
-*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM 
Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// Implementation of the frontend part of the __builtin_object_size and
+// __builtin_dynamic_object_size builtins.
+
+#include "InterpHelpers.h"
+#include "Pointer.h"
+#include "Record.h"
+#include "clang/AST/RecordLayout.h"
+
+using namespace clang;
+using namespace clang::interp;
+
+enum : uint8_t {
+  Regular = 1 << 0,
+  IgnoreBaseCasts = 1 << 1,
+  SurroundingArray = 1 << 2,
+};
+
+// Helper to check if a RecordDecl can be passed to
+// ASTContext::getRecordLayout().
+static bool validRecordDecl(const RecordDecl *D) {
+  D = D->getDefinition();
+  return D && !D->isInvalidDecl() && D->isCompleteDefinition();
+}
+
+// Same but for types.
+static bool validType(QualType T) {
+  if (const RecordDecl *RD = T->getAsRecordDecl())
+    return validRecordDecl(RD);
+  return true;
+}
+
+static QualType computeFieldType(const ASTContext &ASTCtx,
+                                 const OpaquePointer &OP,
+                                 unsigned TypeModifier = 0) {
+  QualType CurType = OP.getObjectType();
+
+  unsigned Drop = 0;
+  if (TypeModifier & IgnoreBaseCasts && OP.PathLength != 0 &&
+      OP.path().back().Kind == PointerPathEntry::Base)
+    Drop = 1;
+
+  if (TypeModifier & SurroundingArray && OP.PathLength != 0 &&
+      OP.path().back().Kind == PointerPathEntry::Array)
+    Drop = 1;
+
+  for (const PointerPathEntry &Entry : OP.path().drop_back(Drop)) {
+    switch (Entry.Kind) {
+    case PointerPathEntry::Base:
+      CurType = ASTCtx.getCanonicalTagType(Entry.RD.getPointer());
+      break;
+    case PointerPathEntry::Field:
+      CurType = Entry.FD->getType();
+      break;
+    case PointerPathEntry::Array:
+    case PointerPathEntry::NegativeArray:
+      if (!CurType->isArrayType())
+        continue;
+      CurType = CurType->getAsArrayTypeUnsafe()->getElementType();
+    }
+  }
+
+  return CurType;
+}
+
+static std::optional<unsigned> computeFullDescSize(const ASTContext &ASTCtx,
+                                                   const Descriptor *Desc) {
+  if (Desc->isPrimitive() || Desc->isArray()) {
+    QualType T = Desc->getType();
+    if (!validType(T))
+      return std::nullopt;
+    return ASTCtx.getTypeSizeInChars(T).getQuantity();
+  }
+
+  if (Desc->isRecord()) {
+    // Can't use Descriptor::getType() as that may return a pointer type. Look
+    // at the decl directly.
+
+    const RecordDecl *RD = Desc->ElemRecord->getDecl();
+    if (!validRecordDecl(RD))
+      return std::nullopt;
+
+    return ASTCtx.getTypeSizeInChars(ASTCtx.getCanonicalTagType(RD))
+        .getQuantity();
+  }
+
+  return std::nullopt;
+}
+
+/// Compute the byte offset of \p Ptr in the full declaration.
+static unsigned computePointerOffset(const ASTContext &ASTCtx,
+                                     const Pointer &Ptr) {
+  return Ptr.computeLayoutOffset(ASTCtx).value_or(0);
+}
+
+/// Does Ptr point to the last subobject?
+static bool pointsToLastObject(const Pointer &Ptr) {
+  Pointer P = Ptr;
+  while (!P.isRoot()) {
+
+    if (P.isArrayElement()) {
+      P = P.expand().getArray();
+      continue;
+    }
+    if (P.isBaseClass()) {
+      if (P.getRecord()->getNumFields() > 0)
+        return false;
+      P = P.getBase();
+      continue;
+    }
+
+    Pointer Base = P.getBase();
+    if (const Record *R = Base.getRecord()) {
+      assert(P.getField());
+      if (P.getField()->getFieldIndex() != R->getNumFields() - 1)
+        return false;
+    }
+    P = Base;
+  }
+
+  return true;
+}
+
+/// Does Ptr point to the last object AND to a flexible array member?
+static bool isUserWritingOffTheEnd(const ASTContext &Ctx, const Pointer &Ptr,
+                                   bool InvalidBase) {
+  auto isFlexibleArrayMember = [&](const Descriptor *FieldDesc) {
+    using FAMKind = LangOptions::StrictFlexArraysLevelKind;
+    FAMKind StrictFlexArraysLevel =
+        Ctx.getLangOpts().getStrictFlexArraysLevel();
+
+    if (StrictFlexArraysLevel == FAMKind::Default)
+      return true;
+
+    unsigned NumElems = FieldDesc->getNumElems();
+    if (NumElems == 0 && StrictFlexArraysLevel != FAMKind::IncompleteOnly)
+      return true;
+
+    if (NumElems == 1 && StrictFlexArraysLevel == FAMKind::OneZeroOrIncomplete)
+      return true;
+    return false;
+  };
+
+  const Descriptor *FieldDesc = Ptr.getFieldDesc();
+  if (!FieldDesc->isArray())
+    return false;
+
+  return InvalidBase && pointsToLastObject(Ptr) &&
+         isFlexibleArrayMember(FieldDesc);
+}
+
+static bool isUserWritingOffTheEnd(const ASTContext &ASTCtx,
+                                   const OpaquePointer &OP) {
+  if (OP.PathLength == 0)
+    return false;
+
+  QualType CurType = OP.getObjectType();
+  for (unsigned I = 0; I != OP.PathLength; ++I) {
+    const PointerPathEntry &Entry = OP.Path[I];
+    switch (Entry.Kind) {
+    case PointerPathEntry::Base:
+      return false;
+    case PointerPathEntry::Field: {
+      const FieldDecl *FD = OP.Path[I].FD;
+      if (!FD->getParent()->isUnion() &&
+          FD->getFieldIndex() != FD->getParent()->getNumFields() - 1)
+        return false;
+      CurType = FD->getType();
+    } break;
+    case PointerPathEntry::Array: {
+      if (I == OP.PathLength - 1)
+        break;
+
+      if (!CurType->isArrayType())
+        break;
+
+      unsigned Index = OP.Path[I].Index;
+      const ArrayType *AT = CurType->getAsArrayTypeUnsafe();
+      assert(AT);
+      if (const auto *CAT = dyn_cast<ConstantArrayType>(AT)) {
+        if (Index != CAT->getLimitedSize() - 1)
+          return false;
+        CurType = CAT->getElementType();
+      } else {
+        return false;
+      }
+    } break;
+    case PointerPathEntry::NegativeArray:
+      return false;
+    }
+  }
+
+  // We're pointing to the last field in the full object.
+  // CurType is now the most derived type.
+  if (!CurType->isArrayType())
+    return false;
+
+  if (isa<IncompleteArrayType>(CurType))
+    return true;
+
+  const auto *CAT = dyn_cast<ConstantArrayType>(CurType);
+  if (!CAT)
+    return false;
+
+  using FAMKind = LangOptions::StrictFlexArraysLevelKind;
+  FAMKind StrictFlexArraysLevel =
+      ASTCtx.getLangOpts().getStrictFlexArraysLevel();
+
+  if (StrictFlexArraysLevel == FAMKind::Default)
+    return true;
+
+  unsigned Size = CAT->getZExtSize();
+  if (Size == 0 && StrictFlexArraysLevel != FAMKind::IncompleteOnly)
+    return true;
+
+  if (Size == 1 && StrictFlexArraysLevel == FAMKind::OneZeroOrIncomplete)
+    return true;
+  return false;
+}
+
+/// Determine the offset of the given pointer. Depending on \c
+/// UseClosestSurroundingVariable, the offset is either relative to the full
+/// object or to the closest surrounding field or array.
+static std::optional<uint64_t>
+computeOpaquePtrOffset(const ASTContext &ASTCtx, const Pointer &Ptr,
+                       bool UseClosestSurroundingVariable,
+                       bool &OffsetIsNegative) {
+  const OpaquePointer &OP = Ptr.asOpaquePointer();
+
+  uint64_t Offset = 0;
+  std::optional<uint64_t> SurroundingArrayOffset;
+  QualType CurType = OP.getObjectType();
+  for (const PointerPathEntry &Entry : OP.path()) {
+    switch (Entry.Kind) {
+    case PointerPathEntry::Base: {
+      const RecordDecl *RD = CurType->getAsRecordDecl();
+      if (!validRecordDecl(RD))
+        return std::nullopt;
+
+      const ASTRecordLayout &Layout = ASTCtx.getASTRecordLayout(RD);
+      Offset += Layout.getBaseClassOffset(Entry.RD.getPointer()).getQuantity();
+
+      CurType = ASTCtx.getCanonicalTagType(Entry.RD.getPointer());
+    } break;
+
+    case PointerPathEntry::Field: {
+      const FieldDecl *FD = Entry.FD;
+      const RecordDecl *RD = FD->getParent();
+      if (!validRecordDecl(RD))
+        return std::nullopt;
+
+      const ASTRecordLayout &Layout = ASTCtx.getASTRecordLayout(RD);
+      Offset +=
+          
ASTCtx.toCharUnitsFromBits(Layout.getFieldOffset(FD->getFieldIndex()))
+              .getQuantity();
+
+      CurType = FD->getType();
+    } break;
+    case PointerPathEntry::Array:
+    case PointerPathEntry::NegativeArray: {
+      bool Add = (Entry.Kind == PointerPathEntry::Array);
+      uint64_t Index = Entry.Index;
+      if (!Add) {
+        // NegativeArray is always > 0.
+        OffsetIsNegative = true;
+      }
+      SurroundingArrayOffset = Offset;
+      if (!CurType->isArrayType()) {
+        if (Add)
+          Offset += Index * ASTCtx.getTypeSizeInChars(CurType).getQuantity();
+        else
+          Offset -= Index * ASTCtx.getTypeSizeInChars(CurType).getQuantity();
+        continue;
+      }
+      const ArrayType *AT = CurType->getAsArrayTypeUnsafe();
+      assert(AT);
+      QualType ElemTy = AT->getElementType();
+      if (!validType(ElemTy) || isa<VariableArrayType>(AT))
+        return std::nullopt;
+      if (Add)
+        Offset += Index * ASTCtx.getTypeSizeInChars(ElemTy).getQuantity();
+      else
+        Offset -= Index * ASTCtx.getTypeSizeInChars(ElemTy).getQuantity();
+      CurType = AT->getElementType();
+    } break;
+    }
+  }
+
+  if (UseClosestSurroundingVariable && SurroundingArrayOffset)
+    return Offset - *SurroundingArrayOffset;
+
+  QualType Ty = CurType.getNonReferenceType();
+
+  if (UseClosestSurroundingVariable &&
+      (Ty->isIncompleteType() || Ty->isFunctionType()))
+    return std::nullopt;
+
+  if (isa<VariableArrayType>(Ty))
+    return std::nullopt;
+
+  if (OP.PathLength == 1 && OP.path().back().Kind == PointerPathEntry::Field &&
+      isa<IncompleteArrayType>(CurType)) {
+    return Offset;
+  }
+
+  if (UseClosestSurroundingVariable)
+    return 0;
+
+  return Offset;
+}
+
+/// Check if the given pointer points to the complete object, i.e. either to 
the
+/// very beginning or after the end (into the flexible array member) of the
+/// object.
+static bool pointsToCompleteObject(const ASTContext &ASTCtx,
+                                   const Pointer &Ptr) {
+  const OpaquePointer &OP = Ptr.asOpaquePointer();
+  if (OP.PathLength == 0)
+    return true;
+
+  QualType FieldType = computeFieldType(ASTCtx, OP);
+  if (OP.isArrayElement())
+    FieldType = OP.getSurroundingArray();
+  return isa<IncompleteArrayType>(FieldType);
+}
+
+static std::optional<unsigned>
+computeOpaqueSize(const ASTContext &ASTCtx, const Pointer &Ptr,
+                  bool UseClosestSurroundingVariable, bool WritingOffTheEnd,
+                  bool DetermineForCompleteObject) {
+  const OpaquePointer &OP = Ptr.asOpaquePointer();
+
+  CharUnits TypeSize;
+  // NOTE: Clang does not consider base casts. GCC does.
+  if (UseClosestSurroundingVariable) {
+    QualType FieldTy =
+        computeFieldType(ASTCtx, OP, SurroundingArray | IgnoreBaseCasts);
+    if (!validType(FieldTy))
+      return std::nullopt;
+    TypeSize = ASTCtx.getTypeSizeInChars(FieldTy);
+  } else {
+    QualType ObjectTy = OP.getObjectType();
+    if (!validType(ObjectTy))
+      return std::nullopt;
+    TypeSize = ASTCtx.getTypeSizeInChars(ObjectTy);
+  }
+
+  // The Flexible array member should only be checked if we're pointing to the
+  // object as a whole, or if we're looking for the whole object size.
+  if (!WritingOffTheEnd && !DetermineForCompleteObject)
+    return TypeSize.getQuantity();
+
+  // Check if we need to add the flexible array member size.
+  const VarDecl *Base = dyn_cast<VarDecl>(OP.Base);
+  if (!Base)
+    return TypeSize.getQuantity();
+
+  // If the base type is an incomplete array type (not a flexible array member
+  // of a struct), and we're looking for the complete object... we can't.
+  if (DetermineForCompleteObject && isa<IncompleteArrayType>(Base->getType()))
+    return std::nullopt;
+
+  if (!Base->getType()->isRecordType())
+    return TypeSize.getQuantity();
+
+  if (!Base->hasInit())
+    return TypeSize.getQuantity();
+  CharUnits FlexibleArraySize = Base->getFlexibleArrayInitChars(ASTCtx);
+  return (TypeSize + FlexibleArraySize).getQuantity();
+}
+
+namespace clang {
+namespace interp {
+
+/// Evaluate __builtin_object_size or __builtin_dynamic_object_size for the
+/// given pointer and Kind.
+///
+/// When computing the final result, the most important variable is
+/// UseClosestSurroundingVariable. If it is true, we will use the field the
+/// pointer points to, or the parent array of the element.
+/// UseClosestSurroundingVariable is true for Kind 1 and 3.
+UnsignedOrNone evaluateBuiltinObjectSize(const ASTContext &ASTCtx,
+                                         unsigned Kind, Pointer &Ptr,
+                                         const Expr *E, bool IsDynamic) {
+  if (Ptr.isZero())
+    return std::nullopt;
+
+  bool InvalidBase = false;
+  if (Ptr.isOpaquePointer()) {
+    bool UseClosestSurroundingVariable = (Kind == 1) || (Kind == 3);
+    const OpaquePointer &OP = Ptr.asOpaquePointer();
+    InvalidBase = OP.Base->getType()->isPointerType();
+    bool DetermineForCompleteObject = pointsToCompleteObject(ASTCtx, Ptr);
+    bool WritingOffTheEnd = isUserWritingOffTheEnd(ASTCtx, OP);
+
+    // Either the size of the full variable (Kind = 0 or 2) or the size of the
+    // closest surrounding variable (Kind = 1 or 3).
+    std::optional<unsigned> FullSize =
+        computeOpaqueSize(ASTCtx, Ptr, UseClosestSurroundingVariable,
+                          WritingOffTheEnd, DetermineForCompleteObject);
+
+    if (!FullSize)
+      return std::nullopt;
+
+    // Similar to the FullSize above, the offset is relative either to the full
+    // variable or to the closest surrounding variable.
+    bool OffsetIsNegative = false;
+    std::optional<uint64_t> Offset = computeOpaquePtrOffset(
+        ASTCtx, Ptr, UseClosestSurroundingVariable, OffsetIsNegative);
+
+    if (!Offset)
+      return std::nullopt;
+
+    if (OffsetIsNegative)
+      return 0u;
+
+    // For __builtin_dynamic_object_size on a counted_by-annotated flexible
+    // array member, defer to IR generation (emitCountedBySize in CGBuiltin):
+    // its runtime computation uses the live 'count' field and is more accurate
+    // than the layout/initializer-derived size we'd produce here. Use the same
+    // findStructFieldAccess form-recognition CGBuiltin does, so we refuse to
+    // fold on exactly the shapes that path handles (and, importantly, *not*
+    // on '&af.fam' which designates the array-as-a-whole and stays on the
+    // layout-derived path to match GCC).
+    if (IsDynamic) {
+      const auto *ME =
+          dyn_cast_if_present<MemberExpr>(findStructFieldAccess(E));
+      const auto *FD = ME ? dyn_cast<FieldDecl>(ME->getMemberDecl()) : nullptr;
+      if (FD && FD->getType()->isCountAttributedType())
+        return std::nullopt;
+    }
+
+    if (!UseClosestSurroundingVariable || DetermineForCompleteObject) {
+      // Kind=3 wants a lower bound, so we can't fall back to this.
+      if (Kind == 3 && !DetermineForCompleteObject)
+        return std::nullopt;
+
+      if (InvalidBase)
+        return std::nullopt;
+
+      QualType ObjectTy = OP.getObjectType();
+      if (ObjectTy->isIncompleteType() || isa<VariableArrayType>(ObjectTy) ||
+          ObjectTy->isFunctionType())
+        return std::nullopt;
+    }
+
+    *Offset += Ptr.getByteOffset();
+
+    if (*Offset > *FullSize)
+      return 0u;
+
+    if (Kind == 1 && InvalidBase && WritingOffTheEnd)
+      return std::nullopt;
+
+    assert(*Offset <= *FullSize);
+    return static_cast<unsigned>(*FullSize - *Offset);
+  }
+
+  // 
----------------------------------------------------------------------------------------------------
+
+  if (Ptr.isDummy() && Ptr.getType()->isPointerType())
+    return std::nullopt;
+
+  if (!Ptr.isBlockPointer())
+    return std::nullopt;
+
+  if (Ptr.isDummy()) {
+    if (const VarDecl *VD = Ptr.getRootVarDecl();
+        VD && VD->getType()->isPointerType())
+      InvalidBase = true;
+  }
+
+  bool UseFieldDesc = (Kind & 1u);
+  bool ReportMinimum = (Kind & 2u);
+
+  // According to the GCC documentation, we want the size of the subobject
+  // denoted by the pointer. But that's not quite right -- what we actually
+  // want is the size of the immediately-enclosing array, if there is one.
+  if (Ptr.isArrayElement())
+    Ptr = Ptr.expand();
+
+  bool DetermineForCompleteObject = Ptr.getFieldDesc() == Ptr.getDeclDesc();
+  const Descriptor *DeclDesc = Ptr.getDeclDesc();
+  assert(DeclDesc);
+
+  if (!UseFieldDesc || DetermineForCompleteObject) {
+    // Can't read beyond the pointer decl desc.
+    if (!ReportMinimum && DeclDesc->getDataType(ASTCtx)->isPointerType())
+      return std::nullopt;
+
+    if (InvalidBase)
+      return std::nullopt;
+  } else {
+    if (isUserWritingOffTheEnd(ASTCtx, Ptr, InvalidBase)) {
+      // If we cannot determine the size of the initial allocation, then we
+      // can't given an accurate upper-bound. However, we are still able to 
give
+      // conservative lower-bounds for Type=3.
+      if (Kind == 1)
+        return std::nullopt;
+    }
+  }
+
+  // The "closest surrounding subobject" is NOT a base class,
+  // so strip the base class casts.
+  if (UseFieldDesc && Ptr.isBaseClass())
+    Ptr = Ptr.stripBaseCasts();
+
+  const Descriptor *Desc = UseFieldDesc ? Ptr.getFieldDesc() : DeclDesc;
+  assert(Desc);
+
+  std::optional<unsigned> FullSize = computeFullDescSize(ASTCtx, Desc);
+  if (!FullSize)
+    return std::nullopt;
+
+  unsigned ByteOffset;
+  if (UseFieldDesc) {
+    if (Ptr.isBaseClass()) {
+      assert(computePointerOffset(ASTCtx, Ptr.getBase()) <=
+             computePointerOffset(ASTCtx, Ptr));
+      ByteOffset = computePointerOffset(ASTCtx, Ptr.getBase()) -
+                   computePointerOffset(ASTCtx, Ptr);
+    } else {
+      if (Ptr.inArray())
+        ByteOffset =
+            computePointerOffset(ASTCtx, Ptr) -
+            computePointerOffset(ASTCtx, Ptr.expand().atIndex(0).narrow());
+      else
+        ByteOffset = 0;
+    }
+  } else
+    ByteOffset = computePointerOffset(ASTCtx, Ptr);
+
+  assert(ByteOffset <= *FullSize);
+  return *FullSize - ByteOffset;
+}
+} // namespace interp
+} // namespace clang
diff --git a/clang/lib/AST/ByteCode/InterpHelpers.h 
b/clang/lib/AST/ByteCode/InterpHelpers.h
index 1df570ac971c4..bfe57349c31ac 100644
--- a/clang/lib/AST/ByteCode/InterpHelpers.h
+++ b/clang/lib/AST/ByteCode/InterpHelpers.h
@@ -81,7 +81,8 @@ bool CheckNewDeleteForms(InterpState &S, CodePtr OpPC,
 bool DoMemcpy(InterpState &S, CodePtr OpPC, const Pointer &Src, Pointer &Dest);
 
 UnsignedOrNone evaluateBuiltinObjectSize(const ASTContext &ASTCtx,
-                                         unsigned Kind, Pointer &Ptr);
+                                         unsigned Kind, Pointer &Ptr,
+                                         const Expr *E, bool IsDynamic = 
false);
 
 template <typename T>
 bool handleOverflow(InterpState &S, CodePtr OpPC, const T &SrcValue) {
diff --git a/clang/lib/AST/ByteCode/InterpState.h 
b/clang/lib/AST/ByteCode/InterpState.h
index 050fa4c77cd2f..b4d3203c34c46 100644
--- a/clang/lib/AST/ByteCode/InterpState.h
+++ b/clang/lib/AST/ByteCode/InterpState.h
@@ -127,6 +127,27 @@ class InterpState final : public State, public 
SourceMapper {
     return reinterpret_cast<const CXXRecordDecl **>(
         this->allocate(Length * sizeof(CXXRecordDecl *)));
   }
+  PointerPathEntry *allocPointerPath(unsigned Length,
+                                     const PointerPathEntry *OldPP) {
+    assert(Length != 0);
+    auto *PP = reinterpret_cast<PointerPathEntry *>(
+        this->allocate(Length * sizeof(PointerPathEntry)));
+    if (OldPP)
+      std::memcpy(PP, OldPP, sizeof(PointerPathEntry) * Length);
+    return PP;
+  }
+  /// Allocate a new pointer path of Length \c NewLength.
+  /// NewLength - 1 elements are copied form \c OldPP.
+  PointerPathEntry *extendPointerPath(unsigned NewLength,
+                                      const PointerPathEntry *OldPP,
+                                      PointerPathEntry NewEntry) {
+    auto *PP = reinterpret_cast<PointerPathEntry *>(
+        this->allocate(NewLength * sizeof(PointerPathEntry)));
+    if (OldPP)
+      std::memcpy(PP, OldPP, sizeof(PointerPathEntry) * (NewLength - 1));
+    PP[NewLength - 1] = NewEntry;
+    return PP;
+  }
 
   /// Note that a step has been executed. If there are no more steps remaining,
   /// diagnoses and returns \c false.
diff --git a/clang/lib/AST/ByteCode/Opcodes.td 
b/clang/lib/AST/ByteCode/Opcodes.td
index 74d3cc84e06ab..ba1f0e50b7074 100644
--- a/clang/lib/AST/ByteCode/Opcodes.td
+++ b/clang/lib/AST/ByteCode/Opcodes.td
@@ -564,6 +564,7 @@ class LoadOpcode : Opcode {
 def Load : LoadOpcode {}
 // [Pointer] -> [Value]
 def LoadPop : LoadOpcode {}
+def LoadPopL : Opcode {}
 
 class StoreOpcode : Opcode {
   let Types = [AllTypeClass];
@@ -617,6 +618,11 @@ def AddOffset : Opcode {
   let Types = [IntegralTypeClass];
   let HasGroup = 1;
 }
+
+def GetOpaquePtr : SuccessOpcode {
+  let Args = [ArgValueDecl, ArgBool];
+}
+
 // [Pointer, Integral] -> [Pointer]
 def SubOffset : Opcode {
   let Types = [IntegralTypeClass];
diff --git a/clang/lib/AST/ByteCode/Pointer.cpp 
b/clang/lib/AST/ByteCode/Pointer.cpp
index 4f36d20b352cb..b5ff55d757579 100644
--- a/clang/lib/AST/ByteCode/Pointer.cpp
+++ b/clang/lib/AST/ByteCode/Pointer.cpp
@@ -25,6 +25,19 @@
 using namespace clang;
 using namespace clang::interp;
 
+// Helper to check if a RecordDecl can be passed to
+// ASTContext::getRecordLayout().
+static bool validRecordDecl(const RecordDecl *D) {
+  D = D->getDefinition();
+  return D && !D->isInvalidDecl() && D->isCompleteDefinition();
+}
+// Same but for types.
+static bool validType(QualType T) {
+  if (const RecordDecl *RD = T->getAsRecordDecl())
+    return validRecordDecl(RD);
+  return true;
+}
+
 Pointer::Pointer(Block *Pointee)
     : Pointer(Pointee, Pointee->getDescriptor()->getMetadataSize(),
               Pointee->getDescriptor()->getMetadataSize()) {}
@@ -59,6 +72,9 @@ Pointer::Pointer(const Pointer &P)
   case Storage::Typeid:
     Typeid = P.Typeid;
     break;
+  case Storage::Opaque:
+    Opaque = P.Opaque;
+    break;
   }
 }
 
@@ -78,6 +94,9 @@ Pointer::Pointer(Pointer &&P) : Offset(P.Offset), 
StorageKind(P.StorageKind) {
   case Storage::Typeid:
     Typeid = P.Typeid;
     break;
+  case Storage::Opaque:
+    Opaque = P.Opaque;
+    break;
   }
 }
 
@@ -127,6 +146,10 @@ Pointer &Pointer::operator=(const Pointer &P) {
     break;
   case Storage::Typeid:
     Typeid = P.Typeid;
+    break;
+  case Storage::Opaque:
+    Opaque = P.Opaque;
+    break;
   }
   return *this;
 }
@@ -166,6 +189,10 @@ Pointer &Pointer::operator=(Pointer &&P) {
     break;
   case Storage::Typeid:
     Typeid = P.Typeid;
+    break;
+  case Storage::Opaque:
+    Opaque = P.Opaque;
+    break;
   }
   return *this;
 }
@@ -201,6 +228,9 @@ APValue Pointer::toAPValue(const ASTContext &ASTCtx) const {
                    CharUnits::Zero(), {},
                    /*OnePastTheEnd=*/false, /*IsNull=*/false);
   } break;
+  case Storage::Opaque:
+    return APValue(APValue::LValueBase(Opaque.Base), CharUnits::Zero(), Path,
+                   /*IsOnePastEnd=*/Opaque.isOnePastEnd(), 
/*IsNullPtr=*/false);
   }
 
   assert(isBlockPointer());
@@ -225,7 +255,7 @@ APValue Pointer::toAPValue(const ASTContext &ASTCtx) const {
   auto getFieldOffset = [&](const FieldDecl *FD) -> CharUnits {
     // This shouldn't happen, but if it does, don't crash inside
     // getASTRecordLayout.
-    if (FD->getParent()->isInvalidDecl())
+    if (!validRecordDecl(FD->getParent()))
       return CharUnits::Zero();
     const ASTRecordLayout &Layout = ASTCtx.getASTRecordLayout(FD->getParent());
     unsigned FieldIndex = FD->getFieldIndex();
@@ -353,6 +383,12 @@ void Pointer::print(llvm::raw_ostream &OS) const {
     OS << "(Typeid) { " << (const void *)asTypeidPointer().TypePtr << ", "
        << (const void *)asTypeidPointer().TypeInfoType << " + " << Offset
        << "}";
+    break;
+  case Storage::Opaque:
+    OS << "(Opaque) { Base: " << Opaque.Base << ", "
+       << Opaque.FieldType.getPointer() << " Length: " << Opaque.PathLength
+       << ". PastEnd: " << Opaque.isOnePastEnd();
+    OS << "} + " << Offset;
   }
 }
 
@@ -377,14 +413,13 @@ Pointer::computeOffsetForComparison(const ASTContext 
&ASTCtx) const {
     return getIntegerRepresentation();
   case Storage::Typeid:
     return reinterpret_cast<uintptr_t>(asTypeidPointer().TypePtr) + Offset;
+  case Storage::Opaque:
+    return reinterpret_cast<uintptr_t>(asOpaquePointer().Base) + Offset;
   }
 
   auto getTypeSize = [&](QualType T) -> std::optional<size_t> {
-    if (const RecordType *RT = T->getAs<RecordType>()) {
-      // We cannot get the type size of a forward declaration.
-      if (!RT->getDecl()->getDefinition())
-        return std::nullopt;
-    }
+    if (!validType(T))
+      return std::nullopt;
     return ASTCtx.getTypeSizeInChars(T).getQuantity();
   };
 
@@ -455,14 +490,13 @@ Pointer::computeLayoutOffset(const ASTContext &ASTCtx) 
const {
     return getIntegerRepresentation();
   case Storage::Typeid:
     return reinterpret_cast<uintptr_t>(asTypeidPointer().TypePtr) + Offset;
+  case Storage::Opaque:
+    return Opaque.computeLayoutOffset(ASTCtx);
   }
 
   auto getTypeSize = [&](QualType T) -> std::optional<size_t> {
-    if (const RecordType *RT = T->getAs<RecordType>()) {
-      // We cannot get the type size of a forward declaration.
-      if (!RT->getDecl()->getDefinition())
-        return std::nullopt;
-    }
+    if (!validType(T))
+      return std::nullopt;
     return ASTCtx.getTypeSizeInChars(T).getQuantity();
   };
 
@@ -1142,6 +1176,8 @@ std::optional<APValue> Pointer::toRValue(const Context 
&Ctx,
 const VarDecl *Pointer::getRootVarDecl() const {
   if (isBlockPointer())
     return getDeclDesc()->asVarDecl();
+  if (isOpaquePointer())
+    return dyn_cast<VarDecl>(Opaque.Base);
   return nullptr;
 }
 
@@ -1202,3 +1238,172 @@ IntPointer IntPointer::baseCast(const interp::Context 
&Ctx,
                                               std::nullopt, RD, false);
   return {T.getTypePtr(), Value + BaseLayoutOffset.getQuantity()};
 }
+
+std::optional<size_t>
+OpaquePointer::computeLayoutOffset(const ASTContext &ASTCtx) const {
+  size_t Offset = 0;
+  QualType CurType = getObjectType();
+  for (const PointerPathEntry &Entry : path()) {
+    switch (Entry.Kind) {
+    case PointerPathEntry::Base: {
+      const RecordDecl *RD = CurType->getAsRecordDecl();
+      if (!validRecordDecl(RD))
+        return std::nullopt;
+
+      const ASTRecordLayout &Layout = ASTCtx.getASTRecordLayout(RD);
+      Offset += Layout.getBaseClassOffset(Entry.RD.getPointer()).getQuantity();
+
+      CurType = ASTCtx.getCanonicalTagType(Entry.RD.getPointer());
+    } break;
+
+    case PointerPathEntry::Field: {
+      const FieldDecl *FD = Entry.FD;
+      const RecordDecl *RD = FD->getParent();
+      if (!validRecordDecl(RD))
+        return std::nullopt;
+
+      const ASTRecordLayout &Layout = ASTCtx.getASTRecordLayout(RD);
+      Offset +=
+          
ASTCtx.toCharUnitsFromBits(Layout.getFieldOffset(FD->getFieldIndex()))
+              .getQuantity();
+
+      CurType = FD->getType();
+    } break;
+    case PointerPathEntry::Array:
+    case PointerPathEntry::NegativeArray: {
+      bool Add = (Entry.Kind == PointerPathEntry::Array);
+      uint64_t Index = Entry.Index;
+      if (!CurType->isArrayType()) {
+        if (Add)
+          Offset += Index * ASTCtx.getTypeSizeInChars(CurType).getQuantity();
+        else
+          Offset -= Index * ASTCtx.getTypeSizeInChars(CurType).getQuantity();
+        continue;
+      }
+      const ArrayType *AT = CurType->getAsArrayTypeUnsafe();
+      assert(AT);
+      QualType ElemTy = AT->getElementType();
+      if (!validType(ElemTy) || isa<VariableArrayType>(AT))
+        return std::nullopt;
+      if (Add)
+        Offset += Index * ASTCtx.getTypeSizeInChars(ElemTy).getQuantity();
+      else
+        Offset -= Index * ASTCtx.getTypeSizeInChars(ElemTy).getQuantity();
+      CurType = AT->getElementType();
+    } break;
+    }
+  }
+
+  return Offset;
+}
+
+QualType OpaquePointer::getSurroundingArray() const {
+  if (PathLength == 0)
+    return getObjectType();
+  if (Path[PathLength - 1].Kind != PointerPathEntry::Array)
+    return getFieldType();
+
+  assert(Path[PathLength - 1].Kind == PointerPathEntry::Array);
+  assert(isArrayElement());
+
+  QualType CurType = getObjectType();
+  for (const PointerPathEntry &Entry : path().drop_back(1)) {
+    switch (Entry.Kind) {
+    case PointerPathEntry::Base:
+      CurType = Entry.RD.getPointer()->getASTContext().getCanonicalTagType(
+          Entry.RD.getPointer());
+      break;
+    case PointerPathEntry::Field:
+      CurType = Entry.FD->getType();
+      break;
+    case PointerPathEntry::Array:
+    case PointerPathEntry::NegativeArray:
+      if (!CurType->isArrayType())
+        break;
+      CurType = CurType->getAsArrayTypeUnsafe()->getElementType();
+    }
+  }
+  return CurType;
+}
+
+/// Check if the pointer has offset 0.
+// As an optimization, don't actually compute the offset.
+bool OpaquePointer::isRoot() const {
+  QualType CurType = getObjectType();
+  for (const PointerPathEntry &Entry : path()) {
+    switch (Entry.Kind) {
+    case PointerPathEntry::Base:
+      if (Entry.RD.getInt())
+        return false;
+      CurType = Entry.RD.getPointer()->getASTContext().getCanonicalTagType(
+          Entry.RD.getPointer());
+      break;
+    case PointerPathEntry::Field:
+      if (!Entry.FD->getParent()->isUnion() && Entry.FD->getFieldIndex() != 0)
+        return false;
+      CurType = Entry.FD->getType();
+      break;
+    case PointerPathEntry::Array:
+      if (Entry.Index != 0)
+        return false;
+      if (!CurType->isArrayType())
+        continue;
+      CurType = CurType->getAsArrayTypeUnsafe()->getElementType();
+      break;
+    case PointerPathEntry::NegativeArray:
+      return false;
+    }
+  }
+  return true;
+}
+
+bool OpaquePointer::isUnknownSizeArray() const {
+  QualType FieldType = getFieldType();
+
+  if (isArrayElement())
+    FieldType = getSurroundingArray();
+
+  bool Result = false;
+  // If the field type is an IncompleteArrayType, we still need to check the
+  // base to see if this array is a flexible array member _and_ has actually
+  // been initialized by data we know the size of.
+  if (isa<IncompleteArrayType>(FieldType)) {
+    const VarDecl *Base = cast<VarDecl>(this->Base);
+    if (!Base || !Base->getType()->isRecordType() || !Base->hasInit())
+      Result = true;
+    else
+      Result = !Base->hasFlexibleArrayInit(Base->getASTContext());
+  } else if (isa<VariableArrayType>(FieldType))
+    Result = true;
+
+  return Result;
+}
+
+/// This is used in Pointer::isOnePastEnd(). We cannot read from such pointers.
+/// We can of course never read from opaque pointers anyway but we diagnose
+/// one-past-the-end pointers differently.
+///
+/// In contrast, OpaquePointer::isOnePastEnd() only uses the past-end bit. That
+/// is used for the APValue conversion.
+bool OpaquePointer::isOnePastEndOrElementPastEnd() const {
+  if (isOnePastEnd())
+    return true;
+
+  if (PathLength == 0)
+    return false;
+
+  if (Path[PathLength - 1].Kind != PointerPathEntry::Array)
+    return false;
+
+  QualType ArrTy = getSurroundingArray();
+  if (!ArrTy->isArrayType())
+    return false;
+  // FIXME: Flexible array members?
+  if (const auto *CAT =
+          dyn_cast<ConstantArrayType>(ArrTy->getAsArrayTypeUnsafe())) {
+    if (Path[PathLength - 1].Index >= CAT->getZExtSize())
+      return true;
+  }
+
+  return false;
+}
diff --git a/clang/lib/AST/ByteCode/Pointer.h b/clang/lib/AST/ByteCode/Pointer.h
index a11031fb7a240..bddd111b0829b 100644
--- a/clang/lib/AST/ByteCode/Pointer.h
+++ b/clang/lib/AST/ByteCode/Pointer.h
@@ -370,7 +370,111 @@ struct TypeidPointer {
   const Type *TypeInfoType;
 };
 
-enum class Storage { Int, Block, Fn, Typeid };
+struct PointerPathEntry {
+  enum { Base, Field, Array, NegativeArray } Kind;
+  union {
+    uint64_t Index;
+    const FieldDecl *FD;
+    llvm::PointerIntPair<const CXXRecordDecl *, 1, bool> RD = {};
+  };
+
+  static PointerPathEntry base(const CXXRecordDecl *RD, bool Virtual = false) {
+    PointerPathEntry E;
+    E.Kind = Base;
+    E.RD = {RD, Virtual};
+    return E;
+  }
+
+  static PointerPathEntry array(int64_t Index) {
+    PointerPathEntry E;
+    E.Kind = Array;
+    E.Index = Index;
+    return E;
+  }
+
+  static PointerPathEntry negativeArray(int64_t Index) {
+    PointerPathEntry E;
+    E.Kind = NegativeArray;
+    E.Index = Index;
+    return E;
+  }
+
+  static PointerPathEntry field(const FieldDecl *FD) {
+    PointerPathEntry E;
+    E.Kind = Field;
+    E.FD = FD;
+    return E;
+  }
+};
+
+struct OpaquePointer {
+  const ValueDecl *Base = nullptr;
+  // FieldType and IsOnePastEnd/IsConstexprUnknown bits.
+  llvm::PointerIntPair<const Type *, 2, unsigned> FieldType = {};
+  const PointerPathEntry *Path = nullptr;
+  unsigned PathLength = 0;
+
+  ArrayRef<PointerPathEntry> path() const { return ArrayRef(Path, PathLength); 
}
+
+  OpaquePointer
+  withFieldType(const Type *FieldTy,
+                std::optional<bool> PastEnd = std::nullopt) const {
+    unsigned NewBitFieldValue = FieldType.getInt();
+    if (PastEnd)
+      NewBitFieldValue =
+          (isConstexprUnknown() ? 2u : 0u) + static_cast<unsigned>(*PastEnd);
+    return OpaquePointer{Base, {FieldTy, NewBitFieldValue}, Path, PathLength};
+  }
+
+  OpaquePointer withPath(const PointerPathEntry *Path, unsigned PathLength,
+                         const Type *FieldTy,
+                         std::optional<bool> PastEnd = std::nullopt) const {
+    unsigned NewBitFieldValue = FieldType.getInt();
+    if (PastEnd)
+      NewBitFieldValue =
+          (isConstexprUnknown() ? 2u : 0u) + static_cast<unsigned>(*PastEnd);
+    return OpaquePointer{Base, {FieldTy, NewBitFieldValue}, Path, PathLength};
+  }
+
+  OpaquePointer withPastEnd(bool PastEnd) const {
+    return OpaquePointer{Base,
+                         {FieldType.getPointer(),
+                          FieldType.getInt() | static_cast<unsigned>(PastEnd)},
+                         Path,
+                         PathLength};
+  }
+
+  QualType getObjectType() const {
+    QualType T = Base->getType();
+    if (T->isPointerOrReferenceType())
+      return T->getPointeeType();
+    return T;
+  }
+
+  QualType getFieldType() const {
+    if (FieldType.getPointer()->isPointerOrReferenceType())
+      return FieldType.getPointer()->getPointeeType();
+    return QualType(FieldType.getPointer(), 0);
+  }
+
+  bool isArrayElement() const {
+    return PathLength != 0 &&
+           Path[PathLength - 1].Kind == PointerPathEntry::Array;
+  }
+
+  std::optional<size_t> computeLayoutOffset(const ASTContext &ASTCtx) const;
+  /// If this is pointing to an array element, return the array.
+  QualType getSurroundingArray() const;
+
+  bool isOnePastEnd() const { return FieldType.getInt() & 1u; }
+  bool isOnePastEndOrElementPastEnd() const;
+  bool isConstexprUnknown() const { return FieldType.getInt() & 2u; }
+  bool isUnknownSizeArray() const;
+  bool isRoot() const;
+};
+struct OpaqueTag {};
+
+enum class Storage { Int, Block, Fn, Typeid, Opaque };
 
 /// A pointer to a memory block, live or dead.
 ///
@@ -420,6 +524,16 @@ class Pointer {
     Typeid.TypePtr = TypePtr;
     Typeid.TypeInfoType = TypeInfoType;
   }
+  Pointer(const ValueDecl *Base, bool ConstexprUnknown = false)
+      : Offset(0), StorageKind(Storage::Opaque) {
+    Opaque.Base = Base;
+    Opaque.FieldType = {Base->getType().getTypePtr(),
+                        ConstexprUnknown ? 2u : 0u};
+    Opaque.Path = nullptr;
+    Opaque.PathLength = 0;
+  }
+  Pointer(OpaquePointer OP, uint64_t Offset = 0)
+      : Offset(Offset), StorageKind(Storage::Opaque), Opaque(OP) {}
 
   Pointer(Block *Pointee, unsigned Base, uint64_t Offset);
   explicit Pointer(PtrView V) : Pointer(V.Pointee, V.Base, V.Offset) {}
@@ -514,6 +628,7 @@ class Pointer {
     case Storage::Fn:
       return !Fn.Func;
     case Storage::Typeid:
+    case Storage::Opaque:
       return false;
     }
     llvm_unreachable("Unknown clang::interp::Storage enum");
@@ -562,7 +677,7 @@ class Pointer {
 
   /// Accessors for information about the innermost field.
   const Descriptor *getFieldDesc() const {
-    if (isIntegralPointer())
+    if (!isBlockPointer())
       return nullptr;
 
     if (isRoot())
@@ -581,6 +696,8 @@ class Pointer {
       return Fn.Func->getDecl()->getType();
     case Storage::Typeid:
       return QualType(Typeid.TypeInfoType, 0);
+    case Storage::Opaque:
+      return Opaque.getFieldType();
     }
     llvm_unreachable("Unhandled StorageKind");
   }
@@ -634,9 +751,11 @@ class Pointer {
   }
   /// Checks if the structure is an array of unknown size.
   bool isUnknownSizeArray() const {
-    if (!isBlockPointer())
-      return false;
-    return getFieldDesc()->isUnknownSizeArray();
+    if (isBlockPointer())
+      return getFieldDesc()->isUnknownSizeArray();
+    if (isOpaquePointer())
+      return Opaque.isUnknownSizeArray();
+    return false;
   }
   /// Checks if the pointer points to an array.
   bool isArrayElement() const {
@@ -647,9 +766,13 @@ class Pointer {
   }
   /// Pointer points directly to a block.
   bool isRoot() const {
-    if (isZero() || !isBlockPointer())
+    if (isZero())
       return true;
-    return view().isRoot();
+    if (isBlockPointer())
+      return view().isRoot();
+    if (isOpaquePointer())
+      return Opaque.isRoot();
+    return true;
   }
   /// If this pointer has an InlineDescriptor we can use to initialize.
   bool canBeInitialized() const {
@@ -675,11 +798,16 @@ class Pointer {
     assert(isTypeidPointer());
     return Typeid;
   }
+  [[nodiscard]] const OpaquePointer &asOpaquePointer() const {
+    assert(isOpaquePointer());
+    return Opaque;
+  }
 
   bool isBlockPointer() const { return StorageKind == Storage::Block; }
   bool isIntegralPointer() const { return StorageKind == Storage::Int; }
   bool isFunctionPointer() const { return StorageKind == Storage::Fn; }
   bool isTypeidPointer() const { return StorageKind == Storage::Typeid; }
+  bool isOpaquePointer() const { return StorageKind == Storage::Opaque; }
 
   /// Returns the record descriptor of a class.
   const Record *getRecord() const {
@@ -799,6 +927,8 @@ class Pointer {
       return Int.Value + Offset;
     if (isTypeidPointer())
       return reinterpret_cast<uintptr_t>(Typeid.TypePtr) + Offset;
+    if (isOpaquePointer())
+      return Offset;
     if (isOnePastEnd())
       return PtrView::PastEndMark;
     return Offset;
@@ -830,6 +960,9 @@ class Pointer {
 
   /// Checks if the index is one past end.
   bool isOnePastEnd() const {
+    if (isOpaquePointer())
+      return Opaque.isOnePastEndOrElementPastEnd();
+
     if (!isBlockPointer())
       return false;
 
@@ -857,6 +990,8 @@ class Pointer {
   bool isZeroSizeArray() const {
     if (isFunctionPointer())
       return false;
+    if (isOpaquePointer())
+      return false; // FIXME: Can actually happen I think?
     if (const auto *Desc = getFieldDesc())
       return Desc->isZeroSizeArray();
     return false;
@@ -896,9 +1031,11 @@ class Pointer {
   }
 
   bool isConstexprUnknown() const {
-    if (!isBlockPointer())
-      return false;
-    return getDeclDesc()->IsConstexprUnknown;
+    if (isOpaquePointer())
+      return Opaque.isConstexprUnknown();
+    if (isBlockPointer())
+      return getDeclDesc()->IsConstexprUnknown;
+    return false;
   }
 
   /// Whether this block can be read from at all. This is only true for
@@ -1072,6 +1209,7 @@ class Pointer {
     BlockPointer BS;
     FunctionPointer Fn;
     TypeidPointer Typeid;
+    OpaquePointer Opaque;
   };
 };
 
diff --git a/clang/lib/AST/ByteCode/Program.cpp 
b/clang/lib/AST/ByteCode/Program.cpp
index 564d2d8fc422d..373ca544a6546 100644
--- a/clang/lib/AST/ByteCode/Program.cpp
+++ b/clang/lib/AST/ByteCode/Program.cpp
@@ -143,14 +143,16 @@ unsigned Program::getOrCreateDummy(DeclOrExpr D, bool 
IsConstexprUnknown) {
     const auto *VD = D.asValueDecl();
     IsWeak = VD->isWeak();
     QT = VD->getType();
-    if (QT->isPointerOrReferenceType())
+
+    if (QT->isReferenceType())
       QT = QT->getPointeeType();
   }
+
   assert(!QT.isNull());
 
   Descriptor *Desc;
   if (OptPrimType T = Ctx.classify(QT))
-    Desc = createDescriptor(D, *T, /*SourceTy=*/nullptr, std::nullopt,
+    Desc = createDescriptor(D, *T, /*SourceTy=*/QT.getTypePtr(), std::nullopt,
                             /*IsConst=*/QT.isConstQualified());
   else
     Desc = createDescriptor(D, QT.getTypePtr(), std::nullopt,
diff --git a/clang/lib/AST/CMakeLists.txt b/clang/lib/AST/CMakeLists.txt
index e3f74d73f21da..ac3a13a2d155d 100644
--- a/clang/lib/AST/CMakeLists.txt
+++ b/clang/lib/AST/CMakeLists.txt
@@ -78,6 +78,7 @@ add_clang_library(clangAST
   ByteCode/Function.cpp
   ByteCode/InterpBuiltin.cpp
   ByteCode/InterpBuiltinBitCast.cpp
+  ByteCode/InterpBuiltinObjectSize.cpp
   ByteCode/Floating.cpp
   ByteCode/EvaluationResult.cpp
   ByteCode/DynamicAllocator.cpp
diff --git a/clang/lib/AST/ExprConstShared.h b/clang/lib/AST/ExprConstShared.h
index cdf2a5697528e..0eee03dce57ab 100644
--- a/clang/lib/AST/ExprConstShared.h
+++ b/clang/lib/AST/ExprConstShared.h
@@ -110,4 +110,6 @@ std::optional<llvm::APFloat>
 EvalScalarMinMaxFp(const llvm::APFloat &A, const llvm::APFloat &B,
                    std::optional<llvm::APSInt> RoundingMode, bool IsMin);
 
+const Expr *ignorePointerCastsAndParens(const Expr *E);
+
 #endif
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 480d5119a5363..5ea3938ca8bae 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -16508,7 +16508,7 @@ static QualType getObjectType(APValue::LValueBase B) {
 /// Ex. For E = `(short*)((char*)(&foo))`, returns `&foo`
 ///
 /// Always returns an RValue with a pointer representation.
-static const Expr *ignorePointerCastsAndParens(const Expr *E) {
+const Expr *ignorePointerCastsAndParens(const Expr *E) {
   assert(E->isPRValue() && E->getType()->hasPointerRepresentation());
 
   const Expr *NoParens = E->IgnoreParens();
@@ -23006,7 +23006,10 @@ std::optional<uint64_t> 
Expr::tryEvaluateObjectSize(const ASTContext &Ctx,
   Expr::EvalStatus Status;
   EvalInfo Info(Ctx, Status, EvaluationMode::ConstantFold);
   if (Info.EnableNewConstInterp)
-    return Info.Ctx.getInterpContext().tryEvaluateObjectSize(Info, this, Type);
+    return Info.Ctx.getInterpContext().tryEvaluateObjectSize(
+        Info, this, Type,
+        /*IsDynamic=*/false);
+
   return tryEvaluateBuiltinObjectSize(this, Type, Info);
 }
 
diff --git a/clang/test/AST/ByteCode/builtin-object-size-codegen-cxx23.cpp 
b/clang/test/AST/ByteCode/builtin-object-size-codegen-cxx23.cpp
new file mode 100644
index 0000000000000..2f229fff28d11
--- /dev/null
+++ b/clang/test/AST/ByteCode/builtin-object-size-codegen-cxx23.cpp
@@ -0,0 +1,15 @@
+// RUN: %clang_cc1 -std=c++23 -fexperimental-new-constant-interpreter -triple 
x86_64-apple-darwin -emit-llvm -o - %s | FileCheck %s
+// RUN: %clang_cc1 -std=c++23                                         -triple 
x86_64-apple-darwin -emit-llvm -o - %s | FileCheck %s
+
+struct basic_filebuf {
+  char __extbuf_;
+  char __extbuf_min_[8];
+};
+// CHECK-LABEL: @_Z4swapR13basic_filebuf
+void swap(basic_filebuf &__rhs) {
+  int gi;
+  // CHECK: store i32 8
+  gi = __builtin_object_size(__rhs.__extbuf_min_, 0);
+}
+
+
diff --git a/clang/test/AST/ByteCode/builtin-object-size-codegen.c 
b/clang/test/AST/ByteCode/builtin-object-size-codegen.c
index 1b2561a89ebba..445a95f5487b9 100644
--- a/clang/test/AST/ByteCode/builtin-object-size-codegen.c
+++ b/clang/test/AST/ByteCode/builtin-object-size-codegen.c
@@ -45,3 +45,92 @@ void foo2(struct Foo *t) {
 void foo(void *p) {
   int i = __builtin_object_size(&p[2], 3);
 }
+
+struct DynStructVar {
+  char fst[16];
+  char snd[];
+};
+
+static struct DynStructVar D32 = {
+  .fst = {},
+  .snd = { 0, 1, 2, 3, 4, 5, 6 },
+};
+
+// CHECK-LABEL: @test32
+void test32(void) {
+  // CHECK: store i32 23
+  gi = __builtin_object_size(&D32, 0);
+  // CHECK: store i32 23
+  gi = __builtin_object_size(&D32, 1);
+  // CHECK: store i32 23
+  gi = __builtin_object_size(&D32, 2);
+  // CHECK: store i32 23
+  gi = __builtin_object_size(&D32, 3);
+
+  // CHECK: store i32 7
+  gi = __builtin_object_size(&D32.snd[0], 0);
+  // CHECK: store i32 1
+  gi = __builtin_object_size(&D32.snd[6], 0);
+  // CHECK: store i32 0
+  gi = __builtin_object_size(&D32.snd[10], 0);
+}
+
+struct S {
+  char c[7];
+  char k[];
+};
+
+struct S s = {
+  .c = {1,2,3,4,5,6,7},
+  .k = {1,2,3,4,5    }
+};
+
+// CHECK-LABEL: @testflex
+void testflex() {
+  int gi;
+  // CHECK: store i32 5
+  gi = __builtin_object_size(&s.k, 0);
+  // CHECK: call i64 @llvm.objectsize.i64.p0(ptr {{.*}}, i1 false, i1 true, i1 
false)
+  gi = __builtin_object_size(&s.k, 1);
+  // CHECK: store i32 5
+  gi = __builtin_object_size(&s.k, 2);
+  // CHECK: store i32 0
+  gi = __builtin_object_size(&s.k, 3);
+
+  // CHECK: store i32 2
+  gi = __builtin_object_size(&s.k[3], 0);
+  // CHECK: store i32 2
+  gi = __builtin_object_size(&s.k[3], 1);
+  // CHECK: store i32 2
+  gi = __builtin_object_size(&s.k[3], 2);
+  /// The following fails to evaluate in clang but returns 2 in GCC.
+  // store i32 0
+  gi = __builtin_object_size(&s.k[3], 3);
+}
+
+// CHECK-LABEL: @vlas
+void vlas(int size) {
+  char z[size];
+
+  int gi;
+  // CHECK: call i64 @llvm.objectsize.i64.p0(ptr {{.*}}, i1 false, i1 true, i1 
false)
+  gi = __builtin_object_size(z, 0);
+  // CHECK: call i64 @llvm.objectsize.i64.p0(ptr {{.*}}, i1 false, i1 true, i1 
false)
+  gi = __builtin_object_size(z, 1);
+  // CHECK: call i64 @llvm.objectsize.i64.p0(ptr {{.*}}, i1 true, i1 true, i1 
false)
+  gi = __builtin_object_size(z, 2);
+  // CHECK: store i32 0
+  gi = __builtin_object_size(z, 3);
+}
+
+struct hh {
+  char s1[0];
+  char * s2;
+};
+// CHECK-LABEL: @f17
+void f17(void) {
+  struct hh h0;
+  int gi;
+  // CHECK: store i32 8
+  gi = __builtin_object_size(h0.s1, 0);
+}
diff --git a/clang/test/AST/ByteCode/builtin-object-size-codegen.cpp 
b/clang/test/AST/ByteCode/builtin-object-size-codegen.cpp
index 7a7ac26c1b0be..7055d99d75635 100644
--- a/clang/test/AST/ByteCode/builtin-object-size-codegen.cpp
+++ b/clang/test/AST/ByteCode/builtin-object-size-codegen.cpp
@@ -51,6 +51,7 @@ typedef struct {
   double c[0];
   float f;
 } foofoo0_t;
+
 // CHECK-LABEL: @_Z6babar0P9foofoo0_t
 unsigned babar0(foofoo0_t *f) {
   // CHECK: ret i32 0
@@ -127,3 +128,190 @@ void nonPtrParam(C c) {
   gi = __builtin_object_size(&c.bs[0], 2);
 }
 
+
+struct X {
+  char p[7];
+};
+
+struct Y: X {
+  char p[3];
+};
+
+struct F {
+  Y y;
+};
+
+// CHECK-LABEL: @_Z6testXYv
+void testXY() {
+  int gi;
+  Y y;
+
+  // CHECK: store i32 10
+  gi = __builtin_object_size(&y, 0);
+  // CHECK: store i32 10
+  gi = __builtin_object_size(&y, 1);
+  // CHECK: store i32 10
+  gi = __builtin_object_size(&y, 2);
+  // CHECK: store i32 10
+  gi = __builtin_object_size(&y, 3);
+
+  // CHECK: store i32 10
+  gi = __builtin_object_size((X*)&y, 0);
+  // CHECK: store i32 10
+  gi = __builtin_object_size((X*)&y, 1);
+  // CHECK: store i32 10
+  gi = __builtin_object_size((X*)&y, 2);
+  // CHECK: store i32 10
+  gi = __builtin_object_size((X*)&y, 3);
+
+
+  F f;
+  // CHECK: store i32 10
+  gi = __builtin_object_size((X*)&f.y, 0);
+  // CHECK: store i32 10
+  gi = __builtin_object_size((X*)&f.y, 1);
+  // CHECK: store i32 10
+  gi = __builtin_object_size((X*)&f.y, 2);
+  // CHECK: store i32 10
+  gi = __builtin_object_size((X*)&f.y, 3);
+
+
+  // CHECK: store i32 6
+  gi = __builtin_object_size(&((X*)&f.y)->p[4], 0);
+  // CHECK: store i32 3
+  gi = __builtin_object_size(&((X*)&f.y)->p[4], 1);
+  // CHECK: store i32 6
+  gi = __builtin_object_size(&((X*)&f.y)->p[4], 2);
+  // CHECK: store i32 3
+  gi = __builtin_object_size(&((X*)&f.y)->p[4], 3);
+}
+
+// CHECK-LABEL: @_Z7testOPEv
+int s;
+void testOPE() {
+  int gi;
+
+  // CHECK: store i32 4
+  gi = __builtin_object_size(&s, 0);
+  // CHECK: store i32 4
+  gi = __builtin_object_size(&s, 1);
+  // CHECK: store i32 4
+  gi = __builtin_object_size(&s, 2);
+  // CHECK: store i32 4
+  gi = __builtin_object_size(&s, 3);
+
+  // CHECK: store i32 0
+  gi = __builtin_object_size(&s + 1, 0);
+  // CHECK: store i32 0
+  gi = __builtin_object_size(&s + 1, 1);
+  // CHECK: store i32 0
+  gi = __builtin_object_size(&s + 1, 2);
+  // CHECK: store i32 0
+  gi = __builtin_object_size(&s + 1, 3);
+
+  // CHECK: store i32 0
+  gi = __builtin_object_size(&s + 20, 0);
+  // CHECK: store i32 0
+  gi = __builtin_object_size(&s + 20, 1);
+  // CHECK: store i32 0
+  gi = __builtin_object_size(&s + 20, 2);
+  // CHECK: store i32 0
+  gi = __builtin_object_size(&s + 20, 3);
+}
+
+struct K {char p[6]; };
+// CHECK-LABEL: @_Z18testArrayAddOffsetv
+void testArrayAddOffset() {
+  int gi;
+
+  K ks[4];
+  // CHECK: store i32 18
+  gi = __builtin_object_size(ks + 1, 0);
+  // CHECK: store i32 18
+  gi = __builtin_object_size(ks + 1, 1);
+  // CHECK: store i32 18
+  gi = __builtin_object_size(ks + 1, 2);
+  // CHECK: store i32 18
+  gi = __builtin_object_size(ks + 1, 3);
+
+  // CHECK: store i32 18
+  gi = __builtin_object_size(ks + 3 - 2, 0);
+  // CHECK: store i32 18
+  gi = __builtin_object_size(ks + 3 - 2, 1);
+  // CHECK: store i32 18
+  gi = __builtin_object_size(ks + 3 - 2, 2);
+  // CHECK: store i32 18
+  gi = __builtin_object_size(ks + 3 - 2, 3);
+
+  // CHECK: store i32 0
+  gi = __builtin_object_size(ks - 5, 0);
+  // CHECK: store i32 0
+  gi = __builtin_object_size(ks - 5, 1);
+  // CHECK: store i32 0
+  gi = __builtin_object_size(ks - 5, 2);
+  // CHECK: store i32 0
+  gi = __builtin_object_size(ks - 5, 3);
+}
+
+
+struct LoadCommandInfo {
+  char *Ptr;
+  int a;
+  int b;
+};
+
+// CHECK-LABEL: @_Z16testNonConstBasev
+void testNonConstBase() {
+  struct A { char buf[16]; };
+  struct B : A {};
+  struct C { int i; B bs[1]; } *c;
+
+  LoadCommandInfo LC;
+  int gi;
+  // CHECK: call i64 @llvm.objectsize.i64.p0(ptr {{.*}}, i1 false, i1 true, i1 
false)
+  gi = __builtin_object_size(LC.Ptr, 0);
+  // CHECK: call i64 @llvm.objectsize.i64.p0(ptr {{.*}}, i1 false, i1 true, i1 
false)
+  gi = __builtin_object_size(LC.Ptr + 8, 0);
+}
+
+
+struct Ref_struct {
+  int RD, Sib;
+  int *Op;
+};
+
+struct NodeBase {
+  int Next;
+  Ref_struct RefData;
+};
+
+struct NodeAddr {
+  NodeBase *Addr;
+  int Id;
+};
+
+// CHECK-LABEL: @_Z9cloneNode8NodeAddr
+void cloneNode(const NodeAddr B) {
+  NodeBase NA_0;
+  // memcpy(&NA_0, B.Addr, sizeof(NodeBase));
+
+  int gi;
+
+  // CHECK: store i32 24
+  gi = __builtin_object_size(&NA_0, 0);
+  // CHECK: store i32 24
+  gi = __builtin_object_size(&NA_0, 1);
+  // CHECK: store i32 24
+  gi = __builtin_object_size(&NA_0, 2);
+  // CHECK: store i32 24
+  gi = __builtin_object_size(&NA_0, 3);
+
+  // CHECK: call i64 @llvm.objectsize.i64.p0(ptr {{.*}}, i1 false, i1 true, i1 
false)
+  gi = __builtin_object_size(B.Addr, 0);
+  // CHECK: call i64 @llvm.objectsize.i64.p0(ptr {{.*}}, i1 false, i1 true, i1 
false)
+  gi = __builtin_object_size(B.Addr, 1);
+  // CHECK: call i64 @llvm.objectsize.i64.p0(ptr {{.*}}, i1 true, i1 true, i1 
false)
+  gi = __builtin_object_size(B.Addr, 2);
+  // CHECK: store i32 0
+  gi = __builtin_object_size(B.Addr, 3);
+}
diff --git a/clang/test/AST/ByteCode/codegen.c 
b/clang/test/AST/ByteCode/codegen.c
index 7f877bb4240fe..598e8f130ddab 100644
--- a/clang/test/AST/ByteCode/codegen.c
+++ b/clang/test/AST/ByteCode/codegen.c
@@ -27,3 +27,25 @@ int test(void) {
   return i23;
 }
 // CHECK: @test.i23 = internal global i32 4, align 4
+
+union reg
+{
+    unsigned char b[2][20];
+    unsigned short w[2];
+    unsigned int d;
+};
+struct cpu
+{
+    union reg pc;
+};
+extern struct cpu cpu;
+struct svar
+{
+    void *ptr;
+};
+
+// CHECK: @svars2 = {{(dso_local )?}}global [1 x %struct.svar] [%struct.svar { 
ptr getelementptr (i8, ptr @cpu, i64 1) }]
+struct svar svars2[] =
+{
+    { &((cpu.pc).b[0][1]) }
+};
diff --git a/clang/test/AST/ByteCode/enable_if.c 
b/clang/test/AST/ByteCode/enable_if.c
deleted file mode 100644
index 8148db2719449..0000000000000
--- a/clang/test/AST/ByteCode/enable_if.c
+++ /dev/null
@@ -1,202 +0,0 @@
-// RUN: %clang_cc1 -verify=ref,both      %s
-// RUN: %clang_cc1 -verify=expected,both %s 
-fexperimental-new-constant-interpreter
-
-// %clang_cc1 %s -DCODEGEN -emit-llvm -o - | FileCheck %s
-// %clang_cc1 %s -DCODEGEN -emit-llvm -o - 
-fexperimental-new-constant-interpreter | FileCheck %s
-
-/// This is the same file we have in test/Sema/, but there is one test that 
doesn't yet pass with the bytecode interpreter.
-/// TODO: Delete this file and add an appropriate RUN line to the file in 
test/Sema/ instead.
-///
-/// The problem is related to a wrongly computed value in the 
__builtin_object_size implementation.
-
-#define O_CREAT 0x100
-typedef int mode_t;
-typedef unsigned long size_t;
-
-enum { TRUE = 1 };
-
-int open(const char *pathname, int flags) __attribute__((enable_if(!(flags & 
O_CREAT), "must specify mode when using O_CREAT"))) 
__attribute__((overloadable));  // both-note{{candidate disabled: must specify 
mode when using O_CREAT}}
-int open(const char *pathname, int flags, mode_t mode) 
__attribute__((overloadable));  // both-note{{candidate function not viable: 
requires 3 arguments, but 2 were provided}}
-
-void test1(void) {
-#ifndef CODEGEN
-  open("path", O_CREAT);  // both-error{{no matching function for call to 
'open'}}
-#endif
-  open("path", O_CREAT, 0660);
-  open("path", 0);
-  open("path", 0, 0);
-}
-
-size_t __strnlen_chk(const char *s, size_t requested_amount, size_t s_len);
-
-size_t strnlen(const char *s, size_t maxlen)
-  __attribute__((overloadable))
-  __asm__("strnlen_real1");
-
-__attribute__((always_inline))
-inline size_t strnlen(const char *s, size_t maxlen)
-  __attribute__((overloadable))
-  __attribute__((enable_if(__builtin_object_size(s, 0) != -1,
-                           "chosen when target buffer size is known")))
-{
-  return __strnlen_chk(s, maxlen, __builtin_object_size(s, 0));
-}
-
-size_t strnlen(const char *s, size_t maxlen)
-  __attribute__((overloadable))
-  __attribute__((enable_if(__builtin_object_size(s, 0) != -1,
-                           "chosen when target buffer size is known")))
-  __attribute__((enable_if(maxlen <= __builtin_object_size(s, 0),
-                           "chosen when 'maxlen' is known to be less than or 
equal to the buffer size")))
-  __asm__("strnlen_real2");
-
-size_t strnlen(const char *s, size_t maxlen) // ref-note {{'strnlen' has been 
explicitly marked unavailable here}}
-  __attribute__((overloadable))
-  __attribute__((enable_if(__builtin_object_size(s, 0) != -1,
-                           "chosen when target buffer size is known")))
-  __attribute__((enable_if(maxlen > __builtin_object_size(s, 0),
-                           "chosen when 'maxlen' is larger than the buffer 
size")))
-  __attribute__((unavailable("'maxlen' is larger than the buffer size")));
-
-void test2(const char *s, int i) {
-// CHECK: define {{.*}}void @test2
-  const char c[123] = { 0 };
-  strnlen(s, i);
-// CHECK: call {{.*}}strnlen_real1
-  strnlen(s, 999);
-// CHECK: call {{.*}}strnlen_real1
-  strnlen(c, 1);
-// CHECK: call {{.*}}strnlen_real2
-  strnlen(c, i);
-// CHECK: call {{.*}}strnlen_chk
-#ifndef CODEGEN
-  strnlen(c, 999);  // ref-error{{'strnlen' is unavailable: 'maxlen' is larger 
than the buffer size}}
-#endif
-}
-
-int isdigit(int c) __attribute__((overloadable));
-int isdigit(int c) __attribute__((overloadable)) // both-note {{'isdigit' has 
been explicitly marked unavailable here}}
-  __attribute__((enable_if(c <= -1 || c > 255, "'c' must have the value of an 
unsigned char or EOF")))
-  __attribute__((unavailable("'c' must have the value of an unsigned char or 
EOF")));
-
-void test3(int c) {
-  isdigit(c); // both-warning{{ignoring return value of function declared with 
pure attribute}}
-  isdigit(10); // both-warning{{ignoring return value of function declared 
with pure attribute}}
-#ifndef CODEGEN
-  isdigit(-10);  // both-error{{'isdigit' is unavailable: 'c' must have the 
value of an unsigned char or EOF}}
-#endif
-}
-
-// Verify that the alternate spelling __enable_if__ works as well.
-int isdigit2(int c) __attribute__((overloadable));
-int isdigit2(int c) __attribute__((overloadable)) // both-note {{'isdigit2' 
has been explicitly marked unavailable here}}
-  __attribute__((__enable_if__(c <= -1 || c > 255, "'c' must have the value of 
an unsigned char or EOF")))
-  __attribute__((unavailable("'c' must have the value of an unsigned char or 
EOF")));
-
-void test4(int c) {
-  isdigit2(c);
-  isdigit2(10);
-#ifndef CODEGEN
-  isdigit2(-10);  // both-error{{'isdigit2' is unavailable: 'c' must have the 
value of an unsigned char or EOF}}
-#endif
-}
-
-void test5(void) {
-  int (*p1)(int) = &isdigit2;
-  int (*p2)(int) = isdigit2;
-  void *p3 = (void *)&isdigit2;
-  void *p4 = (void *)isdigit2;
-}
-
-#ifndef CODEGEN
-__attribute__((enable_if(n == 0, "chosen when 'n' is zero"))) void f1(int n); 
// both-error{{use of undeclared identifier 'n'}}
-
-int n __attribute__((enable_if(1, "always chosen"))); // 
both-warning{{'enable_if' attribute only applies to functions}}
-
-void f(int n) __attribute__((enable_if("chosen when 'n' is zero", n == 0)));  
// both-error{{expected string literal as argument of 'enable_if' attribute}}
-
-void f(int n) __attribute__((enable_if()));  // both-error{{'enable_if' 
attribute requires exactly 2 arguments}}
-
-void f(int n) __attribute__((enable_if(unresolvedid, "chosen when 
'unresolvedid' is non-zero")));  // both-error{{use of undeclared identifier 
'unresolvedid'}}
-
-int global;
-void f(int n) __attribute__((enable_if(global == 0, "chosen when 'global' is 
zero")));  // both-error{{'enable_if' attribute expression never produces a 
constant expression}} \
-                                                                               
         // both-note{{subexpression not valid in a constant expression}}
-
-enum { cst = 7 };
-void return_cst(void) __attribute__((overloadable)) 
__attribute__((enable_if(cst == 7, "chosen when 'cst' is 7")));
-void test_return_cst(void) { return_cst(); }
-
-void f2(void) __attribute__((overloadable)) __attribute__((enable_if(1, 
"always chosen")));       // #f2_1
-void f2(void) __attribute__((overloadable)) __attribute__((enable_if(0, "never 
chosen")));        // #f2_2
-void f2(void) __attribute__((overloadable)) __attribute__((enable_if(TRUE, 
"always chosen #2"))); // #f2_3
-void test6(void) {
-  void (*p1)(void) = &f2; // both-error {{initializing 'void (*)(void)' with 
an expression of incompatible type '<overloaded function type>'}} \
-                          // both-note@#f2_1 {{candidate function}} \
-                          // both-note@#f2_2 {{candidate function made 
ineligible by enable_if}} \
-                          // both-note@#f2_3 {{candidate function}}
-  void (*p2)(void) = f2; // both-error {{initializing 'void (*)(void)' with an 
expression of incompatible type '<overloaded function type>'}} \
-                         // both-note@#f2_1 {{candidate function}} \
-                         // both-note@#f2_2 {{candidate function made 
ineligible by enable_if}} \
-                         // both-note@#f2_3 {{candidate function}}
-  void *p3 = (void*)&f2; // both-error {{address of overloaded function 'f2' 
is ambiguous}} \
-                         // both-note@#f2_1 {{candidate function}} \
-                         // both-note@#f2_2 {{candidate function made 
ineligible by enable_if}} \
-                         // both-note@#f2_3 {{candidate function}}
-  void *p4 = (void*)f2; // both-error {{address of overloaded function 'f2' is 
ambiguous}} \
-                        // both-note@#f2_1 {{candidate function}} \
-                        // both-note@#f2_2 {{candidate function made 
ineligible by enable_if}} \
-                        // both-note@#f2_3 {{candidate function}}
-}
-
-void f3(int m) __attribute__((overloadable)) __attribute__((enable_if(m >= 0, 
"positive"))); // #f3_1
-void f3(int m) __attribute__((overloadable)) __attribute__((enable_if(m < 0, 
"negative")));  // #f3_2
-void test7(void) {
-  void (*p1)(int) = &f3; // both-error {{initializing 'void (*)(int)' with an 
expression of incompatible type '<overloaded function type>'}} \
-                         // both-note@#f3_1 {{candidate function made 
ineligible by enable_if}} \
-                         // both-note@#f3_2 {{candidate function made 
ineligible by enable_if}}
-  void (*p2)(int) = f3; // both-error {{initializing 'void (*)(int)' with an 
expression of incompatible type '<overloaded function type>'}} \
-                        // both-note@#f3_1 {{candidate function made 
ineligible by enable_if}} \
-                        // both-note@#f3_2 {{candidate function made 
ineligible by enable_if}}
-  void *p3 = (void*)&f3; // both-error {{address of overloaded function 'f3' 
does not match required type 'void'}} \
-                         // both-note@#f3_1 {{candidate function made 
ineligible by enable_if}} \
-                         // both-note@#f3_2 {{candidate function made 
ineligible by enable_if}}
-  void *p4 = (void*)f3; // both-error {{address of overloaded function 'f3' 
does not match required type 'void'}} \
-                        // both-note@#f3_1 {{candidate function made 
ineligible by enable_if}} \
-                        // both-note@#f3_2 {{candidate function made 
ineligible by enable_if}}
-}
-
-void f4(int m) __attribute__((enable_if(0, "")));
-void test8(void) {
-  void (*p1)(int) = &f4; // both-error{{cannot take address of function 'f4' 
because it has one or more non-tautological enable_if conditions}}
-  void (*p2)(int) = f4; // both-error{{cannot take address of function 'f4' 
because it has one or more non-tautological enable_if conditions}}
-}
-
-void regular_enable_if(int a) __attribute__((enable_if(a, ""))); // both-note 
3{{declared here}}
-void PR27122_ext(void) {
-  regular_enable_if(0, 2); // both-error{{too many arguments}}
-  regular_enable_if(1, 2); // both-error{{too many arguments}}
-  regular_enable_if(); // both-error{{too few arguments}}
-}
-
-// We had a bug where we'd crash upon trying to evaluate varargs.
-void variadic_enable_if(int a, ...) __attribute__((enable_if(a, ""))); // 
both-note 6 {{disabled}}
-void variadic_test(void) {
-  variadic_enable_if(1);
-  variadic_enable_if(1, 2);
-  variadic_enable_if(1, "c", 3);
-
-  variadic_enable_if(0); // both-error{{no matching}}
-  variadic_enable_if(0, 2); // both-error{{no matching}}
-  variadic_enable_if(0, "c", 3); // both-error{{no matching}}
-
-  int m;
-  variadic_enable_if(1);
-  variadic_enable_if(1, m);
-  variadic_enable_if(1, m, "c");
-
-  variadic_enable_if(0); // both-error{{no matching}}
-  variadic_enable_if(0, m); // both-error{{no matching}}
-  variadic_enable_if(0, m, 3); // both-error{{no matching}}
-}
-#endif
diff --git a/clang/test/AST/ByteCode/literals.cpp 
b/clang/test/AST/ByteCode/literals.cpp
index 3b24b166e39a7..97eab655032b6 100644
--- a/clang/test/AST/ByteCode/literals.cpp
+++ b/clang/test/AST/ByteCode/literals.cpp
@@ -30,6 +30,9 @@ static_assert(!__objc_no, "");
 
 static_assert((long long)0x00000000FFFF0000 == 4294901760, "");
 
+int kk[3];
+static_assert(kk + 3 == &kk[3], "");
+
 constexpr bool b = number;
 static_assert(b, "");
 constexpr int one = true;
diff --git a/clang/test/CodeGen/attr-counted-by-with-sanitizers.c 
b/clang/test/CodeGen/attr-counted-by-with-sanitizers.c
index e840db632957e..272ff5004d3e8 100644
--- a/clang/test/CodeGen/attr-counted-by-with-sanitizers.c
+++ b/clang/test/CodeGen/attr-counted-by-with-sanitizers.c
@@ -1,6 +1,8 @@
 // NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py 
UTC_ARGS: --version 6
-// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -DCOUNTED_BY -O2 -Wall 
-fsanitize=array-bounds,object-size,local-bounds -fstrict-flex-arrays=3 
-emit-llvm -o - %s | FileCheck --check-prefix=SANITIZE-WITH-ATTR %s
-// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu              -O2 -Wall 
-fsanitize=array-bounds,object-size,local-bounds -fstrict-flex-arrays=3 
-emit-llvm -o - %s | FileCheck --check-prefix=SANITIZE-WITHOUT-ATTR %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -DCOUNTED_BY -O2 -Wall 
-fsanitize=array-bounds,object-size,local-bounds -fstrict-flex-arrays=3 
-emit-llvm -o - %s                                         | FileCheck 
--check-prefix=SANITIZE-WITH-ATTR %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu              -O2 -Wall 
-fsanitize=array-bounds,object-size,local-bounds -fstrict-flex-arrays=3 
-emit-llvm -o - %s                                         | FileCheck 
--check-prefix=SANITIZE-WITHOUT-ATTR %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -DCOUNTED_BY -O2 -Wall 
-fsanitize=array-bounds,object-size,local-bounds -fstrict-flex-arrays=3 
-emit-llvm -o - %s -fexperimental-new-constant-interpreter | FileCheck 
--check-prefix=SANITIZE-WITH-ATTR %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu              -O2 -Wall 
-fsanitize=array-bounds,object-size,local-bounds -fstrict-flex-arrays=3 
-emit-llvm -o - %s -fexperimental-new-constant-interpreter | FileCheck 
--check-prefix=SANITIZE-WITHOUT-ATTR %s
 
 #if !__has_attribute(counted_by)
 #error "has attribute broken"
diff --git a/clang/test/CodeGen/attr-counted-by-without-sanitizers.c 
b/clang/test/CodeGen/attr-counted-by-without-sanitizers.c
index 7ceb51c8986bd..1dd307ff7f72b 100644
--- a/clang/test/CodeGen/attr-counted-by-without-sanitizers.c
+++ b/clang/test/CodeGen/attr-counted-by-without-sanitizers.c
@@ -1,6 +1,10 @@
 // NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py 
UTC_ARGS: --version 6
-// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -DCOUNTED_BY -O2 -Wall 
-fstrict-flex-arrays=3 -emit-llvm -o - %s | FileCheck 
--check-prefix=NO-SANITIZE-WITH-ATTR %s
-// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu              -O2 -Wall 
-fstrict-flex-arrays=3 -emit-llvm -o - %s | FileCheck 
--check-prefix=NO-SANITIZE-WITHOUT-ATTR %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -DCOUNTED_BY -O2 -Wall 
-fstrict-flex-arrays=3 -emit-llvm -o - %s                                       
  | FileCheck --check-prefix=NO-SANITIZE-WITH-ATTR %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu              -O2 -Wall 
-fstrict-flex-arrays=3 -emit-llvm -o - %s                                       
   | FileCheck --check-prefix=NO-SANITIZE-WITHOUT-ATTR %s
+
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -DCOUNTED_BY -O2 -Wall 
-fstrict-flex-arrays=3 -emit-llvm -o - %s 
-fexperimental-new-constant-interpreter | FileCheck 
--check-prefix=NO-SANITIZE-WITH-ATTR %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu              -O2 -Wall 
-fstrict-flex-arrays=3 -emit-llvm -o - %s 
-fexperimental-new-constant-interpreter  | FileCheck 
--check-prefix=NO-SANITIZE-WITHOUT-ATTR %s
+
 
 #if !__has_attribute(counted_by)
 #error "has attribute broken"
diff --git a/clang/test/Sema/enable_if.c b/clang/test/Sema/enable_if.c
index 80f8cce5918ed..fd7408c6f4c30 100644
--- a/clang/test/Sema/enable_if.c
+++ b/clang/test/Sema/enable_if.c
@@ -1,5 +1,10 @@
 // RUN: %clang_cc1 %s -verify
 // RUN: %clang_cc1 %s -DCODEGEN -emit-llvm -o - | FileCheck %s
+// RUN: %clang_cc1 %s -verify -fexperimental-new-constant-interpreter
+// RUN: %clang_cc1 %s -DCODEGEN -emit-llvm -o - 
-fexperimental-new-constant-interpreter | FileCheck %s
+
+// RUN: %clang_cc1 %s -fexperimental-new-constant-interpreter -verify
+// RUN: %clang_cc1 %s -fexperimental-new-constant-interpreter -DCODEGEN 
-emit-llvm -o - | FileCheck %s
 
 #define O_CREAT 0x100
 typedef int mode_t;
diff --git a/clang/test/SemaCXX/new-delete.cpp 
b/clang/test/SemaCXX/new-delete.cpp
index 2a2f91186871e..0c1f95ae3d09a 100644
--- a/clang/test/SemaCXX/new-delete.cpp
+++ b/clang/test/SemaCXX/new-delete.cpp
@@ -718,12 +718,22 @@ int *fail = dependent_array_size("hello"); // 
expected-note {{instantiation of}}
 // FIXME: Our behavior here is incredibly inconsistent. GCC allows
 // constant-folding in array bounds in new-expressions.
 int (*const_fold)[12] = new int[3][&const_fold + 12 - &const_fold];
-#if __cplusplus >= 201402L && !defined(NEW_INTERP)
+#if __cplusplus >= 201402L
 // expected-error@-2 {{array size is not a constant expression}}
 // expected-note@-3 {{cannot refer to element 12 of non-array}}
+#elif __cplusplus == 201103L
+#if defined(NEW_INTERP)
+// expected-error@-6 {{only the first dimension of an allocated array may have 
dynamic size}}
+// expected-note@-7 {{cannot refer to element 12 of non-array}}
+#endif
 #elif __cplusplus < 201103L
-// expected-error@-5 {{cannot allocate object of variably modified type}}
-// expected-warning@-6 {{variable length arrays in C++ are a Clang extension}}
+#if defined(NEW_INTERP)
+// expected-error@-11 {{only the first dimension of an allocated array may 
have dynamic size}}
+// expected-note@-12 {{cannot refer to element 12 of non-array}}
+#else
+// expected-error@-14 {{cannot allocate object of variably modified type}}
+// expected-warning@-15 {{variable length arrays in C++ are a Clang extension}}
+#endif
 #endif
 
 #if __cplusplus >= 201103L

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to