Skip to content

Commit 4204e59

Browse files
ConorWilliamsConor
andauthored
[V4] Lift meta-functions (#112)
* lift * lift optimization * overload * decay fn to lifted * lifted pass 1 test * doc * await resume * fix semantics * todo * no separate .fn in pkg * assert non-null --------- Co-authored-by: Conor <conor@myrtle.com>
1 parent b2a4543 commit 4204e59

6 files changed

Lines changed: 456 additions & 43 deletions

File tree

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ target_sources(libfork_libfork
9191
src/core/promise.cxx
9292
src/core/stop.cxx
9393
src/core/projected.cxx
94+
src/core/lift.cxx
9495
# libfork.batteries
9596
src/batteries/batteries.cxx
9697
src/batteries/deque.cxx

src/core/core.cxx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,4 @@ export import :exception;
2323
export import :final_suspend;
2424
export import :awaitables;
2525
export import :projected;
26+
export import :lift;

src/core/lift.cxx

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
module;
2+
#include "libfork/__impl/assume.hpp"
3+
#include "libfork/__impl/exception.hpp"
4+
#include "libfork/__impl/utils.hpp"
5+
export module libfork.core:lift;
6+
7+
import libfork.utils;
8+
9+
import :task;
10+
import :ops;
11+
import :final_suspend;
12+
import :awaitables;
13+
14+
namespace lf {
15+
16+
template <typename T>
17+
using lift_store_t = std::conditional_t<std::is_lvalue_reference_v<T>, T, std::remove_cvref_t<T>>;
18+
19+
struct lift_impl {
20+
private:
21+
template <typename Fn, typename Context, typename... Args>
22+
using task_t = task<std::invoke_result_t<Fn, Args...>, Context>;
23+
24+
template <typename Fn, typename Context, typename... Args>
25+
static auto impl(lift_store_t<Fn> fn, lift_store_t<Args>... args) -> task_t<Fn, Context, Args...> {
26+
co_return std::invoke(static_cast<Fn>(fn), static_cast<Args>(args)...);
27+
}
28+
29+
public:
30+
template <typename Fn, typename Context, typename... Args>
31+
requires std::invocable<Fn &&, Args &&...>
32+
static auto operator()(env<Context>, Fn &&fn, Args &&...args) -> task_t<Fn &&, Context, Args &&...> {
33+
return impl<Fn &&, Context, Args &&...>(LF_FWD(fn), LF_FWD(args)...);
34+
}
35+
};
36+
37+
// TODO: merge fn in ops to args
38+
39+
/**
40+
* @brief Lifts a synchronous function into an asynchronous task.
41+
*
42+
* Forked lifted tasks capture rvalues by value.
43+
* Called lifted tasks have an optimized path that avoids creating a new task.
44+
*
45+
* Both invocations respect cancellation and push exceptions to the parent scope.
46+
*/
47+
export inline constexpr lift_impl lift{};
48+
49+
/**
50+
* @brief An optimization for non-forked lifted functions.
51+
*/
52+
template <worker_context Context, bool StopToken, typename R, typename Fn, typename... Args>
53+
struct lifted_awaitable : std::suspend_never {
54+
55+
[[no_unique_address]]
56+
pkg<category::call, StopToken, Context, R, Fn, Args...> pkg;
57+
58+
frame_t<Context> *parent;
59+
60+
constexpr void await_resume() noexcept {
61+
62+
// Noop if stop has been requested.
63+
if constexpr (StopToken) {
64+
if (pkg.stop_token.stop_requested()) {
65+
return;
66+
}
67+
} else {
68+
if (parent->stop_requested()) {
69+
return;
70+
}
71+
}
72+
73+
LF_TRY {
74+
if constexpr (std::is_void_v<R>) {
75+
std::move(pkg.args).apply([](lift_impl, auto &&fn, auto &&...args) -> void {
76+
std::invoke(LF_FWD(fn), LF_FWD(args)...);
77+
});
78+
} else {
79+
std::move(pkg.args).apply([addr = pkg.return_addr](lift_impl, auto &&fn, auto &&...args) -> void {
80+
LF_ASSUME(addr);
81+
*addr = std::invoke(LF_FWD(fn), LF_FWD(args)...);
82+
});
83+
}
84+
} LF_CATCH(...) {
85+
stash_current_exception(parent);
86+
}
87+
}
88+
};
89+
90+
} // namespace lf

src/core/ops.cxx

Lines changed: 13 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -41,31 +41,11 @@ template <category Cat, bool StopToken, typename Context, typename R, typename F
4141
struct [[nodiscard("You should immediately co_await this!")]] pkg {
4242
[[no_unique_address]] std::conditional_t<StopToken, stop_source::stop_token, no_stop_t> stop_token;
4343
[[no_unique_address]] std::conditional_t<std::is_void_v<R>, no_ret_t, R *> return_addr;
44-
[[no_unique_address]] Fn fn;
45-
[[no_unique_address]] tuple<Args...> args;
44+
[[no_unique_address]] tuple<Fn, Args...> args;
4645
};
4746

4847
// clang-format on
4948

50-
/**
51-
* @brief Forward the function member of a pkg correctly.
52-
*
53-
* Handles three cases:
54-
* - rvalue reference Fn: move it.
55-
* - lvalue reference Fn: return by reference.
56-
* - value type Fn (small trivially-copyable stored directly): return by value.
57-
*/
58-
template <typename Fn>
59-
constexpr auto fwd_fn(auto &&fn) noexcept -> Fn {
60-
if constexpr (std::is_rvalue_reference_v<Fn>) {
61-
return std::move(fn);
62-
} else if constexpr (std::is_lvalue_reference_v<Fn> || small_trivially_copyable<Fn>) {
63-
return fn;
64-
} else {
65-
static_assert(false, "Invalid Fn type in fwd_fn");
66-
}
67-
}
68-
6949
// =============== Join =============== //
7050

7151
struct join_type {};
@@ -107,30 +87,30 @@ struct scope_ops : scope_base {
10787

10888
template <typename R, typename... Args, async_invocable_to<R, Context, Args...> Fn>
10989
static constexpr auto fork(R *ret, Fn &&fn, Args &&...args) noexcept -> fork_pkg<R, Fn, Args...> {
110-
return {.return_addr = ret, .fn = LF_FWD(fn), .args = {LF_FWD(args)...}};
90+
return {.return_addr = ret, .args = {LF_FWD(fn), LF_FWD(args)...}};
11191
}
11292
template <typename... Args, async_invocable<Context, Args...> Fn>
11393
static constexpr auto fork_drop(Fn &&fn, Args &&...args) noexcept -> fork_pkg<void, Fn, Args...> {
114-
return {.return_addr = {}, .fn = LF_FWD(fn), .args = {LF_FWD(args)...}};
94+
return {.return_addr = {}, .args = {LF_FWD(fn), LF_FWD(args)...}};
11595
}
11696
template <typename... Args, async_invocable_to<void, Context, Args...> Fn>
11797
static constexpr auto fork(Fn &&fn, Args &&...args) noexcept -> fork_pkg<void, Fn, Args...> {
118-
return {.return_addr = {}, .fn = LF_FWD(fn), .args = {LF_FWD(args)...}};
98+
return {.return_addr = {}, .args = {LF_FWD(fn), LF_FWD(args)...}};
11999
}
120100

121101
// === Call === //
122102

123103
template <typename R, typename... Args, async_invocable_to<R, Context, Args...> Fn>
124104
static constexpr auto call(R *ret, Fn &&fn, Args &&...args) noexcept -> call_pkg<R, Fn, Args...> {
125-
return {.return_addr = ret, .fn = LF_FWD(fn), .args = {LF_FWD(args)...}};
105+
return {.return_addr = ret, .args = {LF_FWD(fn), LF_FWD(args)...}};
126106
}
127107
template <typename... Args, async_invocable<Context, Args...> Fn>
128108
static constexpr auto call_drop(Fn &&fn, Args &&...args) noexcept -> call_pkg<void, Fn, Args...> {
129-
return {.return_addr = {}, .fn = LF_FWD(fn), .args = {LF_FWD(args)...}};
109+
return {.return_addr = {}, .args = {LF_FWD(fn), LF_FWD(args)...}};
130110
}
131111
template <typename... Args, async_invocable_to<void, Context, Args...> Fn>
132112
static constexpr auto call(Fn &&fn, Args &&...args) noexcept -> call_pkg<void, Fn, Args...> {
133-
return {.return_addr = {}, .fn = LF_FWD(fn), .args = {LF_FWD(args)...}};
113+
return {.return_addr = {}, .args = {LF_FWD(fn), LF_FWD(args)...}};
134114
}
135115
};
136116

@@ -179,30 +159,30 @@ struct child_scope_ops : scope_base, stop_source {
179159

180160
template <typename R, typename... Args, async_invocable_to<R, Context, Args...> Fn>
181161
constexpr auto fork(R *ret, Fn &&fn, Args &&...args) noexcept -> fork_pkg<R, Fn, Args...> {
182-
return {.stop_token = token(), .return_addr = ret, .fn = LF_FWD(fn), .args = {LF_FWD(args)...}};
162+
return {.stop_token = token(), .return_addr = ret, .args = {LF_FWD(fn), LF_FWD(args)...}};
183163
}
184164
template <typename... Args, async_invocable<Context, Args...> Fn>
185165
constexpr auto fork_drop(Fn &&fn, Args &&...args) noexcept -> fork_pkg<void, Fn, Args...> {
186-
return {.stop_token = token(), .return_addr = {}, .fn = LF_FWD(fn), .args = {LF_FWD(args)...}};
166+
return {.stop_token = token(), .return_addr = {}, .args = {LF_FWD(fn), LF_FWD(args)...}};
187167
}
188168
template <typename... Args, async_invocable_to<void, Context, Args...> Fn>
189169
constexpr auto fork(Fn &&fn, Args &&...args) noexcept -> fork_pkg<void, Fn, Args...> {
190-
return {.stop_token = token(), .return_addr = {}, .fn = LF_FWD(fn), .args = {LF_FWD(args)...}};
170+
return {.stop_token = token(), .return_addr = {}, .args = {LF_FWD(fn), LF_FWD(args)...}};
191171
}
192172

193173
// === Call (binds this scope's stop source as child stop source) === //
194174

195175
template <typename R, typename... Args, async_invocable_to<R, Context, Args...> Fn>
196176
constexpr auto call(R *ret, Fn &&fn, Args &&...args) noexcept -> call_pkg<R, Fn, Args...> {
197-
return {.stop_token = token(), .return_addr = ret, .fn = LF_FWD(fn), .args = {LF_FWD(args)...}};
177+
return {.stop_token = token(), .return_addr = ret, .args = {LF_FWD(fn), LF_FWD(args)...}};
198178
}
199179
template <typename... Args, async_invocable<Context, Args...> Fn>
200180
constexpr auto call_drop(Fn &&fn, Args &&...args) noexcept -> call_pkg<void, Fn, Args...> {
201-
return {.stop_token = token(), .return_addr = {}, .fn = LF_FWD(fn), .args = {LF_FWD(args)...}};
181+
return {.stop_token = token(), .return_addr = {}, .args = {LF_FWD(fn), LF_FWD(args)...}};
202182
}
203183
template <typename... Args, async_invocable_to<void, Context, Args...> Fn>
204184
constexpr auto call(Fn &&fn, Args &&...args) noexcept -> call_pkg<void, Fn, Args...> {
205-
return {.stop_token = token(), .return_addr = {}, .fn = LF_FWD(fn), .args = {LF_FWD(args)...}};
185+
return {.stop_token = token(), .return_addr = {}, .args = {LF_FWD(fn), LF_FWD(args)...}};
206186
}
207187
};
208188

src/core/promise.cxx

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import :ops;
2121
import :handles;
2222
import :final_suspend;
2323
import :awaitables;
24+
import :lift;
2425

2526
// TODO: vet constexpr usage in the library
2627

@@ -68,14 +69,25 @@ struct mixin_frame {
6869

6970
// Fork/call
7071
template <category Cat, bool StopToken, typename R, typename Fn, typename... Args>
71-
constexpr auto await_transform(this auto &self, pkg<Cat, StopToken, Context, R, Fn, Args...> &&pkg) noexcept
72-
-> async_awaitable<Cat, Context> {
72+
constexpr auto
73+
await_transform(this auto &self, pkg<Cat, StopToken, Context, R, Fn, Args...> &&pkg) noexcept {
7374
LF_TRY {
7475
return self.await_transform_pkg(std::move(pkg));
7576
} LF_CATCH_ALL {
7677
stash_current_exception(&self.frame);
7778
}
78-
return {.child = nullptr};
79+
return async_awaitable<Cat, Context>{.child = nullptr};
80+
}
81+
82+
// Specialization for lifted functions
83+
template <bool StopToken, typename R, typename Fn, typename... Args>
84+
requires std::same_as<std::remove_cvref_t<Fn>, lift_impl>
85+
constexpr auto
86+
await_transform(this auto &self, pkg<category::call, StopToken, Context, R, Fn, Args...> &&pkg) noexcept {
87+
return lifted_awaitable<Context, StopToken, R, Fn, Args...>{
88+
.pkg = std::move(pkg),
89+
.parent = &self.frame,
90+
};
7991
}
8092

8193
// Custom awaitable
@@ -113,15 +125,10 @@ struct mixin_frame {
113125
constexpr auto
114126
await_transform_pkg(this auto const &self, pkg<Cat, StopToken, Context, R, Fn, Args...> &&pkg) noexcept(
115127
async_nothrow_invocable<Fn, Context, Args...>) -> async_awaitable<Cat, Context> {
116-
using U = async_result_t<Fn, Context, Args...>;
117-
118-
// clang-format off
119128

120-
promise_type<U, Context> *child_promise = get(key(), std::move(pkg.args).apply(
121-
[&](auto &&...args) LF_HOF(ctx_invoke_t<Context>{}(fwd_fn<Fn>(pkg.fn), LF_FWD(args)...))
122-
));
129+
using U = async_result_t<Fn, Context, Args...>;
123130

124-
// clang-format on
131+
promise_type<U, Context> *child_promise = get(key(), std::move(pkg.args).apply(ctx_invoke_t<Context>{}));
125132

126133
LF_ASSUME(child_promise);
127134

0 commit comments

Comments
 (0)