-
Notifications
You must be signed in to change notification settings - Fork 13.6k
link error when compiling coroutine code. #78290
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
Comments
@llvm/issue-subscribers-c-20 Author: Haojian Wu (hokein)
See https://godbolt.org/z/rc37E78EM for a minimal testcase.
The following code is compiled with gcc, but clang emits a link error ( #include <coroutine>
template <typename Final>
class Gen;
template <typename Final>
class CoroutinePromise final {
public:
template <typename... Args>
explicit CoroutinePromise(Args&&... args) {}
Gen<Final> get_return_object() { return Gen<Final>(my_handle()); }
std::coroutine_handle<CoroutinePromise> my_handle() {
return std::coroutine_handle<CoroutinePromise>::from_promise(*this);
}
void unhandled_exception() {}
void return_void() {}
template <typename U>
auto await_transform(Gen<U> gen) {
struct Awaitable {
bool await_ready() { return false; }
std::coroutine_handle<> await_suspend(
const std::coroutine_handle<> handle_in) {
return handle_in;
}
decltype(auto) await_resume() {}
};
return Awaitable();
}
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
};
template <typename Final>
class Gen final {
public:
using final_result_type = Final;
using promise_type =CoroutinePromise<Final>;
explicit Gen(const std::coroutine_handle<promise_type> handle)
: handle_(handle) {}
std::coroutine_handle<promise_type> handle_;
};
class Foo {
public:
Gen<void> TestBody2() {
auto s =
[&]() -> Gen<void> { co_await this->CoroutineBody(); co_return; };
return s();
}
private:
Gen<void> CoroutineBody();
};
Gen<void> Foo ::CoroutineBody() {
if constexpr (0) // remove it will make clang compile.
co_await []() -> Gen<void> { co_return; }();
}
int main() {
return 0;
} |
@llvm/issue-subscribers-coroutines Author: Haojian Wu (hokein)
See https://godbolt.org/z/rc37E78EM for a minimal testcase.
The following code is compiled with gcc, but clang emits a link error ( #include <coroutine>
template <typename Final>
class Gen;
template <typename Final>
class CoroutinePromise final {
public:
template <typename... Args>
explicit CoroutinePromise(Args&&... args) {}
Gen<Final> get_return_object() { return Gen<Final>(my_handle()); }
std::coroutine_handle<CoroutinePromise> my_handle() {
return std::coroutine_handle<CoroutinePromise>::from_promise(*this);
}
void unhandled_exception() {}
void return_void() {}
template <typename U>
auto await_transform(Gen<U> gen) {
struct Awaitable {
bool await_ready() { return false; }
std::coroutine_handle<> await_suspend(
const std::coroutine_handle<> handle_in) {
return handle_in;
}
decltype(auto) await_resume() {}
};
return Awaitable();
}
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
};
template <typename Final>
class Gen final {
public:
using final_result_type = Final;
using promise_type =CoroutinePromise<Final>;
explicit Gen(const std::coroutine_handle<promise_type> handle)
: handle_(handle) {}
std::coroutine_handle<promise_type> handle_;
};
class Foo {
public:
Gen<void> TestBody2() {
auto s =
[&]() -> Gen<void> { co_await this->CoroutineBody(); co_return; };
return s();
}
private:
Gen<void> CoroutineBody();
};
Gen<void> Foo ::CoroutineBody() {
if constexpr (0) // remove it will make clang compile.
co_await []() -> Gen<void> { co_return; }();
}
int main() {
return 0;
} |
Interestingly, turning Gen<void> TestBody2() {
auto s = [&]() -> Gen<void> { co_await this->CoroutineBody(); }();
co_return co_await s;
} |
Reduced a bit more: #include <coroutine>
class Gen;
class Promise {
public:
explicit Promise() {}
template <typename Arg>
explicit Promise(const Arg& args) {}
Gen get_return_object();
std::coroutine_handle<Promise> my_handle() {
return std::coroutine_handle<Promise>::from_promise(*this);
}
void unhandled_exception() {}
void return_void() {}
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
};
class Gen final {
public:
using promise_type =Promise;
explicit Gen(const std::coroutine_handle<promise_type> handle)
: handle_(handle) {}
std::coroutine_handle<promise_type> handle_;
};
Gen Promise::get_return_object() { return Gen(my_handle()); }
class Foo {
private:
Gen f();
};
Gen Foo::f() {
if constexpr (0) // removing it makes clang compile.
co_return;
}
int main() {
return 0;
} The template constructor of
We somehow miss the function body for the template specialization |
Reduced more: Without use of member coroutines #include <coroutine>
class Gen {
public:
class promise_type {
public:
template <typename... Args>
explicit promise_type(Args&&... args) {}
Gen get_return_object() { return {}; }
void unhandled_exception() {}
void return_void() {}
std::suspend_always await_transform(Gen gen) { return {}; }
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
};
};
Gen CoroutineBody() {
if constexpr (0) // remove it make clang compile.
co_await Gen{};
co_await Gen{};
}
int main() { return 0; } As noticed earlier, Adding a co_await before Gen CoroutineBody() {
co_await Gen{};
if constexpr (0) { // remove it make clang compile.
co_await Gen{};
}
} |
This happens because the promise object is built when we see the first coroutine statement which is present in I think the solution can be the coroutine body should be built independently in a separate context irrespective of the context of the first coroutine statement. |
I didn't understand this fully. What is the meaning of the context here? |
I am referring to the expression evaluation context. This is set to |
Thanks. Got it. The proposed solution sounds proper. |
@llvm/issue-subscribers-clang-frontend Author: Haojian Wu (hokein)
See https://godbolt.org/z/5nvhqhsz9 for a minimal testcase.
The following code is compiled with gcc, but clang emits a link error ( #include <coroutine>
template <typename Final>
class Gen;
template <typename Final>
class CoroutinePromise final {
public:
template <typename... Args>
explicit CoroutinePromise(Args&&... args) {}
Gen<Final> get_return_object() { return Gen<Final>(my_handle()); }
std::coroutine_handle<CoroutinePromise> my_handle() {
return std::coroutine_handle<CoroutinePromise>::from_promise(*this);
}
void unhandled_exception() {}
void return_void() {}
template <typename U>
auto await_transform(Gen<U> gen) {
struct Awaitable {
bool await_ready() { return false; }
std::coroutine_handle<> await_suspend(
const std::coroutine_handle<> handle_in) {
return handle_in;
}
decltype(auto) await_resume() {}
};
return Awaitable();
}
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
};
template <typename Final>
class Gen final {
public:
using final_result_type = Final;
using promise_type =CoroutinePromise<Final>;
explicit Gen(const std::coroutine_handle<promise_type> handle)
: handle_(handle) {}
std::coroutine_handle<promise_type> handle_;
};
class Foo {
public:
Gen<void> TestBody2() {
auto s = [&]() -> Gen<void> { co_await this->CoroutineBody(); }();
return s;
}
private:
Gen<void> CoroutineBody();
};
Gen<void> Foo::CoroutineBody() {
if constexpr (0) // remove it make clang compile.
co_return;
}
int main() {
return 0;
} |
Fixes: #78290 See the bug for more context. ```cpp Gen ACoroutine() { if constexpr (0) // remove it make clang compile. co_return; co_await Gen{}; } ``` We miss symbol of ctor of promise_type if the first coroutine statement happens to be inside the disabled branch of `if constexpr`. This happens because the promise object is built when we see the first coroutine statement which is present in `ExpressionEvaluationContext::DiscardedStatement` context due to `if constexpr (0)`. This makes clang believe that the promise constructor is only odr-used and not really "used". The expr evaluation context for the coroutine body should not be related to the context in which the first coroutine statement appears. We override the context to `PotentiallyEvaluated`. --------- Co-authored-by: cor3ntin <[email protected]>
Uh oh!
There was an error while loading. Please reload this page.
See https://godbolt.org/z/5nvhqhsz9 for a minimal testcase.
The following code is compiled with gcc, but clang emits a link error (
in function Foo::CoroutineBody(): <source>:60: undefined reference to CoroutinePromise<void>::CoroutinePromise<Foo&>(Foo&)
) when linking to a binary. We seem to miss a symbol.The text was updated successfully, but these errors were encountered: