Skip to content

Commit

Permalink
attach two attributes to folly::coro to improve elidability
Browse files Browse the repository at this point in the history
Summary:
# Context
Recent upstream LLVM development gives us this opportunity to elide heap allocations for coroutine function calls. Our folly::coro's heap allocation costs us about $4M every year fleet wide. This patch potentially gives us a decent chunk of capacity savings.

This diff adds two attributes to folly:
## [[clang::coro_await_elidable]]
This attribute is introduced by llvm/llvm-project#99282.
This attribute is designed to make the following case always apply HALO
```
class [[clang::coro_await_elidable]] Task { ... };
Task<void> foo();
Task<void> bar() {
  co_await foo();
}
```
This requires 1) bar is a coroutine returns a type attributed as such; and 2) `foo()` to be immediately co_awaited.

Note: if the example above had `bar()` call itself recursively, the elide won't happen because we cannot determine the necessary frame size for potentially unbound recursion. Same goes for mutually recursive functions.

This won't work on the fan out case for the same reason. This optimization is off limits if we can't statically determine the necessary frame size for the outer most coroutine.

## [[clang::coro_await_elidable_argument]]

This attribute is introduced by llvm/llvm-project#108474.
The attribute propagates a "safe elide context" as defined in the summary of the PR.

The motivation of this attribute is to support eliding foo in the following case:
```
class [[clang::coro_await_elidable]] Task { ... };
Task<void> foo();
Task<void> bar() {
  co_await co_nothrow(foo());
}
```
`co_nothrow` itself is not a coroutine. `foo()` in this case is not immediately `co_await`ed. Hence not elidable. However, if we add this "coro_await_elidable_argument" attribute to co_nothrow's parameter (or parameter pack), we allow the compiler to treat its arguments as safe to elide as well as long as both co_nothrow() is awaited as a prvalue, and `foo()` is immediately passed to `co_nothrow`'s param as a prvalue.

This can also be useful for composition of senders in the future.

## Why is this safe to elide?
Generally, we depend on the fact that `folly::coro::Task` does not provide the user a way to destroy the caller while the callee is in a running or leave it in a resumeable state. And `co_nothrow`'s awaitable object doesn't try to destroy caller either.

## How many more cases are elided after this change?
IG is the top coroutine user according to this [strobelight](https://fburl.com/strobelight/dgx7cyzv). With this change, we found 3009 out of 4292 coroutine calls are elidable. Among the 1283 non-elidable cases, only 2 had a profile hotness greater than zero.

Reviewed By: yfeldblum

Differential Revision: D58956574

fbshipit-source-id: da8be360a03a34dec7111004a1af44e20d163839
  • Loading branch information
yuxuanchen1997 authored and facebook-github-bot committed Oct 14, 2024
1 parent b293f9e commit a98e6bb
Show file tree
Hide file tree
Showing 3 changed files with 15 additions and 2 deletions.
13 changes: 13 additions & 0 deletions folly/CppAttributes.h
Original file line number Diff line number Diff line change
Expand Up @@ -168,3 +168,16 @@
#else
#define FOLLY_ATTR_GNU_USED
#endif

#if FOLLY_HAS_CPP_ATTRIBUTE(clang::coro_await_elidable)
#define FOLLY_ATTR_CLANG_CORO_AWAIT_ELIDABLE clang::coro_await_elidable
#else
#define FOLLY_ATTR_CLANG_CORO_AWAIT_ELIDABLE
#endif

#if FOLLY_HAS_CPP_ATTRIBUTE(clang::coro_await_elidable_argument)
#define FOLLY_ATTR_CLANG_CORO_AWAIT_ELIDABLE_ARGUMENT \
clang::coro_await_elidable_argument
#else
#define FOLLY_ATTR_CLANG_CORO_AWAIT_ELIDABLE_ARGUMENT
#endif
2 changes: 1 addition & 1 deletion folly/coro/Task.h
Original file line number Diff line number Diff line change
Expand Up @@ -661,7 +661,7 @@ class FOLLY_NODISCARD TaskWithExecutor {
///
/// @refcode folly/docs/examples/folly/experimental/coro/Task.cpp
template <typename T>
class FOLLY_NODISCARD Task {
class FOLLY_NODISCARD [[FOLLY_ATTR_CLANG_CORO_AWAIT_ELIDABLE]] Task {
public:
using promise_type = detail::TaskPromise<T>;
using StorageType = typename promise_type::StorageType;
Expand Down
2 changes: 1 addition & 1 deletion folly/coro/ViaIfAsync.h
Original file line number Diff line number Diff line change
Expand Up @@ -691,7 +691,7 @@ class NothrowAwaitable

template <typename Awaitable>
detail::NothrowAwaitable<remove_cvref_t<Awaitable>> co_nothrow(
Awaitable&& awaitable) {
[[FOLLY_ATTR_CLANG_CORO_AWAIT_ELIDABLE_ARGUMENT]] Awaitable&& awaitable) {
return detail::NothrowAwaitable<remove_cvref_t<Awaitable>>{
static_cast<Awaitable&&>(awaitable)};
}
Expand Down

0 comments on commit a98e6bb

Please sign in to comment.