From 3ff5b53c0404d0e2c79721aaf4cadfb5d00dee28 Mon Sep 17 00:00:00 2001 From: Alexander Polyakov Date: Tue, 17 Sep 2024 15:26:46 +0300 Subject: [PATCH] K2: add support for asynchronous callbacks (#1103) Also, this commit adds array_map implementation that supports both sync and async functors --- builtin-functions/kphp-light/array.txt | 4 +++ builtin-functions/kphp-light/functions.txt | 1 + .../kphp-light/unsupported/arrays.txt | 1 - compiler/code-gen/vertex-compiler.cpp | 25 ++++++++++----- runtime-light/coroutine/task.h | 31 +++++++++++++++++++ runtime-light/stdlib/array/array-functions.h | 24 ++++++++++++-- tests/k2-components/test_async_builtins.php | 31 +++++++++++++++++++ 7 files changed, 106 insertions(+), 11 deletions(-) create mode 100644 builtin-functions/kphp-light/array.txt create mode 100644 tests/k2-components/test_async_builtins.php diff --git a/builtin-functions/kphp-light/array.txt b/builtin-functions/kphp-light/array.txt new file mode 100644 index 0000000000..a097ffa1e4 --- /dev/null +++ b/builtin-functions/kphp-light/array.txt @@ -0,0 +1,4 @@ + #include "common/wrappers/field_getter.h" - #include "common/wrappers/likely.h" #include "compiler/code-gen/common.h" #include "compiler/code-gen/const-globals-batched-mem.h" @@ -17,17 +16,18 @@ #include "compiler/code-gen/files/tracing-autogen.h" #include "compiler/code-gen/naming.h" #include "compiler/code-gen/raw-data.h" +#include "compiler/compiler-core.h" #include "compiler/data/class-data.h" #include "compiler/data/define-data.h" #include "compiler/data/ffi-data.h" #include "compiler/data/function-data.h" #include "compiler/data/src-file.h" #include "compiler/data/var-data.h" -#include "compiler/type-hint.h" #include "compiler/inferring/public.h" #include "compiler/name-gen.h" -#include "compiler/vertex.h" +#include "compiler/type-hint.h" #include "compiler/vertex-util.h" +#include "compiler/vertex.h" namespace { @@ -1991,7 +1991,12 @@ void compile_callback_of_builtin(VertexAdaptor root, Cod * Will be transformed to: * array_map([captured1 = $extern] (auto &&... args) { * return lambda$xxx(captured1, std::forward(args)...); - * }), const_array); + * }, const_array); + * or in K2 mode: + * co_await array_map([captured1 = $extern] (auto &&... args) (-> task_t)? { + * (return lambda$xxx(captured1, std::forward(args)...); + * | co_return(co_await lambda$xxx(captured1, std::forward(args)...));) + * }, const_array); * Where the body calls a lambda function: * int lambda$xxx(int $extern, int $x) { return $xx + $extern; } * Captured vars are not always op_var. For example, [captured1 = check_not_false($extern).val()] after smart casts. @@ -2003,13 +2008,19 @@ void compile_callback_of_builtin(VertexAdaptor root, Cod W << "captured" << (++idx) << " = " << cpp_captured; }) << "]"; - FunctionSignatureGenerator(W) << "(auto &&... args) " << BEGIN; + const bool k2_async_callback = G->is_output_mode_k2() && root->func_id->is_interruptible; + + FunctionSignatureGenerator(W) << "(auto &&... args) "; + if (k2_async_callback) { // add explicit return type to make this lambda async + W << "-> task_t<" << TypeName(tinf::get_type(root->func_id, -1)) << "> "; + } + W << BEGIN; - W << "return " << FunctionName(root->func_id) << "("; + W << (k2_async_callback ? "co_return(co_await " : "return ") << FunctionName(root->func_id) << "("; for (int idx = 1; idx <= root->size(); ++idx) { W << "captured" << idx << ", "; } - W << "std::forward(args)...);"; + W << "std::forward(args)...)" << (k2_async_callback ? ")" : "") << ";"; W << NL << END; W << UnlockComments{}; diff --git a/runtime-light/coroutine/task.h b/runtime-light/coroutine/task.h index db8dff5c08..5751936c48 100644 --- a/runtime-light/coroutine/task.h +++ b/runtime-light/coroutine/task.h @@ -8,6 +8,7 @@ #include #include #include +#include #include #include "common/containers/final_action.h" @@ -238,3 +239,33 @@ struct task_t : public task_base_t { return task_t{std::coroutine_handle<>::from_address(std::exchange(handle_address, nullptr))}; } }; + +// === Type traits ================================================================================ + +template +requires std::invocable inline constexpr bool is_async_function_v = requires { + {static_cast>(std::declval>())}; +}; + +// ================================================================================================ + +template +requires std::invocable class async_function_unwrapped_return_type { + using return_type = std::invoke_result_t; + + template + struct task_inner { + using type = U; + }; + + template + struct task_inner> { + using type = U; + }; + +public: + using type = task_inner::type; +}; + +template +requires std::invocable using async_function_unwrapped_return_type_t = async_function_unwrapped_return_type::type; diff --git a/runtime-light/stdlib/array/array-functions.h b/runtime-light/stdlib/array/array-functions.h index 65cd664eeb..4eec86f6df 100644 --- a/runtime-light/stdlib/array/array-functions.h +++ b/runtime-light/stdlib/array/array-functions.h @@ -4,7 +4,12 @@ #pragma once +#include +#include +#include + #include "runtime-core/runtime-core.h" +#include "runtime-light/coroutine/task.h" inline constexpr int64_t SORT_REGULAR = 0; inline constexpr int64_t SORT_NUMERIC = 1; @@ -60,9 +65,22 @@ array f$array_filter_by_key(const array &a, const T1 &callback) noexcept { php_critical_error("call to unsupported function"); } -template, T>> -array f$array_map(const CallbackT &callback, const array &a) { - php_critical_error("call to unsupported function"); +/** + * Currently, array_map is always considered async. Despite we rely on symmetric transfer optimization, + * we need to be careful with such functions. We may want to split such functions into sync and async + * versions in case we face with performance problems. + */ +template F, class R = async_function_unwrapped_return_type_t> +task_t> f$array_map(F f, array arr) noexcept { + array result{arr.size()}; + for (const auto &it : arr) { + if constexpr (is_async_function_v) { + result.set_value(it.get_key(), co_await std::invoke(f, it.get_value())); + } else { + result.set_value(it.get_key(), std::invoke(f, it.get_value())); + } + } + co_return result; } template diff --git a/tests/k2-components/test_async_builtins.php b/tests/k2-components/test_async_builtins.php new file mode 100644 index 0000000000..6708ac8f8d --- /dev/null +++ b/tests/k2-components/test_async_builtins.php @@ -0,0 +1,31 @@ +