From 8d0dc31dcada7f8002022b02a1cee471494e530b Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Sun, 21 Jul 2013 23:12:18 +0000 Subject: [PATCH] Tighten up the set of operator new/operator delete calls we're permitted to optimize, to follow the permissions granted in N3664. Under those rules, only calls generated by new-expressions and delete-expressions are permitted to be optimized, and direct calls to ::operator new and ::operator delete must be treated as normal calls. llvm-svn: 186799 --- clang/include/clang/AST/Decl.h | 15 +++++ clang/lib/AST/Decl.cpp | 50 +++++++++++++++- clang/lib/CodeGen/CGExprCXX.cpp | 53 +++++++++++------ clang/lib/CodeGen/CodeGenModule.cpp | 6 ++ clang/test/CodeGenCXX/new.cpp | 88 +++++++++++++++++++++++++++-- clang/www/cxx_status.html | 2 +- 6 files changed, 190 insertions(+), 24 deletions(-) diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h index c0422e336af2..6be323a981c4 100644 --- a/clang/include/clang/AST/Decl.h +++ b/clang/include/clang/AST/Decl.h @@ -1720,6 +1720,21 @@ public: /// This function must be an allocation or deallocation function. bool isReservedGlobalPlacementOperator() const; + /// \brief Determines whether this function is one of the replaceable + /// global allocation functions: + /// void *operator new(size_t); + /// void *operator new(size_t, const std::nothrow_t &) noexcept; + /// void *operator new[](size_t); + /// void *operator new[](size_t, const std::nothrow_t &) noexcept; + /// void operator delete(void *) noexcept; + /// void operator delete(void *, const std::nothrow_t &) noexcept; + /// void operator delete[](void *) noexcept; + /// void operator delete[](void *, const std::nothrow_t &) noexcept; + /// These functions have special behavior under C++1y [expr.new]: + /// An implementation is allowed to omit a call to a replaceable global + /// allocation function. [...] + bool isReplaceableGlobalAllocationFunction() const; + /// Compute the language linkage. LanguageLinkage getLanguageLinkage() const; diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp index 0ad15fc683ec..4ecb41b3f0b7 100644 --- a/clang/lib/AST/Decl.cpp +++ b/clang/lib/AST/Decl.cpp @@ -2132,13 +2132,18 @@ void FunctionDecl::setPure(bool P) { Parent->markedVirtualFunctionPure(); } +template +static bool isNamed(const NamedDecl *ND, const char (&Str)[Len]) { + IdentifierInfo *II = ND->getIdentifier(); + return II && II->isStr(Str); +} + bool FunctionDecl::isMain() const { const TranslationUnitDecl *tunit = dyn_cast(getDeclContext()->getRedeclContext()); return tunit && !tunit->getASTContext().getLangOpts().Freestanding && - getIdentifier() && - getIdentifier()->isStr("main"); + isNamed(this, "main"); } bool FunctionDecl::isReservedGlobalPlacementOperator() const { @@ -2163,6 +2168,47 @@ bool FunctionDecl::isReservedGlobalPlacementOperator() const { return (proto->getArgType(1).getCanonicalType() == Context.VoidPtrTy); } +static bool isNamespaceStd(const DeclContext *DC) { + const NamespaceDecl *ND = dyn_cast(DC->getRedeclContext()); + return ND && isNamed(ND, "std") && + ND->getParent()->getRedeclContext()->isTranslationUnit(); +} + +bool FunctionDecl::isReplaceableGlobalAllocationFunction() const { + if (getDeclName().getNameKind() != DeclarationName::CXXOperatorName) + return false; + if (getDeclName().getCXXOverloadedOperator() != OO_New && + getDeclName().getCXXOverloadedOperator() != OO_Delete && + getDeclName().getCXXOverloadedOperator() != OO_Array_New && + getDeclName().getCXXOverloadedOperator() != OO_Array_Delete) + return false; + + if (isa(getDeclContext())) + return false; + assert(getDeclContext()->getRedeclContext()->isTranslationUnit()); + + const FunctionProtoType *FPT = getType()->castAs(); + if (FPT->getNumArgs() > 2 || FPT->isVariadic()) + return false; + + // If this is a single-parameter function, it must be a replaceable global + // allocation or deallocation function. + if (FPT->getNumArgs() == 1) + return true; + + // Otherwise, we're looking for a second parameter whose type is + // 'const std::nothrow_t &'. + QualType Ty = FPT->getArgType(1); + if (!Ty->isReferenceType()) + return false; + Ty = Ty->getPointeeType(); + if (Ty.getCVRQualifiers() != Qualifiers::Const) + return false; + // FIXME: Recognise nothrow_t in an inline namespace inside std? + const CXXRecordDecl *RD = Ty->getAsCXXRecordDecl(); + return RD && isNamed(RD, "nothrow_t") && isNamespaceStd(RD->getDeclContext()); +} + LanguageLinkage FunctionDecl::getLanguageLinkage() const { // Users expect to be able to write // extern "C" void *__builtin_alloca (size_t); diff --git a/clang/lib/CodeGen/CGExprCXX.cpp b/clang/lib/CodeGen/CGExprCXX.cpp index 672e86d0d8f1..1520656c9b81 100644 --- a/clang/lib/CodeGen/CGExprCXX.cpp +++ b/clang/lib/CodeGen/CGExprCXX.cpp @@ -1002,6 +1002,38 @@ static void EmitNewInitializer(CodeGenFunction &CGF, const CXXNewExpr *E, StoreAnyExprIntoOneUnit(CGF, Init, E->getAllocatedType(), NewPtr); } +/// Emit a call to an operator new or operator delete function, as implicitly +/// created by new-expressions and delete-expressions. +static RValue EmitNewDeleteCall(CodeGenFunction &CGF, + const FunctionDecl *Callee, + const FunctionProtoType *CalleeType, + const CallArgList &Args) { + llvm::Instruction *CallOrInvoke; + RValue RV = + CGF.EmitCall(CGF.CGM.getTypes().arrangeFreeFunctionCall(Args, CalleeType), + CGF.CGM.GetAddrOfFunction(Callee), ReturnValueSlot(), Args, + Callee, &CallOrInvoke); + + /// 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 (Callee->isReplaceableGlobalAllocationFunction()) { + // FIXME: Add addAttribute to CallSite. + if (llvm::CallInst *CI = dyn_cast(CallOrInvoke)) + CI->addAttribute(llvm::AttributeSet::FunctionIndex, + llvm::Attribute::Builtin); + else if (llvm::InvokeInst *II = dyn_cast(CallOrInvoke)) + II->addAttribute(llvm::AttributeSet::FunctionIndex, + llvm::Attribute::Builtin); + else + llvm_unreachable("unexpected kind of call instruction"); + } + + return RV; +} + namespace { /// A cleanup to call the given 'operator delete' function upon /// abnormal exit from a new expression. @@ -1051,9 +1083,7 @@ namespace { DeleteArgs.add(getPlacementArgs()[I], *AI++); // Call 'operator delete'. - CGF.EmitCall(CGF.CGM.getTypes().arrangeFreeFunctionCall(DeleteArgs, FPT), - CGF.CGM.GetAddrOfFunction(OperatorDelete), - ReturnValueSlot(), DeleteArgs, OperatorDelete); + EmitNewDeleteCall(CGF, OperatorDelete, FPT, DeleteArgs); } }; @@ -1112,9 +1142,7 @@ namespace { } // Call 'operator delete'. - CGF.EmitCall(CGF.CGM.getTypes().arrangeFreeFunctionCall(DeleteArgs, FPT), - CGF.CGM.GetAddrOfFunction(OperatorDelete), - ReturnValueSlot(), DeleteArgs, OperatorDelete); + EmitNewDeleteCall(CGF, OperatorDelete, FPT, DeleteArgs); } }; } @@ -1227,10 +1255,7 @@ llvm::Value *CodeGenFunction::EmitCXXNewExpr(const CXXNewExpr *E) { // TODO: kill any unnecessary computations done for the size // argument. } else { - RV = EmitCall(CGM.getTypes().arrangeFreeFunctionCall(allocatorArgs, - allocatorType), - CGM.GetAddrOfFunction(allocator), ReturnValueSlot(), - allocatorArgs, allocator); + RV = EmitNewDeleteCall(*this, allocator, allocatorType, allocatorArgs); } // Emit a null check on the allocation result if the allocation @@ -1350,9 +1375,7 @@ void CodeGenFunction::EmitDeleteCall(const FunctionDecl *DeleteFD, DeleteArgs.add(RValue::get(Size), SizeTy); // Emit the call to delete. - EmitCall(CGM.getTypes().arrangeFreeFunctionCall(DeleteArgs, DeleteFTy), - CGM.GetAddrOfFunction(DeleteFD), ReturnValueSlot(), - DeleteArgs, DeleteFD); + EmitNewDeleteCall(*this, DeleteFD, DeleteFTy, DeleteArgs); } namespace { @@ -1508,9 +1531,7 @@ namespace { } // Emit the call to delete. - CGF.EmitCall(CGF.getTypes().arrangeFreeFunctionCall(Args, DeleteFTy), - CGF.CGM.GetAddrOfFunction(OperatorDelete), - ReturnValueSlot(), Args, OperatorDelete); + EmitNewDeleteCall(CGF, OperatorDelete, DeleteFTy, Args); } }; } diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp index af6a0113f2dc..53efaa6349f7 100644 --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -744,6 +744,12 @@ void CodeGenModule::SetFunctionAttributes(GlobalDecl GD, if (const SectionAttr *SA = FD->getAttr()) F->setSection(SA->getName()); + + // A replaceable global allocation function does not act like a builtin by + // default, only if it is invoked by a new-expression or delete-expression. + if (FD->isReplaceableGlobalAllocationFunction()) + F->addAttribute(llvm::AttributeSet::FunctionIndex, + llvm::Attribute::NoBuiltin); } void CodeGenModule::AddUsedGlobal(llvm::GlobalValue *GV) { diff --git a/clang/test/CodeGenCXX/new.cpp b/clang/test/CodeGenCXX/new.cpp index e0523909a3db..a6e082502df2 100644 --- a/clang/test/CodeGenCXX/new.cpp +++ b/clang/test/CodeGenCXX/new.cpp @@ -2,16 +2,38 @@ typedef __typeof__(sizeof(0)) size_t; +// Ensure that this declaration doesn't cause operator new to lose its +// 'noalias' attribute. +void *operator new[](size_t); + void t1() { - int* a = new int; + delete new int; + delete [] new int [3]; } +// CHECK: declare noalias i8* @_Znwm(i64) [[ATTR_NOBUILTIN:#[^ ]*]] +// CHECK: declare void @_ZdlPv(i8*) [[ATTR_NOBUILTIN_NOUNWIND:#[^ ]*]] +// CHECK: declare noalias i8* @_Znam(i64) [[ATTR_NOBUILTIN]] +// CHECK: declare void @_ZdaPv(i8*) [[ATTR_NOBUILTIN_NOUNWIND]] + +namespace std { + struct nothrow_t {}; +} +std::nothrow_t nothrow; + // Declare the reserved placement operators. void *operator new(size_t, void*) throw(); void operator delete(void*, void*) throw(); void *operator new[](size_t, void*) throw(); void operator delete[](void*, void*) throw(); +// Declare the replaceable global allocation operators. +void *operator new(size_t, const std::nothrow_t &) throw(); +void *operator new[](size_t, const std::nothrow_t &) throw(); +void operator delete(void *, const std::nothrow_t &) throw(); +void operator delete[](void *, const std::nothrow_t &) throw(); + + void t2(int* a) { int* b = new (a) int; } @@ -77,10 +99,6 @@ void t8(int n) { new U[n]; } -// noalias -// CHECK: declare noalias i8* @_Znam -void *operator new[](size_t); - void t9() { bool b; @@ -260,3 +278,63 @@ namespace PR13380 { // CHECK-NEXT: call void @_ZN7PR133801BC1Ev void* f() { return new B[2](); } } + +struct MyPlacementType {} mpt; +void *operator new(size_t, MyPlacementType); + +namespace N3664 { + struct S { S() throw(int); }; + + // CHECK-LABEL: define void @_ZN5N36641fEv + void f() { + // CHECK: call noalias i8* @_Znwm(i64 4) [[ATTR_BUILTIN_NEW:#[^ ]*]] + int *p = new int; + // CHECK: call void @_ZdlPv({{.*}}) [[ATTR_BUILTIN_DELETE:#[^ ]*]] + delete p; + + // CHECK: call noalias i8* @_Znam(i64 12) [[ATTR_BUILTIN_NEW]] + int *q = new int[3]; + // CHECK: call void @_ZdaPv({{.*}}) [[ATTR_BUILTIN_DELETE]] + delete [] p; + + // CHECK: call i8* @_ZnamRKSt9nothrow_t(i64 3, {{.*}}) [[ATTR_BUILTIN_NOTHROW_NEW:#[^ ]*]] + (void) new (nothrow) S[3]; + + // CHECK: call i8* @_Znwm15MyPlacementType(i64 4){{$}} + (void) new (mpt) int; + } + + // FIXME: Can we mark this noalias? + // CHECK: declare i8* @_ZnamRKSt9nothrow_t(i64, {{.*}}) [[ATTR_NOBUILTIN_NOUNWIND]] + + // CHECK-LABEL: define void @_ZN5N36641gEv + void g() { + // It's OK for there to be attributes here, so long as we don't have a + // 'builtin' attribute. + // CHECK: call noalias i8* @_Znwm(i64 4){{$}} + int *p = (int*)operator new(4); + // CHECK: call void @_ZdlPv({{.*}}) [[ATTR_NOUNWIND:#[^ ]*]] + operator delete(p); + + // CHECK: call noalias i8* @_Znam(i64 12){{$}} + int *q = (int*)operator new[](12); + // CHECK: call void @_ZdaPv({{.*}}) [[ATTR_NOUNWIND]] + operator delete [](p); + + // CHECK: call i8* @_ZnamRKSt9nothrow_t(i64 3, {{.*}}) [[ATTR_NOUNWIND]] + (void) operator new[](3, nothrow); + } +} + +// CHECK-DAG: attributes [[ATTR_NOBUILTIN]] = {{[{].*}} nobuiltin {{.*[}]}} +// CHECK-DAG: attributes [[ATTR_NOBUILTIN_NOUNWIND]] = {{[{].*}} nobuiltin nounwind {{.*[}]}} + +// CHECK: attributes [[ATTR_NOUNWIND]] = +// CHECK-NOT: builtin +// CHECK-NOT: attributes +// CHECK: nounwind +// CHECK-NOT: builtin +// CHECK: attributes + +// CHECK-DAG: attributes [[ATTR_BUILTIN_NEW]] = {{[{].*}} builtin {{.*[}]}} +// CHECK-DAG: attributes [[ATTR_BUILTIN_DELETE]] = {{[{].*}} builtin {{.*[}]}} diff --git a/clang/www/cxx_status.html b/clang/www/cxx_status.html index 44ddd32519ea..4b88697175fd 100644 --- a/clang/www/cxx_status.html +++ b/clang/www/cxx_status.html @@ -467,7 +467,7 @@ available.

Clarifying memory allocation N3664 - Partial + SVN