This is an archive of the discontinued LLVM Phabricator instance.

[libc] Add a resizable container with constexpr constructor and destructor.
ClosedPublic

Authored by sivachandra on Mar 9 2022, 7:02 PM.

Details

Summary

The new container is used to store atexit callbacks. This way, we avoid
the possibility of the destructor of the container itself getting added
as an at exit callback.

Diff Detail

Event Timeline

sivachandra created this revision.Mar 9 2022, 7:02 PM
Herald added projects: Restricted Project, Restricted Project. · View Herald TranscriptMar 9 2022, 7:02 PM
sivachandra requested review of this revision.Mar 9 2022, 7:02 PM

The new container is used to store atexit callbacks. This way, we avoid
the possibility of the destructor of the container itself getting added
as at exit callback.

Ah I didn't think about that, we would run into this when we implement __cxa_atexit. That would have been a confusing sanitizer error! Good catch.

libc/src/__support/CPP/blobstore.h
31 ↗(On Diff #414265)

Could we just have a reverse_iterator [rbegin|rend]() instead of this REVERSE_ORDER template parameter? Maybe it makes more sense for BlobStore<T>::iterator to have REVERSE_ORDER?

libc/src/stdlib/CMakeLists.txt
280

From what I understand, as long as T is POD and BlobStore<T>::~BlobStore() is defaulted, then there shouldn't be a destructor emitted because then BlobStore will be a POD too. Do you see __cxa_atexit` being called in this TU?

We could in the future when we add an analogue to is_trivially_destructible in our type_traits, add static_assert(cpp::IsTriviallyDestructibleV<ExitCallbackList>)

libc/src/stdlib/atexit.cpp
31

If we keep BlobStore with REVERSE_ORDER this could just be for (auto callback : exit_callbacks)

35

It may not be worth deallocating this when we are going to exit soon anyway. But it might not be worth the trouble of needing to make sanitizers ignore this

Use range for-loop.

sivachandra added inline comments.Mar 10 2022, 1:17 AM
libc/src/__support/CPP/blobstore.h
31 ↗(On Diff #414265)

If we have to support bidirectional iteration, then we need a doubly-linked list. Not sure if we will ever need bidirectional iteration so I have chosen to keep it a simple linked list with the direction attached to the type.

Maybe it makes more sense for BlobStore<T>::iterator to have REVERSE_ORDER?

Do you mean, it enables range-for-loop? Or, something else?

libc/src/stdlib/CMakeLists.txt
280

You are correct. I think I misread or checked it with the previous mutex handling still present that I did see a call to __cxa_atexit. I don't see it now.

That said, in a debug build, I surprisingly see a call to the constructor (via __cxx_global_var_init) if I don't use -O3 [may be -O2 is sufficient, I did not check]. In a release build, everything works as expected without -O3 - I don't see calls to the constructor or __cxa_atexit. So, I have kept the -O3 for now.

libc/src/stdlib/atexit.cpp
31

Ah, yes. Done now.

sivachandra edited the summary of this revision. (Show Details)Mar 10 2022, 1:17 AM
abrachet added inline comments.Mar 10 2022, 8:14 AM
libc/src/__support/CPP/blobstore.h
1 ↗(On Diff #414298)

Do you think the name BlockStore better describes what this data structure does? It may not be worth the churn to change the name everywhere, no strong preference.

31 ↗(On Diff #414265)

No strong preference because we would need to add one pointer overhead with a new prev pointer to Blob, but it would be much more ergonomic if BlobStore could iterate both ways.

REVERSE_ORDER could be changed to only be used in the iterator. So maybe it could be like

template <bool REVERSE_ORDER> class iterator_impl { ... };
using iterator = iterator_impl<false>;
using reverse_iterator = iterator_impl<true>;
iterator rbegin() {
  return reverse_iterator(current, fill_count);
}
...
libc/src/stdlib/CMakeLists.txt
280

That said, in a debug build, I surprisingly see a call to the constructor (via __cxx_global_var_init) if I don't use -O3

We just bumped up to C++17 and we already need C++20's constinit! :)

Specifying O3 here makes this TU basically impossible to debug and generally limits options. Maybe we want to optimize hard for size with Oz but atexit will always be O3.

Are global constructors something we need to avoid? The destructor was going to affect correctness, but unless I'm missing something the same isn't true for constructors. Is it worth the downside above if this will be optimized away during release builds anyway?

libc/src/stdlib/atexit.cpp
22

I don't know why I made this static before when it is already in an anonymous namespace

30

Not needed anymore

40

I think this is how we would normally format this. But if clang-format get's confused because of the parentheses then we should just go with what clang-format says.

sivachandra marked 2 inline comments as done.
  • Change the name from BlobStore to BlockStore.
  • Use c++ 20 to compile atexit.cpp.
sivachandra added inline comments.Mar 10 2022, 9:23 AM
libc/src/__support/CPP/blobstore.h
1 ↗(On Diff #414298)

I like BlockStore better so I changed it.

31 ↗(On Diff #414265)

I agree that having a forward and reverse iterator would be aesthetically/"ergonomically" ideal. Also, while I do not see today as to why we would need bidirectional iteration, I won't be surprised we actually make it a doubly linked list and allow bidirectional iteration in future. But, until then, I personally prefer to keep it restrictive.

libc/src/stdlib/CMakeLists.txt
280

The high level design goal is that, the runtime should iterate over init array (which is how the constructors will get called) and fini array to facilitate certain language features. If the runtime itself starts requiring them, we will soon end up with chicken and egg problems.

We can make an exception here because it is affecting debug builds only. But, a difference in behavior between debug and release builds will be hard to debug when we have to.

There are two options here - use C++ 20 (for constinit) for this TU or use a higher optimization level for this TU. I personally do not have problems with either of them. By the time this gets used in real production systems, C++ 20 will be acceptable. I preferred -O3 because I am not sure if our bots will like the C++ 20 feature. I have changed now to use C++ 20 instead of -O3. If the bots don't like it, I can switch back to -O3.

libc/src/stdlib/atexit.cpp
40

Yes, that was done by clang-format.

abrachet accepted this revision.Mar 10 2022, 9:52 AM

Thanks, this is much more correct than what I had and more performant too.

libc/src/__support/CPP/blobstore.h
31 ↗(On Diff #414265)

Sounds good.

libc/src/stdlib/CMakeLists.txt
280

You're right, it's actually very likely that a C++ object will have it's constructor and __cxa_atexit registration before this constructor is called. I don't know why I didn't see that... There is no way for us to control the order .init_array elements will be ordered across TU's so yes we need to make sure exit_callbacks is constructed statically.

I was actually joking about constinit, but it might be our best option here. O3 doesn't guarantee that this object will be constructed statically. I was thinking this would be easy enough to optimize that any compiler would do it and it would be as good as a guarantee, but actually GCC doesn't at any optimization level but clang get's it even at O1. https://godbolt.org/z/4Pv3TdG77

Using a C++20 compiler isn't that big of an ask because there's one in the same repo as this, and we could recommend the runtimes build for this case. Let's discuss further offline

This revision is now accepted and ready to land.Mar 10 2022, 9:52 AM