gpt4 book ai didi

C++20 协程,await_resume、return_value 和 yield_value 的意外重新排序

转载 作者:行者123 更新时间:2023-12-03 06:49:02 24 4
gpt4 key购买 nike

背景
我有一个任务类型可以co_returnco_yield .
在 LLVM 中,该任务按预期工作并通过了一些早期测试。在 MSVC 和 GCC 中,代码以相同的方式失败(巧合?)。

简要问题
具有以下测试功能:

Task<int> test_yielding()
{
co_yield 1;
co_return 2;
}
从 Task 对象中检索到两个值。
auto a = co_await fn;
auto b = co_await fn;
a 的值预计为 1,b 的值预计为 2。
结果针对 a + b == 3 进行了测试.
上述测试通过,但以下测试失败:
auto res = co_await fn + co_await fn
GCC 和 MSVC 的 res 的值为 4。两者都从最终的 co_return 中检索。据我了解, co_await fn 的第一次和第二次调用应该是 1 和 2 的任一顺序。
在 MSVC 和 GCC 中,代码失败,因为它们似乎重新排序 await_resume , return_valueyield_value .

详情
我已经通过 clang tidy、PVS studio 运行了代码,启用了 LLVM、GCC、MSVC 中的所有可用 sanitizer ,并且没有任何相关弹出(只是关于销毁和恢复的评论不是 noexcept)。
我有几个非常相似的测试:
相关的测试是:
功能:
Task<int> test_yielding()
{
co_yield 1;
co_return 2;
}
测试 1(通过):
Title("Test co_yield + co_return lvalue");
auto fn = test_yielding();
auto a = co_await fn;
auto b = co_await fn;
ASSERT(a + b == 3);
测试 2(失败):
Title("Test co_yield + co_return rvalue");
auto fn = test_yielding();
auto res =
(
co_await fn +
co_await fn
);
ASSERT(res == 3);
测试 MSVC 1 (PASS) 的结果:
---------------------------------
Title Test co_yield + co_return lvalue
---------------------------------
get_return_object: 02F01DA0
initial_suspend: 02F01DA0
await_transform: 02D03C80
AwaitAwaitable: await_ready: 02F01DA0
AwaitAwaitable: await_suspend: 02F01DA0
SetCurrent: 02F01DA0
ContinueWith: 02F01DA0
yield_value: 02F01DA0
SetValue: 02F01DA0
YieldAwaitable: await_ready: 02F01DA0
YieldAwaitable: await_suspend: 02F01DA0
ContinueWith: 02F01DA0
AwaitAwaitable: await_resume: 02F01DA0
GetValue: 02F01DA0
await_transform: 02D03C80
AwaitAwaitable: await_ready: 02F01DA0
AwaitAwaitable: await_suspend: 02F01DA0
SetCurrent: 02F01DA0
ContinueWith: 02F01DA0
YieldAwaitable: await_resume: 02F01DA0
return_value: 02F01DA0
SetValue: 02F01DA0
final_suspend: 02F01DA0
YieldAwaitable: await_ready: 02F01DA0
YieldAwaitable: await_suspend: 02F01DA0
ContinueWith: 02F01DA0
AwaitAwaitable: await_resume: 02F01DA0
GetValue: 02F01DA0
PASS test_task:323 a + b == 3
[ result = 3, expected = 3 ]
Destroy: 02F01DA0
测试 MSVC 2 (FAIL) 的结果:
---------------------------------
Title Test co_yield + co_return rvalue
---------------------------------
get_return_object: 02F01CA0
initial_suspend: 02F01CA0
await_transform: 02D03C80
AwaitAwaitable: await_ready: 02F01CA0
AwaitAwaitable: await_suspend: 02F01CA0
SetCurrent: 02F01CA0
ContinueWith: 02F01CA0
yield_value: 02F01CA0
SetValue: 02F01CA0
YieldAwaitable: await_ready: 02F01CA0
YieldAwaitable: await_suspend: 02F01CA0
ContinueWith: 02F01CA0
await_transform: 02D03C80
AwaitAwaitable: await_ready: 02F01CA0
AwaitAwaitable: await_suspend: 02F01CA0
SetCurrent: 02F01CA0
ContinueWith: 02F01CA0
YieldAwaitable: await_resume: 02F01CA0
return_value: 02F01CA0
SetValue: 02F01CA0
final_suspend: 02F01CA0
YieldAwaitable: await_ready: 02F01CA0
YieldAwaitable: await_suspend: 02F01CA0
ContinueWith: 02F01CA0
AwaitAwaitable: await_resume: 02F01CA0
GetValue: 02F01CA0
AwaitAwaitable: await_resume: 02F01CA0
GetValue: 02F01CA0
FAIL test_task:342 res == 3
[ result = 4, expected = 3 ]
Destroy: 02F01CA0
如果您查看工作 MSVC FAIL 和 MSVC PASS 之间的差异(地址已更正,则会出现以下内容):
enter image description here
这清楚地表明以下行已重新排序:
        AwaitAwaitable: await_resume: 02901E20  
GetValue: 02901E20
LLVM 和 GCC 的来源和结果是 here .
查看 GCC FAIL 和 LLVM PASS 之间的测试 2 差异:
GCC vs LLVM
GCC 中也发生了非常相似的重新排序。
差异中突出显示的行产生于以下来源:
template <typename Promise>
struct AwaitAwaitable
{
Promise & m_promise;

bool await_ready() const noexcept
{
WriteLine("AwaitAwaitable: ", __func__, ": ", &m_promise);
return false;
}

void await_suspend(default_handle handle) noexcept
{
WriteLine("AwaitAwaitable: ", __func__, ": ", &m_promise);
m_promise.SetCurrent( m_promise.Handle() );
m_promise.ContinueWith( handle );
}

auto await_resume() const noexcept
{
WriteLine("AwaitAwaitable: ", __func__, ": ", &m_promise);
return m_promise.GetValue();
}
};
有人知道这里发生了什么,这是编译器/库/用户错误吗?

最佳答案

观察到的行为似乎是由于 GCC 和 MSVC 在处理参数都是 co_await 的加法运算符时存在类似错误。表达式。

  • GCC Bug Report
  • MSVC Bug Report

  • 在这种情况下,GCC 和 MSVC 似乎都错误地排序了对 await_resume() 的调用。两个 co_await从第二个挂起点恢复后的表达式(即在执行加法之前)。
    相反,他们应该对 await_resume() 的调用进行排序。第一个 co_await在从第一个暂停点恢复之后和开始计算第二个暂停点之前的表达式(不确定是哪一个) co_await表达。

    关于C++20 协程,await_resume、return_value 和 yield_value 的意外重新排序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64348125/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com