Chuanqi Xu 7037331a2f [Coroutines] [CoroElide] Don't think exceptional terminator don't leak coro handle unconditionally any more
Close https://github.com/llvm/llvm-project/issues/59723.

The fundamental cause of the above issue is that we assumed the memory
of coroutine frame can be released by stack unwinding automatically
if the allocation of the coroutine frame is elided. But we missed one
point: the stack unwinding has different semantics with the explicit
coroutine_handle<>::destroy(). Since the latter is explicit so it shows
the intention of the user. So we can blame the user to destroy the
coroutine frame incorrectly in case of use-after-free happens. But we
can't do so with stack unwinding.

So after this patch, we won't think the exceptional terminator don't
leak the coroutine handle unconditionally. Instead, we think the
exceptional terminator will leak the coroutine handle too if the
coroutine is leaked somewhere along the search path.

Concretely for C++, we can think the exceptional terminator is not
special any more. Maybe this may cause some performance regressions.
But I've tested the motivating example (std::generator). And on the
other side, the coroutine elision is a middle end opitmization and not
a language feature. So we don't think we should blame such regressions
especially we are correcting the miscompilations.
2023-08-23 16:51:53 +08:00

105 lines
2.6 KiB
C++

// This tests that the coroutine heap allocation elision optimization could happen succesfully.
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -O2 -emit-llvm %s -o - | FileCheck %s
// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++20 -O2 -emit-llvm %s \
// RUN: -fcxx-exceptions -fexceptions -o - | FileCheck %s
#include "Inputs/coroutine.h"
#include "Inputs/numeric.h"
template <typename T> struct generator {
struct promise_type {
T current_value;
std::suspend_always yield_value(T value) {
this->current_value = value;
return {};
}
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
generator get_return_object() { return generator{this}; };
void unhandled_exception() {}
void return_void() {}
};
struct iterator {
std::coroutine_handle<promise_type> _Coro;
bool _Done;
iterator(std::coroutine_handle<promise_type> Coro, bool Done)
: _Coro(Coro), _Done(Done) {}
iterator &operator++() {
_Coro.resume();
_Done = _Coro.done();
return *this;
}
bool operator==(iterator const &_Right) const {
return _Done == _Right._Done;
}
bool operator!=(iterator const &_Right) const { return !(*this == _Right); }
T const &operator*() const { return _Coro.promise().current_value; }
T const *operator->() const { return &(operator*()); }
};
iterator begin() {
p.resume();
return {p, p.done()};
}
iterator end() { return {p, true}; }
generator(generator const &) = delete;
generator(generator &&rhs) : p(rhs.p) { rhs.p = nullptr; }
~generator() {
if (p)
p.destroy();
}
private:
explicit generator(promise_type *p)
: p(std::coroutine_handle<promise_type>::from_promise(*p)) {}
std::coroutine_handle<promise_type> p;
};
template <typename T>
generator<T> seq() {
for (T i = {};; ++i)
co_yield i;
}
template <typename T>
generator<T> take_until(generator<T> &g, T limit) {
for (auto &&v : g)
if (v < limit)
co_yield v;
else
break;
}
template <typename T>
generator<T> multiply(generator<T> &g, T factor) {
for (auto &&v : g)
co_yield v *factor;
}
template <typename T>
generator<T> add(generator<T> &g, T adder) {
for (auto &&v : g)
co_yield v + adder;
}
int main() {
auto s = seq<int>();
auto t = take_until(s, 10);
auto m = multiply(t, 2);
auto a = add(m, 110);
return std::accumulate(a.begin(), a.end(), 0);
}
// CHECK-LABEL: define{{.*}} i32 @main(
// CHECK: ret i32 1190
// CHECK-NOT: call{{.*}}_Znwm