diff --git a/clang/docs/CommandGuide/clang.rst b/clang/docs/CommandGuide/clang.rst --- a/clang/docs/CommandGuide/clang.rst +++ b/clang/docs/CommandGuide/clang.rst @@ -252,8 +252,24 @@ .. option:: -fno-builtin - Disable special handling and optimizations of builtin functions like - :c:func:`strlen` and :c:func:`malloc`. + Disable special handling and optimizations of well-known library functions, + like :c:func:`strlen` and :c:func:`malloc`. + +.. option:: -fno-builtin- + + Disable special handling and optimizations for the specific library function. + For example, ``-fno-builtin-strlen`` removes any special handling for the + :c:func:`strlen` library function. + +.. option:: -fno-builtin-std- + + Disable special handling and optimizations for the specific C++ standard + library function in namespace ``std``. For example, + ``-fno-builtin-std-move_if_noexcept`` removes any special handling for the + :cpp:func:`std::move_if_noexcept` library function. + + For C standard library functions that the C++ standard library also provides + in namespace ``std``, use :option:`-fno-builtin-\` instead. .. option:: -fmath-errno diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -230,7 +230,10 @@ C++ Language Changes in Clang ----------------------------- -- ... +- Improved ``-O0`` code generation for calls to ``std::move``, ``std::forward``, + ``std::move_if_noexcept``, ``std::addressof``, and ``std::as_const``. These + are now treated as compiler builtins and implemented directly, rather than + instantiating the definition from the standard library. C++20 Feature Support ^^^^^^^^^^^^^^^^^^^^^ diff --git a/clang/include/clang/Basic/Builtins.h b/clang/include/clang/Basic/Builtins.h --- a/clang/include/clang/Basic/Builtins.h +++ b/clang/include/clang/Basic/Builtins.h @@ -138,6 +138,10 @@ /// Determines whether this builtin is a predefined libc/libm /// function, such as "malloc", where we know the signature a /// priori. + /// In C, such functions behave as if they are predeclared, + /// possibly with a warning on first use. In Objective-C and C++, + /// they do not, but they are recognized as builtins once we see + /// a declaration. bool isPredefinedLibFunction(unsigned ID) const { return strchr(getRecord(ID).Attributes, 'f') != nullptr; } @@ -156,6 +160,23 @@ return strchr(getRecord(ID).Attributes, 'i') != nullptr; } + /// Determines whether this builtin is a C++ standard library function + /// that lives in (possibly-versioned) namespace std, possibly a template + /// specialization, where the signature is determined by the standard library + /// declaration. + bool isInStdNamespace(unsigned ID) const { + return strchr(getRecord(ID).Attributes, 'z') != nullptr; + } + + /// Determines whether this builtin can have its address taken with no + /// special action required. + bool isDirectlyAddressable(unsigned ID) const { + // Most standard library functions can have their addresses taken. C++ + // standard library functions formally cannot in C++20 onwards, and when + // we allow it, we need to ensure we instantiate a definition. + return isPredefinedLibFunction(ID) && !isInStdNamespace(ID); + } + /// Determines whether this builtin has custom typechecking. bool hasCustomTypechecking(unsigned ID) const { return strchr(getRecord(ID).Attributes, 't') != nullptr; @@ -237,10 +258,6 @@ private: const Info &getRecord(unsigned ID) const; - /// Is this builtin supported according to the given language options? - bool builtinIsSupported(const Builtin::Info &BuiltinInfo, - const LangOptions &LangOpts); - /// Helper function for isPrintfLike and isScanfLike. bool isLike(unsigned ID, unsigned &FormatIdx, bool &HasVAListArg, const char *Fmt) const; diff --git a/clang/include/clang/Basic/Builtins.def b/clang/include/clang/Basic/Builtins.def --- a/clang/include/clang/Basic/Builtins.def +++ b/clang/include/clang/Basic/Builtins.def @@ -81,7 +81,9 @@ // builtin even if type doesn't match signature, and don't warn if we // can't be sure the type is right // F -> this is a libc/libm function with a '__builtin_' prefix added. -// f -> this is a libc/libm function without the '__builtin_' prefix. +// f -> this is a libc/libm function without a '__builtin_' prefix, or with +// 'z', a C++ standard library function in namespace std::. This builtin +// is disableable by '-fno-builtin-foo' / '-fno-builtin-std-foo'. // h -> this function requires a specific header or an explicit declaration. // i -> this is a runtime library implemented function without the // '__builtin_' prefix. It will be implemented in compiler-rt or libgcc. @@ -101,6 +103,7 @@ // V:N: -> requires vectors of at least N bits to be legal // C -> callback behavior: argument N is called with argument // M_0, ..., M_k as payload +// z -> this is a function in (possibly-versioned) namespace std // FIXME: gcc has nonnull #if defined(BUILTIN) && !defined(LIBBUILTIN) @@ -919,7 +922,7 @@ LANGBUILTIN(_exception_info, "v*", "n", ALL_MS_LANGUAGES) LANGBUILTIN(__abnormal_termination, "i", "n", ALL_MS_LANGUAGES) LANGBUILTIN(_abnormal_termination, "i", "n", ALL_MS_LANGUAGES) -LANGBUILTIN(__GetExceptionInfo, "v*.", "ntu", ALL_MS_LANGUAGES) +LANGBUILTIN(__GetExceptionInfo, "v*.", "zntu", ALL_MS_LANGUAGES) LANGBUILTIN(_InterlockedAnd8, "ccD*c", "n", ALL_MS_LANGUAGES) LANGBUILTIN(_InterlockedAnd16, "ssD*s", "n", ALL_MS_LANGUAGES) LANGBUILTIN(_InterlockedAnd, "NiNiD*Ni", "n", ALL_MS_LANGUAGES) @@ -1543,6 +1546,15 @@ LIBBUILTIN(_Block_object_dispose, "vvC*iC", "f", "Blocks.h", ALL_LANGUAGES) // FIXME: Also declare NSConcreteGlobalBlock and NSConcreteStackBlock. +// C++ standard library builtins in namespace 'std'. +LIBBUILTIN(addressof, "v*v&", "zfncTh", "memory", CXX_LANG) +// Synonym for addressof used internally by libstdc++. +LANGBUILTIN(__addressof, "v*v&", "zfncT", CXX_LANG) +LIBBUILTIN(as_const, "v&v&", "zfncTh", "utility", CXX_LANG) +LIBBUILTIN(forward, "v&v&", "zfncTh", "utility", CXX_LANG) +LIBBUILTIN(move, "v&v&", "zfncTh", "utility", CXX_LANG) +LIBBUILTIN(move_if_noexcept, "v&v&", "zfncTh", "utility", CXX_LANG) + // Annotation function BUILTIN(__builtin_annotation, "v.", "tn") diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -6586,6 +6586,15 @@ "explicitly moving variable of type %0 to itself">, InGroup, DefaultIgnore; +def err_builtin_move_forward_unsupported : Error< + "unsupported signature for %q0">; +def err_use_of_unaddressable_function : Error< + "taking address of non-addressable standard library function">; +// FIXME: This should also be in -Wc++23-compat once we have it. +def warn_cxx20_compat_use_of_unaddressable_function : Warning< + "taking address of non-addressable standard library function " + "is incompatible with C++20">, InGroup; + def warn_redundant_move_on_return : Warning< "redundant move in return statement">, InGroup, DefaultIgnore; diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -8127,6 +8127,7 @@ bool VisitVarDecl(const Expr *E, const VarDecl *VD); bool VisitUnaryPreIncDec(const UnaryOperator *UO); + bool VisitCallExpr(const CallExpr *E); bool VisitDeclRefExpr(const DeclRefExpr *E); bool VisitPredefinedExpr(const PredefinedExpr *E) { return Success(E); } bool VisitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *E); @@ -8292,6 +8293,20 @@ return Success(*V, E); } +bool LValueExprEvaluator::VisitCallExpr(const CallExpr *E) { + switch (unsigned BuiltinOp = E->getBuiltinCallee()) { + case Builtin::BIas_const: + case Builtin::BIforward: + case Builtin::BImove: + case Builtin::BImove_if_noexcept: + if (cast(E->getCalleeDecl())->isConstexpr()) + return Visit(E->getArg(0)); + break; + } + + return ExprEvaluatorBaseTy::VisitCallExpr(E); +} + bool LValueExprEvaluator::VisitMaterializeTemporaryExpr( const MaterializeTemporaryExpr *E) { // Walk through the expression to find the materialized temporary itself. @@ -9070,6 +9085,8 @@ bool PointerExprEvaluator::VisitBuiltinCallExpr(const CallExpr *E, unsigned BuiltinOp) { switch (BuiltinOp) { + case Builtin::BIaddressof: + case Builtin::BI__addressof: case Builtin::BI__builtin_addressof: return evaluateLValue(E->getArg(0), Result); case Builtin::BI__builtin_assume_aligned: { diff --git a/clang/lib/Analysis/BodyFarm.cpp b/clang/lib/Analysis/BodyFarm.cpp --- a/clang/lib/Analysis/BodyFarm.cpp +++ b/clang/lib/Analysis/BodyFarm.cpp @@ -20,6 +20,7 @@ #include "clang/AST/ExprObjC.h" #include "clang/AST/NestedNameSpecifier.h" #include "clang/Analysis/CodeInjector.h" +#include "clang/Basic/Builtins.h" #include "clang/Basic/OperatorKinds.h" #include "llvm/ADT/StringSwitch.h" #include "llvm/Support/Debug.h" @@ -86,6 +87,9 @@ ImplicitCastExpr *makeImplicitCast(const Expr *Arg, QualType Ty, CastKind CK = CK_LValueToRValue); + /// Create a cast to reference type. + CastExpr *makeReferenceCast(const Expr *Arg, QualType Ty); + /// Create an Objective-C bool literal. ObjCBoolLiteralExpr *makeObjCBool(bool Val); @@ -173,6 +177,16 @@ /* FPFeatures */ FPOptionsOverride()); } +CastExpr *ASTMaker::makeReferenceCast(const Expr *Arg, QualType Ty) { + assert(Ty->isReferenceType()); + return CXXStaticCastExpr::Create( + C, Ty.getNonReferenceType(), + Ty->isLValueReferenceType() ? VK_LValue : VK_XValue, CK_NoOp, + const_cast(Arg), /*CXXCastPath=*/nullptr, + /*Written=*/C.getTrivialTypeSourceInfo(Ty), FPOptionsOverride(), + SourceLocation(), SourceLocation(), SourceRange()); +} + Expr *ASTMaker::makeIntegralCast(const Expr *Arg, QualType Ty) { if (Arg->getType() == Ty) return const_cast(Arg); @@ -296,6 +310,22 @@ /*FPFeatures=*/FPOptionsOverride()); } +/// Create a fake body for 'std::move' or 'std::forward'. This is just: +/// +/// \code +/// return static_cast(param); +/// \endcode +static Stmt *create_std_move_forward(ASTContext &C, const FunctionDecl *D) { + LLVM_DEBUG(llvm::dbgs() << "Generating body for std::move / std::forward\n"); + + ASTMaker M(C); + + QualType ReturnType = D->getType()->castAs()->getReturnType(); + Expr *Param = M.makeDeclRefExpr(D->getParamDecl(0)); + Expr *Cast = M.makeReferenceCast(Param, ReturnType); + return M.makeReturn(Cast); +} + /// Create a fake body for std::call_once. /// Emulates the following function body: /// @@ -681,8 +711,20 @@ FunctionFarmer FF; - if (Name.startswith("OSAtomicCompareAndSwap") || - Name.startswith("objc_atomicCompareAndSwap")) { + if (unsigned BuiltinID = D->getBuiltinID()) { + switch (BuiltinID) { + case Builtin::BIas_const: + case Builtin::BIforward: + case Builtin::BImove: + case Builtin::BImove_if_noexcept: + FF = create_std_move_forward; + break; + default: + FF = nullptr; + break; + } + } else if (Name.startswith("OSAtomicCompareAndSwap") || + Name.startswith("objc_atomicCompareAndSwap")) { FF = create_OSAtomicCompareAndSwap; } else if (Name == "call_once" && D->getDeclContext()->isStdNamespace()) { FF = create_call_once; diff --git a/clang/lib/Basic/Builtins.cpp b/clang/lib/Basic/Builtins.cpp --- a/clang/lib/Basic/Builtins.cpp +++ b/clang/lib/Basic/Builtins.cpp @@ -48,18 +48,22 @@ } bool Builtin::Context::isBuiltinFunc(llvm::StringRef FuncName) { - for (unsigned i = Builtin::NotBuiltin + 1; i != Builtin::FirstTSBuiltin; ++i) - if (FuncName.equals(BuiltinInfo[i].Name)) + bool InStdNamespace = FuncName.consume_front("std-"); + for (unsigned i = Builtin::NotBuiltin + 1; i != Builtin::FirstTSBuiltin; + ++i) { + if (FuncName.equals(BuiltinInfo[i].Name) && + (bool)strchr(BuiltinInfo[i].Attributes, 'z') == InStdNamespace) return strchr(BuiltinInfo[i].Attributes, 'f') != nullptr; + } return false; } -bool Builtin::Context::builtinIsSupported(const Builtin::Info &BuiltinInfo, - const LangOptions &LangOpts) { +/// Is this builtin supported according to the given language options? +static bool builtinIsSupported(const Builtin::Info &BuiltinInfo, + const LangOptions &LangOpts) { bool BuiltinsUnsupported = - (LangOpts.NoBuiltin || LangOpts.isNoBuiltinFunc(BuiltinInfo.Name)) && - strchr(BuiltinInfo.Attributes, 'f'); + LangOpts.NoBuiltin && strchr(BuiltinInfo.Attributes, 'f') != nullptr; bool CorBuiltinsUnsupported = !LangOpts.Coroutines && (BuiltinInfo.Langs & COR_LANG); bool MathBuiltinsUnsupported = @@ -111,6 +115,19 @@ for (unsigned i = 0, e = AuxTSRecords.size(); i != e; ++i) Table.get(AuxTSRecords[i].Name) .setBuiltinID(i + Builtin::FirstTSBuiltin + TSRecords.size()); + + // Step #4: Unregister any builtins specified by -fno-builtin-foo. + for (llvm::StringRef Name : LangOpts.NoBuiltinFuncs) { + bool InStdNamespace = Name.consume_front("std-"); + auto NameIt = Table.find(Name); + if (NameIt != Table.end()) { + unsigned ID = NameIt->second->getBuiltinID(); + if (ID != Builtin::NotBuiltin && isPredefinedLibFunction(ID) && + isInStdNamespace(ID) == InStdNamespace) { + Table.get(Name).setBuiltinID(Builtin::NotBuiltin); + } + } + } } unsigned Builtin::Context::getRequiredVectorWidth(unsigned ID) const { @@ -190,8 +207,7 @@ } bool Builtin::Context::canBeRedeclared(unsigned ID) const { - return ID == Builtin::NotBuiltin || - ID == Builtin::BI__va_start || - (!hasReferenceArgsOrResult(ID) && - !hasCustomTypechecking(ID)); + return ID == Builtin::NotBuiltin || ID == Builtin::BI__va_start || + (!hasReferenceArgsOrResult(ID) && !hasCustomTypechecking(ID)) || + isInStdNamespace(ID); } diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp --- a/clang/lib/CodeGen/CGBuiltin.cpp +++ b/clang/lib/CodeGen/CGBuiltin.cpp @@ -2271,8 +2271,9 @@ ReturnValueSlot ReturnValue) { const FunctionDecl *FD = GD.getDecl()->getAsFunction(); // See if we can constant fold this builtin. If so, don't emit it at all. + // TODO: Extend this handling to all builtin calls that we can constant-fold. Expr::EvalResult Result; - if (E->EvaluateAsRValue(Result, CGM.getContext()) && + if (E->isPRValue() && E->EvaluateAsRValue(Result, CGM.getContext()) && !Result.hasSideEffects()) { if (Result.Val.isInt()) return RValue::get(llvm::ConstantInt::get(getLLVMContext(), @@ -4566,6 +4567,8 @@ return RValue::get(Carry); } + case Builtin::BIaddressof: + case Builtin::BI__addressof: case Builtin::BI__builtin_addressof: return RValue::get(EmitLValue(E->getArg(0)).getPointer(*this)); case Builtin::BI__builtin_function_start: @@ -4725,6 +4728,12 @@ } break; + // C++ std:: builtins. + case Builtin::BImove: + case Builtin::BImove_if_noexcept: + case Builtin::BIforward: + case Builtin::BIas_const: + return RValue::get(EmitLValue(E->getArg(0)).getPointer(*this)); case Builtin::BI__GetExceptionInfo: { if (llvm::GlobalVariable *GV = CGM.getCXXABI().getThrowInfo(FD->getParamDecl(0)->getType())) diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp --- a/clang/lib/CodeGen/CGCall.cpp +++ b/clang/lib/CodeGen/CGCall.cpp @@ -1805,6 +1805,8 @@ if (AttrOnCallSite) { // Attributes that should go on the call site only. + // FIXME: Look for 'BuiltinAttr' on the function rather than re-checking + // the -fno-builtin-foo list. if (!CodeGenOpts.SimplifyLibCalls || LangOpts.isNoBuiltinFunc(Name)) FuncAttrs.addAttribute(llvm::Attribute::NoBuiltin); if (!CodeGenOpts.TrapFuncName.empty()) diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -2130,6 +2130,32 @@ TheCall->setType(Context.VoidPtrTy); break; + case Builtin::BIaddressof: + case Builtin::BI__addressof: + case Builtin::BIforward: + case Builtin::BImove: + case Builtin::BImove_if_noexcept: + case Builtin::BIas_const: { + // These are all expected to be of the form + // T &/&&/* f(U &/&&) + // where T and U only differ in qualification. + if (checkArgCount(*this, TheCall, 1)) + return ExprError(); + QualType Param = FDecl->getParamDecl(0)->getType(); + QualType Result = FDecl->getReturnType(); + bool ReturnsPointer = BuiltinID == Builtin::BIaddressof || + BuiltinID == Builtin::BI__addressof; + if (!(Param->isReferenceType() && + (ReturnsPointer ? Result->isPointerType() + : Result->isReferenceType()) && + Context.hasSameUnqualifiedType(Param->getPointeeType(), + Result->getPointeeType()))) { + Diag(TheCall->getBeginLoc(), diag::err_builtin_move_forward_unsupported) + << FDecl; + return ExprError(); + } + break; + } // OpenCL v2.0, s6.13.16 - Pipe functions case Builtin::BIread_pipe: case Builtin::BIwrite_pipe: diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -9269,6 +9269,32 @@ return S; } +/// Determine whether a declaration matches a known function in namespace std. +static bool isStdBuiltin(ASTContext &Ctx, FunctionDecl *FD, + unsigned BuiltinID) { + switch (BuiltinID) { + case Builtin::BI__GetExceptionInfo: + // No type checking whatsoever. + return Ctx.getTargetInfo().getCXXABI().isMicrosoft(); + + case Builtin::BIaddressof: + case Builtin::BI__addressof: + case Builtin::BIforward: + case Builtin::BImove: + case Builtin::BImove_if_noexcept: + case Builtin::BIas_const: { + // Ensure that we don't treat the algorithm + // OutputIt std::move(InputIt, InputIt, OutputIt) + // as the builtin std::move. + const auto *FPT = FD->getType()->castAs(); + return FPT->getNumParams() == 1 && !FPT->isVariadic(); + } + + default: + return false; + } +} + NamedDecl* Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, DeclContext *DC, TypeSourceInfo *TInfo, LookupResult &Previous, @@ -10121,28 +10147,30 @@ // If this is the first declaration of a library builtin function, add // attributes as appropriate. - if (!D.isRedeclaration() && - NewFD->getDeclContext()->getRedeclContext()->isFileContext()) { + if (!D.isRedeclaration()) { if (IdentifierInfo *II = Previous.getLookupName().getAsIdentifierInfo()) { if (unsigned BuiltinID = II->getBuiltinID()) { - if (NewFD->getLanguageLinkage() == CLanguageLinkage) { - // Validate the type matches unless this builtin is specified as - // matching regardless of its declared type. - if (Context.BuiltinInfo.allowTypeMismatch(BuiltinID)) { - NewFD->addAttr(BuiltinAttr::CreateImplicit(Context, BuiltinID)); - } else { - ASTContext::GetBuiltinTypeError Error; - LookupNecessaryTypesForBuiltin(S, BuiltinID); - QualType BuiltinType = Context.GetBuiltinType(BuiltinID, Error); - - if (!Error && !BuiltinType.isNull() && - Context.hasSameFunctionTypeIgnoringExceptionSpec( - NewFD->getType(), BuiltinType)) + bool InStdNamespace = Context.BuiltinInfo.isInStdNamespace(BuiltinID); + if (!InStdNamespace && + NewFD->getDeclContext()->getRedeclContext()->isFileContext()) { + if (NewFD->getLanguageLinkage() == CLanguageLinkage) { + // Validate the type matches unless this builtin is specified as + // matching regardless of its declared type. + if (Context.BuiltinInfo.allowTypeMismatch(BuiltinID)) { NewFD->addAttr(BuiltinAttr::CreateImplicit(Context, BuiltinID)); + } else { + ASTContext::GetBuiltinTypeError Error; + LookupNecessaryTypesForBuiltin(S, BuiltinID); + QualType BuiltinType = Context.GetBuiltinType(BuiltinID, Error); + + if (!Error && !BuiltinType.isNull() && + Context.hasSameFunctionTypeIgnoringExceptionSpec( + NewFD->getType(), BuiltinType)) + NewFD->addAttr(BuiltinAttr::CreateImplicit(Context, BuiltinID)); + } } - } else if (BuiltinID == Builtin::BI__GetExceptionInfo && - Context.getTargetInfo().getCXXABI().isMicrosoft()) { - // FIXME: We should consider this a builtin only in the std namespace. + } else if (InStdNamespace && NewFD->isInStdNamespace() && + isStdBuiltin(Context, NewFD, BuiltinID)) { NewFD->addAttr(BuiltinAttr::CreateImplicit(Context, BuiltinID)); } } diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -3395,7 +3395,7 @@ case Decl::Function: { if (unsigned BID = cast(VD)->getBuiltinID()) { - if (!Context.BuiltinInfo.isPredefinedLibFunction(BID)) { + if (!Context.BuiltinInfo.isDirectlyAddressable(BID)) { type = Context.BuiltinFnTy; valueKind = VK_PRValue; break; @@ -20528,7 +20528,8 @@ auto *DRE = dyn_cast(E->IgnoreParenImpCasts()); if (DRE) { auto *FD = cast(DRE->getDecl()); - if (FD->getBuiltinID() == Builtin::BI__noop) { + unsigned BuiltinID = FD->getBuiltinID(); + if (BuiltinID == Builtin::BI__noop) { E = ImpCastExprToType(E, Context.getPointerType(FD->getType()), CK_BuiltinFnToFnPtr) .get(); @@ -20536,6 +20537,36 @@ VK_PRValue, SourceLocation(), FPOptionsOverride()); } + + if (Context.BuiltinInfo.isInStdNamespace(BuiltinID)) { + // Any use of these other than a direct call is ill-formed as of C++20, + // because they are not addressable functions. In earlier language + // modes, warn and force an instantiation of the real body. + Diag(E->getBeginLoc(), + getLangOpts().CPlusPlus20 + ? diag::err_use_of_unaddressable_function + : diag::warn_cxx20_compat_use_of_unaddressable_function); + if (FD->isImplicitlyInstantiable()) { + // Require a definition here because a normal attempt at + // instantiation for a builtin will be ignored, and we won't try + // again later. We assume that the definition of the template + // precedes this use. + InstantiateFunctionDefinition(E->getBeginLoc(), FD, + /*Recursive=*/false, + /*DefinitionRequired=*/true, + /*AtEndOfTU=*/false); + } + // Produce a properly-typed reference to the function. + CXXScopeSpec SS; + SS.Adopt(DRE->getQualifierLoc()); + TemplateArgumentListInfo TemplateArgs; + DRE->copyTemplateArgumentsInto(TemplateArgs); + return BuildDeclRefExpr( + FD, FD->getType(), VK_LValue, DRE->getNameInfo(), + DRE->hasQualifier() ? &SS : nullptr, DRE->getFoundDecl(), + DRE->getTemplateKeywordLoc(), + DRE->hasExplicitTemplateArgs() ? &TemplateArgs : nullptr); + } } Diag(E->getBeginLoc(), diag::err_builtin_fn_use); diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp --- a/clang/lib/Sema/SemaExprCXX.cpp +++ b/clang/lib/Sema/SemaExprCXX.cpp @@ -4236,6 +4236,14 @@ return ExprError(); From = FixOverloadedFunctionReference(From, Found, Fn); + + // We might get back another placeholder expression if we resolved to a + // builtin. + ExprResult Checked = CheckPlaceholderExpr(From); + if (Checked.isInvalid()) + return ExprError(); + + From = Checked.get(); FromType = From->getType(); } diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp --- a/clang/lib/Sema/SemaInit.cpp +++ b/clang/lib/Sema/SemaInit.cpp @@ -8215,6 +8215,10 @@ CurInit = S.FixOverloadedFunctionReference(CurInit, Step->Function.FoundDecl, Step->Function.Function); + // We might get back another placeholder expression if we resolved to a + // builtin. + if (!CurInit.isInvalid()) + CurInit = S.CheckPlaceholderExpr(CurInit.get()); break; case SK_CastDerivedToBasePRValue: diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp --- a/clang/lib/Sema/SemaOverload.cpp +++ b/clang/lib/Sema/SemaOverload.cpp @@ -1747,13 +1747,6 @@ "Non-address-of operator for overloaded function expression"); FromType = S.Context.getPointerType(FromType); } - - // Check that we've computed the proper type after overload resolution. - // FIXME: FixOverloadedFunctionReference has side-effects; we shouldn't - // be calling it from within an NDEBUG block. - assert(S.Context.hasSameType( - FromType, - S.FixOverloadedFunctionReference(From, AccessPair, Fn)->getType())); } else { return false; } @@ -15188,10 +15181,9 @@ if (SubExpr == UnOp->getSubExpr()) return UnOp; - return UnaryOperator::Create( - Context, SubExpr, UO_AddrOf, Context.getPointerType(SubExpr->getType()), - VK_PRValue, OK_Ordinary, UnOp->getOperatorLoc(), false, - CurFPFeatureOverrides()); + // FIXME: This can't currently fail, but in principle it could. + return CreateBuiltinUnaryOp(UnOp->getOperatorLoc(), UO_AddrOf, SubExpr) + .get(); } if (UnresolvedLookupExpr *ULE = dyn_cast(E)) { @@ -15202,10 +15194,20 @@ TemplateArgs = &TemplateArgsBuffer; } - DeclRefExpr *DRE = - BuildDeclRefExpr(Fn, Fn->getType(), VK_LValue, ULE->getNameInfo(), - ULE->getQualifierLoc(), Found.getDecl(), - ULE->getTemplateKeywordLoc(), TemplateArgs); + QualType Type = Fn->getType(); + ExprValueKind ValueKind = getLangOpts().CPlusPlus ? VK_LValue : VK_PRValue; + + // FIXME: Duplicated from BuildDeclarationNameExpr. + if (unsigned BID = Fn->getBuiltinID()) { + if (!Context.BuiltinInfo.isDirectlyAddressable(BID)) { + Type = Context.BuiltinFnTy; + ValueKind = VK_PRValue; + } + } + + DeclRefExpr *DRE = BuildDeclRefExpr( + Fn, Type, ValueKind, ULE->getNameInfo(), ULE->getQualifierLoc(), + Found.getDecl(), ULE->getTemplateKeywordLoc(), TemplateArgs); DRE->setHadMultipleCandidates(ULE->getNumDecls() > 1); return DRE; } diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp --- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp @@ -4771,6 +4771,12 @@ if (TSK == TSK_ExplicitSpecialization) return; + // Never implicitly instantiate a builtin; we don't actually need a function + // body. + if (Function->getBuiltinID() && TSK == TSK_ImplicitInstantiation && + !DefinitionRequired) + return; + // Don't instantiate a definition if we already have one. const FunctionDecl *ExistingDefn = nullptr; if (Function->isDefined(ExistingDefn, diff --git a/clang/test/Analysis/inner-pointer.cpp b/clang/test/Analysis/inner-pointer.cpp --- a/clang/test/Analysis/inner-pointer.cpp +++ b/clang/test/Analysis/inner-pointer.cpp @@ -379,7 +379,7 @@ const char *c; std::string s; c = s.c_str(); - addressof(s); + (void)addressof(s); consume(c); // no-warning } diff --git a/clang/test/Analysis/use-after-move.cpp b/clang/test/Analysis/use-after-move.cpp --- a/clang/test/Analysis/use-after-move.cpp +++ b/clang/test/Analysis/use-after-move.cpp @@ -244,7 +244,7 @@ A a; if (i == 1) { // peaceful-note 2 {{'i' is not equal to 1}} // peaceful-note@-1 2 {{Taking false branch}} - std::move(a); + (void)std::move(a); } if (i == 2) { // peaceful-note 2 {{'i' is not equal to 2}} // peaceful-note@-1 2 {{Taking false branch}} @@ -494,7 +494,7 @@ // Moves of global variables are not reported. A global_a; void globalVariablesTest() { - std::move(global_a); + (void)std::move(global_a); global_a.foo(); // no-warning } diff --git a/clang/test/CodeGenCXX/builtin-std-move.cpp b/clang/test/CodeGenCXX/builtin-std-move.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGenCXX/builtin-std-move.cpp @@ -0,0 +1,66 @@ +// RUN: %clang_cc1 -triple=x86_64-linux-gnu -emit-llvm -o - -std=c++17 %s | FileCheck %s --implicit-check-not=@_ZSt4move + +namespace std { + template constexpr T &&move(T &val) { return static_cast(val); } + template constexpr T &&move_if_noexcept(T &val); + template constexpr T &&forward(T &val); + template constexpr const T &as_const(T &val); + + // Not the builtin. + template T move(U source, U source_end, T dest); +} + +class T {}; +extern "C" void take(T &&); +extern "C" void take_lval(const T &); + +T a; + +// Check emission of a constant-evaluated call. +// CHECK-DAG: @move_a = constant ptr @a +T &&move_a = std::move(a); +// CHECK-DAG: @move_if_noexcept_a = constant ptr @a +T &&move_if_noexcept_a = std::move_if_noexcept(a); +// CHECK-DAG: @forward_a = constant ptr @a +T &forward_a = std::forward(a); + +// Check emission of a non-constant call. +// CHECK-LABEL: define {{.*}} void @test +extern "C" void test(T &t) { + // CHECK: store ptr %{{.*}}, ptr %[[T_REF:[^,]*]] + // CHECK: %0 = load ptr, ptr %[[T_REF]] + // CHECK: call void @take(ptr {{.*}} %0) + take(std::move(t)); + // CHECK: %1 = load ptr, ptr %[[T_REF]] + // CHECK: call void @take(ptr {{.*}} %1) + take(std::move_if_noexcept(t)); + // CHECK: %2 = load ptr, ptr %[[T_REF]] + // CHECK: call void @take(ptr {{.*}} %2) + take(std::forward(t)); + // CHECK: %3 = load ptr, ptr %[[T_REF]] + // CHECK: call void @take_lval(ptr {{.*}} %3) + take_lval(std::as_const(t)); + + // CHECK: call {{.*}} @_ZSt4moveI1TS0_ET_T0_S2_S1_ + std::move(t, t, t); +} + +// CHECK: declare {{.*}} @_ZSt4moveI1TS0_ET_T0_S2_S1_ + +// Check that we instantiate and emit if the address is taken. +// CHECK-LABEL: define {{.*}} @use_address +extern "C" void *use_address() { + // CHECK: ret {{.*}} @_ZSt4moveIiEOT_RS0_ + return (void*)&std::move; +} + +// CHECK: define {{.*}} ptr @_ZSt4moveIiEOT_RS0_(ptr + +extern "C" void take_const_int_rref(const int &&); +// CHECK-LABEL: define {{.*}} @move_const_int( +extern "C" void move_const_int() { + // CHECK: store i32 5, ptr %[[N_ADDR:[^,]*]] + const int n = 5; + // CHECK: call {{.*}} @take_const_int_rref(ptr {{.*}} %[[N_ADDR]]) + take_const_int_rref(std::move(n)); +} diff --git a/clang/test/CodeGenCXX/builtins.cpp b/clang/test/CodeGenCXX/builtins.cpp --- a/clang/test/CodeGenCXX/builtins.cpp +++ b/clang/test/CodeGenCXX/builtins.cpp @@ -30,6 +30,24 @@ return __builtin_addressof(b ? s : t); } +namespace std { template T *addressof(T &); } + +// CHECK: define {{.*}} @_Z13std_addressofbR1SS0_( +S *std_addressof(bool b, S &s, S &t) { + // CHECK: %[[LVALUE:.*]] = phi + // CHECK: ret {{.*}}* %[[LVALUE]] + return std::addressof(b ? s : t); +} + +namespace std { template T *__addressof(T &); } + +// CHECK: define {{.*}} @_Z15std___addressofbR1SS0_( +S *std___addressof(bool b, S &s, S &t) { + // CHECK: %[[LVALUE:.*]] = phi + // CHECK: ret {{.*}}* %[[LVALUE]] + return std::__addressof(b ? s : t); +} + extern "C" int __builtin_abs(int); // #1 long __builtin_abs(long); // #2 extern "C" int __builtin_abs(int); // #3 diff --git a/clang/test/CodeGenCXX/microsoft-abi-throw.cpp b/clang/test/CodeGenCXX/microsoft-abi-throw.cpp --- a/clang/test/CodeGenCXX/microsoft-abi-throw.cpp +++ b/clang/test/CodeGenCXX/microsoft-abi-throw.cpp @@ -1,5 +1,4 @@ // RUN: %clang_cc1 -no-opaque-pointers -emit-llvm -o - -triple=i386-pc-win32 -std=c++11 %s -fcxx-exceptions -fms-extensions | FileCheck %s -// RUN: %clang_cc1 -no-opaque-pointers -emit-llvm -o - -triple=i386-pc-win32 -std=c++11 %s -fcxx-exceptions -fms-extensions -DSTD | FileCheck %s // CHECK-DAG: @"??_R0?AUY@@@8" = linkonce_odr global %rtti.TypeDescriptor7 { i8** @"??_7type_info@@6B@", i8* null, [8 x i8] c".?AUY@@\00" }, comdat // CHECK-DAG: @"_CT??_R0?AUY@@@8??0Y@@QAE@ABU0@@Z8" = linkonce_odr unnamed_addr constant %eh.CatchableType { i32 4, i8* bitcast (%rtti.TypeDescriptor7* @"??_R0?AUY@@@8" to i8*), i32 0, i32 -1, i32 0, i32 8, i8* bitcast (%struct.Y* (%struct.Y*, %struct.Y*, i32)* @"??0Y@@QAE@ABU0@@Z" to i8*) }, section ".xdata", comdat @@ -134,15 +133,10 @@ throw nullptr; } -#ifdef STD namespace std { template void *__GetExceptionInfo(T); } -#else -template -void *__GetExceptionInfo(T); -#endif using namespace std; void *GetExceptionInfo_test0() { diff --git a/clang/test/SemaCXX/builtin-std-move-nobuiltin.cpp b/clang/test/SemaCXX/builtin-std-move-nobuiltin.cpp new file mode 100644 --- /dev/null +++ b/clang/test/SemaCXX/builtin-std-move-nobuiltin.cpp @@ -0,0 +1,20 @@ +// RUN: %clang_cc1 -std=c++20 -verify %s -DBUILTIN=builtin +// RUN: %clang_cc1 -std=c++20 -verify %s -DBUILTIN=nobuiltin -fno-builtin +// RUN: %clang_cc1 -std=c++20 -verify %s -DBUILTIN=nobuiltin -fno-builtin-std-move -fno-builtin-std-move_if_noexcept -fno-builtin-std-forward +// RUN: %clang_cc1 -std=c++20 -verify %s -DBUILTIN=nobuiltin -ffreestanding +// expected-no-diagnostics + +int nobuiltin; + +namespace std { + template constexpr T &&move(T &x) { return (T&&)nobuiltin; } + template constexpr T &&move_if_noexcept(T &x) { return (T&&)nobuiltin; } + template constexpr T &&forward(T &x) { return (T&&)nobuiltin; } +} + +template constexpr T *addr(T &&r) { return &r; } + +int builtin; +static_assert(addr(std::move(builtin)) == &BUILTIN); +static_assert(addr(std::move_if_noexcept(builtin)) == &BUILTIN); +static_assert(addr(std::forward(builtin)) == &BUILTIN); diff --git a/clang/test/SemaCXX/builtin-std-move.cpp b/clang/test/SemaCXX/builtin-std-move.cpp new file mode 100644 --- /dev/null +++ b/clang/test/SemaCXX/builtin-std-move.cpp @@ -0,0 +1,126 @@ +// RUN: %clang_cc1 -std=c++17 -verify %s +// RUN: %clang_cc1 -std=c++17 -verify %s -DNO_CONSTEXPR +// RUN: %clang_cc1 -std=c++20 -verify %s + +namespace std { +#ifndef NO_CONSTEXPR +#define CONSTEXPR constexpr +#else +#define CONSTEXPR +#endif + + template CONSTEXPR T &&move(T &x) { + static_assert(T::moveable, "instantiated move"); // expected-error {{no member named 'moveable' in 'B'}} + // expected-error@-1 {{no member named 'moveable' in 'C'}} + return static_cast(x); + } + + // Unrelated move functions are not the builtin. + template CONSTEXPR int move(T, T) { return 5; } + + template struct ref { using type = T&; }; + template struct ref { using type = T&&; }; + + template CONSTEXPR auto move_if_noexcept(T &x) -> typename ref(x)))>::type { + static_assert(T::moveable, "instantiated move_if_noexcept"); // expected-error {{no member named 'moveable' in 'B'}} + return static_cast(x)))>::type>(x); + } + + template struct remove_reference { using type = T; }; + template struct remove_reference { using type = T; }; + template struct remove_reference { using type = T; }; + + template CONSTEXPR T &&forward(typename remove_reference::type &x) { + static_assert(T::moveable, "instantiated forward"); // expected-error {{no member named 'moveable' in 'B'}} + // expected-error@-1 {{no member named 'moveable' in 'C'}} + return static_cast(x); + } + + template CONSTEXPR const T &as_const(T &x) { + static_assert(T::moveable, "instantiated as_const"); // expected-error {{no member named 'moveable' in 'B'}} + return x; + } + + template CONSTEXPR T *addressof(T &x) { + static_assert(T::moveable, "instantiated addressof"); // expected-error {{no member named 'moveable' in 'B'}} + return __builtin_addressof(x); + } + + template CONSTEXPR T *__addressof(T &x) { + static_assert(T::moveable, "instantiated __addressof"); // expected-error {{no member named 'moveable' in 'B'}} + return __builtin_addressof(x); + } +} + +// Note: this doesn't have a 'moveable' member. Instantiation of the above +// functions will fail if it's attempted. +struct A {}; +constexpr bool f(A a) { // #f + A &&move = std::move(a); // #call + A &&move_if_noexcept = std::move_if_noexcept(a); + A &&forward1 = std::forward(a); + A &forward2 = std::forward(a); + const A &as_const = std::as_const(a); + A *addressof = std::addressof(a); + A *addressof2 = std::__addressof(a); + return &move == &a && &move_if_noexcept == &a && + &forward1 == &a && &forward2 == &a && + &as_const == &a && addressof == &a && + addressof2 == &a && std::move(a, a) == 5; +} + +#ifndef NO_CONSTEXPR +static_assert(f({}), "should be constexpr"); +#else +// expected-error@#f {{never produces a constant expression}} +// expected-note@#call {{}} +#endif + +struct B {}; +B &&(*pMove)(B&) = std::move; // #1 expected-note {{instantiation of}} +B &&(*pMoveIfNoexcept)(B&) = &std::move_if_noexcept; // #2 expected-note {{instantiation of}} +B &&(*pForward)(B&) = &std::forward; // #3 expected-note {{instantiation of}} +const B &(*pAsConst)(B&) = &std::as_const; // #4 expected-note {{instantiation of}} +B *(*pAddressof)(B&) = &std::addressof; // #5 expected-note {{instantiation of}} +B *(*pUnderUnderAddressof)(B&) = &std::__addressof; // #6 expected-note {{instantiation of}} +int (*pUnrelatedMove)(B, B) = std::move; + +struct C {}; +C &&(&rMove)(C&) = std::move; // #7 expected-note {{instantiation of}} +C &&(&rForward)(C&) = std::forward; // #8 expected-note {{instantiation of}} +int (&rUnrelatedMove)(B, B) = std::move; + +#if __cplusplus <= 201703L +// expected-warning@#1 {{non-addressable}} +// expected-warning@#2 {{non-addressable}} +// expected-warning@#3 {{non-addressable}} +// expected-warning@#4 {{non-addressable}} +// expected-warning@#5 {{non-addressable}} +// expected-warning@#6 {{non-addressable}} +// expected-warning@#7 {{non-addressable}} +// expected-warning@#8 {{non-addressable}} +#else +// expected-error@#1 {{non-addressable}} +// expected-error@#2 {{non-addressable}} +// expected-error@#3 {{non-addressable}} +// expected-error@#4 {{non-addressable}} +// expected-error@#5 {{non-addressable}} +// expected-error@#6 {{non-addressable}} +// expected-error@#7 {{non-addressable}} +// expected-error@#8 {{non-addressable}} +#endif + +void attribute_const() { + int n; + std::move(n); // expected-warning {{ignoring return value}} + std::move_if_noexcept(n); // expected-warning {{ignoring return value}} + std::forward(n); // expected-warning {{ignoring return value}} + std::addressof(n); // expected-warning {{ignoring return value}} + std::__addressof(n); // expected-warning {{ignoring return value}} + std::as_const(n); // expected-warning {{ignoring return value}} +} + +namespace std { + template int move(T); +} +int bad_signature = std::move(0); // expected-error {{unsupported signature for 'std::move'}} diff --git a/clang/test/SemaCXX/unqualified-std-call-fixits.cpp b/clang/test/SemaCXX/unqualified-std-call-fixits.cpp --- a/clang/test/SemaCXX/unqualified-std-call-fixits.cpp +++ b/clang/test/SemaCXX/unqualified-std-call-fixits.cpp @@ -6,9 +6,9 @@ namespace std { -void move(auto &&a) {} +int &&move(auto &&a) { return a; } -void forward(auto &a) {} +int &&forward(auto &a) { return a; } } // namespace std @@ -16,8 +16,8 @@ void f() { int i = 0; - move(i); // expected-warning {{unqualified call to std::move}} - // CHECK: {{^}} std:: - forward(i); // expected-warning {{unqualified call to std::forward}} - // CHECK: {{^}} std:: + (void)move(i); // expected-warning {{unqualified call to std::move}} + // CHECK: {{^}} (void)std::move + (void)forward(i); // expected-warning {{unqualified call to std::forward}} + // CHECK: {{^}} (void)std::forward } diff --git a/clang/test/SemaCXX/unqualified-std-call.cpp b/clang/test/SemaCXX/unqualified-std-call.cpp --- a/clang/test/SemaCXX/unqualified-std-call.cpp +++ b/clang/test/SemaCXX/unqualified-std-call.cpp @@ -1,17 +1,17 @@ -// RUN: %clang_cc1 -fsyntax-only -verify -Wall -std=c++11 %s +// RUN: %clang_cc1 -fsyntax-only -verify -Wall -std=c++11 %s -Wno-unused-value namespace std { template void dummy(T &&) {} template -void move(T &&) {} +T &&move(T &&x) { return x; } template void move(T &&, U &&) {} inline namespace __1 { template -void forward(T &) {} +T &forward(T &x) { return x; } } // namespace __1 struct foo {}; diff --git a/clang/test/SemaCXX/warn-consumed-analysis.cpp b/clang/test/SemaCXX/warn-consumed-analysis.cpp --- a/clang/test/SemaCXX/warn-consumed-analysis.cpp +++ b/clang/test/SemaCXX/warn-consumed-analysis.cpp @@ -953,12 +953,12 @@ namespace std { void move(); template - void move(T&&); + T &&move(T&); namespace __1 { void move(); template - void move(T&&); + T &&move(T&); } } @@ -971,7 +971,7 @@ void test() { x.move(); std::move(); - std::move(x); + std::move(x); // expected-warning {{ignoring return value}} std::__1::move(); std::__1::move(x); } diff --git a/clang/unittests/Analysis/ExprMutationAnalyzerTest.cpp b/clang/unittests/Analysis/ExprMutationAnalyzerTest.cpp --- a/clang/unittests/Analysis/ExprMutationAnalyzerTest.cpp +++ b/clang/unittests/Analysis/ExprMutationAnalyzerTest.cpp @@ -1444,7 +1444,7 @@ TEST(ExprMutationAnalyzerTest, ReproduceFailureMinimal) { const std::string Reproducer = "namespace std {" - "template T forward(T & A) { return static_cast(A); }" + "template T &forward(T &A) { return static_cast(A); }" "template struct __bind {" " T f;" " template __bind(T v, V &&) : f(forward(v)) {}"