llvm / llvm-project

The LLVM Project is a collection of modular and reusable compiler and toolchain technologies.
http://llvm.org
Other
27.17k stars 11.12k forks source link

coroutine frame from lambda #91123

Open kelbon opened 2 months ago

kelbon commented 2 months ago

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

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));
  }
};
llvmbot commented 2 months ago

@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 ```cpp 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)); } }; ```
ChuanqiXu9 commented 2 months ago

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

kelbon commented 2 months ago

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 commented 2 months ago

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