Skip to content

Commit eb28b1b

Browse files
authored
[k2] pass const coroutine parameters by const ref where it's safe (#1427)
In certain scenarios, it is safe for coroutines to accept arguments by reference. This commit allows the code generator to produce user-defined coroutines with reference parameters. The rationale behind this decision is that all await points are encapsulated within runtime functions, which are implemented with safety in mind. This commit also updates several runtime functions to accept arguments by const &. An additional // SAFETY comment has been added to explain why this change is considered safe.
1 parent f5dd325 commit eb28b1b

7 files changed

Lines changed: 32 additions & 31 deletions

File tree

compiler/code-gen/declarations.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,7 @@ void FunctionParams::declare_cpp_param(CodeGenerator &W, VertexAdaptor<op_var> v
116116
auto var_ptr = var->var_id;
117117
if (var->ref_flag) {
118118
W << "&";
119-
} else if (!function->is_interruptible && (var_ptr->marked_as_const || (!function->has_variadic_param && var_ptr->is_read_only))) {
120-
// interruptible function must take arguments by value (see C++ avoid reference parameters in coroutines)
119+
} else if (!function->is_k2_fork && (var_ptr->marked_as_const || (!function->has_variadic_param && var_ptr->is_read_only))) {
121120
W << (!type.type->is_primitive_type() ? "const &" : "");
122121
}
123122
W << VarName(var_ptr);

compiler/code-gen/vertex-compiler.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -920,7 +920,6 @@ void compile_func_call(VertexAdaptor<op_func_call> root, CodeGenerator &W, func_
920920
W << "SAVE_BUILTIN_CALL_STATS(\"" << func->name << "\", (";
921921
}
922922

923-
924923
if (mode == func_call_mode::fork_call) {
925924
if (func->is_interruptible) {
926925
W << "(kphp::forks::start(" << FunctionName(func);

compiler/data/function-data.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ class FunctionData {
118118
bool cpp_variadic_call = false;
119119
bool is_resumable = false;
120120
bool is_interruptible = false;
121+
bool is_k2_fork = false;
121122
bool is_stub = false;
122123
bool need_generated_stub = false;
123124
bool can_be_implicitly_interrupted_by_other_resumable = false;

compiler/pipes/calc-bad-vars.cpp

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@
66

77
#include <algorithm>
88
#include <queue>
9+
#include <utility>
910
#include <vector>
1011

1112
#include "compiler/compiler-core.h"
1213
#include "compiler/data/class-data.h"
14+
#include "compiler/data/data_ptr.h"
1315
#include "compiler/data/src-file.h"
1416
#include "compiler/function-pass.h"
1517
#include "compiler/kphp_assert.h"
@@ -558,10 +560,16 @@ class CalcBadVars {
558560
}
559561
}
560562

561-
static void calc_k2_fork(const FuncCallGraph &call_graph, const std::vector<DepData> &dep_data) {
563+
static void calc_k2_fork(const FuncCallGraph& call_graph, const std::vector<DepData>& dep_data) {
562564
for (int i = 0; i < call_graph.n; ++i) {
563-
for (const auto &fork : dep_data[i].forks) {
565+
for (const auto& fork : dep_data[i].forks) {
564566
fork->is_interruptible = true;
567+
if (!std::exchange(fork->is_k2_fork, true)) { // check only once
568+
for (VarPtr param : fork->param_ids) {
569+
kphp_error(!param->is_reference, fmt_format("Function '{}' cannot be forked since it has a reference parameter '{}'\n", fork->as_human_readable(),
570+
param->as_human_readable()));
571+
}
572+
}
565573
}
566574
}
567575
}

runtime-light/stdlib/array/array-functions.h

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ template<class T, std::invocable<T> F>
209209
requires convertible_to_php_bool<kphp::coro::async_function_return_type_t<F, T>>
210210
kphp::coro::task<array<T>> array_filter_impl(array<T> a, F f) noexcept {
211211
array<T> result{a.size()};
212-
for (const auto& it : a) {
212+
for (const auto& it : std::as_const(a)) {
213213
bool condition{};
214214
if constexpr (kphp::coro::is_async_function_v<F, T>) {
215215
condition = f$boolval(co_await std::invoke(f, it.get_value()));
@@ -227,7 +227,7 @@ template<class T, std::invocable<typename array<T>::const_iterator::key_type> F>
227227
requires convertible_to_php_bool<kphp::coro::async_function_return_type_t<F, typename array<T>::const_iterator::key_type>>
228228
kphp::coro::task<array<T>> array_filter_by_key_impl(array<T> a, F f) noexcept {
229229
array<T> result{a.size()};
230-
for (const auto& it : a) {
230+
for (const auto& it : std::as_const(a)) {
231231
bool condition{};
232232
if constexpr (kphp::coro::is_async_function_v<F, typename array<T>::const_iterator::key_type>) {
233233
condition = f$boolval(co_await std::invoke(f, it.get_key()));
@@ -245,7 +245,7 @@ template<class T, class F>
245245
requires convertible_to_php_bool<kphp::coro::async_function_return_type_t<F, typename array<T>::const_iterator::value_type>>
246246
kphp::coro::task<std::tuple<typename array<T>::const_iterator::key_type, typename array<T>::const_iterator::value_type>> array_find_impl(array<T> a,
247247
F f) noexcept {
248-
for (const auto& it : a) {
248+
for (const auto& it : std::as_const(a)) {
249249
bool condition{};
250250
if constexpr (kphp::coro::is_async_function_v<F, typename array<T>::const_iterator::value_type>) {
251251
condition = co_await std::invoke(f, it.get_value());
@@ -339,7 +339,7 @@ mixed f$array_rand(const array<T>& a, int64_t num) noexcept {
339339
template<class A, std::invocable<A> F, class R = kphp::coro::async_function_return_type_t<F, A>>
340340
kphp::coro::task<array<R>> f$array_map(F f, array<A> a) noexcept {
341341
array<R> result{a.size()};
342-
for (const auto& it : a) {
342+
for (const auto& it : std::as_const(a)) {
343343
if constexpr (kphp::coro::is_async_function_v<F, A>) {
344344
result.set_value(it.get_key(), co_await std::invoke(f, it.get_value()));
345345
} else {
@@ -353,7 +353,7 @@ template<class R, class T, std::invocable<R, T> F, class I>
353353
requires std::constructible_from<R, std::add_rvalue_reference_t<I>>
354354
kphp::coro::task<R> f$array_reduce(array<T> a, F f, I init) noexcept {
355355
R result{R(std::move(init))}; // explicit constructor call to avoid implicit cast
356-
for (const auto& it : a) {
356+
for (const auto& it : std::as_const(a)) {
357357
if constexpr (kphp::coro::is_async_function_v<F, R, T>) {
358358
result = co_await std::invoke(f, result, it.get_value());
359359
} else {
@@ -365,12 +365,9 @@ kphp::coro::task<R> f$array_reduce(array<T> a, F f, I init) noexcept {
365365

366366
template<class T, class Comparator>
367367
requires(std::invocable<Comparator, T, T>)
368-
kphp::coro::task<> f$usort(array<T>& a, Comparator compare) {
368+
kphp::coro::task<> f$usort(array<T>& a, Comparator compare) noexcept {
369369
if constexpr (kphp::coro::is_async_function_v<Comparator, T, T>) {
370-
/* ATTENTION: temporary copy is necessary since functions is coroutine and sort is inplace */
371-
array<T> tmp{a};
372-
co_await array_functions_impl_::async_sort<kphp::coro::task<>>(tmp, std::move(compare), true);
373-
a = tmp;
370+
co_await array_functions_impl_::async_sort<kphp::coro::task<>>(a, std::move(compare), true);
374371
co_return;
375372
} else {
376373
co_return a.sort(std::move(compare), true);
@@ -379,25 +376,19 @@ kphp::coro::task<> f$usort(array<T>& a, Comparator compare) {
379376

380377
template<class T, class Comparator>
381378
requires(std::invocable<Comparator, T, T>)
382-
kphp::coro::task<> f$uasort(array<T>& a, Comparator compare) {
379+
kphp::coro::task<> f$uasort(array<T>& a, Comparator compare) noexcept {
383380
if constexpr (kphp::coro::is_async_function_v<Comparator, T, T>) {
384-
/* ATTENTION: temporary copy is necessary since functions is coroutine and sort is inplace */
385-
array<T> tmp{a};
386-
co_await array_functions_impl_::async_sort<kphp::coro::task<>>(tmp, std::move(compare), false);
387-
a = tmp;
381+
co_await array_functions_impl_::async_sort<kphp::coro::task<>>(a, std::move(compare), false);
388382
} else {
389383
co_return a.sort(std::move(compare), false);
390384
}
391385
}
392386

393387
template<class T, class Comparator>
394388
requires(std::invocable<Comparator, typename array<T>::key_type, typename array<T>::key_type>)
395-
kphp::coro::task<> f$uksort(array<T>& a, Comparator compare) {
389+
kphp::coro::task<> f$uksort(array<T>& a, Comparator compare) noexcept {
396390
if constexpr (kphp::coro::is_async_function_v<Comparator, typename array<T>::key_type, typename array<T>::key_type>) {
397-
/* ATTENTION: temporary copy is necessary since functions is coroutine and sort is inplace */
398-
array<T> tmp{a};
399-
co_await array_functions_impl_::async_ksort<kphp::coro::task<>>(tmp, std::move(compare));
400-
a = tmp;
391+
co_await array_functions_impl_::async_ksort<kphp::coro::task<>>(a, std::move(compare));
401392
} else {
402393
co_return a.ksort(std::move(compare));
403394
}

runtime-light/stdlib/confdata/confdata-functions.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ mixed extract_confdata_value(const tl::confdataValue& confdata_value) noexcept {
4444

4545
} // namespace
4646

47-
kphp::coro::task<mixed> f$confdata_get_value(string key) noexcept {
47+
kphp::coro::task<mixed> f$confdata_get_value(const string& key) noexcept {
4848
tl::ConfdataGet confdata_get{.key = {.value = {key.c_str(), key.size()}}};
4949
tl::storer tls{confdata_get.footprint()};
5050
confdata_get.store(tls);
@@ -70,7 +70,7 @@ kphp::coro::task<mixed> f$confdata_get_value(string key) noexcept {
7070
co_return extract_confdata_value(*maybe_confdata_value.opt_value); // the key exists
7171
}
7272

73-
kphp::coro::task<array<mixed>> f$confdata_get_values_by_any_wildcard(string wildcard) noexcept {
73+
kphp::coro::task<array<mixed>> f$confdata_get_values_by_any_wildcard(const string& wildcard) noexcept {
7474
static constexpr size_t CONFDATA_GET_WILDCARD_STREAM_CAPACITY = 1 << 20;
7575

7676
tl::ConfdataGetWildcard confdata_get_wildcard{.wildcard = {.value = {wildcard.c_str(), wildcard.size()}}};

runtime-light/stdlib/confdata/confdata-functions.h

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,14 @@ inline bool f$is_confdata_loaded() noexcept {
1414
return k2::access(kphp::confdata::COMPONENT_NAME) == k2::errno_ok;
1515
}
1616

17-
kphp::coro::task<mixed> f$confdata_get_value(string key) noexcept;
17+
// SAFETY: `key` is only used before await point
18+
kphp::coro::task<mixed> f$confdata_get_value(const string& key) noexcept;
1819

19-
kphp::coro::task<array<mixed>> f$confdata_get_values_by_any_wildcard(string wildcard) noexcept;
20+
// SAFETY: `wildcard` is only used before await point
21+
kphp::coro::task<array<mixed>> f$confdata_get_values_by_any_wildcard(const string& wildcard) noexcept;
2022

21-
inline kphp::coro::task<array<mixed>> f$confdata_get_values_by_predefined_wildcard(string wildcard) noexcept {
23+
// SAFETY: `f$confdata_get_values_by_any_wildcard` is coro-pass-by-ref safe
24+
inline kphp::coro::task<array<mixed>> f$confdata_get_values_by_predefined_wildcard(const string& wildcard) noexcept {
2225
kphp::log::info("k2-confdata doesn't support predefined wildcard optimization. wildcard: {}", wildcard.c_str());
23-
co_return co_await f$confdata_get_values_by_any_wildcard(std::move(wildcard));
26+
co_return co_await f$confdata_get_values_by_any_wildcard(wildcard);
2427
}

0 commit comments

Comments
 (0)