llvm-project/clang/lib/CIR/CodeGen/CIRGenExprCXX.cpp
Andy Kaylor 729480c4ae
[CIR] Generalize cxx alloc new size handling (#187790)
The non-constant size handling in `emitCXXNewAllocSize` was making the
incorrect assumption that the default behavior of the size value being
explicitly cast to size_t would be the only behavior we'd see. This is
actually only true with C++14 and later. To properly handle earlier
standards, we need the more robust checking that classic codegen does in
the equivalent function. This change adds that handling.

Assisted-by: Cursor / claude-4.6-opus-high
2026-03-23 11:47:16 -07:00

1727 lines
70 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//===--- CIRGenExprCXX.cpp - Emit CIR Code for C++ expressions ------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// This contains code dealing with code generation of C++ expressions
//
//===----------------------------------------------------------------------===//
#include "CIRGenCXXABI.h"
#include "CIRGenConstantEmitter.h"
#include "CIRGenFunction.h"
#include "clang/AST/CharUnits.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/ExprCXX.h"
#include "clang/AST/ExprObjC.h"
#include "clang/Basic/OperatorKinds.h"
#include "clang/CIR/MissingFeatures.h"
#include "llvm/Support/TrailingObjects.h"
using namespace clang;
using namespace clang::CIRGen;
namespace {
struct MemberCallInfo {
RequiredArgs reqArgs;
// Number of prefix arguments for the call. Ignores the `this` pointer.
unsigned prefixSize;
};
} // namespace
static MemberCallInfo commonBuildCXXMemberOrOperatorCall(
CIRGenFunction &cgf, const CXXMethodDecl *md, mlir::Value thisPtr,
mlir::Value implicitParam, QualType implicitParamTy, const CallExpr *ce,
CallArgList &args, CallArgList *rtlArgs) {
assert(ce == nullptr || isa<CXXMemberCallExpr>(ce) ||
isa<CXXOperatorCallExpr>(ce));
assert(md->isInstance() &&
"Trying to emit a member or operator call expr on a static method!");
// Push the this ptr.
const CXXRecordDecl *rd =
cgf.cgm.getCXXABI().getThisArgumentTypeForMethod(md);
args.add(RValue::get(thisPtr), cgf.getTypes().deriveThisType(rd, md));
// If there is an implicit parameter (e.g. VTT), emit it.
if (implicitParam) {
args.add(RValue::get(implicitParam), implicitParamTy);
}
const auto *fpt = md->getType()->castAs<FunctionProtoType>();
RequiredArgs required =
RequiredArgs::getFromProtoWithExtraSlots(fpt, args.size());
unsigned prefixSize = args.size() - 1;
// Add the rest of the call args
if (rtlArgs) {
// Special case: if the caller emitted the arguments right-to-left already
// (prior to emitting the *this argument), we're done. This happens for
// assignment operators.
args.addFrom(*rtlArgs);
} else if (ce) {
// Special case: skip first argument of CXXOperatorCall (it is "this").
unsigned argsToSkip = isa<CXXOperatorCallExpr>(ce) ? 1 : 0;
cgf.emitCallArgs(args, fpt, drop_begin(ce->arguments(), argsToSkip),
ce->getDirectCallee());
} else {
assert(
fpt->getNumParams() == 0 &&
"No CallExpr specified for function with non-zero number of arguments");
}
// return {required, prefixSize};
return {required, prefixSize};
}
RValue
CIRGenFunction::emitCXXMemberPointerCallExpr(const CXXMemberCallExpr *ce,
ReturnValueSlot returnValue) {
const BinaryOperator *bo =
cast<BinaryOperator>(ce->getCallee()->IgnoreParens());
const Expr *baseExpr = bo->getLHS();
const Expr *memFnExpr = bo->getRHS();
const auto *mpt = memFnExpr->getType()->castAs<MemberPointerType>();
const auto *fpt = mpt->getPointeeType()->castAs<FunctionProtoType>();
// Emit the 'this' pointer.
Address thisAddr = Address::invalid();
if (bo->getOpcode() == BO_PtrMemI)
thisAddr = emitPointerWithAlignment(baseExpr);
else
thisAddr = emitLValue(baseExpr).getAddress();
assert(!cir::MissingFeatures::emitTypeCheck());
// Get the member function pointer.
mlir::Value memFnPtr = emitScalarExpr(memFnExpr);
// Resolve the member function pointer to the actual callee and adjust the
// "this" pointer for call.
mlir::Location loc = getLoc(ce->getExprLoc());
auto [/*mlir::Value*/ calleePtr, /*mlir::Value*/ adjustedThis] =
builder.createGetMethod(loc, memFnPtr, thisAddr.getPointer());
// Prepare the call arguments.
CallArgList argsList;
argsList.add(RValue::get(adjustedThis), getContext().VoidPtrTy);
emitCallArgs(argsList, fpt, ce->arguments());
RequiredArgs required = RequiredArgs::getFromProtoWithExtraSlots(fpt, 1);
// Build the call.
CIRGenCallee callee(fpt, calleePtr.getDefiningOp());
assert(!cir::MissingFeatures::opCallMustTail());
return emitCall(cgm.getTypes().arrangeCXXMethodCall(argsList, fpt, required,
/*PrefixSize=*/0),
callee, returnValue, argsList, nullptr, loc);
}
RValue CIRGenFunction::emitCXXMemberOrOperatorMemberCallExpr(
const CallExpr *ce, const CXXMethodDecl *md, ReturnValueSlot returnValue,
bool hasQualifier, NestedNameSpecifier qualifier, bool isArrow,
const Expr *base) {
assert(isa<CXXMemberCallExpr>(ce) || isa<CXXOperatorCallExpr>(ce));
// Compute the object pointer.
bool canUseVirtualCall = md->isVirtual() && !hasQualifier;
const CXXMethodDecl *devirtualizedMethod = nullptr;
assert(!cir::MissingFeatures::devirtualizeMemberFunction());
// Note on trivial assignment
// --------------------------
// Classic codegen avoids generating the trivial copy/move assignment operator
// when it isn't necessary, choosing instead to just produce IR with an
// equivalent effect. We have chosen not to do that in CIR, instead emitting
// trivial copy/move assignment operators and allowing later transformations
// to optimize them away if appropriate.
// C++17 demands that we evaluate the RHS of a (possibly-compound) assignment
// operator before the LHS.
CallArgList rtlArgStorage;
CallArgList *rtlArgs = nullptr;
if (auto *oce = dyn_cast<CXXOperatorCallExpr>(ce)) {
if (oce->isAssignmentOp()) {
rtlArgs = &rtlArgStorage;
emitCallArgs(*rtlArgs, md->getType()->castAs<FunctionProtoType>(),
drop_begin(ce->arguments(), 1), ce->getDirectCallee(),
/*ParamsToSkip*/ 0);
}
}
LValue thisPtr;
if (isArrow) {
LValueBaseInfo baseInfo;
assert(!cir::MissingFeatures::opTBAA());
Address thisValue = emitPointerWithAlignment(base, &baseInfo);
thisPtr = makeAddrLValue(thisValue, base->getType(), baseInfo);
} else {
thisPtr = emitLValue(base);
}
if (isa<CXXConstructorDecl>(md)) {
cgm.errorNYI(ce->getSourceRange(),
"emitCXXMemberOrOperatorMemberCallExpr: constructor call");
return RValue::get(nullptr);
}
if ((md->isTrivial() || (md->isDefaulted() && md->getParent()->isUnion())) &&
isa<CXXDestructorDecl>(md))
return RValue::get(nullptr);
// Compute the function type we're calling
const CXXMethodDecl *calleeDecl =
devirtualizedMethod ? devirtualizedMethod : md;
const CIRGenFunctionInfo *fInfo = nullptr;
if (const auto *dtor = dyn_cast<CXXDestructorDecl>(calleeDecl))
fInfo = &cgm.getTypes().arrangeCXXStructorDeclaration(
GlobalDecl(dtor, Dtor_Complete));
else
fInfo = &cgm.getTypes().arrangeCXXMethodDeclaration(calleeDecl);
cir::FuncType ty = cgm.getTypes().getFunctionType(*fInfo);
assert(!cir::MissingFeatures::sanitizers());
assert(!cir::MissingFeatures::emitTypeCheck());
// C++ [class.virtual]p12:
// Explicit qualification with the scope operator (5.1) suppresses the
// virtual call mechanism.
//
// We also don't emit a virtual call if the base expression has a record type
// because then we know what the type is.
bool useVirtualCall = canUseVirtualCall && !devirtualizedMethod;
if (const auto *dtor = dyn_cast<CXXDestructorDecl>(calleeDecl)) {
assert(ce->arg_begin() == ce->arg_end() &&
"Destructor shouldn't have explicit parameters");
assert(returnValue.isNull() && "Destructor shouldn't have return value");
if (useVirtualCall) {
cgm.getCXXABI().emitVirtualDestructorCall(*this, dtor, Dtor_Complete,
thisPtr.getAddress(),
cast<CXXMemberCallExpr>(ce));
} else {
GlobalDecl globalDecl(dtor, Dtor_Complete);
CIRGenCallee callee;
assert(!cir::MissingFeatures::appleKext());
if (!devirtualizedMethod) {
callee = CIRGenCallee::forDirect(
cgm.getAddrOfCXXStructor(globalDecl, fInfo, ty), globalDecl);
} else {
cgm.errorNYI(ce->getSourceRange(), "devirtualized destructor call");
return RValue::get(nullptr);
}
QualType thisTy =
isArrow ? base->getType()->getPointeeType() : base->getType();
// CIRGen does not pass CallOrInvoke here (different from OG LLVM codegen)
// because in practice it always null even in OG.
emitCXXDestructorCall(globalDecl, callee, thisPtr.getPointer(), thisTy,
/*implicitParam=*/nullptr,
/*implicitParamTy=*/QualType(), ce);
}
return RValue::get(nullptr);
}
CIRGenCallee callee;
if (useVirtualCall) {
callee = CIRGenCallee::forVirtual(ce, md, thisPtr.getAddress(), ty);
} else {
assert(!cir::MissingFeatures::sanitizers());
if (getLangOpts().AppleKext) {
cgm.errorNYI(ce->getSourceRange(),
"emitCXXMemberOrOperatorMemberCallExpr: AppleKext");
return RValue::get(nullptr);
}
callee = CIRGenCallee::forDirect(cgm.getAddrOfFunction(calleeDecl, ty),
GlobalDecl(calleeDecl));
}
if (md->isVirtual()) {
Address newThisAddr =
cgm.getCXXABI().adjustThisArgumentForVirtualFunctionCall(
*this, calleeDecl, thisPtr.getAddress(), useVirtualCall);
thisPtr.setAddress(newThisAddr);
}
return emitCXXMemberOrOperatorCall(
calleeDecl, callee, returnValue, thisPtr.getPointer(),
/*ImplicitParam=*/nullptr, QualType(), ce, rtlArgs);
}
RValue
CIRGenFunction::emitCXXOperatorMemberCallExpr(const CXXOperatorCallExpr *e,
const CXXMethodDecl *md,
ReturnValueSlot returnValue) {
assert(md->isInstance() &&
"Trying to emit a member call expr on a static method!");
return emitCXXMemberOrOperatorMemberCallExpr(
e, md, returnValue, /*HasQualifier=*/false, /*Qualifier=*/std::nullopt,
/*IsArrow=*/false, e->getArg(0));
}
RValue CIRGenFunction::emitCUDAKernelCallExpr(const CUDAKernelCallExpr *expr,
ReturnValueSlot returnValue) {
// Emit as a device kernel call if CUDA device code is to be generated.
if (!getLangOpts().HIP && getLangOpts().CUDAIsDevice)
cgm.errorNYI("CUDA Device side kernel call");
return cgm.getCUDARuntime().emitCUDAKernelCallExpr(*this, expr, returnValue);
}
RValue CIRGenFunction::emitCXXMemberOrOperatorCall(
const CXXMethodDecl *md, const CIRGenCallee &callee,
ReturnValueSlot returnValue, mlir::Value thisPtr, mlir::Value implicitParam,
QualType implicitParamTy, const CallExpr *ce, CallArgList *rtlArgs) {
const auto *fpt = md->getType()->castAs<FunctionProtoType>();
CallArgList args;
MemberCallInfo callInfo = commonBuildCXXMemberOrOperatorCall(
*this, md, thisPtr, implicitParam, implicitParamTy, ce, args, rtlArgs);
auto &fnInfo = cgm.getTypes().arrangeCXXMethodCall(
args, fpt, callInfo.reqArgs, callInfo.prefixSize);
assert((ce || currSrcLoc) && "expected source location");
mlir::Location loc = ce ? getLoc(ce->getExprLoc()) : *currSrcLoc;
assert(!cir::MissingFeatures::opCallMustTail());
return emitCall(fnInfo, callee, returnValue, args, nullptr, loc);
}
static void emitNullBaseClassInitialization(CIRGenFunction &cgf,
Address destPtr,
const CXXRecordDecl *base) {
if (base->isEmpty())
return;
const ASTRecordLayout &layout = cgf.getContext().getASTRecordLayout(base);
CharUnits nvSize = layout.getNonVirtualSize();
// We cannot simply zero-initialize the entire base sub-object if vbptrs are
// present, they are initialized by the most derived class before calling the
// constructor.
SmallVector<std::pair<CharUnits, CharUnits>, 1> stores;
stores.emplace_back(CharUnits::Zero(), nvSize);
// Each store is split by the existence of a vbptr.
// TODO(cir): This only needs handling for the MS CXXABI.
assert(!cir::MissingFeatures::msabi());
// If the type contains a pointer to data member we can't memset it to zero.
// Instead, create a null constant and copy it to the destination.
// TODO: there are other patterns besides zero that we can usefully memset,
// like -1, which happens to be the pattern used by member-pointers.
// TODO: isZeroInitializable can be over-conservative in the case where a
// virtual base contains a member pointer.
mlir::TypedAttr nullConstantForBase = cgf.cgm.emitNullConstantForBase(base);
if (!cgf.getBuilder().isNullValue(nullConstantForBase)) {
cgf.cgm.errorNYI(
base->getSourceRange(),
"emitNullBaseClassInitialization: base constant is not null");
} else {
// Otherwise, just memset the whole thing to zero. This is legal
// because in LLVM, all default initializers (other than the ones we just
// handled above) are guaranteed to have a bit pattern of all zeros.
// TODO(cir): When the MS CXXABI is supported, we will need to iterate over
// `stores` and create a separate memset for each one. For now, we know that
// there will only be one store and it will begin at offset zero, so that
// simplifies this code considerably.
assert(stores.size() == 1 && "Expected only one store");
assert(stores[0].first == CharUnits::Zero() &&
"Expected store to begin at offset zero");
CIRGenBuilderTy builder = cgf.getBuilder();
mlir::Location loc = cgf.getLoc(base->getBeginLoc());
builder.createStore(loc, builder.getConstant(loc, nullConstantForBase),
destPtr);
}
}
void CIRGenFunction::emitCXXConstructExpr(const CXXConstructExpr *e,
AggValueSlot dest) {
assert(!dest.isIgnored() && "Must have a destination!");
const CXXConstructorDecl *cd = e->getConstructor();
// If we require zero initialization before (or instead of) calling the
// constructor, as can be the case with a non-user-provided default
// constructor, emit the zero initialization now, unless destination is
// already zeroed.
if (e->requiresZeroInitialization() && !dest.isZeroed()) {
switch (e->getConstructionKind()) {
case CXXConstructionKind::Delegating:
case CXXConstructionKind::Complete:
emitNullInitialization(getLoc(e->getSourceRange()), dest.getAddress(),
e->getType());
break;
case CXXConstructionKind::VirtualBase:
case CXXConstructionKind::NonVirtualBase:
emitNullBaseClassInitialization(*this, dest.getAddress(),
cd->getParent());
break;
}
}
// If this is a call to a trivial default constructor, do nothing.
if (cd->isTrivial() && cd->isDefaultConstructor())
return;
// Elide the constructor if we're constructing from a temporary
if (getLangOpts().ElideConstructors && e->isElidable()) {
// FIXME: This only handles the simplest case, where the source object is
// passed directly as the first argument to the constructor. This
// should also handle stepping through implicit casts and conversion
// sequences which involve two steps, with a conversion operator
// follwed by a converting constructor.
const Expr *srcObj = e->getArg(0);
assert(srcObj->isTemporaryObject(getContext(), cd->getParent()));
assert(
getContext().hasSameUnqualifiedType(e->getType(), srcObj->getType()));
emitAggExpr(srcObj, dest);
return;
}
if (const ArrayType *arrayType = getContext().getAsArrayType(e->getType())) {
assert(!cir::MissingFeatures::sanitizers());
emitCXXAggrConstructorCall(cd, arrayType, dest.getAddress(), e, false);
} else {
clang::CXXCtorType type = Ctor_Complete;
bool forVirtualBase = false;
bool delegating = false;
switch (e->getConstructionKind()) {
case CXXConstructionKind::Complete:
type = Ctor_Complete;
break;
case CXXConstructionKind::Delegating:
// We should be emitting a constructor; GlobalDecl will assert this
type = curGD.getCtorType();
delegating = true;
break;
case CXXConstructionKind::VirtualBase:
forVirtualBase = true;
[[fallthrough]];
case CXXConstructionKind::NonVirtualBase:
type = Ctor_Base;
break;
}
emitCXXConstructorCall(cd, type, forVirtualBase, delegating, dest, e);
}
}
static CharUnits calculateCookiePadding(CIRGenFunction &cgf,
const CXXNewExpr *e) {
if (!e->isArray())
return CharUnits::Zero();
// No cookie is required if the operator new[] being used is the
// reserved placement operator new[].
if (e->getOperatorNew()->isReservedGlobalPlacementOperator())
return CharUnits::Zero();
return cgf.cgm.getCXXABI().getArrayCookieSize(e);
}
static mlir::Value emitCXXNewAllocSize(CIRGenFunction &cgf, const CXXNewExpr *e,
unsigned minElements,
mlir::Value &numElements,
mlir::Value &sizeWithoutCookie) {
QualType type = e->getAllocatedType();
mlir::Location loc = cgf.getLoc(e->getSourceRange());
if (!e->isArray()) {
CharUnits typeSize = cgf.getContext().getTypeSizeInChars(type);
sizeWithoutCookie = cgf.getBuilder().getConstant(
loc, cir::IntAttr::get(cgf.sizeTy, typeSize.getQuantity()));
return sizeWithoutCookie;
}
// The width of size_t.
unsigned sizeWidth = cgf.cgm.getDataLayout().getTypeSizeInBits(cgf.sizeTy);
// The number of elements can be have an arbitrary integer type;
// essentially, we need to multiply it by a constant factor, add a
// cookie size, and verify that the result is representable as a
// size_t. That's just a gloss, though, and it's wrong in one
// important way: if the count is negative, it's an error even if
// the cookie size would bring the total size >= 0.
//
// If the array size is constant, Sema will have prevented negative
// values and size overflow.
// Compute the constant factor.
llvm::APInt arraySizeMultiplier(sizeWidth, 1);
while (const ConstantArrayType *cat =
cgf.getContext().getAsConstantArrayType(type)) {
type = cat->getElementType();
arraySizeMultiplier *= cat->getSize();
}
CharUnits typeSize = cgf.getContext().getTypeSizeInChars(type);
llvm::APInt typeSizeMultiplier(sizeWidth, typeSize.getQuantity());
typeSizeMultiplier *= arraySizeMultiplier;
// Figure out the cookie size.
llvm::APInt cookieSize(sizeWidth,
calculateCookiePadding(cgf, e).getQuantity());
// This will be a size_t.
mlir::Value size;
// Emit the array size expression.
// We multiply the size of all dimensions for NumElements.
// e.g for 'int[2][3]', ElemType is 'int' and NumElements is 6.
const Expr *arraySize = *e->getArraySize();
mlir::Attribute constNumElements =
ConstantEmitter(cgf.cgm, &cgf)
.tryEmitAbstract(arraySize, arraySize->getType());
if (constNumElements) {
// Get an APInt from the constant
const llvm::APInt &count =
mlir::cast<cir::IntAttr>(constNumElements).getValue();
[[maybe_unused]] unsigned numElementsWidth = count.getBitWidth();
bool hasAnyOverflow = false;
// The equivalent code in CodeGen/CGExprCXX.cpp handles these cases as
// overflow, but that should never happen. The size argument is implicitly
// cast to a size_t, so it can never be negative and numElementsWidth will
// always equal sizeWidth.
assert(!count.isNegative() && "Expected non-negative array size");
assert(numElementsWidth == sizeWidth &&
"Expected a size_t array size constant");
// Okay, compute a count at the right width.
llvm::APInt adjustedCount = count.zextOrTrunc(sizeWidth);
// Scale numElements by that. This might overflow, but we don't
// care because it only overflows if allocationSize does too, and
// if that overflows then we shouldn't use this.
// This emits a constant that may not be used, but we can't tell here
// whether it will be needed or not.
numElements =
cgf.getBuilder().getConstInt(loc, adjustedCount * arraySizeMultiplier);
// Compute the size before cookie, and track whether it overflowed.
bool overflow;
llvm::APInt allocationSize =
adjustedCount.umul_ov(typeSizeMultiplier, overflow);
// Sema prevents us from hitting this case
assert(!overflow && "Overflow in array allocation size");
// Add in the cookie, and check whether it's overflowed.
if (cookieSize != 0) {
// Save the current size without a cookie. This shouldn't be
// used if there was overflow
sizeWithoutCookie = cgf.getBuilder().getConstInt(
loc, allocationSize.zextOrTrunc(sizeWidth));
allocationSize = allocationSize.uadd_ov(cookieSize, overflow);
hasAnyOverflow |= overflow;
}
// On overflow, produce a -1 so operator new will fail
if (hasAnyOverflow) {
size =
cgf.getBuilder().getConstInt(loc, llvm::APInt::getAllOnes(sizeWidth));
} else {
size = cgf.getBuilder().getConstInt(loc, allocationSize);
}
} else {
// Create a value for the variable number of elements
numElements = cgf.emitScalarExpr(*e->getArraySize());
auto numElementsType = mlir::cast<cir::IntType>(numElements.getType());
unsigned numElementsWidth = numElementsType.getWidth();
// The number of elements can have an arbitrary integer type;
// essentially, we need to multiply it by a constant factor, add a
// cookie size, and verify that the result is representable as a
// size_t. That's just a gloss, though, and it's wrong in one
// important way: if the count is negative, it's an error even if
// the cookie size would bring the total size >= 0.
bool isSigned =
(*e->getArraySize())->getType()->isSignedIntegerOrEnumerationType();
// There are up to five conditions we need to test for:
// 1) if isSigned, we need to check whether numElements is negative;
// 2) if numElementsWidth > sizeWidth, we need to check whether
// numElements is larger than something representable in size_t;
// 3) if minElements > 0, we need to check whether numElements is smaller
// than that.
// 4) we need to compute
// sizeWithoutCookie := numElements * typeSizeMultiplier
// and check whether it overflows; and
// 5) if we need a cookie, we need to compute
// size := sizeWithoutCookie + cookieSize
// and check whether it overflows.
mlir::Value hasOverflow;
// If numElementsWidth > sizeWidth, then one way or another, we're
// going to have to do a comparison for (2), and this happens to
// take care of (1), too.
if (numElementsWidth > sizeWidth) {
llvm::APInt threshold =
llvm::APInt::getOneBitSet(numElementsWidth, sizeWidth);
// Use an unsigned comparison regardless of the sign of numElements.
mlir::Value unsignedNumElements = numElements;
if (isSigned)
unsignedNumElements = cgf.getBuilder().createIntCast(
numElements, cgf.getBuilder().getUIntNTy(numElementsWidth));
mlir::Value thresholdV =
cgf.getBuilder().getConstInt(loc, threshold, /*isUnsigned=*/true);
hasOverflow = cgf.getBuilder().createCompare(
loc, cir::CmpOpKind::ge, unsignedNumElements, thresholdV);
numElements = cgf.getBuilder().createIntCast(
unsignedNumElements, mlir::cast<cir::IntType>(cgf.sizeTy));
// Otherwise, if we're signed, we want to sext up to size_t.
} else if (isSigned) {
if (numElementsWidth < sizeWidth)
numElements = cgf.getBuilder().createIntCast(
numElements, cgf.getBuilder().getSIntNTy(sizeWidth));
// If there's a non-1 type size multiplier, then we can do the
// signedness check at the same time as we do the multiply
// because a negative number times anything will cause an
// unsigned overflow. Otherwise, we have to do it here. But at
// least in this case, we can subsume the >= minElements check.
if (typeSizeMultiplier == 1)
hasOverflow = cgf.getBuilder().createCompare(
loc, cir::CmpOpKind::lt, numElements,
cgf.getBuilder().getConstInt(loc, numElements.getType(),
minElements));
numElements = cgf.getBuilder().createIntCast(
numElements, mlir::cast<cir::IntType>(cgf.sizeTy));
// Otherwise, zext up to size_t if necessary.
} else if (numElementsWidth < sizeWidth) {
numElements = cgf.getBuilder().createIntCast(
numElements, mlir::cast<cir::IntType>(cgf.sizeTy));
}
assert(numElements.getType() == cgf.sizeTy);
if (minElements) {
// Don't allow allocation of fewer elements than we have initializers.
if (!hasOverflow) {
mlir::Value minElementsV = cgf.getBuilder().getConstInt(
loc, llvm::APInt(sizeWidth, minElements));
hasOverflow = cgf.getBuilder().createCompare(loc, cir::CmpOpKind::lt,
numElements, minElementsV);
} else if (numElementsWidth > sizeWidth) {
// The other existing overflow subsumes this check.
// We do an unsigned comparison, since any signed value < -1 is
// taken care of either above or below.
mlir::Value minElementsV = cgf.getBuilder().getConstInt(
loc, llvm::APInt(sizeWidth, minElements));
hasOverflow = cgf.getBuilder().createOr(
loc, hasOverflow,
cgf.getBuilder().createCompare(loc, cir::CmpOpKind::lt, numElements,
minElementsV));
}
}
size = numElements;
// Multiply by the type size if necessary. This multiplier
// includes all the factors for nested arrays.
//
// This step also causes numElements to be scaled up by the
// nested-array factor if necessary. Overflow on this computation
// can be ignored because the result shouldn't be used if
// allocation fails.
if (typeSizeMultiplier != 1) {
mlir::Value tsmV = cgf.getBuilder().getConstInt(loc, typeSizeMultiplier);
auto mulOp = cir::MulOverflowOp::create(
cgf.getBuilder(), loc, mlir::cast<cir::IntType>(cgf.sizeTy), size,
tsmV);
if (hasOverflow)
hasOverflow =
cgf.getBuilder().createOr(loc, hasOverflow, mulOp.getOverflow());
else
hasOverflow = mulOp.getOverflow();
size = mulOp.getResult();
// Also scale up numElements by the array size multiplier.
if (arraySizeMultiplier != 1) {
// If the base element type size is 1, then we can re-use the
// multiply we just did.
if (typeSize.isOne()) {
assert(arraySizeMultiplier == typeSizeMultiplier);
numElements = size;
// Otherwise we need a separate multiply.
} else {
mlir::Value asmV =
cgf.getBuilder().getConstInt(loc, arraySizeMultiplier);
numElements = cgf.getBuilder().createMul(loc, numElements, asmV);
}
}
} else {
// numElements doesn't need to be scaled.
assert(arraySizeMultiplier == 1);
}
// Add in the cookie size if necessary.
if (cookieSize != 0) {
sizeWithoutCookie = size;
mlir::Value cookieSizeV = cgf.getBuilder().getConstInt(loc, cookieSize);
auto addOp = cir::AddOverflowOp::create(
cgf.getBuilder(), loc, mlir::cast<cir::IntType>(cgf.sizeTy), size,
cookieSizeV);
if (hasOverflow)
hasOverflow =
cgf.getBuilder().createOr(loc, hasOverflow, addOp.getOverflow());
else
hasOverflow = addOp.getOverflow();
size = addOp.getResult();
}
// If we had any possibility of dynamic overflow, make a select to
// overwrite 'size' with an all-ones value, which should cause
// operator new to throw.
if (hasOverflow) {
mlir::Value allOnes =
cgf.getBuilder().getConstInt(loc, llvm::APInt::getAllOnes(sizeWidth));
size = cgf.getBuilder().createSelect(loc, hasOverflow, allOnes, size);
}
}
if (cookieSize == 0)
sizeWithoutCookie = size;
else
assert(sizeWithoutCookie && "didn't set sizeWithoutCookie?");
return size;
}
/// Emit a call to an operator new or operator delete function, as implicitly
/// created by new-expressions and delete-expressions.
static RValue emitNewDeleteCall(CIRGenFunction &cgf,
const FunctionDecl *calleeDecl,
const FunctionProtoType *calleeType,
const CallArgList &args) {
cir::CIRCallOpInterface callOrTryCall;
cir::FuncOp calleePtr = cgf.cgm.getAddrOfFunction(calleeDecl);
CIRGenCallee callee =
CIRGenCallee::forDirect(calleePtr, GlobalDecl(calleeDecl));
RValue rv =
cgf.emitCall(cgf.cgm.getTypes().arrangeFreeFunctionCall(args, calleeType),
callee, ReturnValueSlot(), args, &callOrTryCall);
/// C++1y [expr.new]p10:
/// [In a new-expression,] an implementation is allowed to omit a call
/// to a replaceable global allocation function.
///
/// We model such elidable calls with the 'builtin' attribute.
if (calleeDecl->isReplaceableGlobalAllocationFunction() && calleePtr &&
calleePtr->hasAttr(cir::CIRDialect::getNoBuiltinAttrName())) {
callOrTryCall->setAttr(cir::CIRDialect::getBuiltinAttrName(),
mlir::UnitAttr::get(callOrTryCall->getContext()));
}
return rv;
}
RValue CIRGenFunction::emitNewOrDeleteBuiltinCall(const FunctionProtoType *type,
const CallExpr *callExpr,
OverloadedOperatorKind op) {
CallArgList args;
emitCallArgs(args, type, callExpr->arguments());
// Find the allocation or deallocation function that we're calling.
ASTContext &astContext = getContext();
assert(op == OO_New || op == OO_Delete);
DeclarationName name = astContext.DeclarationNames.getCXXOperatorName(op);
clang::DeclContextLookupResult lookupResult =
astContext.getTranslationUnitDecl()->lookup(name);
for (const NamedDecl *decl : lookupResult) {
if (const auto *funcDecl = dyn_cast<FunctionDecl>(decl)) {
if (astContext.hasSameType(funcDecl->getType().getTypePtr(), type)) {
if (sanOpts.has(SanitizerKind::AllocToken)) {
// TODO: Set !alloc_token metadata.
assert(!cir::MissingFeatures::allocToken());
cgm.errorNYI("Alloc token sanitizer not yet supported!");
}
// Emit the call to operator new/delete.
return emitNewDeleteCall(*this, funcDecl, type, args);
}
}
}
llvm_unreachable("predeclared global operator new/delete is missing");
}
namespace {
template <typename Traits> struct PlacementArg {
typename Traits::RValueTy argValue;
QualType argType;
};
/// A cleanup to call the given 'operator delete' function upon abnormal
/// exit from a new expression. Templated on a traits type that deals with
/// ensuring that the arguments dominate the cleanup if necessary.
template <typename Traits>
class CallDeleteDuringNew final
: public EHScopeStack::Cleanup,
private llvm::TrailingObjects<CallDeleteDuringNew<Traits>,
PlacementArg<Traits>> {
using TrailingObj =
llvm::TrailingObjects<CallDeleteDuringNew<Traits>, PlacementArg<Traits>>;
friend TrailingObj;
using TrailingObj::getTrailingObjects;
/// Type used to hold llvm::Value*s.
typedef typename Traits::ValueTy ValueTy;
/// Type used to hold RValues.
typedef typename Traits::RValueTy RValueTy;
unsigned numPlacementArgs : 30;
LLVM_PREFERRED_TYPE(AlignedAllocationMode)
unsigned passAlignmentToPlacementDelete : 1;
const FunctionDecl *operatorDelete;
ValueTy ptr;
ValueTy allocSize;
CharUnits allocAlign;
PlacementArg<Traits> *getPlacementArgs() { return getTrailingObjects(); }
void setPlacementArg(unsigned i, RValueTy argValue, QualType argType) {
assert(i < numPlacementArgs && "index out of range");
getPlacementArgs()[i] = {argValue, argType};
}
public:
static size_t getExtraSize(size_t numPlacementArgs) {
return TrailingObj::template additionalSizeToAlloc<PlacementArg<Traits>>(
numPlacementArgs);
}
CallDeleteDuringNew(size_t numPlacementArgs,
const FunctionDecl *operatorDelete, ValueTy ptr,
ValueTy allocSize,
const ImplicitAllocationParameters &iap,
CharUnits allocAlign, const CallArgList *newArgs,
unsigned numNonPlacementArgs, CIRGenFunction *cgf,
mlir::Location loc)
: numPlacementArgs(numPlacementArgs),
passAlignmentToPlacementDelete(isAlignedAllocation(iap.PassAlignment)),
operatorDelete(operatorDelete), ptr(ptr), allocSize(allocSize),
allocAlign(allocAlign) {
for (unsigned i = 0, n = numPlacementArgs; i != n; ++i) {
const CallArg &arg = (*newArgs)[i + numNonPlacementArgs];
setPlacementArg(i, arg.getRValue(*cgf, loc), arg.ty);
}
}
void emit(CIRGenFunction &cgf, Flags flags) override {
const auto *fpt = operatorDelete->getType()->castAs<FunctionProtoType>();
CallArgList deleteArgs;
unsigned firstNonTypeArg = 0;
TypeAwareAllocationMode typeAwareDeallocation = TypeAwareAllocationMode::No;
assert(!cir::MissingFeatures::typeAwareAllocation());
// The first argument after type-identity parameter (if any) is always
// a void* (or C* for a destroying operator delete for class type C).
deleteArgs.add(Traits::get(cgf, ptr), fpt->getParamType(firstNonTypeArg));
// Figure out what other parameters we should be implicitly passing.
UsualDeleteParams params;
if (numPlacementArgs) {
// A placement deallocation function is implicitly passed an alignment
// if the placement allocation function was, but is never passed a size.
params.Alignment =
alignedAllocationModeFromBool(passAlignmentToPlacementDelete);
params.TypeAwareDelete = typeAwareDeallocation;
params.Size = isTypeAwareAllocation(params.TypeAwareDelete);
} else {
// For a non-placement new-expression, 'operator delete' can take a
// size and/or an alignment if it has the right parameters.
params = operatorDelete->getUsualDeleteParams();
}
assert(!params.DestroyingDelete &&
"should not call destroying delete in a new-expression");
// The second argument can be a std::size_t (for non-placement delete).
if (params.Size)
deleteArgs.add(Traits::get(cgf, allocSize),
cgf.getContext().getSizeType());
// The next (second or third) argument can be a std::align_val_t, which
// is an enum whose underlying type is std::size_t.
// FIXME: Use the right type as the parameter type. Note that in a call
// to operator delete(size_t, ...), we may not have it available.
if (isAlignedAllocation(params.Alignment))
cgf.cgm.errorNYI("CallDeleteDuringNew: aligned allocation");
// Pass the rest of the arguments, which must match exactly.
for (unsigned i = 0; i != numPlacementArgs; ++i) {
auto arg = getPlacementArgs()[i];
deleteArgs.add(Traits::get(cgf, arg.argValue), arg.argType);
}
// Call 'operator delete'.
emitNewDeleteCall(cgf, operatorDelete, fpt, deleteArgs);
}
};
} // namespace
/// Enter a cleanup to call 'operator delete' if the initializer in a
/// new-expression throws.
static void enterNewDeleteCleanup(CIRGenFunction &cgf, const CXXNewExpr *e,
Address newPtr, mlir::Value allocSize,
CharUnits allocAlign,
const CallArgList &newArgs) {
unsigned numNonPlacementArgs = e->getNumImplicitArgs();
// If we're not inside a conditional branch, then the cleanup will
// dominate and we can do the easier (and more efficient) thing.
if (!cgf.isInConditionalBranch()) {
struct DirectCleanupTraits {
typedef mlir::Value ValueTy;
typedef RValue RValueTy;
static RValue get(CIRGenFunction &, ValueTy v) { return RValue::get(v); }
static RValue get(CIRGenFunction &, RValueTy v) { return v; }
};
typedef CallDeleteDuringNew<DirectCleanupTraits> DirectCleanup;
assert(!cir::MissingFeatures::typeAwareAllocation());
cgf.ehStack.pushCleanupWithExtra<DirectCleanup>(
EHCleanup, e->getNumPlacementArgs(), e->getOperatorDelete(),
newPtr.getPointer(), allocSize, e->implicitAllocationParameters(),
allocAlign, &newArgs, numNonPlacementArgs, &cgf,
cgf.getLoc(e->getSourceRange()));
return;
}
cgf.cgm.errorNYI(e->getSourceRange(),
"enterNewDeleteCleanup: conditional branch");
}
static void storeAnyExprIntoOneUnit(CIRGenFunction &cgf, const Expr *init,
QualType allocType, Address newPtr,
AggValueSlot::Overlap_t mayOverlap) {
// FIXME: Refactor with emitExprAsInit.
switch (cgf.getEvaluationKind(allocType)) {
case cir::TEK_Scalar:
cgf.emitScalarInit(init, cgf.getLoc(init->getSourceRange()),
cgf.makeAddrLValue(newPtr, allocType), false);
return;
case cir::TEK_Complex:
cgf.emitComplexExprIntoLValue(init, cgf.makeAddrLValue(newPtr, allocType),
/*isInit*/ true);
return;
case cir::TEK_Aggregate: {
assert(!cir::MissingFeatures::aggValueSlotGC());
assert(!cir::MissingFeatures::sanitizers());
AggValueSlot slot = AggValueSlot::forAddr(
newPtr, allocType.getQualifiers(), AggValueSlot::IsDestructed,
AggValueSlot::IsNotAliased, mayOverlap, AggValueSlot::IsNotZeroed);
cgf.emitAggExpr(init, slot);
return;
}
}
llvm_unreachable("bad evaluation kind");
}
void CIRGenFunction::emitNewArrayInitializer(
const CXXNewExpr *e, QualType elementType, mlir::Type elementTy,
Address beginPtr, mlir::Value numElements,
mlir::Value allocSizeWithoutCookie) {
// If we have a type with trivial initialization and no initializer,
// there's nothing to do.
if (!e->hasInitializer())
return;
Address curPtr = beginPtr;
unsigned initListElements = 0;
const Expr *init = e->getInitializer();
Address endOfInit = Address::invalid();
QualType::DestructionKind dtorKind = elementType.isDestructedType();
assert(!cir::MissingFeatures::cleanupDeactivationScope());
// Attempt to perform zero-initialization using memset.
auto tryMemsetInitialization = [&]() -> bool {
mlir::Location loc = numElements.getLoc();
// FIXME: If the type is a pointer-to-data-member under the Itanium ABI,
// we can initialize with a memset to -1.
if (!cgm.getTypes().isZeroInitializable(elementType))
return false;
// Optimization: since zero initialization will just set the memory
// to all zeroes, generate a single memset to do it in one shot.
// Subtract out the size of any elements we've already initialized.
auto remainingSize = allocSizeWithoutCookie;
if (initListElements) {
// We know this can't overflow; we check this when doing the allocation.
unsigned initializedSize =
getContext().getTypeSizeInChars(elementType).getQuantity() *
initListElements;
cir::ConstantOp initSizeOp =
builder.getConstInt(loc, remainingSize.getType(), initializedSize);
remainingSize = builder.createSub(loc, remainingSize, initSizeOp);
}
// Create the memset.
mlir::Value castOp =
builder.createPtrBitcast(curPtr.getPointer(), cgm.voidTy);
builder.createMemSet(loc, castOp, builder.getConstInt(loc, cgm.uInt8Ty, 0),
remainingSize);
return true;
};
const InitListExpr *ile = dyn_cast<InitListExpr>(init);
const CXXParenListInitExpr *cplie = nullptr;
const StringLiteral *sl = nullptr;
const ObjCEncodeExpr *ocee = nullptr;
const Expr *ignoreParen = nullptr;
if (!ile) {
ignoreParen = init->IgnoreParenImpCasts();
cplie = dyn_cast<CXXParenListInitExpr>(ignoreParen);
sl = dyn_cast<StringLiteral>(ignoreParen);
ocee = dyn_cast<ObjCEncodeExpr>(ignoreParen);
}
// If the initializer is an initializer list, first do the explicit elements.
if (ile || cplie || sl || ocee) {
// Initializing from a (braced) string literal is a special case; the init
// list element does not initialize a (single) array element.
if ((ile && ile->isStringLiteralInit()) || sl || ocee) {
cgm.errorNYI(ile->getSourceRange(),
"emitNewArrayInitializer: string literal init");
return;
}
ArrayRef<const Expr *> initExprs =
ile ? ile->inits() : cplie->getInitExprs();
initListElements = initExprs.size();
// If this is a multi-dimensional array new, we will initialize multiple
// elements with each init list element.
QualType allocType = e->getAllocatedType();
if (const ConstantArrayType *cat = dyn_cast_or_null<ConstantArrayType>(
allocType->getAsArrayTypeUnsafe())) {
(void)cat;
cgm.errorNYI(ile->getSourceRange(),
"emitNewArrayInitializer: constant array init");
return;
}
// Enter a partial-destruction Cleanup if necessary.
if (dtorKind) {
cgm.errorNYI(ile->getSourceRange(),
"emitNewArrayInitializer: init requires dtor");
return;
}
CharUnits elementSize = getContext().getTypeSizeInChars(elementType);
CharUnits startAlign = curPtr.getAlignment();
unsigned i = 0;
for (const Expr *ie : initExprs) {
// Tell the cleanup that it needs to destroy up to this
// element. TODO: some of these stores can be trivially
// observed to be unnecessary.
if (endOfInit.isValid()) {
cgm.errorNYI(ie->getSourceRange(),
"emitNewArrayInitializer: update dtor cleanup ptr");
return;
}
// FIXME: If the last initializer is an incomplete initializer list for
// an array, and we have an array filler, we can fold together the two
// initialization loops.
storeAnyExprIntoOneUnit(*this, ie, ie->getType(), curPtr,
AggValueSlot::DoesNotOverlap);
mlir::Location loc = getLoc(ie->getExprLoc());
mlir::Value castOp = builder.createPtrBitcast(
curPtr.getPointer(), convertTypeForMem(allocType));
mlir::Value offsetOp = builder.getSignedInt(loc, 1, /*width=*/32);
mlir::Value dataPtr = builder.createPtrStride(loc, castOp, offsetOp);
curPtr = Address(dataPtr, curPtr.getElementType(),
startAlign.alignmentAtOffset((++i) * elementSize));
}
// The remaining elements are filled with the array filler expression.
init = ile ? ile->getArrayFiller() : cplie->getArrayFiller();
// Extract the initializer for the individual array elements by pulling
// out the array filler from all the nested initializer lists. This avoids
// generating a nested loop for the initialization.
while (init && init->getType()->isConstantArrayType()) {
auto *subIle = dyn_cast<InitListExpr>(init);
if (!subIle)
break;
assert(subIle->getNumInits() == 0 && "explicit inits in array filler?");
init = subIle->getArrayFiller();
}
// Switch back to initializing one base element at a time.
curPtr = curPtr.withElementType(builder, beginPtr.getElementType());
}
// If all elements have already been initialized, skip any further
// initialization.
auto constOp = mlir::dyn_cast<cir::ConstantOp>(numElements.getDefiningOp());
if (constOp) {
auto constIntAttr = mlir::dyn_cast<cir::IntAttr>(constOp.getValue());
// Just skip out if the constant count is zero.
if (constIntAttr && constIntAttr.getUInt() <= initListElements)
return;
}
assert(init && "have trailing elements to initialize but no initializer");
// If this is a constructor call, try to optimize it out, and failing that
// emit a single loop to initialize all remaining elements.
if (const CXXConstructExpr *cce = dyn_cast<CXXConstructExpr>(init)) {
CXXConstructorDecl *ctor = cce->getConstructor();
if (ctor->isTrivial()) {
// If new expression did not specify value-initialization, then there
// is no initialization.
if (!cce->requiresZeroInitialization())
return;
cgm.errorNYI(cce->getSourceRange(),
"emitNewArrayInitializer: trivial ctor zero-init");
return;
}
// Store the new Cleanup position for irregular Cleanups.
//
// FIXME: Share this cleanup with the constructor call emission rather than
// having it create a cleanup of its own.
if (endOfInit.isValid())
builder.createStore(getLoc(e->getSourceRange()), curPtr.emitRawPointer(),
endOfInit);
mlir::Type initType = convertType(cce->getType());
// Emit a constructor call loop to initialize the remaining elements.
if (initListElements) {
// If the number of elements is a constant, we will have already gotten
// the constant op above. Here we use it to get the number of remaining
// elements as a new constant.
if (constOp) {
auto constIntAttr = mlir::cast<cir::IntAttr>(constOp.getValue());
uint64_t numRemainingElements =
constIntAttr.getUInt() - initListElements;
numElements =
builder.getConstInt(getLoc(e->getSourceRange()),
numElements.getType(), numRemainingElements);
// Currently, the AST gives us a pointer to the element type here
// rather than an array. That's inconsistent with what it does
// without an explicit initializer list, so we need to create an
// array type here. That will decay back to a pointer when we lower
// the cir.array.ctor op, but we need an array type for the initial
// representation.
if (!mlir::isa<cir::ArrayType>(initType))
initType = cir::ArrayType::get(initType, numRemainingElements);
} else {
cgm.errorNYI(e->getSourceRange(),
"emitNewArrayInitializer: numRemainingElements with "
"non-constant count");
return;
}
}
curPtr = curPtr.withElementType(builder, initType);
emitCXXAggrConstructorCall(ctor, numElements, curPtr, cce,
/*newPointerIsChecked=*/true,
cce->requiresZeroInitialization());
if (getContext().getTargetInfo().emitVectorDeletingDtors(
getContext().getLangOpts())) {
cgm.errorNYI(e->getSourceRange(),
"emitNewArrayInitializer: emitVectorDeletingDtors");
}
return;
}
// If this is value-initialization, we can usually use memset.
if (isa<ImplicitValueInitExpr>(init)) {
if (tryMemsetInitialization())
return;
cgm.errorNYI(init->getSourceRange(),
"emitNewArrayInitializer: implicit value init");
return;
}
cgm.errorNYI(init->getSourceRange(),
"emitNewArrayInitializer: unsupported initializer");
return;
}
static void emitNewInitializer(CIRGenFunction &cgf, const CXXNewExpr *e,
QualType elementType, mlir::Type elementTy,
Address newPtr, mlir::Value numElements,
mlir::Value allocSizeWithoutCookie) {
assert(!cir::MissingFeatures::generateDebugInfo());
if (e->isArray()) {
cgf.emitNewArrayInitializer(e, elementType, elementTy, newPtr, numElements,
allocSizeWithoutCookie);
} else if (const Expr *init = e->getInitializer()) {
storeAnyExprIntoOneUnit(cgf, init, e->getAllocatedType(), newPtr,
AggValueSlot::DoesNotOverlap);
}
}
RValue CIRGenFunction::emitCXXDestructorCall(
GlobalDecl dtor, const CIRGenCallee &callee, mlir::Value thisVal,
QualType thisTy, mlir::Value implicitParam, QualType implicitParamTy,
const CallExpr *ce) {
const CXXMethodDecl *dtorDecl = cast<CXXMethodDecl>(dtor.getDecl());
assert(!thisTy.isNull());
assert(thisTy->getAsCXXRecordDecl() == dtorDecl->getParent() &&
"Pointer/Object mixup");
assert(!cir::MissingFeatures::addressSpace());
CallArgList args;
commonBuildCXXMemberOrOperatorCall(*this, dtorDecl, thisVal, implicitParam,
implicitParamTy, ce, args, nullptr);
assert((ce || dtor.getDecl()) && "expected source location provider");
assert(!cir::MissingFeatures::opCallMustTail());
return emitCall(cgm.getTypes().arrangeCXXStructorDeclaration(dtor), callee,
ReturnValueSlot(), args, nullptr,
ce ? getLoc(ce->getExprLoc())
: getLoc(dtor.getDecl()->getSourceRange()));
}
RValue CIRGenFunction::emitCXXPseudoDestructorExpr(
const CXXPseudoDestructorExpr *expr) {
QualType destroyedType = expr->getDestroyedType();
if (destroyedType.hasStrongOrWeakObjCLifetime()) {
assert(!cir::MissingFeatures::objCLifetime());
cgm.errorNYI(expr->getExprLoc(),
"emitCXXPseudoDestructorExpr: Objective-C lifetime is NYI");
} else {
// C++ [expr.pseudo]p1:
// The result shall only be used as the operand for the function call
// operator (), and the result of such a call has type void. The only
// effect is the evaluation of the postfix-expression before the dot or
// arrow.
emitIgnoredExpr(expr->getBase());
}
return RValue::get(nullptr);
}
namespace {
/// Calls the given 'operator delete' on a single object.
struct CallObjectDelete final : EHScopeStack::Cleanup {
mlir::Value ptr;
const FunctionDecl *operatorDelete;
QualType elementType;
CallObjectDelete(mlir::Value ptr, const FunctionDecl *operatorDelete,
QualType elementType)
: ptr(ptr), operatorDelete(operatorDelete), elementType(elementType) {}
void emit(CIRGenFunction &cgf, Flags flags) override {
cgf.emitDeleteCall(operatorDelete, ptr, elementType);
}
};
} // namespace
/// Emit the code for deleting a single object.
static void emitObjectDelete(CIRGenFunction &cgf, const CXXDeleteExpr *de,
Address ptr, QualType elementType) {
// C++11 [expr.delete]p3:
// If the static type of the object to be deleted is different from its
// dynamic type, the static type shall be a base class of the dynamic type
// of the object to be deleted and the static type shall have a virtual
// destructor or the behavior is undefined.
assert(!cir::MissingFeatures::emitTypeCheck());
const FunctionDecl *operatorDelete = de->getOperatorDelete();
assert(!operatorDelete->isDestroyingOperatorDelete());
// Find the destructor for the type, if applicable. If the
// destructor is virtual, we'll just emit the vcall and return.
const CXXDestructorDecl *dtor = nullptr;
if (const auto *rd = elementType->getAsCXXRecordDecl()) {
if (rd->hasDefinition() && !rd->hasTrivialDestructor()) {
dtor = rd->getDestructor();
if (dtor->isVirtual()) {
assert(!cir::MissingFeatures::devirtualizeDestructor());
cgf.cgm.getCXXABI().emitVirtualObjectDelete(cgf, de, ptr, elementType,
dtor);
return;
}
}
}
// Make sure that we call delete even if the dtor throws.
// This doesn't have to a conditional cleanup because we're going
// to pop it off in a second.
cgf.ehStack.pushCleanup<CallObjectDelete>(
NormalAndEHCleanup, ptr.getPointer(), operatorDelete, elementType);
if (dtor) {
cgf.emitCXXDestructorCall(dtor, Dtor_Complete,
/*ForVirtualBase=*/false,
/*Delegating=*/false, ptr, elementType);
} else if (elementType.getObjCLifetime()) {
assert(!cir::MissingFeatures::objCLifetime());
cgf.cgm.errorNYI(de->getSourceRange(), "emitObjectDelete: ObjCLifetime");
}
cgf.popCleanupBlock();
}
void CIRGenFunction::emitCXXDeleteExpr(const CXXDeleteExpr *e) {
const Expr *arg = e->getArgument();
Address ptr = emitPointerWithAlignment(arg);
// Null check the pointer.
//
// We could avoid this null check if we can determine that the object
// destruction is trivial and doesn't require an array cookie; we can
// unconditionally perform the operator delete call in that case. For now, we
// assume that deleted pointers are null rarely enough that it's better to
// keep the branch. This might be worth revisiting for a -O0 code size win.
assert(!cir::MissingFeatures::emitNullCheckForDeleteCalls());
cir::YieldOp thenYield;
mlir::Value notNull = builder.createPtrIsNotNull(ptr.getPointer());
cir::IfOp::create(builder, getLoc(e->getExprLoc()), notNull,
/*withElseRegion=*/false,
/*thenBuilder=*/
[&](mlir::OpBuilder &b, mlir::Location loc) {
thenYield = builder.createYield(loc);
});
// Emit the rest of the CIR inside the if-op's then region, but restore the
// insertion point to the point after the if when this function returns.
mlir::OpBuilder::InsertionGuard guard(builder);
builder.setInsertionPoint(thenYield);
QualType deleteTy = e->getDestroyedType();
// A destroying operator delete overrides the entire operation of the
// delete expression.
if (e->getOperatorDelete()->isDestroyingOperatorDelete()) {
cgm.errorNYI(e->getSourceRange(),
"emitCXXDeleteExpr: destroying operator delete");
return;
}
// We might be deleting a pointer to array.
deleteTy = getContext().getBaseElementType(deleteTy);
ptr = ptr.withElementType(builder, convertTypeForMem(deleteTy));
if (e->isArrayForm() &&
cgm.getASTContext().getTargetInfo().emitVectorDeletingDtors(
cgm.getASTContext().getLangOpts())) {
cgm.errorNYI(e->getSourceRange(),
"emitCXXDeleteExpr: emitVectorDeletingDtors");
}
if (e->isArrayForm()) {
const FunctionDecl *operatorDelete = e->getOperatorDelete();
cir::FuncOp operatorDeleteFn = cgm.getAddrOfFunction(operatorDelete);
auto deleteFn =
mlir::FlatSymbolRefAttr::get(operatorDeleteFn.getSymNameAttr());
UsualDeleteParams udp = operatorDelete->getUsualDeleteParams();
auto deleteParams = cir::UsualDeleteParamsAttr::get(
builder.getContext(), udp.Size, isAlignedAllocation(udp.Alignment),
isTypeAwareAllocation(udp.TypeAwareDelete), udp.DestroyingDelete);
mlir::FlatSymbolRefAttr elementDtor;
if (const auto *rd = deleteTy->getAsCXXRecordDecl()) {
if (rd->hasDefinition() && !rd->hasTrivialDestructor()) {
const CXXDestructorDecl *dtor = rd->getDestructor();
if (dtor->getType()->castAs<FunctionProtoType>()->canThrow())
cgm.errorNYI(e->getSourceRange(),
"emitCXXDeleteExpr: throwing destructor");
cir::FuncOp dtorFn =
cgm.getAddrOfCXXStructor(GlobalDecl(dtor, Dtor_Complete));
elementDtor = mlir::FlatSymbolRefAttr::get(builder.getContext(),
dtorFn.getSymNameAttr());
}
}
cir::DeleteArrayOp::create(builder, ptr.getPointer().getLoc(),
ptr.getPointer(), deleteFn, deleteParams,
elementDtor);
} else {
emitObjectDelete(*this, e, ptr, deleteTy);
}
}
mlir::Value CIRGenFunction::emitCXXNewExpr(const CXXNewExpr *e) {
// The element type being allocated.
QualType allocType = getContext().getBaseElementType(e->getAllocatedType());
// 1. Build a call to the allocation function.
FunctionDecl *allocator = e->getOperatorNew();
// If there is a brace-initializer, cannot allocate fewer elements than inits.
unsigned minElements = 0;
if (e->isArray() && e->hasInitializer()) {
const InitListExpr *ile = dyn_cast<InitListExpr>(e->getInitializer());
if (ile && ile->isStringLiteralInit())
minElements =
cast<ConstantArrayType>(ile->getType()->getAsArrayTypeUnsafe())
->getSize()
.getZExtValue();
else if (ile)
minElements = ile->getNumInits();
}
mlir::Value numElements = nullptr;
mlir::Value allocSizeWithoutCookie = nullptr;
mlir::Value allocSize = emitCXXNewAllocSize(
*this, e, minElements, numElements, allocSizeWithoutCookie);
CharUnits allocAlign = getContext().getTypeAlignInChars(allocType);
// Emit the allocation call.
Address allocation = Address::invalid();
CallArgList allocatorArgs;
if (allocator->isReservedGlobalPlacementOperator()) {
// If the allocator is a global placement operator, just
// "inline" it directly.
assert(e->getNumPlacementArgs() == 1);
const Expr *arg = *e->placement_arguments().begin();
LValueBaseInfo baseInfo;
allocation = emitPointerWithAlignment(arg, &baseInfo);
// The pointer expression will, in many cases, be an opaque void*.
// In these cases, discard the computed alignment and use the
// formal alignment of the allocated type.
if (baseInfo.getAlignmentSource() != AlignmentSource::Decl)
allocation = allocation.withAlignment(allocAlign);
// Set up allocatorArgs for the call to operator delete if it's not
// the reserved global operator.
if (e->getOperatorDelete() &&
!e->getOperatorDelete()->isReservedGlobalPlacementOperator()) {
cgm.errorNYI(e->getSourceRange(),
"emitCXXNewExpr: reserved placement new with delete");
}
} else {
const FunctionProtoType *allocatorType =
allocator->getType()->castAs<FunctionProtoType>();
unsigned paramsToSkip = 0;
// The allocation size is the first argument.
QualType sizeType = getContext().getSizeType();
allocatorArgs.add(RValue::get(allocSize), sizeType);
++paramsToSkip;
if (allocSize != allocSizeWithoutCookie) {
CharUnits cookieAlign = getSizeAlign(); // FIXME: Ask the ABI.
allocAlign = std::max(allocAlign, cookieAlign);
}
// The allocation alignment may be passed as the second argument.
if (e->passAlignment()) {
cgm.errorNYI(e->getSourceRange(), "emitCXXNewExpr: pass alignment");
}
// FIXME: Why do we not pass a CalleeDecl here?
emitCallArgs(allocatorArgs, allocatorType, e->placement_arguments(),
AbstractCallee(), paramsToSkip);
RValue rv =
emitNewDeleteCall(*this, allocator, allocatorType, allocatorArgs);
// Set !heapallocsite metadata on the call to operator new.
assert(!cir::MissingFeatures::generateDebugInfo());
// If this was a call to a global replaceable allocation function that does
// not take an alignment argument, the allocator is known to produce storage
// that's suitably aligned for any object that fits, up to a known
// threshold. Otherwise assume it's suitably aligned for the allocated type.
CharUnits allocationAlign = allocAlign;
if (!e->passAlignment() &&
allocator->isReplaceableGlobalAllocationFunction()) {
const TargetInfo &target = cgm.getASTContext().getTargetInfo();
unsigned allocatorAlign = llvm::bit_floor(std::min<uint64_t>(
target.getNewAlign(), getContext().getTypeSize(allocType)));
allocationAlign = std::max(
allocationAlign, getContext().toCharUnitsFromBits(allocatorAlign));
}
mlir::Value allocPtr = rv.getValue();
allocation = Address(
allocPtr, mlir::cast<cir::PointerType>(allocPtr.getType()).getPointee(),
allocationAlign);
}
// Emit a null check on the allocation result if the allocation
// function is allowed to return null (because it has a non-throwing
// exception spec or is the reserved placement new) and we have an
// interesting initializer will be running sanitizers on the initialization.
bool nullCheck = e->shouldNullCheckAllocation() &&
(!allocType.isPODType(getContext()) || e->hasInitializer());
assert(!cir::MissingFeatures::exprNewNullCheck());
if (nullCheck)
cgm.errorNYI(e->getSourceRange(), "emitCXXNewExpr: null check");
// If there's an operator delete, enter a cleanup to call it if an
// exception is thrown. If we do this, we'll be creating the result pointer
// inside a cleanup scope, either with a bitcast or an offset based on the
// array cookie size. However, we need to return that pointer from outside
// the cleanup scope, so we need to store it in a temporary variable.
bool useNewDeleteCleanup =
e->getOperatorDelete() &&
!e->getOperatorDelete()->isReservedGlobalPlacementOperator();
EHScopeStack::stable_iterator operatorDeleteCleanup;
mlir::Operation *cleanupDominator = nullptr;
if (useNewDeleteCleanup) {
assert(!cir::MissingFeatures::typeAwareAllocation());
enterNewDeleteCleanup(*this, e, allocation, allocSize, allocAlign,
allocatorArgs);
operatorDeleteCleanup = ehStack.stable_begin();
cleanupDominator =
cir::UnreachableOp::create(builder, getLoc(e->getSourceRange()))
.getOperation();
}
if (allocSize != allocSizeWithoutCookie) {
assert(e->isArray());
allocation = cgm.getCXXABI().initializeArrayCookie(
*this, allocation, numElements, e, allocType);
}
mlir::Type elementTy;
if (e->isArray()) {
// For array new, use the allocated type to handle multidimensional arrays
// correctly
elementTy = convertTypeForMem(e->getAllocatedType());
} else {
elementTy = convertTypeForMem(allocType);
}
Address result = builder.createElementBitCast(getLoc(e->getSourceRange()),
allocation, elementTy);
// If we're inside a new delete cleanup, store the result pointer.
Address resultPtr = Address::invalid();
if (useNewDeleteCleanup) {
resultPtr =
createTempAlloca(builder.getPointerTo(elementTy), result.getAlignment(),
getLoc(e->getSourceRange()), "__new_result");
builder.createStore(getLoc(e->getSourceRange()), result.getPointer(),
resultPtr);
}
// Passing pointer through launder.invariant.group to avoid propagation of
// vptrs information which may be included in previous type.
// To not break LTO with different optimizations levels, we do it regardless
// of optimization level.
if (cgm.getCodeGenOpts().StrictVTablePointers &&
allocator->isReservedGlobalPlacementOperator())
cgm.errorNYI(e->getSourceRange(), "emitCXXNewExpr: strict vtable pointers");
assert(!cir::MissingFeatures::sanitizers());
emitNewInitializer(*this, e, allocType, elementTy, result, numElements,
allocSizeWithoutCookie);
// Deactivate the 'operator delete' cleanup if we finished
// initialization.
if (useNewDeleteCleanup) {
assert(operatorDeleteCleanup.isValid());
assert(resultPtr.isValid());
deactivateCleanupBlock(operatorDeleteCleanup, cleanupDominator);
cleanupDominator->erase();
cir::LoadOp loadResult =
builder.createLoad(getLoc(e->getSourceRange()), resultPtr);
result = result.withPointer(loadResult.getResult());
}
assert(!cir::MissingFeatures::exprNewNullCheck());
return result.getPointer();
}
void CIRGenFunction::emitDeleteCall(const FunctionDecl *deleteFD,
mlir::Value ptr, QualType deleteTy) {
assert(!cir::MissingFeatures::deleteArray());
const auto *deleteFTy = deleteFD->getType()->castAs<FunctionProtoType>();
CallArgList deleteArgs;
UsualDeleteParams params = deleteFD->getUsualDeleteParams();
auto paramTypeIt = deleteFTy->param_type_begin();
// Pass std::type_identity tag if present
if (isTypeAwareAllocation(params.TypeAwareDelete))
cgm.errorNYI(deleteFD->getSourceRange(),
"emitDeleteCall: type aware delete");
// Pass the pointer itself.
QualType argTy = *paramTypeIt++;
mlir::Value deletePtr =
builder.createBitcast(ptr.getLoc(), ptr, convertType(argTy));
deleteArgs.add(RValue::get(deletePtr), argTy);
// Pass the std::destroying_delete tag if present.
if (params.DestroyingDelete)
cgm.errorNYI(deleteFD->getSourceRange(),
"emitDeleteCall: destroying delete");
// Pass the size if the delete function has a size_t parameter.
if (params.Size) {
QualType sizeType = *paramTypeIt++;
CharUnits deleteTypeSize = getContext().getTypeSizeInChars(deleteTy);
assert(mlir::isa<cir::IntType>(convertType(sizeType)) &&
"expected cir::IntType");
cir::ConstantOp size = builder.getConstInt(
*currSrcLoc, convertType(sizeType), deleteTypeSize.getQuantity());
deleteArgs.add(RValue::get(size), sizeType);
}
// Pass the alignment if the delete function has an align_val_t parameter.
if (isAlignedAllocation(params.Alignment))
cgm.errorNYI(deleteFD->getSourceRange(),
"emitDeleteCall: aligned allocation");
assert(paramTypeIt == deleteFTy->param_type_end() &&
"unknown parameter to usual delete function");
// Emit the call to delete.
emitNewDeleteCall(*this, deleteFD, deleteFTy, deleteArgs);
}
static mlir::Value emitDynamicCastToNull(CIRGenFunction &cgf,
mlir::Location loc, QualType destTy) {
mlir::Type destCIRTy = cgf.convertType(destTy);
assert(mlir::isa<cir::PointerType>(destCIRTy) &&
"result of dynamic_cast should be a ptr");
if (!destTy->isPointerType()) {
mlir::Region *currentRegion = cgf.getBuilder().getBlock()->getParent();
/// C++ [expr.dynamic.cast]p9:
/// A failed cast to reference type throws std::bad_cast
cgf.cgm.getCXXABI().emitBadCastCall(cgf, loc);
// The call to bad_cast will terminate the current block. Create a new block
// to hold any follow up code.
cgf.getBuilder().createBlock(currentRegion, currentRegion->end());
}
return cgf.getBuilder().getNullPtr(destCIRTy, loc);
}
mlir::Value CIRGenFunction::emitDynamicCast(Address thisAddr,
const CXXDynamicCastExpr *dce) {
mlir::Location loc = getLoc(dce->getSourceRange());
cgm.emitExplicitCastExprType(dce, this);
QualType destTy = dce->getTypeAsWritten();
QualType srcTy = dce->getSubExpr()->getType();
// C++ [expr.dynamic.cast]p7:
// If T is "pointer to cv void," then the result is a pointer to the most
// derived object pointed to by v.
bool isDynCastToVoid = destTy->isVoidPointerType();
bool isRefCast = destTy->isReferenceType();
QualType srcRecordTy;
QualType destRecordTy;
if (isDynCastToVoid) {
srcRecordTy = srcTy->getPointeeType();
// No destRecordTy.
} else if (const PointerType *destPTy = destTy->getAs<PointerType>()) {
srcRecordTy = srcTy->castAs<PointerType>()->getPointeeType();
destRecordTy = destPTy->getPointeeType();
} else {
srcRecordTy = srcTy;
destRecordTy = destTy->castAs<ReferenceType>()->getPointeeType();
}
assert(srcRecordTy->isRecordType() && "source type must be a record type!");
assert(!cir::MissingFeatures::emitTypeCheck());
if (dce->isAlwaysNull())
return emitDynamicCastToNull(*this, loc, destTy);
auto destCirTy = mlir::cast<cir::PointerType>(convertType(destTy));
return cgm.getCXXABI().emitDynamicCast(*this, loc, srcRecordTy, destRecordTy,
destCirTy, isRefCast, thisAddr);
}
static mlir::Value emitCXXTypeidFromVTable(CIRGenFunction &cgf, const Expr *e,
mlir::Type typeInfoPtrTy,
bool hasNullCheck) {
Address thisPtr = cgf.emitLValue(e).getAddress();
QualType srcType = e->getType();
// C++ [class.cdtor]p4:
// If the operand of typeid refers to the object under construction or
// destruction and the static type of the operand is neither the constructor
// or destructors class nor one of its bases, the behavior is undefined.
assert(!cir::MissingFeatures::sanitizers());
if (hasNullCheck && cgf.cgm.getCXXABI().shouldTypeidBeNullChecked(srcType)) {
mlir::Value isThisNull =
cgf.getBuilder().createPtrIsNull(thisPtr.getPointer());
// We don't really care about the value, we just want to make sure the
// 'true' side calls bad-type-id.
cir::IfOp::create(
cgf.getBuilder(), cgf.getLoc(e->getSourceRange()), isThisNull,
/*withElseRegion=*/false, [&](mlir::OpBuilder &, mlir::Location loc) {
cgf.cgm.getCXXABI().emitBadTypeidCall(cgf, loc);
});
}
return cgf.cgm.getCXXABI().emitTypeid(cgf, srcType, thisPtr, typeInfoPtrTy);
}
mlir::Value CIRGenFunction::emitCXXTypeidExpr(const CXXTypeidExpr *e) {
mlir::Location loc = getLoc(e->getSourceRange());
mlir::Type resultType = cir::PointerType::get(convertType(e->getType()));
QualType ty = e->isTypeOperand() ? e->getTypeOperand(getContext())
: e->getExprOperand()->getType();
// If the non-default global var address space is not default, we need to do
// an address-space cast here.
assert(!cir::MissingFeatures::addressSpace());
// C++ [expr.typeid]p2:
// When typeid is applied to a glvalue expression whose type is a
// polymorphic class type, the result refers to a std::type_info object
// representing the type of the most derived object (that is, the dynamic
// type) to which the glvalue refers.
// If the operand is already most derived object, no need to look up vtable.
if (!e->isTypeOperand() && e->isPotentiallyEvaluated() &&
!e->isMostDerived(getContext()))
return emitCXXTypeidFromVTable(*this, e->getExprOperand(), resultType,
e->hasNullCheck());
auto typeInfo =
cast<cir::GlobalViewAttr>(cgm.getAddrOfRTTIDescriptor(loc, ty));
// `getAddrOfRTTIDescriptor` lies to us and always gives us a uint8ptr as its
// type, however we need the value of the actual global to call the
// get-global-op, so look it up here.
auto typeInfoGlobal =
cast<cir::GlobalOp>(cgm.getGlobalValue(typeInfo.getSymbol().getValue()));
auto getTypeInfo = cir::GetGlobalOp::create(
builder, loc, builder.getPointerTo(typeInfoGlobal.getSymType()),
typeInfoGlobal.getSymName());
// The ABI is just generating these sometimes as ptr to u8, but they are
// simply a representation of the type_info. So we have to cast this, if
// necessary (createBitcast is a noop if the types match).
return builder.createBitcast(getTypeInfo, resultType);
}