Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[k2] make sort functions implementation runtime specific #1208

Open
wants to merge 18 commits into
base: master
Choose a base branch
from
11 changes: 11 additions & 0 deletions builtin-functions/kphp-light/array.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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;



4 changes: 0 additions & 4 deletions builtin-functions/kphp-light/unsupported/arrays.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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[*];

4 changes: 2 additions & 2 deletions compiler/code-gen/declarations.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,8 @@ void FunctionParams::declare_cpp_param(CodeGenerator &W, VertexAdaptor<op_var> 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);
Expand Down
19 changes: 18 additions & 1 deletion runtime-common/core/core-types/decl/array_decl.inl
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,18 @@ struct array_size {

namespace dl {
template<class T, class TT, class T1>
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<typename Result, typename U, typename Comparator>
Result async_sort(array<U> & arr, Comparator comparator, bool renumber) noexcept;

template<typename Result, typename U, typename Comparator>
Result async_ksort(array<U> & arr, Comparator comparator) noexcept;
}

enum class overwrite_element { YES, NO };
Expand Down Expand Up @@ -438,6 +449,12 @@ private:

template<class T1>
friend class array;

template<typename Result, typename U, typename Comparator>
friend Result array_functions_impl_::async_sort(array<U> & arr, Comparator comparator, bool renumber) noexcept;

template<typename Result, typename U, typename Comparator>
friend Result array_functions_impl_::async_ksort(array<U> & arr, Comparator comparator) noexcept;
};

template<class T>
Expand Down
226 changes: 217 additions & 9 deletions runtime-light/stdlib/array/array-functions.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,202 @@
#pragma once

#include <concepts>
#include <cstdint>
#include <functional>
#include <utility>

#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<typename T, typename Comparator>
requires(std::invocable<Comparator, T, T> && is_async_function_v<Comparator, T, T>)
task_t<void> 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<typename Result, typename U, typename Comparator>
Result async_sort(array<U> &arr, Comparator comparator, bool renumber) noexcept {
using array_inner = typename array<U>::array_inner;
using array_bucket = typename array<U>::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<U *>(arr.p->entries());
co_await dl::async_sort<U, decltype(comparator)>(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<array_bucket **>(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 = []<typename Compare>(Compare compare, const array_bucket *lhs, const array_bucket *rhs) -> task_t<bool> {
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<array_bucket *, decltype(partial_hash_entry_cmp)>(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<typename Result, typename U, typename Comparator>
Result async_ksort(array<U> &arr, Comparator comparator) noexcept {
using array_bucket = typename array<U>::array_bucket;
using key_type = typename array<U>::key_type;
using list_hash_entry = typename array<U>::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<key_type> 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<key_type *>(keys.p->entries());
co_await dl::async_sort<key_type, Comparator>(keysp, keysp + n, std::move(comparator));

auto *prev = static_cast<list_hash_entry *>(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<list_hash_entry *>(&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<list_hash_entry *>(&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<typename T>
concept convertible_to_php_bool = requires(T t) {
{ f$boolval(t) } -> std::convertible_to<bool>;
Expand Down Expand Up @@ -223,19 +409,41 @@ array<T> f$array_combine(const array<T1> &keys, const array<T> &values) {
php_critical_error("call to unsupported function");
}

template<class T, class T1>
void f$usort(array<T> &a, const T1 &compare) {
php_critical_error("call to unsupported function");
template<class T, class Comparator>
requires(std::invocable<Comparator, T, T>) task_t<void> f$usort(array<T> &a, Comparator compare) {
if constexpr (is_async_function_v<Comparator, T, T>) {
/* make temporary copy since functions is coroutine and sort is inplace */
array<T> tmp = a;
co_await array_functions_impl_::async_sort<task_t<void>>(tmp, std::move(compare), true);
a = tmp;
co_return;
} else {
co_return a.sort(std::move(compare), true);
}
}

template<class T, class T1>
void f$uasort(array<T> &a, const T1 &compare) {
php_critical_error("call to unsupported function");
template<class T, class Comparator>
requires(std::invocable<Comparator, T, T>) task_t<void> f$uasort(array<T> &a, Comparator compare) {
if constexpr (is_async_function_v<Comparator, T, T>) {
/* make temporary copy since functions is coroutine and sort is inplace */
array<T> tmp = a;
co_await array_functions_impl_::async_sort<task_t<void>>(tmp, std::move(compare), false);
a = tmp;
} else {
co_return a.sort(std::move(compare), false);
}
}

template<class T, class T1>
void f$uksort(array<T> &a, const T1 &compare) {
php_critical_error("call to unsupported function");
template<class T, class Comparator>
requires(std::invocable<Comparator, typename array<T>::key_type, typename array<T>::key_type>) task_t<void> f$uksort(array<T> &a, Comparator compare) {
if constexpr (is_async_function_v<Comparator, T, T>) {
/* make temporary copy since functions is coroutine and sort is inplace */
array<T> tmp = a;
co_await array_functions_impl_::async_ksort<task_t<void>>(tmp, std::move(compare), false);
a = tmp;
} else {
co_return a.ksort(std::move(compare));
}
}

template<class T>
Expand Down
12 changes: 12 additions & 0 deletions runtime/array_functions.h
Original file line number Diff line number Diff line change
Expand Up @@ -720,3 +720,15 @@ T f$vk_dot_product(const array<T> &a, const array<T> &b) {
}
return vk_dot_product_sparse<T>(a, b);
}

template<typename Result, typename U, typename Comparator>
Result array_functions_impl_::async_sort([[maybe_unused]] array<U> & arr, [[maybe_unused]] Comparator comparator, [[maybe_unused]] bool renumber) noexcept {
struct async_sort_stub_class {};
static_assert(std::is_same_v<Result, async_sort_stub_class>, "array async sort functions supported only in runtime light ");
}

template<typename Result, typename U, typename Comparator>
Result array_functions_impl_::async_ksort([[maybe_unused]] array<U> & arr, [[maybe_unused]] Comparator comparator) noexcept {
struct async_ksort_stub_class {};
static_assert(std::is_same_v<Result, async_ksort_stub_class>, "array async sort functions supported only in runtime light ");
}
2 changes: 1 addition & 1 deletion tests/phpt/cl/156_recursive_assumptions.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@ok k2_skip
@ok
<?php

/**
Expand Down
2 changes: 1 addition & 1 deletion tests/phpt/dl/495_sort.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@ok benchmark callback k2_skip
@ok benchmark callback
<?php
echo "*** Testing sort() : basic functionality ***\n";

Expand Down
2 changes: 1 addition & 1 deletion tests/phpt/nn/010_natsort.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
@ok k2_skip
@ok
<?php
/*
* proto bool natsort ( array &$array )
Expand Down
Loading