[libcxx-commits] [PATCH] D46140: [coroutines] Add std::experimental::task<T> type
Casey Carter via Phabricator via libcxx-commits
libcxx-commits at lists.llvm.org
Wed Mar 27 11:02:58 PDT 2019
CaseyCarter requested changes to this revision.
CaseyCarter added a subscriber: mclow.lists.
CaseyCarter added inline comments.
This revision now requires changes to proceed.
================
Comment at: include/experimental/__memory:80
+{
+ return (__s + __a - 1) & ~(__a - 1);
+}
----------------
This is missing preconditions that `__a` is a power of 2, and that `__s <= -__a`.
================
Comment at: include/experimental/task:141
+
+ void* __pointer = __charAllocator.allocate(
+ __get_padded_frame_size_with_allocator<_CharAlloc>(__size));
----------------
The return value of `allocate` isn't necessarily convertible to `void*`, it could be a fancy pointer. We should either `static_assert(is_same_v<typename allocator_traits<_CharAlloc>::void_pointer, void*>, "Piss off with your fancy pointers");` or use `pointer_traits` here and in `__deallocFunc` to unfancy and re-fancy the pointer.
================
Comment at: include/experimental/task:210
+private:
+ friend struct __task_promise_final_awaitable;
+
----------------
Pure style comment: I recommend using the non-elaborated `friend __task_promise_final_awaitable;` whenever possible. `friend struct foo` declares or redeclares `struct foo` in the enclosing namespace, whereas `friend foo` uses name lookup to find `foo` and makes it a friend. The latter form makes it far easier to analyze compiler errors when you screw something up in maintenance. (And on 480)
================
Comment at: include/experimental/task:220
+public:
+ __task_promise() _NOEXCEPT : __state_(_State::__no_value) {}
+
----------------
This mem-initializer for `__state_` is redundant with the default member initializer on 306.
================
Comment at: include/experimental/task:227
+ break;
+#ifndef _LIBCPP_NO_EXCEPTIONS
+ case _State::__exception:
----------------
I suggest moving the `case` label and `break` outside the `#ifndef` here so the compiler won't warn about this case being unhandled when `_LIBCPP_NO_EXCEPTIONS`.
================
Comment at: include/experimental/task:244
+ exception_ptr(current_exception());
+ __state_ = _State::__exception;
+#else
----------------
Are you certain that `unhandled_exception` can't possibly be called after storing a value? If so, this would leak the value.
================
Comment at: include/experimental/task:254
+ template <typename _Value,
+ enable_if_t<is_convertible<_Value, _Tp>::value, int> = 0>
+ void return_value(_Value&& __value)
----------------
Style: you've been using the `_v` variable templates for traits elsewhere.
================
Comment at: include/experimental/task:261
+ template <typename _Value>
+ auto return_value(std::initializer_list<_Value> __initializer) _NOEXCEPT_(
+ (is_nothrow_constructible_v<_Tp, std::initializer_list<_Value>>))
----------------
Unnecessary `std::` qualifier. (Occurs repeatedly.)
================
Comment at: include/experimental/task:263
+ (is_nothrow_constructible_v<_Tp, std::initializer_list<_Value>>))
+ -> std::enable_if_t<
+ std::is_constructible_v<_Tp, std::initializer_list<_Value>>> {
----------------
Style: the use of trailing-return-type SFINAE here is inconsistent with the use of template parameter SFINAE on 254.
================
Comment at: include/experimental/task:308
+ union {
+ char __empty_;
+ _Tp __value_;
----------------
These `__empty_` members seem extraneous.
================
Comment at: include/experimental/task:309
+ char __empty_;
+ _Tp __value_;
+ exception_ptr __exception_;
----------------
Should we `static_assert` that `_Tp` is a destructible object type?
================
Comment at: include/experimental/task:373
+template <>
+class __task_promise<void> final : public __task_promise_base {
+ using _Handle = coroutine_handle<__task_promise>;
----------------
Do we care about `task<cv-void>`?
================
Comment at: include/experimental/task:389
+
+ void __rvalue_result() { __throw_if_exception(); }
+
----------------
Should these `__foovalue_result` members be `foo`-qualified to make them harder to misuse?
================
Comment at: include/experimental/task:407
+ using promise_type = __task_promise<_Tp>;
+
+private:
----------------
Similar to above, should we `static_assert` that `_Tp` is `void`, an lvalue reference type, or a destructible non-array object type?
================
Comment at: include/experimental/task:453
+ decltype(auto) await_resume() {
+ return this->__coro_.promise().__lvalue_result();
+ }
----------------
`this->` is extraneous in these classes that derive from the concrete `_AwaiterBase`. (And on 470)
================
Comment at: include/experimental/task:458
+ _LIBCPP_ASSERT(__coro_,
+ "Undefined behaviour to co_await an invalid task<T>");
+ return _Awaiter{__coro_};
----------------
Is missing a subject. How about "co_await on an invalid task<T> has undefined behavior"? (And on 475)
================
Comment at: include/experimental/task:28
+#if defined(_LIBCPP_WARNING)
+_LIBCPP_WARNING("<experimental/task> cannot be used with this compiler")
+#else
----------------
"requires a compiler with support for coroutines" would be more informative.
================
Comment at: test/std/experimental/task/awaitable_traits.hpp:17
+
+_LIBCPP_BEGIN_NAMESPACE_EXPERIMENTAL_COROUTINES
+
----------------
It's super sketchy for test code to inject things into the production namespace and to use reserved names. It's also inappropriate to use libc++ internals in a test in the `std` tree. (Occurs repeatedly.)
================
Comment at: test/std/experimental/task/counted.hpp:81
+
+ static std::size_t nextId_;
+ static std::size_t defaultConstructedCount_;
----------------
IIUC that the tests all require C++17, you could declare these `inline` and avoid the external definitions / `DEFINE_COUNTED_VARIABLES` macro.
================
Comment at: test/std/experimental/task/counted.hpp:90
+#define DEFINE_COUNTED_VARIABLES() \
+ std::size_t counted::nextId_; \
+ std::size_t counted::defaultConstructedCount_; \
----------------
Should `nextId_` be initialized to `1`, as it is in `reset` on 40?
================
Comment at: test/std/experimental/task/manual_reset_event.hpp:18
+// manual_reset_event is a coroutine synchronisation tool that allows one
+// coroutine to await the event object and if the event was not crrently
+// in the 'set' state then will suspend the awaiting coroutine until some
----------------
"currently". Also, "was not currently" is weird.
================
Comment at: test/std/experimental/task/manual_reset_event.hpp:23
+{
+ friend class _Awaiter;
+
----------------
Here's a great example of why elaborated `friend` can be weird. This doesn't make the `_Awaiter` class defined on 25 a friend - which needn't be a friend, it's nested so it has full access to `manual_reset_event` - it declares a class named `_Awaiter` in the enclosing namespace that is a friend.
================
Comment at: test/std/experimental/task/manual_reset_event.hpp:25
+
+ class _Awaiter
+ {
----------------
Ditto "using reserved names in test code". (Occurs repeatedly.)
================
Comment at: test/std/experimental/task/manual_reset_event.hpp:40
+ {
+ _LIBCPP_ASSERT(
+ __event_->__state_.load(std::memory_order_relaxed) !=
----------------
`_LIBCPP_ASSERT` is inappropriate in `std` test code; it's undefined when the library under test is not libc++. Use `LIBCPP_ASSERT` (with no leading underscore) from `test/support/test_macros.h` to mean "this assertion must hold only if the library under test is libc++" and `assert` if the assertion must hold regardless of the tested library. (Occurs repeatedly.)
(Ignore the misuses of `_LIBCPP_ASSERT` that @mclow.lists put in the `span` tests; I'll fix them when we implement `span` and enable the tests.)
EDIT: Actually, I'll fix them now to save myself the trouble of figuring out why the tests are broken later.
================
Comment at: test/std/experimental/task/manual_reset_event.hpp:47
+
+ __event_->__awaitingCoroutine_ = __coro;
+
----------------
Why are we concerned about data races on `__event_->__state_`, but not `__event_->__awaitingCoroutine_`?
================
Comment at: test/std/experimental/task/manual_reset_event.hpp:49
+
+ // If the compare-exchange fails then this means that the event was
+ // already 'set' and so we should not suspend - this code path requires
----------------
s/this means that//; s/and so we/so we/; s/suspend - this/suspend. This/ (Ok, I'm nitpicking, but I actually did have issues parsing this comment.)
================
Comment at: test/std/experimental/task/manual_reset_event.hpp:86
+ // so that we have visibility of the writes to the coroutine frame in
+ // the current thrad before we resume it.
+ // Also needs to be 'release' in case the old value was 'not-set' so that
----------------
thread
================
Comment at: test/std/experimental/task/manual_reset_event.hpp:88
+ // Also needs to be 'release' in case the old value was 'not-set' so that
+ // another thread that subsequently awaits the
+ _State oldState = __state_.exchange(_State::__set, std::memory_order_acq_rel);
----------------
"awaits the" what? The suspense is killing me!
================
Comment at: test/std/experimental/task/manual_reset_event.hpp:92
+ {
+ _VSTD::exchange(__awaitingCoroutine_, {}).resume();
+ }
----------------
`std::exchange`
================
Comment at: test/std/experimental/task/manual_reset_event.hpp:102
+
+ // Note, we use 'relaxed' memory order here since it considered a
+ // data-race to call reset() concurrently either with operator co_await()
----------------
What's the antecedent of "it"?
================
Comment at: test/std/experimental/task/sync_wait.hpp:36-37
+ __isSet_ = true;
+ }
+ __cv_.notify_all();
+ }
----------------
lewissbaker wrote:
> The call to `notify_all()` needs to be inside the lock.
> Otherwise, it's possible the waiting thread may see the write to `__isSet_` inside `wait()` below and return, destroying the `condition_variable` before `__cv_.notify_all()` call completes.
The write to `__isSet_` must be under the lock, the call to `notify_all` need not be. (Not that I care either way, just clarifying.)
================
Comment at: test/std/experimental/task/sync_wait.hpp:14
+
+#include <experimental/__config>
+#include <experimental/coroutine>
----------------
There's no such header in the working draft or the Coroutines TS.
================
Comment at: test/std/experimental/task/sync_wait.hpp:70
+
+ friend struct _FinalAwaiter;
+
----------------
Ditto "doesn't do what you think it does and is unnecessary."
================
Comment at: test/std/experimental/task/sync_wait.hpp:115
+ break;
+#ifndef _LIBCPP_NO_EXCEPTIONS
+ case _State::__exception:
----------------
Ditto "not in test code". You should include `"test_macros.h"` and use `TEST_NO_EXCEPTIONS`. (Occurs repeatedly.)
================
Comment at: test/std/experimental/task/sync_wait.hpp:135
+#ifndef _LIBCPP_NO_EXCEPTIONS
+ ::new (static_cast<void*>(&__exception_)) exception_ptr(
+ std::current_exception());
----------------
`#include<new>` for the definition of True Placement New. (This may be missing above as well, I don't recall or have time to scroll back.)
================
Comment at: test/std/experimental/task/sync_wait.hpp:139
+#else
+ _VSTD::abort();
+#endif
----------------
Here's another `_VSTD` that should be `std`. Please audit all test code.
================
Comment at: test/std/experimental/task/task.basic/task_custom_allocator.pass.cpp:18
+#include <memory>
+#include <experimental/memory_resource>
+
----------------
This is not a Standard or Coroutines TS header.
================
Comment at: test/std/experimental/task/task.basic/task_custom_allocator.pass.cpp:26
+{
+ static size_t allocator_instance_count = 0;
+
----------------
`static` is extraneous in an anonymous namespace.
================
Comment at: test/std/experimental/task/task.basic/task_custom_allocator.pass.cpp:37
+ using difference_type = std::ptrdiff_t;
+ using is_always_equal = std::false_type;
+
----------------
These are the defaults for `size_type`, `difference_type`, and `is_always_equal`; you should omit them to verify that the tested code properly uses `allocator_traits`.
================
Comment at: test/std/experimental/task/task.basic/task_custom_allocator.pass.cpp:54
+ my_allocator(my_allocator&& other)
+ : totalAllocated_(std::move(other.totalAllocated_))
+ {
----------------
This is almost certainly going to cause problems, since it leaves the moved-from allocator invalid. (And on 68.)
================
Comment at: test/std/experimental/task/task.basic/task_custom_allocator.pass.cpp:75
+ {
+ --allocator_instance_count;
+ }
----------------
I suggest `assert(allocator_instance_count > 0)` before decrementing. We're testing code that explicitly destroys things, we should make sure it doesn't destroy more things than it constructs.
================
Comment at: test/std/experimental/task/task.basic/task_custom_allocator.pass.cpp:78
+
+ char* allocate(size_t n) {
+ const auto byteCount = n * sizeof(T);
----------------
`allocate` needs to return `T*`. (Also, `std::size_t`.) (And on 88)
================
Comment at: test/std/experimental/task/task.basic/task_custom_allocator.pass.cpp:90
+ const auto byteCount = n * sizeof(T);
+ *totalAllocated_ -= byteCount;
+ std::free(p);
----------------
Here again, I suggest an assertion that we're not freeing more bytes than have been allocated.
================
Comment at: test/std/experimental/task/task.basic/task_custom_allocator.pass.cpp:221
+
+int main()
+{
----------------
In libc++ tests `main` is always declared `int main(int, char**)` (and explicitly returns `0`) to support the freestanding work. (Occurs repeatedly.)
================
Comment at: test/std/experimental/task/task.lifetime/task_parameter_lifetime.pass.cpp:46
+ // calling the move-constructor. This move could also potentially be
+ // elided by the
+ assert(counted::move_constructor_count() <= 1);
----------------
Again, leaving me in suspense.
Repository:
rCXX libc++
CHANGES SINCE LAST ACTION
https://reviews.llvm.org/D46140/new/
https://reviews.llvm.org/D46140
More information about the libcxx-commits
mailing list