https://github.com/rapidsna updated https://github.com/llvm/llvm-project/pull/223267
>From 1ba9ff3537c50ba94eddacc41ca56857f1802582 Mon Sep 17 00:00:00 2001 From: Yeoul Na <[email protected]> Date: Wed, 9 Sep 2026 08:43:30 -0700 Subject: [PATCH 1/2] [BoundsSafety][NFC] Allow CountAttributedType's count to be filled in later Prepare CountAttributedType so its count expression can be supplied after the node is created, which the new late-parsed counted_by mechanism needs: the type is built when the attribute is seen, but its argument isn't parsed until the enclosing record is complete. - Drop the TrailingObjects coupled-decl storage in favour of an ASTContext-allocated array held by the ArrayRef the base class already has, so the decls can be attached after construction. - Add CountAttributedType::setCountExpr for in-place completion. - Add ASTContext::getIncompleteCountAttributedType (count-less, not uniqued) and completeCountAttributedType. Incomplete nodes are not registered in ASTContext.Types until completed, so a node abandoned with a null count is never reachable. No functional change: getCountAttributedType still builds a fully-formed, count-carrying type as before. --- clang/include/clang/AST/ASTContext.h | 16 ++++++++ clang/include/clang/AST/TypeBase.h | 39 ++++++++++++------- clang/lib/AST/ASTContext.cpp | 57 +++++++++++++++++++++++++--- clang/lib/AST/Type.cpp | 8 ++-- clang/lib/Sema/SemaBoundsSafety.cpp | 4 +- 5 files changed, 100 insertions(+), 24 deletions(-) diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h index f875ae365b892..4fef0d57fd1e4 100644 --- a/clang/include/clang/AST/ASTContext.h +++ b/clang/include/clang/AST/ASTContext.h @@ -1663,6 +1663,22 @@ class ASTContext : public RefCountedBase<ASTContext> { bool OrNull, ArrayRef<TypeCoupledDeclRefInfo> DependentDecls) const; + /// Return a `CountAttributedType` whose count expression has not been parsed + /// yet, for use by a late-parsed bounds attribute. The result is *not* + /// uniqued, and must be completed with `completeCountAttributedType` once the + /// argument becomes parseable. Returns the node rather than a `QualType` so + /// the caller can retain it for completion. + CountAttributedType *getIncompleteCountAttributedType(QualType WrappedTy, + bool CountInBytes, + bool OrNull) const; + + /// Supply the count expression and coupled declarations for a type created by + /// `getIncompleteCountAttributedType`. Enclosing types keep pointing at the + /// same node, so nothing above it needs rebuilding. + void completeCountAttributedType( + CountAttributedType *CATy, Expr *CountExpr, + ArrayRef<TypeCoupledDeclRefInfo> DependentDecls) const; + /// Return a placeholder type for a late-parsed type attribute. /// This type wraps another type and holds the LateParsedAttribute /// that will be parsed later. diff --git a/clang/include/clang/AST/TypeBase.h b/clang/include/clang/AST/TypeBase.h index cdbd20b62bac5..afcb0172623df 100644 --- a/clang/include/clang/AST/TypeBase.h +++ b/clang/include/clang/AST/TypeBase.h @@ -3474,6 +3474,13 @@ class BoundsAttributedType : public Type, public llvm::FoldingSetNode { BoundsAttributedType(TypeClass TC, QualType Wrapped, QualType Canon); public: + enum BoundsAttrKind { + CountedBy = 0, + SizedBy, + CountedByOrNull, + SizedByOrNull, + }; + bool isSugared() const { return true; } QualType desugar() const { return WrappedTy; } @@ -3510,10 +3517,7 @@ class BoundsAttributedType : public Type, public llvm::FoldingSetNode { /// Represents a sugar type with `__counted_by` or `__sized_by` annotations, /// including their `_or_null` variants. -class CountAttributedType final - : public BoundsAttributedType, - public llvm::TrailingObjects<CountAttributedType, - TypeCoupledDeclRefInfo> { +class CountAttributedType final : public BoundsAttributedType { friend class ASTContext; Expr *CountExpr; @@ -3523,27 +3527,34 @@ class CountAttributedType final /// __counted_by_or_null or __sized_by_or_null) \p CoupledDecls contains the /// list of declarations referenced by \p CountExpr, which the type depends on /// for the bounds information. + /// + /// \p CountExpr may be null, and \p CoupledDecls empty, for a type created by + /// a late-parsed attribute whose argument has not been parsed yet; such a + /// type is completed by \c setCountExpr once the enclosing scope is known. + /// See + /// \c Parser::CompleteLateParsedTypeAttributes. CountAttributedType(QualType Wrapped, QualType Canon, Expr *CountExpr, bool CountInBytes, bool OrNull, ArrayRef<TypeCoupledDeclRefInfo> CoupledDecls); - unsigned numTrailingObjects(OverloadToken<TypeCoupledDeclRefInfo>) const { - return CountAttributedTypeBits.NumCoupledDecls; + /// Supply the count expression and its coupled declarations for a type that + /// was created without them by a late-parsed attribute. \p CoupledDecls must + /// already be allocated in the \c ASTContext, since it is retained by + /// reference. + void setCountExpr(Expr *E, ArrayRef<TypeCoupledDeclRefInfo> CoupledDecls) { + assert(!CountExpr && "count expression is already set"); + assert(E && "completing with a null count expression"); + CountExpr = E; + Decls = CoupledDecls; + CountAttributedTypeBits.NumCoupledDecls = CoupledDecls.size(); } public: - enum DynamicCountPointerKind { - CountedBy = 0, - SizedBy, - CountedByOrNull, - SizedByOrNull, - }; - Expr *getCountExpr() const { return CountExpr; } bool isCountInBytes() const { return CountAttributedTypeBits.CountInBytes; } bool isOrNull() const { return CountAttributedTypeBits.OrNull; } - DynamicCountPointerKind getKind() const { + BoundsAttrKind getKind() const { if (isOrNull()) return isCountInBytes() ? SizedByOrNull : CountedByOrNull; return isCountInBytes() ? SizedBy : CountedBy; diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index 84455fb6396bd..c58ad654e0050 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -3754,11 +3754,29 @@ QualType ASTContext::removePtrSizeAddrSpace(QualType T) const { return T; } +/// Allocate \p Decls in this context so a CountAttributedType can retain it by +/// reference. +static ArrayRef<TypeCoupledDeclRefInfo> +allocateCoupledDecls(const ASTContext &Ctx, + ArrayRef<TypeCoupledDeclRefInfo> Decls) { + if (Decls.empty()) + return {}; + auto *Slots = Ctx.Allocate<TypeCoupledDeclRefInfo>(Decls.size()); + llvm::copy(Decls, Slots); + return ArrayRef(Slots, Decls.size()); +} + QualType ASTContext::getCountAttributedType( QualType WrappedTy, Expr *CountExpr, bool CountInBytes, bool OrNull, ArrayRef<TypeCoupledDeclRefInfo> DependentDecls) const { assert(WrappedTy->isPointerType() || WrappedTy->isArrayType()); + assert(CountExpr && "use getIncompleteCountAttributedType for a null count"); + // Complete (non-late-parsed) path: the count expression is known up front. + // This deliberately preserves the pre-existing uniquing behavior -- the + // FoldingSet lookup/insert below is unchanged by late-parse support. Only + // getIncompleteCountAttributedType (count filled in later) opts out of + // uniquing. llvm::FoldingSetNodeID ID; CountAttributedType::Profile(ID, WrappedTy, CountExpr, CountInBytes, OrNull); @@ -3768,17 +3786,46 @@ QualType ASTContext::getCountAttributedType( return QualType(CATy, 0); QualType CanonTy = getCanonicalType(WrappedTy); - size_t Size = CountAttributedType::totalSizeToAlloc<TypeCoupledDeclRefInfo>( - DependentDecls.size()); - CATy = (CountAttributedType *)Allocate(Size, TypeAlignment); - new (CATy) CountAttributedType(WrappedTy, CanonTy, CountExpr, CountInBytes, - OrNull, DependentDecls); + ArrayRef<TypeCoupledDeclRefInfo> Decls = + allocateCoupledDecls(*this, DependentDecls); + CATy = new (*this, alignof(CountAttributedType)) CountAttributedType( + WrappedTy, CanonTy, CountExpr, CountInBytes, OrNull, Decls); Types.push_back(CATy); CountAttributedTypes.insert(CATy, Token); return QualType(CATy, 0); } +CountAttributedType *ASTContext::getIncompleteCountAttributedType( + QualType WrappedTy, bool CountInBytes, bool OrNull) const { + assert(WrappedTy->isPointerType() || WrappedTy->isArrayType()); + + // Deliberately not uniqued. `CountAttributedType::Profile` keys on the + // `CountExpr` pointer, so every incomplete node would hash identically as + // `(WrappedTy, flags, nullptr)` and two fields with different counts would + // share a node. This is fine because expressions are not shared anyway. + // `getVariableArrayType` declines to unique for the same + // underlying reason: expressions themselves are not uniqued. + // + // Not added to `Types` yet: an incomplete node whose position turns out to be + // invalid (a nested counted_by, or a rejected argument) is abandoned without + // completion, and a node with a null count must never be reachable by + // anything that iterates `Types`. It is registered in + // `completeCountAttributedType` instead. + return new (*this, alignof(CountAttributedType)) CountAttributedType( + WrappedTy, getCanonicalType(WrappedTy), /*CountExpr=*/nullptr, + CountInBytes, OrNull, /*CoupledDecls=*/{}); +} + +void ASTContext::completeCountAttributedType( + CountAttributedType *CATy, Expr *CountExpr, + ArrayRef<TypeCoupledDeclRefInfo> DependentDecls) const { + ArrayRef<TypeCoupledDeclRefInfo> Decls = + allocateCoupledDecls(*this, DependentDecls); + CATy->setCountExpr(CountExpr, Decls); + Types.push_back(CATy); +} + QualType ASTContext::getLateParsedAttrType( QualType WrappedTy, LateParsedTypeAttribute *LateParsedAttr) const { QualType CanonTy = getCanonicalType(WrappedTy); diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp index 4b539b7c2b1f6..7254835e47390 100644 --- a/clang/lib/AST/Type.cpp +++ b/clang/lib/AST/Type.cpp @@ -4158,9 +4158,11 @@ CountAttributedType::CountAttributedType( CountAttributedTypeBits.NumCoupledDecls = CoupledDecls.size(); CountAttributedTypeBits.CountInBytes = CountInBytes; CountAttributedTypeBits.OrNull = OrNull; - auto *DeclSlot = getTrailingObjects(); - llvm::copy(CoupledDecls, DeclSlot); - Decls = llvm::ArrayRef(DeclSlot, CoupledDecls.size()); + // `CoupledDecls` is allocated in the ASTContext by the caller, so it can be + // retained by reference. This lets a type created by a late-parsed attribute + // start out with no decls and gain them later via `setCountExpr`, which a + // trailing-object array could not accommodate. + Decls = CoupledDecls; } StringRef CountAttributedType::getAttributeName(bool WithMacroPrefix) const { diff --git a/clang/lib/Sema/SemaBoundsSafety.cpp b/clang/lib/Sema/SemaBoundsSafety.cpp index 066dab2f0bef2..75041c801b6ff 100644 --- a/clang/lib/Sema/SemaBoundsSafety.cpp +++ b/clang/lib/Sema/SemaBoundsSafety.cpp @@ -17,8 +17,8 @@ namespace clang { -static CountAttributedType::DynamicCountPointerKind -getCountAttrKind(bool CountInBytes, bool OrNull) { +static CountAttributedType::BoundsAttrKind getCountAttrKind(bool CountInBytes, + bool OrNull) { if (CountInBytes) return OrNull ? CountAttributedType::SizedByOrNull : CountAttributedType::SizedBy; >From 7c8520f175d6a16dc58a796acced4468a93c5006 Mon Sep 17 00:00:00 2001 From: Yeoul Na <[email protected]> Date: Wed, 16 Sep 2026 17:05:17 -0700 Subject: [PATCH 2/2] Address Erich's feedback about allocations should happen in ::create --- clang/include/clang/AST/TypeBase.h | 28 +++++++------- clang/lib/AST/ASTContext.cpp | 59 ++++++++++++++---------------- clang/lib/AST/Type.cpp | 42 +++++++++++++++++++-- 3 files changed, 80 insertions(+), 49 deletions(-) diff --git a/clang/include/clang/AST/TypeBase.h b/clang/include/clang/AST/TypeBase.h index afcb0172623df..eb34e775f7da6 100644 --- a/clang/include/clang/AST/TypeBase.h +++ b/clang/include/clang/AST/TypeBase.h @@ -3530,24 +3530,26 @@ class CountAttributedType final : public BoundsAttributedType { /// /// \p CountExpr may be null, and \p CoupledDecls empty, for a type created by /// a late-parsed attribute whose argument has not been parsed yet; such a - /// type is completed by \c setCountExpr once the enclosing scope is known. - /// See + /// type is completed by \c complete once the enclosing scope is known. See /// \c Parser::CompleteLateParsedTypeAttributes. CountAttributedType(QualType Wrapped, QualType Canon, Expr *CountExpr, bool CountInBytes, bool OrNull, ArrayRef<TypeCoupledDeclRefInfo> CoupledDecls); - /// Supply the count expression and its coupled declarations for a type that - /// was created without them by a late-parsed attribute. \p CoupledDecls must - /// already be allocated in the \c ASTContext, since it is retained by - /// reference. - void setCountExpr(Expr *E, ArrayRef<TypeCoupledDeclRefInfo> CoupledDecls) { - assert(!CountExpr && "count expression is already set"); - assert(E && "completing with a null count expression"); - CountExpr = E; - Decls = CoupledDecls; - CountAttributedTypeBits.NumCoupledDecls = CoupledDecls.size(); - } + /// Allocate and construct a \c CountAttributedType in \p Ctx, including its + /// coupled-declaration array. \p CountExpr may be null (with \p CoupledDecls + /// empty) for a late-parsed attribute whose argument is not yet parsed; + /// complete such a node later with \c complete. + static CountAttributedType * + Create(const ASTContext &Ctx, QualType Wrapped, QualType Canon, + Expr *CountExpr, bool CountInBytes, bool OrNull, + ArrayRef<TypeCoupledDeclRefInfo> CoupledDecls); + + /// Supply the count expression and coupled declarations for a node created by + /// \c Create with a null count -- a late-parsed attribute whose argument has + /// now been parsed. Allocates the decl array in \p Ctx, so the node owns it. + void complete(const ASTContext &Ctx, Expr *E, + ArrayRef<TypeCoupledDeclRefInfo> CoupledDecls); public: Expr *getCountExpr() const { return CountExpr; } diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index c58ad654e0050..ee9ac03bd1682 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -3754,18 +3754,6 @@ QualType ASTContext::removePtrSizeAddrSpace(QualType T) const { return T; } -/// Allocate \p Decls in this context so a CountAttributedType can retain it by -/// reference. -static ArrayRef<TypeCoupledDeclRefInfo> -allocateCoupledDecls(const ASTContext &Ctx, - ArrayRef<TypeCoupledDeclRefInfo> Decls) { - if (Decls.empty()) - return {}; - auto *Slots = Ctx.Allocate<TypeCoupledDeclRefInfo>(Decls.size()); - llvm::copy(Decls, Slots); - return ArrayRef(Slots, Decls.size()); -} - QualType ASTContext::getCountAttributedType( QualType WrappedTy, Expr *CountExpr, bool CountInBytes, bool OrNull, ArrayRef<TypeCoupledDeclRefInfo> DependentDecls) const { @@ -3786,10 +3774,8 @@ QualType ASTContext::getCountAttributedType( return QualType(CATy, 0); QualType CanonTy = getCanonicalType(WrappedTy); - ArrayRef<TypeCoupledDeclRefInfo> Decls = - allocateCoupledDecls(*this, DependentDecls); - CATy = new (*this, alignof(CountAttributedType)) CountAttributedType( - WrappedTy, CanonTy, CountExpr, CountInBytes, OrNull, Decls); + CATy = CountAttributedType::Create(*this, WrappedTy, CanonTy, CountExpr, + CountInBytes, OrNull, DependentDecls); Types.push_back(CATy); CountAttributedTypes.insert(CATy, Token); @@ -3800,29 +3786,38 @@ CountAttributedType *ASTContext::getIncompleteCountAttributedType( QualType WrappedTy, bool CountInBytes, bool OrNull) const { assert(WrappedTy->isPointerType() || WrappedTy->isArrayType()); - // Deliberately not uniqued. `CountAttributedType::Profile` keys on the - // `CountExpr` pointer, so every incomplete node would hash identically as + // Deliberately opts out of the uniquing that `getCountAttributedType` does: + // `CountAttributedType::Profile` keys on the `CountExpr` pointer, which is + // null here, so every incomplete node would profile identically as // `(WrappedTy, flags, nullptr)` and two fields with different counts would - // share a node. This is fine because expressions are not shared anyway. - // `getVariableArrayType` declines to unique for the same - // underlying reason: expressions themselves are not uniqued. + // collide. The node stays un-uniqued even after completion; see + // `completeCountAttributedType`. // - // Not added to `Types` yet: an incomplete node whose position turns out to be - // invalid (a nested counted_by, or a rejected argument) is abandoned without - // completion, and a node with a null count must never be reachable by - // anything that iterates `Types`. It is registered in - // `completeCountAttributedType` instead. - return new (*this, alignof(CountAttributedType)) CountAttributedType( - WrappedTy, getCanonicalType(WrappedTy), /*CountExpr=*/nullptr, - CountInBytes, OrNull, /*CoupledDecls=*/{}); + // Also deliberately not in `Types` yet. An incomplete node can be abandoned + // without ever being completed (a nested counted_by, or an argument that + // fails to parse), and a null-count node must not be reachable by anything + // that scans `Types`. `completeCountAttributedType` registers it once the + // count is in place. + return CountAttributedType::Create( + *this, WrappedTy, getCanonicalType(WrappedTy), + /*CountExpr=*/nullptr, CountInBytes, OrNull, + /*CoupledDecls=*/{}); } void ASTContext::completeCountAttributedType( CountAttributedType *CATy, Expr *CountExpr, ArrayRef<TypeCoupledDeclRefInfo> DependentDecls) const { - ArrayRef<TypeCoupledDeclRefInfo> Decls = - allocateCoupledDecls(*this, DependentDecls); - CATy->setCountExpr(CountExpr, Decls); + CATy->complete(*this, CountExpr, DependentDecls); + // Safe for `Types` scanners now that the count is in place; see + // `getIncompleteCountAttributedType` for why it was held back. + // + // It stays out of the `CountAttributedTypes` FoldingSet permanently, unlike + // an eagerly built node: this pointer is already embedded in the enclosing + // types and handed out, so an equal node that happens to exist cannot be + // merged into. The only cost is that a completed node is never + // pointer-shared with an equal eager one, which does not affect semantic + // type equality -- `hasSameType` compares canonical types, and this sugar's + // canonical type is the wrapped type's. Types.push_back(CATy); } diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp index 7254835e47390..87053d6048dd9 100644 --- a/clang/lib/AST/Type.cpp +++ b/clang/lib/AST/Type.cpp @@ -4158,13 +4158,47 @@ CountAttributedType::CountAttributedType( CountAttributedTypeBits.NumCoupledDecls = CoupledDecls.size(); CountAttributedTypeBits.CountInBytes = CountInBytes; CountAttributedTypeBits.OrNull = OrNull; - // `CoupledDecls` is allocated in the ASTContext by the caller, so it can be - // retained by reference. This lets a type created by a late-parsed attribute - // start out with no decls and gain them later via `setCountExpr`, which a - // trailing-object array could not accommodate. + // `CoupledDecls` is already allocated by the caller (Create), so it + // can be retained by reference. This lets a type created by a late-parsed + // attribute start out with no decls and gain them later via `complete`, + // which a trailing-object array could not accommodate. Decls = CoupledDecls; } +/// Copy \p Decls into \p Ctx so a \c CountAttributedType can retain it by +/// reference. The node owns this allocation rather than its callers, so both +/// \c Create and \c complete route through here. +static ArrayRef<TypeCoupledDeclRefInfo> +allocateCoupledDecls(const ASTContext &Ctx, + ArrayRef<TypeCoupledDeclRefInfo> Decls) { + if (Decls.empty()) + return {}; + auto *Slots = Ctx.Allocate<TypeCoupledDeclRefInfo>(Decls.size()); + llvm::copy(Decls, Slots); + return ArrayRef(Slots, Decls.size()); +} + +CountAttributedType * +CountAttributedType::Create(const ASTContext &Ctx, QualType Wrapped, + QualType Canon, Expr *CountExpr, bool CountInBytes, + bool OrNull, + ArrayRef<TypeCoupledDeclRefInfo> CoupledDecls) { + ArrayRef<TypeCoupledDeclRefInfo> Decls = + allocateCoupledDecls(Ctx, CoupledDecls); + return new (Ctx, alignof(CountAttributedType)) CountAttributedType( + Wrapped, Canon, CountExpr, CountInBytes, OrNull, Decls); +} + +void CountAttributedType::complete( + const ASTContext &Ctx, Expr *E, + ArrayRef<TypeCoupledDeclRefInfo> CoupledDecls) { + assert(!CountExpr && "count expression is already set"); + assert(E && "completing with a null count expression"); + CountExpr = E; + Decls = allocateCoupledDecls(Ctx, CoupledDecls); + CountAttributedTypeBits.NumCoupledDecls = Decls.size(); +} + StringRef CountAttributedType::getAttributeName(bool WithMacroPrefix) const { // TODO: This method isn't really ideal because it doesn't return the spelling // of the attribute that was used in the user's code. This method is used for _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
