Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[clang] Add clang::behaves_like_std(...) attribute #76596

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions clang/include/clang/Basic/Attr.td
Original file line number Diff line number Diff line change
Expand Up @@ -1922,6 +1922,15 @@ def Convergent : InheritableAttr {
let SimpleHandler = 1;
}

def BehavesLikeStd : InheritableAttr {
let Spellings = [Clang<"behaves_like_std">];
let Subjects = SubjectList<[Function]>;
let Args = [StringArgument<"StdFunction">];
let LangOpts = [CPlusPlus];
let PragmaAttributeSupport = 0;
let Documentation = [BehavesLikeStdDocs];
}

def NoInline : DeclOrStmtAttr {
let Spellings = [CustomKeyword<"__noinline__">, GCC<"noinline">,
CXX11<"clang", "noinline">, C23<"clang", "noinline">,
Expand Down
29 changes: 29 additions & 0 deletions clang/include/clang/Basic/AttrDocs.td
Original file line number Diff line number Diff line change
Expand Up @@ -577,6 +577,35 @@ effect applies only to a specific function pointer. For example:
}];
}

def BehavesLikeStdDocs : Documentation {
let Category = DocCatFunction;
let Content = [{
This function attribute can be used to tag functions that behave like `std` functions.
This allows custom STL libraries in non-freestanding environments to get the same benefits
as the `std` functions that are treated like builtins without conflicting with the `std` declarations
and without including costly `std` headers.

This attribute currently supports all `std` functions that are implicitly treated as builtins which include
`std::addressof`, `std::forward`, `std::forward_like`, `std::move`, `std::move_if_noexcept`, and `std::as_const`.

.. code-block:: c

namespace MySTL {
template<class T>
[[clang::behaves_like_std("move")]] constexpr remove_reference_t<T>&& move(T &&t) noexcept;
}

template<class T>
[[clang::behaves_like_std("move")]] constexpr remove_reference_t<T>&& myMove(T &&t) noexcept;

void example(std::string a, std::string b) {
foo(MySTL::move(a));
foo(myMove(b));
}

}];
}

def NoInlineDocs : Documentation {
let Category = DocCatFunction;
let Content = [{
Expand Down
2 changes: 2 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -3132,6 +3132,8 @@ def err_attribute_no_member_function : Error<
def err_attribute_parameter_types : Error<
"%0 attribute parameter types do not match: parameter %1 of function %2 has type %3, "
"but parameter %4 of function %5 has type %6">;
def err_attribute_invalid_std_builtin : Error<
"not a valid std builtin for attribute %0">;

def err_attribute_too_many_arguments : Error<
"%0 attribute takes no more than %1 argument%s1">;
Expand Down
47 changes: 36 additions & 11 deletions clang/lib/Sema/SemaDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9753,14 +9753,11 @@ static Scope *getTagInjectionScope(Scope *S, const LangOptions &LangOpts) {
return S;
}

/// Determine whether a declaration matches a known function in namespace std.
static bool isStdBuiltin(ASTContext &Ctx, FunctionDecl *FD,
unsigned BuiltinID) {
/// Determine whether a declaration matches a known cast function in namespace
/// std.
static bool isStdCastBuiltin(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:
Expand All @@ -9774,12 +9771,23 @@ static bool isStdBuiltin(ASTContext &Ctx, FunctionDecl *FD,
const auto *FPT = FD->getType()->castAs<FunctionProtoType>();
return FPT->getNumParams() == 1 && !FPT->isVariadic();
}

default:
return false;
}
}

/// 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();
default:
return isStdCastBuiltin(Ctx, FD, BuiltinID);
}
}

NamedDecl*
Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, DeclContext *DC,
TypeSourceInfo *TInfo, LookupResult &Previous,
Expand Down Expand Up @@ -10704,11 +10712,28 @@ Sema::ActOnFunctionDeclarator(Scope *S, Declarator &D, DeclContext *DC,
// If this is the first declaration of a library builtin function, add
// attributes as appropriate.
if (!D.isRedeclaration()) {
if (IdentifierInfo *II = Previous.getLookupName().getAsIdentifierInfo()) {
IdentifierInfo *II = nullptr;
if (auto *GA = NewFD->getAttr<BehavesLikeStdAttr>()) {
auto iter = PP.getIdentifierTable().find(GA->getStdFunction());
if (iter != PP.getIdentifierTable().end())
II = iter->getValue();
else
Diag(NewFD->getLocation(), diag::err_attribute_invalid_std_builtin)
<< GA;
} else {
II = Previous.getLookupName().getAsIdentifierInfo();
}
if (II) {
if (unsigned BuiltinID = II->getBuiltinID()) {
bool InStdNamespace = Context.BuiltinInfo.isInStdNamespace(BuiltinID);
if (!InStdNamespace &&
NewFD->getDeclContext()->getRedeclContext()->isFileContext()) {
if (NewFD->hasAttr<BehavesLikeStdAttr>()) {
if (!InStdNamespace || !isStdCastBuiltin(Context, NewFD, BuiltinID))
Diag(NewFD->getLocation(), diag::err_attribute_invalid_std_builtin)
<< NewFD->getAttr<BehavesLikeStdAttr>();
NewFD->addAttr(BuiltinAttr::Create(Context, BuiltinID));
} else 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.
Expand Down
16 changes: 16 additions & 0 deletions clang/lib/Sema/SemaDeclAttr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8485,6 +8485,19 @@ static void handleNoUniqueAddressAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
D->addAttr(NoUniqueAddressAttr::Create(S.Context, AL));
}

static void handleBehavesLikeStdAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
if (AL.getNumArgs() > 1) {
S.Diag(AL.getLoc(), diag::err_attribute_wrong_number_arguments) << AL << 1;
return;
}

StringRef Str;
if (!S.checkStringLiteralArgumentAttr(AL, 0, Str))
return;

D->addAttr(BehavesLikeStdAttr::Create(S.Context, Str, AL));
}

static void handleSYCLKernelAttr(Sema &S, Decl *D, const ParsedAttr &AL) {
// The 'sycl_kernel' attribute applies only to function templates.
const auto *FD = cast<FunctionDecl>(D);
Expand Down Expand Up @@ -9397,6 +9410,9 @@ ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D, const ParsedAttr &AL,
case ParsedAttr::AT_NoUniqueAddress:
handleNoUniqueAddressAttr(S, D, AL);
break;
case ParsedAttr::AT_BehavesLikeStd:
handleBehavesLikeStdAttr(S, D, AL);
break;

case ParsedAttr::AT_AvailableOnlyInDefaultEvalMethod:
handleAvailableOnlyInDefaultEvalMethod(S, D, AL);
Expand Down
23 changes: 23 additions & 0 deletions clang/lib/Sema/SemaExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7145,6 +7145,29 @@ static void DiagnosedUnqualifiedCallsToStdFunctions(Sema &S,
if (BuiltinID != Builtin::BImove && BuiltinID != Builtin::BIforward)
return;

if (auto *GA = FD->getAttr<BehavesLikeStdAttr>()) {
if (auto *DC = FD->getDeclContext()) {
const NamespaceDecl *NSD = nullptr;
while (DC->isNamespace()) {
NSD = cast<NamespaceDecl>(DC);
if (NSD->isInline())
DC = NSD->getParent();
else
break;
}
if (NSD && NSD->getIdentifier()) {
SmallString<32> Str;
StringRef Name =
(NSD->getIdentifier()->getName() + "::").toStringRef(Str);
S.Diag(DRE->getLocation(),
diag::warn_unqualified_call_to_std_cast_function)
<< FD->getQualifiedNameAsString()
<< FixItHint::CreateInsertion(DRE->getLocation(), Name);
}
}
return;
}

S.Diag(DRE->getLocation(), diag::warn_unqualified_call_to_std_cast_function)
<< FD->getQualifiedNameAsString()
<< FixItHint::CreateInsertion(DRE->getLocation(), "std::");
Expand Down
72 changes: 72 additions & 0 deletions clang/test/CodeGenCXX/builtin-std-move.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,20 @@ namespace std {
template<typename T, typename U> T move(U source, U source_end, T dest);
}

namespace mystd {
template<typename T> [[clang::behaves_like_std("move")]] constexpr T &&move(T &val) { return static_cast<T&&>(val); }
template<typename T> [[clang::behaves_like_std("move_if_noexcept")]] constexpr T &&move_if_noexcept(T &val);
template<typename T> [[clang::behaves_like_std("forward")]] constexpr T &&forward(T &val);
template<typename U, typename T> [[clang::behaves_like_std("forward_like")]] constexpr T &&forward_like(T &&val);
template<typename T> [[clang::behaves_like_std("as_const")]] constexpr const T &as_const(T &val);
}

template<typename T> [[clang::behaves_like_std("move")]] constexpr T &&mymove(T &val) { return static_cast<T&&>(val); }
template<typename T> [[clang::behaves_like_std("move_if_noexcept")]] constexpr T &&mymove_if_noexcept(T &val);
template<typename T> [[clang::behaves_like_std("forward")]] constexpr T &&myforward(T &val);
template<typename U, typename T> [[clang::behaves_like_std("forward_like")]] constexpr T &&myforward_like(T &&val);
template<typename T> [[clang::behaves_like_std("as_const")]] constexpr const T &myas_const(T &val);

class T {};
extern "C" void take(T &&);
extern "C" void take_lval(const T &);
Expand All @@ -27,6 +41,24 @@ T &forward_a = std::forward<T&>(a);
// CHECK-DAG: @forward_like_a = constant ptr @a
T &forward_like_a = std::forward_like<int&>(a);

// CHECK-DAG: @move_a_2 = constant ptr @a
T &&move_a_2 = mystd::move(a);
// CHECK-DAG: @move_if_noexcept_a_2 = constant ptr @a
T &&move_if_noexcept_a_2 = mystd::move_if_noexcept(a);
// CHECK-DAG: @forward_a_2 = constant ptr @a
T &forward_a_2 = mystd::forward<T&>(a);
// CHECK-DAG: @forward_like_a_2 = constant ptr @a
T &forward_like_a_2 = mystd::forward_like<int&>(a);

// CHECK-DAG: @move_a_3 = constant ptr @a
T &&move_a_3 = mymove(a);
// CHECK-DAG: @move_if_noexcept_a_3 = constant ptr @a
T &&move_if_noexcept_a_3 = mymove_if_noexcept(a);
// CHECK-DAG: @forward_a_3 = constant ptr @a
T &forward_a_3 = myforward<T&>(a);
// CHECK-DAG: @forward_like_a_3 = constant ptr @a
T &forward_like_a_3 = myforward_like<int&>(a);

// Check emission of a non-constant call.
// CHECK-LABEL: define {{.*}} void @test
extern "C" void test(T &t) {
Expand All @@ -53,6 +85,46 @@ extern "C" void test(T &t) {

// CHECK: declare {{.*}} @_ZSt4moveI1TS0_ET_T0_S2_S1_

// CHECK-LABEL: define {{.*}} void @test2
extern "C" void test2(T &t) {
// CHECK: store ptr %{{.*}}, ptr %[[T_REF:[^,]*]]
// CHECK: %0 = load ptr, ptr %[[T_REF]]
// CHECK: call void @take(ptr {{.*}} %0)
take(mystd::move(t));
// CHECK: %1 = load ptr, ptr %[[T_REF]]
// CHECK: call void @take(ptr {{.*}} %1)
take(mystd::move_if_noexcept(t));
// CHECK: %2 = load ptr, ptr %[[T_REF]]
// CHECK: call void @take(ptr {{.*}} %2)
take(mystd::forward<T&&>(t));
// CHECK: %3 = load ptr, ptr %[[T_REF]]
// CHECK: call void @take_lval(ptr {{.*}} %3)
take_lval(mystd::forward_like<int&>(t));
// CHECK: %4 = load ptr, ptr %[[T_REF]]
// CHECK: call void @take_lval(ptr {{.*}} %4)
take_lval(mystd::as_const<T&&>(t));
}

// CHECK-LABEL: define {{.*}} void @test3
extern "C" void test3(T &t) {
// CHECK: store ptr %{{.*}}, ptr %[[T_REF:[^,]*]]
// CHECK: %0 = load ptr, ptr %[[T_REF]]
// CHECK: call void @take(ptr {{.*}} %0)
take(mymove(t));
// CHECK: %1 = load ptr, ptr %[[T_REF]]
// CHECK: call void @take(ptr {{.*}} %1)
take(mymove_if_noexcept(t));
// CHECK: %2 = load ptr, ptr %[[T_REF]]
// CHECK: call void @take(ptr {{.*}} %2)
take(myforward<T&&>(t));
// CHECK: %3 = load ptr, ptr %[[T_REF]]
// CHECK: call void @take_lval(ptr {{.*}} %3)
take_lval(myforward_like<int&>(t));
// CHECK: %4 = load ptr, ptr %[[T_REF]]
// CHECK: call void @take_lval(ptr {{.*}} %4)
take_lval(myas_const<T&&>(t));
}

// Check that we instantiate and emit if the address is taken.
// CHECK-LABEL: define {{.*}} @use_address
extern "C" void *use_address() {
Expand Down
7 changes: 7 additions & 0 deletions clang/test/CodeGenCXX/builtins.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ S *addressof(bool b, S &s, S &t) {
}

namespace std { template<typename T> T *addressof(T &); }
namespace mystd { template<typename T> [[clang::behaves_like_std("addressof")]] T *addressof(T &); }

// CHECK: define {{.*}} @_Z13std_addressofbR1SS0_(
S *std_addressof(bool b, S &s, S &t) {
Expand All @@ -39,6 +40,12 @@ S *std_addressof(bool b, S &s, S &t) {
return std::addressof(b ? s : t);
}

S *mystd_addressof(bool b, S &s, S &t) {
// CHECK: %[[LVALUE:.*]] = phi
// CHECK: ret ptr %[[LVALUE]]
return mystd::addressof(b ? s : t);
}

namespace std { template<typename T> T *__addressof(T &); }

// CHECK: define {{.*}} @_Z15std___addressofbR1SS0_(
Expand Down
20 changes: 20 additions & 0 deletions clang/test/SemaCXX/err-invalid-behaves-like-std.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// RUN: %clang_cc1 -fsyntax-only -std=c++11 -verify %s

namespace mystd {
inline namespace bar {
template <class T> struct remove_reference { typedef T type; };
template <class T> struct remove_reference<T&> { typedef T type; };
template <class T> struct remove_reference<T&&> { typedef T type; };
Comment on lines +5 to +7
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe just use __remove_reference_t?


template <class T>
[[clang::behaves_like_std("moved")]] typename remove_reference<T>::type &&move(T &&t); // expected-error {{not a valid std builtin for attribute 'behaves_like_std'}}

template <class T>
[[clang::behaves_like_std("__builtin_abs")]] typename remove_reference<T>::type &&move2(T &&t); // expected-error {{not a valid std builtin for attribute 'behaves_like_std'}}

template <class T>
[[clang::behaves_like_std("strlen")]] typename remove_reference<T>::type &&move3(T &&t); // expected-error {{not a valid std builtin for attribute 'behaves_like_std'}}

}
}

28 changes: 27 additions & 1 deletion clang/test/SemaCXX/unqualified-std-call-fixits.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,38 @@ int &&forward(auto &a) { return a; }

} // namespace std

using namespace std;
namespace mystd {

[[clang::behaves_like_std("move")]] int &&move(auto &&a) { return a; }

[[clang::behaves_like_std("forward")]] int &&forward(auto &a) { return a; }

} // namespace mystd

[[clang::behaves_like_std("move")]] int &&mymove(auto &&a) { return a; }

[[clang::behaves_like_std("forward")]] int &&myforward(auto &a) { return a; }

void f() {
using namespace std;
int i = 0;
(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
}

void g() {
using namespace mystd;
int i = 0;
(void)move(i); // expected-warning {{unqualified call to 'mystd::move}}
// CHECK: {{^}} (void)mystd::move
(void)forward(i); // expected-warning {{unqualified call to 'mystd::forward}}
// CHECK: {{^}} (void)mystd::forward
}

void h() {
int i = 0;
(void)mymove(i); // no-warning
(void)myforward(i); // no-warning
}
Loading