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
