llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT--> @llvm/pr-subscribers-clang Author: Timm Baeder (tbaederr) <details> <summary>Changes</summary> Implement handling for new/delete/new[]/delete[] expressions via a new `DynamicAllocator` class. This introduces four new opcodes: - `Alloc` - Allocates one element (`new int(14)`) - `AllocN` - Allocates N elements of the given primitive (`new int[100]`) - `AllocCN` - Allocates N elements of the given (composite) descriptor (`new S[100]`) - `Free` - de-allocates memory allocates using any of the above. This patch currently has a few NFC changes (like adding `const` in some places) that I will push to `main` later today. Some other changes I just noticed are in `tests/`, which I will just remove. --- Patch is 41.19 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/70306.diff 20 Files Affected: - (modified) clang/lib/AST/CMakeLists.txt (+1) - (modified) clang/lib/AST/ExprConstant.cpp (+1-1) - (modified) clang/lib/AST/Interp/ByteCodeExprGen.cpp (+76) - (modified) clang/lib/AST/Interp/ByteCodeExprGen.h (+2) - (modified) clang/lib/AST/Interp/Context.cpp (+2-1) - (modified) clang/lib/AST/Interp/Descriptor.cpp (+1-1) - (modified) clang/lib/AST/Interp/Descriptor.h (+3-3) - (added) clang/lib/AST/Interp/DynamicAllocator.cpp (+91) - (added) clang/lib/AST/Interp/DynamicAllocator.h (+90) - (modified) clang/lib/AST/Interp/EvalEmitter.cpp (+3-2) - (modified) clang/lib/AST/Interp/Interp.cpp (+37) - (modified) clang/lib/AST/Interp/Interp.h (+94) - (modified) clang/lib/AST/Interp/InterpBlock.h (+5-5) - (modified) clang/lib/AST/Interp/InterpState.cpp (+17) - (modified) clang/lib/AST/Interp/InterpState.h (+10) - (modified) clang/lib/AST/Interp/Opcodes.td (+25) - (modified) clang/lib/AST/Interp/Pointer.cpp (+1-1) - (modified) clang/lib/AST/Interp/Pointer.h (+4-3) - (added) clang/test/AST/Interp/new-delete.cpp (+247) - (modified) clang/test/SemaCXX/cxx2a-consteval.cpp (+2-41) ``````````diff diff --git a/clang/lib/AST/CMakeLists.txt b/clang/lib/AST/CMakeLists.txt index fe3f8c485ec1c56..1423623fb038cab 100644 --- a/clang/lib/AST/CMakeLists.txt +++ b/clang/lib/AST/CMakeLists.txt @@ -75,6 +75,7 @@ add_clang_library(clangAST Interp/Function.cpp Interp/InterpBuiltin.cpp Interp/Floating.cpp + Interp/DynamicAllocator.cpp Interp/Interp.cpp Interp/InterpBlock.cpp Interp/InterpFrame.cpp diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 6b47b8a1256477d..0a95259821466e2 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -6860,8 +6860,8 @@ static std::optional<DynAlloc *> CheckDeleteKind(EvalInfo &Info, const Expr *E, return std::nullopt; } - QualType AllocType = Pointer.Base.getDynamicAllocType(); if (DeallocKind != (*Alloc)->getKind()) { + QualType AllocType = Pointer.Base.getDynamicAllocType(); Info.FFDiag(E, diag::note_constexpr_new_delete_mismatch) << DeallocKind << (*Alloc)->getKind() << AllocType; NoteLValueLocation(Info, Pointer.Base); diff --git a/clang/lib/AST/Interp/ByteCodeExprGen.cpp b/clang/lib/AST/Interp/ByteCodeExprGen.cpp index 1b33c69b93aa4b9..c42fcabe3b075ba 100644 --- a/clang/lib/AST/Interp/ByteCodeExprGen.cpp +++ b/clang/lib/AST/Interp/ByteCodeExprGen.cpp @@ -1617,6 +1617,82 @@ bool ByteCodeExprGen<Emitter>::VisitCXXScalarValueInitExpr( return this->visitZeroInitializer(classifyPrim(Ty), Ty, E); } +template <class Emitter> +bool ByteCodeExprGen<Emitter>::VisitCXXNewExpr(const CXXNewExpr *E) { + assert(classifyPrim(E->getType()) == PT_Ptr); + const Expr *Init = E->getInitializer(); + QualType ElementType = E->getAllocatedType(); + std::optional<PrimType> ElemT = classify(ElementType); + + const Descriptor *Desc; + if (ElemT) { + if (E->isArray()) + Desc = nullptr; // We're not going to use it in this case. + else + Desc = P.createDescriptor(E, *ElemT, Descriptor::InlineDescMD, + /*IsConst=*/false, /*IsTemporary=*/false, + /*IsMutable=*/false); + } else { + Desc = P.createDescriptor( + E, ElementType.getTypePtr(), + E->isArray() ? std::nullopt : Descriptor::InlineDescMD, + /*IsConst=*/false, /*IsTemporary=*/false, /*IsMutable=*/false, Init); + } + + if (E->isArray()) { + assert(E->getArraySize()); + PrimType SizeT = classifyPrim((*E->getArraySize())->getType()); + + if (!this->visit(*E->getArraySize())) + return false; + + if (ElemT) { + // N primitive elements. + if (!this->emitAllocN(SizeT, *ElemT, E, E)) + return false; + } else { + // N Composite elements. + if (!this->emitAllocCN(SizeT, Desc, E)) + return false; + } + + } else { + // Allocate just one element. + if (!this->emitAlloc(Desc, E)) + return false; + + if (Init) { + if (ElemT) { + if (!this->visit(Init)) + return false; + + if (!this->emitInit(*ElemT, E)) + return false; + } else { + // Composite. + if (!this->visitInitializer(Init)) + return false; + } + } + } + + if (DiscardResult) + return this->emitPopPtr(E); + + return true; +} + +template <class Emitter> +bool ByteCodeExprGen<Emitter>::VisitCXXDeleteExpr(const CXXDeleteExpr *E) { + const Expr *Arg = E->getArgument(); + + // Arg must be an lvalue. + if (!this->visit(Arg)) + return false; + + return this->emitFree(E->isArrayForm(), E); +} + template <class Emitter> bool ByteCodeExprGen<Emitter>::discard(const Expr *E) { if (E->containsErrors()) return false; diff --git a/clang/lib/AST/Interp/ByteCodeExprGen.h b/clang/lib/AST/Interp/ByteCodeExprGen.h index 83986d3dd579ed6..f65dc9494db36ef 100644 --- a/clang/lib/AST/Interp/ByteCodeExprGen.h +++ b/clang/lib/AST/Interp/ByteCodeExprGen.h @@ -107,6 +107,8 @@ class ByteCodeExprGen : public ConstStmtVisitor<ByteCodeExprGen<Emitter>, bool>, bool VisitSourceLocExpr(const SourceLocExpr *E); bool VisitOffsetOfExpr(const OffsetOfExpr *E); bool VisitCXXScalarValueInitExpr(const CXXScalarValueInitExpr *E); + bool VisitCXXNewExpr(const CXXNewExpr *E); + bool VisitCXXDeleteExpr(const CXXDeleteExpr *E); protected: bool visitExpr(const Expr *E) override; diff --git a/clang/lib/AST/Interp/Context.cpp b/clang/lib/AST/Interp/Context.cpp index cb96e56fb5e1ad8..93da5267bc1e6f4 100644 --- a/clang/lib/AST/Interp/Context.cpp +++ b/clang/lib/AST/Interp/Context.cpp @@ -164,7 +164,7 @@ bool Context::Run(State &Parent, const Function *Func, APValue &Result) { State.Current = new InterpFrame(State, Func, /*Caller=*/nullptr, {}); if (Interpret(State, Result)) { assert(Stk.empty()); - return true; + return !State.maybeDiagnoseDanglingAllocations(); } // State gets destroyed here, so the Stk.clear() below doesn't accidentally @@ -177,6 +177,7 @@ bool Context::Run(State &Parent, const Function *Func, APValue &Result) { bool Context::Check(State &Parent, llvm::Expected<bool> &&Flag) { if (Flag) + // return !Parent.maybeDiagnoseDanglingAllocations(); return *Flag; handleAllErrors(Flag.takeError(), [&Parent](ByteCodeGenError &Err) { Parent.FFDiag(Err.getRange().getBegin(), diff --git a/clang/lib/AST/Interp/Descriptor.cpp b/clang/lib/AST/Interp/Descriptor.cpp index abd75a8d67bbd3f..2a21f60588d46cd 100644 --- a/clang/lib/AST/Interp/Descriptor.cpp +++ b/clang/lib/AST/Interp/Descriptor.cpp @@ -262,7 +262,7 @@ Descriptor::Descriptor(const DeclTy &D, PrimType Type, bool IsTemporary, } /// Arrays of composite elements. -Descriptor::Descriptor(const DeclTy &D, Descriptor *Elem, MetadataSize MD, +Descriptor::Descriptor(const DeclTy &D, const Descriptor *Elem, MetadataSize MD, unsigned NumElems, bool IsConst, bool IsTemporary, bool IsMutable) : Source(D), ElemSize(Elem->getAllocSize() + sizeof(InlineDescriptor)), diff --git a/clang/lib/AST/Interp/Descriptor.h b/clang/lib/AST/Interp/Descriptor.h index be9a380138a7b11..2fd4e9208264525 100644 --- a/clang/lib/AST/Interp/Descriptor.h +++ b/clang/lib/AST/Interp/Descriptor.h @@ -72,7 +72,7 @@ struct InlineDescriptor { /// Flag indicating if the field is mutable (if in a record). unsigned IsFieldMutable : 1; - Descriptor *Desc; + const Descriptor *Desc; }; /// Describes a memory block created by an allocation site. @@ -102,7 +102,7 @@ struct Descriptor final { /// Pointer to the record, if block contains records. Record *const ElemRecord = nullptr; /// Descriptor of the array element. - Descriptor *const ElemDesc = nullptr; + const Descriptor *const ElemDesc = nullptr; /// Flag indicating if the block is mutable. const bool IsConst = false; /// Flag indicating if a field is mutable. @@ -129,7 +129,7 @@ struct Descriptor final { Descriptor(const DeclTy &D, PrimType Type, bool IsTemporary, UnknownSize); /// Allocates a descriptor for an array of composites. - Descriptor(const DeclTy &D, Descriptor *Elem, MetadataSize MD, + Descriptor(const DeclTy &D, const Descriptor *Elem, MetadataSize MD, unsigned NumElems, bool IsConst, bool IsTemporary, bool IsMutable); /// Allocates a descriptor for an array of composites of unknown size. diff --git a/clang/lib/AST/Interp/DynamicAllocator.cpp b/clang/lib/AST/Interp/DynamicAllocator.cpp new file mode 100644 index 000000000000000..a353d09d1f960ea --- /dev/null +++ b/clang/lib/AST/Interp/DynamicAllocator.cpp @@ -0,0 +1,91 @@ +//==---- DynamicAllocator.cpp - Types for the constexpr VM -------*- C++ -*-==// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "DynamicAllocator.h" +#include "InterpBlock.h" +#include "InterpState.h" + +using namespace clang; +using namespace clang::interp; + +Block *DynamicAllocator::allocate(const Expr *Source, PrimType T, + size_t NumElements) { + assert(NumElements > 0); + // Create a new descriptor for an array of the specified size and + // element type. + const Descriptor *D = allocateDescriptor( + Source, T, Descriptor::InlineDescMD, NumElements, /*IsConst=*/false, + /*IsTemporary=*/false, /*IsMutable=*/false); + return allocate(D); +} + +Block *DynamicAllocator::allocate(const Descriptor *ElementDesc, + size_t NumElements) { + assert(NumElements > 0); + // Create a new descriptor for an array of the specified size and + // element type. + const Descriptor *D = allocateDescriptor( + ElementDesc->asExpr(), ElementDesc, Descriptor::InlineDescMD, NumElements, + /*IsConst=*/false, /*IsTemporary=*/false, /*IsMutable=*/false); + return allocate(D); +} + +Block *DynamicAllocator::allocate(const Descriptor *D) { + assert(D->asExpr()); + + auto Memory = + std::make_unique<std::byte[]>(sizeof(Block) + D->getAllocSize()); + auto *B = new (Memory.get()) Block(D, /*isStatic=*/false); + B->invokeCtor(); + + InlineDescriptor *ID = reinterpret_cast<InlineDescriptor *>(B->rawData()); + ID->Desc = const_cast<Descriptor *>(D); + ID->IsActive = true; + ID->Offset = sizeof(InlineDescriptor); + ID->IsBase = false; + ID->IsFieldMutable = false; + ID->IsConst = false; + ID->IsInitialized = false; + assert(ID->Desc); + + if (auto It = AllocationSites.find(D->asExpr()); It != AllocationSites.end()) + It->second.Allocations.emplace_back(std::move(Memory)); + else + AllocationSites.insert( + {D->asExpr(), AllocationSite(std::move(Memory), D->isArray())}); + return B; +} + +void DynamicAllocator::deallocate(const Expr *Source, + const Block *BlockToDelete, InterpState &S) { + assert(AllocationSites.contains(Source)); + + auto It = AllocationSites.find(Source); + assert(It != AllocationSites.end()); + + auto &Site = It->second; + assert(Site.size() > 0); + + // Find the Block to delete. + size_t I = 0; + [[maybe_unused]] bool Found = false; + for (auto &A : Site.Allocations) { + Block *B = reinterpret_cast<Block *>(A.Memory.get()); + if (B == BlockToDelete) { + S.deallocate(B); + B->invokeDtor(); + Site.Allocations.erase(Site.Allocations.begin() + I); + Found = true; + break; + } + } + assert(Found); + + if (Site.size() == 0) + AllocationSites.erase(It); +} diff --git a/clang/lib/AST/Interp/DynamicAllocator.h b/clang/lib/AST/Interp/DynamicAllocator.h new file mode 100644 index 000000000000000..d2a1d96232b9730 --- /dev/null +++ b/clang/lib/AST/Interp/DynamicAllocator.h @@ -0,0 +1,90 @@ +//==- DynamicAllocator.h - Bytecode allocator for the constexpr VM -*- C++ +//-*-=// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_AST_INTERP_DYNAMIC_ALLOCATOR_H +#define LLVM_CLANG_AST_INTERP_DYNAMIC_ALLOCATOR_H + +#include "Descriptor.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/iterator_range.h" +#include "llvm/Support/Allocator.h" + +namespace clang { +class Expr; +namespace interp { +class Block; +class InterpState; + +/// Manages dynamic memory allocations done during bytecode interpretation. +/// +/// We manage allocations as a map from their new-expression to a list +/// of allocations. This is called an AllocationSite. For each site, we +/// record whether it was allocated using new or new[], the +/// IsArrayAllocation flag. +/// +/// For all array allocations, we need to allocat new Descriptor instances, +/// so the DynamicAllocator has a llvm::BumpPtrAllocator similar to Program. +class DynamicAllocator final { + struct Allocation { + std::unique_ptr<std::byte[]> Memory; + Allocation(std::unique_ptr<std::byte[]> Memory) + : Memory(std::move(Memory)) {} + }; + + struct AllocationSite { + llvm::SmallVector<Allocation> Allocations; + bool IsArrayAllocation = false; + + AllocationSite(std::unique_ptr<std::byte[]> Memory, bool Array) + : IsArrayAllocation(Array) { + Allocations.push_back({std::move(Memory)}); + } + + size_t size() const { return Allocations.size(); } + }; + +public: + DynamicAllocator() = default; + + unsigned getNumAllocations() const { return AllocationSites.size(); } + + /// Allocate ONE element of the given descriptor. + Block *allocate(const Descriptor *D); + /// Allocate \p NumElements primitive elements of the given type. + Block *allocate(const Expr *Source, PrimType T, size_t NumElements); + /// Allocate \p NumElements elements of the given descriptor. + Block *allocate(const Descriptor *D, size_t NumElements); + + /// Deallocate the given source+block combination. + void deallocate(const Expr *Source, const Block *BlockToDelete, + InterpState &S); + + /// Checks whether the allocation done at the given source is an array + /// allocation. + bool isArrayAllocation(const Expr *Source) const { + assert(AllocationSites.contains(Source)); + return AllocationSites.at(Source).IsArrayAllocation; + } + + // FIXME: Public because I'm not sure how to expose an iterator to it. + llvm::DenseMap<const Expr *, AllocationSite> AllocationSites; + +private: + using PoolAllocTy = llvm::BumpPtrAllocatorImpl<llvm::MallocAllocator>; + PoolAllocTy DescAllocator; + + /// Allocates a new descriptor. + template <typename... Ts> Descriptor *allocateDescriptor(Ts &&...Args) { + return new (DescAllocator) Descriptor(std::forward<Ts>(Args)...); + } +}; + +} // namespace interp +} // namespace clang +#endif diff --git a/clang/lib/AST/Interp/EvalEmitter.cpp b/clang/lib/AST/Interp/EvalEmitter.cpp index f8942291b3b162d..14d5446f64324ee 100644 --- a/clang/lib/AST/Interp/EvalEmitter.cpp +++ b/clang/lib/AST/Interp/EvalEmitter.cpp @@ -35,7 +35,7 @@ EvalEmitter::~EvalEmitter() { llvm::Expected<bool> EvalEmitter::interpretExpr(const Expr *E) { if (this->visitExpr(E)) - return true; + return S.maybeDiagnoseDanglingAllocations(); if (BailLocation) return llvm::make_error<ByteCodeGenError>(*BailLocation); return false; @@ -43,7 +43,7 @@ llvm::Expected<bool> EvalEmitter::interpretExpr(const Expr *E) { llvm::Expected<bool> EvalEmitter::interpretDecl(const VarDecl *VD) { if (this->visitDecl(VD)) - return true; + return S.maybeDiagnoseDanglingAllocations(); if (BailLocation) return llvm::make_error<ByteCodeGenError>(*BailLocation); return false; @@ -122,6 +122,7 @@ template <PrimType OpType> bool EvalEmitter::emitRet(const SourceInfo &Info) { bool EvalEmitter::emitRetVoid(const SourceInfo &Info) { return true; } bool EvalEmitter::emitRetValue(const SourceInfo &Info) { + llvm::errs() << __PRETTY_FUNCTION__ << "\n"; // Method to recursively traverse composites. std::function<bool(QualType, const Pointer &, APValue &)> Composite; Composite = [this, &Composite](QualType Ty, const Pointer &Ptr, APValue &R) { diff --git a/clang/lib/AST/Interp/Interp.cpp b/clang/lib/AST/Interp/Interp.cpp index 8b0e7beb4a1acc1..3ec093b16c8f9c2 100644 --- a/clang/lib/AST/Interp/Interp.cpp +++ b/clang/lib/AST/Interp/Interp.cpp @@ -548,6 +548,43 @@ bool CheckFloatResult(InterpState &S, CodePtr OpPC, const Floating &Result, return true; } +bool CheckDynamicMemoryAllocation(InterpState &S, CodePtr OpPC) { + if (S.getLangOpts().CPlusPlus20) + return true; + + const SourceInfo &E = S.Current->getSource(OpPC); + S.FFDiag(E, diag::note_constexpr_new); + return false; +} + +bool CheckNewDeleteForms(InterpState &S, CodePtr OpPC, bool NewWasArray, + bool DeleteIsArray, const Block *B, + const Expr *NewExpr) { + if (NewWasArray == DeleteIsArray) + return true; + + const Descriptor *D = B->getDescriptor(); + + QualType TypeToDiagnose; + // We need to shuffle things around a bit here to get a better diagnostic, + // because the expression we allocated the block for was of type int*, + // but we want to get the array size right. + if (D->isArray()) { + QualType ElemQT = D->getType()->getPointeeType(); + TypeToDiagnose = S.getCtx().getConstantArrayType( + ElemQT, APInt(64, D->getNumElems(), false), nullptr, ArrayType::Normal, + 0); + } else + TypeToDiagnose = D->getType()->getPointeeType(); + + const SourceInfo &E = S.Current->getSource(OpPC); + S.FFDiag(E, diag::note_constexpr_new_delete_mismatch) + << DeleteIsArray << 0 << TypeToDiagnose; + S.Note(NewExpr->getExprLoc(), diag::note_constexpr_dynamic_alloc_here) + << NewExpr->getSourceRange(); + return false; +} + /// We aleady know the given DeclRefExpr is invalid for some reason, /// now figure out why and print appropriate diagnostics. bool CheckDeclRef(InterpState &S, CodePtr OpPC, const DeclRefExpr *DR) { diff --git a/clang/lib/AST/Interp/Interp.h b/clang/lib/AST/Interp/Interp.h index 7ef1e344224a3c3..9062e4a81772888 100644 --- a/clang/lib/AST/Interp/Interp.h +++ b/clang/lib/AST/Interp/Interp.h @@ -14,6 +14,7 @@ #define LLVM_CLANG_AST_INTERP_INTERP_H #include "Boolean.h" +#include "DynamicAllocator.h" #include "Floating.h" #include "Function.h" #include "FunctionPointer.h" @@ -112,6 +113,15 @@ bool CheckCtorCall(InterpState &S, CodePtr OpPC, const Pointer &This); bool CheckPotentialReinterpretCast(InterpState &S, CodePtr OpPC, const Pointer &Ptr); +/// Checks if dynamic memory allocation is available in the current +/// language mode. +bool CheckDynamicMemoryAllocation(InterpState &S, CodePtr OpPC); + +/// Diagnose mismatched new[]/delete or new/delete[] pairs. +bool CheckNewDeleteForms(InterpState &S, CodePtr OpPC, bool NewWasArray, + bool DeleteIsArray, const Block *B, + const Expr *NewExpr); + /// Sets the given integral value to the pointer, which is of /// a std::{weak,partial,strong}_ordering type. bool SetThreeWayComparisonField(InterpState &S, CodePtr OpPC, @@ -1369,6 +1379,17 @@ bool InitPop(InterpState &S, CodePtr OpPC) { return true; } +template <PrimType Name, class T = typename PrimConv<Name>::T> +bool Init(InterpState &S, CodePtr OpPC) { + const T &Value = S.Stk.pop<T>(); + const Pointer &Ptr = S.Stk.peek<Pointer>(); + if (!CheckInit(S, OpPC, Ptr)) + return false; + Ptr.initialize(); + new (&Ptr.deref<T>()) T(Value); + return true; +} + /// 1) Pops the value from the stack /// 2) Peeks a pointer and gets its index \Idx /// 3) Sets the value on the pointer, leaving the pointer on the stack. @@ -1990,6 +2011,79 @@ inline bool OffsetOf(InterpState &S, CodePtr OpPC, const OffsetOfExpr *E) { return true; } +inline bool Alloc(InterpState &S, CodePtr OpPC, const Descriptor *Desc) { + assert(Desc); + + if (!CheckDynamicMemoryAllocation(S, OpPC)) + return false; + + DynamicAllocator &Allocator = S.getAllocator(); + Block *B = Allocator.allocate(Desc); + + S.Stk.push<Pointer>(B, sizeof(InlineDescriptor)); + + return true; +} + +template <PrimType Name, class SizeT = typename PrimConv<Name>::T> +inline bool AllocN(InterpState &S, CodePtr OpPC, PrimType T, + const Expr *Source) { + if (!CheckDynamicMemoryAllocation(S, OpPC)) + return false; + + SizeT NumElements = S.Stk.pop<SizeT>(); + + DynamicAllocator &Allocator = S.getAllocator(); + Block *B = Allocator.allocate(Source, T, static_cast<size_t>(NumElements)); + + S.Stk.push<Pointer>(B, sizeof(InlineDescriptor)); + + return true; +} + +template <PrimType Name, class SizeT = typename PrimConv<Name>::T> +inline bool AllocCN(InterpState &S, CodePtr OpPC, + const Descriptor *ElementDesc) { + if (!CheckDynamicMemoryAllocation(S, OpPC)) + return false; + + SizeT NumElements = S.Stk.pop<SizeT>(); + + DynamicAllocator &Allocator = S.getAllocator(); + Block *B = Allocator.allocate(ElementDesc, static_cast<size_t>(NumElements)); + + S.Stk.push<Pointer>(B, sizeof(InlineDescriptor)); + + return true; +} + +static inline bool Free(InterpState &S, CodePtr OpPC, bool DeleteIsArr... [truncated] `````````` </details> https://github.com/llvm/llvm-project/pull/70306 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits