背景
我有一个可以同时使用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_resumereturn_valueyield_value

详细信息
我已经通过整洁的PVS studio运行了代码,启用了LLVM,GCC,MSVC中的所有可用的消毒器,并且没有弹出任何相关消息(只是关于destroy和resume的评论并非异常(exception))。
我有几个非常相似的测试:
相关测试为:
功能:
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的结果(失败):
---------------------------------
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之间的区别(已更正地址,将显示以下内容):
c&#43;&#43; - C&#43;&#43; 20协程,await_resume,return_value和yield_value的意外重新排序-LMLPHP
很明显,以下几行已重新排序:
        AwaitAwaitable: await_resume: 02901E20
        GetValue: 02901E20
LLVM和GCC的来源和结果为here
查看GCC FAIL和LLVM PASS之间的测试2差异:
c&#43;&#43; - C&#43;&#43; 20协程,await_resume,return_value和yield_value的意外重新排序-LMLPHP
在海湾合作委员会中发生了非常相似的排序。
差异中突出显示的行是由以下来源产生的:
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++ - C++ 20协程,await_resume,return_value和yield_value的意外重新排序,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/64348125/

    10-14 05:34