[Bug c++/100897] Symmetric transfer does not prevent stack-overflow for C++20 coroutines

2021-06-11 Thread l.v.merzljak at gmail dot com via Gcc-bugs
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=100897

--- Comment #2 from Leonard von Merzljak  ---
(In reply to Iain Sandoe from comment #1)
> for symmetric transfer to work without stack overflow, it relies on an
> indirect tailcall.
> 
> For some GCC targets indirect tail-calls are not available without some
> additional support (see PR94794).
> 
> I tried to reproduce this (with a test case I use regularly for this) on a
> target that normally completes symmetric transfers successfully when the
> optimisation level is > 1. (x86_64, darwin).
> 
> The fail also occurs with my regular test case with -fsanitize=address - so,
> it seems that the inclusion of the address sanitiser is preventing or
> interfering with the tailcall.  Note that there are also other known issues
> with coroutines and the sanitizers (PR95137).

Thank you for your comment. I tried it out and can confirm that I don't get a
stack-overflow anymore if I omit -fsanitize=address and use an optimization
level > 1. If the issues with coroutines and sanitizers are already known, then
this bug report can be marked as resolved.
Of course, it would be nice if the stack-overflow would not occur even when
using an optimization level <= 1, but this probably does not qualify as a bug.

[Bug c++/100897] New: Symmetric transfer does not prevent stack-overflow for C++20 coroutines

2021-06-03 Thread l.v.merzljak at gmail dot com via Gcc-bugs
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=100897

Bug ID: 100897
   Summary: Symmetric transfer does not prevent stack-overflow for
C++20 coroutines
   Product: gcc
   Version: 11.1.0
Status: UNCONFIRMED
  Severity: normal
  Priority: P3
 Component: c++
  Assignee: unassigned at gcc dot gnu.org
  Reporter: l.v.merzljak at gmail dot com
  Target Milestone: ---

Although the following code uses symmetric transfer, it crashes due to a
stack-overflow. The crash is also reproducible when using the task<> type of
the cppcoro library. The crash does not occur when using clang.

```
// main.cc
#include 
#include 

class Task {
 public:
  struct promise_type {
Task get_return_object() { return Handle::from_promise(*this); }

struct FinalAwaitable {
  bool await_ready() const noexcept { return false; }

  // Use symmetric transfer. Resuming coro.promise().m_continuation should
  // not require extra stack space
  std::coroutine_handle<> await_suspend(
  std::coroutine_handle coro) noexcept {
if (coro.promise().m_continuation) {
  return coro.promise().m_continuation;
} else {
  // The top-level task started from within main() does not have a
  // continuation. This will give control back to the main function.
  return std::noop_coroutine();
}
  }

  void await_resume() noexcept {}
};

std::suspend_always initial_suspend() noexcept { return {}; }

FinalAwaitable final_suspend() noexcept { return {}; }

void unhandled_exception() noexcept { std::terminate(); }

void set_continuation(std::coroutine_handle<> continuation) noexcept {
  m_continuation = continuation;
}

void return_void() noexcept {}

   private:
std::coroutine_handle<> m_continuation;
  };

  using Handle = std::coroutine_handle;

  Task(Handle coroutine) : m_coroutine(coroutine) {}

  ~Task() {
if (m_coroutine) {
  m_coroutine.destroy();
}
  }

  void start() noexcept { m_coroutine.resume(); }

  auto operator co_await() const noexcept { return Awaitable{m_coroutine}; }

 private:
  struct Awaitable {
Handle m_coroutine;

Awaitable(Handle coroutine) noexcept : m_coroutine(coroutine) {}

bool await_ready() const noexcept { return false; }

// Use symmetric transfer. Resuming m_coroutine should not require extra
// stack space
std::coroutine_handle<> await_suspend(
std::coroutine_handle<> awaitingCoroutine) noexcept {
  m_coroutine.promise().set_continuation(awaitingCoroutine);
  return m_coroutine;
}

void await_resume() {}
  };

  Handle m_coroutine;
};

Task inner() { co_return; }

Task outer() {
  // Use large number of iterations to trigger stack-overflow
  for (int i = 0; i != 5000; ++i) {
co_await inner();
  }
}

int main() {
  auto task = outer();
  task.start();
}
```

I compile the code with `g++-11 main.cc -std=c++20 -O3 -fsanitize=address`.

Here is the output:
```
$ ./a.out
AddressSanitizer:DEADLYSIGNAL
=
==21002==ERROR: AddressSanitizer: stack-overflow on address 0x7fffc666dff8 (pc
0x7f6ec2dfa16d bp 0x7fffc666e870 sp 0x7fffc666e000 T0)
#0 0x7f6ec2dfa16d in __sanitizer::BufferedStackTrace::UnwindImpl(unsigned
long, unsigned long, void*, bool, unsigned int)
../../../../src/libsanitizer/asan/asan_stack.cpp:57
#1 0x7f6ec2df00eb in __sanitizer::BufferedStackTrace::Unwind(unsigned long,
unsigned long, void*, bool, unsigned int)
../../../../src/libsanitizer/sanitizer_common/sanitizer_stacktrace.h:122
#2 0x7f6ec2df00eb in operator delete(void*)
../../../../src/libsanitizer/asan/asan_new_delete.cpp:160
#3 0x560193552e57 in _Z5innerv.destroy(inner()::_Z5innerv.frame*)
(/home/leonard/Desktop/hiwi/async_io_uring/stack-overflow/a.out+0x1e57)
#4 0x560193553b30 in _Z5outerv.actor(outer()::_Z5outerv.frame*)
(/home/leonard/Desktop/hiwi/async_io_uring/stack-overflow/a.out+0x2b30)
#5 0x560193552bbb in _Z5innerv.actor(inner()::_Z5innerv.frame*)
(/home/leonard/Desktop/hiwi/async_io_uring/stack-overflow/a.out+0x1bbb)
...
```