https://github.com/keinflue created https://github.com/llvm/llvm-project/pull/174549
Previously the old constant interpreter would crash on `__builtin_assume_aligned` applied to dynamic allocations and the new interpreter would incorrectly always assume an alignment of 8. This implements computation of the alignments of dynamic allocations for the builtin by using the minimum alignment guarantees made in the standard for each kind of possible dynamic allocation during constant evaluation. The kind of allocation depends on the source expression causing it to be created, but the alignment also depends on the size of the allocation which can be retrieved from the allocated type, but is decided dynamically. For the old constant expression evaluator, the allocated type is always available from the LValue, but the source expression is only available from the `DynAlloc` representing the allocation. The `DynAlloc` object does not continue to exist when the lifetime of the dynamic allocation ends. Because `__builtin_assume_aligned` should still be valid out-of-lifetime, it is therefore not possible to retrieve the source expression from the `DynAlloc` object. Instead this calculates the alignment during creation of the allocation and stores it as part of the `LValueBase` by appropriating 5 bits of the allocation identifier, reducing the total number of possible allocations to 2**25-1. The new constant expression interpreter does not need additional state to store the alignment, because it already has both the source expression and allocated size available. Fixes #173767 --- I am not happy with the modifications to `DynamicAllocLValue` and I have doubts that it is ok. If anyone has suggestions for a better way to resolve the issue described above I'll try to implement it. Also implements suggestion from https://github.com/llvm/llvm-project/pull/174132#discussion_r2658541020. >From 2b848ff9e8663d091dbbb2ecdba4a3870f81f1ae Mon Sep 17 00:00:00 2001 From: keinflue <[email protected]> Date: Tue, 6 Jan 2026 09:39:15 +0100 Subject: [PATCH] [clang] Implement constexpr heap allocation alignments Previously the old constant interpreter would crash on `__builtin_assume_aligned` applied to dynamic allocations and the new interpreter would incorrectly always assume an alignment of 8. This implements computation of the alignments of dynamic allocations for the builtin by using the minimum alignment guarantees made in the standard for each kind of possible dynamic allocation during constant evaluation. The kind of allocation depends on the source expression causing it to be created, but the alignment also depends on the size of the allocation which can be retrieved from the allocated type, but is decided dynamically. For the old constant expression evaluator, the allocated type is always available from the LValue, but the source expression is only available from the `DynAlloc` representing the allocation. The `DynAlloc` object does not continue to exist when the lifetime of the dynamic allocation ends. Because `__builtin_assume_aligned` should still be valid out-of-lifetime, it is therefore not possible to retrieve the source expression from the `DynAlloc` object. Instead this calculates the alignment during creation of the allocation and stores it as part of the `LValueBase` by appropriating 5 bits of the allocation identifier, reducing the total number of possible allocations to 2**25-1. The new constant expression interpreter does not need additional state to store the alignment, because it already has both the source expression and allocated size available. Fixes #173767 --- clang/docs/ReleaseNotes.rst | 3 + clang/include/clang/AST/APValue.h | 35 +++-- clang/include/clang/AST/PropertiesBase.td | 6 +- clang/lib/AST/ByteCode/Descriptor.cpp | 132 +++++++++++++++--- clang/lib/AST/ByteCode/Descriptor.h | 20 +++ clang/lib/AST/ByteCode/Interp.cpp | 16 +-- clang/lib/AST/ByteCode/Interp.h | 3 +- clang/lib/AST/ByteCode/InterpBlock.h | 7 +- clang/lib/AST/ByteCode/InterpBuiltin.cpp | 11 +- clang/lib/AST/ByteCode/Pointer.cpp | 7 +- clang/lib/AST/ExprConstShared.h | 3 + clang/lib/AST/ExprConstant.cpp | 88 +++++++++++- clang/lib/Sema/SemaExprCXX.cpp | 1 + clang/test/SemaCXX/builtin-assume-aligned.cpp | 85 ++++++++++- 14 files changed, 359 insertions(+), 58 deletions(-) diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 411cc348d4caf..fa97c382de764 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -557,6 +557,9 @@ Bug Fixes to Compiler Builtins ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - Fix an ambiguous reference to the builtin `type_info` (available when using `-fms-compatibility`) with modules. (#GH38400) +- Fix a crash when using ``__builtin_assume_aligned`` with dynamic allocations during + constant evaluation. Also correct the behavior with the new constant interpreter, so + that both apply alignment guarantees made by the standard. (#GH173767) Bug Fixes to Attribute Support ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/clang/include/clang/AST/APValue.h b/clang/include/clang/AST/APValue.h index 8a2d6d434792a..87a71a8bb3dc7 100644 --- a/clang/include/clang/AST/APValue.h +++ b/clang/include/clang/AST/APValue.h @@ -20,6 +20,7 @@ #include "llvm/ADT/FoldingSet.h" #include "llvm/ADT/PointerIntPair.h" #include "llvm/ADT/PointerUnion.h" +#include "llvm/ADT/bit.h" #include "llvm/Support/AlignOf.h" namespace clang { @@ -63,30 +64,48 @@ class TypeInfoLValue { /// Symbolic representation of a dynamic allocation. class DynamicAllocLValue { - unsigned Index; + // lower NumAlignmentBits: alignment exponent + // remaining bits: allocation index incremented by one + // value of zero indicates distinct empty state + unsigned AlignAndIndex; public: - DynamicAllocLValue() : Index(0) {} - explicit DynamicAllocLValue(unsigned Index) : Index(Index + 1) {} - unsigned getIndex() { return Index - 1; } + DynamicAllocLValue() : AlignAndIndex(0) {} + explicit DynamicAllocLValue(unsigned Index, uint64_t Align) { + assert(Align > 0 && "Invalid alignment for DynamicAllocLValue constructor"); + AlignAndIndex = + ((Index + 1) << NumAlignmentBits) + llvm::countr_zero(Align); + } + unsigned getIndex() const { return (AlignAndIndex >> NumAlignmentBits) - 1; } + unsigned getAlignExponent() const { + return AlignAndIndex & ((1U << NumAlignmentBits) - 1); + } + uint64_t getAlign() const { + const unsigned AlignExponent = getAlignExponent(); + assert(AlignExponent < 64 && "Invalid alignment in DynamicAllocLValue."); + return uint64_t{1} << AlignExponent; + } - explicit operator bool() const { return Index != 0; } + explicit operator bool() const { return AlignAndIndex != 0; } const void *getOpaqueValue() const { - return reinterpret_cast<const void *>(static_cast<uintptr_t>(Index) + return reinterpret_cast<const void *>(static_cast<uintptr_t>(AlignAndIndex) << NumLowBitsAvailable); } static DynamicAllocLValue getFromOpaqueValue(const void *Value) { DynamicAllocLValue V; - V.Index = reinterpret_cast<uintptr_t>(Value) >> NumLowBitsAvailable; + V.AlignAndIndex = reinterpret_cast<uintptr_t>(Value) >> NumLowBitsAvailable; return V; } static unsigned getMaxIndex() { - return (std::numeric_limits<unsigned>::max() >> NumLowBitsAvailable) - 1; + return (std::numeric_limits<unsigned>::max() >> + (NumLowBitsAvailable + NumAlignmentBits)) - + 1; } static constexpr int NumLowBitsAvailable = 3; + static constexpr int NumAlignmentBits = 5; }; } diff --git a/clang/include/clang/AST/PropertiesBase.td b/clang/include/clang/AST/PropertiesBase.td index 5b10127526e4e..3ffd8406ce838 100644 --- a/clang/include/clang/AST/PropertiesBase.td +++ b/clang/include/clang/AST/PropertiesBase.td @@ -502,6 +502,10 @@ let Class = PropertyTypeCase<APValue, "LValue"> in { let Conditional = [{ hasBase && isDynamicAlloc }]; let Read = [{ node.getLValueBase().get<DynamicAllocLValue>().getIndex() }]; } + def : Property<"dynamicAlign", UInt64> { + let Conditional = [{ hasBase && isDynamicAlloc }]; + let Read = [{ node.getLValueBase().get<DynamicAllocLValue>().getAlign() }]; + } def : Property<"type", QualType> { let Conditional = [{ hasBase && (isTypeInfo || isDynamicAlloc) }]; let Read = [{ @@ -544,7 +548,7 @@ let Class = PropertyTypeCase<APValue, "LValue"> in { TypeInfoLValue(typeInfo->getTypePtr()), *type); } else if (isDynamicAlloc) { base = APValue::LValueBase::getDynamicAlloc( - DynamicAllocLValue(*dynamicAlloc), *type); + DynamicAllocLValue(*dynamicAlloc, *dynamicAlign), *type); } else if (isExpr) { base = APValue::LValueBase(cast<Expr>(*stmt), *callIndex, *version); diff --git a/clang/lib/AST/ByteCode/Descriptor.cpp b/clang/lib/AST/ByteCode/Descriptor.cpp index a3cee034191d2..4f677d361ccac 100644 --- a/clang/lib/AST/ByteCode/Descriptor.cpp +++ b/clang/lib/AST/ByteCode/Descriptor.cpp @@ -17,6 +17,8 @@ #include "Record.h" #include "Source.h" #include "clang/AST/ExprCXX.h" +#include "clang/Basic/TargetInfo.h" +#include "llvm/Support/ErrorHandling.h" using namespace clang; using namespace clang::interp; @@ -418,33 +420,15 @@ QualType Descriptor::getDataType(const ASTContext &Ctx) const { return ElemType; }; - if (const auto *E = asExpr()) { - if (isa<CXXNewExpr>(E)) - return MakeArrayType(E->getType()->getPointeeType()); - - // std::allocator.allocate() call. - if (const auto *ME = dyn_cast<CXXMemberCallExpr>(E); - ME && ME->getRecordDecl()->getName() == "allocator" && - ME->getMethodDecl()->getName() == "allocate") - return MakeArrayType(E->getType()->getPointeeType()); - return E->getType(); - } + if (isDynAlloc()) + return MakeArrayType(asExpr()->getType()->getPointeeType()); return getType(); } QualType Descriptor::getDataElemType() const { - if (const auto *E = asExpr()) { - if (isa<CXXNewExpr>(E)) - return E->getType()->getPointeeType(); - - // std::allocator.allocate() call. - if (const auto *ME = dyn_cast<CXXMemberCallExpr>(E); - ME && ME->getRecordDecl()->getName() == "allocator" && - ME->getMethodDecl()->getName() == "allocate") - return E->getType()->getPointeeType(); - return E->getType(); - } + if (isDynAlloc()) + return asExpr()->getType()->getPointeeType(); return getType(); } @@ -481,3 +465,107 @@ bool Descriptor::hasTrivialDtor() const { } bool Descriptor::isUnion() const { return isRecord() && ElemRecord->isUnion(); } + +Descriptor::DynAllocKind Descriptor::getDynAllocKindForExpr(const Expr *E) { + // new or new[] expression + if (const auto *NE = dyn_cast<CXXNewExpr>(E)) + return NE->isArray() ? DynAllocKind::ArrayNew : DynAllocKind::New; + // std::allocator::allocate call + if (const auto *ME = dyn_cast<CXXMemberCallExpr>(E); + ME && ME->getRecordDecl()->getName() == "allocator" && + ME->getMethodDecl()->getName() == "allocate") + return DynAllocKind::StdAllocator; + // __builtin_operator_new call + if (const auto *CE = dyn_cast<CallExpr>(E); + CE && CE->getBuiltinCallee() == Builtin::BI__builtin_operator_new) + return DynAllocKind::BuiltinOperatorNew; + return DynAllocKind::None; +} + +uint64_t Descriptor::computeAlignForDynamicAlloc(const ASTContext &Ctx) const { + const Expr *AllocExpr = asExpr(); + const QualType AllocType = getDataType(Ctx); + + const TargetInfo &TI = Ctx.getTargetInfo(); + + const uint64_t DefaultNewAlign = TI.getNewAlign(); + const uint64_t MaxFundamentalAlign = + std::max(TI.getLongLongAlign(), TI.getLongDoubleAlign()); + + const DynAllocKind AllocKind = getDynAllocKindForExpr(AllocExpr); + assert((AllocKind != DynAllocKind::None) && + "should only be called on dynamically allocated blocks"); + assert((AllocKind != DynAllocKind::BuiltinOperatorNew) && + "__builtin_operator_new should have been allowed only from " + "std::allocator::allocate"); + + const uint64_t AllocSize = Ctx.getTypeSize(AllocType); + + // Allocating a zero-sized array is allowed, however it doesn't have + // any alignment guarantees. + if ((AllocKind == DynAllocKind::ArrayNew || + AllocKind == DynAllocKind::StdAllocator) && + AllocSize == 0) + return TI.getCharWidth(); + + // For the non-array form, the size of the allocated object should be + // positive. + assert(AllocSize > 0 && "Unknown size for allocated type!"); + + const uint64_t TypeAlignment = Ctx.getTypeAlign(AllocType); + assert(TypeAlignment > 0 && "Unknown alignment for allocated type!"); + + // For new-extended alignment the ::operator new overload with + // std::align_val_t parameter is used. According to C++ + // [basic.stc.dynamic.allocation]p3.1 this overload returns memory + // according to the requested alignment. No stricter guarantees are + // made. + if (TypeAlignment > DefaultNewAlign) + return TypeAlignment; + + switch (AllocKind) { + // According to C++ [allocator.members]p5 it is unspecified how the + // memory obtained from ::operator new is used by + // std::allocator::allocate, therefore be conservative here. + case DynAllocKind::StdAllocator: + return TypeAlignment; + + // The non-array form of new does not permit allocation overhead and + // therefore provides alignment as guaranteed by ::operator new. + // According to C++ [basic.stc.dynamic.allocation]p3.3 the allocation + // is suitably aligned for all objects without new-extended alignment + // with the exact size of the allocation. An object of the exact size + // AllocSize can have alignment of at most the lowest bit set in + // AllocSize. + case DynAllocKind::New: + return std::min(DefaultNewAlign, + uint64_t{1} << llvm::countr_zero(AllocSize)); + + case DynAllocKind::ArrayNew: { + const Type *ET = AllocType.getTypePtr() + ->getArrayElementTypeNoTypeQual() + ->getCanonicalTypeUnqualified() + .getTypePtr(); + // According to C++ [expr.new]p17, unless the element type of an + // array new expression is char, unsigned char or std::byte, the + // allocation may be offset into the allocation returned by + // ::operator new[]. Therefore no stricter alignment than the type's + // alignment is guaranteed. For char, unsigned char and std::byte + if (!ET->isSpecificBuiltinType(BuiltinType::UChar) && + !ET->isSpecificBuiltinType(BuiltinType::Char_U) && + !ET->isSpecificBuiltinType(BuiltinType::Char_S) && !ET->isStdByteType()) + return TypeAlignment; + + // Otherwise, the allocation is offset from the result of ::operator + // new[] by a multiple of the strictest fundamental alignment. + // According C++ [basic.stc.dynamic.allocation]p3.2 the allocation + // returned by ::operator new[] is suitably aligned for all objects + // without new-extended alignment and size up to the allocated size. + return std::min( + {DefaultNewAlign, MaxFundamentalAlign, llvm::bit_floor(AllocSize)}); + } + + default: + llvm_unreachable("Unhandled DynAllocKind"); + } +} diff --git a/clang/lib/AST/ByteCode/Descriptor.h b/clang/lib/AST/ByteCode/Descriptor.h index 5bf550ffe1172..906f028d3cfe2 100644 --- a/clang/lib/AST/ByteCode/Descriptor.h +++ b/clang/lib/AST/ByteCode/Descriptor.h @@ -272,6 +272,26 @@ struct Descriptor final { /// Whether variables of this descriptor need their destructor called or not. bool hasTrivialDtor() const; + /// Kind of source for a dynamic allocation. + enum class DynAllocKind { + None, // not a dynamic allocation + New, // new expression + ArrayNew, // new[] expression + StdAllocator, // std::allocator::allocate call + BuiltinOperatorNew // __operator_builtin_new call + }; + /// Returns the kind of dynamic allocation source of this block. + static DynAllocKind getDynAllocKindForExpr(const Expr *E); + /// Returns the kind of dynamic allocation source of this block. + DynAllocKind getDynAllocKind() const { + return asExpr() ? getDynAllocKindForExpr(asExpr()) : DynAllocKind::None; + } + /// Checks if the descriptor is of a dynamic allocation. + bool isDynAlloc() const { return getDynAllocKind() != DynAllocKind::None; } + + /// Compute the alignment for a dynamic allocation. + uint64_t computeAlignForDynamicAlloc(const ASTContext &Ctx) const; + void dump() const; void dump(llvm::raw_ostream &OS) const; void dumpFull(unsigned Offset = 0, unsigned Indent = 0) const; diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp index fe3069a4d8ef8..772701fc6c5d9 100644 --- a/clang/lib/AST/ByteCode/Interp.cpp +++ b/clang/lib/AST/ByteCode/Interp.cpp @@ -1136,18 +1136,8 @@ bool CheckNewDeleteForms(InterpState &S, CodePtr OpPC, return false; } -bool CheckDeleteSource(InterpState &S, CodePtr OpPC, const Expr *Source, - const Pointer &Ptr) { - // Regular new type(...) call. - if (isa_and_nonnull<CXXNewExpr>(Source)) - return true; - // operator new. - if (const auto *CE = dyn_cast_if_present<CallExpr>(Source); - CE && CE->getBuiltinCallee() == Builtin::BI__builtin_operator_new) - return true; - // std::allocator.allocate() call - if (const auto *MCE = dyn_cast_if_present<CXXMemberCallExpr>(Source); - MCE && MCE->getMethodDecl()->getIdentifier()->isStr("allocate")) +bool CheckDeleteSource(InterpState &S, CodePtr OpPC, const Pointer &Ptr) { + if (Ptr.block()->isDynamic()) return true; // Whatever this is, we didn't heap allocate it. @@ -1329,7 +1319,7 @@ bool Free(InterpState &S, CodePtr OpPC, bool DeleteIsArrayForm, return false; } - if (!CheckDeleteSource(S, OpPC, Source, Ptr)) + if (!CheckDeleteSource(S, OpPC, Ptr)) return false; // For a class type with a virtual destructor, the selected operator delete diff --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h index 155d96fc1652b..3c50e9d580778 100644 --- a/clang/lib/AST/ByteCode/Interp.h +++ b/clang/lib/AST/ByteCode/Interp.h @@ -90,8 +90,7 @@ bool CheckDynamicMemoryAllocation(InterpState &S, CodePtr OpPC); /// Check the source of the pointer passed to delete/delete[] has actually /// been heap allocated by us. -bool CheckDeleteSource(InterpState &S, CodePtr OpPC, const Expr *Source, - const Pointer &Ptr); +bool CheckDeleteSource(InterpState &S, CodePtr OpPC, const Pointer &Ptr); bool CheckActive(InterpState &S, CodePtr OpPC, const Pointer &Ptr, AccessKinds AK); diff --git a/clang/lib/AST/ByteCode/InterpBlock.h b/clang/lib/AST/ByteCode/InterpBlock.h index 57f9e7ec3714d..33cb801358540 100644 --- a/clang/lib/AST/ByteCode/InterpBlock.h +++ b/clang/lib/AST/ByteCode/InterpBlock.h @@ -80,7 +80,12 @@ class Block final { /// Checks if the block is temporary. bool isTemporary() const { return Desc->IsTemporary; } bool isWeak() const { return AccessFlags & WeakFlag; } - bool isDynamic() const { return (DynAllocId != std::nullopt); } + bool isDynamic() const { + const bool Result = (DynAllocId != std::nullopt); + assert((Result == Desc->isDynAlloc()) && + "Inconsistent block/descriptor dynamic alloc state"); + return Result; + } bool isDummy() const { return AccessFlags & DummyFlag; } bool isDead() const { return AccessFlags & DeadFlag; } /// Returns the size of the block. diff --git a/clang/lib/AST/ByteCode/InterpBuiltin.cpp b/clang/lib/AST/ByteCode/InterpBuiltin.cpp index b4acb786fa90c..50936e8f5f62f 100644 --- a/clang/lib/AST/ByteCode/InterpBuiltin.cpp +++ b/clang/lib/AST/ByteCode/InterpBuiltin.cpp @@ -12,6 +12,8 @@ #include "InterpHelpers.h" #include "PrimType.h" #include "Program.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/ExprCXX.h" #include "clang/AST/InferAlloc.h" #include "clang/AST/OSLog.h" #include "clang/AST/RecordLayout.h" @@ -1250,6 +1252,8 @@ static bool interp__builtin_assume_aligned(InterpState &S, CodePtr OpPC, const CallExpr *Call) { assert(Call->getNumArgs() == 2 || Call->getNumArgs() == 3); + ASTContext &ASTCtx = S.getASTContext(); + std::optional<APSInt> ExtraOffset; if (Call->getNumArgs() == 3) ExtraOffset = popToAPSInt(S.Stk, *S.Ctx.classify(Call->getArg(2))); @@ -1263,9 +1267,12 @@ static bool interp__builtin_assume_aligned(InterpState &S, CodePtr OpPC, if (Ptr.isBlockPointer()) { CharUnits BaseAlignment; if (const auto *VD = Ptr.getDeclDesc()->asValueDecl()) - BaseAlignment = S.getASTContext().getDeclAlign(VD); + BaseAlignment = ASTCtx.getDeclAlign(VD); + else if (Ptr.block()->isDynamic()) + BaseAlignment = ASTCtx.toCharUnitsFromBits( + Ptr.getDeclDesc()->computeAlignForDynamicAlloc(ASTCtx)); else if (const auto *E = Ptr.getDeclDesc()->asExpr()) - BaseAlignment = GetAlignOfExpr(S.getASTContext(), E, UETT_AlignOf); + BaseAlignment = GetAlignOfExpr(ASTCtx, E, UETT_AlignOf); if (BaseAlignment < Align) { S.CCEDiag(Call->getArg(0), diff --git a/clang/lib/AST/ByteCode/Pointer.cpp b/clang/lib/AST/ByteCode/Pointer.cpp index 5d11321d09079..7a1f80b48600b 100644 --- a/clang/lib/AST/ByteCode/Pointer.cpp +++ b/clang/lib/AST/ByteCode/Pointer.cpp @@ -205,9 +205,10 @@ APValue Pointer::toAPValue(const ASTContext &ASTCtx) const { Base = VD; else if (const auto *E = Desc->asExpr()) { if (block()->isDynamic()) { - QualType AllocatedType = getDeclPtr().getFieldDesc()->getDataType(ASTCtx); - DynamicAllocLValue DA(*block()->DynAllocId); - Base = APValue::LValueBase::getDynamicAlloc(DA, AllocatedType); + DynamicAllocLValue DA(*block()->DynAllocId, + Desc->computeAlignForDynamicAlloc(ASTCtx)); + Base = + APValue::LValueBase::getDynamicAlloc(DA, Desc->getDataType(ASTCtx)); } else { Base = E; } diff --git a/clang/lib/AST/ExprConstShared.h b/clang/lib/AST/ExprConstShared.h index def57665e0998..ac85b62d435a9 100644 --- a/clang/lib/AST/ExprConstShared.h +++ b/clang/lib/AST/ExprConstShared.h @@ -77,6 +77,9 @@ void HandleComplexComplexDiv(llvm::APFloat A, llvm::APFloat B, llvm::APFloat C, CharUnits GetAlignOfExpr(const ASTContext &Ctx, const Expr *E, UnaryExprOrTypeTrait ExprKind); +uint64_t GetAlignOfDynamicAlloc(const ASTContext &Ctx, QualType AllocType, + const Expr *AllocExpr); + uint8_t GFNIMultiplicativeInverse(uint8_t Byte); uint8_t GFNIMul(uint8_t AByte, uint8_t BByte); uint8_t GFNIAffine(uint8_t XByte, const llvm::APInt &AQword, diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 8618979d1eba0..34324ba5bd135 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -59,6 +59,7 @@ #include "llvm/ADT/Sequence.h" #include "llvm/ADT/SmallBitVector.h" #include "llvm/ADT/StringExtras.h" +#include "llvm/ADT/bit.h" #include "llvm/Support/Casting.h" #include "llvm/Support/Debug.h" #include "llvm/Support/SaveAndRestore.h" @@ -766,12 +767,14 @@ namespace { /// Get the kind of the allocation. This must match between allocation /// and deallocation. - Kind getKind() const { + static Kind kindOfExpr(const Expr *AllocExpr) { if (auto *NE = dyn_cast<CXXNewExpr>(AllocExpr)) return NE->isArray() ? ArrayNew : New; assert(isa<CallExpr>(AllocExpr)); return StdAllocator; } + + Kind getKind() const { return kindOfExpr(AllocExpr); } }; struct DynAllocOrder { @@ -1956,13 +1959,92 @@ APValue &CallStackFrame::createLocal(APValue::LValueBase Base, const void *Key, return Result; } +uint64_t GetAlignOfDynamicAlloc(const ASTContext &Ctx, QualType AllocType, + const Expr *AllocExpr) { + const TargetInfo &TI = Ctx.getTargetInfo(); + const uint64_t DefaultNewAlign = TI.getNewAlign(); + const uint64_t MaxFundamentalAlign = + std::max(TI.getLongLongAlign(), TI.getLongDoubleAlign()); + const DynAlloc::Kind AllocKind = DynAlloc::kindOfExpr(AllocExpr); + + const uint64_t AllocSize = Ctx.getTypeSize(AllocType); + + // Allocating a zero-sized array is allowed, however it doesn't have + // any alignment guarantees. + if ((AllocKind == DynAlloc::Kind::ArrayNew || + AllocKind == DynAlloc::Kind::StdAllocator) && + AllocSize == 0) + return TI.getCharWidth(); + + // For the non-array form, the size of the allocated object should be + // positive. + assert(AllocSize > 0 && "Unknown size for allocated type!"); + + const uint64_t TypeAlignment = Ctx.getTypeAlign(AllocType); + assert(TypeAlignment > 0 && "Unknown alignment for allocated type!"); + + // For new-extended alignment the ::operator new overload with + // std::align_val_t parameter is used. According to C++ + // [basic.stc.dynamic.allocation]p3.1 this overload returns memory + // according to the requested alignment. No stricter guarantees are + // made. + if (TypeAlignment > DefaultNewAlign) + return TypeAlignment; + + switch (AllocKind) { + // According to C++ [allocator.members]p5 it is unspecified how the + // memory obtained from ::operator new is used by + // std::allocator::allocate, therefore be conservative here. + case DynAlloc::Kind::StdAllocator: + return TypeAlignment; + + // The non-array form of new does not permit allocation overhead and + // therefore provides alignment as guaranteed by ::operator new. + // According to C++ [basic.stc.dynamic.allocation]p3.3 the allocation + // is suitably aligned for all objects without new-extended alignment + // with the exact size of the allocation. An object of the exact size + // AllocSize can have alignment of at most the lowest bit set in + // AllocSize. + case DynAlloc::Kind::New: + return std::min(DefaultNewAlign, + uint64_t{1} << llvm::countr_zero(AllocSize)); + + case DynAlloc::Kind::ArrayNew: { + const Type *ET = AllocType.getTypePtr() + ->getArrayElementTypeNoTypeQual() + ->getCanonicalTypeUnqualified() + .getTypePtr(); + // According to C++ [expr.new]p17, unless the element type of an + // array new expression is char, unsigned char or std::byte, the + // allocation may be offset into the allocation returned by + // ::operator new[]. Therefore no stricter alignment than the type's + // alignment is guaranteed. For char, unsigned char and std::byte + if (!ET->isSpecificBuiltinType(BuiltinType::UChar) && + !ET->isSpecificBuiltinType(BuiltinType::Char_U) && + !ET->isSpecificBuiltinType(BuiltinType::Char_S) && !ET->isStdByteType()) + return TypeAlignment; + + // Otherwise, the allocation is offset from the result of ::operator + // new[] by a multiple of the strictest fundamental alignment. + // According C++ [basic.stc.dynamic.allocation]p3.2 the allocation + // returned by ::operator new[] is suitably aligned for all objects + // without new-extended alignment and size up to the allocated size. + return std::min( + {DefaultNewAlign, MaxFundamentalAlign, llvm::bit_floor(AllocSize)}); + } + + default: + llvm_unreachable("Unhandled DynAlloc::Kind"); + } +} + APValue *EvalInfo::createHeapAlloc(const Expr *E, QualType T, LValue &LV) { if (NumHeapAllocs > DynamicAllocLValue::getMaxIndex()) { FFDiag(E, diag::note_constexpr_heap_alloc_limit_exceeded); return nullptr; } - DynamicAllocLValue DA(NumHeapAllocs++); + DynamicAllocLValue DA(NumHeapAllocs++, GetAlignOfDynamicAlloc(Ctx, T, E)); LV.set(APValue::LValueBase::getDynamicAlloc(DA, T)); auto Result = HeapAllocs.emplace(std::piecewise_construct, std::forward_as_tuple(DA), std::tuple<>()); @@ -10341,6 +10423,8 @@ static CharUnits getBaseAlignment(EvalInfo &Info, const LValue &Value) { return Info.Ctx.getDeclAlign(VD); if (const auto *E = Value.Base.dyn_cast<const Expr *>()) return GetAlignOfExpr(Info.Ctx, E, UETT_AlignOf); + if (const auto &DA = Value.Base.dyn_cast<DynamicAllocLValue>()) + return Info.Ctx.toCharUnitsFromBits(DA.getAlign()); return GetAlignOfType(Info.Ctx, Value.Base.getTypeInfoType(), UETT_AlignOf); } diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp index 91967a7a9ff97..7c3ede2744977 100644 --- a/clang/lib/Sema/SemaExprCXX.cpp +++ b/clang/lib/Sema/SemaExprCXX.cpp @@ -2618,6 +2618,7 @@ ExprResult Sema::BuildCXXNew(SourceRange Range, bool UseGlobal, } else { Diag(TypeRange.getEnd(), diag::err_new_array_size_unknown_from_init) << Initializer->getSourceRange(); + return ExprError(); } } } diff --git a/clang/test/SemaCXX/builtin-assume-aligned.cpp b/clang/test/SemaCXX/builtin-assume-aligned.cpp index 30296c72c6be8..4b5bbf316f078 100644 --- a/clang/test/SemaCXX/builtin-assume-aligned.cpp +++ b/clang/test/SemaCXX/builtin-assume-aligned.cpp @@ -1,14 +1,18 @@ -// RUN: %clang_cc1 -fsyntax-only -verify -std=c++11 -triple x86_64-linux-gnu %s +// RUN: %clang_cc1 -fsyntax-only -verify=expected,cxx11 -std=c++11 -triple x86_64-linux-gnu %s +// RUN: %clang_cc1 -fsyntax-only -verify=expected,cxx11 -std=c++11 -triple x86_64-linux-gnu -fexperimental-new-constant-interpreter %s +// RUN: %clang_cc1 -fsyntax-only -verify=expected,cxx26 -std=c++26 -triple x86_64-linux-gnu %s +// RUN: %clang_cc1 -fsyntax-only -verify=expected,cxx26 -std=c++26 -triple x86_64-linux-gnu -fexperimental-new-constant-interpreter %s int n; constexpr int *p = 0; -// expected-error@+1 {{must be initialized by a constant expression}} +// expected-error@+2 {{must be initialized by a constant expression}} +// cxx26-note@+1 {{a constant expression cannot modify an object that is visible outside that expression}} constexpr int *k = (int *) __builtin_assume_aligned(p, 16, n = 5); constexpr void *l = __builtin_assume_aligned(p, 16); -// expected-error@+2 {{must be initialized by a constant expression}} -// expected-note@+1 {{cast from 'void *' is not allowed in a constant expression}} +// cxx11-error@+2 {{must be initialized by a constant expression}} +// cxx11-note@+1 {{cast from 'void *' is not allowed in a constant expression}} constexpr int *c = (int *) __builtin_assume_aligned(p, 16); // expected-error@+2 {{must be initialized by a constant expression}} @@ -60,3 +64,76 @@ void AllocateAlignedBytes() { void *m = __builtin_assume_aligned( reinterpret_cast<void *>(AllocateAlignedBytes_payload), kAlignment); } + +namespace std { + enum class byte : unsigned char {}; +} // namespace std + +namespace GH173767 { +#if __cplusplus > 202302L + constexpr int a1a = (delete[] (unsigned char*)__builtin_assume_aligned(new unsigned char[65], __STDCPP_DEFAULT_NEW_ALIGNMENT__), 0); + constexpr int a1b = (delete[] (char*)__builtin_assume_aligned(new char[65], __STDCPP_DEFAULT_NEW_ALIGNMENT__), 0); + constexpr int a1c = (delete[] (std::byte*)__builtin_assume_aligned(new std::byte[65], __STDCPP_DEFAULT_NEW_ALIGNMENT__), 0); + // expected-error@+2 {{must be initialized by a constant expression}} + // expected-note@+1 {{alignment of the base pointee object (16 bytes) is less than the asserted 32 bytes}} + constexpr int a2a = (delete[] (unsigned char*)__builtin_assume_aligned(new unsigned char[65], 2*__STDCPP_DEFAULT_NEW_ALIGNMENT__), 0); + // expected-error@+2 {{must be initialized by a constant expression}} + // expected-note@+1 {{alignment of the base pointee object (16 bytes) is less than the asserted 32 bytes}} + constexpr int a2b = (delete[] (char*)__builtin_assume_aligned(new char[65], 2*__STDCPP_DEFAULT_NEW_ALIGNMENT__), 0); + // expected-error@+2 {{must be initialized by a constant expression}} + // expected-note@+1 {{alignment of the base pointee object (16 bytes) is less than the asserted 32 bytes}} + constexpr int a2c = (delete[] (std::byte*)__builtin_assume_aligned(new std::byte[65], 2*__STDCPP_DEFAULT_NEW_ALIGNMENT__), 0); + + constexpr int b1 = (delete (int*)__builtin_assume_aligned(new int, alignof(int)), 0); + // expected-error@+2 {{must be initialized by a constant expression}} + // expected-note@+1 {{alignment of the base pointee object (4 bytes) is less than the asserted 8 bytes}} + constexpr int b2 = (delete (int*)__builtin_assume_aligned(new int, 2*alignof(int)), 0); + + constexpr int c1 = (delete[] (int*)__builtin_assume_aligned(new int[4], alignof(int)), 0); + // expected-error@+2 {{must be initialized by a constant expression}} + // expected-note@+1 {{alignment of the base pointee object (4 bytes) is less than the asserted 8 bytes}} + constexpr int c2 = (delete[] (int*)__builtin_assume_aligned(new int[4], 2*alignof(int)), 0); + + struct D { + alignas(2*__STDCPP_DEFAULT_NEW_ALIGNMENT__) int x[2]; + }; + + constexpr int d1 = (delete (D*)__builtin_assume_aligned(new D, alignof(D)), 0); + // expected-error@+2 {{must be initialized by a constant expression}} + // expected-note@+1 {{alignment of the base pointee object (32 bytes) is less than the asserted 64 bytes}} + constexpr int d2 = (delete (D*)__builtin_assume_aligned(new D, 2*alignof(D)), 0); + + constexpr int d3 = []{ + auto p = new D; + (void)__builtin_assume_aligned(p->x + 1, alignof(int)); + delete p; + return 0; + }(); + + // expected-error@+3 {{must be initialized by a constant expression}} + // expected-note@+2 {{in call to}} + // expected-note@+3 {{offset of the aligned pointer from the base pointee object (4 bytes) is not a multiple of the asserted 8 bytes}} + constexpr int d4 = []{ + auto p = new D; + (void)__builtin_assume_aligned(p->x + 1, 2*alignof(int)); + delete p; + return 0; + }(); + + struct E { + unsigned char x[65]; + }; + + constexpr int e1 = (delete (E*)__builtin_assume_aligned(new E, 1), 0); + // expected-error@+2 {{must be initialized by a constant expression}} + // expected-note@+1 {{alignment of the base pointee object (1 byte) is less than the asserted 2 bytes}} + constexpr int e2 = (delete (E*)__builtin_assume_aligned(new E, 2), 0); + + constexpr int f = []{ + auto p = new int; + delete p; + (void)__builtin_assume_aligned(p, alignof(int)); + return 0; + }(); +#endif +} // namespace GH173767 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
