Skip to content

Commit

Permalink
Treat std::move, forward, etc. as builtins.
Browse files Browse the repository at this point in the history
This is extended to all `std::` functions that take a reference to a
value and return a reference (or pointer) to that same value: `move`,
`forward`, `move_if_noexcept`, `as_const`, `addressof`, and the
libstdc++-specific function `__addressof`.

We still require these functions to be declared before they can be used,
but don't instantiate their definitions unless their addresses are
taken. Instead, code generation, constant evaluation, and static
analysis are given direct knowledge of their effect.

This change aims to reduce various costs associated with these functions
-- per-instantiation memory costs, compile time and memory costs due to
creating out-of-line copies and inlining them, code size at -O0, and so
on -- so that they are not substantially more expensive than a cast.
Most of these improvements are very small, but I measured a 3% decrease
in -O0 object file size for a simple C++ source file using the standard
library after this change.

We now automatically infer the `const` and `nothrow` attributes on these
now-builtin functions, in particular meaning that we get a warning for
an unused call to one of these functions.

In C++20 onwards, we disallow taking the addresses of these functions,
per the C++20 "addressable function" rule. In earlier language modes, a
compatibility warning is produced but the address can still be taken.

The same infrastructure is extended to the existing MSVC builtin
`__GetExceptionInfo`, which is now only recognized in namespace `std`
like it always should have been.

This is a re-commit of
  fc30901,
  a571f82,
  64c045e, and
  de6ddae,
and reverts aa643f4.
This change also includes a workaround for users using libc++ 3.1 and
earlier (!!), as apparently happens on AIX, where std::move sometimes
returns by value.

Reviewed By: aaron.ballman

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

Revert "Fixup D123950 to address revert of D123345"

This reverts commit aa643f4.
  • Loading branch information
zygoloid committed Apr 21, 2022
1 parent e3cd8fe commit 72315d0
Show file tree
Hide file tree
Showing 31 changed files with 623 additions and 80 deletions.
20 changes: 18 additions & 2 deletions clang/docs/CommandGuide/clang.rst
Original file line number Diff line number Diff line change
Expand Up @@ -252,8 +252,24 @@ Language Selection and Mode Options

.. 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-<function>

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-<function>

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-\<function\>` instead.

.. option:: -fmath-errno

Expand Down
5 changes: 4 additions & 1 deletion clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,10 @@ C2x Feature Support
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
^^^^^^^^^^^^^^^^^^^^^
Expand Down
16 changes: 14 additions & 2 deletions clang/include/clang/Basic/Builtins.def
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -101,6 +103,7 @@
// V:N: -> requires vectors of at least N bits to be legal
// C<N,M_0,...,M_k> -> 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)
Expand Down Expand Up @@ -919,7 +922,7 @@ LANGBUILTIN(__exception_info, "v*", "n", ALL_MS_LANGUAGES)
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)
Expand Down Expand Up @@ -1543,6 +1546,15 @@ LIBBUILTIN(_Block_object_assign, "vv*vC*iC", "f", "Blocks.h", ALL_LANGUAGES)
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")

Expand Down
25 changes: 21 additions & 4 deletions clang/include/clang/Basic/Builtins.h
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@ class Context {
/// 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;
}
Expand All @@ -156,6 +160,23 @@ class Context {
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;
Expand Down Expand Up @@ -237,10 +258,6 @@ class Context {
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;
Expand Down
9 changes: 9 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -6590,6 +6590,15 @@ def warn_self_move : Warning<
"explicitly moving variable of type %0 to itself">,
InGroup<SelfMove>, 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<CXX20Compat>;

def warn_redundant_move_on_return : Warning<
"redundant move in return statement">,
InGroup<RedundantMove>, DefaultIgnore;
Expand Down
17 changes: 17 additions & 0 deletions clang/lib/AST/ExprConstant.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8127,6 +8127,7 @@ class LValueExprEvaluator
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);
Expand Down Expand Up @@ -8292,6 +8293,20 @@ bool LValueExprEvaluator::VisitVarDecl(const Expr *E, const VarDecl *VD) {
return Success(*V, E);
}

bool LValueExprEvaluator::VisitCallExpr(const CallExpr *E) {
switch (E->getBuiltinCallee()) {
case Builtin::BIas_const:
case Builtin::BIforward:
case Builtin::BImove:
case Builtin::BImove_if_noexcept:
if (cast<FunctionDecl>(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.
Expand Down Expand Up @@ -9070,6 +9085,8 @@ static bool isOneByteCharacterType(QualType T) {
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: {
Expand Down
46 changes: 44 additions & 2 deletions clang/lib/Analysis/BodyFarm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -86,6 +87,9 @@ class ASTMaker {
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);

Expand Down Expand Up @@ -173,6 +177,16 @@ ImplicitCastExpr *ASTMaker::makeImplicitCast(const Expr *Arg, QualType Ty,
/* 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<Expr *>(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<Expr*>(Arg);
Expand Down Expand Up @@ -296,6 +310,22 @@ static CallExpr *create_call_once_lambda_call(ASTContext &C, ASTMaker M,
/*FPFeatures=*/FPOptionsOverride());
}

/// Create a fake body for 'std::move' or 'std::forward'. This is just:
///
/// \code
/// return static_cast<return_type>(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<FunctionType>()->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:
///
Expand Down Expand Up @@ -681,8 +711,20 @@ Stmt *BodyFarm::getBody(const FunctionDecl *D) {

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;
Expand Down
36 changes: 26 additions & 10 deletions clang/lib/Basic/Builtins.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,18 +48,22 @@ void Builtin::Context::InitializeTarget(const TargetInfo &Target,
}

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 =
Expand Down Expand Up @@ -111,6 +115,19 @@ void Builtin::Context::initializeBuiltins(IdentifierTable &Table,
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 {
Expand Down Expand Up @@ -190,8 +207,7 @@ bool Builtin::Context::performsCallback(unsigned ID,
}

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);
}
11 changes: 10 additions & 1 deletion clang/lib/CodeGen/CGBuiltin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2271,8 +2271,9 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
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(),
Expand Down Expand Up @@ -4566,6 +4567,8 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,

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:
Expand Down Expand Up @@ -4725,6 +4728,12 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
}
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()))
Expand Down
2 changes: 2 additions & 0 deletions clang/lib/CodeGen/CGCall.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1805,6 +1805,8 @@ void CodeGenModule::getDefaultFunctionAttributes(StringRef Name,

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())
Expand Down
2 changes: 2 additions & 0 deletions clang/lib/CodeGen/CGExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1175,6 +1175,8 @@ Address CodeGenFunction::EmitPointerWithAlignment(const Expr *E,
switch (Call->getBuiltinCallee()) {
default:
break;
case Builtin::BIaddressof:
case Builtin::BI__addressof:
case Builtin::BI__builtin_addressof: {
LValue LV = EmitLValue(Call->getArg(0));
if (BaseInfo) *BaseInfo = LV.getBaseInfo();
Expand Down
Loading

0 comments on commit 72315d0

Please sign in to comment.