Edit: Thanks for everyone's answer and replies. Language Lawyer's answer is technically the correct one so that's accepted, but Human-Compiler's answer is the only one that meets the criteria (getting 2+ points) for the bounty, or that is elaborated enough on the question's specific topic.
Is it defined behavior to have an object b
placed in the coroutine state
(by e.g. having it as a parameter,
or preserving it across a suspension point),
where alignof(b) > __STDCPP_DEFAULT_NEW_ALIGNMENT__
?
Example:
inline constexpr size_t large_alignment =
__STDCPP_DEFAULT_NEW_ALIGNMENT__ * 2;
struct alignas(large_alignment) behemoth {
void attack();
unsigned char data[large_alignment];
};
task<void> invade(task_queue &q) {
behemoth b{};
co_await submit_to(q);
b.attack();
}
When a coroutine is called,
heap memory for the coroutine state
is allocated via operator new
.
This call to operator new
may take one of the following forms:
Whichever form the call takes,
note that it doesn't use the overloads
accepting a std::align_val_t
,
which are necessary to allocate memory
that must be aligned more than __STDCPP_DEFAULT_NEW_ALIGNMENT__
.
Therefore, if an object whose alignment
is larger than __STDCPP_DEFAULT_NEW_ALIGNMENT__
must be saved in the coroutine state,
there should be no way to guarantee
that the object will end up properly aligned
in memory.
Godbolt
async f(): Assertion `reinterpret_cast<uintptr_t>(&b) % 32ull == 0' failed.
so it definitely doesn't work on GCC trunk
(11.0.1 20210307
).
Replacing 32
with 16
(which equals __STDCPP_DEFAULT_NEW_ALIGNMENT__
)
eliminates this assertion failure.
godbolt.org cannot run Windows binaries, but the assertion fires with MSVC on my computer as well.
From my reading, this would be undefined behavior.
dcl.fct.def.coroutine/9 covers the lookup order for determining the allocation function that will be used should the coroutine need additional storage. The lookup order is quite clear:
An implementation may need to allocate additional storage for a coroutine. This storage is known as the coroutine state and is obtained by calling a non-array allocation function ([basic.stc.dynamic.allocation]).
The allocation function's name is looked up in the scope of the promise type. If this lookup fails, the allocation function's name is looked up in the global scope. If the lookup finds an allocation function in the scope of the promise type, overload resolution is performed on a function call created by assembling an argument list. The first argument is the amount of space requested, and has type std::size_t. The lvalues
p1
…pn
are the succeeding arguments.If no viable function is found ([over.match.viable]), overload resolution is performed again on a function call created by passing just the amount of space required as an argument of type std::size_t.
(Emphasis mine)
This explicitly mentions that the new
overload it will call must start with a std::size_t
argument, and may optionally operate on a list of lvalue references p1
, p2
, ..., pn
(if its found in the scope of the promise).
Since in your above example there is no custom operator new
defined for the promise type, that means it must select ::operator new(std::size_t)
as the overload.
As you already know, ::operator new
is only guaranteed to be aligned to __STDCPP_DEFAULT_NEW_ALIGNMENT__
-- which is below the extended alignment required for the coroutine storage. This effectively makes any extended-aligned type in a coroutine be undefined behavior due to misalignment.
Because of how strict the wording is that it must call ::operator new(std::size_t)
, this should be consistent on any system that implements c++20
correctly. If an implementation chose to support extended-aligned types, it would technically be violating the standard by calling the wrong new
overload (which would be an observable deviation).
Judging by the wording on the overload resolution for the allocation function, I think in a case where you require extended-alignment, you should be defining a member-based operator new
for your promise that is aware of the possible alignment requirement.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With