Timm =?utf-8?q?Bäder?= <[email protected]>
Message-ID: <llvm.org/llvm/llvm-project/pull/[email protected]>
In-Reply-To:


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

This adds a new pointer type to represent something we don't have data for, but 
can still refer to.
They are similar to LValue APValues: it's a base (currently always a `VarDecl`) 
and a "lvalue designator" path.

For the first argument of `__builtin_object_size` and 
`__builtin_dynamic_object_size`, when we see an unknown variable, we emit an 
opaque pointer for the variable instead of a dummy pointer. Also if any load in 
that argument fails, we don't abort the evaluation but instead push an opaque 
pointer of the appropriate type on the stack.



This fixes the review complaint from 
https://github.com/llvm/llvm-project/pull/196548, namely that we didn't have a 
way to just point to an opaque type if a load fails.



Cases that still don't work:

 1) The stuff in `test/Sema/builtin-object-size-cxx14.cpp` that use 
non-constexpr functions in the first argument of `__builtin_object_size` 
(everything in the `InvalidBase` namespace). This behavior matches GCC.
 2) From `test/CodeGen/object-size.c`, the sample
     ```c
     void test26(void) {
        struct { int v[10]; } t[10];

        // CHECK: store i32 312
        gi = OBJECT_SIZE_BUILTIN(&t[1].v[12], 1);
    }
   results in 0, not 312. I don't know why it would result in 312. For modes 1 
and 3, we should consider the closest surrounding   variable, wich only has 10 
elements, so at index 12, we have 0 bytes we can read from. GCC also returns 0.

 3) From `test/CodeGen/object-size.c`, functions `test7` through `test17`, 
because they modify variables in the first argument of `__builtin_object_size`.
 4) This line from `test/CodeGen/pass-object-size.c`:
      ```c
       gi = NoViableOverloadObjectSize1(&t[1]);
      ```




This might seem overkill for now, but I plan on using opaque pointers as 
replacement for the current "dummy pointer" mechanism as well.

>From f934c4f2ffbce4deac392652dfea533459a41510 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <[email protected]>
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 debbc4c69da72516bc5bf2dc828219a6a2fe37e7 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 2/2] opaque pointers

---
 clang/lib/AST/ByteCode/Compiler.cpp           |  15 +-
 clang/lib/AST/ByteCode/Context.cpp            |  16 +-
 clang/lib/AST/ByteCode/Context.h              |   2 +-
 clang/lib/AST/ByteCode/Interp.cpp             | 127 +++++
 clang/lib/AST/ByteCode/Interp.h               | 115 ++--
 clang/lib/AST/ByteCode/InterpBuiltin.cpp      | 232 +-------
 .../AST/ByteCode/InterpBuiltinObjectSize.cpp  | 527 ++++++++++++++++++
 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            |  53 ++
 clang/lib/AST/ByteCode/Pointer.h              |  95 +++-
 clang/lib/AST/ByteCode/Program.cpp            |   6 +-
 clang/lib/AST/ByteCode/Record.h               |   1 +
 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  | 125 +++++
 clang/test/AST/ByteCode/enable_if.c           |   4 +-
 20 files changed, 1148 insertions(+), 268 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..1cb6768d6c67c 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);
@@ -8706,8 +8710,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 23bdc5ea28c03..795f63690bfcf 100644
--- a/clang/lib/AST/ByteCode/Interp.cpp
+++ b/clang/lib/AST/ByteCode/Interp.cpp
@@ -1612,6 +1612,29 @@ static bool getField(InterpState &S, CodePtr OpPC, const 
Pointer &Ptr,
     return false;
   }
 
+  if (Ptr.isOpaquePointer()) {
+    const OpaquePointer &OP = Ptr.asOpaquePointer();
+    const Record *R =
+        S.getContext().getRecord(OP.getFieldType()->getAsRecordDecl());
+    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 +1669,31 @@ 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 Record *R =
+        S.getContext().getRecord(OP.getFieldType()->getAsRecordDecl());
+    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 +1954,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) {
 
@@ -3219,6 +3268,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,
+                        uint64_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->getZExtSize();
+  } 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, OP.Path,
+                      OP.PathLength, PastEnd, NewOffset);
+  return true;
+}
+
 // FIXME: Would be nice to generate this instead of hardcoding it here.
 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..bc22c896db655 100644
--- a/clang/lib/AST/ByteCode/Interp.h
+++ b/clang/lib/AST/ByteCode/Interp.h
@@ -2252,6 +2252,37 @@ 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()) {
+      S.Stk.push<Pointer>(OpaqueTag{}, Ptr.asOpaquePointer().Base,
+                          Ptr.getType()->getPointeeType().getTypePtr());
+    } 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 +2677,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 +2701,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 +2713,11 @@ 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 +3477,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,
+                        uint64_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 +3510,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 +3606,12 @@ inline bool ArrayDecay(InterpState &S, CodePtr OpPC) {
       return false;
   }
 
+  if (Ptr.isOpaquePointer()) {
+    // llvm::errs()<< "ArrayDecay of Opaque pointer\n";
+    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..5f271d0e772a5
--- /dev/null
+++ b/clang/lib/AST/ByteCode/InterpBuiltinObjectSize.cpp
@@ -0,0 +1,527 @@
+//===------------- 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,
+};
+
+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()) {
+    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) {
+  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) {
+  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 ASTRecordLayout &Layout =
+          ASTCtx.getASTRecordLayout(CurType->getAsRecordDecl());
+      Offset += Layout.getBaseClassOffset(Entry.RD.getPointer()).getQuantity();
+
+      CurType = ASTCtx.getCanonicalTagType(Entry.RD.getPointer());
+    } break;
+
+    case PointerPathEntry::Field: {
+      const FieldDecl *FD = Entry.FD;
+      const ASTRecordLayout &Layout =
+          ASTCtx.getASTRecordLayout(FD->getParent());
+      Offset +=
+          
ASTCtx.toCharUnitsFromBits(Layout.getFieldOffset(FD->getFieldIndex()))
+              .getQuantity();
+
+      CurType = FD->getType();
+    } break;
+    case PointerPathEntry::Array: {
+      unsigned Index = Entry.Index;
+      SurroundingArrayOffset = Offset;
+      if (!CurType->isArrayType()) {
+        Offset += Index * ASTCtx.getTypeSizeInChars(CurType).getQuantity();
+        continue;
+      }
+      const ArrayType *AT = CurType->getAsArrayTypeUnsafe();
+      assert(AT);
+      Offset +=
+          Index * 
ASTCtx.getTypeSizeInChars(AT->getElementType()).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 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)
+    TypeSize = ASTCtx.getTypeSizeInChars(
+        computeFieldType(ASTCtx, OP, SurroundingArray | IgnoreBaseCasts));
+  else
+    TypeSize = ASTCtx.getTypeSizeInChars(OP.getObjectType());
+
+  // 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()) {
+    // llvm::errs() << "err2\n";
+    return std::nullopt;
+  }
+
+  // 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;
+  }
+
+  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';
+    }
+
+    if (!UseClosestSurroundingVariable || DetermineForCompleteObject) {
+      if (InvalidBase) {
+        if (b)
+          llvm::errs() << "err4\n";
+        return std::nullopt;
+      }
+    }
+
+    // Either the size of the full variable (Kind = 0 or 2) or the size of the
+    // closest surrounding variable (Kind = 1 or 3).
+    unsigned FullSize =
+        computeOpaqueSize(ASTCtx, Ptr, UseClosestSurroundingVariable);
+    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.
+    std::optional<uint64_t> Offset = computeOpaquePtrOffset(
+        ASTCtx, Ptr, UseClosestSurroundingVariable, Kind);
+
+    if (!Offset)
+      return std::nullopt;
+
+    if (b) {
+      llvm::errs() << "FullSize: " << FullSize << '\n';
+      llvm::errs() << "Offset : " << Offset << '\n';
+      llvm::errs() << "Offset : " << (*Offset + Ptr.getByteOffset()) << '\n';
+
+      llvm::errs() << FullSize << " - " << Offset << '\n';
+    }
+    *Offset += Ptr.getByteOffset();
+
+    if (*Offset > FullSize)
+      return 0u;
+
+    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..feffed981c2c5 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,11 @@ 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
+       << " Length: " << Opaque.PathLength;
+    OS << "} + " << Offset;
   }
 }
 
@@ -371,6 +395,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 +475,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 +1224,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..5a8f5d179de8b 100644
--- a/clang/lib/AST/ByteCode/Pointer.h
+++ b/clang/lib/AST/ByteCode/Pointer.h
@@ -370,7 +370,64 @@ 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(unsigned 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;
+  const Type *FieldType = nullptr;
+  const PointerPathEntry *Path = nullptr;
+  unsigned PathLength = 0;
+  bool IsOnePastEnd = false;
+
+  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->isPointerOrReferenceType())
+      return FieldType->getPointeeType();
+    return QualType(FieldType, 0);
+  }
+
+  /// If this is pointing to an array element, return the array.
+  QualType getSurroundingArray(const ASTContext &ASTCtx) const;
+};
+struct OpaqueTag {};
+
+enum class Storage { Int, Block, Fn, Typeid, Opaque };
 
 /// A pointer to a memory block, live or dead.
 ///
@@ -420,6 +477,33 @@ 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;
+    Opaque.Path = nullptr;
+    Opaque.PathLength = 0;
+    Opaque.Base = Base;
+    Opaque.IsOnePastEnd = false;
+  }
+  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;
+    Opaque.Path = Path;
+    Opaque.PathLength = PathLength;
+    Opaque.Base = Base;
+    Opaque.IsOnePastEnd = OPE;
+  }
+  Pointer(OpaqueTag, const ValueDecl *Base, uint64_t Offset = 0)
+      : Offset(Offset), StorageKind(Storage::Opaque) {
+    Opaque.FieldType = Base->getType().getTypePtr();
+    Opaque.Path = nullptr;
+    Opaque.PathLength = 0;
+    Opaque.Base = Base;
+    Opaque.IsOnePastEnd = false;
+  }
 
   Pointer(Block *Pointee, unsigned Base, uint64_t Offset);
   explicit Pointer(PtrView V) : Pointer(V.Pointee, V.Base, V.Offset) {}
@@ -514,6 +598,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 +666,8 @@ class Pointer {
       return Fn.Func->getDecl()->getType();
     case Storage::Typeid:
       return QualType(Typeid.TypeInfoType, 0);
+    case Storage::Opaque:
+      return QualType(Opaque.FieldType, 0);
     }
     llvm_unreachable("Unhandled StorageKind");
   }
@@ -675,11 +762,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 +1164,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/ByteCode/Record.h b/clang/lib/AST/ByteCode/Record.h
index e49f31a1d6a52..190a752b077b1 100644
--- a/clang/lib/AST/ByteCode/Record.h
+++ b/clang/lib/AST/ByteCode/Record.h
@@ -111,6 +111,7 @@ class Record final {
     assert(I < getNumBases());
     return &Bases[I];
   }
+  const Base *findBase(unsigned Offset) const;
   /// Returns a base descriptor.
   const Base *getBase(QualType T) const;
   /// Returns a base descriptor.
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..9b7e25564a81e 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,127 @@ 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);
+}
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
 }
 

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

Reply via email to