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

Max Winkler via cfe-commits cfe-commits at lists.llvm.org
Sun Dec 31 17:50:50 PST 2023


MaxEW707 wrote:

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.
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.
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 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: https://github.com/llvm/llvm-project/pull/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 :).

https://github.com/llvm/llvm-project/pull/76596


More information about the cfe-commits mailing list