This is an archive of the discontinued LLVM Phabricator instance.

[libcxx] Externally threaded libc++ variant
AbandonedPublic

Authored by rmaprath on May 17 2016, 6:34 AM.

Details

Summary

This patch builds on the work done under D19412 where all pthread dependencies were refactored under a separate thread-support API.

The current patch enables building a new variant of libc++ where all the threading dependencies are exported into a runtime API. This allows platform vendors to re-target these threading calls in a platform-defined manner (at runtime), paving the way for libc++ on non-pthread platforms.

For running the libc++ test suite, I've introduced a sample implementation of this external-thread-API using pthreads. This implementation should give an idea of what the end goal is.

This patch supersedes the sketch presented in D19415.

Diff Detail

Event Timeline

rmaprath updated this revision to Diff 57471.May 17 2016, 6:34 AM
rmaprath retitled this revision from to [libcxx] Externally threaded libc++ variant.
rmaprath updated this object.
rmaprath added reviewers: mclow.lists, EricWF, bcraig.
rmaprath added a subscriber: cfe-commits.

@mclow.lists, @EricWF: Any comments on this? If it helps, I could split off the library source changes and test setup into two separate patches. I thought to keep the two together as the test setup gives an idea of the overall objective.

Thanks.

/ Asiri

bcraig edited edge metadata.May 24 2016, 10:09 AM

Note: You'll want to look at http://reviews.llvm.org/D20573, as there will be confilicts for whoever submits second.

include/__threading_support
201

I'm not sure I like taking the freedom to define _LIBCPP_MUTEX_INITIALIZER away from implementers.

Would it be too terrible to replace this entire #elif block with something like the following?

#if !defined(__has_include) || __has_include(<os_provided_thread.h>)
#include <os_provided_thread.h>
#else
#error "_LIBCPP_THREAD_API_EXTERNAL requires the implementer to provide <os_provided_thread.h> in the include path"
#endif
rmaprath added inline comments.May 24 2016, 11:38 AM
include/__threading_support
201

The problem is that, std::mutex constructor needs to be constexpr (as you pointed out earlier). And since __libcpp_mutex_t is a pointer type (for this externally threaded variant), sensible definitions for _LIBCPP_MUTEX_INITIALIZER are limited.

Other than nullptr, one may be able to define _LIBCPP_MUTEX_INITIALIZER to be a pointer to some constant mutex (presuming that make it constexpr OK?) but I'm not convinced if such a setup would be very useful.

Hope that sounds sensible?

bcraig added inline comments.May 24 2016, 12:19 PM
include/__threading_support
201

If the implementer gets to provide an entire header, then they also get to choose what libcpp_mutex_t will be. They could make it a struct.

rmaprath added inline comments.May 24 2016, 12:53 PM
include/__threading_support
201

This externally-threaded library variant needs to be compiled against a set API, so we have this header with declarations like __libcpp_mutex_lock() which are referred from within the library sources (same function signatures as those used in LIBCPP_THREAD_API_PTHREAD - this allows us to keep the library source changes to a minimum).

Now, in this particular library variant, we want to differ these calls to runtime rather than libcxx compile-time!

On the other hand, I think you are proposing a compile-time (static) thread porting setup where the platform vendors need to supply a header file with appropriate definitions for these functions / types, and they would compile libcxx themselves? That sounds like a good idea, but the current patch is aiming for a different goal: we provide a pre-compiled libcxx library which calls out to those __libcpp_xxx functions at runtime, and platform vendors need to provide those functions, they don't have to compile libcxx themselves. This is what forces us to use opaque pointer types to capture platform-defined threading primitives.

Perhaps I could improve the naming here, may be LIBCPP_HAS_RUNTIME_THREAD_API? Or LIBCPP_HAS_DYNAMIC_THREAD_API?

bcraig added inline comments.May 24 2016, 1:05 PM
include/__threading_support
201

That is an excellent summary, and it does a good job of explaining the disconnect we were having. I'm going to push forward with the disconnect though :)

I think you can implement the dynamic case in terms of the static case without loss of generality or performance. You ("Vendor X") could pre-compile your library with a pointer-sized libcpp_mutex_t defined in <os_provided_thread.h>, and at runtime call out to a different library that has an implementation of libcpp_mutex_lock. Someone else ("Vendor Y") could have a larger libcpp_mutex_t defined, and provide a static inline definition of libcpp_mutex_lock.

rmaprath added inline comments.May 24 2016, 1:39 PM
include/__threading_support
201

OK, I think I understand your point :)

Will meditate on it a bit and spin a new patch tomorrow. Thanks!

rmaprath added inline comments.May 25 2016, 7:48 AM
include/__threading_support
201

There is a slight complication here. If we fully offload the external thread-API to platform vendors, nobody will be able to build this externally-threaded library variant using the vanilla libcxx sources, because they would have to first come up with a platform_threads.h header.

We could provide a dynamic_threads.h header with the method signatures only (which is selected if the platform_threads.h header is absent - and you get a "dynamically-threaded" library build). Now the library can be built + tested with the vanilla upstream sources. But note that we are introducing yet another header. I remember @mclow.lists mentioned that each additional header adds to the overhead of header lookup and we should try to keep that to a minimum.

We can workaround this additional header by not collecting it when building the normal library variant (so it doesn't add to the header lookup overhead).

Does that sound like an OK approach?

bcraig added inline comments.May 25 2016, 8:19 AM
include/__threading_support
201

Sounds reasonable.

rmaprath updated this revision to Diff 59363.Jun 2 2016, 4:41 AM
rmaprath edited edge metadata.

Addressed review comments from @bcraig.

@EricWF: Gentle ping.

bcraig added inline comments.Jun 2 2016, 2:23 PM
include/__threading_support
201

This #include deserves a comment. Some perplexed developer is going to come along later and wonder where __static_threading is supposed to come from.

rmaprath updated this revision to Diff 59519.Jun 3 2016, 2:56 AM

Added a comment about __static_threading and __dynamic_threading header includes.

@EricWF: Ping (sorry).

@mclow.lists, @EricWF: Ping.

@bcraig: I guess you wouldn't be able to let this through on your own? (in case if there isn't much interest in this functionality from others). Eric might be away though (didn't see many emails from him), so I'll hold off for now.

/ Asiri

mclow.lists edited edge metadata.EditedJun 7 2016, 9:06 AM

Some nits while reading this. More to come.

Also, I don't see how this can be retargeted "at runtime"; are you implying that someone can choose at program launch time what threading system to use?

How could that work given (say) a constexpr constructor for a mutex type?
[ I read the previous discussion, but still don't see how to make it work]

src/algorithm.cpp
99

Why the undef here?

src/mutex.cpp
277

Again; why undef at the end of a source file (not an include)?

For that matter, there's no need to use reserved identifiers here - __LOCKXXX

Also, I don't see how this can be retargeted "at runtime"; are you implying that someone can choose at program launch time what threading system to use?

Yup, users can link their objects with an implementation of the dynamic threading API and it should all be fine.

How could that work given (say) a constexpr constructor for a mutex type?
[ I read the previous discussion, but still don't see how to make it work]

Yeah, that is a real pain point. I have provided a sample implementation of the API in test/support/external_threads.cpp (last file in this patch) where I've adopted a initialize-on-first-use policy to workaround this particular problem. I've put a bunch of comments there to explain the details.

The constexpr mutex constructor is apparently also a pain point on windows [1], I'm sure @STL_MSFT knows more about it.

Thanks!

/ Asiri

[1] https://blogs.msdn.microsoft.com/vcblog/2015/06/02/constexpr-complete-for-vs-2015-rtm-c11-compiler-c17-stl/

Also, I don't see how this can be retargeted "at runtime"; are you implying that someone can choose at program launch time what threading system to use?

Yup, users can link their objects with an implementation of the dynamic threading API and it should all be fine.

Apologies, not at program launch time but link time.

[Asiri Rathnayake]

The constexpr mutex constructor is apparently also a
pain point on windows [1], I'm sure @STL_MSFT knows more about it.

That's because of MSVC-specific issues, where we need to dynamically switch between WinAPI and ConcRT implementations depending on whether the end user's machine is Win7+/Vista/XP. If we could assume Win7+, we wouldn't need this squirrelly machinery.

STL

[Asiri Rathnayake]

The constexpr mutex constructor is apparently also a
pain point on windows [1], I'm sure @STL_MSFT knows more about it.

That's because of MSVC-specific issues, where we need to dynamically switch between WinAPI and ConcRT implementations depending on whether the end user's machine is Win7+/Vista/XP. If we could assume Win7+, we wouldn't need this squirrelly machinery.

STL

Thanks. I was thinking may be it's because you also need to call some dynamic initialization routine from within the std::mutex constructor (like, to initialize a kernel provided mutex). Good to know that this isn't the case.

Cheers,

/ Asiri

rmaprath updated this revision to Diff 60017.Jun 8 2016, 5:06 AM
rmaprath edited edge metadata.

Addressed comments from @mclow.lists.

rmaprath marked 2 inline comments as done.Jun 8 2016, 5:06 AM

@mclow.lists, @EricWF: Gentle (and shameless) ping!

Apologies, not at program launch time but link time.

I'm OK with that; I think that's unnecessary complication, but not a deal-breaker.
The choosing at program launch seems unworkable to me.

[ I think that the threading implementation should be chosen by the provider of libc++, frankly. ]

I've adopted a initialize-on-first-use policy to workaround this particular problem.

That's not constexpr.

You say it in your comment "This prohibits any prospects of calling a runtime initialization routine", but then you have a runtime initialization routine.

I've adopted a initialize-on-first-use policy to workaround this particular problem.

That's not constexpr.

You say it in your comment "This prohibits any prospects of calling a runtime initialization routine", but then you have a runtime initialization routine.

std::mutex() constructor is still constexpr, what I've done is to defer the initialization to lock() and unlock() methods. This is pretty much the only way I can keep the constructor constexpr and allow an underlying platform implementation to provide the guts of std::mutex.

Hope that makes sense?

rmaprath added a comment.EditedJun 13 2016, 9:51 AM

Apologies, not at program launch time but link time.

I'm OK with that; I think that's unnecessary complication, but not a deal-breaker.
The choosing at program launch seems unworkable to me.

[ I think that the threading implementation should be chosen by the provider of libc++, frankly. ]

The problem with the latter is the number of different platforms we have to support, each with different threading primitives. For most RTOS platforms, pthreads is a big ask and even if they can provide a subset of <pthread.h>, different platforms will have different definitions of PTHREAD_MUTEX_INITIALIZER for example, making it very difficult for us to build libc++ in a platform agnostic way.

rmaprath abandoned this revision.Jul 4 2016, 4:58 AM

Reason for abandoning: This patch uses opaque pointers to delegate std::mutex and std::condition_variable types to the underlying platform implementation.

This creates problems given that these two types have constexpr constructors. We must make std::mutex::lock() (and the like) initialize the internal opaque pointer type before it is used (cannot initialize from the constructor), which in turn implies that the std::mutex::lock() method could fail if the implementation cannot allocate memory to hold the underlying platform mutex type - this is not a valid condition for the std::mutex::lock() method to fail. Besides, with this workaround, we are essentially trying to beat the purpose of a constexpr mutex constructor (be able to statically declare a mutex and use it without any explicit initialization).

After discussion with @mclow.lists, we think it's best to have libc++ vendors know the underlying mutex / condition variable types in advance (libc++ build time). I will create a new patch to implement this approach soon.