diff --git a/builtin-functions/kphp-light/array.txt b/builtin-functions/kphp-light/array.txt index c761056875..f88badd317 100644 --- a/builtin-functions/kphp-light/array.txt +++ b/builtin-functions/kphp-light/array.txt @@ -107,3 +107,14 @@ function rsort (&$a ::: array, $flag ::: int = SORT_REGULAR) ::: void; function sort (&$a ::: array, $flag ::: int = SORT_REGULAR) ::: void; +/** @kphp-extern-func-info interruptible */ +function uksort (&$a ::: array, callable(mixed $x, mixed $y):int $callback) ::: void; + +/** @kphp-extern-func-info interruptible */ +function usort (&$a ::: array, callable(^1[*] $x, ^1[*] $y):int $callback) ::: void; + +/** @kphp-extern-func-info interruptible */ +function uasort (&$a ::: array, callable(^1[*] $x, ^1[*] $y):int $callback) ::: void; + + + diff --git a/builtin-functions/kphp-light/unsupported/arrays.txt b/builtin-functions/kphp-light/unsupported/arrays.txt index a1fdee0cb5..6d8a78de70 100644 --- a/builtin-functions/kphp-light/unsupported/arrays.txt +++ b/builtin-functions/kphp-light/unsupported/arrays.txt @@ -46,10 +46,6 @@ function array_is_vector ($a ::: array) ::: bool; function array_is_list ($a ::: array) ::: bool; -function uasort (&$a ::: array, callable(^1[*] $x, ^1[*] $y):int $callback) ::: void; -function uksort (&$a ::: array, callable(mixed $x, mixed $y):int $callback) ::: void; -function usort (&$a ::: array, callable(^1[*] $x, ^1[*] $y):int $callback) ::: void; - /** @kphp-extern-func-info cpp_template_call */ function vk_dot_product ($a ::: array, $b ::: array) ::: ^1[*] | ^2[*]; diff --git a/compiler/code-gen/declarations.cpp b/compiler/code-gen/declarations.cpp index e1218376c5..be6c236fbc 100644 --- a/compiler/code-gen/declarations.cpp +++ b/compiler/code-gen/declarations.cpp @@ -115,8 +115,8 @@ void FunctionParams::declare_cpp_param(CodeGenerator &W, VertexAdaptor v auto var_ptr = var->var_id; if (var->ref_flag) { W << "&"; - } else if (!function->is_k2_fork && (var_ptr->marked_as_const || (!function->has_variadic_param && var_ptr->is_read_only))) { - // the top of k2 fork must take arguments by value (see C++ avoid reference parameters in coroutines) + } else if (!function->is_interruptible && (var_ptr->marked_as_const || (!function->has_variadic_param && var_ptr->is_read_only))) { + // interruptible function must take arguments by value (see C++ avoid reference parameters in coroutines) W << (!type.type->is_primitive_type() ? "const &" : ""); } W << VarName(var_ptr); diff --git a/runtime-common/core/core-types/decl/array_decl.inl b/runtime-common/core/core-types/decl/array_decl.inl index 893616334e..fcf7cdc0a3 100644 --- a/runtime-common/core/core-types/decl/array_decl.inl +++ b/runtime-common/core/core-types/decl/array_decl.inl @@ -29,7 +29,18 @@ struct array_size { namespace dl { template -void sort(TT *begin_init, TT *end_init, const T1 &compare); +void sort(TT *begin_init, TT *end_init, T1 compare) noexcept; +} + +namespace array_functions_impl_ { +/* + * async analog of array::sort and array::ksort since in runtime-light comparator can be coroutine + * */ +template +Result async_sort(array & arr, Comparator comparator, bool renumber) noexcept; + +template +Result async_ksort(array & arr, Comparator comparator) noexcept; } enum class overwrite_element { YES, NO }; @@ -438,6 +449,12 @@ private: template friend class array; + + template + friend Result array_functions_impl_::async_sort(array & arr, Comparator comparator, bool renumber) noexcept; + + template + friend Result array_functions_impl_::async_ksort(array & arr, Comparator comparator) noexcept; }; template diff --git a/runtime-light/stdlib/array/array-functions.h b/runtime-light/stdlib/array/array-functions.h index cde085d5a7..b514298a4c 100644 --- a/runtime-light/stdlib/array/array-functions.h +++ b/runtime-light/stdlib/array/array-functions.h @@ -5,16 +5,202 @@ #pragma once #include +#include #include #include #include "runtime-common/core/runtime-core.h" +#include "runtime-common/stdlib/array/array-functions.h" #include "runtime-light/coroutine/task.h" #include "runtime-light/stdlib/math/random-functions.h" #include "runtime-light/utils/concepts.h" +namespace dl { + +template +requires(std::invocable && is_async_function_v) +task_t async_sort(T *begin_init, T *end_init, Comparator compare) noexcept { + T *begin_stack[32]; + T *end_stack[32]; + + begin_stack[0] = begin_init; + end_stack[0] = end_init - 1; + + for (int depth = 0; depth >= 0; --depth) { + T *begin = begin_stack[depth]; + T *end = end_stack[depth]; + + while (begin < end) { + const auto offset = (end - begin) >> 1; + swap(*begin, begin[offset]); + + T *i = begin + 1; + T *j = end; + + while (true) { + while (i < j && (co_await std::invoke(compare, *begin, *i)) > 0) { + i++; + } + + while (i <= j && (co_await std::invoke(compare, *j, *begin)) > 0) { + j--; + } + + if (i >= j) { + break; + } + + swap(*i++, *j--); + } + + swap(*begin, *j); + + if (j - begin <= end - j) { + if (j + 1 < end) { + begin_stack[depth] = j + 1; + end_stack[depth++] = end; + } + end = j - 1; + } else { + if (begin < j - 1) { + begin_stack[depth] = begin; + end_stack[depth++] = j - 1; + } + begin = j + 1; + } + } + } + co_return; +} +} // namespace dl + namespace array_functions_impl_ { +template +Result async_sort(array &arr, Comparator comparator, bool renumber) noexcept { + using array_inner = typename array::array_inner; + using array_bucket = typename array::array_bucket; + int64_t n = arr.count(); + + if (renumber) { + if (n == 0) { + co_return; + } + + if (!arr.is_vector()) { + array_inner *res = array_inner::create(n, true); + for (array_bucket *it = arr.p->begin(); it != arr.p->end(); it = arr.p->next(it)) { + res->push_back_vector_value(it->value); + } + + arr.p->dispose(); + arr.p = res; + } else { + arr.mutate_if_vector_shared(); + } + + U *begin = reinterpret_cast(arr.p->entries()); + co_await dl::async_sort(begin, begin + n, std::move(comparator)); + co_return; + } + + if (n <= 1) { + co_return; + } + + if (arr.is_vector()) { + arr.convert_to_map(); + } else { + arr.mutate_if_map_shared(); + } + + auto **arTmp = static_cast(RuntimeAllocator::get().alloc_script_memory(n * sizeof(array_bucket *))); + uint32_t i = 0; + for (array_bucket *it = arr.p->begin(); it != arr.p->end(); it = arr.p->next(it)) { + arTmp[i++] = it; + } + php_assert(i == n); + + const auto hash_entry_cmp = [](Compare compare, const array_bucket *lhs, const array_bucket *rhs) -> task_t { + co_return(co_await std::invoke(compare, lhs->value, rhs->value)) > 0; + }; + + const auto partial_hash_entry_cmp = std::bind_front(hash_entry_cmp, std::move(comparator)); + + co_await dl::async_sort(arTmp, arTmp + n, partial_hash_entry_cmp); + + arTmp[0]->prev = arr.p->get_pointer(arr.p->end()); + arr.p->end()->next = arr.p->get_pointer(arTmp[0]); + for (uint32_t j = 1; j < n; j++) { + arTmp[j]->prev = arr.p->get_pointer(arTmp[j - 1]); + arTmp[j - 1]->next = arr.p->get_pointer(arTmp[j]); + } + arTmp[n - 1]->next = arr.p->get_pointer(arr.p->end()); + arr.p->end()->prev = arr.p->get_pointer(arTmp[n - 1]); + + RuntimeAllocator::get().free_script_memory(arTmp, n * sizeof(array_bucket *)); +} + +template +Result async_ksort(array &arr, Comparator comparator) noexcept { + using array_bucket = typename array::array_bucket; + using key_type = typename array::key_type; + using list_hash_entry = typename array::list_hash_entry; + + int64_t n = arr.count(); + if (n <= 1) { + co_return; + } + + if (arr.is_vector()) { + arr.convert_to_map(); + } else { + arr.mutate_if_map_shared(); + } + + array keys(array_size(n, true)); + for (auto *it = arr.p->begin(); it != arr.p->end(); it = arr.p->next(it)) { + keys.p->push_back_vector_value(it->get_key()); + } + + auto *keysp = reinterpret_cast(keys.p->entries()); + co_await dl::async_sort(keysp, keysp + n, std::move(comparator)); + + auto *prev = static_cast(arr.p->end()); + for (uint32_t j = 0; j < n; j++) { + list_hash_entry *cur = nullptr; + if (arr.is_int_key(keysp[j])) { + int64_t int_key = keysp[j].to_int(); + uint32_t bucket = arr.p->choose_bucket(int_key); + while (arr.p->entries()[bucket].int_key != int_key || !arr.p->entries()[bucket].string_key.is_dummy_string()) { + if (++bucket == arr.p->buf_size) [[unlikely]] { + bucket = 0; + } + } + cur = static_cast(&arr.p->entries()[bucket]); + } else { + string string_key = keysp[j].to_string(); + int64_t int_key = string_key.hash(); + array_bucket *string_entries = arr.p->entries(); + uint32_t bucket = arr.p->choose_bucket(int_key); + while ( + (string_entries[bucket].int_key != int_key || string_entries[bucket].string_key.is_dummy_string() || string_entries[bucket].string_key != string_key)) { + if (++bucket == arr.p->buf_size) [[unlikely]] { + bucket = 0; + } + } + cur = static_cast(&string_entries[bucket]); + } + + cur->prev = arr.p->get_pointer(prev); + prev->next = arr.p->get_pointer(cur); + + prev = cur; + } + prev->next = arr.p->get_pointer(arr.p->end()); + arr.p->end()->prev = arr.p->get_pointer(prev); +} + template concept convertible_to_php_bool = requires(T t) { { f$boolval(t) } -> std::convertible_to; @@ -223,19 +409,41 @@ array f$array_combine(const array &keys, const array &values) { php_critical_error("call to unsupported function"); } -template -void f$usort(array &a, const T1 &compare) { - php_critical_error("call to unsupported function"); +template +requires(std::invocable) task_t f$usort(array &a, Comparator compare) { + if constexpr (is_async_function_v) { + /* make temporary copy since functions is coroutine and sort is inplace */ + array tmp = a; + co_await array_functions_impl_::async_sort>(tmp, std::move(compare), true); + a = tmp; + co_return; + } else { + co_return a.sort(std::move(compare), true); + } } -template -void f$uasort(array &a, const T1 &compare) { - php_critical_error("call to unsupported function"); +template +requires(std::invocable) task_t f$uasort(array &a, Comparator compare) { + if constexpr (is_async_function_v) { + /* make temporary copy since functions is coroutine and sort is inplace */ + array tmp = a; + co_await array_functions_impl_::async_sort>(tmp, std::move(compare), false); + a = tmp; + } else { + co_return a.sort(std::move(compare), false); + } } -template -void f$uksort(array &a, const T1 &compare) { - php_critical_error("call to unsupported function"); +template +requires(std::invocable::key_type, typename array::key_type>) task_t f$uksort(array &a, Comparator compare) { + if constexpr (is_async_function_v) { + /* make temporary copy since functions is coroutine and sort is inplace */ + array tmp = a; + co_await array_functions_impl_::async_ksort>(tmp, std::move(compare), false); + a = tmp; + } else { + co_return a.ksort(std::move(compare)); + } } template diff --git a/runtime/array_functions.h b/runtime/array_functions.h index e1d9fba1c2..2b2a00d08f 100644 --- a/runtime/array_functions.h +++ b/runtime/array_functions.h @@ -720,3 +720,15 @@ T f$vk_dot_product(const array &a, const array &b) { } return vk_dot_product_sparse(a, b); } + +template +Result array_functions_impl_::async_sort(__attribute__((unused)) array & arr, __attribute__((unused)) Comparator comparator, __attribute__((unused)) bool renumber) noexcept { + struct async_sort_stub_class {}; + static_assert(std::is_same_v, "array async sort functions supported only in runtime light "); +} + +template +Result array_functions_impl_::async_ksort(__attribute__((unused)) array & arr, __attribute__((unused)) Comparator comparator) noexcept { + struct async_ksort_stub_class {}; + static_assert(std::is_same_v, "array async sort functions supported only in runtime light "); +} diff --git a/tests/phpt/dl/495_sort.php b/tests/phpt/dl/495_sort.php index 1035a64378..040b8cd398 100644 --- a/tests/phpt/dl/495_sort.php +++ b/tests/phpt/dl/495_sort.php @@ -1,4 +1,4 @@ -@ok benchmark callback k2_skip +@ok benchmark callback