C++ C++;20协程、等待恢复、返回值和收益值的意外重新排序

C++ C++;20协程、等待恢复、返回值和收益值的意外重新排序,c++,c++20,c++-coroutine,C++,C++20,C++ Coroutine,背景 我的任务类型既可以co\u返回也可以co\u产生。 在LLVM中,任务按预期工作,并通过了一些早期测试。在MSVC和GCC中,代码以相同的方式失败(巧合?) 简短问题 具有以下测试功能: Task<int> test_yielding() { co_yield 1; co_return 2; } a的值应为1,b的值应为2 根据a+b==3测试结果 上述测试通过,但以下测试失败: auto res = co_await fn + co_await fn G

背景

我的任务类型既可以
co\u返回
也可以
co\u产生
。 在LLVM中,任务按预期工作,并通过了一些早期测试。在MSVC和GCC中,代码以相同的方式失败(巧合?)


简短问题

具有以下测试功能:

Task<int> test_yielding()
{
    co_yield 1;
    co_return 2;
}
a的值应为1,b的值应为2

根据
a+b==3
测试结果

上述测试通过,但以下测试失败:

auto res = co_await fn + co_await fn
GCC和MSVC的res值为4。都是从最终的co_返回中检索的。据我所知,
co_wait fn
的第一次和第二次调用应该是1和2

在MSVC和GCC中,代码失败,因为它们似乎重新排序
等待恢复
返回值
产生值


详细信息

我已经通过clang tidy、PVS studio运行了代码,启用了LLVM、GCC、MSVC中所有可用的消毒剂,并且没有任何相关的弹出窗口(只是关于销毁和恢复的注释不例外)

我有几个非常相似的测试: 有关测试包括:

功能:

Task<int> test_yielding()
{
    co_yield 1;
    co_return 2;
}
测试2(失败):

测试结果MSVC 1(通过):

测试结果MSVC 2(失败):

如果查看工作MSVC失败和MSVC通过之间的差异(纠正地址后,将显示以下内容): 这表明以下行已重新排序:

        AwaitAwaitable: await_resume: 02901E20  
        GetValue: 02901E20
LLVM和GCC的源代码和结果如下所示

查看GCC失败和LLVM通过之间的测试2差异: GCC中也发生了类似的重新排序

diff中高亮显示的线条由以下来源生成:

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();
    }
};
模板
结构等待
{
承诺&m_承诺;
bool wait_ready()常量无异常
{
WriteLine(“等待:”、“功能:”、&m_承诺”);
返回false;
}
void wait_suspend(默认句柄)noexcept
{
WriteLine(“等待:”、“功能:”、&m_承诺”);
m_promise.SetCurrent(m_promise.Handle());
承诺。继续与(处理);
}
自动等待_resume()常量noexcept
{
WriteLine(“等待:”、“功能:”、&m_承诺”);
返回m_promise.GetValue();
}
};

有人知道这里发生了什么吗,这是编译器/库/用户错误吗?

观察到的行为似乎是由于GCC和MSVC在处理加法运算符时出现了类似的错误,其中参数都是
co_wait
表达式

在本例中,GCC和MSVC似乎在从第二个挂起点恢复后(即在执行添加之前),对
co\u wait
表达式调用
wait_resume()
的顺序错误


他们应该为第一个
co\u wait
表达式对
await\u resume()
的调用排序(不确定是哪一个)从第一个挂起点恢复后,在开始计算第二个
co_wait
表达式之前,编译器可以在检索值并添加它们之前计算这两个
co_wait fn
。由于两者都将结果存储在相同的
任务中
,因此最后一个值(本例中为2)将覆盖结果。而
auto a=co_wait fn
在第一次
co_wait
之后复制结果。我想这就是正在发生的事情。假设
(co_wait fn+co_wait fn)
行的结果没有很好的定义。它们可以交错,即[eval foo()]->[eval bar()]->[read foo()result]->[read bar()result]是可能的。至少我是这样理解“排序”部分的:“它们可以按任何顺序执行,也可以重叠”嗯,尽管规则11与此相矛盾。至少对于正常的函数调用。然而,在有关订购的标准中,我找不到任何关于
co_wait
。我想现在这是UB。这是不真实的,如此简单的表达。这需要修正:|这是核心问题2466(尚未在公开问题列表上)。感谢刘易斯和CPPCRO!我一直想引起大家对这个问题的注意。我的GCC报告在这里:标准在哪里说这是不正确的?只是用更好的链接替换我的评论:)第[expr.await]p5.2节声明:>如果等待就绪的结果为真,或者当协同程序恢复时,将计算等待恢复表达式,其结果就是等待表达式的结果。对我来说,这意味着在恢复协同路由时立即计算wait表达式。但是如果我们阅读[intro.execution]p10,它说除非另有规定,否则子表达式是不排序的,而[expr.add]没有为内置运算符+参数指定任何排序,那么编译器可能会将co_wait部分视为未排序的…?是的,我认为这应该是一个缺陷——如果规范的解释是正确的。
Title("Test co_yield + co_return rvalue");
auto fn = test_yielding();
auto res =
(
    co_await fn +
    co_await fn
);
ASSERT(res == 3);
---------------------------------
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
---------------------------------
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
        AwaitAwaitable: await_resume: 02901E20  
        GetValue: 02901E20
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();
    }
};