Skip to content

Commit 72315d0

Browse files
committed
Treat std::move, forward, etc. as builtins.
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.
1 parent e3cd8fe commit 72315d0

31 files changed

+623
-80
lines changed

clang/docs/CommandGuide/clang.rst

+18-2
Original file line numberDiff line numberDiff line change
@@ -252,8 +252,24 @@ Language Selection and Mode Options
252252

253253
.. option:: -fno-builtin
254254

255-
Disable special handling and optimizations of builtin functions like
256-
:c:func:`strlen` and :c:func:`malloc`.
255+
Disable special handling and optimizations of well-known library functions,
256+
like :c:func:`strlen` and :c:func:`malloc`.
257+
258+
.. option:: -fno-builtin-<function>
259+
260+
Disable special handling and optimizations for the specific library function.
261+
For example, ``-fno-builtin-strlen`` removes any special handling for the
262+
:c:func:`strlen` library function.
263+
264+
.. option:: -fno-builtin-std-<function>
265+
266+
Disable special handling and optimizations for the specific C++ standard
267+
library function in namespace ``std``. For example,
268+
``-fno-builtin-std-move_if_noexcept`` removes any special handling for the
269+
:cpp:func:`std::move_if_noexcept` library function.
270+
271+
For C standard library functions that the C++ standard library also provides
272+
in namespace ``std``, use :option:`-fno-builtin-\<function\>` instead.
257273

258274
.. option:: -fmath-errno
259275

clang/docs/ReleaseNotes.rst

+4-1
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,10 @@ C2x Feature Support
250250
C++ Language Changes in Clang
251251
-----------------------------
252252

253-
- ...
253+
- Improved ``-O0`` code generation for calls to ``std::move``, ``std::forward``,
254+
``std::move_if_noexcept``, ``std::addressof``, and ``std::as_const``. These
255+
are now treated as compiler builtins and implemented directly, rather than
256+
instantiating the definition from the standard library.
254257

255258
C++20 Feature Support
256259
^^^^^^^^^^^^^^^^^^^^^

clang/include/clang/Basic/Builtins.def

+14-2
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,9 @@
8181
// builtin even if type doesn't match signature, and don't warn if we
8282
// can't be sure the type is right
8383
// F -> this is a libc/libm function with a '__builtin_' prefix added.
84-
// f -> this is a libc/libm function without the '__builtin_' prefix.
84+
// f -> this is a libc/libm function without a '__builtin_' prefix, or with
85+
// 'z', a C++ standard library function in namespace std::. This builtin
86+
// is disableable by '-fno-builtin-foo' / '-fno-builtin-std-foo'.
8587
// h -> this function requires a specific header or an explicit declaration.
8688
// i -> this is a runtime library implemented function without the
8789
// '__builtin_' prefix. It will be implemented in compiler-rt or libgcc.
@@ -101,6 +103,7 @@
101103
// V:N: -> requires vectors of at least N bits to be legal
102104
// C<N,M_0,...,M_k> -> callback behavior: argument N is called with argument
103105
// M_0, ..., M_k as payload
106+
// z -> this is a function in (possibly-versioned) namespace std
104107
// FIXME: gcc has nonnull
105108

106109
#if defined(BUILTIN) && !defined(LIBBUILTIN)
@@ -919,7 +922,7 @@ LANGBUILTIN(__exception_info, "v*", "n", ALL_MS_LANGUAGES)
919922
LANGBUILTIN(_exception_info, "v*", "n", ALL_MS_LANGUAGES)
920923
LANGBUILTIN(__abnormal_termination, "i", "n", ALL_MS_LANGUAGES)
921924
LANGBUILTIN(_abnormal_termination, "i", "n", ALL_MS_LANGUAGES)
922-
LANGBUILTIN(__GetExceptionInfo, "v*.", "ntu", ALL_MS_LANGUAGES)
925+
LANGBUILTIN(__GetExceptionInfo, "v*.", "zntu", ALL_MS_LANGUAGES)
923926
LANGBUILTIN(_InterlockedAnd8, "ccD*c", "n", ALL_MS_LANGUAGES)
924927
LANGBUILTIN(_InterlockedAnd16, "ssD*s", "n", ALL_MS_LANGUAGES)
925928
LANGBUILTIN(_InterlockedAnd, "NiNiD*Ni", "n", ALL_MS_LANGUAGES)
@@ -1543,6 +1546,15 @@ LIBBUILTIN(_Block_object_assign, "vv*vC*iC", "f", "Blocks.h", ALL_LANGUAGES)
15431546
LIBBUILTIN(_Block_object_dispose, "vvC*iC", "f", "Blocks.h", ALL_LANGUAGES)
15441547
// FIXME: Also declare NSConcreteGlobalBlock and NSConcreteStackBlock.
15451548

1549+
// C++ standard library builtins in namespace 'std'.
1550+
LIBBUILTIN(addressof, "v*v&", "zfncTh", "memory", CXX_LANG)
1551+
// Synonym for addressof used internally by libstdc++.
1552+
LANGBUILTIN(__addressof, "v*v&", "zfncT", CXX_LANG)
1553+
LIBBUILTIN(as_const, "v&v&", "zfncTh", "utility", CXX_LANG)
1554+
LIBBUILTIN(forward, "v&v&", "zfncTh", "utility", CXX_LANG)
1555+
LIBBUILTIN(move, "v&v&", "zfncTh", "utility", CXX_LANG)
1556+
LIBBUILTIN(move_if_noexcept, "v&v&", "zfncTh", "utility", CXX_LANG)
1557+
15461558
// Annotation function
15471559
BUILTIN(__builtin_annotation, "v.", "tn")
15481560

clang/include/clang/Basic/Builtins.h

+21-4
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,10 @@ class Context {
138138
/// Determines whether this builtin is a predefined libc/libm
139139
/// function, such as "malloc", where we know the signature a
140140
/// priori.
141+
/// In C, such functions behave as if they are predeclared,
142+
/// possibly with a warning on first use. In Objective-C and C++,
143+
/// they do not, but they are recognized as builtins once we see
144+
/// a declaration.
141145
bool isPredefinedLibFunction(unsigned ID) const {
142146
return strchr(getRecord(ID).Attributes, 'f') != nullptr;
143147
}
@@ -156,6 +160,23 @@ class Context {
156160
return strchr(getRecord(ID).Attributes, 'i') != nullptr;
157161
}
158162

163+
/// Determines whether this builtin is a C++ standard library function
164+
/// that lives in (possibly-versioned) namespace std, possibly a template
165+
/// specialization, where the signature is determined by the standard library
166+
/// declaration.
167+
bool isInStdNamespace(unsigned ID) const {
168+
return strchr(getRecord(ID).Attributes, 'z') != nullptr;
169+
}
170+
171+
/// Determines whether this builtin can have its address taken with no
172+
/// special action required.
173+
bool isDirectlyAddressable(unsigned ID) const {
174+
// Most standard library functions can have their addresses taken. C++
175+
// standard library functions formally cannot in C++20 onwards, and when
176+
// we allow it, we need to ensure we instantiate a definition.
177+
return isPredefinedLibFunction(ID) && !isInStdNamespace(ID);
178+
}
179+
159180
/// Determines whether this builtin has custom typechecking.
160181
bool hasCustomTypechecking(unsigned ID) const {
161182
return strchr(getRecord(ID).Attributes, 't') != nullptr;
@@ -237,10 +258,6 @@ class Context {
237258
private:
238259
const Info &getRecord(unsigned ID) const;
239260

240-
/// Is this builtin supported according to the given language options?
241-
bool builtinIsSupported(const Builtin::Info &BuiltinInfo,
242-
const LangOptions &LangOpts);
243-
244261
/// Helper function for isPrintfLike and isScanfLike.
245262
bool isLike(unsigned ID, unsigned &FormatIdx, bool &HasVAListArg,
246263
const char *Fmt) const;

clang/include/clang/Basic/DiagnosticSemaKinds.td

+9
Original file line numberDiff line numberDiff line change
@@ -6590,6 +6590,15 @@ def warn_self_move : Warning<
65906590
"explicitly moving variable of type %0 to itself">,
65916591
InGroup<SelfMove>, DefaultIgnore;
65926592

6593+
def err_builtin_move_forward_unsupported : Error<
6594+
"unsupported signature for %q0">;
6595+
def err_use_of_unaddressable_function : Error<
6596+
"taking address of non-addressable standard library function">;
6597+
// FIXME: This should also be in -Wc++23-compat once we have it.
6598+
def warn_cxx20_compat_use_of_unaddressable_function : Warning<
6599+
"taking address of non-addressable standard library function "
6600+
"is incompatible with C++20">, InGroup<CXX20Compat>;
6601+
65936602
def warn_redundant_move_on_return : Warning<
65946603
"redundant move in return statement">,
65956604
InGroup<RedundantMove>, DefaultIgnore;

clang/lib/AST/ExprConstant.cpp

+17
Original file line numberDiff line numberDiff line change
@@ -8127,6 +8127,7 @@ class LValueExprEvaluator
81278127
bool VisitVarDecl(const Expr *E, const VarDecl *VD);
81288128
bool VisitUnaryPreIncDec(const UnaryOperator *UO);
81298129

8130+
bool VisitCallExpr(const CallExpr *E);
81308131
bool VisitDeclRefExpr(const DeclRefExpr *E);
81318132
bool VisitPredefinedExpr(const PredefinedExpr *E) { return Success(E); }
81328133
bool VisitMaterializeTemporaryExpr(const MaterializeTemporaryExpr *E);
@@ -8292,6 +8293,20 @@ bool LValueExprEvaluator::VisitVarDecl(const Expr *E, const VarDecl *VD) {
82928293
return Success(*V, E);
82938294
}
82948295

8296+
bool LValueExprEvaluator::VisitCallExpr(const CallExpr *E) {
8297+
switch (E->getBuiltinCallee()) {
8298+
case Builtin::BIas_const:
8299+
case Builtin::BIforward:
8300+
case Builtin::BImove:
8301+
case Builtin::BImove_if_noexcept:
8302+
if (cast<FunctionDecl>(E->getCalleeDecl())->isConstexpr())
8303+
return Visit(E->getArg(0));
8304+
break;
8305+
}
8306+
8307+
return ExprEvaluatorBaseTy::VisitCallExpr(E);
8308+
}
8309+
82958310
bool LValueExprEvaluator::VisitMaterializeTemporaryExpr(
82968311
const MaterializeTemporaryExpr *E) {
82978312
// Walk through the expression to find the materialized temporary itself.
@@ -9070,6 +9085,8 @@ static bool isOneByteCharacterType(QualType T) {
90709085
bool PointerExprEvaluator::VisitBuiltinCallExpr(const CallExpr *E,
90719086
unsigned BuiltinOp) {
90729087
switch (BuiltinOp) {
9088+
case Builtin::BIaddressof:
9089+
case Builtin::BI__addressof:
90739090
case Builtin::BI__builtin_addressof:
90749091
return evaluateLValue(E->getArg(0), Result);
90759092
case Builtin::BI__builtin_assume_aligned: {

clang/lib/Analysis/BodyFarm.cpp

+44-2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include "clang/AST/ExprObjC.h"
2121
#include "clang/AST/NestedNameSpecifier.h"
2222
#include "clang/Analysis/CodeInjector.h"
23+
#include "clang/Basic/Builtins.h"
2324
#include "clang/Basic/OperatorKinds.h"
2425
#include "llvm/ADT/StringSwitch.h"
2526
#include "llvm/Support/Debug.h"
@@ -86,6 +87,9 @@ class ASTMaker {
8687
ImplicitCastExpr *makeImplicitCast(const Expr *Arg, QualType Ty,
8788
CastKind CK = CK_LValueToRValue);
8889

90+
/// Create a cast to reference type.
91+
CastExpr *makeReferenceCast(const Expr *Arg, QualType Ty);
92+
8993
/// Create an Objective-C bool literal.
9094
ObjCBoolLiteralExpr *makeObjCBool(bool Val);
9195

@@ -173,6 +177,16 @@ ImplicitCastExpr *ASTMaker::makeImplicitCast(const Expr *Arg, QualType Ty,
173177
/* FPFeatures */ FPOptionsOverride());
174178
}
175179

180+
CastExpr *ASTMaker::makeReferenceCast(const Expr *Arg, QualType Ty) {
181+
assert(Ty->isReferenceType());
182+
return CXXStaticCastExpr::Create(
183+
C, Ty.getNonReferenceType(),
184+
Ty->isLValueReferenceType() ? VK_LValue : VK_XValue, CK_NoOp,
185+
const_cast<Expr *>(Arg), /*CXXCastPath=*/nullptr,
186+
/*Written=*/C.getTrivialTypeSourceInfo(Ty), FPOptionsOverride(),
187+
SourceLocation(), SourceLocation(), SourceRange());
188+
}
189+
176190
Expr *ASTMaker::makeIntegralCast(const Expr *Arg, QualType Ty) {
177191
if (Arg->getType() == Ty)
178192
return const_cast<Expr*>(Arg);
@@ -296,6 +310,22 @@ static CallExpr *create_call_once_lambda_call(ASTContext &C, ASTMaker M,
296310
/*FPFeatures=*/FPOptionsOverride());
297311
}
298312

313+
/// Create a fake body for 'std::move' or 'std::forward'. This is just:
314+
///
315+
/// \code
316+
/// return static_cast<return_type>(param);
317+
/// \endcode
318+
static Stmt *create_std_move_forward(ASTContext &C, const FunctionDecl *D) {
319+
LLVM_DEBUG(llvm::dbgs() << "Generating body for std::move / std::forward\n");
320+
321+
ASTMaker M(C);
322+
323+
QualType ReturnType = D->getType()->castAs<FunctionType>()->getReturnType();
324+
Expr *Param = M.makeDeclRefExpr(D->getParamDecl(0));
325+
Expr *Cast = M.makeReferenceCast(Param, ReturnType);
326+
return M.makeReturn(Cast);
327+
}
328+
299329
/// Create a fake body for std::call_once.
300330
/// Emulates the following function body:
301331
///
@@ -681,8 +711,20 @@ Stmt *BodyFarm::getBody(const FunctionDecl *D) {
681711

682712
FunctionFarmer FF;
683713

684-
if (Name.startswith("OSAtomicCompareAndSwap") ||
685-
Name.startswith("objc_atomicCompareAndSwap")) {
714+
if (unsigned BuiltinID = D->getBuiltinID()) {
715+
switch (BuiltinID) {
716+
case Builtin::BIas_const:
717+
case Builtin::BIforward:
718+
case Builtin::BImove:
719+
case Builtin::BImove_if_noexcept:
720+
FF = create_std_move_forward;
721+
break;
722+
default:
723+
FF = nullptr;
724+
break;
725+
}
726+
} else if (Name.startswith("OSAtomicCompareAndSwap") ||
727+
Name.startswith("objc_atomicCompareAndSwap")) {
686728
FF = create_OSAtomicCompareAndSwap;
687729
} else if (Name == "call_once" && D->getDeclContext()->isStdNamespace()) {
688730
FF = create_call_once;

clang/lib/Basic/Builtins.cpp

+26-10
Original file line numberDiff line numberDiff line change
@@ -48,18 +48,22 @@ void Builtin::Context::InitializeTarget(const TargetInfo &Target,
4848
}
4949

5050
bool Builtin::Context::isBuiltinFunc(llvm::StringRef FuncName) {
51-
for (unsigned i = Builtin::NotBuiltin + 1; i != Builtin::FirstTSBuiltin; ++i)
52-
if (FuncName.equals(BuiltinInfo[i].Name))
51+
bool InStdNamespace = FuncName.consume_front("std-");
52+
for (unsigned i = Builtin::NotBuiltin + 1; i != Builtin::FirstTSBuiltin;
53+
++i) {
54+
if (FuncName.equals(BuiltinInfo[i].Name) &&
55+
(bool)strchr(BuiltinInfo[i].Attributes, 'z') == InStdNamespace)
5356
return strchr(BuiltinInfo[i].Attributes, 'f') != nullptr;
57+
}
5458

5559
return false;
5660
}
5761

58-
bool Builtin::Context::builtinIsSupported(const Builtin::Info &BuiltinInfo,
59-
const LangOptions &LangOpts) {
62+
/// Is this builtin supported according to the given language options?
63+
static bool builtinIsSupported(const Builtin::Info &BuiltinInfo,
64+
const LangOptions &LangOpts) {
6065
bool BuiltinsUnsupported =
61-
(LangOpts.NoBuiltin || LangOpts.isNoBuiltinFunc(BuiltinInfo.Name)) &&
62-
strchr(BuiltinInfo.Attributes, 'f');
66+
LangOpts.NoBuiltin && strchr(BuiltinInfo.Attributes, 'f') != nullptr;
6367
bool CorBuiltinsUnsupported =
6468
!LangOpts.Coroutines && (BuiltinInfo.Langs & COR_LANG);
6569
bool MathBuiltinsUnsupported =
@@ -111,6 +115,19 @@ void Builtin::Context::initializeBuiltins(IdentifierTable &Table,
111115
for (unsigned i = 0, e = AuxTSRecords.size(); i != e; ++i)
112116
Table.get(AuxTSRecords[i].Name)
113117
.setBuiltinID(i + Builtin::FirstTSBuiltin + TSRecords.size());
118+
119+
// Step #4: Unregister any builtins specified by -fno-builtin-foo.
120+
for (llvm::StringRef Name : LangOpts.NoBuiltinFuncs) {
121+
bool InStdNamespace = Name.consume_front("std-");
122+
auto NameIt = Table.find(Name);
123+
if (NameIt != Table.end()) {
124+
unsigned ID = NameIt->second->getBuiltinID();
125+
if (ID != Builtin::NotBuiltin && isPredefinedLibFunction(ID) &&
126+
isInStdNamespace(ID) == InStdNamespace) {
127+
Table.get(Name).setBuiltinID(Builtin::NotBuiltin);
128+
}
129+
}
130+
}
114131
}
115132

116133
unsigned Builtin::Context::getRequiredVectorWidth(unsigned ID) const {
@@ -190,8 +207,7 @@ bool Builtin::Context::performsCallback(unsigned ID,
190207
}
191208

192209
bool Builtin::Context::canBeRedeclared(unsigned ID) const {
193-
return ID == Builtin::NotBuiltin ||
194-
ID == Builtin::BI__va_start ||
195-
(!hasReferenceArgsOrResult(ID) &&
196-
!hasCustomTypechecking(ID));
210+
return ID == Builtin::NotBuiltin || ID == Builtin::BI__va_start ||
211+
(!hasReferenceArgsOrResult(ID) && !hasCustomTypechecking(ID)) ||
212+
isInStdNamespace(ID);
197213
}

clang/lib/CodeGen/CGBuiltin.cpp

+10-1
Original file line numberDiff line numberDiff line change
@@ -2271,8 +2271,9 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
22712271
ReturnValueSlot ReturnValue) {
22722272
const FunctionDecl *FD = GD.getDecl()->getAsFunction();
22732273
// See if we can constant fold this builtin. If so, don't emit it at all.
2274+
// TODO: Extend this handling to all builtin calls that we can constant-fold.
22742275
Expr::EvalResult Result;
2275-
if (E->EvaluateAsRValue(Result, CGM.getContext()) &&
2276+
if (E->isPRValue() && E->EvaluateAsRValue(Result, CGM.getContext()) &&
22762277
!Result.hasSideEffects()) {
22772278
if (Result.Val.isInt())
22782279
return RValue::get(llvm::ConstantInt::get(getLLVMContext(),
@@ -4566,6 +4567,8 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
45664567

45674568
return RValue::get(Carry);
45684569
}
4570+
case Builtin::BIaddressof:
4571+
case Builtin::BI__addressof:
45694572
case Builtin::BI__builtin_addressof:
45704573
return RValue::get(EmitLValue(E->getArg(0)).getPointer(*this));
45714574
case Builtin::BI__builtin_function_start:
@@ -4725,6 +4728,12 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
47254728
}
47264729
break;
47274730

4731+
// C++ std:: builtins.
4732+
case Builtin::BImove:
4733+
case Builtin::BImove_if_noexcept:
4734+
case Builtin::BIforward:
4735+
case Builtin::BIas_const:
4736+
return RValue::get(EmitLValue(E->getArg(0)).getPointer(*this));
47284737
case Builtin::BI__GetExceptionInfo: {
47294738
if (llvm::GlobalVariable *GV =
47304739
CGM.getCXXABI().getThrowInfo(FD->getParamDecl(0)->getType()))

clang/lib/CodeGen/CGCall.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -1805,6 +1805,8 @@ void CodeGenModule::getDefaultFunctionAttributes(StringRef Name,
18051805

18061806
if (AttrOnCallSite) {
18071807
// Attributes that should go on the call site only.
1808+
// FIXME: Look for 'BuiltinAttr' on the function rather than re-checking
1809+
// the -fno-builtin-foo list.
18081810
if (!CodeGenOpts.SimplifyLibCalls || LangOpts.isNoBuiltinFunc(Name))
18091811
FuncAttrs.addAttribute(llvm::Attribute::NoBuiltin);
18101812
if (!CodeGenOpts.TrapFuncName.empty())

clang/lib/CodeGen/CGExpr.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -1175,6 +1175,8 @@ Address CodeGenFunction::EmitPointerWithAlignment(const Expr *E,
11751175
switch (Call->getBuiltinCallee()) {
11761176
default:
11771177
break;
1178+
case Builtin::BIaddressof:
1179+
case Builtin::BI__addressof:
11781180
case Builtin::BI__builtin_addressof: {
11791181
LValue LV = EmitLValue(Call->getArg(0));
11801182
if (BaseInfo) *BaseInfo = LV.getBaseInfo();

0 commit comments

Comments
 (0)