
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.
105 lines
2.6 KiB
C++
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
|