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

Conversation

MaxEW707
Copy link
Contributor

@MaxEW707 MaxEW707 commented Dec 30, 2023

Background

https://godbolt.org/z/hv53svTrq for reference on all of the below.

In games debug performance is critical as much as optimized performance.
We mainly accomplish this by reducing the amount of function calls being made in debug builds. This does not imply the use of always_inline but mainly being diligent in the amount of small functions being called such as getters and accessor functions.
As has been gaining support the last couple years and shown by clangs builtin support there are a lot of language level features that are implemented in the standard library such as std::move which lead to poor debugging performance, experience and extra compile time due to template instantiations.
Since clang already treats these meta cast functions as implicit builtins I will assume the reasoning for such is already a given.

However us and many other game studios do not use the std and have our own core libraries and/or STL implementations. Therefore we do not get the benefits that clang provides by recognizing certain functions inside the std namespace. I will try to list some reasons why we cannot just include <utility> and use the std functions. I am happy to expand on those reasons in the comments if desired. The EASTL paper here is a good starting point and still relevant today in my opinion.

Use Case

We have our own core libraries and STL implementation.
Internally our STL and core libraries use static_cast to implement move and forward semantics.
However almost all user code, the engine and games themselves, use the provided move and forward template functions from these libraries.
Currently we have two variants due to historical reasons that will most likely never converge. One is inside a namespace like so MySTL::move(...) and one is prefixed as is done in C code like so MyMove(...).

ClangCL

Last year MSVC added [[msvc::intrinsic]] for us game devs here. This was explicitly added as an attribute under the request of us since a lot of us have custom STLs.
Clang currently lacks such an attribute which means we can't fully move onto ClangCL until we have a similar facility. It would also be helpful to have this attribute for our other platforms which are mostly console vendors and mobile who supply their own fork of clang but more on that below.

Besides the overloaded use of the word intrinsic one downside to this approach is that any warnings that could be generated by knowledge of std function implicit behaviours are not generated. Pessimizing move return warnings aren't generated.
This is still the case for our explicit core code that uses static_cast and something I plan to improve within Clang in the future.

Force Inline Move

To get around GCC/Clang we do currently mark our move/forward functions as force inline which is respected in debug.
However as the godbolt shows the codegen isn't as good as the builtin in debug.
We still incur the compile-time cost compared to the builtin.
We do not use force inline liberally and have disabled this in past for some compilers where we noticed force inline regressions in compile times. We haven't noticed such regressions lately.

Just use std

While we might be able to use std::move via using declarations and macros that expand to std::move we cannot at all suffer the include times from vendor std implementations. Do note that we need to ship across different STL versions and vendor STLs that are not one of the major 3 public STLs, as referenced below, that we all know.

MSVC STL <utility> shipped with MSVC 1938 takes 45ms to parse with clang.
libstdc++ <utility> shipped with Ubuntu 22.04 takes 31ms to parse with clang.
libc++ <utility> shipped with Ubuntu 22.04 takes 156ms to parse with clang.

We cannot include such costly headers into basically every single source file. We also don't want to balloon our PCH headers with expensive std includes and instead keep them for our internal includes as much as possible since the larger the PCH the slower your builds.
I haven't looked into -fpch-instantiate-templates yet or whether MSVC /Yc still has the same behaviour as -fpch-instantiate-templates to deal with PCH performance issues. As MSVC gets more conforming and since we have to deal with vendor clang forks and vendor libraries, some which include templates in their headers, I don't want to be enforcing non-conforming defaults on our game teams using our core libraries.

Just declare move

Clang only looks for the declaration so we can just declare move inside the std namespace.
We have done this in the past however libc++ started marking a bunch of functions with abi_tag which cannot be redeclared as shown in the godbolt.

We still need to ensure that our headers work with std headers included after us because there are some platform specific source files that need to interact with vendor or third party libraries who do use and include std from their headers.

clang::behaves_like_std

Introducing behaves_like_std attribute which can be used to mark a function that has the same guarantees as the std equivalent.
behaves_like_std("move") will notify clang that your function has the same semantics as std::move and should be treated as such.
This also has the benefit of still emitting all the std warnings such as self move and pessimizing move.
In the future we can probably extend this attribute to support more std behaviours especially as the compiler is increasing being asked to poke into the std namespace in every C++ standard and as the C++ std gets more meta functions that are implemented in the library but not the language itself.

I don't mean for any of this to come off as a rant. Trying to plead my case for the viability of this attribute and its necessity for some users of C++ which in my case is the games industry.

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Dec 30, 2023
@llvmbot
Copy link

llvmbot commented Dec 30, 2023

@llvm/pr-subscribers-clang

Author: Max Winkler (MaxEW707)

Changes

Background

https://godbolt.org/z/hv53svTrq for reference on all of the below.

In games debug performance is critical as much as optimized performance.
We mainly accomplish this by reducing the amount of function calls being made in debug builds. This does not imply the use of always_inline but mainly being diligent in the amount of small functions being called such as getters and accessor functions.
As has been gaining support the last couple years and shown by clangs builtin support there are a lot of language level features that are implemented in the standard library such as std::move which lead to poor debugging performance, experience and extra compile time due to template instantiations.
Since clang already treats these meta cast functions as implicit builtins I will assume the reasoning for such is already a given.

However us and many other game studios do not use the std and have our own core libraries and/or STL implementations. Therefore we do not get the benefits that clang provides by recognizing certain functions inside the std namespace. I will try to list some reasons why we cannot just include &lt;utility&gt; and use the std functions. I am happy to expand on those reasons in the comments if desired. The EASTL paper here is a good starting point and still relevant today in my opinion.

Use Case

We have our own core libraries and STL implementation.
Internally our STL and core libraries use static_cast to implement move and forward semantics.
However almost all user code, the engine and games themselves, use the provided move and forward template function from these libraries.
Currently we have two variants due to historical reasons that will most likely never converge. One is inside a namespace like so MySTL::move(...) and one is prefixed as is done in C code like so MyMove(...).

ClangCL

Last year MSVC added [[msvc::intrinsic]] for us game devs here. This was explicitly added as an attribute under the request of us since a lot of us have custom STLs.
Clang currently lacks such an attribute which means we can't fully move onto ClangCL until we have a similar facility. It would also be helpful to have this attribute for our other platforms which are mostly console vendors who supply their own fork of clang but more on that below.

Besides the overloaded use of the word intrinsic one downside to this approach is that any warnings that could be generated by knowledge of std function implicit behaviours are not generated. Pessimizing move return warnings aren't generated.
This is still the case for our explicit core code that uses static_cast and something I plan to improve within Clang in the future.

Force Inline Move

To get around the GCC/Clang we do currently mark our move/forward functions as force inline which is respected in debug.
However as the godbolt shows the codegen isn't as good as the builtin in debug.
We still incur the compile-time cost compared to the builtin.

Just use std

While we might be able to use std::move via using declarations and macros that expand to std::move we cannot at all suffer the include times from vendor std implementations. Do note that we need to ship across different STL versions and vendor STLs that are not one of the major 3 public STLs that we all know.

MSVC STL shipped with MSVC 1938 takes 45ms to parse with clang.
libstdc++ shipped with Ubuntu 22.04 takes 31ms to parse with clang.
libc++ shipped with Ubuntu 22.04 takes 156ms to parse with clang.

We cannot include such costly headers into basically every single source file. We also don't want to balloon our PCH headers with expensive std includes and instead keep them for our internal includes as much as possible since the larger the PCH the slower your builds.
I haven't looked into -fpch-instantiate-templates yet or whether MSVC /Yc still has the same behaviour as -fpch-instantiate-templates. As MSVC gets more conforming and since we have to deal with vendor clang forks and vendor libraries, some which include templates in their headers, I don't want to be enforcing non-conforming defaults on our game teams using our core libraries.

Just declare move

Clang only looks for the declaration so we can just declare move inside the std namespace.
We have done this in the past however libc++ started marking a bunch of functions with abi_tag which cannot be redeclared as shown in the godbolt.

We still need to ensure that our headers work with std headers included after us because there are some platform specific source files that need to interact with vendor or third party libraries who do use and include std from their headers.

clang::behaves_like_std

Introducing behaves_like_std attribute which can be used to mark a function that has the same guarantees as the std equivalent.
behaves_like_std("move") will notify clang that your function has the same semantics as std::move and should be treated as such.
This also has the benefit of still emitting all the std warnings such as self move and pessimizing move.
In the future we can probably extend this attribute to support more std behaviours especially as the compiler is increasing being asked to poke into the std namespace in every C++ standard and as the C++ std gets more meta functions that are implemented in the library but not the language itself.

I don't mean for any of this to come off as a rant. Trying to plead my case for the viability of this attribute and its necessity for some users of C++ which in my case is the games industry.


Patch is 22.94 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/76596.diff

13 Files Affected:

  • (modified) clang/include/clang/Basic/Attr.td (+9)
  • (modified) clang/include/clang/Basic/AttrDocs.td (+29)
  • (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+2)
  • (modified) clang/lib/Sema/SemaDecl.cpp (+36-11)
  • (modified) clang/lib/Sema/SemaDeclAttr.cpp (+16)
  • (modified) clang/lib/Sema/SemaExpr.cpp (+21)
  • (modified) clang/test/CodeGenCXX/builtin-std-move.cpp (+72)
  • (modified) clang/test/CodeGenCXX/builtins.cpp (+7)
  • (added) clang/test/SemaCXX/err-invalid-behaves-like-std.cpp (+20)
  • (modified) clang/test/SemaCXX/unqualified-std-call-fixits.cpp (+27-1)
  • (modified) clang/test/SemaCXX/warn-pessmizing-move.cpp (+22)
  • (modified) clang/test/SemaCXX/warn-redundant-move.cpp (+43-4)
  • (modified) clang/test/SemaCXX/warn-self-move.cpp (+55)
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index db17211747b17d..5ad72c7026425d 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -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">,
diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td
index 98a7ecc7fd7df3..5d99bb87587ceb 100644
--- a/clang/include/clang/Basic/AttrDocs.td
+++ b/clang/include/clang/Basic/AttrDocs.td
@@ -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 = [{
diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td
index aebb7d9b945c33..4ebcda089ca307 100644
--- a/clang/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td
@@ -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">;
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index ffbe317d559995..2498af5c7e6a1a 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -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:
@@ -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,
@@ -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.
diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp
index af8b90ecfed973..a48e0c19fce23a 100644
--- a/clang/lib/Sema/SemaDeclAttr.cpp
+++ b/clang/lib/Sema/SemaDeclAttr.cpp
@@ -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);
@@ -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);
diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp
index 960f513d1111b2..4adac2e61fdc52 100644
--- a/clang/lib/Sema/SemaExpr.cpp
+++ b/clang/lib/Sema/SemaExpr.cpp
@@ -7145,6 +7145,27 @@ 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()) {
+        std::string Name = NSD->getIdentifier()->getName().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::");
diff --git a/clang/test/CodeGenCXX/builtin-std-move.cpp b/clang/test/CodeGenCXX/builtin-std-move.cpp
index 6eb7073d67f1cc..91f9c3d06f04c6 100644
--- a/clang/test/CodeGenCXX/builtin-std-move.cpp
+++ b/clang/test/CodeGenCXX/builtin-std-move.cpp
@@ -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 &);
@@ -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) {
@@ -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() {
diff --git a/clang/test/CodeGenCXX/builtins.cpp b/clang/test/CodeGenCXX/builtins.cpp
index 90265186fb3d8c..aea494dcb5a942 100644
--- a/clang/test/CodeGenCXX/builtins.cpp
+++ b/clang/test/CodeGenCXX/builtins.cpp
@@ -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) {
@@ -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_(
diff --git a/clang/test/SemaCXX/err-invalid-behaves-like-std.cpp b/clang/test/SemaCXX/err-invalid-behaves-like-std.cpp
new file mode 100644
index 00000000000000..9374ae9461b045
--- /dev/null
+++ b/clang/test/SemaCXX/err-invalid-behaves-like-std.cpp
@@ -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; };
+
+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'}}
+
+}
+}
+
diff --git a/clang/test/SemaCXX/unqualified-std-call-fixits.cpp b/clang/test/SemaCXX/unqualified-std-call-fixits.cpp
index 1a1ffc7ba2e822..52c3b0b6f13b6f 100644
--- a/clang/test/SemaCXX/unqualified-std-call-fixits.cpp
+++ b/clang/test/SemaCXX/unqualified-std-call-fixits.cpp
@@ -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
+}
diff --git a/clang/test/SemaCXX/warn-pessmizing-move.cpp b/clang/test/SemaCXX/warn-pessmizing-move.cpp
index 2c27cd7f95f74d..d9c63dda10d1cd 100644
--- a/clang/test/SemaCXX/warn-pessmizing-move.cpp
+++ b/clang/test/SemaCXX/warn-pessmizing-move.cpp
@@ -13,6 +13,16 @@ template <class T> typename remove_reference<T>::type &&move(T &&t);
 }
 }
 
+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; };
+
+template <class T> [[clang::behaves_like_std("move")]] typename remove_reference<T>::type &&move(T &&t);
+}
+}
+
 struct A {
 #ifdef USER_DEFINED
   A() {}
@@ -39,6 +49,18 @@ A test1(A a1) {
   // CHECK: fix-it:"{{.*}}":{[[@LINE-4]]:22-[[@LINE-4]]:23}:""
 }
 
+A test1_mystd(A a1) {
+  A a2;
+  return a1;
+  return a2;
+  return mystd::move(a1);
+  return mystd::move(a2);
+  // expected-warning@-1{{prevents copy elision}}
+  // expected-note@-2{{remove std::move call}}
+  // CHECK: fix-it:"{{.*}}":{[[@LINE-3]]:10-[[@LINE-3]]:22}:""
+  // CHECK: fix-it:"{{.*}}":{[[@LINE-4]]:24-[[@LINE-4]]:25}:""
+}
+
 B test2(A a1, B b1) {
   // Object is different than return type so don't warn.
   A a2;
diff --git a/clang/test/SemaCXX/warn-redundant-move.cpp b/clang/test/SemaCXX/warn-redundant-move.cpp
index 2bfc8c9312f02e..0617c06702fcd4 100644
--- a/clang/test/SemaCXX/warn-redundant-move.cpp
+++ b/clang/test/SemaCXX/warn-redundant-move.cpp
@@ -13,6 +13,16 @@ template <class T> typename remove_reference<T>::type &&move(T &&t);
 }
 }
 
+namespace mystd {
+inline namespace baz {
+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; };
+
+template <class T> [[clang::behaves_like_std("move")]] typename remove_reference<T>::type &&move(T &&t);
+}
+}
+
 // test1 and test2 should not warn until after implementation of DR1579.
 struct A {};
 struct B : public A {};
@@ -86,6 +96,21 @@ D test5(D d) {
   // CHECK: fix-it:"{{.*}}":{[[@LINE-4]]:21-[[@LINE-4]]:22}:""
 }
 
+D test6(D d) {
+  return d;
+  // Verify the implicit move from the AST dump
+  // CHECK-AST: ReturnStmt{{.*}}line:[[@LINE-2]]
+  // CHECK-AST-NEXT: CXXConstructExpr{{.*}}D{{.*}}void (D &&)
+  // CHECK-AST-NEXT: ImplicitCastExpr
+  // CHECK-AST-NEXT: DeclRefExpr{{.*}}ParmVar{{.*}}'d'
+
+  return mystd::move(d);
+  // expected-warning@-1{{redundant move in return statement}}
+  // expected-note@-2{{remove std::move call here}}
+  // CHECK: fix-it:"{{.*}}":{[[@LINE-3]]:10-[[@LINE-3]]:22}:""
+  // CHECK: fix-it:"{{.*}}":{[[@LINE-4]]:23-[[@LINE-4]]:24}:""
+}
+
 namespace templates {
   struct A {};
   struct B { B(A); };
@@ -104,13 +129,27 @@ namespace templates {
     test1<B>(A());
   }
 
+  // Warn once here since the type is not dependent.
+  template <typename T>
+  A test2(A a) {
+    return mystd::move(a);
+    // expected-warning@-1{{redundant move in return statement}}
+    // expected-note@-2{{remove std::move call here}}
+    // CHECK: fix-it:"{{.*}}":{[[@LINE-3]]:12-[[@LINE-3]]:24}:""
+    // CHECK: fix-it:"{{.*}}":{[[@LINE-4]]:25-[[@LINE-4]]:26}:""
+  }
+  void run_test2() {
+    test2<A>(A());
+    test2<B>(A());
+  }
+
   // T1 and T2 may not be the same, the warning may not always apply.
   template <typename T1, typename T2>
-  T1 test2(T2 t) {
+  T1 test3(T2 t) {
     return std::move(t);
   }
-  void run_test2() {
-    test2<A, A>(A());
-    test2<B, A>(A()...
[truncated]

Copy link
Contributor

@philnik777 philnik777 left a comment

Choose a reason for hiding this comment

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

While I disagree with the reasoning for not just using std (@MaxEW707 I think having a chat about this informally would be nice.), I do think it makes sense to add the ability to declare some functions as builtins. There are a few cases inside the standard library itself:

  • simply allowing the same optimizations to be done with -ffreestanding
  • there are quite a few more functions which behave just like builtins, but are currently not recognized as such by the compiler. e.g. identity::operator()
  • library-internal functionality which would have to be handled by the compiler for every implementation, like __identity::operator() in libc++

The above ones are all handled by what is currently proposed, but there is also functionality which doesn't work with this proposal. For example:

  • allowing the declaration of non-std builtins, like char_traits<char>::find(), which could be declared as equivalent to __builtin_memchr.
  • other functions which are essentially casts, but take their arguments by-value, like to_underlying
  • Functions that are essentially builtin operations, like char_traits<char>::eq

I don't know whether all of these things should be handled with the same attribute, or whether they should be handled at all, but they should definitely be considered when designing this attribute.

Comment on lines +5 to +7
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; };
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?

@MaxEW707
Copy link
Contributor Author

MaxEW707 commented Dec 30, 2023

(@MaxEW707 I think having a chat about this informally would be nice.)

Sounds good. Let me know the best way to contact you.

While I disagree with the reasoning for not just using std

I'll try to give more context here. The main reason is include times and not wanting to work around a variety of vendor STLs who have wildly different include times and behaviours.
For clang we need to worry about 5 different STLs: MSVC STL, libstdc++, libc++, a vendor STL and some vendor forks of libc++.
There are larger issues here when dealing with shipped vendor STLs that are custom or forks of libc++ that we can talk about offline.

On one of our smaller projects the PC build is ~16,000 cpp files which includes the generated cpp files, the tools, engine, tests, and the game itself. This is the build that most engineers work on and they usually need to work across a variety of domains since a change in the engine may require a change in the code generator, editor, asset building, the unit tests, the integration tests, etc.

On this smaller project one of our console builds which only includes the game itself is around ~4k source files when I last checked. Just adding #include <new> to our corelib/placement_new.h header increased build times by ~22 seconds. On this platform the vendor STL <new> took 29ms to parse so that means that header was roughly included into ~750 files. That is a build time increase for one file include that we cannot at all live with.

We do not use the STL anywhere in our code base. We have our own so just including any STL header also brings in other headers like type_traits and their config headers. On MSVC STL it brings in yvals_core.h and its dependencies.
For us these headers are never amortized by assuming a cpp file might use <string>, <vector>, <new>, etc from the STL.

At the end of the day we don't want to deal with different STL implementations.
We do not use STL and do not want to include it.
We do not agree with a lot of language level features like move, placement new, etc being implemented in the std. We would prefer to just have compiler builtins for us like __builtin_launder and __builtin_addressof.
A lot of std headers also aren't small. <utility> is huge when I just need move and forward. <new> is massive when I just need placement new because we do not at all ever hit global new. libc++ is getting better here, https://libcxx.llvm.org/DesignDocs/HeaderRemovalPolicy.html, but we still have to deal with MSVC STL, vendor STL and vendor forks of libc++.
We would rather just have compiler support that everyone can use instead of relying on the STL to implement language level features especially given how much the compiler is being asked to poke into the std lately.
In my view C++ should not require the STL to "work". I shouldn't need a library to use a language.
Imagine how crazy it would be if alignof was actually std::alignof and I had to include a header to use it. That is effectively what a lot of the facilities are becoming in C++.
While others may not agree I don't believe having compiler builtins / extensions to support this is too much to ask for the users who require it especially considering all the compiler builtins already for libc functions like math. I think the format printf attribute is a fantastic example of an extension that allows users to get format string checking for their own functions and not just libc printf. For example MSVC only checks libc printf with no way to format check your strings for your own functions which is the wrong way to go.

@MaxEW707
Copy link
Contributor Author

MaxEW707 commented Dec 31, 2023

I don't know whether all of these things should be handled with the same attribute, or whether they should be handled at all, but they should definitely be considered when designing this attribute.

For this I am more concerned with language level features such as move, forward, reserved placement operator new, etc and not all generic std functions. I can see where that becomes muddy.

For example to_integer, to_underlying or identity::operator I don't view as language level features that require a std implementation. I view them as a larger issue with high level operations being poorly represented by the language itself.
I would love operator-> and operator* on smart pointers that just return a member variable to be replaced with a direct field access.
I would love for bitwise operators on enum class types to be replaced directly with the builtin bitwise operation on the underlying type instead of a function call.

I would love for std::byte to be a builtin type in the compiler like char8_t and not be implemented in the std with a special check inside clang for isStdByte. Thankfully we can workaround this with the may_alias attribute on clang for our own enum class byte type.

I would also love for std::initializer_list to be a builtin type without this delicate dance with the std and the compiler on specific signatures for the constructor. Look at how MSVC defines std::initializer_list.

I foresee the compiler increasing making assumptions about the names of functions and types inside the std namespace like std::to_underlying, std::byte, etc. clang::behaves_like_std gives users an easy to tell the compiler to make those same assumptions with user defined names.

There are cases where the std functions have other implicit properties. Reserved placement operator new can be assumed to not have to check the returned pointer for null. If you implement your own placement operator new you do not get this benefit. MSVC only removes this check for the reserved placement new but thankfully provides the define __PLACEMENT_NEW_INLINE if you decide to define it yourself. I believe you can work around this with the returns_nonnull attribute on clang when I checked some months back. However clang still does the function call in debug if you are not the global reserved placement operator new which sucks.

Maybe for stuff like operator-> on a smart pointer the compiler will start looking at std::unique_ptr::operator->.
Maybe for these facilities we instead have a more generic attribute like [[clang::treat_as_macro]] which more closely aligns with the semantics of mvsc::intrinsic.
Maybe an attribute like [[clang::treat_as_cast]] which treats any marked function as a cast from its input to its output type for those functions that effectively are casts like to_underlying and to_integer.
Maybe we improve always_inline code gen for small functions like std::move and operator-> on a object that just returns its member variable to have the same assembly in debug as if those functions were implemented like macros.

I haven't thought too much about those other cases yet but if always_inline on MyMove produced the same code in debug as std::move that gets me 90% of what I want and that would also probably fix all the other small function calls.
My 2c.

@cor3ntin
Copy link
Contributor

I struggle to understand the motivation here: If you are not using a standard library implementation at all and instead act as your own standard library vendor, just providing a declaration of move / forward should be enough.

The concern about ABI tags only come up if the STL is indeed included, and you seem to say it never is. Can you explain a bit more why you are concerned about how things are defined in libc++?

@philnik777 Do std::move/ std::forward etc actually need an abi tag? Maybe we should simply not set a tag given that clang / gcc replace call to these functions.

I prefer this approach over msvc::intrinsic because at least it gives control over how the call is replaced.
Ie, I think the semantics you'd want is really a substitute_with_builtin (presumably just after lookup). intrinsics a very generic name with a very narrow set of use cases (some casts).

More general observations on the "using c++ without the STL thing":
I don't think this is a major consideration of the language/standard committee going forward.
As you noted, there exist a lot of type traits/functions which are or should be backed by compiler magic.
And we are actually extending the set of freestanding function dramatically. Reflection will also have a dependency on the STL

That being said, I understand the pain.
Modules should ease that pain - I don't have a good visibility on that but my understanding is that some progress is being made. In the meantime, I wonder if there is room for a lighter utility header, one without pair/rel_ops, maybe that would help.

I think we could also extend the set of std functions replaced in the front end if we can show it would have measurable compile times improvements (ie to_integer, as_const, to_underlying, etc).

PS: I appreciate that this patch comes with complete documentation :)

@philnik777
Copy link
Contributor

@philnik777 Do std::move/ std::forward etc actually need an abi tag? Maybe we should simply not set a tag given that clang / gcc replace call to these functions.

If that were always the case we could simply provide a declaration without ever defining the function, but clang and gcc don't always replace it. e.g. if you take the address (which both compilers unfortunately still allow). While it's unlikely for std::move specifically, it's always possible that the implementation changes and we have ABI breaks in the end, and having an ABI break vs. not allowing forward declarations of standard library functions seems like a no-brainer to me.

@MaxEW707
Copy link
Contributor Author

MaxEW707 commented Jan 1, 2024

I appreciate the discussions. I know I have a tendency to get ranty in my writing so I just want to be clear that I mean no malice.
I can further explain my side.

Include times
Modules should ease that pain

I don't know how else to make it clear that we will never be including std headers from our core library headers and within the core engine itself. The include times are a non-starter. If the verdict is just use std, we are going to continue just marking these functions as always_inline and live with the worse code gen than a builtin but still better than a function call.

Even if libc++ becomes tolerable we have to deal with msvc stl, libstdc++, libc++, SCE stl, and vendor forks of libc++ that I will not mention since this specific company is super duper litigious over the most minor things all compiled under clang.
If I could have an attribute that solves my problems without header includes and I can chuck it on the implementations in our core libraries then I am golden instead of trying to work around various std libraries.

I also want to highlight that we in particular have a wide range of platform support.
We were just able to get rid of VS2015 early 2023. We also finally removed GCC4.8 late 2022.
We still have to ship on a custom toolchain of VS2017 msvc for Xbox One.
We have game teams that are also on the latest VS2022.
Our macOS/iPhone support is quite wide as well.
SCE is very quick to upgrade their clang so we are on Clang 16 now if my memory is correct there on SCE. Any change to libc++ does not help SCE STL or the other vendors shipping their own fork of clang + libc++. A lot of these vendors will upgrade clang but not libc++ as well.

On our Linux servers we have such a variety of distros and GCC/Clang versions.

Regarding modules. Try moving the titanic that is [insert favourite game here that is shipping content every year].
Now try moving 3+ titanics.
Try moving that when you still have platforms that cannot move past VS2017.
Plus some vendors have no intention, as far as I am aware, of supporting modules in their vendor libraries which do include <utility> and such for move/forward.

Apple Clang + Xcode still don't support modules last I checked. We haven't upgraded to Xcode 15 yet.

We have our own build system generator. We have our own package management system. We have our stuff working well with PCH files where needed.
We have all the infrastructure set up to promote fast builds from the core libraries down the stack. I don't foresee us even considering modules for 10+ years.

More general observations on the "using c++ without the STL thing":
I don't think this is a major consideration of the language/standard committee going forward.
Reflection will also have a dependency on the STL

I don't want to speak for my peers but I think I can speak to the general sentiment of my community.
This is why among many many many other reasons SG14 failed and why a lot of us in the game dev community have just given up.
Some are at companies that do not allow any form of open source contributions or participation in the C++ discourse. That seems to be changing.
Thankfully I am at a company that does allows me to make these contributions here on my free time, LLVM license makes it easy for me to do so and I have the means to try to push them through. Selfishly for me but also for the game dev community at large.
Especially nowadays where we can basically ship with clang on almost every platform, a change in clang affects our entire ecosystem. It seems gone are the days of custom vendor compilers for every platform with EDG as the frontend.

I have followed the Reflection TS and its fine. At every game company I worked at our reflection needs are so esoteric that we are going to continue writing our own with our own code generators.
Almost every single game engine has its own way to do some form of dynamic casting, similar reasons that llvm also seems to have its own infrastructure from reading the docs, and that casting is usually very tightly integrated with the engine's reflection system.
Trying to highlight that in general a lot of these papers, for the language itself or the std, will never solve the esoteric needs of us or handle the esoteric platforms that we support. Not everything uses the BSD sockets API for example. Win32 is considered BSD compared to the stuff I've seen.
We just want a systems language that allows us to accomplish system level tasks while still retaining a lot of really useful core features like templates, easy support for intrusive data structures, control over allocations, etc.

As mentioned above our Linux servers use a variety of distros. The server software running in our datacentres still uses all our core libraries like all the game software but has more liberty with what game teams can run. We are less strict on what the various amount of server services are allowed to use since that software is usually tightly written for one specific platform.

For example I know, https://github.com/confluentinc/librdkafka, is a common library used in some backend services across the games industry which has a C++ interface with std. I've seen kafka used for log aggregation, game server monitoring, login queue monitoring, etc in backend services.
A lot of times these are installed from the distros package manager or some vendor apt-get link. The library is dynamically linked to the built app.
It is a lot easier for me to say hey if you want to use these cool facilities just apt-get install clang-18 and build with that or even build the compiler ourselves.
There is no way I am going to be building the software with latest libc++ and not the platform libstdc++ especially since std may be in use here for some third-party libs.
Like I said we have more pull and constraints on the engine proper but modern multiplayer games have a loooooooooooooot of services :).
At the very least we try to wrap these vendor and/or third-party libraries so the std includes are only in a subset of source files especially if we do not have the time and/or man-power to re-write said libraries ourselves.

If you are not using a standard library implementation at all and instead act as your own standard library vendor

I first want to talk about freestanding. We cannot ship with freestanding on consoles and mobile.
Especially consoles we will fail TRC(technical requirements checklist). We cannot at all muck with the environment on consoles, rightfully so, nowadays compared to when everyone in the industry would hack them up back in the day.
Android has its own infrastructure with Bionic.

As I tried to explain above we do not use the std but we are forced to use it in some cases. It is very likely that std headers will get included after our headers transitively in platform specific files.

For example since it is super dead now, Stadia. All of Stadia's APIs had std in them.
So ControllerInput_*.Stadia.cpp platform specific files that abstract Stadia's APIs would end up including std.

A lot of game studios use https://github.com/syoyo/tinyexr to handle OpenEXR image formats when converting from RAW assets to Editor assets and/or Editor assets to Runtime assets.
That lib also uses std in its header and interface. And since that library is used on the tooling side it isn't a high priority on my list to re-work it.

There are many more examples especially vendor code from some platforms that aren't public that we cannot at all control.

Our style guide requires system/library headers to be included after our headers and I don't even want to start the bikeshedding on changing that...

@philnik777 Do std::move/ std::forward etc actually need an abi tag? Maybe we should simply not set a tag given that clang / gcc replace call to these functions.

As @philnik777 highlighted the std implementers have to implement it pedantically and guard against users doing insane things like taking the addressof std::move. We do not have those constraints and would rather not have to deal with them personally.
I am happy I am not in that boat :).

I think we could also extend the set of std functions replaced in the front end if we can show it would have measurable compile times improvements (ie to_integer, as_const, to_underlying, etc).

Maybe an attribute name change would suffice here. Something like [[clang::cast_to_return_type]] which is basically what the builtins do.
This attribute will work on any function that returns a pointer or reference and has a single argument that is a pointer or reference.
It tells the compiler that the function body will just cast the input type to the return type.

The function could be something crazy like

template<class T, class U>
[[nodiscard]] constexpr auto&& forward_like(U&& x) noexcept
{
    constexpr bool is_adding_const = std::is_const_v<std::remove_reference_t<T>>;
    if constexpr (std::is_lvalue_reference_v<T&&>)
    {
        if constexpr (is_adding_const)
            return std::as_const(x);
        else
            return static_cast<U&>(x);
    }
    else
    {
        if constexpr (is_adding_const)
            return std::move(std::as_const(x));
        else
            return std::move(x);
    }
}

or as simple as

template<class T>
std::remove_reference_t<T>&& MyMove(T&& t) { return static_cast<std::remove_reference_t<T>&&>(t); }

template<class T, SFINAE(...)>
T* MyAddressOf(T& arg) { return reinterpret_cast<T*>(&const_cast<char&>(reinterpret_cast<const volatile char&>(arg))); }

The warnings not working with this proposed attribute is not a big deal to me. We already don't get them with our implementation of these meta functions. I intend to fix these warnings anyways and I have a PR already for one here: #76646.

static assertions possibly not firing such as static_assert(!is_lvalue_reference<_Tp>::value, "cannot forward an rvalue as an lvalue"); inside forward is not a big deal for me. We already do not get this for our inlined static casts.
Currently if the builtin is BIforward, clang has a special check for this inside SemaTemplateInstantiateDecl.cpp with

case Builtin::BIforward:
      // Do not treat 'std::forward' as a builtin if it takes an rvalue reference
      // type and returns an lvalue reference type. The library implementation
      // will produce an error in this case; don't get in its way.

Maybe this is an easy solve with the proposed [[clang::cast_to_return_type]]. Not sure. I need to dig through that part of the code in clang which I haven't touched yet.

I think I addressed all of the statements above. Please let me know if I missed any :).

@philnik777
Copy link
Contributor

It's not ready, but I have a draft for a much more general attribute: #78071

@MaxEW707
Copy link
Contributor Author

MaxEW707 commented Jan 16, 2024

It's not ready, but I have a draft for a much more general attribute: #78071

I was playing around with your draft PR and I do prefer your solution over mine.
It will even help to cleanup my custom xmmintrin.h header that we have to reduce include times from SSE headers across different compilers.

Let me know if you want me to close this PR in favour of your PR.

@shafik
Copy link
Collaborator

shafik commented Feb 29, 2024

Adding @AaronBallman and @erichkeane for a wider audience

@AaronBallman
Copy link
Collaborator

One question I have is related to:

Last year MSVC added [[msvc::intrinsic]] for us game devs here. This was explicitly added as an attribute under the request of us since a lot of us have custom STLs.
Clang currently lacks such an attribute which means we can't fully move onto ClangCL until we have a similar facility. It would also be helpful to have this attribute for our other platforms which are mostly console vendors and mobile who supply their own fork of clang but more on that below.

Clang supports attributes in the msvc vendor namespace already ([[msvc::no_unique_address]], [[msvc::constexpr]]) under -fms-extensions mode, so would a potential approach be supporting [[msvc::intrinsic]] for compatibility? (I realize that feature may not be ideal, but we do aim for cross-compiler compatibility for important use cases, so rather than redesign something similar-but-different, I think it may be more useful to implement the compatible extension first and build new functionality from there.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants