Skip to content

Commit 4c7e2b5

Browse files
authored
Awaiting an already-completed IAsyncXxx does not consume stack in C++20 mode (#1040)
1 parent 1091200 commit 4c7e2b5

File tree

6 files changed

+149
-22
lines changed

6 files changed

+149
-22
lines changed

cppwinrt.props

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,14 @@
5555
<WarningLevel>Level4</WarningLevel>
5656
<SDLCheck>true</SDLCheck>
5757
<ConformanceMode>true</ConformanceMode>
58-
<LanguageStandard>stdcpp17</LanguageStandard>
58+
<LanguageStandard Condition="'$(CppWinRTLanguageStandard)'==''">stdcpp17</LanguageStandard>
59+
<LanguageStandard Condition="'$(CppWinRTLanguageStandard)'=='latest'">stdcpplatest</LanguageStandard>
5960
<PrecompiledHeader>Use</PrecompiledHeader>
6061
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
6162
<PreprocessorDefinitions>CPPWINRT_VERSION_STRING="$(CppWinRTBuildVersion)";%(PreprocessorDefinitions)</PreprocessorDefinitions>
6263
<MultiProcessorCompilation>true</MultiProcessorCompilation>
63-
<AdditionalOptions>/await /bigobj</AdditionalOptions>
64+
<AdditionalOptions>/bigobj</AdditionalOptions>
65+
<AdditionalOptions Condition="'$(CppWinRTLanguageStandard)'==''">/await %(AdditionalOptions)</AdditionalOptions>
6466
<AdditionalOptions Condition="'$(Clang)'=='1'">-Wno-unused-command-line-argument -fno-delayed-template-parsing -Xclang -fcoroutines-ts -mcx16</AdditionalOptions>
6567
</ClCompile>
6668
<Link>

strings/base_coroutine_foundation.h

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -98,34 +98,45 @@ namespace winrt::impl
9898
return async.GetResults();
9999
}
100100

101+
template<typename Awaiter>
101102
struct disconnect_aware_handler
102103
{
103-
disconnect_aware_handler(coroutine_handle<> handle, int32_t* failure) noexcept
104-
: m_handle(handle), m_failure(failure) { }
104+
disconnect_aware_handler(Awaiter* awaiter, coroutine_handle<> handle) noexcept
105+
: m_awaiter(awaiter), m_handle(handle) { }
105106

106107
disconnect_aware_handler(disconnect_aware_handler&& other) noexcept
107108
: m_context(std::move(other.m_context))
108-
, m_handle(std::exchange(other.m_handle, {}))
109-
, m_failure(other.m_failure) { }
109+
, m_awaiter(std::exchange(other.m_awaiter, {}))
110+
, m_handle(std::exchange(other.m_handle, {})) { }
110111

111112
~disconnect_aware_handler()
112113
{
113114
if (m_handle) Complete();
114115
}
115116

116-
void operator()()
117+
template<typename Async>
118+
void operator()(Async&&, Windows::Foundation::AsyncStatus status)
117119
{
120+
m_awaiter->status = status;
118121
Complete();
119122
}
120123

121124
private:
122125
resume_apartment_context m_context;
126+
Awaiter* m_awaiter;
123127
coroutine_handle<> m_handle;
124-
int32_t* m_failure;
125128

126129
void Complete()
127130
{
128-
resume_apartment(m_context, std::exchange(m_handle, {}), m_failure);
131+
if (m_awaiter->suspending.exchange(false, std::memory_order_release))
132+
{
133+
m_handle = nullptr; // resumption deferred to await_suspend
134+
}
135+
else
136+
{
137+
resume_apartment(m_context, std::exchange(m_handle, {}), &m_awaiter->failure);
138+
}
139+
129140
}
130141
};
131142

@@ -138,6 +149,7 @@ namespace winrt::impl
138149
Async const& async;
139150
Windows::Foundation::AsyncStatus status = Windows::Foundation::AsyncStatus::Started;
140151
int32_t failure = 0;
152+
std::atomic<bool> suspending{ true };
141153

142154
void enable_cancellation(cancellable_promise* promise)
143155
{
@@ -152,14 +164,18 @@ namespace winrt::impl
152164
return false;
153165
}
154166

155-
void await_suspend(coroutine_handle<> handle)
167+
auto await_suspend(coroutine_handle<> handle)
156168
{
157169
auto extend_lifetime = async;
158-
async.Completed([this, handler = disconnect_aware_handler{ handle, &failure }](auto&&, auto operation_status) mutable
170+
async.Completed(disconnect_aware_handler(this, handle));
171+
#ifdef _RESUMABLE_FUNCTIONS_SUPPORTED
172+
if (!suspending.exchange(false, std::memory_order_acquire))
159173
{
160-
status = operation_status;
161-
handler();
162-
});
174+
handle.resume();
175+
}
176+
#else
177+
return suspending.exchange(false, std::memory_order_acquire);
178+
#endif
163179
}
164180

165181
auto await_resume() const

test/test/await_completed.cpp

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#include "pch.h"
2+
3+
using namespace winrt;
4+
using namespace Windows::Foundation;
5+
6+
namespace
7+
{
8+
//
9+
// Checks that awaiting an already-completed async operation
10+
// does not consume additional stack.
11+
//
12+
IAsyncAction AlreadyCompleted()
13+
{
14+
co_return;
15+
}
16+
17+
#ifdef _MSC_VER
18+
__declspec(noinline) uintptr_t approximate_stack_pointer()
19+
{
20+
return reinterpret_cast<uintptr_t>(_AddressOfReturnAddress());
21+
}
22+
#else
23+
// gcc, clang, and icc all support this.
24+
__attribute__((noinline)) uintptr_t approximate_stack_pointer()
25+
{
26+
return reinterpret_cast<uintptr_t>(__builtin_frame_address(0));
27+
}
28+
#endif
29+
30+
// Simple awaiter that (inefficiently) resumes from inside
31+
// await_suspend, for the purpose of measuring how much stack it consumes.
32+
// This is the best we can do with MSVC prerelease coroutines prior to 16.11.
33+
struct resume_sync_from_await_suspend
34+
{
35+
bool await_ready() { return false; }
36+
void await_suspend(winrt::impl::coroutine_handle<> h) { h(); }
37+
void await_resume() { }
38+
};
39+
40+
IAsyncAction SyncCompletion()
41+
{
42+
uintptr_t initial = approximate_stack_pointer();
43+
co_await resume_sync_from_await_suspend();
44+
uintptr_t sync_usage = initial - approximate_stack_pointer();
45+
46+
initial = approximate_stack_pointer();
47+
co_await AlreadyCompleted();
48+
uintptr_t consumed = initial - approximate_stack_pointer();
49+
#ifdef _RESUMABLE_FUNCTIONS_SUPPORTED
50+
// This branch is taken only for MSVC prerelease coroutines.
51+
//
52+
// MSVC prerelease coroutines prior to 16.11 do not implement "bool await_suspend" reliably,
53+
// so we can't use it impl::await_adapter. We must resume inline inside await_suspend,
54+
// so there is a small amount of stack usage. (Pre-16.11 and post-16.11 prerelease coroutines
55+
// are interoperable, so we cannot change behavior based on which compiler we are using,
56+
// because that would introduce ODR violations. Our first opportunity to change behavior
57+
// is the ABI breaking change with MSVC standard-conforming coroutines.)
58+
REQUIRE(consumed <= sync_usage);
59+
#else
60+
// MSVC standard-conforming coroutines (as well as gcc and clang coroutines)
61+
// support "bool await_suspend" just fine.
62+
REQUIRE(consumed == 0);
63+
#endif
64+
}
65+
}
66+
TEST_CASE("await_completed_await")
67+
{
68+
SyncCompletion().get();
69+
}

test/test/test.vcxproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,7 @@
296296
<ClCompile Include="async_completed.cpp" />
297297
<ClCompile Include="async_propagate_cancel.cpp" />
298298
<ClCompile Include="async_ref_result.cpp" />
299+
<ClCompile Include="await_completed.cpp" />
299300
<ClCompile Include="box_array.cpp" />
300301
<ClCompile Include="box_delegate.cpp" />
301302
<ClCompile Include="box_guid.cpp" />
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#include "pch.h"
2+
3+
using namespace winrt;
4+
using namespace Windows::Foundation;
5+
6+
namespace
7+
{
8+
//
9+
// Checks that awaiting an already-completed async operation
10+
// does not consume additional stack.
11+
//
12+
IAsyncAction AlreadyCompleted()
13+
{
14+
co_return;
15+
}
16+
17+
#ifdef _MSC_VER
18+
__declspec(noinline) uintptr_t approximate_stack_pointer()
19+
{
20+
return reinterpret_cast<uintptr_t>(_AddressOfReturnAddress());
21+
}
22+
#else
23+
// gcc, clang, and icc all support this.
24+
__attribute__((noinline)) uintptr_t approximate_stack_pointer()
25+
{
26+
return reinterpret_cast<uintptr_t>(__builtin_frame_address(0));
27+
}
28+
#endif
29+
30+
#ifdef _RESUMABLE_FUNCTIONS_SUPPORTED
31+
#error C++20 test should be compiled in C++20 mode.
32+
#endif
33+
34+
IAsyncAction SyncCompletion()
35+
{
36+
uintptr_t initial = approximate_stack_pointer();
37+
co_await AlreadyCompleted();
38+
uintptr_t consumed = initial - approximate_stack_pointer();
39+
REQUIRE(consumed == 0);
40+
}
41+
}
42+
TEST_CASE("await_completed_await")
43+
{
44+
SyncCompletion().get();
45+
}

test/test_cpp20/test_cpp20.vcxproj

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
<RootNamespace>unittests</RootNamespace>
4141
<ProjectName>test_cpp20</ProjectName>
4242
<WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
43+
<CppWinRTLanguageStandard>latest</CppWinRTLanguageStandard>
4344
</PropertyGroup>
4445
<Import Project="$(SolutionDir)\cppwinrt.props" />
4546
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
@@ -130,7 +131,6 @@
130131
<AdditionalIncludeDirectories>$(OutputPath);Generated Files;..\</AdditionalIncludeDirectories>
131132
<PreprocessorDefinitions>NOMINMAX;_MBCS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
132133
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
133-
<LanguageStandard>stdcpplatest</LanguageStandard>
134134
</ClCompile>
135135
<Link>
136136
<SubSystem>Console</SubSystem>
@@ -152,7 +152,6 @@
152152
<AdditionalIncludeDirectories>$(OutputPath);Generated Files;..\</AdditionalIncludeDirectories>
153153
<PreprocessorDefinitions>NOMINMAX;_MBCS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
154154
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
155-
<LanguageStandard>stdcpplatest</LanguageStandard>
156155
</ClCompile>
157156
<Link>
158157
<SubSystem>Console</SubSystem>
@@ -172,7 +171,6 @@
172171
<AdditionalIncludeDirectories>$(OutputPath);Generated Files;..\</AdditionalIncludeDirectories>
173172
<PreprocessorDefinitions>NOMINMAX;_MBCS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
174173
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
175-
<LanguageStandard>stdcpplatest</LanguageStandard>
176174
</ClCompile>
177175
<Link>
178176
<SubSystem>Console</SubSystem>
@@ -192,7 +190,6 @@
192190
<AdditionalIncludeDirectories>$(OutputPath);Generated Files;..\</AdditionalIncludeDirectories>
193191
<PreprocessorDefinitions>NOMINMAX;_MBCS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
194192
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
195-
<LanguageStandard>stdcpplatest</LanguageStandard>
196193
</ClCompile>
197194
<Link>
198195
<SubSystem>Console</SubSystem>
@@ -212,7 +209,6 @@
212209
<AdditionalIncludeDirectories>$(OutputPath);Generated Files;..\</AdditionalIncludeDirectories>
213210
<PreprocessorDefinitions>NOMINMAX;_MBCS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
214211
<RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary>
215-
<LanguageStandard>stdcpplatest</LanguageStandard>
216212
</ClCompile>
217213
<Link>
218214
<SubSystem>Console</SubSystem>
@@ -234,7 +230,6 @@
234230
<AdditionalIncludeDirectories>$(OutputPath);Generated Files;..\</AdditionalIncludeDirectories>
235231
<PreprocessorDefinitions>NOMINMAX;_MBCS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
236232
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
237-
<LanguageStandard>stdcpplatest</LanguageStandard>
238233
</ClCompile>
239234
<Link>
240235
<SubSystem>Console</SubSystem>
@@ -258,7 +253,6 @@
258253
<AdditionalIncludeDirectories>$(OutputPath);Generated Files;..\</AdditionalIncludeDirectories>
259254
<PreprocessorDefinitions>NOMINMAX;_MBCS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
260255
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
261-
<LanguageStandard>stdcpplatest</LanguageStandard>
262256
</ClCompile>
263257
<Link>
264258
<SubSystem>Console</SubSystem>
@@ -282,7 +276,6 @@
282276
<AdditionalIncludeDirectories>$(OutputPath);Generated Files;..\</AdditionalIncludeDirectories>
283277
<PreprocessorDefinitions>NOMINMAX;_MBCS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
284278
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
285-
<LanguageStandard>stdcpplatest</LanguageStandard>
286279
</ClCompile>
287280
<Link>
288281
<SubSystem>Console</SubSystem>
@@ -302,6 +295,7 @@
302295
<ClInclude Include="pch.h" />
303296
</ItemGroup>
304297
<ItemGroup>
298+
<ClCompile Include="await_completed.cpp" />
305299
<ClCompile Include="format.cpp" />
306300
<ClCompile Include="hstring.cpp" />
307301
<ClCompile Include="main.cpp">

0 commit comments

Comments
 (0)