[Bug libstdc++/115402] std::atomic_ref compile-error in compare_exchange_[weak/strong]() and wait()
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=115402 --- Comment #7 from Lewis Baker --- This paper has now been published and is available at: https://isocpp.org/files/papers/P3323R0.html
[Bug libstdc++/115402] New: std::atomic_ref compile-error in compare_exchange_[weak/strong]() and wait()
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=115402 Bug ID: 115402 Summary: std::atomic_ref compile-error in compare_exchange_[weak/strong]() and wait() Product: gcc Version: 15.0 Status: UNCONFIRMED Severity: normal Priority: P3 Component: libstdc++ Assignee: unassigned at gcc dot gnu.org Reporter: lewissbaker.opensource at gmail dot com Target Milestone: --- See https://godbolt.org/z/q4jYvPaah The following code-snippet fails to compile: volatile int vi = 0; std::atomic_ref vref(vi); int val = vref.load(); // ok vref.exchange(val); // ok vref.compare_exchange_weak(val, 0); // error vref.compare_exchange_strong(val, 0); // error vref.wait(0); // error Fails with error messages like: .../include/c++/15.0.0/bits/atomic_base.h: In instantiation of 'bool std::__atomic_ref<_Tp, true, false>::compare_exchange_weak(_Tp&, _Tp, std::memory_order, std::memory_order) const [with _Tp = volatile int]': /opt/compiler-explorer/gcc-trunk-20240609/include/c++/15.0.0/bits/atomic_base.h:1674:30: required from 'bool std::__atomic_ref<_Tp, true, false>::compare_exchange_weak(_Tp&, _Tp, std::memory_order) const [with _Tp = volatile int]' 1674 | return compare_exchange_weak(__expected, __desired, __order, |~^~~~ 1675 | __cmpexch_failure_order(__order)); | ~ :8:31: required from here 8 | vref.compare_exchange_weak(val, 0); // error | ~~^~~~ .../include/c++/15.0.0/bits/atomic_base.h:1656:58: error: binding reference of type 'std::__atomic_impl::_Val&' {aka 'int&'} to 'volatile int' discards qualifiers 1656 | return __atomic_impl::compare_exchange_weak( |~~^ 1657 | _M_ptr, __expected, __desired, __success, __failure); | .../include/c++/15.0.0/bits/atomic_base.h:1119:52: note: initializing argument 2 of 'bool std::__atomic_impl::compare_exchange_weak(_Tp*, _Val<_Tp>&, _Val<_Tp>, std::memory_order, std::memory_order, bool) [with bool _AtomicRef = true; _Tp = volatile int; _Val<_Tp> = int]' 1119 | compare_exchange_weak(_Tp* __ptr, _Val<_Tp>& __expected, | ~~~^~
[Bug libstdc++/106183] New: std::atomic::wait might deadlock on platforms without platform_wait()
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=106183 Bug ID: 106183 Summary: std::atomic::wait might deadlock on platforms without platform_wait() Product: gcc Version: unknown Status: UNCONFIRMED Severity: normal Priority: P3 Component: libstdc++ Assignee: unassigned at gcc dot gnu.org Reporter: lewissbaker.opensource at gmail dot com Target Milestone: --- I have been doing some research on implementations of std::atomic::notify_all() and std::atomic::wait() as part of a C++ paper I've been working on. I've recently been studying the libc++ implementation and I think I have discovered a potential bug in the implementation for platforms that do not have __GLIBCXX_HAVE_PLATFORM_WAIT defined (i.e. that don't have futex syscall or similar) and for std::atomic types where T is different from __platform_wait_t. I believe there is potential for a thread calling x.wait(old) to fail to be unblocked by a call by another thread to x.notify_all() after modifying the value to something other than 'old'. I have reduced the current implementation of the std::atomic::wait() and std::atomic::notify_all() functions and I believe the code currently in trunk to be effectively equivalent to the following code-snippet: -- using __platform_wait_t = std::uint64_t; struct __waiter_pool { std::atomic<__platform_wait_t> _M_wait{0}; std::mutex _M_mut; std::atomic<__platform_wait_t> _M_ver{0}; std::condition_variable _M_cv; static __waiter_pool& _S_for(void* __addr) noexcept { constexpr uintptr_t __count = 16; static __waiter_pool __x[__count]; uintptr_t __key = (((uintptr_t)__addr) >> 2) % __count; return __x[__key]; } }; template bool __atomic_compare(const _Tp& __a, const _Tp& __b) noexcept { return std::memcmp(std::addressof(__a), std::addressof(__b), sizeof(_Tp)) == 0; } template void atomic::wait(T __old, memory_order __mo = memory_order_seq_cst) noexcept { __waiter_pool& __w = __waiter_pool::_S_for(this); __w._M_wait.fetch_add(1, std::memory_order_seq_cst); do { __platform_wait_t __val1 = __w._M_ver.load(std::memory_order_acquire); if (!__atomic_compare(__old, this->load(__mo))) { return; } __platform__wait_t __val2 = __w._M_ver.load(std::memory_order_seq_cst); // < BUG: problem if notify_all() is executed at this point if (__val2 == __val1) { lock_guard __lk(__w._M_mtx); __w._M_cv.wait(__lk); } } while (__atomic_compare(__old, this->load(__mo))); __w._M_wait.fetch_sub(1, std::memory_order_release); } void atomic::notify_all() noexcept { __waiter_pool& __w = __waiter_pool::_S_for(this); __w._M_ver.fetch_add(1, memory_order_seq_cst); if (__w._M_wait.load(memory_order_seq_cst) != 0) { __w._M_cv.notify_all(); } } --- The wait() method reads the _M_ver value then checks whether the value being waited on has changed and then if it has not, then reads the _M_ver value again. If the two values read from _M_ver are the same then we can infer that there has not been an intervening call to notify_all(). However, after checking that _M_ver has not changed, it then (and only then) proceeds to acquire the lock on the _M_mut mutex and then waits on the _M_cv condition variable. The problem occurs if the waiting thread happens to be delayed between reading _M_ver for the second time and blocking inside the call to _M_cv.wait() (indicated with a comment). If this happens, it may be possible then for another thread that was supposed to unblock this thread to then modify the atomic value, call notify_all() which increments _M_ver and calls _M_cv.notify_all(), all before the waiting thread acquires the mutex and blocks on the condition-variable. If this happens and no other thread is subsequently going to call notify_all() on the atomic variable then it's possible the call to wait() will block forever as it missed its wake-up call. The solution here is to do more work while holding the mutex. I haven't fully verified the correctness of the following code, but I think it should help to avoid the missed wake-ups situations that are possible in the current implementation. It does come at a higher synchronisation cost, however, as notifying threads also need to acquire the mutex. - template void atomic::wait(T __old, memory_order __mo = memory_order_seq_cst) noexcept { __waiter_pool& __w = __waiter_pool::_S_for(this); __w._M_wait.fetch_add(1, std::memory_order_seq_cst); do { __platform_wait_t __val1 = __w._M_ver.load(std::memory_order_acquire); if (!__atomic_compare(__old, this->load(__mo))) { return; } __platform__wait_t __val2 = __w._M_ver.load(std::memory_order_seq_cst); if (__val2 == __val1) { lock_guard __lk(__w._M_mtx)
[Bug c++/101247] New: ICE when using static constexpr bool defined in base-class in derived class constructor requires-clause
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=101247 Bug ID: 101247 Summary: ICE when using static constexpr bool defined in base-class in derived class constructor requires-clause Product: gcc Version: unknown Status: UNCONFIRMED Severity: normal Priority: P3 Component: c++ Assignee: unassigned at gcc dot gnu.org Reporter: lewissbaker.opensource at gmail dot com Target Milestone: --- Compiling the following code with GCC trunk and flags: -std=c++20 ``` template struct in_place_type_t {}; template inline constexpr in_place_type_t in_place_type; template inline constexpr bool outer_helper_v = true; template struct foo { struct type; template static constexpr bool helper_v = outer_helper_v; }; template struct foo::type { template requires helper_v type(in_place_type_t) {} }; int main() { foo::type x(in_place_type); } ``` results in an internal compiler-error ``` : In substitution of 'template requires helper_v foo::type::type(in_place_type_t) [with U = {int}]': :26:40: required from here :21:18: internal compiler error: tree check: accessed elt 1 of 'tree_vec' with 0 elts in tsubst_pack_expansion, at cp/pt.c:13084 21 | requires helper_v | ^~~ 0x1d4c179 internal_error(char const*, ...) ???:0 0x685746 tree_vec_elt_check_failed(int, int, char const*, int, char const*) ???:0 0x99e0bc tsubst_pack_expansion(tree_node*, tree_node*, int, tree_node*) ???:0 0x9afa62 tsubst_template_args(tree_node*, tree_node*, int, tree_node*) ???:0 0x9b064b tsubst_argument_pack(tree_node*, tree_node*, int, tree_node*) ???:0 0x9afa04 tsubst_template_args(tree_node*, tree_node*, int, tree_node*) ???:0 0x983ebd tsubst(tree_node*, tree_node*, int, tree_node*) ???:0 0x7abcda constraints_satisfied_p(tree_node*, tree_node*) ???:0 0x9c26c3 fn_type_unification(tree_node*, tree_node*, tree_node*, tree_node* const*, unsigned int, tree_node*, unification_kind_t, int, conversion**, bool, bool) ???:0 0x74abc9 build_new_method_call(tree_node*, tree_node*, vec**, tree_node*, int, tree_node**, int) ???:0 0x74c8c0 build_special_member_call(tree_node*, tree_node*, vec**, tree_node*, int, int) ???:0 0x8611c8 build_aggr_init(tree_node*, tree_node*, int, int) ???:0 0x81f0d9 cp_finish_decl(tree_node*, tree_node*, bool, tree_node*, int) ???:0 0x94f36d c_parse_file() ???:0 0xad2922 c_common_parse_file() ???:0 ``` See https://godbolt.org/z/ebnxfsMs9 for an example.
[Bug c++/97452] [coroutines] incorrect sequencing of await_resume() when multiple co_await expressions occur in a single statement
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=97452 --- Comment #9 from Lewis Baker --- > In terms of the standard do you think this is technically undefined behaviour? Yes, I think this is something that Gor was looking into as a wording issue that could do with some clarification. I think the suggestion was something along the lines of adding some wording to ensure that the evaluation of a an await-expression was sequenced atomically with respect to the evaluation of other expressions in the statement.
[Bug libstdc++/99537] New: Wrong memory_order used in stop_token ref-counting
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=99537 Bug ID: 99537 Summary: Wrong memory_order used in stop_token ref-counting Product: gcc Version: unknown Status: UNCONFIRMED Severity: normal Priority: P3 Component: libstdc++ Assignee: unassigned at gcc dot gnu.org Reporter: lewissbaker.opensource at gmail dot com Target Milestone: --- In the implementation of stop_token the _Stop_state_t implements reference-counting for tracking shared ownership of the stop-state. This is done via two methods: void _M_add_owner() noexcept { _M_owners.fetch_add(1, memory_order::relaxed); } void _M_release_ownership() noexcept { if (_M_owners.fetch_sub(1, memory_order::release) == 1) delete this; } Other than initialising the _M_owners atomic value to 1, these are the only two accesses of the _M_owners variable. The 'fetch_sub()' operation in _M_release_ownership() should be using memory_order::acq_rel instead of memory_order::release. The use of 'release' only is insufficient as it does not synchronise with any corresponding 'acquire' operation. With the current implementation, it's possible that a prior write to one of the _M_value or _M_head data-members by a thread releasing the second-to-last reference might not be visible to another thread that releases the last reference and frees the memory, resulting in potential write to freed memory.
[Bug c++/97452] New: [coroutines] incorrect sequencing of await_resume() when multiple co_await expressions occur in a single statement
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=97452 Bug ID: 97452 Summary: [coroutines] incorrect sequencing of await_resume() when multiple co_await expressions occur in a single statement Product: gcc Version: unknown Status: UNCONFIRMED Severity: normal Priority: P3 Component: c++ Assignee: unassigned at gcc dot gnu.org Reporter: lewissbaker.opensource at gmail dot com Target Milestone: --- See https://godbolt.org/z/9Kj9o8 Compile the following program with GCC trunk and flags: -std=c++20 #include #include #include #include #include struct task { struct promise_type { task get_return_object() noexcept { return task{std::coroutine_handle::from_promise(*this)}; } std::suspend_always initial_suspend() noexcept { std::puts("task initial_suspend"); return {}; } struct yield_awaiter { bool await_ready() noexcept { return false; } auto await_suspend(std::coroutine_handle h) noexcept { std::puts("task yielding/returning value"); return std::exchange(h.promise().continuation, {}); } void await_resume() noexcept { std::puts("task resumed"); } }; yield_awaiter yield_value(int x) noexcept { value = x; return {}; } void return_value(int x) noexcept { value = x; } yield_awaiter final_suspend() noexcept { return {}; } [[noreturn]] void unhandled_exception() noexcept { std::terminate(); } int value; std::coroutine_handle<> continuation; }; explicit task(std::coroutine_handle coro) noexcept : coro(coro) {} task(task&& t) noexcept : coro(std::exchange(t.coro, {})) {} ~task() { if (coro) coro.destroy(); } __attribute__((noinline)) bool await_ready() { std::puts("task::await_ready"); return false; } __attribute__((noinline)) auto await_suspend(std::coroutine_handle<> h) noexcept { std::puts("task::await_suspend"); coro.promise().continuation = h; return coro; } __attribute__((noinline)) int await_resume() { std::puts("task::await_resume"); return coro.promise().value; } std::coroutine_handle coro; }; task two_values() { co_yield 1; co_return 2; } task example() { task t = two_values(); int result = co_await t + co_await t; std::printf("result = %i (should be 3)\n", result); std::fflush(stdout); co_return result; } int main() { task t = example(); t.coro.resume(); assert(t.coro.done()); } Expected output: task initial_suspend task initial_suspend task::await_ready task::await_suspend task yielding/returning value task::await_resume task::await_ready task::await_suspend task resumed task yielding/returning value task::await_resume result = 3 (should be 3) Actual output: task initial_suspend task initial_suspend task::await_ready task::await_suspend task yielding/returning value task::await_ready task::await_suspend task resumed task yielding/returning value task::await_resume task::await_resume result = 4 (should be 3) Note that the output indicates the the compiler is only calling await_resume() on both awaiters immediately before evaluating the plus-expression rather than evaluating await_resume() on the first awaiter immediately upon resuming from the first suspend-point and before evaluating await_ready() for the second awaiter.
[Bug c++/95736] coroutine method improperly copies awaitable
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95736 Lewis Baker changed: What|Removed |Added CC||lewissbaker.opensource@gmai ||l.com --- Comment #1 from Lewis Baker --- I've also investigated this a bit more and have annotated the assembly of the code being generated by GCC in this example (slightly modified). https://godbolt.org/z/_HJNHC Immediately before the 'co_await foo' expression the compiler generates a copy of the this->foo member into the coroutine frame. But it does this doing the equivalent of a memcpy() rather than calling the copy-constructor. The compiler should not be making this copy of the operand to 'co_await' and should instead be passing the address of the 'this->foo' member to the as the implicit object parameter in the call to await_ready/await_suspend/await_resume.
[Bug c++/95616] New: [coroutines] coroutines with potentially-throwing 'co_await promise.final_suspend()' expressions should be ill-formed
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95616 Bug ID: 95616 Summary: [coroutines] coroutines with potentially-throwing 'co_await promise.final_suspend()' expressions should be ill-formed Product: gcc Version: 10.1.1 Status: UNCONFIRMED Severity: normal Priority: P3 Component: c++ Assignee: unassigned at gcc dot gnu.org Reporter: lewissbaker.opensource at gmail dot com Target Milestone: --- The wording of [dcl.fct.def.coroutine]/15 states: > The expression co_await promise.final_suspend() shall not be > potentially-throwing ([except.spec]). See http://eel.is/c++draft/dcl.fct.def.coroutine#15 and http://eel.is/c++draft/except.spec#6 I believe this means that the compiler should reject with a diagnostic any coroutine with a promise_type where `co_await promise.final_suspend()` is not noexcept. ie. all of the following must be declared noexcept (if they form part of the await-expression): - promise_type::final_suspend() - finalSuspendObj.operator co_await() - finalSuspendAwaiter.await_ready() - finalSuspendAwaiter.await_suspend() - finalSuspendAwaiter.await_resume() - finalSuspedObj destructor - finalSuspendAwaiter destructor GCC (and Clang) currently accepts code that that does not declare final_suspend() as noexcept. e.g. see https://godbolt.org/z/Et_5eo
[Bug c++/95615] New: [coroutines] Coroutine frame and promise is leaked if exception thrown from promise.initial_suspend()
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95615 Bug ID: 95615 Summary: [coroutines] Coroutine frame and promise is leaked if exception thrown from promise.initial_suspend() Product: gcc Version: 10.1.1 Status: UNCONFIRMED Severity: normal Priority: P3 Component: c++ Assignee: unassigned at gcc dot gnu.org Reporter: lewissbaker.opensource at gmail dot com Target Milestone: --- If an exception is thrown from any of the following: - promise_type constructor - promise.get_return_object() - promise.initial_suspend() - initSuspendAwaitable.await_ready() - initSuspendAwaitable.await_suspend() Then I believe the compiler is required to automatically destroy the promise (if constructor completed successfully), destroy the return return-object (if get_return_object() completed successfully) and free the coroutine frame before letting the exception propagate to the caller. However, it seems that this cleanup logic is not currently begin called by GCC in these situations. For example, see https://godbolt.org/z/kQWjpF which shows the behaviour when an exception is thrown from promise.initial_suspend()'s await_suspend() method (other variants are commented out in the code). The wording described in [dcl.fct.def.coroutine] p5 in conjunction with p11 seems to indicate that the promise object should be destroyed as the exception propagates out of the coroutine.
[Bug c++/95599] [coroutines] destructor for temporary operand to co_yield expression called before end of full-expression
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95599 --- Comment #3 from Lewis Baker --- I don't think that whether or not the awaitables or operands to co_yield are captured by reference / aliased should play into the sequencing of calls to the destructor. The destructors of temporaries should always be sequenced to occur at the semicolon, which will always happen after the coroutine resumes. For another clearer example, see https://godbolt.org/z/tKQVnb e.g. changing the example -- struct resource { int x; resource(int x) : x(x) { std::printf("resource(%i)\n", x); } ~resource() { std::printf("~resource(%i)\n", x); } resource(resource&&) = delete; }; generator f() { (resource{1}, co_yield resource{2}); } -- Here we are creating temporaries and sequencing expressions using operator,(). The destructor of both resource{1} and resource{2} should both occur at the semicolon, after the coroutine suspends and resumes, regardless of whether the object is captured by reference in an awaitable. ie. the expected output of the modified program should be (this is what clang does): ``` resource(1) resource(2) awaitable::await_suspend() awaitable::await_resume() ~resource(2) ~resource(1) ``` But the observed output with GCC is: ``` resource(2) awaitable::await_suspend() resource(1) awaitable::await_resume() ~resource(1) ``` Also, I'm not sure why GCC is evaluating the second operand to operator,() first and why it is constructing resource{1} _after_ the coroutine has suspended. Nothing should happen in the coroutine body between await_suspend() and await_resume().
[Bug c++/95599] [coroutines] destructor for temporary operand to co_yield expression called before end of full-expression
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95599 --- Comment #1 from Lewis Baker --- Note there is also some different behaviour if we change the example slightly by removing awaitable::~awaitable() and leaving the awaitable type with a trivial destructor. See https://godbolt.org/z/ff5Uvy In this case, instead of the resource destructor being called before the coroutine suspends, the resource destructor is never called. ie. observed output becomes: ``` resource() awaitable::await_suspend() awaitable::await_resume() ```
[Bug c++/95599] New: [coroutines] destructor for temporary operand to co_yield expression called before end of full-expression
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95599 Bug ID: 95599 Summary: [coroutines] destructor for temporary operand to co_yield expression called before end of full-expression Product: gcc Version: 10.1.1 Status: UNCONFIRMED Severity: normal Priority: P3 Component: c++ Assignee: unassigned at gcc dot gnu.org Reporter: lewissbaker.opensource at gmail dot com Target Milestone: --- If I write the statement: co_yield T{}; Then I expect the T constructor to be called before the coroutine suspends and that the T destructor will not be called until after the coroutine resumes and execution reaches the semicolon (ie. end of full expression). However, I am observing that the T destructor is being called before the await_suspend() method on the awaitable returned from promise.yield_value() is called (ie. before the coroutine suspends). To reproduce, compile the following code sample using GCC trunk. Flags: -std=c++2a -fcoroutines --- #include using namespace std; #include #include struct resource { resource() { std::printf("resource()\n"); } ~resource() { std::printf("~resource()\n"); } resource(resource&&) = delete; }; template struct generator { struct promise_type { generator get_return_object() { return generator{coroutine_handle::from_promise(*this)}; } void return_void() {} void unhandled_exception() {} suspend_always initial_suspend() { return {}; } suspend_always final_suspend() { return {}; } struct awaitable { resource& r; awaitable(resource&& r) : r(r) {} ~awaitable() {} bool await_ready() noexcept { return false; } void await_suspend(coroutine_handle<> h) noexcept { std::printf("awaitable::await_suspend()\n"); } void await_resume() noexcept { std::printf("awaitable::await_resume()\n"); } }; awaitable yield_value(resource&& r) { return awaitable{std::move(r)}; } }; generator(coroutine_handle coro) : coro(coro) {} generator(generator&& g) noexcept : coro(std::exchange(g.coro, {})) {} ~generator() { if (coro) { coro.destroy(); } } coroutine_handle coro; }; generator f() { co_yield resource{}; } int main() { generator x = f(); x.coro.resume(); x.coro.resume(); } --- The expected output of this program is: ``` resource() awaitable::await_suspend() awaitable::await_resume() ~resource() ``` However, the observed output of this program is: ``` resource() ~resource() awaitable::await_suspend() awaitable::await_resume() ``` i.e. the temporary is not being kept alive until the end of the full-expression containing the co_yield statement.
[Bug c++/95598] New: [coroutines] Destructor for object returned from get_return_object() never called
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95598 Bug ID: 95598 Summary: [coroutines] Destructor for object returned from get_return_object() never called Product: gcc Version: 10.1.1 Status: UNCONFIRMED Severity: normal Priority: P3 Component: c++ Assignee: unassigned at gcc dot gnu.org Reporter: lewissbaker.opensource at gmail dot com Target Milestone: --- Compile the following code with GCC trunk with flags -std=c++2a -fcoroutines See https://godbolt.org/z/zTjjGm #include #include using namespace std; struct generator { struct promise_type { generator get_return_object() { return generator{coroutine_handle::from_promise(*this)}; } void return_void() {} void unhandled_exception() {} suspend_always initial_suspend() { return {}; } suspend_always final_suspend() { return {}; } }; generator(coroutine_handle coro) { std::printf("generator() @ %p\n", this); } generator(generator&& g) noexcept { std::printf("generator(move from %p) @ %p\n", &g, this); } ~generator() { std::printf("~generator() @ %p\n", this); } }; generator f() { co_return; } int main() { generator g = f(); } The expected output of this program is following (assuming copy-elision has occurred - this is what Clang outputs): ``` generator() @ ~generator() @ ``` Or if copy elision has not occurred, at least the number of constructor/destructor calls should be balanced: ``` generator() @ generator(move from ) @ ~generator() @ ~generator() @ ``` However, the observed output is: ``` generator() @ generator(move from ) @ ~generator() @ ``` ie. the destructor of the generator constructed at is never called. In this case the object constructed at is the object returned from promise.get_return_object() and the object constructed at is the object returned from the coroutine ramp function f(). It seems that the coroutine ramp function is failing to call the destructor of the object returned from promise.get_return_object() when the coroutine ramp function returns. Normally, this omission of the call to the destructor would not be observed as the return-value is moved and the destructor of a moved-from object has no effect, but that is not always the case.
[Bug c++/95591] New: [coroutines] ICE when co_yielding string literal from generator coroutine
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95591 Bug ID: 95591 Summary: [coroutines] ICE when co_yielding string literal from generator coroutine Product: gcc Version: 10.0 Status: UNCONFIRMED Severity: normal Priority: P3 Component: c++ Assignee: unassigned at gcc dot gnu.org Reporter: lewissbaker.opensource at gmail dot com Target Milestone: --- Build the following code sample using GCC trunk. Command-line flags: -std=c++2a -fcoroutines -- #include using namespace std; struct generator { struct promise_type { generator get_return_object(); void return_void(); void unhandled_exception(); suspend_always initial_suspend(); suspend_always final_suspend(); template suspend_always yield_value(Arg&&) { return {}; } }; }; generator x() { co_yield "foo"; } -- Fails with following error: : In function 'generator x()': :23:1: internal compiler error: in captures_temporary, at cp/coroutines.cc:2773 23 | } | ^ See https://godbolt.org/z/UUbcKQ
[Bug c++/95350] New: [coroutines] coroutines with move-only by-value parameters attempt to copy parameter instead of move it
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95350 Bug ID: 95350 Summary: [coroutines] coroutines with move-only by-value parameters attempt to copy parameter instead of move it Product: gcc Version: 10.0 Status: UNCONFIRMED Severity: normal Priority: P3 Component: c++ Assignee: unassigned at gcc dot gnu.org Reporter: lewissbaker.opensource at gmail dot com Target Milestone: --- The following code fails to compile under GCC 10.0 and GCC trunk. Compile with -fcoroutines -std=c++2a See https://godbolt.org/z/zB_RVC This code compiles successfully under both Clang and MSVC coroutines implementations. - #include struct task { struct promise_type { task get_return_object(); void return_void(); void unhandled_exception(); std::suspend_always initial_suspend() noexcept; std::suspend_always final_suspend() noexcept; }; }; struct move_only { move_only(); move_only(const move_only&) = delete; move_only(move_only&) = delete; move_only(move_only&&) = default; }; task f(move_only x) { co_return; } - Fails with compile-error: : In function 'task f(move_only)': :30:1: error: use of deleted function 'move_only::move_only(const move_only&)' 30 | } | ^ :24:5: note: declared here 24 | move_only(move_only&) = delete; | ^ When copying parameters into the coroutine frame which are passed by value it should be initialising the parameter copy using an xvalue referring to the original function parameter. Whereas it seems like GCC is attempting to initialise the parameter-copy with an lvalue-reference to the original parameter, which ends up calling the copy-constructor. See http://eel.is/c++draft/dcl.fct.def.coroutine#13 > ... the copy is a variable of type cv T with automatic storage duration > that is direct-initialized from an xvalue of type T referring to the > parameter ...
[Bug c++/95346] New: [coroutines] coroutine return-type should be initialised with rvalue if different from get_return_object() return-type
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95346 Bug ID: 95346 Summary: [coroutines] coroutine return-type should be initialised with rvalue if different from get_return_object() return-type Product: gcc Version: 10.0 Status: UNCONFIRMED Severity: normal Priority: P3 Component: c++ Assignee: unassigned at gcc dot gnu.org Reporter: lewissbaker.opensource at gmail dot com Target Milestone: --- If I have the following code: -- #include struct task { struct promise_type { task get_return_object(); void return_void(); void unhandled_exception(); std::suspend_always initial_suspend() noexcept; std::suspend_always final_suspend() noexcept; }; }; struct wrapper { using promise_type = task::promise_type; wrapper(task&&); }; wrapper f() { co_return; } -- Then building with GCC 10.0 and GCC trunk with -std=c++20 -fcoroutines fails with the following compile-error: x86-64 gcc (trunk) -O3 -std=c++2a -Wall -fcoroutines 1 x86-64 gcc (trunk) - 368ms : In function 'wrapper f()': error: cannot bind rvalue reference of type 'task&&' to lvalue of type 'task' 30 | } | ^ note: initializing argument 1 of 'wrapper::wrapper(task&&)' 25 | wrapper(task&&); I think the return-value of a coroutine should be initialised with an rvalue-reference to the object returned from get_return_object() if the two objects have different types. GCC seems to be trying to initialise the return-value using an lvalue-reference to the object returned from get_return_object(). Both Clang and MSVC accept this code, while GCC does not. See https://godbolt.org/z/4RRtaL
[Bug c++/95017] [coroutines] Failure to generate code for co_yield expression if its the only statement in a loop
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95017 --- Comment #1 from Lewis Baker --- Created attachment 48487 --> https://gcc.gnu.org/bugzilla/attachment.cgi?id=48487&action=edit test.cpp
[Bug c++/95017] New: [coroutines] Failure to generate code for co_yield expression if its the only statement in a loop
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95017 Bug ID: 95017 Summary: [coroutines] Failure to generate code for co_yield expression if its the only statement in a loop Product: gcc Version: 10.0 Status: UNCONFIRMED Severity: normal Priority: P3 Component: c++ Assignee: unassigned at gcc dot gnu.org Reporter: lewissbaker.opensource at gmail dot com Target Milestone: --- See https://godbolt.org/z/v-Tkvi Given a simple coroutine generator type, if I compile the following coroutine with GCC it fails to generate code for the 'co_yield' expression if the co_yield is the only statement in the loop body. However, if I add another statement after the co_yield then it correctly generates code for the co_yield expression. Works: -std=c++2a -fcoroutines -DWITH_EXTRA_STATEMENT Fails: -std=c++2a -fcoroutines - generator test() { double i = 0; while(i++ < 5) { // If the second statement below is compiled-out then GCC fails to generate // code for the co_yield expression and the loop completes without // ever suspending. co_yield i * 2; #ifdef WITH_EXTRA_STATEMENT (void)i; #endif } } int main() { for (auto i : test()) { printf("%f\t", i); } } -
[Bug c++/94752] New: [coroutines] compiler ICE with coroutine with unnamed parameter
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=94752 Bug ID: 94752 Summary: [coroutines] compiler ICE with coroutine with unnamed parameter Product: gcc Version: 10.0 Status: UNCONFIRMED Severity: normal Priority: P3 Component: c++ Assignee: unassigned at gcc dot gnu.org Reporter: lewissbaker.opensource at gmail dot com Target Milestone: --- See https://godbolt.org/z/QCXqeo Compile the following source code with GCC trunk. Flags: -std=c++20 -fcoroutines - #include using namespace std; struct task { struct promise_type { promise_type() {} task get_return_object() { return {}; } suspend_never initial_suspend() { return {}; } suspend_never final_suspend() { return {}; } void return_void() {} void unhandled_exception() {} }; }; task foo(int) { co_return; } - Results in an internal-compile-error - segmentation-fault. Pointing at closing curly-brace of 'foo()'. Seems to be related to the parameter being unnamed as naming the parameter results in this code compiling successfully.
[Bug c++/70790] Can't mangle noexcept expressions
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=70790 Lewis Baker changed: What|Removed |Added CC||lewissbaker.opensource@gmai ||l.com --- Comment #2 from Lewis Baker --- The relevant code seems to live in gcc/cp/mangle.c (at least for IA64 mangling) Does it just need something like the following inserted into the write_expression() function? ``` else if (TREE_CODE (expr) == NOEXCEPT_EXPR) { write_string ("nx"); write_expression (TREE_OPERAND (expr, 0)); } ``` The "nx" prefix seems to be what LLVM uses for its Itanium mangling. See https://github.com/llvm/llvm-project/blob/13a1504ffb9a9a4f82dc1b60d9e8cbb173c7d030/clang/lib/AST/ItaniumMangle.cpp#L4057 Also see example showing this under clang (working) and gcc (failing): https://godbolt.org/z/yHrZDm
[Bug c++/92933] New: [coroutines] compiler ICE compiling coroutine with try/catch
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=92933 Bug ID: 92933 Summary: [coroutines] compiler ICE compiling coroutine with try/catch Product: gcc Version: unknown Status: UNCONFIRMED Severity: normal Priority: P3 Component: c++ Assignee: unassigned at gcc dot gnu.org Reporter: lewissbaker.opensource at gmail dot com Target Milestone: --- Created attachment 47490 --> https://gcc.gnu.org/bugzilla/attachment.cgi?id=47490&action=edit Example program Compiling the attached program crashes the compiler with a segmentation fault when compiling with gcc coroutines branch. See https://godbolt.org/z/kCNVfQ This has a simple coroutine that looks like: struct some_error {}; task foo() { try { co_return; } catch (some_error) { } } Compilation with '-std=c++2a' crashes with the following error: : In function 'task foo()': :64:1: internal compiler error: Segmentation fault 64 | } | ^ Removing the try/catch compiles fine. Replacing the catch(some_error) with catch(...) compiles fine.
[Bug libstdc++/92895] New: [libstdc++] stop_token conformance issues
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=92895 Bug ID: 92895 Summary: [libstdc++] stop_token conformance issues Product: gcc Version: unknown Status: UNCONFIRMED Severity: normal Priority: P3 Component: libstdc++ Assignee: unassigned at gcc dot gnu.org Reporter: lewissbaker.opensource at gmail dot com Target Milestone: --- The current implementation of std::stop_token in trunk has a few issues that make it non-conformant with regards to the latest draft specification N4842. The version I reviewed was: https://github.com/gcc-mirror/gcc/blob/87f8610d5d61ceda80ca91be963bdf3e989af4f6/libstdc%2B%2B-v3/include/std/stop_token Note that there is a reference implementation available here: https://github.com/josuttis/jthread/blob/master/source/stop_token.hpp stop_token::stop_possible() This implementation is just checking whether the stop_token has ownership of a shared-state. This is necessary as per [stoptoken.mem]/2.1, but insufficient. It also needs to take into account [stoptoken.mem]/2.2 which requires that this method also return false when it does have ownership of a shared state but where there is no associated stop_source. This generally requires keeping a separate ref-count for the number of stop_source instances. stop_token::_Stop_cb::_M_callback The function pointer type could be declared noexcept here. This might avoid some extra exception-handling codegen being generated in _M_request_stop() when this function pointer is invoked. stop_token::_Stop_cb::_S_execute() It is valid for the invocation of a callback registered with the stop_callback to end up calling the destructor of the stop_callback object from within the callback. See [stopcallback.cons]/6 which says: "... If callback is executing on the current thread, then the destructor does not block (3.6) waiting for the return from the invocation of callback. ..." The intent of this wording was to indicate that it is potentially valid for the destructor of a stop_callback object to execute on the thread that is currently executing that instance's registered callback. i.e. that the destructor might run inside the user-provided callback. Thus the current logic that tries to update '__cb->_M_prev = __cb->_M_next = nullptr;' after invoking the registered callback is thus potentially writing to a dangling pointer. You would either need to update these pointers before invoking the callback or otherwise somehow detect whether the destructor has been run during the invocation of the callback. stop_token::_Stop_state_t::_M_request_stop() This method holds the _Stop_state_t::_M_mtx lock while invoking user-provided callbacks. This prevents stop_callbacks from being deregistered concurrently while the user-provided callback is executing. See [stopcallback.cons]/6 which says: "The destructor does not block waiting for the execution of another callback registered by an associated stop_callback." This is necessary to prevent certain deadlock situations that would otherwise arise were a callback to try to acquire a lock on some mutex that might currently be held by another thread that is concurrently trying to deregister a stop_callback. stop_source::operator=(stop_source&& rhs) This implementation just does a swap() which leaves rhs with the previous state of *this rather than leaving rhs as an empty state. See [stopcallback.cons]/10 which says that this should instead be equivalent to: stop_source(std::move(rhs)).swap(*this). ie. you first need to construct a temporary stop_source and call swap() with that. stop_callback::stop_callback(const stop_token&, Callback&&) stop_callback::stop_callback(stop_token&&, Callback&&) These constructors are only supposed to throw exceptions that are thrown by the constructor of the callback object. See [stopcallback.cons]/5 which states: "Throws: Any exception thrown by the initialization of callback." This implementation calls the _Stop_state_t::_M_register_callback() method which is not declared noexcept and which calls std::mutex::lock() which might throw a std::system_error if it failed to acquire the mutex lock. It might be easier to provide a noexcept implementation here if the implementation was built on top of std::atomic and used std::atomic::wait()-based spin-locks instead of std::mutex. These constructors also have a race-condition that fails to call the callback being registered inline if request_stop() is called concurrently by another thread in-between the call to if (__state->_M_stop_requested()) and else if (__state->_M_register_callback(this)) stop_callback::~stop_callback() This needs to ensure that it blocks until the callback finishes executing if the
[Bug c++/91673] New: GCC ICE when partially specialising class template on a function-signature type with deduced noexcept qualifier
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=91673 Bug ID: 91673 Summary: GCC ICE when partially specialising class template on a function-signature type with deduced noexcept qualifier Product: gcc Version: unknown Status: UNCONFIRMED Severity: normal Priority: P3 Component: c++ Assignee: unassigned at gcc dot gnu.org Reporter: lewissbaker.opensource at gmail dot com Target Milestone: --- See https://godbolt.org/z/BndP6r Version: GCC trunk Command-line: -std=c++1z Compiling the following code: ``` template struct overload; template struct overload { using signature_t = Ret(Args...) noexcept(NoExcept); }; overload x; ``` results in the following output: ``` : In instantiation of 'struct overload': :9:18: required from here :6:11: internal compiler error: in tsubst_copy, at cp/pt.c:16183 6 | using signature_t = Ret(Args...) noexcept(NoExcept); | ^~~ ``` This seemed to compile without error under earlier releases of GCC.