Skip to content

Commit

Permalink
[Builtins] Overload __builtin_operator_new/delete to allow forwarding…
Browse files Browse the repository at this point in the history
… to usual allocation/deallocation functions.

Summary:
Libc++'s default allocator uses `__builtin_operator_new` and `__builtin_operator_delete` in order to allow the calls to new/delete to be ellided. However, libc++ now needs to support over-aligned types in the default allocator. In order to support this without disabling the existing optimization Clang needs to support calling the aligned new overloads from the builtins.

See llvm.org/PR22634 for more information about the libc++ bug.

This patch changes `__builtin_operator_new`/`__builtin_operator_delete` to call any usual `operator new`/`operator delete` function. It does this by performing overload resolution with the arguments passed to the builtin to determine which allocation function to call. If the selected function is not a usual allocation function a diagnostic is issued.

One open issue is if the `align_val_t` overloads should be considered "usual" when `LangOpts::AlignedAllocation` is disabled.


In order to allow libc++ to detect this new behavior the value for `__has_builtin(__builtin_operator_new)` has been updated to `201802`.

Reviewers: rsmith, majnemer, aaron.ballman, erik.pilkington, bogner, ahatanak

Reviewed By: rsmith

Subscribers: cfe-commits

Differential Revision: https://reviews.llvm.org/D43047

llvm-svn: 328134
  • Loading branch information
EricWF committed Mar 21, 2018
1 parent b17fff7 commit fa752f2
Showing 11 changed files with 386 additions and 30 deletions.
4 changes: 2 additions & 2 deletions clang/include/clang/Basic/Builtins.def
Original file line number Diff line number Diff line change
@@ -1371,8 +1371,8 @@ BUILTIN(__builtin_smulll_overflow, "bSLLiCSLLiCSLLi*", "n")

// Clang builtins (not available in GCC).
BUILTIN(__builtin_addressof, "v*v&", "nct")
BUILTIN(__builtin_operator_new, "v*z", "c")
BUILTIN(__builtin_operator_delete, "vv*", "n")
BUILTIN(__builtin_operator_new, "v*z", "tc")
BUILTIN(__builtin_operator_delete, "vv*", "tn")
BUILTIN(__builtin_char_memchr, "c*cC*iz", "n")

// Safestack builtins
5 changes: 5 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
@@ -7627,6 +7627,11 @@ def err_destroying_operator_delete_not_usual : Error<
"alignment parameter">;
def note_implicit_delete_this_in_destructor_here : Note<
"while checking implicit 'delete this' for virtual destructor">;
def err_builtin_operator_new_delete_not_usual : Error<
"call to '%select{__builtin_operator_new|__builtin_operator_delete}0' "
"selects non-usual %select{allocation|deallocation}0 function">;
def note_non_usual_function_declared_here : Note<
"non-usual %0 declared here">;

// C++ literal operators
def err_literal_operator_outside_namespace : Error<
2 changes: 2 additions & 0 deletions clang/include/clang/Sema/Sema.h
Original file line number Diff line number Diff line change
@@ -10376,6 +10376,8 @@ class Sema {
ExprResult SemaBuiltinNontemporalOverloaded(ExprResult TheCallResult);
ExprResult SemaAtomicOpsOverloaded(ExprResult TheCallResult,
AtomicExpr::AtomicOp Op);
ExprResult SemaBuiltinOperatorNewDeleteOverloaded(ExprResult TheCallResult,
bool IsDelete);
bool SemaBuiltinConstantArg(CallExpr *TheCall, int ArgNum,
llvm::APSInt &Result);
bool SemaBuiltinConstantArgRange(CallExpr *TheCall, int ArgNum,
9 changes: 5 additions & 4 deletions clang/lib/CodeGen/CGBuiltin.cpp
Original file line number Diff line number Diff line change
@@ -2611,11 +2611,12 @@ RValue CodeGenFunction::EmitBuiltinExpr(const FunctionDecl *FD,
case Builtin::BI__builtin_addressof:
return RValue::get(EmitLValue(E->getArg(0)).getPointer());
case Builtin::BI__builtin_operator_new:
return EmitBuiltinNewDeleteCall(FD->getType()->castAs<FunctionProtoType>(),
E->getArg(0), false);
return EmitBuiltinNewDeleteCall(
E->getCallee()->getType()->castAs<FunctionProtoType>(), E, false);
case Builtin::BI__builtin_operator_delete:
return EmitBuiltinNewDeleteCall(FD->getType()->castAs<FunctionProtoType>(),
E->getArg(0), true);
return EmitBuiltinNewDeleteCall(
E->getCallee()->getType()->castAs<FunctionProtoType>(), E, true);

case Builtin::BI__noop:
// __noop always evaluates to an integer literal zero.
return RValue::get(ConstantInt::get(IntTy, 0));
8 changes: 4 additions & 4 deletions clang/lib/CodeGen/CGExprCXX.cpp
Original file line number Diff line number Diff line change
@@ -1307,19 +1307,19 @@ static RValue EmitNewDeleteCall(CodeGenFunction &CGF,
}

RValue CodeGenFunction::EmitBuiltinNewDeleteCall(const FunctionProtoType *Type,
const Expr *Arg,
const CallExpr *TheCall,
bool IsDelete) {
CallArgList Args;
const Stmt *ArgS = Arg;
EmitCallArgs(Args, *Type->param_type_begin(), llvm::makeArrayRef(ArgS));
EmitCallArgs(Args, Type->getParamTypes(), TheCall->arguments());
// Find the allocation or deallocation function that we're calling.
ASTContext &Ctx = getContext();
DeclarationName Name = Ctx.DeclarationNames
.getCXXOperatorName(IsDelete ? OO_Delete : OO_New);

for (auto *Decl : Ctx.getTranslationUnitDecl()->lookup(Name))
if (auto *FD = dyn_cast<FunctionDecl>(Decl))
if (Ctx.hasSameType(FD->getType(), QualType(Type, 0)))
return EmitNewDeleteCall(*this, cast<FunctionDecl>(Decl), Type, Args);
return EmitNewDeleteCall(*this, FD, Type, Args);
llvm_unreachable("predeclared global operator new/delete is missing");
}

2 changes: 1 addition & 1 deletion clang/lib/CodeGen/CodeGenFunction.h
Original file line number Diff line number Diff line change
@@ -2356,7 +2356,7 @@ class CodeGenFunction : public CodeGenTypeCache {
CharUnits CookieSize = CharUnits());

RValue EmitBuiltinNewDeleteCall(const FunctionProtoType *Type,
const Expr *Arg, bool IsDelete);
const CallExpr *TheCallExpr, bool IsDelete);

llvm::Value *EmitCXXTypeidExpr(const CXXTypeidExpr *E);
llvm::Value *EmitDynamicCast(Address V, const CXXDynamicCastExpr *DCE);
15 changes: 12 additions & 3 deletions clang/lib/Lex/PPMacroExpansion.cpp
Original file line number Diff line number Diff line change
@@ -1801,12 +1801,21 @@ void Preprocessor::ExpandBuiltinMacro(Token &Tok) {
[this](Token &Tok, bool &HasLexedNextToken) -> int {
IdentifierInfo *II = ExpectFeatureIdentifierInfo(Tok, *this,
diag::err_feature_check_malformed);
const LangOptions &LangOpts = getLangOpts();
if (!II)
return false;
else if (II->getBuiltinID() != 0)
else if (II->getBuiltinID() != 0) {
switch (II->getBuiltinID()) {
case Builtin::BI__builtin_operator_new:
case Builtin::BI__builtin_operator_delete:
// denotes date of behavior change to support calling arbitrary
// usual allocation and deallocation functions. Required by libc++
return 201802;
default:
return true;
}
return true;
else {
const LangOptions &LangOpts = getLangOpts();
} else {
return llvm::StringSwitch<bool>(II->getName())
.Case("__make_integer_seq", LangOpts.CPlusPlus)
.Case("__type_pack_element", LangOpts.CPlusPlus)
22 changes: 8 additions & 14 deletions clang/lib/Sema/SemaChecking.cpp
Original file line number Diff line number Diff line change
@@ -1097,20 +1097,14 @@ Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID,
return ExprError();
break;
case Builtin::BI__builtin_operator_new:
case Builtin::BI__builtin_operator_delete:
if (!getLangOpts().CPlusPlus) {
Diag(TheCall->getExprLoc(), diag::err_builtin_requires_language)
<< (BuiltinID == Builtin::BI__builtin_operator_new
? "__builtin_operator_new"
: "__builtin_operator_delete")
<< "C++";
return ExprError();
}
// CodeGen assumes it can find the global new and delete to call,
// so ensure that they are declared.
DeclareGlobalNewDelete();
break;

case Builtin::BI__builtin_operator_delete: {
bool IsDelete = BuiltinID == Builtin::BI__builtin_operator_delete;
ExprResult Res =
SemaBuiltinOperatorNewDeleteOverloaded(TheCallResult, IsDelete);
if (Res.isInvalid())
CorrectDelayedTyposInExpr(TheCallResult.get());
return Res;
}
// check secure string manipulation functions where overflows
// are detectable at compile time
case Builtin::BI__builtin___memcpy_chk:
125 changes: 123 additions & 2 deletions clang/lib/Sema/SemaExprCXX.cpp
Original file line number Diff line number Diff line change
@@ -1443,7 +1443,7 @@ namespace {
CUDAPref = S.IdentifyCUDAPreference(Caller, FD);
}

operator bool() const { return FD; }
explicit operator bool() const { return FD; }

bool isBetterThan(const UsualDeallocFnInfo &Other, bool WantSize,
bool WantAlign) const {
@@ -2271,7 +2271,6 @@ static bool resolveAllocationOverload(
llvm_unreachable("Unreachable, bad result from BestViableFunction");
}


/// FindAllocationFunctions - Finds the overloads of operator new and delete
/// that are appropriate for the allocation.
bool Sema::FindAllocationFunctions(SourceLocation StartLoc, SourceRange Range,
@@ -3343,6 +3342,128 @@ Sema::ActOnCXXDelete(SourceLocation StartLoc, bool UseGlobal,
return Result;
}

static bool resolveBuiltinNewDeleteOverload(Sema &S, CallExpr *TheCall,
bool IsDelete,
FunctionDecl *&Operator) {

DeclarationName NewName = S.Context.DeclarationNames.getCXXOperatorName(
IsDelete ? OO_Delete : OO_New);

LookupResult R(S, NewName, TheCall->getLocStart(), Sema::LookupOrdinaryName);
S.LookupQualifiedName(R, S.Context.getTranslationUnitDecl());
assert(!R.empty() && "implicitly declared allocation functions not found");
assert(!R.isAmbiguous() && "global allocation functions are ambiguous");

// We do our own custom access checks below.
R.suppressDiagnostics();

SmallVector<Expr *, 8> Args(TheCall->arg_begin(), TheCall->arg_end());
OverloadCandidateSet Candidates(R.getNameLoc(),
OverloadCandidateSet::CSK_Normal);
for (LookupResult::iterator FnOvl = R.begin(), FnOvlEnd = R.end();
FnOvl != FnOvlEnd; ++FnOvl) {
// Even member operator new/delete are implicitly treated as
// static, so don't use AddMemberCandidate.
NamedDecl *D = (*FnOvl)->getUnderlyingDecl();

if (FunctionTemplateDecl *FnTemplate = dyn_cast<FunctionTemplateDecl>(D)) {
S.AddTemplateOverloadCandidate(FnTemplate, FnOvl.getPair(),
/*ExplicitTemplateArgs=*/nullptr, Args,
Candidates,
/*SuppressUserConversions=*/false);
continue;
}

FunctionDecl *Fn = cast<FunctionDecl>(D);
S.AddOverloadCandidate(Fn, FnOvl.getPair(), Args, Candidates,
/*SuppressUserConversions=*/false);
}

SourceRange Range = TheCall->getSourceRange();

// Do the resolution.
OverloadCandidateSet::iterator Best;
switch (Candidates.BestViableFunction(S, R.getNameLoc(), Best)) {
case OR_Success: {
// Got one!
FunctionDecl *FnDecl = Best->Function;
assert(R.getNamingClass() == nullptr &&
"class members should not be considered");

if (!FnDecl->isReplaceableGlobalAllocationFunction()) {
S.Diag(R.getNameLoc(), diag::err_builtin_operator_new_delete_not_usual)
<< (IsDelete ? 1 : 0) << Range;
S.Diag(FnDecl->getLocation(), diag::note_non_usual_function_declared_here)
<< R.getLookupName() << FnDecl->getSourceRange();
return true;
}

Operator = FnDecl;
return false;
}

case OR_No_Viable_Function:
S.Diag(R.getNameLoc(), diag::err_ovl_no_viable_function_in_call)
<< R.getLookupName() << Range;
Candidates.NoteCandidates(S, OCD_AllCandidates, Args);
return true;

case OR_Ambiguous:
S.Diag(R.getNameLoc(), diag::err_ovl_ambiguous_call)
<< R.getLookupName() << Range;
Candidates.NoteCandidates(S, OCD_ViableCandidates, Args);
return true;

case OR_Deleted: {
S.Diag(R.getNameLoc(), diag::err_ovl_deleted_call)
<< Best->Function->isDeleted() << R.getLookupName()
<< S.getDeletedOrUnavailableSuffix(Best->Function) << Range;
Candidates.NoteCandidates(S, OCD_AllCandidates, Args);
return true;
}
}
llvm_unreachable("Unreachable, bad result from BestViableFunction");
}

ExprResult
Sema::SemaBuiltinOperatorNewDeleteOverloaded(ExprResult TheCallResult,
bool IsDelete) {
CallExpr *TheCall = cast<CallExpr>(TheCallResult.get());
if (!getLangOpts().CPlusPlus) {
Diag(TheCall->getExprLoc(), diag::err_builtin_requires_language)
<< (IsDelete ? "__builtin_operator_delete" : "__builtin_operator_new")
<< "C++";
return ExprError();
}
// CodeGen assumes it can find the global new and delete to call,
// so ensure that they are declared.
DeclareGlobalNewDelete();

FunctionDecl *OperatorNewOrDelete = nullptr;
if (resolveBuiltinNewDeleteOverload(*this, TheCall, IsDelete,
OperatorNewOrDelete))
return ExprError();
assert(OperatorNewOrDelete && "should be found");

TheCall->setType(OperatorNewOrDelete->getReturnType());
for (unsigned i = 0; i != TheCall->getNumArgs(); ++i) {
QualType ParamTy = OperatorNewOrDelete->getParamDecl(i)->getType();
InitializedEntity Entity =
InitializedEntity::InitializeParameter(Context, ParamTy, false);
ExprResult Arg = PerformCopyInitialization(
Entity, TheCall->getArg(i)->getLocStart(), TheCall->getArg(i));
if (Arg.isInvalid())
return ExprError();
TheCall->setArg(i, Arg.get());
}
auto Callee = dyn_cast<ImplicitCastExpr>(TheCall->getCallee());
assert(Callee && Callee->getCastKind() == CK_BuiltinFnToFnPtr &&
"Callee expected to be implicit cast to a builtin function pointer");
Callee->setType(OperatorNewOrDelete->getType());

return TheCallResult;
}

void Sema::CheckVirtualDtorCall(CXXDestructorDecl *dtor, SourceLocation Loc,
bool IsDelete, bool CallCanBeVirtual,
bool WarnOnNonAbstractTypes,
71 changes: 71 additions & 0 deletions clang/test/CodeGenCXX/builtin-operator-new-delete.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// RUN: %clang_cc1 -triple x86_64-unknown-unknown %s \
// RUN: -faligned-allocation -fsized-deallocation -emit-llvm -o - \
// RUN: | FileCheck %s

typedef __SIZE_TYPE__ size_t;

// Declare an 'operator new' template to tickle a bug in __builtin_operator_new.
template<typename T> void *operator new(size_t, int (*)(T));

// Ensure that this declaration doesn't cause operator new to lose its
// 'noalias' attribute.
void *operator new(size_t);

namespace std {
struct nothrow_t {};
enum class align_val_t : size_t { __zero = 0,
__max = (size_t)-1 };
}
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();

// Declare some other placement operators.
void *operator new(size_t, void*, bool) throw();
void *operator new[](size_t, void*, bool) throw();


// CHECK-LABEL: define void @test_basic(
extern "C" void test_basic() {
// CHECK: call i8* @_Znwm(i64 4) [[ATTR_BUILTIN_NEW:#[^ ]*]]
// CHECK: call void @_ZdlPv({{.*}}) [[ATTR_BUILTIN_DELETE:#[^ ]*]]
// CHECK: ret void
__builtin_operator_delete(__builtin_operator_new(4));
}
// CHECK: declare noalias i8* @_Znwm(i64) [[ATTR_NOBUILTIN:#[^ ]*]]
// CHECK: declare void @_ZdlPv(i8*) [[ATTR_NOBUILTIN_NOUNWIND:#[^ ]*]]

// CHECK-LABEL: define void @test_aligned_alloc(
extern "C" void test_aligned_alloc() {
// CHECK: call i8* @_ZnwmSt11align_val_t(i64 4, i64 4) [[ATTR_BUILTIN_NEW:#[^ ]*]]
// CHECK: call void @_ZdlPvSt11align_val_t({{.*}}, i64 4) [[ATTR_BUILTIN_DELETE:#[^ ]*]]
__builtin_operator_delete(__builtin_operator_new(4, std::align_val_t(4)), std::align_val_t(4));
}
// CHECK: declare noalias i8* @_ZnwmSt11align_val_t(i64, i64) [[ATTR_NOBUILTIN:#[^ ]*]]
// CHECK: declare void @_ZdlPvSt11align_val_t(i8*, i64) [[ATTR_NOBUILTIN_NOUNWIND:#[^ ]*]]


// CHECK-LABEL: define void @test_sized_delete(
extern "C" void test_sized_delete() {
// CHECK: call i8* @_Znwm(i64 4) [[ATTR_BUILTIN_NEW:#[^ ]*]]
// CHECK: call void @_ZdlPvm({{.*}}, i64 4) [[ATTR_BUILTIN_DELETE:#[^ ]*]]
__builtin_operator_delete(__builtin_operator_new(4), 4);
}
// CHECK: declare void @_ZdlPvm(i8*, i64) [[ATTR_NOBUILTIN_UNWIND:#[^ ]*]]


// CHECK-DAG: attributes [[ATTR_NOBUILTIN]] = {{[{].*}} nobuiltin {{.*[}]}}
// CHECK-DAG: attributes [[ATTR_NOBUILTIN_NOUNWIND]] = {{[{].*}} nobuiltin nounwind {{.*[}]}}

// CHECK-DAG: attributes [[ATTR_BUILTIN_NEW]] = {{[{].*}} builtin {{.*[}]}}
// CHECK-DAG: attributes [[ATTR_BUILTIN_DELETE]] = {{[{].*}} builtin {{.*[}]}}
Loading

0 comments on commit fa752f2

Please sign in to comment.