Author: Kees Cook
Date: 2026-06-08T17:49:39-07:00
New Revision: 564d69a8dec3183ebd78a779efdd8903bbe89f68

URL: 
https://github.com/llvm/llvm-project/commit/564d69a8dec3183ebd78a779efdd8903bbe89f68
DIFF: 
https://github.com/llvm/llvm-project/commit/564d69a8dec3183ebd78a779efdd8903bbe89f68.diff

LOG: [Clang][counted_by] Honor counted_by in __bdos on direct struct access 
(#201161)

__builtin_dynamic_object_size on a flexible array member must consult
the 'counted_by' attribute even when the containing struct is accessed
directly (a local or global variable) rather than through a pointer
dereference. The pointer-deref form (p->fam) already worked because the
constant evaluator could not determine the LValue for an opaque
parameter and fell through to the counted_by-aware runtime path in
CGBuiltin. The direct form (af.fam, gaf.fam) was being folded by
tryEvaluateBuiltinObjectSize to a layout-derived size (e.g. trailing
struct padding for locals, trailing initializer data for globals)
silently bypassing emitCountedBySize.

Make the AST constant evaluator refuse to fold __bdos on the same
operands that CGBuiltin's __bdos lowering classifies as a counted_by
FAM access. The check runs after the existing negative-offset early
return so that obviously out-of-bounds operands like &p->array[-42]
still fold to 0, preserving the behavior the sanitizer-bounds test in
attr-counted-by.c (test35) relies on.

Rather than duplicate the Expr-walking logic, promote CGBuiltin's
StructFieldAccess visitor into a shared function findStructFieldAccess
declared in clang/AST/Expr.h, with the visitor moved to an
anonymous-namespace implementation in Expr.cpp. Both CGBuiltin's
emitCountedBySize and the new ExprConstant check use it, so they
recognize
exactly the same set of expression shapes.

Note that the helper deliberately treats `&af.fam` (address-of-array)
as *not* a counted_by access. `&af.fam` designates the array object
as a whole and gets the layout-derived size, while `af.fam` (decayed)
and `&af.fam[idx]` designate element-pointers and get the count-based
size. This matches GCC 16's behavior.

The regression test covers the full {local, global, pointer-arg} x
{fam, &fam[idx], &fam} matrix (9 tests) with both -O0 and -O2 RUN
lines, autogenerated via update_cc_test_checks.py. -O0 checks the
frontend output directly: emitCountedBySize's counted_by.gep /
counted_by.load / flexible_array_member_size for the counted_by tests,
and the raw @llvm.objectsize.i64.p0 intrinsic call for the '&fam'
layout tests. -O2 pins down the post-optimization shape (smax(%n, 0)
for the runtime tests, ret i64 <constant> for the layout tests). All
nine tests match GCC 16's __bdos output.

Fixes: #200014

Added: 
    clang/test/CodeGen/attr-counted-by-issue200014.c

Modified: 
    clang/include/clang/AST/Expr.h
    clang/lib/AST/ByteCode/InterpBuiltin.cpp
    clang/lib/AST/Expr.cpp
    clang/lib/AST/ExprConstant.cpp
    clang/lib/CodeGen/CGBuiltin.cpp

Removed: 
    


################################################################################
diff  --git a/clang/include/clang/AST/Expr.h b/clang/include/clang/AST/Expr.h
index b91bf4a5375fb..eeac69cb1d0eb 100644
--- a/clang/include/clang/AST/Expr.h
+++ b/clang/include/clang/AST/Expr.h
@@ -7548,6 +7548,22 @@ inline const StreamingDiagnostic &operator<<(const 
StreamingDiagnostic &DB,
   return DB;
 }
 
+/// Walk @p E through parens, implicit casts, unary &/*, array subscripts and
+/// comma operators to find the head of a struct-field access -- typically a
+/// MemberExpr, or an LValueToRValue ImplicitCastExpr over a pointer-typed
+/// field. Returns nullptr for shapes we don't handle (multiple subscripts,
+/// non-comma binary ops, or '&fam' on an array lvalue which designates the
+/// array-as-a-whole rather than an element pointer).
+///
+/// If @p OutArrayIndex / @p OutArrayElementTy are non-null, they receive the
+/// index expression and base array type for forms like '&p->fam[idx]'.
+///
+/// Shared by CGBuiltin's __builtin_*_object_size lowering and the AST
+/// constant evaluator so they recognize the same 'counted_by' access shapes.
+const Expr *findStructFieldAccess(const Expr *E,
+                                  const Expr **OutArrayIndex = nullptr,
+                                  QualType *OutArrayElementTy = nullptr);
+
 } // end namespace clang
 
 #endif // LLVM_CLANG_AST_EXPR_H

diff  --git a/clang/lib/AST/ByteCode/InterpBuiltin.cpp 
b/clang/lib/AST/ByteCode/InterpBuiltin.cpp
index 9b76cdb75ef70..679895f797df5 100644
--- a/clang/lib/AST/ByteCode/InterpBuiltin.cpp
+++ b/clang/lib/AST/ByteCode/InterpBuiltin.cpp
@@ -2460,6 +2460,16 @@ UnsignedOrNone evaluateBuiltinObjectSize(const 
ASTContext &ASTCtx,
       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,

diff  --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp
index babb30750e41b..f3acb366dc079 100644
--- a/clang/lib/AST/Expr.cpp
+++ b/clang/lib/AST/Expr.cpp
@@ -25,6 +25,7 @@
 #include "clang/AST/IgnoreExpr.h"
 #include "clang/AST/Mangle.h"
 #include "clang/AST/RecordLayout.h"
+#include "clang/AST/StmtVisitor.h"
 #include "clang/AST/TypeBase.h"
 #include "clang/Basic/Builtins.h"
 #include "clang/Basic/CharInfo.h"
@@ -5702,3 +5703,66 @@ APValue &CompoundLiteralExpr::getStaticValue() const {
   assert(StaticValue);
   return *StaticValue;
 }
+
+namespace {
+/// Visitor that walks an Expr to the head of a struct-field access chain;
+/// see clang::findStructFieldAccess.
+class StructFieldAccessVisitor
+    : public ConstStmtVisitor<StructFieldAccessVisitor, const Expr *> {
+  bool AddrOfSeen = false;
+
+public:
+  const Expr *ArrayIndex = nullptr;
+  QualType ArrayElementTy;
+
+  const Expr *VisitMemberExpr(const MemberExpr *E) {
+    if (AddrOfSeen && E->getType()->isArrayType())
+      // '&fam' designates the array object as a whole, not the
+      // pointer-to-element value that 'fam' decays to.
+      return nullptr;
+    return E;
+  }
+
+  const Expr *VisitArraySubscriptExpr(const ArraySubscriptExpr *E) {
+    if (ArrayIndex)
+      // We don't support multiple subscripts.
+      return nullptr;
+
+    AddrOfSeen = false; // '&ptr->array[idx]' is okay.
+    ArrayIndex = E->getIdx();
+    ArrayElementTy = E->getBase()->getType();
+    return Visit(E->getBase());
+  }
+  const Expr *VisitCastExpr(const CastExpr *E) {
+    if (E->getCastKind() == CK_LValueToRValue)
+      return E;
+    return Visit(E->getSubExpr());
+  }
+  const Expr *VisitParenExpr(const ParenExpr *E) {
+    return Visit(E->getSubExpr());
+  }
+  const Expr *VisitUnaryAddrOf(const UnaryOperator *E) {
+    AddrOfSeen = true;
+    return Visit(E->getSubExpr());
+  }
+  const Expr *VisitUnaryDeref(const UnaryOperator *E) {
+    AddrOfSeen = false;
+    return Visit(E->getSubExpr());
+  }
+  const Expr *VisitBinaryOperator(const BinaryOperator *Op) {
+    return Op->isCommaOp() ? Visit(Op->getRHS()) : nullptr;
+  }
+};
+} // namespace
+
+const Expr *clang::findStructFieldAccess(const Expr *E,
+                                         const Expr **OutArrayIndex,
+                                         QualType *OutArrayElementTy) {
+  StructFieldAccessVisitor V;
+  const Expr *Result = V.Visit(E);
+  if (OutArrayIndex)
+    *OutArrayIndex = V.ArrayIndex;
+  if (OutArrayElementTy)
+    *OutArrayElementTy = V.ArrayElementTy;
+  return Result;
+}

diff  --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index f574443dad8c3..f88a751a18563 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -16291,8 +16291,14 @@ static bool determineEndOffset(EvalInfo &Info, 
SourceLocation ExprLoc,
 ///
 /// If @p WasError is non-null, this will report whether the failure to 
evaluate
 /// is to be treated as an Error in IntExprEvaluator.
+///
+/// If @p IsDynamic is true (i.e. we're evaluating
+/// __builtin_dynamic_object_size) and the operand designates a flexible array
+/// member annotated with 'counted_by', we refuse to fold so that IR generation
+/// can emit the count-based runtime size computation.
 static std::optional<uint64_t>
-tryEvaluateBuiltinObjectSize(const Expr *E, unsigned Type, EvalInfo &Info) {
+tryEvaluateBuiltinObjectSize(const Expr *E, unsigned Type, EvalInfo &Info,
+                             bool IsDynamic = false) {
   // Determine the denoted object.
   LValue LVal;
   {
@@ -16319,6 +16325,23 @@ tryEvaluateBuiltinObjectSize(const Expr *E, unsigned 
Type, EvalInfo &Info) {
   if (LVal.getLValueOffset().isNegative())
     return 0;
 
+  // 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). Checked after the negative-offset
+  // early return above so that obviously out-of-bounds operands still fold
+  // to 0, preserving existing behavior.
+  if (IsDynamic) {
+    const auto *ME = dyn_cast_or_null<MemberExpr>(findStructFieldAccess(E));
+    const auto *FD = ME ? dyn_cast<FieldDecl>(ME->getMemberDecl()) : nullptr;
+    if (FD && FD->getType()->isCountAttributedType())
+      return std::nullopt;
+  }
+
   CharUnits EndOffset;
   if (!determineEndOffset(Info, E->getExprLoc(), Type, LVal, EndOffset))
     return std::nullopt;
@@ -16458,8 +16481,9 @@ bool IntExprEvaluator::VisitBuiltinCallExpr(const 
CallExpr *E,
         E->getArg(1)->EvaluateKnownConstInt(Info.Ctx).getZExtValue();
     assert(Type <= 3 && "unexpected type");
 
+    bool IsDynamic = BuiltinOp == Builtin::BI__builtin_dynamic_object_size;
     if (std::optional<uint64_t> Size =
-            tryEvaluateBuiltinObjectSize(E->getArg(0), Type, Info))
+            tryEvaluateBuiltinObjectSize(E->getArg(0), Type, Info, IsDynamic))
       return Success(*Size, E);
 
     if (E->getArg(0)->HasSideEffects(Info.Ctx))

diff  --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp
index e5c47073de30f..682b125890fe1 100644
--- a/clang/lib/CodeGen/CGBuiltin.cpp
+++ b/clang/lib/CodeGen/CGBuiltin.cpp
@@ -903,58 +903,6 @@ CodeGenFunction::evaluateOrEmitBuiltinObjectSize(const 
Expr *E, unsigned Type,
   return emitBuiltinObjectSize(E, Type, ResType, EmittedE, IsDynamic);
 }
 
-namespace {
-
-/// StructFieldAccess is a simple visitor class to grab the first MemberExpr
-/// from an Expr. It records any ArraySubscriptExpr we meet along the way.
-class StructFieldAccess
-    : public ConstStmtVisitor<StructFieldAccess, const Expr *> {
-  bool AddrOfSeen = false;
-
-public:
-  const Expr *ArrayIndex = nullptr;
-  QualType ArrayElementTy;
-
-  const Expr *VisitMemberExpr(const MemberExpr *E) {
-    if (AddrOfSeen && E->getType()->isArrayType())
-      // Avoid forms like '&ptr->array'.
-      return nullptr;
-    return E;
-  }
-
-  const Expr *VisitArraySubscriptExpr(const ArraySubscriptExpr *E) {
-    if (ArrayIndex)
-      // We don't support multiple subscripts.
-      return nullptr;
-
-    AddrOfSeen = false; // '&ptr->array[idx]' is okay.
-    ArrayIndex = E->getIdx();
-    ArrayElementTy = E->getBase()->getType();
-    return Visit(E->getBase());
-  }
-  const Expr *VisitCastExpr(const CastExpr *E) {
-    if (E->getCastKind() == CK_LValueToRValue)
-      return E;
-    return Visit(E->getSubExpr());
-  }
-  const Expr *VisitParenExpr(const ParenExpr *E) {
-    return Visit(E->getSubExpr());
-  }
-  const Expr *VisitUnaryAddrOf(const clang::UnaryOperator *E) {
-    AddrOfSeen = true;
-    return Visit(E->getSubExpr());
-  }
-  const Expr *VisitUnaryDeref(const clang::UnaryOperator *E) {
-    AddrOfSeen = false;
-    return Visit(E->getSubExpr());
-  }
-  const Expr *VisitBinaryOperator(const clang::BinaryOperator *Op) {
-    return Op->isCommaOp() ? Visit(Op->getRHS()) : nullptr;
-  }
-};
-
-} // end anonymous namespace
-
 /// Find a struct's flexible array member. It may be embedded inside multiple
 /// sub-structs, but must still be the last field.
 static const FieldDecl *FindFlexibleArrayMemberField(CodeGenFunction &CGF,
@@ -1044,12 +992,12 @@ llvm::Value *CodeGenFunction::emitCountedBySize(const 
Expr *E,
   // structure. Therefore, because of the above issue, we choose to match what
   // GCC does for consistency's sake.
 
-  StructFieldAccess Visitor;
-  E = Visitor.Visit(E);
+  const Expr *Idx = nullptr;
+  QualType ArrayElementTy;
+  E = findStructFieldAccess(E, &Idx, &ArrayElementTy);
   if (!E)
     return nullptr;
 
-  const Expr *Idx = Visitor.ArrayIndex;
   if (Idx) {
     if (Idx->HasSideEffects(getContext()))
       // We can't have side-effects.
@@ -1069,14 +1017,14 @@ llvm::Value *CodeGenFunction::emitCountedBySize(const 
Expr *E,
   // __counted_by on either a flexible array member or a pointer into a struct
   // with a flexible array member.
   if (const auto *ME = dyn_cast<MemberExpr>(E))
-    return emitCountedByMemberSize(ME, Idx, EmittedE, Visitor.ArrayElementTy,
-                                   Type, ResType);
+    return emitCountedByMemberSize(ME, Idx, EmittedE, ArrayElementTy, Type,
+                                   ResType);
 
   // __counted_by on a pointer in a struct.
   if (const auto *ICE = dyn_cast<ImplicitCastExpr>(E);
       ICE && ICE->getCastKind() == CK_LValueToRValue)
-    return emitCountedByPointerSize(ICE, Idx, EmittedE, Visitor.ArrayElementTy,
-                                    Type, ResType);
+    return emitCountedByPointerSize(ICE, Idx, EmittedE, ArrayElementTy, Type,
+                                    ResType);
 
   return nullptr;
 }

diff  --git a/clang/test/CodeGen/attr-counted-by-issue200014.c 
b/clang/test/CodeGen/attr-counted-by-issue200014.c
new file mode 100644
index 0000000000000..02c50d23b6def
--- /dev/null
+++ b/clang/test/CodeGen/attr-counted-by-issue200014.c
@@ -0,0 +1,347 @@
+// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py 
UTC_ARGS: --version 6
+//
+// FRONTEND prefix: post-mem2reg/sroa output of clang -O0 lit-side. Pins
+// down the IR emitCountedBySize emits directly (so a regression that
+// re-folds __bdos at AST time would lose 'counted_by.load' / 'mul' /
+// 'flexible_array_member_size' here). The same prefix is used for both
+// the default constant-evaluator and -fexperimental-new-constant-interpreter
+// runs: those must produce identical IR for every test below, which is the
+// regression guard for "the bytecode interpreter also defers to runtime
+// for counted_by FAM __bdos."
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O0 
-Wno-missing-declarations \
+// RUN:   -disable-O0-optnone -emit-llvm -o - %s \
+// RUN:   | opt -S -passes=mem2reg,sroa | FileCheck %s --check-prefix=FRONTEND
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O0 
-Wno-missing-declarations \
+// RUN:   -fexperimental-new-constant-interpreter -disable-O0-optnone \
+// RUN:   -emit-llvm -o - %s \
+// RUN:   | opt -S -passes=mem2reg,sroa | FileCheck %s --check-prefix=FRONTEND
+// O2 prefix: post-optimization shape; pins down the concrete folded
+// constants for the '&fam' layout tests.
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O2 
-Wno-missing-declarations \
+// RUN:   -emit-llvm -o - %s | FileCheck %s --check-prefix=O2
+
+// See https://github.com/llvm/llvm-project/issues/200014
+//
+// __builtin_dynamic_object_size on a flexible array member must consult the
+// 'counted_by' attribute on the same expression shapes the IR runtime path
+// in CGBuiltin handles -- and *only* those.
+//
+// Pre-fix, the direct-struct-access tests (local/global x {fam, &fam[idx]})
+// were folded by the AST constant evaluator to layout-derived constants
+// (trailing struct padding for locals; trailing initializer data for
+// globals), silently bypassing emitCountedBySize.
+//
+// The '&fam' (array-as-a-whole) tests are *intentionally* layout-derived in
+// both GCC and Clang: '&af.fam' designates the array object itself, not
+// the pointer-to-element that 'af.fam' decays to, and findStructFieldAccess
+// bails out on that shape.
+//
+// Behavior matrix (verified against GCC 16 for __bdos(_, 1)):
+//
+//   form \ base   | local af (count=%n) | global gaf (count=10,init=15) | 
pointer p
+//   
--------------+---------------------+-------------------------------+-----------
+//   fam (decay)   | runtime: count*1    | runtime: count*1              | 
runtime
+//   &fam[idx]     | runtime: count-idx  | runtime: count-idx            | 
runtime
+//   &fam (whole)  | layout: 7 (pad)     | layout: 15 (init)             | 
unknown:-1
+//
+// See the RUN-line block above for the prefix conventions
+// (FRONTEND covers both the default and bytecode -O0 const-evaluators;
+// O2 covers post-optimization).
+
+typedef __SIZE_TYPE__ size_t;
+
+struct annotated_flex {
+    size_t  count;
+    char    induce_padding;
+    char    fam[] __attribute__((counted_by(count)));
+};
+
+// Initializer length 15 bytes ("i am very long" + NUL); count lies as 10.
+struct annotated_flex gaf = {
+    .fam = "i am very long",
+    .count = 10,
+};
+
+extern size_t sink;
+
+// Test: local x fam (decayed pointer-to-element).
+// Pre-fix bug: AST const-eval folded this to trailing struct padding (7).
+// Expected: runtime counted_by path -- look for 'counted_by.load' in
+// FRONTEND IR and 'smax(%n, 0)' at -O2.
+// FRONTEND-LABEL: define dso_local i64 @local_fam(
+// FRONTEND-SAME: i64 noundef [[N:%.*]]) #[[ATTR0:[0-9]+]] {
+// FRONTEND-NEXT:  [[ENTRY:.*:]]
+// FRONTEND-NEXT:    [[AF:%.*]] = alloca [[STRUCT_ANNOTATED_FLEX:%.*]], align 8
+// FRONTEND-NEXT:    [[COUNT:%.*]] = getelementptr inbounds nuw 
[[STRUCT_ANNOTATED_FLEX]], ptr [[AF]], i32 0, i32 0
+// FRONTEND-NEXT:    store i64 [[N]], ptr [[COUNT]], align 8
+// FRONTEND-NEXT:    [[INDUCE_PADDING:%.*]] = getelementptr inbounds nuw 
[[STRUCT_ANNOTATED_FLEX]], ptr [[AF]], i32 0, i32 1
+// FRONTEND-NEXT:    store i8 0, ptr [[INDUCE_PADDING]], align 8
+// FRONTEND-NEXT:    [[TMP0:%.*]] = getelementptr i8, ptr [[AF]], i64 9
+// FRONTEND-NEXT:    call void @llvm.memset.p0.i64(ptr align 1 [[TMP0]], i8 0, 
i64 7, i1 false)
+// FRONTEND-NEXT:    [[FAM:%.*]] = getelementptr inbounds nuw 
[[STRUCT_ANNOTATED_FLEX]], ptr [[AF]], i32 0, i32 2
+// FRONTEND-NEXT:    [[ARRAYDECAY:%.*]] = getelementptr inbounds [0 x i8], ptr 
[[FAM]], i64 0, i64 0
+// FRONTEND-NEXT:    [[TMP1:%.*]] = ptrtoint ptr [[ARRAYDECAY]] to i64
+// FRONTEND-NEXT:    store i64 [[TMP1]], ptr @sink, align 8
+// FRONTEND-NEXT:    [[FAM1:%.*]] = getelementptr inbounds nuw 
[[STRUCT_ANNOTATED_FLEX]], ptr [[AF]], i32 0, i32 2
+// FRONTEND-NEXT:    [[ARRAYDECAY2:%.*]] = getelementptr inbounds [0 x i8], 
ptr [[FAM1]], i64 0, i64 0
+// FRONTEND-NEXT:    [[COUNTED_BY_GEP:%.*]] = getelementptr inbounds 
[[STRUCT_ANNOTATED_FLEX]], ptr [[AF]], i32 0, i32 0
+// FRONTEND-NEXT:    [[COUNTED_BY_LOAD:%.*]] = load i64, ptr 
[[COUNTED_BY_GEP]], align 4
+// FRONTEND-NEXT:    [[FLEXIBLE_ARRAY_MEMBER_SIZE:%.*]] = mul nuw i64 
[[COUNTED_BY_LOAD]], 1
+// FRONTEND-NEXT:    [[TMP2:%.*]] = icmp sgt i64 
[[FLEXIBLE_ARRAY_MEMBER_SIZE]], -1
+// FRONTEND-NEXT:    [[TMP3:%.*]] = select i1 [[TMP2]], i64 
[[FLEXIBLE_ARRAY_MEMBER_SIZE]], i64 0
+// FRONTEND-NEXT:    ret i64 [[TMP3]]
+//
+// O2-LABEL: define dso_local noundef range(i64 0, -9223372036854775808) i64 
@local_fam(
+// O2-SAME: i64 noundef [[N:%.*]]) local_unnamed_addr #[[ATTR0:[0-9]+]] {
+// O2-NEXT:  [[ENTRY:.*:]]
+// O2-NEXT:    [[AF:%.*]] = alloca [[STRUCT_ANNOTATED_FLEX:%.*]], align 8
+// O2-NEXT:    call void @llvm.lifetime.start.p0(ptr nonnull [[AF]]) 
#[[ATTR6:[0-9]+]]
+// O2-NEXT:    [[TMP0:%.*]] = getelementptr inbounds nuw i8, ptr [[AF]], i64 9
+// O2-NEXT:    [[TMP1:%.*]] = ptrtoint ptr [[TMP0]] to i64
+// O2-NEXT:    store i64 [[TMP1]], ptr @sink, align 8, !tbaa 
[[LONG_TBAA5:![0-9]+]]
+// O2-NEXT:    [[TMP2:%.*]] = call i64 @llvm.smax.i64(i64 [[N]], i64 0)
+// O2-NEXT:    call void @llvm.lifetime.end.p0(ptr nonnull [[AF]]) #[[ATTR6]]
+// O2-NEXT:    ret i64 [[TMP2]]
+//
+size_t local_fam(size_t n) {
+    struct annotated_flex af = { .count = n };
+    sink = (size_t)(__UINTPTR_TYPE__)af.fam;
+    return __builtin_dynamic_object_size(af.fam, 1);
+}
+
+// Test: local x &fam[idx].
+// Pre-fix bug: AST const-eval folded this to padding-minus-idx (5).
+// Expected: runtime counted_by path with index subtracted -- look for
+// 'counted_by.load' + 'sub' in FRONTEND IR and 'add %n, -2' at -O2.
+// FRONTEND-LABEL: define dso_local i64 @local_subscript(
+// FRONTEND-SAME: i64 noundef [[N:%.*]]) #[[ATTR0]] {
+// FRONTEND-NEXT:  [[ENTRY:.*:]]
+// FRONTEND-NEXT:    [[AF:%.*]] = alloca [[STRUCT_ANNOTATED_FLEX:%.*]], align 8
+// FRONTEND-NEXT:    [[COUNT:%.*]] = getelementptr inbounds nuw 
[[STRUCT_ANNOTATED_FLEX]], ptr [[AF]], i32 0, i32 0
+// FRONTEND-NEXT:    store i64 [[N]], ptr [[COUNT]], align 8
+// FRONTEND-NEXT:    [[INDUCE_PADDING:%.*]] = getelementptr inbounds nuw 
[[STRUCT_ANNOTATED_FLEX]], ptr [[AF]], i32 0, i32 1
+// FRONTEND-NEXT:    store i8 0, ptr [[INDUCE_PADDING]], align 8
+// FRONTEND-NEXT:    [[TMP0:%.*]] = getelementptr i8, ptr [[AF]], i64 9
+// FRONTEND-NEXT:    call void @llvm.memset.p0.i64(ptr align 1 [[TMP0]], i8 0, 
i64 7, i1 false)
+// FRONTEND-NEXT:    [[FAM:%.*]] = getelementptr inbounds nuw 
[[STRUCT_ANNOTATED_FLEX]], ptr [[AF]], i32 0, i32 2
+// FRONTEND-NEXT:    [[ARRAYIDX:%.*]] = getelementptr inbounds [0 x i8], ptr 
[[FAM]], i64 0, i64 2
+// FRONTEND-NEXT:    [[TMP1:%.*]] = ptrtoint ptr [[ARRAYIDX]] to i64
+// FRONTEND-NEXT:    store i64 [[TMP1]], ptr @sink, align 8
+// FRONTEND-NEXT:    [[FAM1:%.*]] = getelementptr inbounds nuw 
[[STRUCT_ANNOTATED_FLEX]], ptr [[AF]], i32 0, i32 2
+// FRONTEND-NEXT:    [[ARRAYIDX2:%.*]] = getelementptr inbounds [0 x i8], ptr 
[[FAM1]], i64 0, i64 2
+// FRONTEND-NEXT:    [[COUNTED_BY_GEP:%.*]] = getelementptr inbounds 
[[STRUCT_ANNOTATED_FLEX]], ptr [[AF]], i32 0, i32 0
+// FRONTEND-NEXT:    [[COUNTED_BY_LOAD:%.*]] = load i64, ptr 
[[COUNTED_BY_GEP]], align 4
+// FRONTEND-NEXT:    [[FLEXIBLE_ARRAY_MEMBER_SIZE:%.*]] = mul nuw i64 
[[COUNTED_BY_LOAD]], 1
+// FRONTEND-NEXT:    [[RESULT:%.*]] = sub nuw i64 
[[FLEXIBLE_ARRAY_MEMBER_SIZE]], 2
+// FRONTEND-NEXT:    [[TMP2:%.*]] = icmp sgt i64 [[RESULT]], -1
+// FRONTEND-NEXT:    [[TMP3:%.*]] = and i1 true, [[TMP2]]
+// FRONTEND-NEXT:    [[TMP4:%.*]] = select i1 [[TMP3]], i64 [[RESULT]], i64 0
+// FRONTEND-NEXT:    ret i64 [[TMP4]]
+//
+// O2-LABEL: define dso_local noundef range(i64 0, -9223372036854775808) i64 
@local_subscript(
+// O2-SAME: i64 noundef [[N:%.*]]) local_unnamed_addr #[[ATTR0]] {
+// O2-NEXT:  [[ENTRY:.*:]]
+// O2-NEXT:    [[AF:%.*]] = alloca [[STRUCT_ANNOTATED_FLEX:%.*]], align 8
+// O2-NEXT:    call void @llvm.lifetime.start.p0(ptr nonnull [[AF]]) #[[ATTR6]]
+// O2-NEXT:    [[ARRAYIDX:%.*]] = getelementptr inbounds nuw i8, ptr [[AF]], 
i64 11
+// O2-NEXT:    [[TMP0:%.*]] = ptrtoint ptr [[ARRAYIDX]] to i64
+// O2-NEXT:    store i64 [[TMP0]], ptr @sink, align 8, !tbaa [[LONG_TBAA5]]
+// O2-NEXT:    [[RESULT:%.*]] = add i64 [[N]], -2
+// O2-NEXT:    [[TMP1:%.*]] = call i64 @llvm.smax.i64(i64 [[RESULT]], i64 0)
+// O2-NEXT:    call void @llvm.lifetime.end.p0(ptr nonnull [[AF]]) #[[ATTR6]]
+// O2-NEXT:    ret i64 [[TMP1]]
+//
+size_t local_subscript(size_t n) {
+    struct annotated_flex af = { .count = n };
+    sink = (size_t)(__UINTPTR_TYPE__)&af.fam[2];
+    return __builtin_dynamic_object_size(&af.fam[2], 1);
+}
+
+// Test: local x &fam (array-as-a-whole).
+// Intentionally layout-derived, NOT counted_by (matches GCC). The frontend
+// must fall through to @llvm.objectsize, which folds to the trailing struct
+// padding (7) at -O2.
+// FRONTEND-LABEL: define dso_local i64 @local_addrof(
+// FRONTEND-SAME: i64 noundef [[N:%.*]]) #[[ATTR0]] {
+// FRONTEND-NEXT:  [[ENTRY:.*:]]
+// FRONTEND-NEXT:    [[AF:%.*]] = alloca [[STRUCT_ANNOTATED_FLEX:%.*]], align 8
+// FRONTEND-NEXT:    [[COUNT:%.*]] = getelementptr inbounds nuw 
[[STRUCT_ANNOTATED_FLEX]], ptr [[AF]], i32 0, i32 0
+// FRONTEND-NEXT:    store i64 [[N]], ptr [[COUNT]], align 8
+// FRONTEND-NEXT:    [[INDUCE_PADDING:%.*]] = getelementptr inbounds nuw 
[[STRUCT_ANNOTATED_FLEX]], ptr [[AF]], i32 0, i32 1
+// FRONTEND-NEXT:    store i8 0, ptr [[INDUCE_PADDING]], align 8
+// FRONTEND-NEXT:    [[TMP0:%.*]] = getelementptr i8, ptr [[AF]], i64 9
+// FRONTEND-NEXT:    call void @llvm.memset.p0.i64(ptr align 1 [[TMP0]], i8 0, 
i64 7, i1 false)
+// FRONTEND-NEXT:    [[FAM:%.*]] = getelementptr inbounds nuw 
[[STRUCT_ANNOTATED_FLEX]], ptr [[AF]], i32 0, i32 2
+// FRONTEND-NEXT:    [[TMP1:%.*]] = ptrtoint ptr [[FAM]] to i64
+// FRONTEND-NEXT:    store i64 [[TMP1]], ptr @sink, align 8
+// FRONTEND-NEXT:    [[FAM1:%.*]] = getelementptr inbounds nuw 
[[STRUCT_ANNOTATED_FLEX]], ptr [[AF]], i32 0, i32 2
+// FRONTEND-NEXT:    [[TMP2:%.*]] = call i64 @llvm.objectsize.i64.p0(ptr 
[[FAM1]], i1 false, i1 true, i1 true)
+// FRONTEND-NEXT:    ret i64 [[TMP2]]
+//
+// O2-LABEL: define dso_local noundef i64 @local_addrof(
+// O2-SAME: i64 noundef [[N:%.*]]) local_unnamed_addr #[[ATTR0]] {
+// O2-NEXT:  [[ENTRY:.*:]]
+// O2-NEXT:    [[AF:%.*]] = alloca [[STRUCT_ANNOTATED_FLEX:%.*]], align 8
+// O2-NEXT:    call void @llvm.lifetime.start.p0(ptr nonnull [[AF]]) #[[ATTR6]]
+// O2-NEXT:    [[TMP0:%.*]] = getelementptr inbounds nuw i8, ptr [[AF]], i64 9
+// O2-NEXT:    [[TMP1:%.*]] = ptrtoint ptr [[TMP0]] to i64
+// O2-NEXT:    store i64 [[TMP1]], ptr @sink, align 8, !tbaa [[LONG_TBAA5]]
+// O2-NEXT:    call void @llvm.lifetime.end.p0(ptr nonnull [[AF]]) #[[ATTR6]]
+// O2-NEXT:    ret i64 7
+//
+size_t local_addrof(size_t n) {
+    struct annotated_flex af = { .count = n };
+    sink = (size_t)(__UINTPTR_TYPE__)&af.fam;
+    return __builtin_dynamic_object_size(&af.fam, 1);
+}
+
+// Test: global x fam (decayed pointer-to-element).
+// Pre-fix bug: AST const-eval folded this to the static initializer length
+// (15 = strlen("i am very long")+1), bypassing the counted_by lie of 10.
+// Expected: runtime path -- 'load i64, ptr @gaf' (the count field) in both
+// FRONTEND and O2 IR.
+// FRONTEND-LABEL: define dso_local i64 @global_fam(
+// FRONTEND-SAME: ) #[[ATTR0]] {
+// FRONTEND-NEXT:  [[ENTRY:.*:]]
+// FRONTEND-NEXT:    [[COUNTED_BY_LOAD:%.*]] = load i64, ptr @gaf, align 4
+// FRONTEND-NEXT:    [[FLEXIBLE_ARRAY_MEMBER_SIZE:%.*]] = mul nuw i64 
[[COUNTED_BY_LOAD]], 1
+// FRONTEND-NEXT:    [[TMP0:%.*]] = icmp sgt i64 
[[FLEXIBLE_ARRAY_MEMBER_SIZE]], -1
+// FRONTEND-NEXT:    [[TMP1:%.*]] = select i1 [[TMP0]], i64 
[[FLEXIBLE_ARRAY_MEMBER_SIZE]], i64 0
+// FRONTEND-NEXT:    ret i64 [[TMP1]]
+//
+// O2-LABEL: define dso_local range(i64 0, -9223372036854775808) i64 
@global_fam(
+// O2-SAME: ) local_unnamed_addr #[[ATTR2:[0-9]+]] {
+// O2-NEXT:  [[ENTRY:.*:]]
+// O2-NEXT:    [[COUNTED_BY_LOAD:%.*]] = load i64, ptr @gaf, align 8
+// O2-NEXT:    [[TMP0:%.*]] = tail call i64 @llvm.smax.i64(i64 
[[COUNTED_BY_LOAD]], i64 0)
+// O2-NEXT:    ret i64 [[TMP0]]
+//
+size_t global_fam(void) {
+    return __builtin_dynamic_object_size(gaf.fam, 1);
+}
+
+// Test: global x &fam[idx].
+// Pre-fix bug: folded to (initializer_length - idx) = 12.
+// Expected: runtime path with index subtracted -- 'load i64, ptr @gaf' then
+// 'sub' in FRONTEND IR / 'add %count, -3' at -O2.
+// FRONTEND-LABEL: define dso_local i64 @global_subscript(
+// FRONTEND-SAME: ) #[[ATTR0]] {
+// FRONTEND-NEXT:  [[ENTRY:.*:]]
+// FRONTEND-NEXT:    [[COUNTED_BY_LOAD:%.*]] = load i64, ptr @gaf, align 4
+// FRONTEND-NEXT:    [[FLEXIBLE_ARRAY_MEMBER_SIZE:%.*]] = mul nuw i64 
[[COUNTED_BY_LOAD]], 1
+// FRONTEND-NEXT:    [[RESULT:%.*]] = sub nuw i64 
[[FLEXIBLE_ARRAY_MEMBER_SIZE]], 3
+// FRONTEND-NEXT:    [[TMP0:%.*]] = icmp sgt i64 [[RESULT]], -1
+// FRONTEND-NEXT:    [[TMP1:%.*]] = and i1 true, [[TMP0]]
+// FRONTEND-NEXT:    [[TMP2:%.*]] = select i1 [[TMP1]], i64 [[RESULT]], i64 0
+// FRONTEND-NEXT:    ret i64 [[TMP2]]
+//
+// O2-LABEL: define dso_local range(i64 0, -9223372036854775808) i64 
@global_subscript(
+// O2-SAME: ) local_unnamed_addr #[[ATTR2]] {
+// O2-NEXT:  [[ENTRY:.*:]]
+// O2-NEXT:    [[COUNTED_BY_LOAD:%.*]] = load i64, ptr @gaf, align 8
+// O2-NEXT:    [[RESULT:%.*]] = add i64 [[COUNTED_BY_LOAD]], -3
+// O2-NEXT:    [[TMP0:%.*]] = tail call i64 @llvm.smax.i64(i64 [[RESULT]], i64 
0)
+// O2-NEXT:    ret i64 [[TMP0]]
+//
+size_t global_subscript(void) {
+    return __builtin_dynamic_object_size(&gaf.fam[3], 1);
+}
+
+// Test: global x &fam (array-as-a-whole).
+// Intentionally layout-derived. Frontend emits @llvm.objectsize, which
+// folds to the initializer length (15) at -O2. Matches GCC.
+// FRONTEND-LABEL: define dso_local i64 @global_addrof(
+// FRONTEND-SAME: ) #[[ATTR0]] {
+// FRONTEND-NEXT:  [[ENTRY:.*:]]
+// FRONTEND-NEXT:    [[TMP0:%.*]] = call i64 @llvm.objectsize.i64.p0(ptr 
getelementptr inbounds nuw (i8, ptr @gaf, i64 9), i1 false, i1 true, i1 true)
+// FRONTEND-NEXT:    ret i64 [[TMP0]]
+//
+// O2-LABEL: define dso_local noundef i64 @global_addrof(
+// O2-SAME: ) local_unnamed_addr #[[ATTR3:[0-9]+]] {
+// O2-NEXT:  [[ENTRY:.*:]]
+// O2-NEXT:    ret i64 15
+//
+size_t global_addrof(void) {
+    return __builtin_dynamic_object_size(&gaf.fam, 1);
+}
+
+// Test: pointer x fam (decayed pointer-to-element).
+// This form already worked pre-fix: const-eval can't resolve %p, so it
+// fell through to CGBuiltin's counted_by path. Kept here as a regression
+// boundary -- the runtime IR must keep loading the count from %p.
+// FRONTEND-LABEL: define dso_local i64 @ptr_fam(
+// FRONTEND-SAME: ptr noundef [[P:%.*]]) #[[ATTR0]] {
+// FRONTEND-NEXT:  [[ENTRY:.*:]]
+// FRONTEND-NEXT:    [[FAM:%.*]] = getelementptr inbounds nuw 
[[STRUCT_ANNOTATED_FLEX:%.*]], ptr [[P]], i32 0, i32 2
+// FRONTEND-NEXT:    [[ARRAYDECAY:%.*]] = getelementptr inbounds [0 x i8], ptr 
[[FAM]], i64 0, i64 0
+// FRONTEND-NEXT:    [[COUNTED_BY_GEP:%.*]] = getelementptr inbounds 
[[STRUCT_ANNOTATED_FLEX]], ptr [[P]], i32 0, i32 0
+// FRONTEND-NEXT:    [[COUNTED_BY_LOAD:%.*]] = load i64, ptr 
[[COUNTED_BY_GEP]], align 4
+// FRONTEND-NEXT:    [[FLEXIBLE_ARRAY_MEMBER_SIZE:%.*]] = mul nuw i64 
[[COUNTED_BY_LOAD]], 1
+// FRONTEND-NEXT:    [[TMP0:%.*]] = icmp sgt i64 
[[FLEXIBLE_ARRAY_MEMBER_SIZE]], -1
+// FRONTEND-NEXT:    [[TMP1:%.*]] = select i1 [[TMP0]], i64 
[[FLEXIBLE_ARRAY_MEMBER_SIZE]], i64 0
+// FRONTEND-NEXT:    ret i64 [[TMP1]]
+//
+// O2-LABEL: define dso_local range(i64 0, -9223372036854775808) i64 @ptr_fam(
+// O2-SAME: ptr noundef readonly captures(none) [[P:%.*]]) local_unnamed_addr 
#[[ATTR4:[0-9]+]] {
+// O2-NEXT:  [[ENTRY:.*:]]
+// O2-NEXT:    [[COUNTED_BY_LOAD:%.*]] = load i64, ptr [[P]], align 4
+// O2-NEXT:    [[TMP0:%.*]] = tail call i64 @llvm.smax.i64(i64 
[[COUNTED_BY_LOAD]], i64 0)
+// O2-NEXT:    ret i64 [[TMP0]]
+//
+size_t ptr_fam(struct annotated_flex *p) {
+    return __builtin_dynamic_object_size(p->fam, 1);
+}
+
+// Test: pointer x &fam[idx].
+// Same regression-boundary purpose as ptr_fam: runtime path with index
+// subtracted from the count loaded via %p.
+// FRONTEND-LABEL: define dso_local i64 @ptr_subscript(
+// FRONTEND-SAME: ptr noundef [[P:%.*]]) #[[ATTR0]] {
+// FRONTEND-NEXT:  [[ENTRY:.*:]]
+// FRONTEND-NEXT:    [[FAM:%.*]] = getelementptr inbounds nuw 
[[STRUCT_ANNOTATED_FLEX:%.*]], ptr [[P]], i32 0, i32 2
+// FRONTEND-NEXT:    [[ARRAYIDX:%.*]] = getelementptr inbounds [0 x i8], ptr 
[[FAM]], i64 0, i64 3
+// FRONTEND-NEXT:    [[COUNTED_BY_GEP:%.*]] = getelementptr inbounds 
[[STRUCT_ANNOTATED_FLEX]], ptr [[P]], i32 0, i32 0
+// FRONTEND-NEXT:    [[COUNTED_BY_LOAD:%.*]] = load i64, ptr 
[[COUNTED_BY_GEP]], align 4
+// FRONTEND-NEXT:    [[FLEXIBLE_ARRAY_MEMBER_SIZE:%.*]] = mul nuw i64 
[[COUNTED_BY_LOAD]], 1
+// FRONTEND-NEXT:    [[RESULT:%.*]] = sub nuw i64 
[[FLEXIBLE_ARRAY_MEMBER_SIZE]], 3
+// FRONTEND-NEXT:    [[TMP0:%.*]] = icmp sgt i64 [[RESULT]], -1
+// FRONTEND-NEXT:    [[TMP1:%.*]] = and i1 true, [[TMP0]]
+// FRONTEND-NEXT:    [[TMP2:%.*]] = select i1 [[TMP1]], i64 [[RESULT]], i64 0
+// FRONTEND-NEXT:    ret i64 [[TMP2]]
+//
+// O2-LABEL: define dso_local range(i64 0, -9223372036854775808) i64 
@ptr_subscript(
+// O2-SAME: ptr noundef readonly captures(none) [[P:%.*]]) local_unnamed_addr 
#[[ATTR4]] {
+// O2-NEXT:  [[ENTRY:.*:]]
+// O2-NEXT:    [[COUNTED_BY_LOAD:%.*]] = load i64, ptr [[P]], align 4
+// O2-NEXT:    [[RESULT:%.*]] = add i64 [[COUNTED_BY_LOAD]], -3
+// O2-NEXT:    [[TMP0:%.*]] = tail call i64 @llvm.smax.i64(i64 [[RESULT]], i64 
0)
+// O2-NEXT:    ret i64 [[TMP0]]
+//
+size_t ptr_subscript(struct annotated_flex *p) {
+    return __builtin_dynamic_object_size(&p->fam[3], 1);
+}
+
+// Test: pointer x &fam (array-as-a-whole over an opaque base).
+// Layout-derived path, but @llvm.objectsize can't determine the size of
+// the underlying object, so it returns -1 (size_t max). Matches GCC.
+// FRONTEND-LABEL: define dso_local i64 @ptr_addrof(
+// FRONTEND-SAME: ptr noundef [[P:%.*]]) #[[ATTR0]] {
+// FRONTEND-NEXT:  [[ENTRY:.*:]]
+// FRONTEND-NEXT:    [[FAM:%.*]] = getelementptr inbounds nuw 
[[STRUCT_ANNOTATED_FLEX:%.*]], ptr [[P]], i32 0, i32 2
+// FRONTEND-NEXT:    [[TMP0:%.*]] = call i64 @llvm.objectsize.i64.p0(ptr 
[[FAM]], i1 false, i1 true, i1 true)
+// FRONTEND-NEXT:    ret i64 [[TMP0]]
+//
+// O2-LABEL: define dso_local i64 @ptr_addrof(
+// O2-SAME: ptr noundef readnone [[P:%.*]]) local_unnamed_addr #[[ATTR3]] {
+// O2-NEXT:  [[ENTRY:.*:]]
+// O2-NEXT:    ret i64 -1
+//
+size_t ptr_addrof(struct annotated_flex *p) {
+    return __builtin_dynamic_object_size(&p->fam, 1);
+}
+//.
+// O2: [[META3:![0-9]+]] = !{!"omnipotent char", [[META4:![0-9]+]], i64 0}
+// O2: [[META4]] = !{!"Simple C/C++ TBAA"}
+// O2: [[LONG_TBAA5]] = !{[[META6:![0-9]+]], [[META6]], i64 0}
+// O2: [[META6]] = !{!"long", [[META3]], i64 0}
+//.


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

Reply via email to