This reverts commit
54a4da9df6.
MSVC supports an extension allowing to delete an array of objects via
pointer whose static type doesn't match its dynamic type. This is done
via generation of special destructors - vector deleting destructors.
MSVC's virtual tables always contain a pointer to the vector deleting
destructor for classes with virtual destructors, so not having this
extension implemented causes clang to generate code that is not
compatible with the code generated by MSVC, because clang always puts a
pointer to a scalar deleting destructor to the vtable. As a bonus the
deletion of an array of polymorphic object will work just like it does
with MSVC - no memory leaks and correct destructors are called.
This patch will cause clang to emit code that is compatible with code
produced by MSVC but not compatible with code produced with clang of
older versions, so the new behavior can be disabled via passing
-fclang-abi-compat=21 (or lower).
Fixes https://github.com/llvm/llvm-project/issues/19772
1041 lines
41 KiB
C++
1041 lines
41 KiB
C++
//===--- 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/DeclCXX.h"
|
|
#include "clang/AST/ExprCXX.h"
|
|
#include "clang/Basic/OperatorKinds.h"
|
|
#include "clang/CIR/MissingFeatures.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::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::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)
|
|
.emitAbstract(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 {
|
|
// TODO: Handle the variable size case
|
|
cgf.cgm.errorNYI(e->getSourceRange(),
|
|
"emitCXXNewAllocSize: variable array size");
|
|
}
|
|
|
|
if (cookieSize == 0)
|
|
sizeWithoutCookie = size;
|
|
else
|
|
assert(sizeWithoutCookie && "didn't set sizeWithoutCookie?");
|
|
|
|
return size;
|
|
}
|
|
|
|
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;
|
|
|
|
unsigned initListElements = 0;
|
|
|
|
const Expr *init = e->getInitializer();
|
|
const InitListExpr *ile = dyn_cast<InitListExpr>(init);
|
|
if (ile) {
|
|
cgm.errorNYI(ile->getSourceRange(), "emitNewArrayInitializer: init list");
|
|
return;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
cgm.errorNYI(cce->getSourceRange(),
|
|
"emitNewArrayInitializer: ctor initializer");
|
|
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);
|
|
}
|
|
|
|
/// 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.
|
|
assert(!cir::MissingFeatures::attributeBuiltin());
|
|
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 auto *decl : lookupResult) {
|
|
if (const auto *funcDecl = dyn_cast<FunctionDecl>(decl)) {
|
|
if (astContext.hasSameType(funcDecl->getType(), QualType(type, 0))) {
|
|
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 {
|
|
/// 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");
|
|
}
|
|
|
|
// In traditional LLVM codegen null checks are emitted to save a delete call.
|
|
// In CIR we optimize for size by default, the null check should be added into
|
|
// this function callers.
|
|
assert(!cir::MissingFeatures::emitNullCheckForDeleteCalls());
|
|
|
|
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.
|
|
//
|
|
// CIR note: emit the code size friendly by default for now, such as mentioned
|
|
// in `emitObjectDelete`.
|
|
assert(!cir::MissingFeatures::emitNullCheckForDeleteCalls());
|
|
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()) {
|
|
assert(!cir::MissingFeatures::deleteArray());
|
|
cgm.errorNYI(e->getSourceRange(), "emitCXXDeleteExpr: array delete");
|
|
return;
|
|
} 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;
|
|
|
|
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 (e->getOperatorDelete() &&
|
|
!e->getOperatorDelete()->isReservedGlobalPlacementOperator())
|
|
cgm.errorNYI(e->getSourceRange(), "emitCXXNewExpr: operator delete");
|
|
|
|
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);
|
|
|
|
// 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);
|
|
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);
|
|
}
|