Skip to content

Commit 64c045e

Browse files
committed
Treat std::move, forward, and move_if_noexcept as builtins.
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. Reviewed By: aaron.ballman Differential Revision: https://reviews.llvm.org/D123345
1 parent 73f5d7d commit 64c045e

25 files changed

+456
-76
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
@@ -230,7 +230,10 @@ C2x Feature Support
230230
C++ Language Changes in Clang
231231
-----------------------------
232232

233-
- ...
233+
- Improved ``-O0`` code generation for calls to ``std::move``, ``std::forward``,
234+
and ``std::move_if_noexcept``. These are now treated as compiler builtins and
235+
implemented directly, rather than instantiating the definition from the
236+
standard library.
234237

235238
C++20 Feature Support
236239
^^^^^^^^^^^^^^^^^^^^^

clang/include/clang/Basic/Builtins.def

+15-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,8 @@
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 C++ standard library function in (possibly-versioned)
107+
// namespace std; implied by STDBUILTIN
104108
// FIXME: gcc has nonnull
105109

106110
#if defined(BUILTIN) && !defined(LIBBUILTIN)
@@ -111,6 +115,10 @@
111115
# define LANGBUILTIN(ID, TYPE, ATTRS, BUILTIN_LANG) BUILTIN(ID, TYPE, ATTRS)
112116
#endif
113117

118+
#if defined(BUILTIN) && !defined(STDBUILTIN)
119+
# define STDBUILTIN(ID, TYPE, ATTRS, HEADER) LIBBUILTIN(ID, TYPE, "zf" ATTRS, HEADER, CXX_LANG)
120+
#endif
121+
114122
// Standard libc/libm functions:
115123
BUILTIN(__builtin_atan2 , "ddd" , "Fne")
116124
BUILTIN(__builtin_atan2f, "fff" , "Fne")
@@ -919,7 +927,7 @@ LANGBUILTIN(__exception_info, "v*", "n", ALL_MS_LANGUAGES)
919927
LANGBUILTIN(_exception_info, "v*", "n", ALL_MS_LANGUAGES)
920928
LANGBUILTIN(__abnormal_termination, "i", "n", ALL_MS_LANGUAGES)
921929
LANGBUILTIN(_abnormal_termination, "i", "n", ALL_MS_LANGUAGES)
922-
LANGBUILTIN(__GetExceptionInfo, "v*.", "ntu", ALL_MS_LANGUAGES)
930+
LANGBUILTIN(__GetExceptionInfo, "v*.", "zntu", ALL_MS_LANGUAGES)
923931
LANGBUILTIN(_InterlockedAnd8, "ccD*c", "n", ALL_MS_LANGUAGES)
924932
LANGBUILTIN(_InterlockedAnd16, "ssD*s", "n", ALL_MS_LANGUAGES)
925933
LANGBUILTIN(_InterlockedAnd, "NiNiD*Ni", "n", ALL_MS_LANGUAGES)
@@ -1543,6 +1551,11 @@ LIBBUILTIN(_Block_object_assign, "vv*vC*iC", "f", "Blocks.h", ALL_LANGUAGES)
15431551
LIBBUILTIN(_Block_object_dispose, "vvC*iC", "f", "Blocks.h", ALL_LANGUAGES)
15441552
// FIXME: Also declare NSConcreteGlobalBlock and NSConcreteStackBlock.
15451553

1554+
// C++11
1555+
STDBUILTIN(move, "v&v&", "ncTh", "utility")
1556+
STDBUILTIN(move_if_noexcept, "v&v&", "ncTh", "utility")
1557+
STDBUILTIN(forward, "v&v&", "ncTh", "utility")
1558+
15461559
// Annotation function
15471560
BUILTIN(__builtin_annotation, "v.", "tn")
15481561

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
@@ -6586,6 +6586,15 @@ def warn_self_move : Warning<
65866586
"explicitly moving variable of type %0 to itself">,
65876587
InGroup<SelfMove>, DefaultIgnore;
65886588

6589+
def err_builtin_move_forward_unsupported : Error<
6590+
"unsupported signature for '%select{std::move|std::forward}0'">;
6591+
def err_use_of_unaddressable_function : Error<
6592+
"taking address of non-addressable standard library function">;
6593+
// FIXME: This should also be in -Wc++23-compat once we have it.
6594+
def warn_cxx20_compat_use_of_unaddressable_function : Warning<
6595+
"taking address of non-addressable standard library function "
6596+
"is incompatible with C++20">, InGroup<CXX20Compat>;
6597+
65896598
def warn_redundant_move_on_return : Warning<
65906599
"redundant move in return statement">,
65916600
InGroup<RedundantMove>, DefaultIgnore;

clang/lib/AST/ExprConstant.cpp

+14
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,19 @@ 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 (unsigned BuiltinOp = E->getBuiltinCallee()) {
8298+
case Builtin::BImove:
8299+
case Builtin::BImove_if_noexcept:
8300+
case Builtin::BIforward:
8301+
if (cast<FunctionDecl>(E->getCalleeDecl())->isConstexpr())
8302+
return Visit(E->getArg(0));
8303+
break;
8304+
}
8305+
8306+
return ExprEvaluatorBaseTy::VisitCallExpr(E);
8307+
}
8308+
82958309
bool LValueExprEvaluator::VisitMaterializeTemporaryExpr(
82968310
const MaterializeTemporaryExpr *E) {
82978311
// Walk through the expression to find the materialized temporary itself.

clang/lib/Analysis/BodyFarm.cpp

+43-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,19 @@ 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::BImove:
717+
case Builtin::BImove_if_noexcept:
718+
case Builtin::BIforward:
719+
FF = create_std_move_forward;
720+
break;
721+
default:
722+
FF = nullptr;
723+
break;
724+
}
725+
} else if (Name.startswith("OSAtomicCompareAndSwap") ||
726+
Name.startswith("objc_atomicCompareAndSwap")) {
686727
FF = create_OSAtomicCompareAndSwap;
687728
} else if (Name == "call_once" && D->getDeclContext()->isStdNamespace()) {
688729
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

+5
Original file line numberDiff line numberDiff line change
@@ -4725,6 +4725,11 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
47254725
}
47264726
break;
47274727

4728+
// C++ std:: builtins.
4729+
case Builtin::BImove:
4730+
case Builtin::BImove_if_noexcept:
4731+
case Builtin::BIforward:
4732+
return RValue::get(EmitLValue(E->getArg(0)).getPointer(*this));
47284733
case Builtin::BI__GetExceptionInfo: {
47294734
if (llvm::GlobalVariable *GV =
47304735
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/Sema/SemaChecking.cpp

+12
Original file line numberDiff line numberDiff line change
@@ -2130,6 +2130,18 @@ Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID,
21302130

21312131
TheCall->setType(Context.VoidPtrTy);
21322132
break;
2133+
case Builtin::BImove:
2134+
case Builtin::BImove_if_noexcept:
2135+
case Builtin::BIforward:
2136+
if (checkArgCount(*this, TheCall, 1))
2137+
return ExprError();
2138+
if (!Context.hasSameUnqualifiedType(TheCall->getType(),
2139+
TheCall->getArg(0)->getType())) {
2140+
Diag(TheCall->getBeginLoc(), diag::err_builtin_move_forward_unsupported)
2141+
<< (BuiltinID == Builtin::BIforward);
2142+
return ExprError();
2143+
}
2144+
break;
21332145
// OpenCL v2.0, s6.13.16 - Pipe functions
21342146
case Builtin::BIread_pipe:
21352147
case Builtin::BIwrite_pipe:

0 commit comments

Comments
 (0)