Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

coroutine frame from lambda #91123

Open
kelbon opened this issue May 5, 2024 · 4 comments
Open

coroutine frame from lambda #91123

kelbon opened this issue May 5, 2024 · 4 comments
Labels
coroutines C++20 coroutines lambda C++11 lambda expressions

Comments

@kelbon
Copy link
Contributor

kelbon commented May 5, 2024

I think there are should be a way to create coroutine frame from lambda. There are use cases:

  • when we sure dont need allocation here, just create a handle and pass into function which accepts coroutine handles
  • when coroutine handle may be invoked concurrently from different threads ('when_any' implementation), but its not possible in standard C++ to handle this (calling .resume concurrently will lead to UB). With lambdas calling .resume concurrently may be handled with simple mutex
  • to create 'always done' coroutine, just like std::noop_coroutine, but .done always returns true
  • to create special coroutines, such as generator, which is always empty (without allocatons). This may be used in std::generator move operator (there are no default constructor for std::generator, but move constructor is present, so to create "default" state for such generator we can use some global generator object, which .resume does nothing and .destroy does nothing too)

I have "implementation", but for obvious reasons it is not good: it contains undefined behavior. And i dont know is it intentional, but clang ignores all noinline and std::launder hacks and just optimizes out all code every time (with O1/O2/O3):
https://godbolt.org/z/EP1b8Eb9x

template <typename F>
struct alignas(__STDCPP_DEFAULT_NEW_ALIGNMENT__) coroutine_frame {
  void (*resume)(void*) = &do_resume<std::decay_t<coroutine_frame>>;
  void (*destroy)(void*) = &do_destroy<std::decay_t<coroutine_frame>>;
  F f;

  coroutine_frame(F value) : f(std::move(value)) {}

  [[gnu::noinline]] std::coroutine_handle<> handle() noexcept {
    return std::coroutine_handle<>::from_address(std::launder(this));
  }
};
@EugeneZelenko EugeneZelenko added coroutines C++20 coroutines lambda C++11 lambda expressions and removed new issue labels May 5, 2024
@llvmbot
Copy link
Collaborator

llvmbot commented May 5, 2024

@llvm/issue-subscribers-coroutines

Author: None (kelbon)

I think there are should be a way to create coroutine frame from lambda. There are use cases:
  • when we sure dont need allocation here, just create a handle and pass into function which accepts coroutine handles
  • when coroutine handle may be invoked concurrently from different threads ('when_any' implementation), but its not possible in standard C++ to handle this (calling .resume concurrently will lead to UB). With lambdas calling .resume concurrently may be handled with simple mutex
  • to create 'always done' coroutine, just like std::noop_coroutine, but .done always returns true
  • to create special coroutines, such as generator, which is always empty (without allocatons). This may be used in std::generator move operator (there are no default constructor for std::generator, but move constructor is present, so to create "default" state for such generator we can use some global generator object, which .resume does nothing and .destroy does nothing too)

I have "implementation", but for obvious reasons it is not good: it contains undefined behavior. And i dont know is it intentional, but clang ignores all noinline and std::launder hacks and just optimizes out all code every time (with O1/O2/O3):
https://godbolt.org/z/EP1b8Eb9x

template &lt;typename F&gt;
struct alignas(__STDCPP_DEFAULT_NEW_ALIGNMENT__) coroutine_frame {
  void (*resume)(void*) = &amp;do_resume&lt;std::decay_t&lt;coroutine_frame&gt;&gt;;
  void (*destroy)(void*) = &amp;do_destroy&lt;std::decay_t&lt;coroutine_frame&gt;&gt;;
  F f;

  coroutine_frame(F value) : f(std::move(value)) {}

  [[gnu::noinline]] std::coroutine_handle&lt;&gt; handle() noexcept {
    return std::coroutine_handle&lt;&gt;::from_address(std::launder(this));
  }
};

@ChuanqiXu9
Copy link
Member

From the perspective of compilers, there is not a lot of things we can do here.

@kelbon
Copy link
Contributor Author

kelbon commented May 6, 2024

From the perspective of compilers, there is not a lot of things we can do here.

im about just using internal knowledge about how coroutines implemented and using this information to create a struct without UB, it seems possible

@ChuanqiXu9
Copy link
Member

It looks like other optimizations clear such uses somehow... I am not sure why it can't works.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
coroutines C++20 coroutines lambda C++11 lambda expressions
Projects
None yet
Development

No branches or pull requests

4 participants