-
Notifications
You must be signed in to change notification settings - Fork 16.5k
Closed as not planned
Closed as not planned
Copy link
Labels
coroutinesC++20 coroutinesC++20 coroutinesinvalidResolved as invalid, i.e. not a bugResolved as invalid, i.e. not a bugllvm:optimizations
Description
Consider the following code:
#include <string>
#include <coroutine>
#include <cstdio>
#include <memory>
#include <optional>
#define debug_promise_state(context, promise) \
printf("(%p) %s: promise memory_zero_test=%d has_immediate_result=%s\n", promise, context, promise->memory_zero_test, promise->has_immediate_result ? "yes" : "no")
template<typename T>
struct promise_base
{
int memory_zero_test;
bool has_immediate_result;
std::optional<T> immediate_result;
promise_base()
: memory_zero_test(0xdeadbeef)
, has_immediate_result()
, immediate_result()
{
}
};
template<typename T>
struct task
{
private:
promise_base<T>* source;
bool has_promise_reference;
public:
task()
: source()
, has_promise_reference(false)
{
}
task(promise_base<T>* source)
: source(source)
, has_promise_reference(true)
{
}
task(const task&) = delete;
task(task&&) = delete;
task& operator=(const task&) = delete;
task& operator=(task&&) = delete;
bool await_ready()
{
debug_promise_state("await_ready", this->source);
if (this->has_promise_reference &&
this->source->has_immediate_result) {
printf("task: await_ready with value reference\n");
return true;
} else {
printf("task: await_ready with heap reference\n");
return false;
}
}
void await_suspend(auto handle)
{
debug_promise_state("await_suspend", this->source);
handle.resume();
}
T await_resume()
{
debug_promise_state("await_resume", this->source);
if (this->has_promise_reference &&
this->source->has_immediate_result) {
printf("task: await_resume with value_ref\n");
return this->source->immediate_result.value();
} else {
printf("task: await_resume with heap_ref\n");
return 10;
}
}
};
template<typename T>
struct promise;
namespace std {
template<typename T>
struct coroutine_traits<task<T>>
{
using promise_type = ::promise<T>;
};
}
template<typename T>
struct promise : public promise_base<T>
{
promise() = default;
promise(const promise&) = delete;
promise(promise&&) = delete;
~promise() = default;
promise& operator=(const promise&) = delete;
promise& operator=(promise&&) = delete;
task<T> get_return_object()
{
debug_promise_state("get_return_object", this);
return task<T>(this);
}
std::suspend_never initial_suspend() noexcept { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_value(T&& value)
{
printf("setting return_value with promise %p\n", this);
this->has_immediate_result = true;
this->immediate_result = value;
// uncomment this line to get the correct value path behaviour.
//printf("using immediate_result field %s\n", this->immediate_result ? "yes" : "no");
}
void unhandled_exception() {}
};
task<int> test()
{
co_return 5;
}
task<int> test2()
{
int blah = co_await test();
co_return blah;
}
int main()
{
auto task = test2();
return 0;
}The correct behaviour is to emit "task: await_ready with value reference" because has_immediate_result is set to true, and then checked with await_ready. For some reason the optimizer is removing the assignment of has_immediate_result if it's not immediately used in return_value.
This happens on trunk (on Godbolt) and all versions of Clang since Clang 18 (where it regressed). Clang 17 has the correct behaviour with the -O1.
Note the commented out printf call inside return_value. This code produces different results with -O0, and with the printf uncommented out. MSVC gets this correct with optimizations turned on (/O2).
Clang trunk -std=c++20 -O0 (correct)
(0x58b8bb19a2c0) get_return_object: promise memory_zero_test=-559038737 has_immediate_result=no
(0x58b8bb19b310) get_return_object: promise memory_zero_test=-559038737 has_immediate_result=no
setting return_value with promise 0x58b8bb19b310
(0x58b8bb19b310) await_ready: promise memory_zero_test=-559038737 has_immediate_result=yes
task: await_ready with value reference
(0x58b8bb19b310) await_resume: promise memory_zero_test=-559038737 has_immediate_result=yes
task: await_resume with value_ref
setting return_value with promise 0x58b8bb19a2c0
Clang trunk -std=c++20 -O1 (incorrect❗)
(0x5d08d38cc2c0) get_return_object: promise memory_zero_test=-559038737 has_immediate_result=no
(0x7fff1dfb23b0) get_return_object: promise memory_zero_test=-559038737 has_immediate_result=no
setting return_value with promise 0x7fff1dfb23b0
(0x7fff1dfb23b0) await_ready: promise memory_zero_test=-559038737 has_immediate_result=no
task: await_ready with heap reference
(0x7fff1dfb23b0) await_suspend: promise memory_zero_test=-559038737 has_immediate_result=no
(0x7fff1dfb23b0) await_resume: promise memory_zero_test=-559038737 has_immediate_result=no
task: await_resume with heap_ref
setting return_value with promise 0x5d08d38cc2c0
Clang trunk -std=c++20 -O1 with printf call uncommented (correct)
(0x578c364ec2c0) get_return_object: promise memory_zero_test=-559038737 has_immediate_result=no
(0x7fff28dec930) get_return_object: promise memory_zero_test=-559038737 has_immediate_result=no
setting return_value with promise 0x7fff28dec930
using immediate_result field yes
(0x7fff28dec930) await_ready: promise memory_zero_test=-559038737 has_immediate_result=yes
task: await_ready with value reference
(0x7fff28dec930) await_resume: promise memory_zero_test=-559038737 has_immediate_result=yes
task: await_resume with value_ref
setting return_value with promise 0x578c364ec2c0
using immediate_result field yes
MSVC with /O2 (optimizations enabled, correct)
(000001DD0B7CF690) get_return_object: promise memory_zero_test=-559038737 has_immediate_result=no
(000001DD0B7CAD30) get_return_object: promise memory_zero_test=-559038737 has_immediate_result=no
setting return_value with promise 000001DD0B7CAD30
(000001DD0B7CAD30) await_ready: promise memory_zero_test=-559038737 has_immediate_result=yes
task: await_ready with value reference
(000001DD0B7CAD30) await_resume: promise memory_zero_test=-559038737 has_immediate_result=yes
task: await_resume with value_ref
setting return_value with promise 000001DD0B7CF690
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
coroutinesC++20 coroutinesC++20 coroutinesinvalidResolved as invalid, i.e. not a bugResolved as invalid, i.e. not a bugllvm:optimizations