[clang] [clang][bytecode] Add opaque pointers to support type-only pointers (PR #213017)

Timm Baeder via cfe-commits cfe-commits at lists.llvm.org
Sun Aug 2 21:20:34 PDT 2026


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


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

>From eee6b8d155322296b96488f470f3367e6119e7c0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbaeder at redhat.com>
Date: Thu, 30 Jul 2026 14:52:59 +0200
Subject: [PATCH 1/2] test changes

---
 clang/test/CodeGen/object-size.c      | 92 +--------------------------
 clang/test/CodeGen/pass-object-size.c |  5 +-
 2 files changed, 5 insertions(+), 92 deletions(-)

diff --git a/clang/test/CodeGen/object-size.c b/clang/test/CodeGen/object-size.c
index 00ab18cb22e84..ad8969f3644d3 100644
--- a/clang/test/CodeGen/object-size.c
+++ b/clang/test/CodeGen/object-size.c
@@ -1,5 +1,8 @@
 // RUN: %clang_cc1 -no-enable-noundef-analysis           -triple x86_64-apple-darwin -emit-llvm %s -o - 2>&1 | FileCheck %s
 // RUN: %clang_cc1 -no-enable-noundef-analysis -DDYNAMIC -triple x86_64-apple-darwin -emit-llvm %s -o - 2>&1 | FileCheck %s
+// RUN: %clang_cc1 -no-enable-noundef-analysis           -triple x86_64-apple-darwin -emit-llvm %s -o - 2>&1 | FileCheck %s
+// RUN: %clang_cc1 -no-enable-noundef-analysis -DDYNAMIC -triple x86_64-apple-darwin -emit-llvm %s -o - 2>&1 | FileCheck %s
+
 
 #ifndef DYNAMIC
 #define OBJECT_SIZE_BUILTIN __builtin_object_size
@@ -59,93 +62,6 @@ void test6(void) {
   strcpy(&buf[4], "Hi there");
 }
 
-// CHECK-LABEL: define{{.*}} void @test7
-void test7(void) {
-  int i;
-  // Ensure we only evaluate the side-effect once.
-  // CHECK:     = add
-  // CHECK-NOT: = add
-  // CHECK:     = call ptr @__strcpy_chk(ptr @gbuf, ptr @.str, i64 63)
-  strcpy((++i, gbuf), "Hi there");
-}
-
-// CHECK-LABEL: define{{.*}} void @test8
-void test8(void) {
-  char *buf[50];
-  // CHECK-NOT:   __strcpy_chk
-  // CHECK:       = call ptr @__inline_strcpy_chk(ptr %{{.*}}, ptr @.str)
-  strcpy(buf[++gi], "Hi there");
-}
-
-// CHECK-LABEL: define{{.*}} void @test9
-void test9(void) {
-  // CHECK-NOT:   __strcpy_chk
-  // CHECK:       = call ptr @__inline_strcpy_chk(ptr %{{.*}}, ptr @.str)
-  strcpy((char *)((++gi) + gj), "Hi there");
-}
-
-// CHECK-LABEL: define{{.*}} void @test10
-char **p;
-void test10(void) {
-  // CHECK-NOT:   __strcpy_chk
-  // CHECK:       = call ptr @__inline_strcpy_chk(ptr %{{.*}}, ptr @.str)
-  strcpy(*(++p), "Hi there");
-}
-
-// CHECK-LABEL: define{{.*}} void @test11
-void test11(void) {
-  // CHECK-NOT:   __strcpy_chk
-  // CHECK:       = call ptr @__inline_strcpy_chk(ptr @gbuf, ptr @.str)
-  strcpy(gp = gbuf, "Hi there");
-}
-
-// CHECK-LABEL: define{{.*}} void @test12
-void test12(void) {
-  // CHECK-NOT:   __strcpy_chk
-  // CHECK:       = call ptr @__inline_strcpy_chk(ptr %{{.*}}, ptr @.str)
-  strcpy(++gp, "Hi there");
-}
-
-// CHECK-LABEL: define{{.*}} void @test13
-void test13(void) {
-  // CHECK-NOT:   __strcpy_chk
-  // CHECK:       = call ptr @__inline_strcpy_chk(ptr %{{.*}}, ptr @.str)
-  strcpy(gp++, "Hi there");
-}
-
-// CHECK-LABEL: define{{.*}} void @test14
-void test14(void) {
-  // CHECK-NOT:   __strcpy_chk
-  // CHECK:       = call ptr @__inline_strcpy_chk(ptr %{{.*}}, ptr @.str)
-  strcpy(--gp, "Hi there");
-}
-
-// CHECK-LABEL: define{{.*}} void @test15
-void test15(void) {
-  // CHECK-NOT:   __strcpy_chk
-  // CHECK:       = call ptr @__inline_strcpy_chk(ptr %{{..*}}, ptr @.str)
-  strcpy(gp--, "Hi there");
-}
-
-// CHECK-LABEL: define{{.*}} void @test16
-void test16(void) {
-  // CHECK-NOT:   __strcpy_chk
-  // CHECK:       = call ptr @__inline_strcpy_chk(ptr %{{.*}}, ptr @.str)
-  strcpy(gp += 1, "Hi there");
-}
-
-// CHECK-LABEL: @test17
-void test17(void) {
-  // CHECK: store i32 -1
-  gi = OBJECT_SIZE_BUILTIN(gp++, 0);
-  // CHECK: store i32 -1
-  gi = OBJECT_SIZE_BUILTIN(gp++, 1);
-  // CHECK: store i32 0
-  gi = OBJECT_SIZE_BUILTIN(gp++, 2);
-  // CHECK: store i32 0
-  gi = OBJECT_SIZE_BUILTIN(gp++, 3);
-}
-
 // CHECK-LABEL: @test18
 unsigned test18(int cond) {
   int a[4], b[4];
@@ -337,8 +253,6 @@ void test26(void) {
 
   // CHECK: store i32 316
   gi = OBJECT_SIZE_BUILTIN(&t[1].v[11], 0);
-  // CHECK: store i32 312
-  gi = OBJECT_SIZE_BUILTIN(&t[1].v[12], 1);
   // CHECK: store i32 308
   gi = OBJECT_SIZE_BUILTIN(&t[1].v[13], 2);
   // CHECK: store i32 0
diff --git a/clang/test/CodeGen/pass-object-size.c b/clang/test/CodeGen/pass-object-size.c
index c7c505b0fb3e7..64a544642bfe0 100644
--- a/clang/test/CodeGen/pass-object-size.c
+++ b/clang/test/CodeGen/pass-object-size.c
@@ -1,4 +1,5 @@
-// RUN: %clang_cc1 -triple x86_64-apple-darwin -emit-llvm -O0 %s -o - 2>&1 | FileCheck %s
+// RUN: %clang_cc1 -triple x86_64-apple-darwin -emit-llvm -O0 %s -o - 2>&1                                         | FileCheck %s
+// RUN: %clang_cc1 -triple x86_64-apple-darwin -emit-llvm -O0 %s -o - 2>&1 -fexperimental-new-constant-interpreter | FileCheck %s
 
 typedef unsigned long size_t;
 
@@ -217,8 +218,6 @@ void test3(void) {
 void test4(struct Foo *t) {
   // CHECK: call i32 @_Z27NoViableOverloadObjectSize0PvU17pass_object_size0(ptr noundef %{{.*}}, i64 noundef %{{.*}})
   gi = NoViableOverloadObjectSize0(&t[1]);
-  // CHECK: call i32 @_Z27NoViableOverloadObjectSize1PvU17pass_object_size1(ptr noundef %{{.*}}, i64 noundef %{{.*}})
-  gi = NoViableOverloadObjectSize1(&t[1]);
   // CHECK: call i32 @_Z27NoViableOverloadObjectSize2PvU17pass_object_size2(ptr noundef %{{.*}}, i64 noundef %{{.*}})
   gi = NoViableOverloadObjectSize2(&t[1]);
   // CHECK: call i32 @_Z27NoViableOverloadObjectSize3PvU17pass_object_size3(ptr noundef %{{.*}}, i64 noundef 0)

>From 56bdee1758178becb54f429780bd857748ae2b07 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbaeder at redhat.com>
Date: Thu, 30 Jul 2026 14:53:12 +0200
Subject: [PATCH 2/2] opaque pointers

---
 clang/lib/AST/ByteCode/Compiler.cpp           |  44 +-
 clang/lib/AST/ByteCode/Context.cpp            |  16 +-
 clang/lib/AST/ByteCode/Context.h              |   2 +-
 clang/lib/AST/ByteCode/Interp.cpp             | 131 ++++
 clang/lib/AST/ByteCode/Interp.h               | 123 ++--
 clang/lib/AST/ByteCode/InterpBuiltin.cpp      | 232 +------
 .../AST/ByteCode/InterpBuiltinObjectSize.cpp  | 601 ++++++++++++++++++
 clang/lib/AST/ByteCode/InterpHelpers.h        |   3 +-
 clang/lib/AST/ByteCode/InterpState.h          |   4 +
 clang/lib/AST/ByteCode/Opcodes.td             |   6 +
 clang/lib/AST/ByteCode/Pointer.cpp            |  54 ++
 clang/lib/AST/ByteCode/Pointer.h              |  94 ++-
 clang/lib/AST/ByteCode/Program.cpp            |   6 +-
 clang/lib/AST/CMakeLists.txt                  |   1 +
 clang/lib/AST/ExprConstant.cpp                |   5 +-
 .../builtin-object-size-codegen-cxx23.cpp     |  15 +
 .../ByteCode/builtin-object-size-codegen.c    |  64 ++
 .../ByteCode/builtin-object-size-codegen.cpp  | 188 ++++++
 clang/test/AST/ByteCode/enable_if.c           |   4 +-
 .../CodeGen/attr-counted-by-with-sanitizers.c |   6 +-
 .../attr-counted-by-without-sanitizers.c      |   8 +-
 clang/test/SemaCXX/new-delete.cpp             |  16 +-
 22 files changed, 1347 insertions(+), 276 deletions(-)
 create mode 100644 clang/lib/AST/ByteCode/InterpBuiltinObjectSize.cpp
 create mode 100644 clang/test/AST/ByteCode/builtin-object-size-codegen-cxx23.cpp

diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp
index 479b5251b9f57..1c13fe7abfd6a 100644
--- a/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/clang/lib/AST/ByteCode/Compiler.cpp
@@ -453,8 +453,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);
@@ -6037,6 +6041,33 @@ bool Compiler<Emitter>::visitAPValueInitializer(const APValue &Val,
   return false;
 }
 
+/// A more selective version of E->IgnoreParenCasts for
+/// tryEvaluateBuiltinObjectSize. This ignores some casts/parens that serve only
+/// to change the type of E.
+/// Ex. For E = `(short*)((char*)(&foo))`, returns `&foo`
+///
+/// Always returns an RValue with a pointer representation.
+static const Expr *ignorePointerCastsAndParens(const Expr *E) {
+  assert(E->isPRValue() && E->getType()->hasPointerRepresentation());
+
+  const Expr *NoParens = E->IgnoreParens();
+  const auto *Cast = dyn_cast<CastExpr>(NoParens);
+  if (Cast == nullptr)
+    return NoParens;
+
+  // We only conservatively allow a few kinds of casts, because this code is
+  // inherently a simple solution that seeks to support the common case.
+  auto CastKind = Cast->getCastKind();
+  if (CastKind != CK_NoOp && CastKind != CK_BitCast &&
+      CastKind != CK_AddressSpaceConversion)
+    return NoParens;
+
+  const auto *SubExpr = Cast->getSubExpr();
+  if (!SubExpr->getType()->hasPointerRepresentation() || !SubExpr->isPRValue())
+    return NoParens;
+  return ignorePointerCastsAndParens(SubExpr);
+}
+
 template <class Emitter>
 bool Compiler<Emitter>::VisitBuiltinCallExpr(const CallExpr *E,
                                              unsigned BuiltinID) {
@@ -6095,7 +6126,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 +8737,13 @@ bool Compiler<Emitter>::emitDestructionPop(const Descriptor *Desc,
 template <class Emitter>
 bool Compiler<Emitter>::emitDummyPtr(const DeclTy &D, const Expr *E, bool CU) {
   assert(!DiscardResult && "Should've been checked before");
-  unsigned DummyID = P.getOrCreateDummy(D, CU);
 
+  if (ToLValue) {
+    if (auto *VD = dyn_cast_if_present<ValueDecl>(D.dyn_cast<const Decl *>()))
+      return this->emitGetOpaquePtr(VD, 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 b913d2a9f539c..6e3b93e2107b5 100644
--- a/clang/lib/AST/ByteCode/Context.cpp
+++ b/clang/lib/AST/ByteCode/Context.cpp
@@ -366,26 +366,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 475d206356de8..8e5d6ec28e246 100644
--- a/clang/lib/AST/ByteCode/Interp.cpp
+++ b/clang/lib/AST/ByteCode/Interp.cpp
@@ -1612,6 +1612,31 @@ 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.allocPointerPath(OP.PathLength + 1);
+    if (OP.Path)
+      std::memcpy(NewPath, Ptr.asOpaquePointer().Path,
+                  sizeof(PointerPathEntry) * OP.PathLength);
+
+    NewPath[OP.PathLength] = PointerPathEntry::field(F->Decl);
+
+    S.Stk.push<Pointer>(OpaqueTag{}, OP.Base, F->Decl->getType().getTypePtr(),
+                        NewPath, OP.PathLength + 1);
+    return true;
+  }
+
   if (!Ptr.isBlockPointer()) {
     // If we're trying to get the field of a TypeId pointer, try to produce a
     // proper diagnostic.
@@ -1646,6 +1671,33 @@ 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;
+
+    unsigned NewPathLength = OP.PathLength + 1;
+    PointerPathEntry *NewPath = S.allocPointerPath(NewPathLength);
+    if (OP.Path)
+      std::memcpy(NewPath, OP.Path,
+                  sizeof(PointerPathEntry) * (NewPathLength - 1));
+
+    NewPath[NewPathLength - 1] =
+        PointerPathEntry::base(cast<CXXRecordDecl>(B->Decl));
+    S.Stk.push<Pointer>(
+        OpaqueTag{}, OP.Base,
+        S.getASTContext().getCanonicalTagType(B->Decl).getTypePtr(), NewPath,
+        NewPathLength);
+    return true;
+  }
+
   if (!Ptr.isBlockPointer()) {
     if (!Ptr.isIntegralPointer())
       return false;
@@ -1906,6 +1958,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) {
 
@@ -3221,6 +3274,84 @@ bool CastFloatingIntegralAPS(InterpState &S, CodePtr OpPC, uint32_t BitWidth,
   return floatAPCast<true>(S, OpPC, F, BitWidth, FPOI);
 }
 
+bool arrayElemPtrOpaque(InterpState &S, CodePtr OpPC, const OpaquePointer &OP,
+                        int64_t Offset) {
+  QualType ArrTy;
+  if (OP.PathLength > 0) {
+    if (OP.path().back().Kind == PointerPathEntry::Array) {
+      ArrTy = OP.getSurroundingArray(S.getASTContext());
+    } else {
+      ArrTy = OP.getFieldType();
+    }
+  } else {
+    ArrTy = OP.getObjectType();
+  }
+
+  QualType ElemType;
+  bool PastEnd = true;
+  if (ArrTy->isArrayType()) {
+    const auto *AT = ArrTy->getAsArrayTypeUnsafe();
+    ElemType = AT->getElementType();
+    if (const auto *CAT = dyn_cast<ConstantArrayType>(AT))
+      PastEnd = Offset >= CAT->getSExtSize();
+  } else if (ArrTy->isRecordType()) {
+    ElemType = ArrTy;
+  } else {
+    ElemType = ArrTy;
+  }
+  if (ElemType.isNull())
+    return false;
+
+  unsigned NewPathLength = OP.PathLength + 1;
+  PointerPathEntry *NewPath = S.allocPointerPath(NewPathLength);
+  if (OP.Path)
+    std::memcpy(NewPath, OP.Path,
+                sizeof(PointerPathEntry) * (NewPathLength - 1));
+
+  NewPath[NewPathLength - 1] = PointerPathEntry::array(Offset);
+  S.Stk.push<Pointer>(OpaqueTag{}, OP.Base, ElemType.getTypePtr(), NewPath,
+                      NewPathLength, PastEnd);
+  return true;
+}
+
+bool addSubOffsetOpaque(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
+                        uint64_t Offset, bool Add) {
+  assert(Ptr.isOpaquePointer());
+  const OpaquePointer &OP = Ptr.asOpaquePointer();
+
+  QualType ArrTy = OP.getFieldType();
+  QualType ElemTy;
+  if (ArrTy->isArrayType())
+    ElemTy = ArrTy->getAsArrayTypeUnsafe()->getElementType();
+  else
+    ElemTy = ArrTy;
+
+  if (Offset != 0) {
+    if (isa<IncompleteArrayType>(ArrTy)) {
+      const SourceInfo &E = S.Current->getSource(OpPC);
+      S.FFDiag(E, diag::note_constexpr_unsized_array_indexed);
+      return false;
+    }
+
+    S.CCEDiag(S.Current->getSource(OpPC), diag::note_constexpr_array_index)
+        << Offset << /*non-array*/ true << 0;
+  }
+
+  unsigned ElemSize =
+      S.getASTContext().getTypeSizeInChars(ElemTy).getQuantity();
+  unsigned NewOffset;
+  if (Add)
+    NewOffset = Ptr.getByteOffset() + (ElemSize * Offset);
+  else
+    NewOffset = Ptr.getByteOffset() - (ElemSize * Offset);
+
+  bool PastEnd = NewOffset > 0;
+
+  S.Stk.push<Pointer>(OpaqueTag{}, OP.Base, OP.FieldType.getPointer(), OP.Path,
+                      OP.PathLength, PastEnd, NewOffset);
+  return true;
+}
+
 // 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 405f4a29ec982..85d5fcd4dc9fb 100644
--- a/clang/lib/AST/ByteCode/Interp.h
+++ b/clang/lib/AST/ByteCode/Interp.h
@@ -2252,6 +2252,45 @@ 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 (!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>(OpaqueTag{}, OP.Base, T.getTypePtr(), OP.Path,
+                          OP.PathLength, OP.isOnePastEnd(),
+                          Ptr.getByteOffset());
+
+    } else {
+      S.Stk.push<Pointer>(OpaqueTag{}, Ptr.getDeclDesc()->asValueDecl(),
+                          Ptr.getType()->getPointeeType().getTypePtr());
+    }
+
+  } 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>();
@@ -2646,11 +2685,17 @@ std::optional<Pointer> OffsetHelper(InterpState &S, CodePtr OpPC,
   return Ptr.atIndex(static_cast<uint64_t>(Result));
 }
 
+bool addSubOffsetOpaque(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
+                        uint64_t Offset, bool Add);
 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())
+    return addSubOffsetOpaque(S, OpPC, Ptr, static_cast<uint64_t>(Offset),
+                              /*Add=*/true);
+
   if (std::optional<Pointer> Result = OffsetHelper<T, ArithOp::Add>(
           S, OpPC, Offset, Ptr, /*IsPointerArith=*/true)) {
     S.Stk.push<Pointer>(Result->narrow());
@@ -2664,6 +2709,10 @@ bool SubOffset(InterpState &S, CodePtr OpPC) {
   const T &Offset = S.Stk.pop<T>();
   const Pointer &Ptr = S.Stk.pop<Pointer>().expand();
 
+  if (Ptr.isOpaquePointer())
+    return addSubOffsetOpaque(S, OpPC, Ptr, static_cast<uint64_t>(Offset),
+                              /*Add=*/false);
+
   if (std::optional<Pointer> Result = OffsetHelper<T, ArithOp::Sub>(
           S, OpPC, Offset, Ptr, /*IsPointerArith=*/true)) {
     S.Stk.push<Pointer>(Result->narrow());
@@ -2672,6 +2721,12 @@ bool SubOffset(InterpState &S, CodePtr OpPC) {
   return false;
 }
 
+inline bool GetOpaquePtr(InterpState &S, const ValueDecl *VD) {
+  S.Stk.push<Pointer>(OpaqueTag{}, VD);
+
+  return true;
+}
+
 template <ArithOp Op>
 static inline bool IncDecPtrHelper(InterpState &S, CodePtr OpPC,
                                    const Pointer &Ptr) {
@@ -3431,18 +3486,16 @@ 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 OpaquePointer &OP,
+                        int64_t Offset);
+
+// 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.asOpaquePointer(),
+                              static_cast<int64_t>(Offset));
 
   if (!Ptr.isZero() && !Offset.isZero()) {
     if (!CheckArray(S, OpPC, Ptr))
@@ -3466,38 +3519,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>
@@ -3569,6 +3615,11 @@ inline bool ArrayDecay(InterpState &S, CodePtr OpPC) {
       return false;
   }
 
+  if (Ptr.isOpaquePointer()) {
+    S.Stk.push<Pointer>(Ptr);
+    return true;
+  }
+
   if (Ptr.isRoot() || !Ptr.isUnknownSizeArray()) {
     S.Stk.push<Pointer>(Ptr.atIndex(0).narrow());
     return true;
diff --git a/clang/lib/AST/ByteCode/InterpBuiltin.cpp b/clang/lib/AST/ByteCode/InterpBuiltin.cpp
index a539fb26abc08..7ba47a99cbb6e 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
@@ -2528,10 +2319,24 @@ static bool interp__builtin_object_size(InterpState &S, CodePtr OpPC,
     return true;
   }
 
-  if (auto Result = evaluateBuiltinObjectSize(ASTCtx, Kind, Ptr)) {
+  if (auto Result = evaluateBuiltinObjectSize(ASTCtx, Kind, Ptr,
+                                              Call->getArg(0), IsDynamic)) {
     pushInteger(S, *Result, Call->getType());
     return true;
   }
+
+  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) ? 0 : -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..306d3c739e62b
--- /dev/null
+++ b/clang/lib/AST/ByteCode/InterpBuiltinObjectSize.cpp
@@ -0,0 +1,601 @@
+//===------------- 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;
+
+static bool b = false;
+
+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:
+      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) {
+  if (auto p = Ptr.computeLayoutOffset(ASTCtx))
+    return *p;
+  return 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;
+      }
+    }
+    }
+  }
+
+  // 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, int Kind,
+                       bool &OffsetIsNegative) {
+  if (b)
+    llvm::errs() << __PRETTY_FUNCTION__ << '\n';
+  const OpaquePointer &OP = Ptr.asOpaquePointer();
+
+  unsigned 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: {
+      int64_t Index = Entry.Index;
+      if (Index < 0)
+        OffsetIsNegative = true;
+      SurroundingArrayOffset = Offset;
+      if (!CurType->isArrayType()) {
+        Offset += Index * ASTCtx.getTypeSizeInChars(CurType).getQuantity();
+        continue;
+      }
+      const ArrayType *AT = CurType->getAsArrayTypeUnsafe();
+      assert(AT);
+      QualType ElemTy = AT->getElementType();
+      if (!validType(ElemTy))
+        return std::nullopt;
+      Offset += Index * ASTCtx.getTypeSizeInChars(ElemTy).getQuantity();
+      CurType = AT->getElementType();
+    }
+    }
+  }
+
+  if (UseClosestSurroundingVariable && SurroundingArrayOffset)
+    return Offset - *SurroundingArrayOffset;
+
+  QualType Ty = CurType.getNonReferenceType();
+
+  if (UseClosestSurroundingVariable &&
+      (Ty->isIncompleteType() || Ty->isFunctionType()))
+    return std::nullopt;
+
+  if (OP.PathLength == 1 && OP.path().back().Kind == PointerPathEntry::Field &&
+      isa<IncompleteArrayType>(CurType)) {
+
+    if (Kind == 1)
+      return std::nullopt;
+
+    return Offset;
+  }
+
+  if (isa<IncompleteArrayType>(CurType))
+    return std::nullopt;
+
+  if (UseClosestSurroundingVariable)
+    return 0;
+
+  return Offset;
+}
+
+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);
+  return isa<IncompleteArrayType>(FieldType);
+}
+
+static std::optional<unsigned>
+computeOpaqueSize(const ASTContext &ASTCtx, const Pointer &Ptr,
+                  bool UseClosestSurroundingVariable) {
+
+  if (b) {
+    llvm::errs() << __PRETTY_FUNCTION__ << '\n';
+    for (auto &E : Ptr.asOpaquePointer().path())
+      llvm::errs() << "Kind: " << E.Kind << '\n';
+  }
+
+  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);
+  }
+
+  // Check if we need to add the flexible array member size.
+  const VarDecl *Base = dyn_cast<VarDecl>(OP.Base);
+  if (!Base || !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 (b) {
+    llvm::errs() << __PRETTY_FUNCTION__ << '\n';
+    llvm::errs() << Ptr << '\n';
+    E->dumpColor();
+  }
+
+  if (Ptr.isZero()) {
+    return std::nullopt;
+  }
+
+  if (Ptr.isDummy() && Ptr.getType()->isPointerType()) {
+    if (b)
+      llvm::errs() << "err2\n";
+    return std::nullopt;
+  }
+
+  bool InvalidBase = false;
+
+  if (Ptr.isDummy()) {
+    if (const VarDecl *VD = Ptr.getRootVarDecl();
+        VD && VD->getType()->isPointerType())
+      InvalidBase = true;
+  }
+
+  if (Ptr.isOpaquePointer()) {
+    bool UseClosestSurroundingVariable = (Kind == 1) || (Kind == 3);
+    const OpaquePointer &OP = Ptr.asOpaquePointer();
+    if (b)
+      llvm::errs() << "------------ OPAQUE BOS\n";
+    InvalidBase = OP.Base->getType()->isPointerType();
+    bool DetermineForCompleteObject = pointsToCompleteObject(ASTCtx, Ptr);
+
+    if (b) {
+      llvm::errs() << "DetermineForCompleteObject: "
+                   << DetermineForCompleteObject << '\n';
+      // llvm::errs() << "UseFieldDesc: " << UseFieldDesc << '\n';
+      // llvm::errs() << "ReportMinimum: " << ReportMinimum << '\n';
+      llvm::errs() << "InvalidBase: " << InvalidBase << '\n';
+      llvm::errs() << "WOTE: " << isUserWritingOffTheEnd(ASTCtx, OP) << '\n';
+      llvm::errs() << "UseClosestSurroundingVariable: "
+                   << UseClosestSurroundingVariable << '\n';
+      llvm::errs() << "Pointer Byte offset: " << Ptr.getByteOffset() << '\n';
+    }
+
+    // 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);
+
+    if (!FullSize)
+      return std::nullopt;
+    if (b) {
+      llvm::errs() << "COMPUTED SIZE: " << *FullSize << '\n';
+    }
+
+    // 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, Kind, 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()) {
+        if (b)
+          llvm::errs() << "FME check\n";
+        return std::nullopt;
+      }
+    }
+
+    if (!(Kind & 1) || false || DetermineForCompleteObject) {
+      // Kind=3 wants a lower bound, so we can't fall back to this.
+      if (Kind == 3 && !DetermineForCompleteObject)
+        return std::nullopt;
+
+      // llvm::APInt APEndOffset;
+      // if (isBaseAnAllocSizeCall(LVal.getLValueBase()) &&
+      // getBytesReturnedByAllocSizeCall(Info.Ctx, LVal, APEndOffset))
+      // return convertUnsignedAPIntToCharUnits(APEndOffset, EndOffset);
+
+      if (OP.Base->getType()->isPointerType()) {
+        // llvm::errs() << "Lval has invalid base\n";
+        return std::nullopt;
+      }
+
+      // QualType BaseTy = getObjectType(LVal.getLValueBase());
+      // const bool Ret = CheckedHandleSizeof(BaseTy, EndOffset);
+      // addFlexibleArrayMemberInitSize(Info, BaseTy, LVal, EndOffset);
+      // return Ret;
+    }
+
+    if (b) {
+      llvm::errs() << "FullSize: " << *FullSize << '\n';
+      llvm::errs() << "Offset : " << Offset << '\n';
+      llvm::errs() << "Offset : " << (*Offset + Ptr.getByteOffset()) << '\n';
+
+      llvm::errs() << *FullSize << " - " << (*Offset + Ptr.getByteOffset())
+                   << '\n';
+    }
+    *Offset += Ptr.getByteOffset();
+
+    if (*Offset > *FullSize)
+      return 0u;
+
+    if (!UseClosestSurroundingVariable || DetermineForCompleteObject) {
+      if (InvalidBase) {
+        if (b)
+          llvm::errs() << "err4\n";
+        return std::nullopt;
+      }
+    }
+
+    if (InvalidBase && isUserWritingOffTheEnd(ASTCtx, OP)) {
+      if (Kind == 1)
+        return std::nullopt;
+    }
+
+    assert(*Offset <= *FullSize);
+    return static_cast<unsigned>(*FullSize - *Offset);
+  }
+
+  // ----------------------------------------------------------------------------------------------------
+
+  if (Ptr.isZero() || !Ptr.isBlockPointer())
+    return std::nullopt;
+
+  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()) {
+      llvm::errs() << "err3\n";
+      return std::nullopt;
+    }
+
+    if (InvalidBase) {
+      llvm::errs() << "err4\n";
+      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) {
+        llvm::errs() << "err5\n";
+        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;
+}
+} // 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..4fdae266e5d86 100644
--- a/clang/lib/AST/ByteCode/InterpState.h
+++ b/clang/lib/AST/ByteCode/InterpState.h
@@ -127,6 +127,10 @@ class InterpState final : public State, public SourceMapper {
     return reinterpret_cast<const CXXRecordDecl **>(
         this->allocate(Length * sizeof(CXXRecordDecl *)));
   }
+  PointerPathEntry *allocPointerPath(unsigned Length) {
+    return reinterpret_cast<PointerPathEntry *>(
+        this->allocate(Length * sizeof(PointerPathEntry)));
+  }
 
   /// 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 c2f0114c81957..8dc299dc80e84 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];
+}
+
 // [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 b987b350f9537..bc80eda50d45a 100644
--- a/clang/lib/AST/ByteCode/Pointer.cpp
+++ b/clang/lib/AST/ByteCode/Pointer.cpp
@@ -59,6 +59,9 @@ Pointer::Pointer(const Pointer &P)
   case Storage::Typeid:
     Typeid = P.Typeid;
     break;
+  case Storage::Opaque:
+    Opaque = P.Opaque;
+    break;
   }
 }
 
@@ -78,6 +81,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 +133,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 +176,10 @@ Pointer &Pointer::operator=(Pointer &&P) {
     break;
   case Storage::Typeid:
     Typeid = P.Typeid;
+    break;
+  case Storage::Opaque:
+    Opaque = P.Opaque;
+    break;
   }
   return *this;
 }
@@ -198,6 +212,11 @@ APValue Pointer::toAPValue(const ASTContext &ASTCtx) const {
                    /*OnePastTheEnd=*/false, /*IsNull=*/false);
   }
 
+  if (isOpaquePointer()) {
+    return APValue(APValue::LValueBase(), CharUnits::Zero(), Path,
+                   /*IsOnePastEnd=*/false, /*IsNullPtr=*/true);
+  }
+
   // Build the lvalue base from the block.
   const Descriptor *Desc = getDeclDesc();
   APValue::LValueBase Base;
@@ -347,6 +366,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;
   }
 }
 
@@ -371,6 +396,8 @@ 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> {
@@ -449,6 +476,8 @@ Pointer::computeLayoutOffset(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> {
@@ -1196,3 +1225,28 @@ IntPointer IntPointer::baseCast(const interp::Context &Ctx,
                                               std::nullopt, RD, false);
   return {T.getTypePtr(), Value + BaseLayoutOffset.getQuantity()};
 }
+
+QualType OpaquePointer::getSurroundingArray(const ASTContext &ASTCtx) const {
+  assert(PathLength != 0);
+  assert(Path[PathLength - 1].Kind == PointerPathEntry::Array);
+
+  QualType CurType = getObjectType();
+  for (const PointerPathEntry &Entry : path().drop_back(1)) {
+    switch (Entry.Kind) {
+    case PointerPathEntry::Base:
+      CurType = ASTCtx.getCanonicalTagType(Entry.RD.getPointer());
+      break;
+    case PointerPathEntry::Field:
+      CurType = Entry.FD->getType();
+      break;
+    case PointerPathEntry::Array: {
+      if (!CurType->isArrayType())
+        break;
+      const ArrayType *AT = CurType->getAsArrayTypeUnsafe();
+      assert(AT);
+      CurType = AT->getElementType();
+    }
+    }
+  }
+  return CurType;
+}
diff --git a/clang/lib/AST/ByteCode/Pointer.h b/clang/lib/AST/ByteCode/Pointer.h
index c06347318dafa..a3ddf75f94180 100644
--- a/clang/lib/AST/ByteCode/Pointer.h
+++ b/clang/lib/AST/ByteCode/Pointer.h
@@ -370,7 +370,66 @@ struct TypeidPointer {
   const Type *TypeInfoType;
 };
 
-enum class Storage { Int, Block, Fn, Typeid };
+struct PointerPathEntry {
+  enum { Base, Field, Array } Kind;
+  union {
+    int64_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 field(const FieldDecl *FD) {
+    PointerPathEntry E;
+    E.Kind = Field;
+    E.FD = FD;
+    return E;
+  }
+};
+
+struct OpaquePointer {
+  const ValueDecl *Base = nullptr;
+  // FieldType and IsOnePastEnd bit.
+  llvm::PointerIntPair<const Type *, 1, bool> FieldType = {};
+  const PointerPathEntry *Path = nullptr;
+  unsigned PathLength = 0;
+
+  ArrayRef<PointerPathEntry> path() const { return ArrayRef(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);
+  }
+
+  /// If this is pointing to an array element, return the array.
+  QualType getSurroundingArray(const ASTContext &ASTCtx) const;
+
+  bool isOnePastEnd() const { return FieldType.getInt(); }
+};
+struct OpaqueTag {};
+
+enum class Storage { Int, Block, Fn, Typeid, Opaque };
 
 /// A pointer to a memory block, live or dead.
 ///
@@ -420,6 +479,30 @@ class Pointer {
     Typeid.TypePtr = TypePtr;
     Typeid.TypeInfoType = TypeInfoType;
   }
+  Pointer(OpaqueTag, const ValueDecl *Base, const Type *FieldType,
+          uint64_t Offset = 0)
+      : Offset(Offset), StorageKind(Storage::Opaque) {
+    Opaque.FieldType = {FieldType, /*IsOnePastEnd=*/false};
+    Opaque.Path = nullptr;
+    Opaque.PathLength = 0;
+    Opaque.Base = Base;
+  }
+  Pointer(OpaqueTag, const ValueDecl *Base, const Type *FieldType,
+          const PointerPathEntry *Path, unsigned PathLength, bool OPE = false,
+          uint64_t Offset = 0)
+      : Offset(Offset), StorageKind(Storage::Opaque) {
+    Opaque.FieldType = {FieldType, OPE};
+    Opaque.Path = Path;
+    Opaque.PathLength = PathLength;
+    Opaque.Base = Base;
+  }
+  Pointer(OpaqueTag, const ValueDecl *Base, uint64_t Offset = 0)
+      : Offset(Offset), StorageKind(Storage::Opaque) {
+    Opaque.FieldType = {Base->getType().getTypePtr(), /*IsOnePastEnd=*/false};
+    Opaque.Path = nullptr;
+    Opaque.PathLength = 0;
+    Opaque.Base = Base;
+  }
 
   Pointer(Block *Pointee, unsigned Base, uint64_t Offset);
   explicit Pointer(PtrView V) : Pointer(V.Pointee, V.Base, V.Offset) {}
@@ -514,6 +597,7 @@ class Pointer {
     case Storage::Fn:
       return !Fn.Func;
     case Storage::Typeid:
+    case Storage::Opaque:
       return false;
     }
     llvm_unreachable("Unknown clang::interp::Storage enum");
@@ -581,6 +665,8 @@ class Pointer {
       return Fn.Func->getDecl()->getType();
     case Storage::Typeid:
       return QualType(Typeid.TypeInfoType, 0);
+    case Storage::Opaque:
+      return QualType(Opaque.FieldType.getPointer(), 0);
     }
     llvm_unreachable("Unhandled StorageKind");
   }
@@ -675,11 +761,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 {
@@ -1072,6 +1163,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 378a190184be9..51f945d4d4308 100644
--- a/clang/lib/AST/ByteCode/Program.cpp
+++ b/clang/lib/AST/ByteCode/Program.cpp
@@ -143,14 +143,16 @@ unsigned Program::getOrCreateDummy(DeclTy D, bool IsConstexprUnknown) {
     const auto *VD = cast<ValueDecl>(cast<const Decl *>(D));
     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/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 9d69de2a7c6fd..351d69b48cabe 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -23001,7 +23001,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..45d91ba197323 100644
--- a/clang/test/AST/ByteCode/builtin-object-size-codegen.c
+++ b/clang/test/AST/ByteCode/builtin-object-size-codegen.c
@@ -45,3 +45,67 @@ 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);
+}
+
+
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/enable_if.c b/clang/test/AST/ByteCode/enable_if.c
index 8148db2719449..9d81964a45998 100644
--- a/clang/test/AST/ByteCode/enable_if.c
+++ b/clang/test/AST/ByteCode/enable_if.c
@@ -50,7 +50,7 @@ size_t strnlen(const char *s, size_t maxlen)
                            "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}}
+size_t strnlen(const char *s, size_t maxlen) // both-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")))
@@ -70,7 +70,7 @@ void test2(const char *s, int i) {
   strnlen(c, i);
 // CHECK: call {{.*}}strnlen_chk
 #ifndef CODEGEN
-  strnlen(c, 999);  // ref-error{{'strnlen' is unavailable: 'maxlen' is larger than the buffer size}}
+  strnlen(c, 999);  // both-error{{'strnlen' is unavailable: 'maxlen' is larger than the buffer size}}
 #endif
 }
 
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/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 at -2 {{array size is not a constant expression}}
 // expected-note at -3 {{cannot refer to element 12 of non-array}}
+#elif __cplusplus == 201103L
+#if defined(NEW_INTERP)
+// expected-error at -6 {{only the first dimension of an allocated array may have dynamic size}}
+// expected-note at -7 {{cannot refer to element 12 of non-array}}
+#endif
 #elif __cplusplus < 201103L
-// expected-error at -5 {{cannot allocate object of variably modified type}}
-// expected-warning at -6 {{variable length arrays in C++ are a Clang extension}}
+#if defined(NEW_INTERP)
+// expected-error at -11 {{only the first dimension of an allocated array may have dynamic size}}
+// expected-note at -12 {{cannot refer to element 12 of non-array}}
+#else
+// expected-error at -14 {{cannot allocate object of variably modified type}}
+// expected-warning at -15 {{variable length arrays in C++ are a Clang extension}}
+#endif
 #endif
 
 #if __cplusplus >= 201103L



More information about the cfe-commits mailing list