From 2022e07dd51f77ce44f03ae2ba63f5d8094ad737 Mon Sep 17 00:00:00 2001 From: Alexander Polyakov Date: Fri, 5 Jul 2024 14:40:18 +0300 Subject: [PATCH] Runtime light: RPC support (#1030) Add preliminary support for RPC into runtime-light. Also, the PR fixes a bug in unsycnhronized_memory_resource. It prevents defragmentation from merging two memory pieces from distinct memory arenas, e.g. main memory resource and extra memory pool. --- builtin-functions/kphp-light/functions.txt | 78 ++- cmake/init-compilation-flags.cmake | 3 + compiler/code-gen/files/init-scripts.cpp | 1 - .../code-gen/files/tl2cpp/tl-combinator.cpp | 13 +- .../code-gen/files/tl2cpp/tl-combinator.h | 2 +- .../code-gen/files/tl2cpp/tl-function.cpp | 8 +- compiler/code-gen/files/tl2cpp/tl-type.cpp | 10 +- compiler/code-gen/files/tl2cpp/tl-type.h | 8 +- compiler/code-gen/files/tl2cpp/tl2cpp.cpp | 16 +- .../allocator/script-allocator-managed.h | 4 + .../details/memory_ordered_chunk_list.cpp | 11 +- .../details/memory_ordered_chunk_list.h | 3 +- .../memory-resource/extra-memory-pool.h | 6 +- .../monotonic_buffer_resource.h | 7 +- .../unsynchronized_pool_resource.cpp | 6 +- .../unsynchronized_pool_resource.h | 3 +- runtime-light/allocator/allocator.h | 14 + .../allocator/runtime-light-allocator.cpp | 28 +- runtime-light/component/component.h | 10 +- runtime-light/component/image.h | 6 +- runtime-light/coroutine/task.h | 2 +- runtime-light/runtime-light.cmake | 20 +- runtime-light/stdlib/rpc/rpc-api.cpp | 522 ++++++++++++++++ runtime-light/stdlib/rpc/rpc-api.h | 113 ++++ runtime-light/stdlib/rpc/rpc-context.cpp | 26 + runtime-light/stdlib/rpc/rpc-context.h | 67 +++ .../stdlib/rpc/rpc-extra-headers.cpp | 67 +++ runtime-light/stdlib/rpc/rpc-extra-headers.h | 38 ++ runtime-light/stdlib/rpc/rpc-extra-info.cpp | 29 + runtime-light/stdlib/rpc/rpc-extra-info.h | 30 + runtime-light/stdlib/rpc/rpc-tl-defs.h | 41 ++ runtime-light/stdlib/rpc/rpc-tl-error.cpp | 94 +++ runtime-light/stdlib/rpc/rpc-tl-error.h | 53 ++ runtime-light/stdlib/rpc/rpc-tl-func-base.h | 34 ++ runtime-light/stdlib/rpc/rpc-tl-function.h | 103 ++++ .../stdlib/rpc/rpc-tl-kphp-request.h | 60 ++ runtime-light/stdlib/rpc/rpc-tl-query.cpp | 85 +++ runtime-light/stdlib/rpc/rpc-tl-query.h | 36 ++ runtime-light/stdlib/rpc/rpc-tl-request.cpp | 47 ++ runtime-light/stdlib/rpc/rpc-tl-request.h | 56 ++ runtime-light/stdlib/stdlib.cmake | 7 + runtime-light/tl/tl-builtins.cpp | 204 +++++++ runtime-light/tl/tl-builtins.h | 560 ++++++++++++++++++ runtime-light/tl/tl.cmake | 3 + runtime-light/utils/concepts.h | 10 + runtime/rpc.cpp | 18 +- runtime/tl/rpc_request.h | 4 +- runtime/tl/rpc_tl_query.cpp | 10 +- runtime/tl/rpc_tl_query.h | 6 +- runtime/tl/tl_builtins.h | 48 +- runtime/typed_rpc.cpp | 6 +- .../details/memory_chunk_tree-test.cpp | 44 +- .../memory_ordered_chunk_list-test.cpp | 24 +- .../unsynchronized_pool_resource-test.cpp | 46 +- 54 files changed, 2597 insertions(+), 153 deletions(-) create mode 100644 runtime-light/allocator/allocator.h create mode 100644 runtime-light/stdlib/rpc/rpc-api.cpp create mode 100644 runtime-light/stdlib/rpc/rpc-api.h create mode 100644 runtime-light/stdlib/rpc/rpc-context.cpp create mode 100644 runtime-light/stdlib/rpc/rpc-context.h create mode 100644 runtime-light/stdlib/rpc/rpc-extra-headers.cpp create mode 100644 runtime-light/stdlib/rpc/rpc-extra-headers.h create mode 100644 runtime-light/stdlib/rpc/rpc-extra-info.cpp create mode 100644 runtime-light/stdlib/rpc/rpc-extra-info.h create mode 100644 runtime-light/stdlib/rpc/rpc-tl-defs.h create mode 100644 runtime-light/stdlib/rpc/rpc-tl-error.cpp create mode 100644 runtime-light/stdlib/rpc/rpc-tl-error.h create mode 100644 runtime-light/stdlib/rpc/rpc-tl-func-base.h create mode 100644 runtime-light/stdlib/rpc/rpc-tl-function.h create mode 100644 runtime-light/stdlib/rpc/rpc-tl-kphp-request.h create mode 100644 runtime-light/stdlib/rpc/rpc-tl-query.cpp create mode 100644 runtime-light/stdlib/rpc/rpc-tl-query.h create mode 100644 runtime-light/stdlib/rpc/rpc-tl-request.cpp create mode 100644 runtime-light/stdlib/rpc/rpc-tl-request.h create mode 100644 runtime-light/tl/tl-builtins.cpp create mode 100644 runtime-light/tl/tl-builtins.h create mode 100644 runtime-light/tl/tl.cmake create mode 100644 runtime-light/utils/concepts.h diff --git a/builtin-functions/kphp-light/functions.txt b/builtin-functions/kphp-light/functions.txt index 75289b9005..d92b9dad24 100644 --- a/builtin-functions/kphp-light/functions.txt +++ b/builtin-functions/kphp-light/functions.txt @@ -76,6 +76,53 @@ function get_hash_of_class (object $klass) ::: int; function strlen ($str ::: string) ::: int; +// === Rpc ======================================================================================== + +/** @kphp-tl-class */ +interface RpcFunction { + public function getTLFunctionName() : string; +} + +/** @kphp-tl-class */ +interface RpcFunctionReturnResult {} + +// type ReqResult <=> RpcResponse +/** @kphp-tl-class */ +interface RpcResponse { + public function getResult() : @tl\RpcFunctionReturnResult; + public function getHeader() : @tl\_common\Types\rpcResponseHeader; + public function getError() : @tl\_common\Types\rpcResponseError; + public function isError() : bool; +} + +/** + * 'KphpRpcRequestsExtraInfo' is a builtin KPHP class. It may accumulate extra information + * about RPC requests sent in both typed and untyped versions of rpc_tl_query builtins. + */ +final class KphpRpcRequestsExtraInfo { + /** + * 'get' returns an array of extra information (request size) about sent RPC requests. + * + * @return tuple(int)[] + */ + public function get (); +} + +/** @kphp-extern-func-info interruptible */ +function rpc_tl_query($actor ::: string, $arr ::: array, $timeout ::: float = -1.0, $ignore_answer ::: bool = false, \KphpRpcRequestsExtraInfo $requests_extra_info = null, $need_responses_extra_info ::: bool = false) ::: int[]; + +/** @kphp-extern-func-info interruptible */ +function typed_rpc_tl_query($actor ::: string, @tl\RpcFunction[] $query_functions, $timeout ::: float = -1.0, $ignore_answer ::: bool = false, \KphpRpcRequestsExtraInfo $requests_extra_info = null, $need_responses_extra_info ::: bool = false) ::: int[]; + +/** @kphp-extern-func-info interruptible */ +function rpc_tl_query_result($query_ids ::: array) ::: mixed[][]; + +/** @kphp-extern-func-info interruptible */ +function typed_rpc_tl_query_result(int[] $query_ids) ::: @tl\RpcResponse[]; + + +// === Component ================================================================================== + class ComponentQuery { private function __construct() ::: \ComponentQuery; } @@ -118,6 +165,34 @@ function component_stream_read_exact($stream ::: ComponentStream, $len ::: int) function component_close_stream($stream ::: ComponentStream) ::: void; function component_finish_stream_processing($stream ::: ComponentStream) ::: void; +// === Json ======================================================================================= + +class JsonEncoder { + const rename_policy = 'none'; + const visibility_policy = 'all'; + const skip_if_default = false; + const float_precision = 0; + + private function __construct(); + + public static function encode(object $instance, int $flags = 0, array $more = []) : string; + public static function decode(string $json, string $class_name) : instance<^2>; + public static function getLastError() : string; + + // JsonEncoderOrChild::encode(...) is actually replaced by JsonEncoder::to_json_impl('JsonEncoderOrChild', ...) + static function to_json_impl(string $encoder_tag, object $instance, int $flags = 0, array $more = []) ::: string; + + // JsonEncoderOrChild::decode(...) is actually replaced by JsonEncoder::from_json_impl('JsonEncoderOrChild', ...) + /** @kphp-extern-func-info cpp_template_call */ + static function from_json_impl(string $encoder_tag, string $json, string $class_name) ::: instance<^3>; +} + +function json_encode ($v ::: mixed, $options ::: int = 0) ::: string | false; + +function json_decode ($v ::: string, $assoc ::: bool = false) ::: mixed; + +// === Misc ======================================================================================= + /** @kphp-extern-func-info cpp_template_call */ function instance_cast(object $instance, $to_type ::: string) ::: instance<^2>; @@ -131,9 +206,6 @@ function warning($message ::: string) ::: void; /** @kphp-no-return */ function critical_error($message ::: string) ::: void; -function json_encode ($v ::: mixed, $options ::: int = 0) ::: string | false; -function json_decode ($v ::: string, $assoc ::: bool = false) ::: mixed; - function debug_print_string($str ::: string) ::: void; function byte_to_int($str ::: string) ::: ?int; diff --git a/cmake/init-compilation-flags.cmake b/cmake/init-compilation-flags.cmake index 35ae461a12..e019a35748 100644 --- a/cmake/init-compilation-flags.cmake +++ b/cmake/init-compilation-flags.cmake @@ -105,6 +105,9 @@ endif() add_compile_options(-Werror -Wall -Wextra -Wunused-function -Wfloat-conversion -Wno-sign-compare -Wuninitialized -Wno-redundant-move -Wno-missing-field-initializers) +if(COMPILE_RUNTIME_LIGHT) + add_compile_options(-Wno-vla-cxx-extension) +endif() if(NOT APPLE) check_cxx_compiler_flag(-gz=zlib DEBUG_COMPRESSION_IS_FOUND) diff --git a/compiler/code-gen/files/init-scripts.cpp b/compiler/code-gen/files/init-scripts.cpp index 8e6398c63d..1a7f1b0a33 100644 --- a/compiler/code-gen/files/init-scripts.cpp +++ b/compiler/code-gen/files/init-scripts.cpp @@ -238,7 +238,6 @@ void InitScriptsCpp::compile(CodeGenerator &W) const { } } - W << FunctionName(main_file_id->main_function) << "$globals_reset(php_globals);" << NL; if (G->is_output_mode_k2_component()) { diff --git a/compiler/code-gen/files/tl2cpp/tl-combinator.cpp b/compiler/code-gen/files/tl2cpp/tl-combinator.cpp index 2320c3cd57..e003a91dbb 100644 --- a/compiler/code-gen/files/tl2cpp/tl-combinator.cpp +++ b/compiler/code-gen/files/tl2cpp/tl-combinator.cpp @@ -66,7 +66,7 @@ void CombinatorStore::gen_before_args_processing(CodeGenerator &W) const { W << "(void)tl_object;" << NL; if (combinator->is_function()) { W << fmt_format("f$store_int({:#010x});", static_cast(combinator->id)) << NL; - W << fmt_format("CurrentProcessingQuery::get().set_last_stored_tl_function_magic({:#010x});", static_cast(combinator->id)) << NL; + W << fmt_format("CurrentTlQuery::get().set_last_stored_tl_function_magic({:#010x});", static_cast(combinator->id)) << NL; } } @@ -84,7 +84,7 @@ void CombinatorStore::gen_arg_processing(CodeGenerator &W, const std::unique_ptr if (!value_check.empty()) { W << "if (" << value_check << ") " << BEGIN; W - << fmt_format(R"(CurrentProcessingQuery::get().raise_storing_error("Optional field %s of %s is not set, but corresponding fields mask bit is set", "{}", "{}");)", + << fmt_format(R"(CurrentTlQuery::get().raise_storing_error("Optional field %s of %s is not set, but corresponding fields mask bit is set", "{}", "{}");)", arg->name, combinator->name) << NL; W << "return" << (combinator->is_function() ? " {};" : ";") << NL; W << END << NL; @@ -100,21 +100,22 @@ void CombinatorStore::gen_arg_processing(CodeGenerator &W, const std::unique_ptr auto *as_type_var = arg->type_expr->as(); kphp_assert(as_type_var); if (!typed_mode) { + const auto *k2_tl_storers_prefix = G->is_output_mode_k2_component() ? "RpcImageState::get()." : ""; W << "auto _cur_arg = " << fmt_format("tl_arr_get(tl_object, {}, {}, {}L)", tl2cpp::register_tl_const_str(arg->name), arg->idx, tl2cpp::hash_tl_const_str(arg->name)) << ";" << NL; W << "string target_f_name = " << fmt_format("tl_arr_get(_cur_arg, {}, 0, {}L).as_string()", tl2cpp::register_tl_const_str("_"), tl2cpp::hash_tl_const_str("_")) << ";" << NL; - W << "if (!tl_storers_ht.has_key(target_f_name)) " << BEGIN - << "CurrentProcessingQuery::get().raise_storing_error(\"Function %s not found in tl-scheme\", target_f_name.c_str());" << NL + W << fmt_format("if (!{}tl_storers_ht.has_key(target_f_name)) ", k2_tl_storers_prefix) << BEGIN + << "CurrentTlQuery::get().raise_storing_error(\"Function %s not found in tl-scheme\", target_f_name.c_str());" << NL << "return {};" << NL << END << NL; - W << "const auto &storer_kv = tl_storers_ht.get_value(target_f_name);" << NL; + W << fmt_format("const auto &storer_kv = {}tl_storers_ht.get_value(target_f_name);", k2_tl_storers_prefix) << NL; W << "tl_func_state->" << combinator->get_var_num_arg(as_type_var->var_num)->name << ".fetcher = storer_kv(_cur_arg);" << NL; } else { W << "if (tl_object->$" << arg->name << ".is_null()) " << BEGIN - << R"(CurrentProcessingQuery::get().raise_storing_error("Field \")" << arg->name << R"(\" not found in tl object");)" << NL + << R"(CurrentTlQuery::get().raise_storing_error("Field \")" << arg->name << R"(\" not found in tl object");)" << NL << "return {};" << NL << END << NL; W << "tl_func_state->" << combinator->get_var_num_arg(as_type_var->var_num)->name << ".fetcher = " diff --git a/compiler/code-gen/files/tl2cpp/tl-combinator.h b/compiler/code-gen/files/tl2cpp/tl-combinator.h index f98182a6e9..bd9f3454d7 100644 --- a/compiler/code-gen/files/tl2cpp/tl-combinator.h +++ b/compiler/code-gen/files/tl2cpp/tl-combinator.h @@ -59,7 +59,7 @@ struct CombinatorGen { auto _cur_arg = tl_arr_get(tl_object, tl_str$query, 4, 1563700686); string target_f_name = tl_arr_get(_cur_arg, tl_str$_, 0, -2147483553).as_string(); if (!tl_storers_ht.has_key(target_f_name)) { - CurrentProcessingQuery::get().raise_storing_error("Function %s not found in tl-scheme", target_f_name.c_str()); + CurrentTlQuery::get().raise_storing_error("Function %s not found in tl-scheme", target_f_name.c_str()); return {}; } const auto &storer_kv = tl_storers_ht.get_value(target_f_name); diff --git a/compiler/code-gen/files/tl2cpp/tl-function.cpp b/compiler/code-gen/files/tl2cpp/tl-function.cpp index f89aa4ace5..6e8bcd444a 100644 --- a/compiler/code-gen/files/tl2cpp/tl-function.cpp +++ b/compiler/code-gen/files/tl2cpp/tl-function.cpp @@ -69,18 +69,18 @@ void TlFunctionDef::compile(CodeGenerator &W) const { } if (f->is_kphp_rpc_server_function() && needs_typed_fetch_store) { FunctionSignatureGenerator(W) << "std::unique_ptr " << struct_name << "::rpc_server_typed_fetch(" << get_php_runtime_type(f) << " *tl_object) " << BEGIN; - W << "CurrentProcessingQuery::get().set_current_tl_function(" << register_tl_const_str(f->name) << ");" << NL; + W << "CurrentTlQuery::get().set_current_tl_function(" << register_tl_const_str(f->name) << ");" << NL; W << "auto tl_func_state = make_unique_on_script_memory<" << struct_name << ">();" << NL; W << CombinatorFetch(f, CombinatorPart::LEFT, true); - W << "CurrentProcessingQuery::get().reset();" << NL; + W << "CurrentTlQuery::get().reset();" << NL; W << "return std::move(tl_func_state);" << NL; W << END << NL << NL; FunctionSignatureGenerator(W) << "void " << struct_name << "::rpc_server_typed_store(const class_instance<" << G->settings().tl_classname_prefix.get() << "RpcFunctionReturnResult> &tl_object_) " << BEGIN; - W << "CurrentProcessingQuery::get().set_current_tl_function(" << register_tl_const_str(f->name) << ");" << NL; + W << "CurrentTlQuery::get().set_current_tl_function(" << register_tl_const_str(f->name) << ");" << NL; W << "auto tl_object = tl_object_.template cast_to<" << get_php_runtime_type(f, false) << "_result>().get();" << NL; W << CombinatorStore(f, CombinatorPart::RIGHT, true); - W << "CurrentProcessingQuery::get().reset();" << NL; + W << "CurrentTlQuery::get().reset();" << NL; W << END << NL << NL; } } diff --git a/compiler/code-gen/files/tl2cpp/tl-type.cpp b/compiler/code-gen/files/tl2cpp/tl-type.cpp index 026fb903fd..4ac6dfde75 100644 --- a/compiler/code-gen/files/tl2cpp/tl-type.cpp +++ b/compiler/code-gen/files/tl2cpp/tl-type.cpp @@ -20,7 +20,7 @@ void TypeStore::compile(CodeGenerator &W) const { if (typed_mode) { W << "if (tl_object.is_null()) " << BEGIN - << "CurrentProcessingQuery::get().raise_storing_error(\"Instance expected, but false given while storing tl type\");" << NL + << "CurrentTlQuery::get().raise_storing_error(\"Instance expected, but false given while storing tl type\");" << NL << "return;" << NL << END << NL; } @@ -48,7 +48,7 @@ void TypeStore::compile(CodeGenerator &W) const { W << cpp_tl_struct_name("c_", c->name, template_str) << "::" << store_call << NL << END; } W << (first ? "" : " else ") << BEGIN - << "CurrentProcessingQuery::get().raise_storing_error(\"Invalid constructor %s of type %s\", " + << "CurrentTlQuery::get().raise_storing_error(\"Invalid constructor %s of type %s\", " << "tl_object.get_class(), \"" << type->name << "\");" << NL << END << NL; @@ -68,7 +68,7 @@ void TypeStore::compile(CodeGenerator &W) const { W << cpp_tl_struct_name("c_", c->name, template_str) << "::" << store_call << NL << END; } W << (first ? "" : " else ") << BEGIN - << "CurrentProcessingQuery::get().raise_storing_error(\"Invalid constructor %s of type %s\", " + << "CurrentTlQuery::get().raise_storing_error(\"Invalid constructor %s of type %s\", " << "c_name.c_str(), \"" << type->name << "\");" << NL << END << NL; } @@ -112,7 +112,7 @@ void TypeFetch::compile(CodeGenerator &W) const { if (default_constructor != nullptr) { W << "int pos = tl_parse_save_pos();" << NL; } - W << "auto magic = static_cast(rpc_fetch_int());" << NL; + W << "auto magic = static_cast(f$fetch_int());" << NL; W << "switch(magic) " << BEGIN; for (const auto &c : type->constructors) { if (c.get() == default_constructor) { @@ -150,7 +150,7 @@ void TypeFetch::compile(CodeGenerator &W) const { W << "tl_object = result;" << NL; } } else { - W << "CurrentProcessingQuery::get().raise_fetching_error(\"Incorrect magic of type " << type->name << ": 0x%08x\", magic);" << NL; + W << "CurrentTlQuery::get().raise_fetching_error(\"Incorrect magic of type " << type->name << ": 0x%08x\", magic);" << NL; } W << END << NL; W << END << NL; diff --git a/compiler/code-gen/files/tl2cpp/tl-type.h b/compiler/code-gen/files/tl2cpp/tl-type.h index 813d970109..7197deaa4b 100644 --- a/compiler/code-gen/files/tl2cpp/tl-type.h +++ b/compiler/code-gen/files/tl2cpp/tl-type.h @@ -22,7 +22,7 @@ void t_Either::store(const mixed &tl_object) f$store_int(0xdf3ecb3b); c_right::store(tl_object, std::move(X), std::move(Y)); } else { - CurrentProcessingQuery::get().raise_storing_error("Invalid constructor %s of type %s", c_name.c_str(), "Either"); + CurrentTlQuery::get().raise_storing_error("Invalid constructor %s of type %s", c_name.c_str(), "Either"); } } * Typed TL: @@ -37,7 +37,7 @@ void t_Either::typed_store(const PhpType &tl const typename right__::type *conv_obj = tl_object.template cast_to::type>().get(); c_right::typed_store(conv_obj, std::move(X), std::move(Y)); } else { - CurrentProcessingQuery::get().raise_storing_error("Invalid constructor %s of type %s", tl_object.get_class(), "Either"); + CurrentTlQuery::get().raise_storing_error("Invalid constructor %s of type %s", tl_object.get_class(), "Either"); } } */ @@ -75,7 +75,7 @@ array t_Either::fetch() { break; } default: { - CurrentProcessingQuery::get().raise_fetching_error("Incorrect magic of type Either: 0x%08x", magic); + CurrentTlQuery::get().raise_fetching_error("Incorrect magic of type Either: 0x%08x", magic); } } return result; @@ -101,7 +101,7 @@ void t_Either::typed_fetch_to(PhpType &tl_ob break; } default: { - CurrentProcessingQuery::get().raise_fetching_error("Incorrect magic of type Either: 0x%08x", magic); + CurrentTlQuery::get().raise_fetching_error("Incorrect magic of type Either: 0x%08x", magic); } } } diff --git a/compiler/code-gen/files/tl2cpp/tl2cpp.cpp b/compiler/code-gen/files/tl2cpp/tl2cpp.cpp index 11212a9e30..4f3c7ab5e7 100644 --- a/compiler/code-gen/files/tl2cpp/tl2cpp.cpp +++ b/compiler/code-gen/files/tl2cpp/tl2cpp.cpp @@ -4,8 +4,9 @@ #include "compiler/code-gen/files/tl2cpp/tl2cpp.h" -#include "common/tlo-parsing/tlo-parsing.h" +#include +#include "common/tlo-parsing/tlo-parsing.h" #include "compiler/code-gen/files/tl2cpp/tl-module.h" #include "compiler/code-gen/files/tl2cpp/tl2cpp-utils.h" #include "compiler/code-gen/naming.h" @@ -89,7 +90,7 @@ void write_rpc_server_functions(CodeGenerator &W) { W << deps << NL; W << ExternInclude{G->settings().runtime_headers.get()} << NL; FunctionSignatureGenerator(W) << "class_instance f$rpc_server_fetch_request() " << BEGIN; - W << "auto function_magic = static_cast(rpc_fetch_int());" << NL; + W << "auto function_magic = static_cast(f$fetch_int());" << NL; W << "switch(function_magic) " << BEGIN; for (const auto &f : kphp_functions) { W << fmt_format("case {:#010x}: ", static_cast(f->id)) << BEGIN; @@ -147,13 +148,20 @@ void write_tl_query_handlers(CodeGenerator &W) { // a hash table that contains all TL functions; // it's passed to the runtime just like the fetch wrapper W << "array gen$tl_storers_ht;" << NL; + // count the number of TL storers + FunctionSignatureGenerator(W) << "void fill_tl_storers_ht() " << BEGIN; + std::stringstream ss{}; // TODO: use std::transform_reduce or something like that + int32_t tl_storers_nums = 0; for (const auto &module_name : modules_with_functions) { + tl_storers_nums += modules[module_name].target_functions.size(); for (const auto &f : modules[module_name].target_functions) { - W << "gen$tl_storers_ht.set_value(" << register_tl_const_str(f->name) << ", " << "&" << cpp_tl_struct_name("f_", f->name) << "::store, " - << hash_tl_const_str(f->name) << "L);" << NL; + ss << "gen$tl_storers_ht.set_value(" << register_tl_const_str(f->name) << ", " << "&" << cpp_tl_struct_name("f_", f->name) << "::store, " + << hash_tl_const_str(f->name) << "L);\n"; } } + W << fmt_format("gen$tl_storers_ht.reserve({}, false);", tl_storers_nums) << NL; + W << ss.str(); W << END << NL; W << CloseFile(); } diff --git a/runtime-core/allocator/script-allocator-managed.h b/runtime-core/allocator/script-allocator-managed.h index eae0cb6125..35231dd273 100644 --- a/runtime-core/allocator/script-allocator-managed.h +++ b/runtime-core/allocator/script-allocator-managed.h @@ -1,3 +1,7 @@ +// Compiler for PHP (aka KPHP) +// Copyright (c) 2024 LLC «V Kontakte» +// Distributed under the GPL v3 License, see LICENSE.notice.txt + #pragma once #include diff --git a/runtime-core/memory-resource/details/memory_ordered_chunk_list.cpp b/runtime-core/memory-resource/details/memory_ordered_chunk_list.cpp index 2458355120..954208acfa 100644 --- a/runtime-core/memory-resource/details/memory_ordered_chunk_list.cpp +++ b/runtime-core/memory-resource/details/memory_ordered_chunk_list.cpp @@ -5,13 +5,15 @@ #include "runtime-core/memory-resource/details/memory_ordered_chunk_list.h" #include +#include #include namespace memory_resource { namespace details { -memory_ordered_chunk_list::memory_ordered_chunk_list(char *memory_resource_begin) noexcept: - memory_resource_begin_(memory_resource_begin) { +memory_ordered_chunk_list::memory_ordered_chunk_list(char *memory_resource_begin, char *memory_resource_end) noexcept + : memory_resource_begin_(memory_resource_begin) + , memory_resource_end_(memory_resource_end) { static_assert(sizeof(list_node) == 8, "8 bytes expected"); } @@ -33,7 +35,12 @@ void memory_ordered_chunk_list::add_from_array(list_node **first, list_node **la return; } + last = std::partition(first, last, [this](const auto *mem) { + return reinterpret_cast(mem) >= reinterpret_cast(this->memory_resource_begin_) + && reinterpret_cast(mem) < reinterpret_cast(this->memory_resource_end_); + }); std::sort(first, last, std::greater<>{}); + if (!head_) { head_ = *first++; } else if (reinterpret_cast(head_) < reinterpret_cast(*first)) { diff --git a/runtime-core/memory-resource/details/memory_ordered_chunk_list.h b/runtime-core/memory-resource/details/memory_ordered_chunk_list.h index da75fe1980..0694a22488 100644 --- a/runtime-core/memory-resource/details/memory_ordered_chunk_list.h +++ b/runtime-core/memory-resource/details/memory_ordered_chunk_list.h @@ -34,7 +34,7 @@ class memory_ordered_chunk_list : vk::not_copyable { uint32_t chunk_size_{0}; }; - explicit memory_ordered_chunk_list(char *memory_resource_begin) noexcept; + explicit memory_ordered_chunk_list(char *memory_resource_begin, char *memory_resource_end) noexcept; list_node *get_next(const list_node *node) const noexcept { return node->has_next() ? reinterpret_cast(memory_resource_begin_ + node->next_chunk_offset_) : nullptr; @@ -56,6 +56,7 @@ class memory_ordered_chunk_list : vk::not_copyable { void add_from_array(list_node **first, list_node **last) noexcept; char *memory_resource_begin_{nullptr}; + char *memory_resource_end_{nullptr}; list_node *head_{nullptr}; size_t tmp_buffer_size_{0}; std::array tmp_buffer_; diff --git a/runtime-core/memory-resource/extra-memory-pool.h b/runtime-core/memory-resource/extra-memory-pool.h index b868291532..e9a6149b61 100644 --- a/runtime-core/memory-resource/extra-memory-pool.h +++ b/runtime-core/memory-resource/extra-memory-pool.h @@ -6,8 +6,8 @@ #include #include -#include #include +#include #include "common/mixin/not_copyable.h" @@ -24,8 +24,8 @@ class alignas(8) extra_memory_pool : vk::not_copyable { } bool is_memory_from_this_pool(const void *mem, size_t mem_size) noexcept { - return memory_begin() <= static_cast(mem) && - static_cast(mem) + mem_size <= memory_begin() + get_pool_payload_size(); + return reinterpret_cast(memory_begin()) <= reinterpret_cast(mem) + && reinterpret_cast(mem) + mem_size <= reinterpret_cast(memory_begin()) + get_pool_payload_size(); } static size_t get_pool_payload_size(size_t buffer_size) noexcept { diff --git a/runtime-core/memory-resource/monotonic_buffer_resource.h b/runtime-core/memory-resource/monotonic_buffer_resource.h index 7eb9cdbad3..a3e1c2a779 100644 --- a/runtime-core/memory-resource/monotonic_buffer_resource.h +++ b/runtime-core/memory-resource/monotonic_buffer_resource.h @@ -41,9 +41,8 @@ class monotonic_buffer : vk::not_copyable { stats_.real_memory_used = static_cast(memory_current_ - memory_begin_); } - bool check_memory_piece(void *mem, size_t size) const noexcept { - return memory_begin_ <= static_cast(mem) && - static_cast(mem) + size <= memory_current_; + bool check_memory_piece_was_used(void *mem, size_t size) const noexcept { + return memory_begin_ <= static_cast(mem) && static_cast(mem) + size <= memory_current_; } MemoryStats stats_; @@ -98,7 +97,7 @@ class monotonic_buffer_resource : protected monotonic_buffer { } bool put_memory_back(void *mem, size_t size) noexcept { - if (unlikely(!check_memory_piece(mem, size))) { + if (unlikely(!check_memory_piece_was_used(mem, size))) { critical_dump(mem, size); } diff --git a/runtime-core/memory-resource/unsynchronized_pool_resource.cpp b/runtime-core/memory-resource/unsynchronized_pool_resource.cpp index 4c0a26481a..da5ba3b24e 100644 --- a/runtime-core/memory-resource/unsynchronized_pool_resource.cpp +++ b/runtime-core/memory-resource/unsynchronized_pool_resource.cpp @@ -36,10 +36,10 @@ void unsynchronized_pool_resource::unfreeze_oom_handling_memory() noexcept { void unsynchronized_pool_resource::perform_defragmentation() noexcept { memory_debug("perform memory defragmentation\n"); - details::memory_ordered_chunk_list mem_list{memory_begin_}; + details::memory_ordered_chunk_list mem_list{memory_begin_, memory_end_}; huge_pieces_.flush_to(mem_list); - if (const size_t fallback_resource_left_size = fallback_resource_.size()) { + if (const auto fallback_resource_left_size = fallback_resource_.size(); fallback_resource_left_size > 0) { mem_list.add_memory(fallback_resource_.memory_current(), fallback_resource_left_size); fallback_resource_.init(nullptr, 0); } @@ -75,7 +75,7 @@ void *unsynchronized_pool_resource::allocate_small_piece_from_fallback_resource( details::memory_chunk_tree::tree_node *smallest_huge_piece = huge_pieces_.extract_smallest(); if (!smallest_huge_piece) { perform_defragmentation(); - if ((mem = try_allocate_small_piece(aligned_size))) { + if (mem = try_allocate_small_piece(aligned_size); mem != nullptr) { return mem; } smallest_huge_piece = huge_pieces_.extract_smallest(); diff --git a/runtime-core/memory-resource/unsynchronized_pool_resource.h b/runtime-core/memory-resource/unsynchronized_pool_resource.h index cc4fb2db3e..434dbca671 100644 --- a/runtime-core/memory-resource/unsynchronized_pool_resource.h +++ b/runtime-core/memory-resource/unsynchronized_pool_resource.h @@ -5,6 +5,7 @@ #pragma once #include +#include #include "runtime-core/memory-resource/extra-memory-pool.h" #include "runtime-core/memory-resource/details/memory_chunk_list.h" @@ -140,7 +141,7 @@ class unsynchronized_pool_resource : private monotonic_buffer_resource { extra_memory_pool *extra_memory_head_{nullptr}; extra_memory_pool extra_memory_tail_{sizeof(extra_memory_pool)}; - static constexpr size_t MAX_CHUNK_BLOCK_SIZE_{16u * 1024u}; + static constexpr size_t MAX_CHUNK_BLOCK_SIZE_{static_cast(16U * 1024U)}; std::array free_chunks_; }; diff --git a/runtime-light/allocator/allocator.h b/runtime-light/allocator/allocator.h new file mode 100644 index 0000000000..ca9d7fa3cb --- /dev/null +++ b/runtime-light/allocator/allocator.h @@ -0,0 +1,14 @@ +// Compiler for PHP (aka KPHP) +// Copyright (c) 2024 LLC «V Kontakte» +// Distributed under the GPL v3 License, see LICENSE.notice.txt + +#pragma once + +#include + +#include "runtime-core/allocator/script-allocator-managed.h" + +template T, typename... Args> +requires std::constructible_from auto make_unique_on_script_memory(Args &&...args) noexcept { + return std::make_unique(std::forward(args)...); +} diff --git a/runtime-light/allocator/runtime-light-allocator.cpp b/runtime-light/allocator/runtime-light-allocator.cpp index 1e82f85392..50ac7625c0 100644 --- a/runtime-light/allocator/runtime-light-allocator.cpp +++ b/runtime-light/allocator/runtime-light-allocator.cpp @@ -2,26 +2,28 @@ // Copyright (c) 2024 LLC «V Kontakte» // Distributed under the GPL v3 License, see LICENSE.notice.txt +#include +#include + #include "runtime-core/runtime-core.h" #include "runtime-light/component/component.h" #include "runtime-light/utils/panic.h" -static constexpr size_t MIN_REQ_EXTRA_MEM_SIZE = 16 * 1024u; - namespace { +// TODO: make it depend on max chunk size, e.g. MIN_EXTRA_MEM_SIZE = f(MAX_CHUNK_SIZE); +constexpr auto MIN_EXTRA_MEM_SIZE = static_cast(32U * 1024U); // extra mem size should be greater than max chunk block size + bool is_script_allocator_available() { return get_component_context() != nullptr; } void request_extra_memory(size_t requested_size) { - ComponentState &rt_ctx = *get_component_context(); - size_t extra_mem_size = std::max(MIN_REQ_EXTRA_MEM_SIZE, requested_size); // extra mem size should be greater than max chunk block size - void *extra_mem = get_platform_context()->allocator.alloc(extra_mem_size); - if (extra_mem == nullptr) { - php_error("script OOM"); - } - rt_ctx.runtime_allocator.memory_resource.add_extra_memory(new (extra_mem) memory_resource::extra_memory_pool{extra_mem_size}); + const size_t extra_mem_size = std::max(MIN_EXTRA_MEM_SIZE, requested_size); + auto &rt_alloc = RuntimeAllocator::current(); + auto *extra_mem = rt_alloc.alloc_global_memory(extra_mem_size); + rt_alloc.memory_resource.add_extra_memory(new (extra_mem) memory_resource::extra_memory_pool{extra_mem_size}); } + } // namespace RuntimeAllocator::RuntimeAllocator(size_t script_mem_size, size_t oom_handling_mem_size) { @@ -61,7 +63,7 @@ void *RuntimeAllocator::alloc_script_memory(size_t size) noexcept { void *ptr = rt_ctx.runtime_allocator.memory_resource.allocate(size); if (ptr == nullptr) { - request_extra_memory(size * 2); + request_extra_memory(size); ptr = rt_ctx.runtime_allocator.memory_resource.allocate(size); php_assert(ptr != nullptr); } @@ -77,7 +79,7 @@ void *RuntimeAllocator::alloc0_script_memory(size_t size) noexcept { ComponentState &rt_ctx = *get_component_context(); void *ptr = rt_ctx.runtime_allocator.memory_resource.allocate0(size); if (ptr == nullptr) { - request_extra_memory(size * 2); + request_extra_memory(size); ptr = rt_ctx.runtime_allocator.memory_resource.allocate0(size); php_assert(ptr != nullptr); } @@ -104,7 +106,7 @@ void RuntimeAllocator::free_script_memory(void *mem, size_t size) noexcept { php_assert(size); ComponentState &rt_ctx = *get_component_context(); - return rt_ctx.runtime_allocator.memory_resource.deallocate(mem, size); + rt_ctx.runtime_allocator.memory_resource.deallocate(mem, size); } void *RuntimeAllocator::alloc_global_memory(size_t size) noexcept { @@ -120,7 +122,7 @@ void *RuntimeAllocator::alloc0_global_memory(size_t size) noexcept { if (unlikely(ptr == nullptr)) { critical_error_handler(); } - memset(ptr, 0, size); + std::memset(ptr, 0, size); return ptr; } diff --git a/runtime-light/component/component.h b/runtime-light/component/component.h index 638274ac98..7057f6dd5a 100644 --- a/runtime-light/component/component.h +++ b/runtime-light/component/component.h @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -16,6 +17,7 @@ #include "runtime-light/core/globals/php-script-globals.h" #include "runtime-light/coroutine/task.h" #include "runtime-light/stdlib/output-control.h" +#include "runtime-light/stdlib/rpc/rpc-context.h" #include "runtime-light/stdlib/superglobals.h" #include "runtime-light/streams/streams.h" #include "runtime-light/utils/context.h" @@ -25,7 +27,7 @@ struct ComponentState { using unordered_map = memory_resource::stl::unordered_map; template using deque = memory_resource::stl::deque; - static constexpr int INIT_RUNTIME_ALLOCATOR_SIZE = 16 * 1024u; + static constexpr auto INIT_RUNTIME_ALLOCATOR_SIZE = static_cast(512U * 1024U); // 512KB ComponentState() : runtime_allocator(INIT_RUNTIME_ALLOCATOR_SIZE, 0) @@ -33,11 +35,12 @@ struct ComponentState { , opened_streams(unordered_map::allocator_type{runtime_allocator.memory_resource}) , awaiting_coroutines(unordered_map>::allocator_type{runtime_allocator.memory_resource}) , timer_callbacks(unordered_map>::allocator_type{runtime_allocator.memory_resource}) - , incoming_pending_queries(deque::allocator_type{runtime_allocator.memory_resource}) {} + , incoming_pending_queries(deque::allocator_type{runtime_allocator.memory_resource}) + , rpc_component_context(runtime_allocator.memory_resource) {} ~ComponentState() = default; - inline bool not_finished() const noexcept { + bool not_finished() const noexcept { return poll_status != PollStatus::PollFinishedOk && poll_status != PollStatus::PollFinishedError; } @@ -67,6 +70,7 @@ struct ComponentState { deque incoming_pending_queries; KphpCoreContext kphp_core_context; + RpcComponentContext rpc_component_context; private: bool is_stream_timer(uint64_t stream_d); diff --git a/runtime-light/component/image.h b/runtime-light/component/image.h index ec75c8d9c1..40025b9c40 100644 --- a/runtime-light/component/image.h +++ b/runtime-light/component/image.h @@ -5,5 +5,9 @@ #pragma once #include "runtime-light/header.h" +#include "runtime-light/stdlib/rpc/rpc-context.h" -struct ImageState {}; +struct ImageState { + char *c_linear_mem; + RpcImageState rpc_image_state; +}; diff --git a/runtime-light/coroutine/task.h b/runtime-light/coroutine/task.h index 1a6db0bec0..cc4b990e0c 100644 --- a/runtime-light/coroutine/task.h +++ b/runtime-light/coroutine/task.h @@ -150,7 +150,7 @@ struct task_t : public task_base_t { if constexpr (!std::is_void{}) { T *t = std::launder(reinterpret_cast(get_handle().promise().bytes)); const vk::final_action final_action([t] { t->~T(); }); - return std::move(*t); + return *t; } } diff --git a/runtime-light/runtime-light.cmake b/runtime-light/runtime-light.cmake index f0dd8f3305..c8d6d1c831 100644 --- a/runtime-light/runtime-light.cmake +++ b/runtime-light/runtime-light.cmake @@ -2,6 +2,7 @@ include(${BASE_DIR}/runtime-light/allocator/allocator.cmake) include(${BASE_DIR}/runtime-light/core/core.cmake) include(${BASE_DIR}/runtime-light/stdlib/stdlib.cmake) include(${BASE_DIR}/runtime-light/streams/streams.cmake) +include(${BASE_DIR}/runtime-light/tl/tl.cmake) include(${BASE_DIR}/runtime-light/utils/utils.cmake) prepend(MONOTOINC_LIGHT_BUFFER_RESOURCE_SRC ${BASE_DIR}/runtime-light/memory-resource-impl/ @@ -11,15 +12,16 @@ prepend(RUNTIME_COMPONENT_SRC ${BASE_DIR}/runtime-light/ component/component.cpp) set(RUNTIME_LIGHT_SRC ${RUNTIME_CORE_SRC} - ${RUNTIME_STDLIB_SRC} - ${RUNTIME_ALLOCATOR_SRC} - ${RUNTIME_COROUTINE_SRC} - ${RUNTIME_COMPONENT_SRC} - ${RUNTIME_STREAMS_SRC} - ${RUNTIME_UTILS_SRC} - ${RUNTIME_LANGUAGE_SRC} - ${MONOTOINC_LIGHT_BUFFER_RESOURCE_SRC} - ${BASE_DIR}/runtime-light/runtime-light.cpp) + ${RUNTIME_STDLIB_SRC} + ${RUNTIME_ALLOCATOR_SRC} + ${RUNTIME_COROUTINE_SRC} + ${RUNTIME_COMPONENT_SRC} + ${RUNTIME_STREAMS_SRC} + ${RUNTIME_TL_SRC} + ${RUNTIME_UTILS_SRC} + ${RUNTIME_LANGUAGE_SRC} + ${MONOTOINC_LIGHT_BUFFER_RESOURCE_SRC} + ${BASE_DIR}/runtime-light/runtime-light.cpp) vk_add_library(runtime-light OBJECT ${RUNTIME_LIGHT_SRC}) set_property(TARGET runtime-light PROPERTY POSITION_INDEPENDENT_CODE ON) diff --git a/runtime-light/stdlib/rpc/rpc-api.cpp b/runtime-light/stdlib/rpc/rpc-api.cpp new file mode 100644 index 0000000000..0e676225ee --- /dev/null +++ b/runtime-light/stdlib/rpc/rpc-api.cpp @@ -0,0 +1,522 @@ +// Compiler for PHP (aka KPHP) +// Copyright (c) 2024 LLC «V Kontakte» +// Distributed under the GPL v3 License, see LICENSE.notice.txt + +#include "runtime-light/stdlib/rpc/rpc-api.h" + +#include +#include +#include +#include +#include +#include + +#include "common/algorithms/find.h" +#include "common/rpc-error-codes.h" +#include "common/tl/constants/common.h" +#include "runtime-light/allocator/allocator.h" +#include "runtime-light/stdlib/rpc/rpc-context.h" +#include "runtime-light/stdlib/rpc/rpc-extra-headers.h" +#include "runtime-light/streams/interface.h" +#include "runtime-light/utils/concepts.h" + +namespace rpc_impl_ { + +constexpr int32_t MAX_TIMEOUT_S = 86400; + +// TODO: change uint64_t to string::size_type after moving it from uint32_t to uint64_t +constexpr uint64_t SMALL_STRING_MAX_LEN = 253; +constexpr uint64_t MEDIUM_STRING_MAX_LEN = (static_cast(1) << 24) - 1; +[[maybe_unused]] constexpr uint64_t LARGE_STRING_MAX_LEN = (static_cast(1) << 56) - 1; + +constexpr uint8_t LARGE_STRING_MAGIC = 0xff; +constexpr uint8_t MEDIUM_STRING_MAGIC = 0xfe; + +mixed mixed_array_get_value(const mixed &arr, const string &str_key, int64_t num_key) noexcept { + if (!arr.is_array()) { + return {}; + } + + if (const auto &elem{arr.get_value(num_key)}; !elem.is_null()) { + return elem; + } + if (const auto &elem{arr.get_value(str_key)}; !elem.is_null()) { + return elem; + } + return {}; +} + +bool rpc_fetch_remaining_enough(size_t len) noexcept { + return RpcComponentContext::get().fetch_state.remaining() >= len; +} + +array make_fetch_error(string &&error_msg, int32_t error_code) { + array res; + res.set_value(string{"__error", 7}, std::move(error_msg)); + res.set_value(string{"__error_code", 12}, error_code); + return res; +} + +template +std::optional fetch_trivial() noexcept { + if (!rpc_fetch_remaining_enough(sizeof(T))) { + return {}; // TODO: error handling + } + + auto &rpc_ctx{RpcComponentContext::get()}; + const auto v{*reinterpret_cast(rpc_ctx.buffer.c_str() + rpc_ctx.fetch_state.pos())}; + rpc_ctx.fetch_state.adjust(sizeof(T)); + return v; +} + +array fetch_function_untyped(const class_instance &rpc_query) noexcept { + php_assert(!rpc_query.is_null()); + CurrentTlQuery::get().set_current_tl_function(rpc_query); + auto fetcher{rpc_query.get()->result_fetcher->extract_untyped_fetcher()}; + php_assert(fetcher); + + const auto res{RpcImageState::get().tl_fetch_wrapper(std::move(fetcher))}; + // TODO: exception handling + // TODO: EOF handling + return res; +} + +class_instance fetch_function_typed(const class_instance &rpc_query, const RpcErrorFactory &error_factory) noexcept { + php_assert(!rpc_query.is_null()); + CurrentTlQuery::get().set_current_tl_function(rpc_query); + // check if the response is error + if (const auto rpc_error{error_factory.fetch_error_if_possible()}; !rpc_error.is_null()) { + return rpc_error; + } + const auto res{rpc_query.get()->result_fetcher->fetch_typed_response()}; + // TODO: exception handling + // TODO: EOF handling + return res; +} + +template +bool store_trivial(T v) noexcept { + RpcComponentContext::get().buffer.append(reinterpret_cast(&v), sizeof(T)); + return true; +} + +class_instance store_function(const mixed &tl_object) noexcept { + auto &cur_query{CurrentTlQuery::get()}; + const auto &rpc_image_state{RpcImageState::get()}; + + const auto fun_name{mixed_array_get_value(tl_object, string{"_"}, 0).to_string()}; // TODO: constexpr ctor for string{"_"} + if (!rpc_image_state.tl_storers_ht.has_key(fun_name)) { + cur_query.raise_storing_error("Function \"%s\" not found in tl-scheme", fun_name.c_str()); + return {}; + } + + auto rpc_tl_query{make_instance()}; + rpc_tl_query.get()->tl_function_name = fun_name; + + cur_query.set_current_tl_function(fun_name); + const auto &untyped_storer = rpc_image_state.tl_storers_ht.get_value(fun_name); + rpc_tl_query.get()->result_fetcher = make_unique_on_script_memory(untyped_storer(tl_object)); + cur_query.reset(); + return rpc_tl_query; +} + +task_t rpc_send_impl(const string &actor, double timeout, bool ignore_answer) noexcept { + auto &rpc_ctx = RpcComponentContext::get(); + + if (timeout <= 0 || timeout > MAX_TIMEOUT_S) { // TODO: handle timeouts + // timeout = conn.get()->timeout_ms * 0.001; + } + + string request_buf{}; + size_t request_size{rpc_ctx.buffer.size()}; + + // 'request_buf' will look like this: + // [ RpcExtraHeaders (optional) ] [ payload ] + if (const auto [opt_new_extra_header, cur_extra_header_size]{regularize_extra_headers(rpc_ctx.buffer.c_str(), ignore_answer)}; opt_new_extra_header) { + const auto new_extra_header{opt_new_extra_header.value()}; + const auto new_extra_header_size{sizeof(std::decay_t)}; + request_size = request_size - cur_extra_header_size + new_extra_header_size; + + request_buf.append(reinterpret_cast(&new_extra_header), new_extra_header_size); + request_buf.append(rpc_ctx.buffer.c_str() + cur_extra_header_size, rpc_ctx.buffer.size() - cur_extra_header_size); + } else { + request_buf.append(rpc_ctx.buffer.c_str(), request_size); + } + + // get timestamp before co_await to also count the time we were waiting for runtime to resume this coroutine + const auto timestamp{std::chrono::duration{std::chrono::system_clock::now().time_since_epoch()}.count()}; + + auto comp_query{co_await f$component_client_send_query(actor, request_buf)}; + if (comp_query.is_null()) { + php_error("could not send rpc query to %s", actor.c_str()); + co_return RpcQueryInfo{.id = RPC_INVALID_QUERY_ID, .request_size = request_size, .timestamp = timestamp}; + } + + if (ignore_answer) { // TODO: wait for answer in a separate coroutine and keep returning RPC_IGNORED_ANSWER_QUERY_ID + co_return RpcQueryInfo{.id = RPC_IGNORED_ANSWER_QUERY_ID, .request_size = request_size, .timestamp = timestamp}; + } + const auto query_id{rpc_ctx.current_query_id++}; + rpc_ctx.pending_component_queries.emplace(query_id, std::move(comp_query)); + co_return RpcQueryInfo{.id = query_id, .request_size = request_size, .timestamp = timestamp}; +} + +task_t rpc_tl_query_one_impl(const string &actor, mixed tl_object, double timeout, bool collect_resp_extra_info, bool ignore_answer) noexcept { + auto &rpc_ctx{RpcComponentContext::get()}; + + if (!tl_object.is_array()) { + rpc_ctx.current_query.raise_storing_error("not an array passed to function rpc_tl_query"); + co_return RpcQueryInfo{}; + } + + rpc_ctx.buffer.clean(); + auto rpc_tl_query{store_function(tl_object)}; // TODO: exception handling + if (rpc_tl_query.is_null()) { + co_return RpcQueryInfo{}; + } + + const auto query_info{co_await rpc_send_impl(actor, timeout, ignore_answer)}; + if (!ignore_answer) { + rpc_ctx.pending_rpc_queries.emplace(query_info.id, std::move(rpc_tl_query)); + } + if (collect_resp_extra_info) { + rpc_ctx.rpc_responses_extra_info.emplace(query_info.id, + std::make_pair(rpc_response_extra_info_status_t::NOT_READY, rpc_response_extra_info_t{0, query_info.timestamp})); + } + + co_return query_info; +} + +task_t typed_rpc_tl_query_one_impl(const string &actor, const RpcRequest &rpc_request, double timeout, bool collect_responses_extra_info, + bool ignore_answer) noexcept { + auto &rpc_ctx{RpcComponentContext::get()}; + + if (rpc_request.empty()) { + rpc_ctx.current_query.raise_storing_error("query function is null"); + co_return RpcQueryInfo{}; + } + + rpc_ctx.buffer.clean(); + auto fetcher{rpc_request.store_request()}; + if (!static_cast(fetcher)) { + rpc_ctx.current_query.raise_storing_error("could not store rpc request"); + co_return RpcQueryInfo{}; + } + + const auto query_info{co_await rpc_send_impl(actor, timeout, ignore_answer)}; + if (!ignore_answer) { + auto rpc_tl_query{make_instance()}; + rpc_tl_query.get()->result_fetcher = std::move(fetcher); + rpc_tl_query.get()->tl_function_name = rpc_request.tl_function_name(); + + rpc_ctx.pending_rpc_queries.emplace(query_info.id, std::move(rpc_tl_query)); + } + if (collect_responses_extra_info) { + rpc_ctx.rpc_responses_extra_info.emplace(query_info.id, + std::make_pair(rpc_response_extra_info_status_t::NOT_READY, rpc_response_extra_info_t{0, query_info.timestamp})); + } + + co_return query_info; +} + +task_t> rpc_tl_query_result_one_impl(int64_t query_id) noexcept { + if (query_id < RPC_VALID_QUERY_ID_RANGE_START) { + co_return make_fetch_error(string{"wrong query_id"}, TL_ERROR_WRONG_QUERY_ID); + } + + auto &rpc_ctx{RpcComponentContext::get()}; + class_instance rpc_query{}; + class_instance component_query{}; + + { + const auto it_rpc_query{rpc_ctx.pending_rpc_queries.find(query_id)}; + const auto it_component_query{rpc_ctx.pending_component_queries.find(query_id)}; + + vk::final_action finalizer{[&rpc_ctx, it_rpc_query, it_component_query]() { + rpc_ctx.pending_rpc_queries.erase(it_rpc_query); + rpc_ctx.pending_component_queries.erase(it_component_query); + }}; + + if (it_rpc_query == rpc_ctx.pending_rpc_queries.end() || it_component_query == rpc_ctx.pending_component_queries.end()) { + co_return make_fetch_error(string{"unexpectedly could not find query in pending queries"}, TL_ERROR_INTERNAL); + } + rpc_query = std::move(it_rpc_query->second); + component_query = std::move(it_component_query->second); + } + + if (rpc_query.is_null()) { + co_return make_fetch_error(string{"can't use rpc_tl_query_result for non-TL query"}, TL_ERROR_INTERNAL); + } + if (!rpc_query.get()->result_fetcher || rpc_query.get()->result_fetcher->empty()) { + co_return make_fetch_error(string{"rpc query has empty result fetcher"}, TL_ERROR_INTERNAL); + } + if (rpc_query.get()->result_fetcher->is_typed) { + co_return make_fetch_error(string{"can't get untyped result from typed TL query. Use consistent API for that"}, TL_ERROR_INTERNAL); + } + + const auto data{co_await f$component_client_get_result(component_query)}; + + // TODO: subscribe to rpc response event? + // update rpc response extra info + if (const auto it_response_extra_info{rpc_ctx.rpc_responses_extra_info.find(query_id)}; it_response_extra_info != rpc_ctx.rpc_responses_extra_info.end()) { + const auto timestamp{std::chrono::duration{std::chrono::system_clock::now().time_since_epoch()}.count()}; + it_response_extra_info->second.second = {data.size(), timestamp - std::get<1>(it_response_extra_info->second.second)}; + it_response_extra_info->second.first = rpc_response_extra_info_status_t::READY; + } + + rpc_ctx.fetch_state.reset(0, data.size()); + rpc_ctx.buffer.clean(); + rpc_ctx.buffer.append(data.c_str(), data.size()); + + co_return fetch_function_untyped(rpc_query); +} + +task_t> typed_rpc_tl_query_result_one_impl(int64_t query_id, const RpcErrorFactory &error_factory) noexcept { + if (query_id < RPC_VALID_QUERY_ID_RANGE_START) { + co_return error_factory.make_error(string{"wrong query_id"}, TL_ERROR_WRONG_QUERY_ID); + } + + auto &rpc_ctx{RpcComponentContext::get()}; + class_instance rpc_query{}; + class_instance component_query{}; + + { + const auto it_rpc_query{rpc_ctx.pending_rpc_queries.find(query_id)}; + const auto it_component_query{rpc_ctx.pending_component_queries.find(query_id)}; + + vk::final_action finalizer{[&rpc_ctx, it_rpc_query, it_component_query]() { + rpc_ctx.pending_rpc_queries.erase(it_rpc_query); + rpc_ctx.pending_component_queries.erase(it_component_query); + }}; + + if (it_rpc_query == rpc_ctx.pending_rpc_queries.end() || it_component_query == rpc_ctx.pending_component_queries.end()) { + co_return error_factory.make_error(string{"unexpectedly could not find query in pending queries"}, TL_ERROR_INTERNAL); + } + rpc_query = std::move(it_rpc_query->second); + component_query = std::move(it_component_query->second); + } + + if (rpc_query.is_null()) { + co_return error_factory.make_error(string{"can't use rpc_tl_query_result for non-TL query"}, TL_ERROR_INTERNAL); + } + if (!rpc_query.get()->result_fetcher || rpc_query.get()->result_fetcher->empty()) { + co_return error_factory.make_error(string{"rpc query has empty result fetcher"}, TL_ERROR_INTERNAL); + } + if (!rpc_query.get()->result_fetcher->is_typed) { + co_return error_factory.make_error(string{"can't get typed result from untyped TL query. Use consistent API for that"}, TL_ERROR_INTERNAL); + } + + const auto data{co_await f$component_client_get_result(component_query)}; + + // TODO: subscribe to rpc response event? + // update rpc response extra info + if (const auto it_response_extra_info{rpc_ctx.rpc_responses_extra_info.find(query_id)}; it_response_extra_info != rpc_ctx.rpc_responses_extra_info.end()) { + const auto timestamp{std::chrono::duration{std::chrono::system_clock::now().time_since_epoch()}.count()}; + it_response_extra_info->second.second = {data.size(), timestamp - std::get<1>(it_response_extra_info->second.second)}; + it_response_extra_info->second.first = rpc_response_extra_info_status_t::READY; + } + + rpc_ctx.fetch_state.reset(0, data.size()); + rpc_ctx.buffer.clean(); + rpc_ctx.buffer.append(data.c_str(), data.size()); + + co_return fetch_function_typed(rpc_query, error_factory); +} + +} // namespace rpc_impl_ + +// === Rpc Store ================================================================================== + +bool f$store_int(int64_t v) noexcept { + if (unlikely(is_int32_overflow(v))) { + php_warning("Got int32 overflow on storing '%" PRIi64 "', the value will be casted to '%d'", v, static_cast(v)); + } + return rpc_impl_::store_trivial(static_cast(v)); +} + +bool f$store_long(int64_t v) noexcept { + return rpc_impl_::store_trivial(v); +} + +bool f$store_float(double v) noexcept { + return rpc_impl_::store_trivial(static_cast(v)); +} + +bool f$store_double(double v) noexcept { + return rpc_impl_::store_trivial(v); +} + +bool f$store_string(const string &v) noexcept { // TODO: support large strings + auto &buffer{RpcComponentContext::get().buffer}; + + string::size_type string_len{v.size()}; + string::size_type size_len{}; + if (string_len <= rpc_impl_::SMALL_STRING_MAX_LEN) { + buffer << static_cast(string_len); + size_len = 1; + } else if (string_len <= rpc_impl_::MEDIUM_STRING_MAX_LEN) { + buffer << static_cast(rpc_impl_::MEDIUM_STRING_MAGIC) << static_cast(string_len & 0xff) << static_cast((string_len >> 8) & 0xff) + << static_cast((string_len >> 16) & 0xff); + size_len = 4; + } else { + buffer << static_cast(rpc_impl_::LARGE_STRING_MAGIC) << static_cast(string_len & 0xff) << static_cast((string_len >> 8) & 0xff) + << static_cast((string_len >> 16) & 0xff) << static_cast((string_len >> 24) & 0xff); +// buffer << static_cast(rpc_impl_::LARGE_STRING_MAGIC) << static_cast(string_len & 0xff) << static_cast((string_len >> 8) & 0xff) +// << static_cast((string_len >> 16) & 0xff) << static_cast((string_len >> 24) & 0xff) +// << static_cast((string_len >> 32) & 0xff) << static_cast((string_len >> 40) & 0xff) +// << static_cast((string_len >> 48) & 0xff); + size_len = 8; + } + buffer.append(v.c_str(), static_cast(string_len)); + + const auto total_len{size_len + string_len}; + const auto total_len_with_padding{(total_len + 3) & ~static_cast(3)}; + const auto padding{total_len_with_padding - total_len}; + + std::array padding_array{'\0', '\0', '\0', '\0'}; + buffer.append(padding_array.data(), padding); + return true; +} + +// === Rpc Fetch ================================================================================== + +int64_t f$fetch_int() noexcept { + const auto opt{rpc_impl_::fetch_trivial()}; + return opt.value_or(0); +} + +int64_t f$fetch_long() noexcept { + const auto opt{rpc_impl_::fetch_trivial()}; + return opt.value_or(0); +} + +double f$fetch_double() noexcept { + const auto opt{rpc_impl_::fetch_trivial()}; + return opt.value_or(0.0); +} + +double f$fetch_float() noexcept { + const auto opt{rpc_impl_::fetch_trivial()}; + return static_cast(opt.value_or(0.0)); +} + +string f$fetch_string() noexcept { + uint8_t first_byte{}; + if (const auto opt_first_byte{rpc_impl_::fetch_trivial()}; opt_first_byte) { + first_byte = opt_first_byte.value(); + } else { + return {}; // TODO: error handling + } + + string::size_type string_len{}; + string::size_type size_len{}; + switch (first_byte) { + case rpc_impl_::LARGE_STRING_MAGIC: { // next 7 bytes are string's length // TODO: support large strings + // static_assert(sizeof(string::size_type) >= 8, "string's length doesn't fit platform size"); + if (!rpc_impl_::rpc_fetch_remaining_enough(7)) { + return {}; // TODO: error handling + } + const auto first{static_cast(rpc_impl_::fetch_trivial().value())}; + const auto second{static_cast(rpc_impl_::fetch_trivial().value()) << 8}; + const auto third{static_cast(rpc_impl_::fetch_trivial().value()) << 16}; + const auto fourth{static_cast(rpc_impl_::fetch_trivial().value()) << 24}; + const auto fifth{static_cast(rpc_impl_::fetch_trivial().value()) << 32}; + const auto sixth{static_cast(rpc_impl_::fetch_trivial().value()) << 40}; + const auto seventh{static_cast(rpc_impl_::fetch_trivial().value()) << 48}; + string_len = first | second | third | fourth | fifth | sixth | seventh; + if (string_len < (1 << 24)) { + php_warning("long string's length is less than 1 << 24"); + } + size_len = 8; + } + case rpc_impl_::MEDIUM_STRING_MAGIC: { // next 3 bytes are string's length + if (!rpc_impl_::rpc_fetch_remaining_enough(3)) { + return {}; // TODO: error handling + } + const auto first{static_cast(rpc_impl_::fetch_trivial().value())}; + const auto second{static_cast(rpc_impl_::fetch_trivial().value()) << 8}; + const auto third{static_cast(rpc_impl_::fetch_trivial().value()) << 16}; + string_len = first | second | third; + if (string_len <= 253) { + php_warning("long string's length is less than 254"); + } + size_len = 4; + } + default: + string_len = static_cast(first_byte); + size_len = 1; + } + + const auto total_len_with_padding{(size_len + string_len + 3) & ~static_cast(3)}; + if (!rpc_impl_::rpc_fetch_remaining_enough(total_len_with_padding - size_len)) { + return {}; // TODO: error handling + } + + auto &rpc_ctx{RpcComponentContext::get()}; + string res{rpc_ctx.buffer.c_str() + rpc_ctx.fetch_state.pos(), string_len}; + rpc_ctx.fetch_state.adjust(total_len_with_padding - size_len); + return res; +} + +// === Rpc Query ================================================================================== + +task_t> f$rpc_tl_query(string actor, array tl_objects, double timeout, bool ignore_answer, + class_instance requests_extra_info, bool need_responses_extra_info) noexcept { + if (ignore_answer && need_responses_extra_info) { + php_warning("Both $ignore_answer and $need_responses_extra_info are 'true'. Can't collect metrics for ignored answers"); + } + + bool collect_resp_extra_info = !ignore_answer && need_responses_extra_info; + array query_ids{tl_objects.size()}; + array req_extra_info_arr{tl_objects.size()}; + + for (const auto &it : tl_objects) { + const auto query_info{co_await rpc_impl_::rpc_tl_query_one_impl(actor, it.get_value(), timeout, collect_resp_extra_info, ignore_answer)}; + query_ids.set_value(it.get_key(), query_info.id); + req_extra_info_arr.set_value(it.get_key(), rpc_request_extra_info_t{query_info.request_size}); + } + + if (!requests_extra_info.is_null()) { + requests_extra_info->extra_info_arr = std::move(req_extra_info_arr); + } + co_return query_ids; +} + +task_t>> f$rpc_tl_query_result(array query_ids) noexcept { + array> res{query_ids.size()}; + for (const auto &it : query_ids) { + res.set_value(it.get_key(), co_await rpc_impl_::rpc_tl_query_result_one_impl(it.get_value())); + } + co_return res; +} + +// === Rpc Misc ================================================================================== + +void f$rpc_clean() noexcept { + auto &rpc_ctx{RpcComponentContext::get()}; + rpc_ctx.buffer.clean(); + rpc_ctx.fetch_state.reset(0, 0); +} + +// === Misc ======================================================================================= + +bool is_int32_overflow(int64_t v) noexcept { + // f$store_int function is used for int and 'magic' storing, + // 'magic' can be assigned via hex literals which may set the 32nd bit, + // this is why we additionally check for the uint32_t here + const auto v32 = static_cast(v); + return vk::none_of_equal(v, int64_t{v32}, int64_t{static_cast(v32)}); +} + +void store_raw_vector_double(const array &vector) noexcept { // TODO: didn't we forget vector's length? + RpcComponentContext::get().buffer.append(reinterpret_cast(vector.get_const_vector_pointer()), sizeof(double) * vector.count()); +} + +void fetch_raw_vector_double(array &vector, int64_t num_elems) noexcept { + const auto len_bytes{sizeof(double) * num_elems}; + if (!rpc_impl_::rpc_fetch_remaining_enough(len_bytes)) { + return; // TODO: error handling + } + auto &rpc_ctx{RpcComponentContext::get()}; + vector.memcpy_vector(num_elems, rpc_ctx.buffer.c_str() + rpc_ctx.fetch_state.pos()); + rpc_ctx.fetch_state.adjust(len_bytes); +} diff --git a/runtime-light/stdlib/rpc/rpc-api.h b/runtime-light/stdlib/rpc/rpc-api.h new file mode 100644 index 0000000000..2bf19203af --- /dev/null +++ b/runtime-light/stdlib/rpc/rpc-api.h @@ -0,0 +1,113 @@ +// Compiler for PHP (aka KPHP) +// Copyright (c) 2024 LLC «V Kontakte» +// Distributed under the GPL v3 License, see LICENSE.notice.txt + +#pragma once + +#include +#include +#include + +#include "runtime-core/runtime-core.h" +#include "runtime-light/coroutine/task.h" +#include "runtime-light/stdlib/rpc/rpc-extra-info.h" +#include "runtime-light/stdlib/rpc/rpc-tl-error.h" +#include "runtime-light/stdlib/rpc/rpc-tl-function.h" +#include "runtime-light/stdlib/rpc/rpc-tl-kphp-request.h" + +constexpr int64_t RPC_VALID_QUERY_ID_RANGE_START = 0; +constexpr int64_t RPC_INVALID_QUERY_ID = -1; +constexpr int64_t RPC_IGNORED_ANSWER_QUERY_ID = -2; + +namespace rpc_impl_ { + +struct RpcQueryInfo { + int64_t id{RPC_INVALID_QUERY_ID}; + size_t request_size{0}; + double timestamp{0.0}; +}; + +task_t typed_rpc_tl_query_one_impl(const string &actor, const RpcRequest &rpc_request, double timeout, bool collect_responses_extra_info, + bool ignore_answer) noexcept; + +task_t> typed_rpc_tl_query_result_one_impl(int64_t query_id, const RpcErrorFactory &error_factory) noexcept; + +} // namespace rpc_impl_ + +// === Rpc Store ================================================================================== + +bool f$store_int(int64_t v) noexcept; + +bool f$store_long(int64_t v) noexcept; + +bool f$store_float(double v) noexcept; + +bool f$store_double(double v) noexcept; + +bool f$store_string(const string &v) noexcept; + +// === Rpc Fetch ================================================================================== + +int64_t f$fetch_int() noexcept; + +int64_t f$fetch_long() noexcept; + +double f$fetch_double() noexcept; + +double f$fetch_float() noexcept; + +string f$fetch_string() noexcept; + +// === Rpc Query ================================================================================== + +task_t> f$rpc_tl_query(string actor, array tl_objects, double timeout = -1.0, bool ignore_answer = false, + class_instance requests_extra_info = {}, bool need_responses_extra_info = false) noexcept; + +template rpc_function_t, std::same_as rpc_request_t = KphpRpcRequest> +task_t> f$typed_rpc_tl_query(string actor, array> query_functions, double timeout = -1.0, + bool ignore_answer = false, class_instance requests_extra_info = {}, + bool need_responses_extra_info = false) noexcept { + if (ignore_answer && need_responses_extra_info) { + php_warning("Both $ignore_answer and $need_responses_extra_info are 'true'. Can't collect metrics for ignored answers"); + } + + bool collect_resp_extra_info = !ignore_answer && need_responses_extra_info; + array query_ids{query_functions.size()}; + array req_extra_info_arr{query_functions.size()}; + + for (const auto &it : query_functions) { + const auto query_info{ + co_await rpc_impl_::typed_rpc_tl_query_one_impl(actor, rpc_request_t{it.get_value()}, timeout, collect_resp_extra_info, ignore_answer)}; + query_ids.set_value(it.get_key(), query_info.id); + req_extra_info_arr.set_value(it.get_key(), rpc_request_extra_info_t{query_info.request_size}); + } + + if (!requests_extra_info.is_null()) { + requests_extra_info->extra_info_arr = std::move(req_extra_info_arr); + } + co_return query_ids; +} + +task_t>> f$rpc_tl_query_result(array query_ids) noexcept; + +template query_id_t = int64_t, std::same_as error_factory_t = RpcResponseErrorFactory> +requires std::default_initializable task_t>> +f$typed_rpc_tl_query_result(array query_ids) noexcept { + array> res{query_ids.size()}; + for (const auto &it : query_ids) { + res.set_value(it.get_key(), co_await rpc_impl_::typed_rpc_tl_query_result_one_impl(it.get_value(), error_factory_t{})); + } + co_return res; +} + +// === Rpc Misc =================================================================================== + +void f$rpc_clean() noexcept; + +// === Misc ======================================================================================= + +bool is_int32_overflow(int64_t v) noexcept; + +void store_raw_vector_double(const array &vector) noexcept; + +void fetch_raw_vector_double(array &vector, int64_t num_elems) noexcept; diff --git a/runtime-light/stdlib/rpc/rpc-context.cpp b/runtime-light/stdlib/rpc/rpc-context.cpp new file mode 100644 index 0000000000..6f3c667329 --- /dev/null +++ b/runtime-light/stdlib/rpc/rpc-context.cpp @@ -0,0 +1,26 @@ +// Compiler for PHP (aka KPHP) +// Copyright (c) 2024 LLC «V Kontakte» +// Distributed under the GPL v3 License, see LICENSE.notice.txt + +#include "runtime-light/stdlib/rpc/rpc-context.h" + +#include "runtime-light/component/component.h" +#include "runtime-light/component/image.h" + +RpcComponentContext::RpcComponentContext(memory_resource::unsynchronized_pool_resource &memory_resource) + : current_query() + , pending_component_queries(unordered_map>::allocator_type{memory_resource}) + , pending_rpc_queries(unordered_map>::allocator_type{memory_resource}) + , rpc_responses_extra_info(unordered_map>::allocator_type{memory_resource}) {} + +RpcComponentContext &RpcComponentContext::get() noexcept { + return get_component_context()->rpc_component_context; +} + +const RpcImageState &RpcImageState::get() noexcept { + return get_image_state()->rpc_image_state; +} + +RpcImageState &RpcImageState::get_mutable() noexcept { + return get_mutable_image_state()->rpc_image_state; +} diff --git a/runtime-light/stdlib/rpc/rpc-context.h b/runtime-light/stdlib/rpc/rpc-context.h new file mode 100644 index 0000000000..f802246854 --- /dev/null +++ b/runtime-light/stdlib/rpc/rpc-context.h @@ -0,0 +1,67 @@ +// Compiler for PHP (aka KPHP) +// Copyright (c) 2024 LLC «V Kontakte» +// Distributed under the GPL v3 License, see LICENSE.notice.txt + +#pragma once + +#include +#include + +#include "common/mixin/not_copyable.h" +#include "runtime-core/memory-resource/resource_allocator.h" +#include "runtime-core/runtime-core.h" +#include "runtime-light/stdlib/rpc/rpc-extra-info.h" +#include "runtime-light/stdlib/rpc/rpc-tl-defs.h" +#include "runtime-light/stdlib/rpc/rpc-tl-query.h" +#include "runtime-light/streams/component-stream.h" + +struct RpcComponentContext final : private vk::not_copyable { + class FetchState { + size_t m_pos{0}; + size_t m_remaining{0}; + + public: + constexpr FetchState() = default; + + constexpr size_t remaining() const noexcept { + return m_remaining; + } + + constexpr size_t pos() const noexcept { + return m_pos; + } + + constexpr void reset(size_t pos, size_t len) noexcept { + m_pos = pos; + m_remaining = len; + } + + constexpr void adjust(size_t len) noexcept { + m_pos += len; + m_remaining -= len; + } + }; + + template + using unordered_map = memory_resource::stl::unordered_map; + + string_buffer buffer; + FetchState fetch_state; + int64_t current_query_id{0}; + CurrentTlQuery current_query; + unordered_map> pending_component_queries; + unordered_map> pending_rpc_queries; + unordered_map> rpc_responses_extra_info; + + explicit RpcComponentContext(memory_resource::unsynchronized_pool_resource &memory_resource); + + static RpcComponentContext &get() noexcept; +}; + +struct RpcImageState final : private vk::not_copyable { + array tl_storers_ht; + tl_fetch_wrapper_ptr tl_fetch_wrapper{nullptr}; + + static const RpcImageState &get() noexcept; + static RpcImageState &get_mutable() noexcept; +}; diff --git a/runtime-light/stdlib/rpc/rpc-extra-headers.cpp b/runtime-light/stdlib/rpc/rpc-extra-headers.cpp new file mode 100644 index 0000000000..60cf80e946 --- /dev/null +++ b/runtime-light/stdlib/rpc/rpc-extra-headers.cpp @@ -0,0 +1,67 @@ +// Compiler for PHP (aka KPHP) +// Copyright (c) 2024 LLC «V Kontakte» +// Distributed under the GPL v3 License, see LICENSE.notice.txt + +#include "runtime-light/stdlib/rpc/rpc-extra-headers.h" + +#include + +#include "common/algorithms/find.h" +#include "common/tl/constants/common.h" +#include "runtime-light/utils/php_assert.h" + +namespace { + +constexpr int64_t EXPECTED_ACTOR_ID = 0; +constexpr uint32_t EMPTY_FLAGS = 0x0; + +} // namespace + +std::pair, uint32_t> regularize_extra_headers(const char *rpc_payload, bool ignore_result) noexcept { + const auto magic{*reinterpret_cast(rpc_payload)}; + if (vk::none_of_equal(magic, TL_RPC_DEST_ACTOR, TL_RPC_DEST_FLAGS, TL_RPC_DEST_ACTOR_FLAGS)) { + return {std::nullopt, 0}; + } + + uint32_t cur_extra_header_size{0}; + uint32_t cur_extra_header_flags{EMPTY_FLAGS}; + int64_t cur_extra_header_actor_id{EXPECTED_ACTOR_ID}; + switch (magic) { + case TL_RPC_DEST_ACTOR_FLAGS: { + cur_extra_header_size = sizeof(RpcDestActorFlagsHeaders); + const auto cur_wrapper{*reinterpret_cast(rpc_payload)}; + cur_extra_header_flags = cur_wrapper.flags; + cur_extra_header_actor_id = cur_wrapper.actor_id; + break; + } + case TL_RPC_DEST_ACTOR: { + cur_extra_header_size = sizeof(RpcDestActorHeaders); + const auto cur_wrapper{*reinterpret_cast(rpc_payload)}; + cur_extra_header_actor_id = cur_wrapper.actor_id; + break; + } + case TL_RPC_DEST_FLAGS: { + cur_extra_header_size = sizeof(RpcDestFlagsHeaders); + const auto cur_wrapper{*reinterpret_cast(rpc_payload)}; + cur_extra_header_flags = cur_wrapper.flags; + break; + } + default: { + php_critical_error("unreachable path"); + } + } + + if (cur_extra_header_actor_id != EXPECTED_ACTOR_ID) { + php_warning("RPC extra headers have actor_id set to %" PRId64 ", but it should not be explicitly set", cur_extra_header_actor_id); + } + const auto cur_extra_header_ignore_result{static_cast(cur_extra_header_flags & vk::tl::common::rpc_invoke_req_extra_flags::no_result)}; + if (!ignore_result && cur_extra_header_ignore_result) { + php_warning("inaccurate use of 'ignore_answer': 'false' was passed into TL query function (e.g., rpc_tl_query), " + "but 'true' was already set in RpcDestFlags or RpcDestActorFlags\n"); + } + + return {RpcDestActorFlagsHeaders{.op = TL_RPC_DEST_ACTOR_FLAGS, + .actor_id = EXPECTED_ACTOR_ID, + .flags = cur_extra_header_flags & ~vk::tl::common::rpc_invoke_req_extra_flags::no_result}, + cur_extra_header_size}; +} diff --git a/runtime-light/stdlib/rpc/rpc-extra-headers.h b/runtime-light/stdlib/rpc/rpc-extra-headers.h new file mode 100644 index 0000000000..dada642143 --- /dev/null +++ b/runtime-light/stdlib/rpc/rpc-extra-headers.h @@ -0,0 +1,38 @@ +// Compiler for PHP (aka KPHP) +// Copyright (c) 2024 LLC «V Kontakte» +// Distributed under the GPL v3 License, see LICENSE.notice.txt + +#pragma once + +#include +#include +#include + +#pragma pack(push, 1) + +struct RpcDestActorFlagsHeaders { + uint32_t op; + int64_t actor_id; + uint32_t flags; +}; + +struct RpcDestActorHeaders { + uint32_t op; + int64_t actor_id; +}; + +struct RpcDestFlagsHeaders { + uint32_t op; + uint32_t flags; +}; + +#pragma pack(pop) + +/** + * Check RPC payload whether it contains some extra header. If so: + * 1) check if actor_id is set in the header; warn if it's set and not equal to 0; + * 2) check if ignore_result is set in the header; warn if it's set in the header and not set in [typed_]rpc_tl_query call; + * 3) return \ pair. + * Otherwise, return \. + * */ +std::pair, uint32_t> regularize_extra_headers(const char *rpc_payload, bool ignore_result) noexcept; diff --git a/runtime-light/stdlib/rpc/rpc-extra-info.cpp b/runtime-light/stdlib/rpc/rpc-extra-info.cpp new file mode 100644 index 0000000000..f30401c16f --- /dev/null +++ b/runtime-light/stdlib/rpc/rpc-extra-info.cpp @@ -0,0 +1,29 @@ +// Compiler for PHP (aka KPHP) +// Copyright (c) 2024 LLC «V Kontakte» +// Distributed under the GPL v3 License, see LICENSE.notice.txt + +#include "runtime-light/stdlib/rpc/rpc-extra-info.h" + +#include "runtime-light/stdlib/rpc/rpc-context.h" + +const char *C$KphpRpcRequestsExtraInfo::get_class() const noexcept { + return R"(KphpRpcRequestsExtraInfo)"; +} + +int C$KphpRpcRequestsExtraInfo::get_hash() const noexcept { + return static_cast(vk::std_hash(vk::string_view(C$KphpRpcRequestsExtraInfo::get_class()))); +} + +array f$KphpRpcRequestsExtraInfo$$get(class_instance v$this) noexcept { + return v$this.get()->extra_info_arr; +} + +Optional f$extract_kphp_rpc_response_extra_info(int64_t query_id) noexcept { + auto &extra_info_map{RpcComponentContext::get().rpc_responses_extra_info}; + if (const auto it{extra_info_map.find(query_id)}; it != extra_info_map.end() && it->second.first == rpc_response_extra_info_status_t::READY) { + const auto extra_info{it->second.second}; + extra_info_map.erase(it); + return extra_info; + } + return {}; +} diff --git a/runtime-light/stdlib/rpc/rpc-extra-info.h b/runtime-light/stdlib/rpc/rpc-extra-info.h new file mode 100644 index 0000000000..7f91818f26 --- /dev/null +++ b/runtime-light/stdlib/rpc/rpc-extra-info.h @@ -0,0 +1,30 @@ +// Compiler for PHP (aka KPHP) +// Copyright (c) 2024 LLC «V Kontakte» +// Distributed under the GPL v3 License, see LICENSE.notice.txt + +#pragma once + +#include +#include + +#include "common/algorithms/hashes.h" +#include "common/wrappers/string_view.h" +#include "runtime-core/class-instance/refcountable-php-classes.h" +#include "runtime-core/runtime-core.h" + +using rpc_request_extra_info_t = std::tuple; // tuple(request_size) +using rpc_response_extra_info_t = std::tuple; // tuple(response_size, response_time) +enum class rpc_response_extra_info_status_t : uint8_t { NOT_READY, READY }; + +// TODO: visitors +struct C$KphpRpcRequestsExtraInfo final : public refcountable_php_classes /*, private DummyVisitorMethods */ { + array extra_info_arr; + + C$KphpRpcRequestsExtraInfo() = default; + const char *get_class() const noexcept; + int get_hash() const noexcept; +}; + +array f$KphpRpcRequestsExtraInfo$$get(class_instance v$this) noexcept; + +Optional f$extract_kphp_rpc_response_extra_info(int64_t query_id) noexcept; diff --git a/runtime-light/stdlib/rpc/rpc-tl-defs.h b/runtime-light/stdlib/rpc/rpc-tl-defs.h new file mode 100644 index 0000000000..9ad4258f93 --- /dev/null +++ b/runtime-light/stdlib/rpc/rpc-tl-defs.h @@ -0,0 +1,41 @@ +// Compiler for PHP (aka KPHP) +// Copyright (c) 2024 LLC «V Kontakte» +// Distributed under the GPL v3 License, see LICENSE.notice.txt + +#pragma once + +#include + +#include "runtime-core/runtime-core.h" +#include "runtime-light/stdlib/rpc/rpc-tl-func-base.h" +#include "runtime-light/stdlib/rpc/rpc-tl-function.h" +#include "runtime-light/utils/php_assert.h" + +using tl_undefined_php_type = std::nullptr_t; +using tl_storer_ptr = std::unique_ptr (*)(const mixed &); +using tl_fetch_wrapper_ptr = array (*)(std::unique_ptr); + +struct tl_exclamation_fetch_wrapper { + std::unique_ptr fetcher; + + explicit tl_exclamation_fetch_wrapper(std::unique_ptr fetcher) + : fetcher(std::move(fetcher)) {} + + tl_exclamation_fetch_wrapper() noexcept = default; + tl_exclamation_fetch_wrapper(const tl_exclamation_fetch_wrapper &) = delete; + tl_exclamation_fetch_wrapper(tl_exclamation_fetch_wrapper &&) noexcept = default; + tl_exclamation_fetch_wrapper &operator=(const tl_exclamation_fetch_wrapper &) = delete; + tl_exclamation_fetch_wrapper &operator=(tl_exclamation_fetch_wrapper &&) noexcept = delete; + ~tl_exclamation_fetch_wrapper() = default; + + mixed fetch() const { + return fetcher->fetch(); + } + + using PhpType = class_instance; + + void typed_fetch_to(PhpType &out) const { + php_assert(fetcher); + out = fetcher->typed_fetch(); + } +}; diff --git a/runtime-light/stdlib/rpc/rpc-tl-error.cpp b/runtime-light/stdlib/rpc/rpc-tl-error.cpp new file mode 100644 index 0000000000..b1299f76b6 --- /dev/null +++ b/runtime-light/stdlib/rpc/rpc-tl-error.cpp @@ -0,0 +1,94 @@ +// Compiler for PHP (aka KPHP) +// Copyright (c) 2024 LLC «V Kontakte» +// Distributed under the GPL v3 License, see LICENSE.notice.txt + +#include "runtime-light/stdlib/rpc/rpc-tl-error.h" + +#include + +#include "common/tl/constants/common.h" +#include "runtime-light/stdlib/rpc/rpc-api.h" +#include "runtime-light/tl/tl-builtins.h" + +bool TlRpcError::try_fetch() noexcept { + const auto backup_pos{tl_parse_save_pos()}; + auto op{f$fetch_int()}; + if (op == TL_REQ_RESULT_HEADER) { + fetch_and_skip_header(); + op = f$fetch_int(); + } + if (op != TL_RPC_REQ_ERROR) { + tl_parse_restore_pos(backup_pos); + return false; + } + + std::ignore = f$fetch_long(); + error_code = static_cast(f$fetch_int()); + error_msg = f$fetch_string(); + + // TODO: exception handling + return true; +} + +void TlRpcError::fetch_and_skip_header() const noexcept { + const auto flags{static_cast(f$fetch_int())}; + + if (flags & vk::tl::common::rpc_req_result_extra_flags::binlog_pos) { + std::ignore = f$fetch_long(); + } + if (flags & vk::tl::common::rpc_req_result_extra_flags::binlog_time) { + std::ignore = f$fetch_long(); + } + if (flags & vk::tl::common::rpc_req_result_extra_flags::engine_pid) { + std::ignore = f$fetch_int(); + std::ignore = f$fetch_int(); + std::ignore = f$fetch_int(); + } + if (flags & vk::tl::common::rpc_req_result_extra_flags::request_size) { + std::ignore = f$fetch_int(); + } + if (flags & vk::tl::common::rpc_req_result_extra_flags::response_size) { + std::ignore = f$fetch_int(); + } + if (flags & vk::tl::common::rpc_req_result_extra_flags::failed_subqueries) { + std::ignore = f$fetch_int(); + } + if (flags & vk::tl::common::rpc_req_result_extra_flags::compression_version) { + std::ignore = f$fetch_int(); + } + if (flags & vk::tl::common::rpc_req_result_extra_flags::stats) { + const auto size{f$fetch_int()}; + for (auto i = 0; i < size; ++i) { + std::ignore = f$fetch_int(); + std::ignore = f$fetch_int(); + } + } + if (flags & vk::tl::common::rpc_req_result_extra_flags::epoch_number) { + std::ignore = f$fetch_long(); + } + if (flags & vk::tl::common::rpc_req_result_extra_flags::view_number) { + std::ignore = f$fetch_long(); + } +} + +class_instance RpcErrorFactory::make_error(const char *error, int32_t error_code) const noexcept { + return make_error(string{error}, error_code); +} + +class_instance RpcErrorFactory::make_error_from_exception_if_possible() const noexcept { + // TODO + // if (!CurException.is_null()) { + // auto rpc_error = make_error(CurException->$message, TL_ERROR_SYNTAX); + // CurException = Optional{}; + // return rpc_error; + // } + return {}; +} + +class_instance RpcErrorFactory::fetch_error_if_possible() const noexcept { + TlRpcError rpc_error{}; + if (!rpc_error.try_fetch()) { + return {}; + } + return make_error(rpc_error.error_msg, rpc_error.error_code); +} diff --git a/runtime-light/stdlib/rpc/rpc-tl-error.h b/runtime-light/stdlib/rpc/rpc-tl-error.h new file mode 100644 index 0000000000..f3859156ec --- /dev/null +++ b/runtime-light/stdlib/rpc/rpc-tl-error.h @@ -0,0 +1,53 @@ +// Compiler for PHP (aka KPHP) +// Copyright (c) 2024 LLC «V Kontakte» +// Distributed under the GPL v3 License, see LICENSE.notice.txt + +#pragma once + +#include + +#include "runtime-core/runtime-core.h" +#include "runtime-light/stdlib/rpc/rpc-tl-function.h" + +struct TlRpcError { + int32_t error_code{0}; + string error_msg; + + bool try_fetch() noexcept; + +private: + void fetch_and_skip_header() const noexcept; +}; + +class RpcErrorFactory { +public: + virtual class_instance make_error(const string &error, int32_t error_code) const noexcept = 0; + + class_instance make_error(const char *error, int32_t error_code) const noexcept; + class_instance make_error_from_exception_if_possible() const noexcept; + class_instance fetch_error_if_possible() const noexcept; + + virtual ~RpcErrorFactory() = default; +}; + +namespace tl_rpc_error_impl_ { + +// use template, because _common\Types\rpcResponseError is unknown on runtime compilation +template +struct RpcResponseErrorFactory : public RpcErrorFactory { + RpcResponseErrorFactory() = default; + +private: + class_instance make_error(const string &error, int32_t error_code) const noexcept final { + auto err{make_instance()}; + err.get()->$error = error; + err.get()->$error_code = error_code; + return err; + } +}; + +} // namespace tl_rpc_error_impl_ + +// the definition appears after the TL scheme codegen, during the site build +struct C$VK$TL$_common$Types$rpcResponseError; +using RpcResponseErrorFactory = tl_rpc_error_impl_::RpcResponseErrorFactory; diff --git a/runtime-light/stdlib/rpc/rpc-tl-func-base.h b/runtime-light/stdlib/rpc/rpc-tl-func-base.h new file mode 100644 index 0000000000..c5bc0fd537 --- /dev/null +++ b/runtime-light/stdlib/rpc/rpc-tl-func-base.h @@ -0,0 +1,34 @@ +// Compiler for PHP (aka KPHP) +// Copyright (c) 2024 LLC «V Kontakte» +// Distributed under the GPL v3 License, see LICENSE.notice.txt + +#pragma once + +#include "runtime-core/allocator/script-allocator-managed.h" +#include "runtime-core/runtime-core.h" +#include "runtime-light/stdlib/rpc/rpc-tl-function.h" + +struct tl_func_base : ScriptAllocatorManaged { + virtual mixed fetch() = 0; + + virtual class_instance typed_fetch() { + // all functions that are called in a typed way override this method with the generated code; + // functions that are not called in a typed way will never call this method + // (it's not a pure virtual method so it's not necessary to generate "return {};" for the untyped functions) + php_critical_error("This function should never be called. Should be overridden in every TL function used in typed mode"); + return {}; + } + + virtual void rpc_server_typed_store([[maybe_unused]] const class_instance &res) { + // all functions annotated with @kphp will override this method with the generated code + php_critical_error("This function should never be called. Should be overridden in every @kphp TL function"); + } + + // every TL function in C++ also has: + // static std::unique_ptr store(const mixed &tl_object); + // static std::unique_ptr typed_store(const C$VK$TL$Functions$thisfunction *tl_object); + // they are not virtual (as they're static), but the implementation is generated for every class + // every one of them creates an instance of itself (fetcher) which is used to do a fetch()/typed_fetch() when the response is received + + virtual ~tl_func_base() = default; +}; diff --git a/runtime-light/stdlib/rpc/rpc-tl-function.h b/runtime-light/stdlib/rpc/rpc-tl-function.h new file mode 100644 index 0000000000..a02a572934 --- /dev/null +++ b/runtime-light/stdlib/rpc/rpc-tl-function.h @@ -0,0 +1,103 @@ +// Compiler for PHP (aka KPHP) +// Copyright (c) 2024 LLC «V Kontakte» +// Distributed under the GPL v3 License, see LICENSE.notice.txt + +#pragma once + +#include + +#include "common/algorithms/hashes.h" +#include "common/wrappers/string_view.h" +#include "runtime-core/class-instance/refcountable-php-classes.h" + +struct tl_func_base; + +class ToArrayVisitor; +class CommonMemoryEstimateVisitor; +class InstanceReferencesCountingVisitor; +class InstanceDeepCopyVisitor; +class InstanceDeepDestroyVisitor; + +// The locations of the typed TL related builtin classes that are described in functions.txt +// are hardcoded to the folder/namespace \VK\TL because after the code generation +// C$VK$TL$... should match that layout + +// this interface is implemented by all PHP classes that represent the TL functions (see tl-to-php) +struct C$VK$TL$RpcFunction : abstract_refcountable_php_interface { + virtual const char *get_class() const { + return "VK\\TL\\RpcFunction"; + } + virtual int32_t get_hash() const { + return static_cast(vk::std_hash(vk::string_view(C$VK$TL$RpcFunction::get_class()))); + } + + virtual void accept(ToArrayVisitor &) noexcept {} + virtual void accept(CommonMemoryEstimateVisitor &) noexcept {} + virtual void accept(InstanceReferencesCountingVisitor &) noexcept {} + virtual void accept(InstanceDeepCopyVisitor &) noexcept {} + virtual void accept(InstanceDeepDestroyVisitor &) noexcept {} + + virtual size_t virtual_builtin_sizeof() const noexcept { + return 0; + } + virtual C$VK$TL$RpcFunction *virtual_builtin_clone() const noexcept { + return nullptr; + } + + ~C$VK$TL$RpcFunction() override = default; + virtual std::unique_ptr store() const = 0; +}; + +// every TL function has a class for the result that implements RpcFunctionReturnResult; +// which has ->value of the required type +struct C$VK$TL$RpcFunctionReturnResult : abstract_refcountable_php_interface { + virtual const char *get_class() const { + return "VK\\TL\\RpcFunctionReturnResult"; + } + virtual int32_t get_hash() const { + return static_cast(vk::std_hash(vk::string_view(C$VK$TL$RpcFunctionReturnResult::get_class()))); + } + + virtual void accept(ToArrayVisitor &) noexcept {} + virtual void accept(CommonMemoryEstimateVisitor &) noexcept {} + virtual void accept(InstanceReferencesCountingVisitor &) noexcept {} + virtual void accept(InstanceDeepCopyVisitor &) noexcept {} + virtual void accept(InstanceDeepDestroyVisitor &) noexcept {} + + virtual size_t virtual_builtin_sizeof() const noexcept { + return 0; + } + virtual C$VK$TL$RpcFunctionReturnResult *virtual_builtin_clone() const noexcept { + return nullptr; + } + + ~C$VK$TL$RpcFunctionReturnResult() override = default; +}; + +// function call response — ReqResult from the TL scheme — is a rpcResponseOk|rpcResponseHeader|rpcResponseError; +// if it's rpcResponseOk or rpcResponseHeader, then their bodies can be retrieved by a fetcher that was returned by a store +struct C$VK$TL$RpcResponse : abstract_refcountable_php_interface { + using X = class_instance; + + virtual void accept(ToArrayVisitor &) noexcept {} + virtual void accept(CommonMemoryEstimateVisitor &) noexcept {} + virtual void accept(InstanceReferencesCountingVisitor &) noexcept {} + virtual void accept(InstanceDeepCopyVisitor &) noexcept {} + virtual void accept(InstanceDeepDestroyVisitor &) noexcept {} + + virtual const char *get_class() const { + return "VK\\TL\\RpcResponse"; + } + virtual int32_t get_hash() const { + return static_cast(vk::std_hash(vk::string_view(C$VK$TL$RpcResponse::get_class()))); + } + + virtual size_t virtual_builtin_sizeof() const noexcept { + return 0; + } + virtual C$VK$TL$RpcResponse *virtual_builtin_clone() const noexcept { + return nullptr; + } + + ~C$VK$TL$RpcResponse() override = default; +}; diff --git a/runtime-light/stdlib/rpc/rpc-tl-kphp-request.h b/runtime-light/stdlib/rpc/rpc-tl-kphp-request.h new file mode 100644 index 0000000000..a87ec61030 --- /dev/null +++ b/runtime-light/stdlib/rpc/rpc-tl-kphp-request.h @@ -0,0 +1,60 @@ +// Compiler for PHP (aka KPHP) +// Copyright (c) 2024 LLC «V Kontakte» +// Distributed under the GPL v3 License, see LICENSE.notice.txt + +#pragma once + +#include + +#include "runtime-light/allocator/allocator.h" +#include "runtime-light/stdlib/rpc/rpc-context.h" +#include "runtime-light/stdlib/rpc/rpc-tl-defs.h" +#include "runtime-light/stdlib/rpc/rpc-tl-request.h" + +namespace tl_rpc_request_impl_ { +// use template, because t_ReqResult_ is unknown on runtime compilation +template class t_ReqResult_> +class KphpRpcRequestResult final : public RpcRequestResult { +public: + using RpcRequestResult::RpcRequestResult; + + explicit KphpRpcRequestResult(std::unique_ptr &&result_fetcher) + : RpcRequestResult(true, std::move(result_fetcher)) {} + + class_instance fetch_typed_response() final { + class_instance $response; + t_ReqResult_(tl_exclamation_fetch_wrapper(std::move(result_fetcher))).typed_fetch_to($response); + return $response; + } + + std::unique_ptr extract_untyped_fetcher() final { + php_assert(!"Forbidden to call for typed rpc requests"); + } +}; + +// use template, because t_ReqResult_ is unknown on runtime compilation +template class t_ReqResult_> +class KphpRpcRequest final : public RpcRequest { +public: + using RpcRequest::RpcRequest; + + std::unique_ptr store_request() const final { + // php_assert(CurException.is_null()); + auto &rpc_ctx{RpcComponentContext::get()}; + rpc_ctx.current_query.set_current_tl_function(tl_function_name()); + std::unique_ptr stored_fetcher = storing_function.get()->store(); + rpc_ctx.current_query.reset(); + // if (!CurException.is_null()) { + // CurException = Optional{}; + // return {}; + // } + + return make_unique_on_script_memory>(std::move(stored_fetcher)); + } +}; +} // namespace tl_rpc_request_impl_ + +template +struct t_ReqResult; // the definition appears after the TL scheme codegen, during the site build + +using KphpRpcRequest = tl_rpc_request_impl_::KphpRpcRequest; diff --git a/runtime-light/stdlib/rpc/rpc-tl-query.cpp b/runtime-light/stdlib/rpc/rpc-tl-query.cpp new file mode 100644 index 0000000000..97b709db99 --- /dev/null +++ b/runtime-light/stdlib/rpc/rpc-tl-query.cpp @@ -0,0 +1,85 @@ +// Compiler for PHP (aka KPHP) +// Copyright (c) 2024 LLC «V Kontakte» +// Distributed under the GPL v3 License, see LICENSE.notice.txt + +#include "runtime-light/stdlib/rpc/rpc-tl-query.h" + +#include + +#include "runtime-light/stdlib/rpc/rpc-context.h" +#include "runtime-light/utils/php_assert.h" + +void CurrentTlQuery::reset() noexcept { + current_tl_function_name = string{}; +} + +void CurrentTlQuery::set_current_tl_function(const string &tl_function_name) noexcept { + // It can be not empty in the following case: + // 1. Timeout is raised in the middle of serialization (when current TL function is still not reset). + // 2. Then shutdown functions called from timeout. + // 3. They use RPC which finally call set_current_tl_function. + // It will be rewritten by another tl_function_name and work fine + current_tl_function_name = tl_function_name; +} + +void CurrentTlQuery::set_current_tl_function(const class_instance ¤t_query) noexcept { + current_tl_function_name = current_query.get()->tl_function_name; +} + +void CurrentTlQuery::raise_fetching_error(const char *format, ...) const noexcept { + php_assert(!current_tl_function_name.empty()); + + if (/* !CurException.is_null() */ false) { + return; + } + + constexpr size_t BUFF_SZ = 1024; + std::array buff{}; + + va_list args; + va_start(args, format); + int32_t sz = vsnprintf(buff.data(), BUFF_SZ, format, args); + php_assert(sz > 0); + va_end(args); + + string msg = string(buff.data(), static_cast(sz)); + php_warning("Fetching error:\n%s\nIn %s deserializing TL object", msg.c_str(), current_tl_function_name.c_str()); + msg.append(string(" in result of ")).append(current_tl_function_name); + // THROW_EXCEPTION(new_Exception(string{}, 0, msg, -1)); +} + +void CurrentTlQuery::raise_storing_error(const char *format, ...) const noexcept { + if (/*!CurException.is_null()*/ false) { + return; + } + + constexpr size_t BUFF_SZ = 1024; + std::array buff{}; + + va_list args; + va_start(args, format); + int32_t sz = vsnprintf(buff.data(), BUFF_SZ, format, args); + php_assert(sz > 0); + va_end(args); + + string msg = string(buff.data(), static_cast(sz)); + php_warning("Storing error:\n%s\nIn %s serializing TL object", msg.c_str(), + current_tl_function_name.empty() ? "_unknown_" : current_tl_function_name.c_str()); + // THROW_EXCEPTION(new_Exception(string{}, 0, msg, -1)); +} + +void CurrentTlQuery::set_last_stored_tl_function_magic(uint32_t tl_magic) noexcept { + last_stored_tl_function_magic = tl_magic; +} + +uint32_t CurrentTlQuery::get_last_stored_tl_function_magic() const noexcept { + return last_stored_tl_function_magic; +} + +const string &CurrentTlQuery::get_current_tl_function_name() const noexcept { + return current_tl_function_name; +} + +CurrentTlQuery &CurrentTlQuery::get() noexcept { + return RpcComponentContext::get().current_query; +} diff --git a/runtime-light/stdlib/rpc/rpc-tl-query.h b/runtime-light/stdlib/rpc/rpc-tl-query.h new file mode 100644 index 0000000000..b3659b1c3e --- /dev/null +++ b/runtime-light/stdlib/rpc/rpc-tl-query.h @@ -0,0 +1,36 @@ +// Compiler for PHP (aka KPHP) +// Copyright (c) 2024 LLC «V Kontakte» +// Distributed under the GPL v3 License, see LICENSE.notice.txt + +#pragma once + +#include +#include + +#include "runtime-core/class-instance/refcountable-php-classes.h" +#include "runtime-core/runtime-core.h" +#include "runtime-light/stdlib/rpc/rpc-tl-request.h" + +struct RpcTlQuery : refcountable_php_classes { + string tl_function_name; + std::unique_ptr result_fetcher; +}; + +struct CurrentTlQuery { + void reset() noexcept; + void set_current_tl_function(const string &tl_function_name) noexcept; + void set_current_tl_function(const class_instance ¤t_query) noexcept; + void raise_fetching_error(const char *format, ...) const noexcept __attribute__((format(printf, 2, 3))); + void raise_storing_error(const char *format, ...) const noexcept __attribute__((format(printf, 2, 3))); + + // called from generated TL serializers (from autogen) + void set_last_stored_tl_function_magic(uint32_t tl_magic) noexcept; + uint32_t get_last_stored_tl_function_magic() const noexcept; + const string &get_current_tl_function_name() const noexcept; + + static CurrentTlQuery &get() noexcept; + +private: + string current_tl_function_name; + uint32_t last_stored_tl_function_magic{0}; +}; diff --git a/runtime-light/stdlib/rpc/rpc-tl-request.cpp b/runtime-light/stdlib/rpc/rpc-tl-request.cpp new file mode 100644 index 0000000000..b46274d7ae --- /dev/null +++ b/runtime-light/stdlib/rpc/rpc-tl-request.cpp @@ -0,0 +1,47 @@ +// Compiler for PHP (aka KPHP) +// Copyright (c) 2024 LLC «V Kontakte» +// Distributed under the GPL v3 License, see LICENSE.notice.txt + +#include "runtime-light/stdlib/rpc/rpc-tl-request.h" + +#include "runtime-light/utils/php_assert.h" + +RpcRequestResult::RpcRequestResult(bool is_typed, std::unique_ptr &&result_fetcher) + : is_typed(is_typed) + , result_fetcher(std::move(result_fetcher)) {} + +bool RpcRequestResult::empty() const { + return !result_fetcher; +} + +RpcRequest::RpcRequest(class_instance function) + : storing_function(std::move(function)) {} + +string RpcRequest::tl_function_name() const { + string class_name{storing_function.get()->get_class()}; + const string tl_class_prefix{"\\Functions\\"}; + const auto pos = class_name.find(tl_class_prefix); + if (pos != string::npos) { + class_name = class_name.substr(pos + tl_class_prefix.size(), class_name.size() - (pos + tl_class_prefix.size())); + } + return class_name; +} + +bool RpcRequest::empty() const { + return storing_function.is_null(); +} + +const class_instance &RpcRequest::get_tl_function() const { + return storing_function; +} + +RpcRequestResultUntyped::RpcRequestResultUntyped(std::unique_ptr &&result_fetcher) + : RpcRequestResult(false, std::move(result_fetcher)) {} + +class_instance RpcRequestResultUntyped::fetch_typed_response() { + php_assert(!"Forbidden to call for non typed rpc requests"); +} + +std::unique_ptr RpcRequestResultUntyped::extract_untyped_fetcher() { + return std::move(result_fetcher); +} diff --git a/runtime-light/stdlib/rpc/rpc-tl-request.h b/runtime-light/stdlib/rpc/rpc-tl-request.h new file mode 100644 index 0000000000..7d730a4eed --- /dev/null +++ b/runtime-light/stdlib/rpc/rpc-tl-request.h @@ -0,0 +1,56 @@ +// Compiler for PHP (aka KPHP) +// Copyright (c) 2024 LLC «V Kontakte» +// Distributed under the GPL v3 License, see LICENSE.notice.txt + +#pragma once + +#include + +#include "runtime-core/allocator/script-allocator-managed.h" +#include "runtime-core/runtime-core.h" +#include "runtime-light/stdlib/rpc/rpc-tl-func-base.h" +#include "runtime-light/stdlib/rpc/rpc-tl-function.h" + +class RpcRequestResult : public ScriptAllocatorManaged { +public: + const bool is_typed{}; + + RpcRequestResult(bool is_typed, std::unique_ptr &&result_fetcher); + + bool empty() const; + + virtual class_instance fetch_typed_response() = 0; + virtual std::unique_ptr extract_untyped_fetcher() = 0; + virtual ~RpcRequestResult() = default; + +protected: + std::unique_ptr result_fetcher; // the store() result +}; + +class RpcRequest { +public: + explicit RpcRequest(class_instance function); + + string tl_function_name() const; + + bool empty() const; + + const class_instance &get_tl_function() const; + + virtual std::unique_ptr store_request() const = 0; + virtual ~RpcRequest() = default; + +protected: + class_instance storing_function; +}; + +class RpcRequestResultUntyped final : public RpcRequestResult { +public: + using RpcRequestResult::RpcRequestResult; + + explicit RpcRequestResultUntyped(std::unique_ptr &&result_fetcher); + + class_instance fetch_typed_response() final; + + std::unique_ptr extract_untyped_fetcher() final; +}; diff --git a/runtime-light/stdlib/stdlib.cmake b/runtime-light/stdlib/stdlib.cmake index 360b50ecf1..b2ea6ac270 100644 --- a/runtime-light/stdlib/stdlib.cmake +++ b/runtime-light/stdlib/stdlib.cmake @@ -5,4 +5,11 @@ prepend(RUNTIME_STDLIB_SRC ${BASE_DIR}/runtime-light/stdlib/ string-functions.cpp variable-handling.cpp superglobals.cpp + rpc/rpc-api.cpp + rpc/rpc-context.cpp + rpc/rpc-extra-headers.cpp + rpc/rpc-extra-info.cpp + rpc/rpc-tl-error.cpp + rpc/rpc-tl-query.cpp + rpc/rpc-tl-request.cpp ) diff --git a/runtime-light/tl/tl-builtins.cpp b/runtime-light/tl/tl-builtins.cpp new file mode 100644 index 0000000000..e10de69d41 --- /dev/null +++ b/runtime-light/tl/tl-builtins.cpp @@ -0,0 +1,204 @@ +// Compiler for PHP (aka KPHP) +// Copyright (c) 2024 LLC «V Kontakte» +// Distributed under the GPL v3 License, see LICENSE.notice.txt + +#include "runtime-light/tl/tl-builtins.h" + +void register_tl_storers_table_and_fetcher(const array &gen$ht, tl_fetch_wrapper_ptr gen$t_ReqResult_fetch) { + auto &rpc_mutable_image_state{RpcImageState::get_mutable()}; + rpc_mutable_image_state.tl_storers_ht = gen$ht; + rpc_mutable_image_state.tl_fetch_wrapper = gen$t_ReqResult_fetch; +} + +int32_t tl_parse_save_pos() { + return static_cast(RpcComponentContext::get().fetch_state.pos()); +} + +bool tl_parse_restore_pos(int32_t pos) { + auto &rpc_ctx{RpcComponentContext::get()}; + if (pos < 0 || pos > rpc_ctx.fetch_state.pos()) { + return false; + } + rpc_ctx.fetch_state.reset(static_cast(pos), rpc_ctx.fetch_state.remaining() + rpc_ctx.fetch_state.pos() - pos); + return true; +} + +mixed tl_arr_get(const mixed &arr, const string &str_key, int64_t num_key, int64_t precomputed_hash) { + auto &cur_query{CurrentTlQuery::get()}; + if (!arr.is_array()) { + cur_query.raise_storing_error("Array expected, when trying to access field #%" PRIi64 " : %s", num_key, str_key.c_str()); + return {}; + } + + if (const auto &elem{arr.get_value(num_key)}; !elem.is_null()) { + return elem; + } + if (const auto &elem{precomputed_hash == 0 ? arr.get_value(str_key) : arr.get_value(str_key, precomputed_hash)}; !elem.is_null()) { + return elem; + } + + cur_query.raise_storing_error("Field %s (#%" PRIi64 ") not found", str_key.c_str(), num_key); + return {}; +} + +void store_magic_if_not_bare(uint32_t inner_magic) { + if (static_cast(inner_magic)) { + f$store_int(inner_magic); + } +} + +void fetch_magic_if_not_bare(uint32_t inner_magic, const char *error_msg) { + if (static_cast(inner_magic)) { + const auto actual_magic = static_cast(f$fetch_int()); + if (actual_magic != inner_magic) { + CurrentTlQuery::get().raise_fetching_error("%s\nExpected 0x%08x, but fetched 0x%08x", error_msg, inner_magic, actual_magic); + } + } +} + +void t_Int::store(const mixed &tl_object) { + int32_t v32{prepare_int_for_storing(f$intval(tl_object))}; + f$store_int(v32); +} + +int32_t t_Int::fetch() { + // CHECK_EXCEPTION(return 0); + return f$fetch_int(); +} + +void t_Int::typed_store(const t_Int::PhpType &v) { + int32_t v32{prepare_int_for_storing(v)}; + f$store_int(v32); +} + +void t_Int::typed_fetch_to(t_Int::PhpType &out) { + // CHECK_EXCEPTION(return); + out = f$fetch_int(); +} + +int32_t t_Int::prepare_int_for_storing(int64_t v) { + auto v32 = static_cast(v); + if (is_int32_overflow(v)) { + // TODO + } + return v32; +} + +void t_Long::store(const mixed &tl_object) { + int64_t v64{f$intval(tl_object)}; + f$store_long(v64); +} + +mixed t_Long::fetch() { + // CHECK_EXCEPTION(return mixed()); + return f$fetch_long(); +} + +void t_Long::typed_store(const t_Long::PhpType &v) { + f$store_long(v); +} + +void t_Long::typed_fetch_to(t_Long::PhpType &out) { + // CHECK_EXCEPTION(return); + out = f$fetch_long(); +} + +void t_Double::store(const mixed &tl_object) { + f$store_double(f$floatval(tl_object)); +} + +double t_Double::fetch() { + // CHECK_EXCEPTION(return 0); + return f$fetch_double(); +} + +void t_Double::typed_store(const t_Double::PhpType &v) { + f$store_double(v); +} + +void t_Double::typed_fetch_to(t_Double::PhpType &out) { + // CHECK_EXCEPTION(return); + out = f$fetch_double(); +} + +void t_Float::store(const mixed &tl_object) { + f$store_float(f$floatval(tl_object)); +} + +double t_Float::fetch() { + // CHECK_EXCEPTION(return 0); + return f$fetch_float(); +} + +void t_Float::typed_store(const t_Float::PhpType &v) { + f$store_float(v); +} + +void t_Float::typed_fetch_to(t_Float::PhpType &out) { + // CHECK_EXCEPTION(return); + out = f$fetch_float(); +} + +void t_String::store(const mixed &tl_object) { + f$store_string(f$strval(tl_object)); +} + +string t_String::fetch() { + // CHECK_EXCEPTION(return tl_str_); + return f$fetch_string(); +} + +void t_String::typed_store(const t_String::PhpType &v) { + f$store_string(f$strval(v)); +} + +void t_String::typed_fetch_to(t_String::PhpType &out) { + // CHECK_EXCEPTION(return); + out = f$fetch_string(); +} + +void t_Bool::store(const mixed &tl_object) { + f$store_int(tl_object.to_bool() ? TL_BOOL_TRUE : TL_BOOL_FALSE); +} + +bool t_Bool::fetch() { + // CHECK_EXCEPTION(return false); + const auto magic = static_cast(f$fetch_int()); + switch (magic) { + case TL_BOOL_FALSE: + return false; + case TL_BOOL_TRUE: + return true; + default: { + CurrentTlQuery::get().raise_fetching_error("Incorrect magic of type Bool: 0x%08x", magic); + return false; + } + } +} + +void t_Bool::typed_store(const t_Bool::PhpType &v) { + f$store_int(v ? TL_BOOL_TRUE : TL_BOOL_FALSE); +} + +void t_Bool::typed_fetch_to(t_Bool::PhpType &out) { + // CHECK_EXCEPTION(return); + const auto magic = static_cast(f$fetch_int()); + if (magic != TL_BOOL_TRUE && magic != TL_BOOL_FALSE) { + CurrentTlQuery::get().raise_fetching_error("Incorrect magic of type Bool: 0x%08x", magic); + return; + } + out = magic == TL_BOOL_TRUE; +} + +void t_True::store([[maybe_unused]] const mixed &v) {} + +array t_True::fetch() { + return {}; +} + +void t_True::typed_store([[maybe_unused]] const t_True::PhpType &v) {} + +void t_True::typed_fetch_to(t_True::PhpType &out) { + // CHECK_EXCEPTION(return); + out = true; +} diff --git a/runtime-light/tl/tl-builtins.h b/runtime-light/tl/tl-builtins.h new file mode 100644 index 0000000000..91b5b325d4 --- /dev/null +++ b/runtime-light/tl/tl-builtins.h @@ -0,0 +1,560 @@ +// Compiler for PHP (aka KPHP) +// Copyright (c) 2024 LLC «V Kontakte» +// Distributed under the GPL v3 License, see LICENSE.notice.txt + +#pragma once + +#include +#include + +#include "common/php-functions.h" +#include "common/tl/constants/common.h" +#include "runtime-core/runtime-core.h" +#include "runtime-light/stdlib/rpc/rpc-api.h" +#include "runtime-light/stdlib/rpc/rpc-context.h" +#include "runtime-light/stdlib/rpc/rpc-tl-defs.h" +#include "runtime-light/stdlib/rpc/rpc-tl-function.h" +#include "runtime-light/stdlib/rpc/rpc-tl-kphp-request.h" +#include "runtime-light/utils/php_assert.h" + +// TODO: get rid of it here +#define CHECK_EXCEPTION(action) + +void register_tl_storers_table_and_fetcher(const array &gen$ht, tl_fetch_wrapper_ptr gen$t_ReqResult_fetch); + +int32_t tl_parse_save_pos(); + +bool tl_parse_restore_pos(int32_t pos); + +mixed tl_arr_get(const mixed &arr, const string &str_key, int64_t num_key, int64_t precomputed_hash = 0); + +void store_magic_if_not_bare(uint32_t inner_magic); + +void fetch_magic_if_not_bare(uint32_t inner_magic, const char *error_msg); + +template +inline void fetch_raw_vector_T(array &out __attribute__((unused)), int64_t n_elems __attribute__((unused))) { + php_assert(0 && "never called in runtime"); +} + +template<> +inline void fetch_raw_vector_T(array &out, int64_t n_elems) { + fetch_raw_vector_double(out, n_elems); +} + +template +inline void store_raw_vector_T(const array &v __attribute__((unused))) { + php_assert(0 && "never called in runtime"); +} + +template<> +inline void store_raw_vector_T(const array &v) { + store_raw_vector_double(v); +} + +// Wrap into Optional that TL types which PhpType is: +// 1. int, double, string, bool +// 2. array +// These types are not wrapped: +// 1. class_instance +// 2. Optional +// 3. mixed (long in TL scheme or mixed in the phpdoc) UPD: it will be wrapped after the int64_t transition is over + +template +struct need_Optional : vk::is_type_in_list {}; + +template +struct need_Optional> : std::true_type {}; + +enum class FieldAccessType : uint8_t { read, write }; + +// C++14 if constexpr +template +inline const typename SerializerT::PhpType &get_serialization_target_from_optional_field(const OptionalFieldT &v) + requires(need_Optional::value &&ac == FieldAccessType::read) { + return v.val(); +} + +template +inline typename SerializerT::PhpType &get_serialization_target_from_optional_field(OptionalFieldT &v) + requires(need_Optional::value &&ac == FieldAccessType::write) { + return v.ref(); +} + +template +inline OptionalFieldT &get_serialization_target_from_optional_field(OptionalFieldT &v) requires(!need_Optional::value) { + return v; +} + +struct t_Int { + void store(const mixed &tl_object); + int32_t fetch(); + + using PhpType = int64_t; + void typed_store(const PhpType &v); + void typed_fetch_to(PhpType &out); + + static int32_t prepare_int_for_storing(int64_t v); +}; + +struct t_Long { + void store(const mixed &tl_object); + mixed fetch(); + + using PhpType = int64_t; + void typed_store(const PhpType &v); + void typed_fetch_to(PhpType &out); +}; + +struct t_Double { + void store(const mixed &tl_object); + double fetch(); + + using PhpType = double; + void typed_store(const PhpType &v); + void typed_fetch_to(PhpType &out); +}; + +struct t_Float { + void store(const mixed &tl_object); + double fetch(); + + using PhpType = double; + void typed_store(const PhpType &v); + void typed_fetch_to(PhpType &out); +}; + +struct t_String { + void store(const mixed &tl_object); + string fetch(); + + using PhpType = string; + void typed_store(const PhpType &v); + void typed_fetch_to(PhpType &out); +}; + +struct t_Bool { + void store(const mixed &tl_object); + bool fetch(); + + using PhpType = bool; + void typed_store(const PhpType &v); + void typed_fetch_to(PhpType &out); +}; + +struct t_True { + void store(const mixed &v __attribute__((unused))); + array fetch(); + + using PhpType = bool; + void typed_store(const PhpType &v __attribute__((unused))); + void typed_fetch_to(PhpType &out); +}; + +template +struct t_Vector { + T elem_state; + + explicit t_Vector(T param_type) + : elem_state(std::move(param_type)) {} + + void store(const mixed &v) { + const auto &cur_query{CurrentTlQuery::get()}; + + if (!v.is_array()) { + cur_query.raise_storing_error("Expected array, got %s", v.get_type_c_str()); + return; + } + + const array &a = v.as_array(); + int64_t n = v.count(); + f$store_int(n); + for (int64_t i = 0; i < n; ++i) { + if (!a.isset(i)) { + cur_query.raise_storing_error("Vector[%ld] not set", i); + return; + } + store_magic_if_not_bare(inner_magic); + elem_state.store(v.get_value(i)); + } + } + + array fetch() { + // CHECK_EXCEPTION(return array()); + const auto size{f$fetch_int()}; + if (size < 0) { + CurrentTlQuery::get().raise_fetching_error("Vector size is negative"); + return {}; + } + + array res{array_size{size, true}}; + for (int64_t i = 0; i < size; ++i) { + fetch_magic_if_not_bare(inner_magic, "Incorrect magic of inner type of type Vector"); + const mixed &elem{elem_state.fetch()}; + // CHECK_EXCEPTION(return result); + res.push_back(elem); + } + + return res; + } + + using PhpType = array; + using PhpElemT = typename T::PhpType; + + void typed_store(const PhpType &v) { + int64_t n = v.count(); + f$store_int(n); + + if (std::is_same_v && inner_magic == 0 && v.is_vector()) { + store_raw_vector_T(v); + return; + } + + for (int64_t i = 0; i < n; ++i) { + if (!v.isset(i)) { + CurrentTlQuery::get().raise_storing_error("Vector[%" PRIi64 "] not set", i); + return; + } + store_magic_if_not_bare(inner_magic); + elem_state.typed_store(v.get_value(i)); + } + } + + void typed_fetch_to(PhpType &out) { + // CHECK_EXCEPTION(return); + const auto size{f$fetch_int()}; + if (size < 0) { + CurrentTlQuery::get().raise_fetching_error("Vector size is negative"); + return; + } + + out.reserve(size, true); + if (std::is_same_v && inner_magic == 0) { + fetch_raw_vector_T(out, size); + return; + } + + for (int64_t i = 0; i < size; ++i) { + fetch_magic_if_not_bare(inner_magic, "Incorrect magic of inner type of type Vector"); + PhpElemT elem; + elem_state.typed_fetch_to(elem); + out.push_back(std::move(elem)); + // CHECK_EXCEPTION(return); + } + } +}; + +template +struct t_Maybe { + static_assert(!std::is_same_v, "Usage (Maybe True) in TL is forbidden"); + + T elem_state; + + explicit t_Maybe(T param_type) + : elem_state(std::move(param_type)) {} + + // TODO: replace string{...} with constants + void store(const mixed &v) { + const string &name = f$strval(tl_arr_get(v, string{"_"}, 0, string_hash("_", 1))); + if (name == string{"resultFalse"}) { + f$store_int(TL_MAYBE_FALSE); + } else if (name == string{"resultTrue"}) { + f$store_int(TL_MAYBE_TRUE); + store_magic_if_not_bare(inner_magic); + elem_state.store(tl_arr_get(v, string{"result"}, 1, string_hash("result", 6))); + } else { + CurrentTlQuery::get().raise_storing_error("Constructor %s of type Maybe was not found in TL scheme", name.c_str()); + } + } + + mixed fetch() { + // CHECK_EXCEPTION(return mixed()); + const auto magic{static_cast(f$fetch_int())}; + switch (magic) { + case TL_MAYBE_FALSE: + return false; + case TL_MAYBE_TRUE: + fetch_magic_if_not_bare(inner_magic, "Incorrect magic of inner type of type Maybe"); + return elem_state.fetch(); + default: + CurrentTlQuery::get().raise_fetching_error("Incorrect magic of type Maybe: 0x%08x", magic); + return -1; + } + } + + static constexpr bool inner_needs_Optional = need_Optional::value; + using PhpType = std::conditional_t, typename T::PhpType>; + + static bool has_maybe_value(const PhpType &v) { + return !v.is_null(); + } + + void typed_store(const PhpType &v) { + if (!has_maybe_value(v)) { + f$store_int(TL_MAYBE_FALSE); + } else { + f$store_int(TL_MAYBE_TRUE); + store_magic_if_not_bare(inner_magic); + elem_state.typed_store(get_serialization_target_from_optional_field(v)); + } + } + + void typed_fetch_to(PhpType &out) { + // CHECK_EXCEPTION(return); + const auto magic{static_cast(f$fetch_int())}; + switch (magic) { + case TL_MAYBE_FALSE: + // Wrapped into Optional: array, int64_t, double, string, bool + // Not wrapped: : var, class_instance, Optional + out = PhpType(); + break; + case TL_MAYBE_TRUE: + fetch_magic_if_not_bare(inner_magic, "Incorrect magic of inner type of type Maybe"); + elem_state.typed_fetch_to(get_serialization_target_from_optional_field(out)); + break; + default: + CurrentTlQuery::get().raise_fetching_error("Incorrect magic of type Maybe: 0x%08x", magic); + return; + } + } +}; + +template +struct tl_Dictionary_impl { + ValueT value_state; + + explicit tl_Dictionary_impl(ValueT value_type) + : value_state(std::move(value_type)) {} + + void store(const mixed &v) { + if (!v.is_array()) { + CurrentTlQuery::get().raise_storing_error("Expected array (dictionary), got something strange"); + return; + } + + int64_t n = v.count(); + f$store_int(n); + for (auto it = v.begin(); it != v.end(); ++it) { + KeyT().store(it.get_key()); + store_magic_if_not_bare(inner_value_magic); + value_state.store(it.get_value()); + } + } + + array fetch() { + // CHECK_EXCEPTION(return array()); + const auto size{f$fetch_int()}; + if (size < 0) { + CurrentTlQuery::get().raise_fetching_error("Dictionary size is negative"); + return {}; + } + + array res{array_size{size, false}}; + for (int64_t i = 0; i < size; ++i) { + const auto &key{KeyT().fetch()}; + fetch_magic_if_not_bare(inner_value_magic, "Incorrect magic of inner type of some Dictionary"); + const mixed &value{value_state.fetch()}; + // CHECK_EXCEPTION(return result); + res.set_value(key, value); + } + return res; + } + + using PhpType = array; + + void typed_store(const PhpType &v) { + int64_t n = v.count(); + f$store_int(n); + + for (auto it = v.begin(); it != v.end(); ++it) { + if constexpr (std::is_same_v) { + KeyT{}.typed_store(it.get_key().to_string()); + } else { + KeyT{}.typed_store(it.get_key().to_int()); + } + store_magic_if_not_bare(inner_value_magic); + value_state.typed_store(it.get_value()); + } + } + + void typed_fetch_to(PhpType &out) { + // CHECK_EXCEPTION(return); + const auto size{f$fetch_int()}; + if (size < 0) { + CurrentTlQuery::get().raise_fetching_error("Dictionary size is negative"); + return; + } + + out.reserve(size, false); + for (int64_t i = 0; i < size; ++i) { + typename KeyT::PhpType key; + KeyT().typed_fetch_to(key); + fetch_magic_if_not_bare(inner_value_magic, "Incorrect magic of inner type of some Dictionary"); + + typename ValueT::PhpType elem; + value_state.typed_fetch_to(elem); + out.set_value(key, std::move(elem)); + // CHECK_EXCEPTION(return); + } + } +}; + +template +using t_Dictionary = tl_Dictionary_impl; + +template +using t_IntKeyDictionary = tl_Dictionary_impl; + +template +using t_LongKeyDictionary = tl_Dictionary_impl; + +template +struct t_Tuple { + T elem_state; + int64_t size; + + t_Tuple(T param_type, int64_t size) + : elem_state(std::move(param_type)) + , size(size) {} + + void store(const mixed &v) { + const auto &cur_query{CurrentTlQuery::get()}; + + if (!v.is_array()) { + cur_query.raise_storing_error("Expected tuple, got %s", v.get_type_c_str()); + return; + } + + const array &a = v.as_array(); + for (int64_t i = 0; i < size; ++i) { + if (!a.isset(i)) { + cur_query.raise_storing_error("Tuple[%ld] not set", i); + return; + } + store_magic_if_not_bare(inner_magic); + elem_state.store(v.get_value(i)); + } + } + + array fetch() { + // CHECK_EXCEPTION(return array()); + array res{array_size{size, true}}; + for (int64_t i = 0; i < size; ++i) { + fetch_magic_if_not_bare(inner_magic, "Incorrect magic of inner type of type Tuple"); + res.push_back(elem_state.fetch()); + } + return res; + } + + using PhpType = array; + + void typed_store(const PhpType &v) { + if (std::is_same_v && inner_magic == 0 && v.is_vector() && v.count() == size) { + store_raw_vector_T(v); + return; + } + + for (int64_t i = 0; i < size; ++i) { + if (!v.isset(i)) { + CurrentTlQuery::get().raise_storing_error("Tuple[%ld] not set", i); + return; + } + store_magic_if_not_bare(inner_magic); + elem_state.typed_store(v.get_value(i)); + } + } + + void typed_fetch_to(PhpType &out) { + // CHECK_EXCEPTION(return); + out.reserve(size, true); + + if (std::is_same_v && inner_magic == 0) { + fetch_raw_vector_T(out, size); + return; + } + + for (int64_t i = 0; i < size; ++i) { + typename T::PhpType elem; + fetch_magic_if_not_bare(inner_magic, "Incorrect magic of inner type of type Tuple"); + elem_state.typed_fetch_to(elem); + out.push_back(std::move(elem)); + // CHECK_EXCEPTION(return); + } + } +}; + +template +struct tl_array { + int64_t size; + T cell; + + tl_array(int64_t size, T cell) + : size(size) + , cell(std::move(cell)) {} + + void store(const mixed &v) { + const auto &cur_query{CurrentTlQuery::get()}; + + if (!v.is_array()) { + cur_query.raise_storing_error("Expected array, got %s", v.get_type_c_str()); + return; + } + + const array &a = v.as_array(); + for (int64_t i = 0; i < size; ++i) { + if (!a.isset(i)) { + cur_query.raise_storing_error("Array[%ld] not set", i); + return; + } + store_magic_if_not_bare(inner_magic); + cell.store(v.get_value(i)); + } + } + + array fetch() { + array result{array_size{size, true}}; + // CHECK_EXCEPTION(return result); + for (int64_t i = 0; i < size; ++i) { + fetch_magic_if_not_bare(inner_magic, "Incorrect magic of inner type of tl array"); + result.push_back(cell.fetch()); + // CHECK_EXCEPTION(return result); + } + return result; + } + + using PhpType = array; + + void typed_store(const PhpType &v) { + if (std::is_same_v && inner_magic == 0 && v.is_vector() && v.count() == size) { + store_raw_vector_T(v); + return; + } + + for (int64_t i = 0; i < size; ++i) { + if (!v.isset(i)) { + CurrentTlQuery::get().raise_storing_error("Array[%ld] not set", i); + return; + } + store_magic_if_not_bare(inner_magic); + cell.typed_store(v.get_value(i)); + } + } + + void typed_fetch_to(PhpType &out) { + // CHECK_EXCEPTION(return); + out.reserve(size, true); + + if (std::is_same_v && inner_magic == 0) { + fetch_raw_vector_T(out, size); + return; + } + + for (int64_t i = 0; i < size; ++i) { + typename T::PhpType elem; + fetch_magic_if_not_bare(inner_magic, "Incorrect magic of inner type of tl array"); + cell.typed_fetch_to(elem); + out.push_back(std::move(elem)); + // CHECK_EXCEPTION(return); + } + } +}; diff --git a/runtime-light/tl/tl.cmake b/runtime-light/tl/tl.cmake new file mode 100644 index 0000000000..f80d755753 --- /dev/null +++ b/runtime-light/tl/tl.cmake @@ -0,0 +1,3 @@ +prepend(RUNTIME_TL_SRC ${BASE_DIR}/runtime-light/tl/ + tl-builtins.cpp +) diff --git a/runtime-light/utils/concepts.h b/runtime-light/utils/concepts.h new file mode 100644 index 0000000000..963a9c6b95 --- /dev/null +++ b/runtime-light/utils/concepts.h @@ -0,0 +1,10 @@ +// Compiler for PHP (aka KPHP) +// Copyright (c) 2024 LLC «V Kontakte» +// Distributed under the GPL v3 License, see LICENSE.notice.txt + +#pragma once + +#include + +template +concept standard_layout = std::is_standard_layout_v; diff --git a/runtime/rpc.cpp b/runtime/rpc.cpp index 0568326b81..d3f0ca9cd7 100644 --- a/runtime/rpc.cpp +++ b/runtime/rpc.cpp @@ -759,7 +759,7 @@ int64_t rpc_send_impl(const class_instance &conn, double timeou double send_timestamp = std::chrono::duration{std::chrono::system_clock::now().time_since_epoch()}.count(); cur->resumable_id = register_forked_resumable(new rpc_resumable(q_id)); - cur->function_magic = CurrentProcessingQuery::get().get_last_stored_tl_function_magic(); + cur->function_magic = CurrentTlQuery::get().get_last_stored_tl_function_magic(); cur->actor_or_port = conn.get()->actor_id > 0 ? conn.get()->actor_id : -conn.get()->port; cur->timer = nullptr; @@ -1128,21 +1128,21 @@ bool try_fetch_rpc_error(array &out_if_error) { class_instance store_function(const mixed &tl_object) { php_assert(CurException.is_null()); if (!tl_object.is_array()) { - CurrentProcessingQuery::get().raise_storing_error("Not an array passed to function rpc_tl_query"); + CurrentTlQuery::get().raise_storing_error("Not an array passed to function rpc_tl_query"); return {}; } string fun_name = tl_arr_get(tl_object, tl_str_underscore, 0).to_string(); if (!tl_storers_ht.has_key(fun_name)) { - CurrentProcessingQuery::get().raise_storing_error("Function \"%s\" not found in tl-scheme", fun_name.c_str()); + CurrentTlQuery::get().raise_storing_error("Function \"%s\" not found in tl-scheme", fun_name.c_str()); return {}; } class_instance rpc_query; rpc_query.alloc(); rpc_query.get()->tl_function_name = fun_name; - CurrentProcessingQuery::get().set_current_tl_function(fun_name); + CurrentTlQuery::get().set_current_tl_function(fun_name); const auto &untyped_storer = tl_storers_ht.get_value(fun_name); rpc_query.get()->result_fetcher = make_unique_on_script_memory(untyped_storer(tl_object)); - CurrentProcessingQuery::get().reset(); + CurrentTlQuery::get().reset(); return rpc_query; } @@ -1152,11 +1152,11 @@ array fetch_function(const class_instance &rpc_query) { return new_tl_object; // this object carries an error (see tl_fetch_error()) } php_assert(!rpc_query.is_null()); - CurrentProcessingQuery::get().set_current_tl_function(rpc_query); + CurrentTlQuery::get().set_current_tl_function(rpc_query); auto stored_fetcher = rpc_query.get()->result_fetcher->extract_untyped_fetcher(); php_assert(stored_fetcher); new_tl_object = tl_fetch_wrapper(std::move(stored_fetcher)); - CurrentProcessingQuery::get().reset(); + CurrentTlQuery::get().reset(); if (!CurException.is_null()) { array result = tl_fetch_error(CurException->$message, TL_ERROR_SYNTAX); CurException = Optional{}; @@ -1480,7 +1480,7 @@ static void reset_rpc_global_vars() { void init_rpc_lib() { php_assert (timeout_wakeup_id != -1); - CurrentProcessingQuery::get().reset(); + CurrentTlQuery::get().reset(); RpcPendingQueries::get().hard_reset(); CurrentRpcServerQuery::get().reset(); reset_rpc_global_vars(); @@ -1500,7 +1500,7 @@ void init_rpc_lib() { void free_rpc_lib() { reset_rpc_global_vars(); RpcPendingQueries::get().hard_reset(); - CurrentProcessingQuery::get().reset(); + CurrentTlQuery::get().reset(); } int64_t f$rpc_queue_create() { diff --git a/runtime/tl/rpc_request.h b/runtime/tl/rpc_request.h index b15f6ddd2d..3fd340e85a 100644 --- a/runtime/tl/rpc_request.h +++ b/runtime/tl/rpc_request.h @@ -106,9 +106,9 @@ class KphpRpcRequest final : public RpcRequest { std::unique_ptr store_request() const final { php_assert(CurException.is_null()); - CurrentProcessingQuery::get().set_current_tl_function(tl_function_name()); + CurrentTlQuery::get().set_current_tl_function(tl_function_name()); std::unique_ptr stored_fetcher = storing_function_.get()->store(); - CurrentProcessingQuery::get().reset(); + CurrentTlQuery::get().reset(); if (!CurException.is_null()) { CurException = Optional{}; return {}; diff --git a/runtime/tl/rpc_tl_query.cpp b/runtime/tl/rpc_tl_query.cpp index c684ae26e6..945d643ff8 100644 --- a/runtime/tl/rpc_tl_query.cpp +++ b/runtime/tl/rpc_tl_query.cpp @@ -24,11 +24,11 @@ void RpcPendingQueries::hard_reset() { hard_reset_var(queries_); } -void CurrentProcessingQuery::reset() { +void CurrentTlQuery::reset() { current_tl_function_name_ = string(); } -void CurrentProcessingQuery::set_current_tl_function(const string &tl_function_name) { +void CurrentTlQuery::set_current_tl_function(const string &tl_function_name) { // It can be not empty in the following case: // 1. Timeout is raised in the middle of serialization (when current TL function is still not reset). // 2. Then shutdown functions called from timeout. @@ -37,11 +37,11 @@ void CurrentProcessingQuery::set_current_tl_function(const string &tl_function_n current_tl_function_name_ = tl_function_name; } -void CurrentProcessingQuery::set_current_tl_function(const class_instance ¤t_query) { +void CurrentTlQuery::set_current_tl_function(const class_instance ¤t_query) { current_tl_function_name_ = current_query.get()->tl_function_name; } -void CurrentProcessingQuery::raise_fetching_error(const char *format, ...) { +void CurrentTlQuery::raise_fetching_error(const char *format, ...) { php_assert(!current_tl_function_name_.empty()); if (CurException.is_null()) { constexpr size_t BUFF_SZ = 1024; @@ -58,7 +58,7 @@ void CurrentProcessingQuery::raise_fetching_error(const char *format, ...) { } } -void CurrentProcessingQuery::raise_storing_error(const char *format, ...) { +void CurrentTlQuery::raise_storing_error(const char *format, ...) { const char *function_name = current_tl_function_name_.empty() ? "_unknown_" : current_tl_function_name_.c_str(); if (CurException.is_null()) { constexpr size_t BUFF_SZ = 1024; diff --git a/runtime/tl/rpc_tl_query.h b/runtime/tl/rpc_tl_query.h index 39d3b5fa7f..1d979d8549 100644 --- a/runtime/tl/rpc_tl_query.h +++ b/runtime/tl/rpc_tl_query.h @@ -37,10 +37,10 @@ class RpcPendingQueries { array> queries_; }; -class CurrentProcessingQuery { +class CurrentTlQuery { public: - static CurrentProcessingQuery &get() { - static CurrentProcessingQuery context; + static CurrentTlQuery &get() { + static CurrentTlQuery context; return context; } diff --git a/runtime/tl/tl_builtins.h b/runtime/tl/tl_builtins.h index d134c58213..417b331553 100644 --- a/runtime/tl/tl_builtins.h +++ b/runtime/tl/tl_builtins.h @@ -45,7 +45,7 @@ using tl_storer_ptr = std::unique_ptr(*)(const mixed &); inline mixed tl_arr_get(const mixed &arr, const string &str_key, int64_t num_key, int64_t precomputed_hash = 0) { if (!arr.is_array()) { - CurrentProcessingQuery::get().raise_storing_error("Array expected, when trying to access field #%" PRIi64 " : %s", num_key, str_key.c_str()); + CurrentTlQuery::get().raise_storing_error("Array expected, when trying to access field #%" PRIi64 " : %s", num_key, str_key.c_str()); return {}; } const mixed &num_v = arr.get_value(num_key); @@ -56,7 +56,7 @@ inline mixed tl_arr_get(const mixed &arr, const string &str_key, int64_t num_key if (!str_v.is_null()) { return str_v; } - CurrentProcessingQuery::get().raise_storing_error("Field %s (#%" PRIi64 ") not found", str_key.c_str(), num_key); + CurrentTlQuery::get().raise_storing_error("Field %s (#%" PRIi64 ") not found", str_key.c_str(), num_key); return {}; } @@ -70,7 +70,7 @@ inline void fetch_magic_if_not_bare(unsigned int inner_magic, const char *error_ if (inner_magic) { auto actual_magic = static_cast(rpc_fetch_int()); if (actual_magic != inner_magic) { - CurrentProcessingQuery::get().raise_fetching_error("%s\nExpected 0x%08x, but fetched 0x%08x", error_msg, inner_magic, actual_magic); + CurrentTlQuery::get().raise_fetching_error("%s\nExpected 0x%08x, but fetched 0x%08x", error_msg, inner_magic, actual_magic); } } } @@ -164,10 +164,10 @@ struct t_Int { auto v32 = static_cast(v); if (unlikely(is_int32_overflow(v))) { if (fail_rpc_on_int32_overflow) { - CurrentProcessingQuery::get().raise_storing_error("Got int32 overflow with value '%" PRIi64 "'. Serialization will fail.", v); + CurrentTlQuery::get().raise_storing_error("Got int32 overflow with value '%" PRIi64 "'. Serialization will fail.", v); } else { php_warning("Got int32 overflow on storing %s: the value '%" PRIi64 "' will be casted to '%d'. Serialization will succeed.", - CurrentProcessingQuery::get().get_current_tl_function_name().c_str(), v, v32); + CurrentTlQuery::get().get_current_tl_function_name().c_str(), v, v32); } } return v32; @@ -276,7 +276,7 @@ struct t_Bool { case TL_BOOL_TRUE: return true; default: { - CurrentProcessingQuery::get().raise_fetching_error("Incorrect magic of type Bool: 0x%08x", magic); + CurrentTlQuery::get().raise_fetching_error("Incorrect magic of type Bool: 0x%08x", magic); return -1; } } @@ -292,7 +292,7 @@ struct t_Bool { CHECK_EXCEPTION(return); auto magic = static_cast(rpc_fetch_int()); if (magic != TL_BOOL_TRUE && magic != TL_BOOL_FALSE) { - CurrentProcessingQuery::get().raise_fetching_error("Incorrect magic of type Bool: 0x%08x", magic); + CurrentTlQuery::get().raise_fetching_error("Incorrect magic of type Bool: 0x%08x", magic); return; } out = magic == TL_BOOL_TRUE; @@ -325,7 +325,7 @@ struct t_Vector { void store(const mixed &v) { if (!v.is_array()) { - CurrentProcessingQuery::get().raise_storing_error("Expected array, got %s", v.get_type_c_str()); + CurrentTlQuery::get().raise_storing_error("Expected array, got %s", v.get_type_c_str()); return; } const array &a = v.as_array(); @@ -333,7 +333,7 @@ struct t_Vector { f$store_int(n); for (int64_t i = 0; i < n; ++i) { if (!a.isset(i)) { - CurrentProcessingQuery::get().raise_storing_error("Vector[%ld] not set", i); + CurrentTlQuery::get().raise_storing_error("Vector[%ld] not set", i); return; } store_magic_if_not_bare(inner_magic); @@ -345,7 +345,7 @@ struct t_Vector { CHECK_EXCEPTION(return array()); int n = rpc_fetch_int(); if (n < 0) { - CurrentProcessingQuery::get().raise_fetching_error("Vector size is negative"); + CurrentTlQuery::get().raise_fetching_error("Vector size is negative"); return {}; } array result(array_size(std::min(n, 10000), true)); @@ -372,7 +372,7 @@ struct t_Vector { for (int64_t i = 0; i < n; ++i) { if (!v.isset(i)) { - CurrentProcessingQuery::get().raise_storing_error("Vector[%" PRIi64 "] not set", i); + CurrentTlQuery::get().raise_storing_error("Vector[%" PRIi64 "] not set", i); return; } store_magic_if_not_bare(inner_magic); @@ -384,7 +384,7 @@ struct t_Vector { CHECK_EXCEPTION(return); int n = rpc_fetch_int(); if (n < 0) { - CurrentProcessingQuery::get().raise_fetching_error("Vector size is negative"); + CurrentTlQuery::get().raise_fetching_error("Vector size is negative"); return; } out.reserve(n, true); @@ -422,7 +422,7 @@ struct t_Maybe { store_magic_if_not_bare(inner_magic); elem_state.store(tl_arr_get(v, tl_str_result, 1, tl_str_result_hash)); } else { - CurrentProcessingQuery::get().raise_storing_error("Constructor %s of type Maybe was not found in TL scheme", name.c_str()); + CurrentTlQuery::get().raise_storing_error("Constructor %s of type Maybe was not found in TL scheme", name.c_str()); return; } } @@ -439,7 +439,7 @@ struct t_Maybe { return elem_state.fetch(); } default: { - CurrentProcessingQuery::get().raise_fetching_error("Incorrect magic of type Maybe: 0x%08x", magic); + CurrentTlQuery::get().raise_fetching_error("Incorrect magic of type Maybe: 0x%08x", magic); return -1; } } @@ -478,7 +478,7 @@ struct t_Maybe { break; } default: { - CurrentProcessingQuery::get().raise_fetching_error("Incorrect magic of type Maybe: 0x%08x", magic); + CurrentTlQuery::get().raise_fetching_error("Incorrect magic of type Maybe: 0x%08x", magic); return; } } @@ -494,7 +494,7 @@ struct tl_Dictionary_impl { void store(const mixed &v) { if (!v.is_array()) { - CurrentProcessingQuery::get().raise_storing_error("Expected array (dictionary), got something strange"); + CurrentTlQuery::get().raise_storing_error("Expected array (dictionary), got something strange"); return; } int64_t n = v.count(); @@ -511,7 +511,7 @@ struct tl_Dictionary_impl { array result; int32_t n = rpc_fetch_int(); if (n < 0) { - CurrentProcessingQuery::get().raise_fetching_error("Dictionary size is negative"); + CurrentTlQuery::get().raise_fetching_error("Dictionary size is negative"); return result; } for (int32_t i = 0; i < n; ++i) { @@ -544,7 +544,7 @@ struct tl_Dictionary_impl { CHECK_EXCEPTION(return); int32_t n = rpc_fetch_int(); if (n < 0) { - CurrentProcessingQuery::get().raise_fetching_error("Dictionary size is negative"); + CurrentTlQuery::get().raise_fetching_error("Dictionary size is negative"); return; } for (int32_t i = 0; i < n; ++i) { @@ -579,13 +579,13 @@ struct t_Tuple { void store(const mixed &v) { if (!v.is_array()) { - CurrentProcessingQuery::get().raise_storing_error("Expected tuple, got %s", v.get_type_c_str()); + CurrentTlQuery::get().raise_storing_error("Expected tuple, got %s", v.get_type_c_str()); return; } const array &a = v.as_array(); for (int64_t i = 0; i < size; ++i) { if (!a.isset(i)) { - CurrentProcessingQuery::get().raise_storing_error("Tuple[%ld] not set", i); + CurrentTlQuery::get().raise_storing_error("Tuple[%ld] not set", i); return; } store_magic_if_not_bare(inner_magic); @@ -613,7 +613,7 @@ struct t_Tuple { for (int64_t i = 0; i < size; ++i) { if (!v.isset(i)) { - CurrentProcessingQuery::get().raise_storing_error("Tuple[%ld] not set", i); + CurrentTlQuery::get().raise_storing_error("Tuple[%ld] not set", i); return; } store_magic_if_not_bare(inner_magic); @@ -651,13 +651,13 @@ struct tl_array { void store(const mixed &v) { if (!v.is_array()) { - CurrentProcessingQuery::get().raise_storing_error("Expected array, got %s", v.get_type_c_str()); + CurrentTlQuery::get().raise_storing_error("Expected array, got %s", v.get_type_c_str()); return; } const array &a = v.as_array(); for (int64_t i = 0; i < size; ++i) { if (!a.isset(i)) { - CurrentProcessingQuery::get().raise_storing_error("Array[%ld] not set", i); + CurrentTlQuery::get().raise_storing_error("Array[%ld] not set", i); return; } store_magic_if_not_bare(inner_magic); @@ -686,7 +686,7 @@ struct tl_array { for (int64_t i = 0; i < size; ++i) { if (!v.isset(i)) { - CurrentProcessingQuery::get().raise_storing_error("Array[%ld] not set", i); + CurrentTlQuery::get().raise_storing_error("Array[%ld] not set", i); return; } store_magic_if_not_bare(inner_magic); diff --git a/runtime/typed_rpc.cpp b/runtime/typed_rpc.cpp index 867ade841c..e327619f6a 100644 --- a/runtime/typed_rpc.cpp +++ b/runtime/typed_rpc.cpp @@ -59,9 +59,9 @@ class typed_rpc_tl_query_result_one_resumable : public Resumable { RETURN(error_factory_.make_error(last_rpc_error_message_get(), last_rpc_error_code_get())); } - CurrentProcessingQuery::get().set_current_tl_function(query_); + CurrentTlQuery::get().set_current_tl_function(query_); auto rpc_result = fetch_result(std::move(query_.get()->result_fetcher), error_factory_); - CurrentProcessingQuery::get().reset(); + CurrentTlQuery::get().reset(); rpc_parse_restore_previous(); RETURN(rpc_result); RESUMABLE_END @@ -243,7 +243,7 @@ array> typed_rpc_tl_query_result_synchronous } void free_typed_rpc_lib() { - CurrentProcessingQuery::get().reset(); + CurrentTlQuery::get().reset(); RpcPendingQueries::get().hard_reset(); CurrentRpcServerQuery::get().reset(); } diff --git a/tests/cpp/runtime/memory_resource/details/memory_chunk_tree-test.cpp b/tests/cpp/runtime/memory_resource/details/memory_chunk_tree-test.cpp index d283440fd3..da5b377700 100644 --- a/tests/cpp/runtime/memory_resource/details/memory_chunk_tree-test.cpp +++ b/tests/cpp/runtime/memory_resource/details/memory_chunk_tree-test.cpp @@ -1,5 +1,7 @@ #include +#include #include +#include #include #include "runtime-core/memory-resource/details/memory_chunk_tree.h" @@ -12,7 +14,7 @@ TEST(memory_chunk_tree_test, empty) { ASSERT_FALSE(mem_chunk_tree.extract(1)); ASSERT_FALSE(mem_chunk_tree.extract(9999)); - memory_resource::details::memory_ordered_chunk_list mem_list{nullptr}; + memory_resource::details::memory_ordered_chunk_list mem_list{nullptr, nullptr}; mem_chunk_tree.flush_to(mem_list); ASSERT_FALSE(mem_list.flush()); } @@ -20,9 +22,9 @@ TEST(memory_chunk_tree_test, empty) { TEST(memory_chunk_tree_test, hard_reset) { memory_resource::details::memory_chunk_tree mem_chunk_tree; - char some_memory[1024]; - mem_chunk_tree.insert(some_memory, 100); - mem_chunk_tree.insert(some_memory + 200, 150); + std::array some_memory{}; + mem_chunk_tree.insert(some_memory.data(), 100); + mem_chunk_tree.insert(some_memory.data() + 200, 150); mem_chunk_tree.hard_reset(); ASSERT_FALSE(mem_chunk_tree.extract_smallest()); @@ -32,34 +34,31 @@ TEST(memory_chunk_tree_test, hard_reset) { TEST(memory_chunk_tree_test, insert_extract) { memory_resource::details::memory_chunk_tree mem_chunk_tree; - char some_memory[1024 * 1024]; + std::array(1024 * 1024)> some_memory{}; const auto chunk_sizes = prepare_test_sizes(); const auto chunk_offsets = make_offsets(chunk_sizes); for (size_t i = 0; i < chunk_sizes.size(); ++i) { - mem_chunk_tree.insert(some_memory + chunk_offsets[i], chunk_sizes[i]); + mem_chunk_tree.insert(some_memory.data() + chunk_offsets[i], chunk_sizes[i]); } size_t extracted = 0; for (int i = 0; i < 2; ++i, ++extracted) { auto *mem = mem_chunk_tree.extract(99); ASSERT_TRUE(mem); - ASSERT_EQ(memory_resource::details::memory_chunk_tree::get_chunk_size(mem), - memory_resource::details::align_for_chunk(99)); + ASSERT_EQ(memory_resource::details::memory_chunk_tree::get_chunk_size(mem), memory_resource::details::align_for_chunk(99)); } for (int i = 0; i < 3; ++i, ++extracted) { auto *mem = mem_chunk_tree.extract(99); ASSERT_TRUE(mem); - ASSERT_EQ(memory_resource::details::memory_chunk_tree::get_chunk_size(mem), - memory_resource::details::align_for_chunk(100)); + ASSERT_EQ(memory_resource::details::memory_chunk_tree::get_chunk_size(mem), memory_resource::details::align_for_chunk(100)); } for (int i = 0; i < 2; ++i, ++extracted) { auto *mem = mem_chunk_tree.extract(112); ASSERT_TRUE(mem); - ASSERT_EQ(memory_resource::details::memory_chunk_tree::get_chunk_size(mem), - memory_resource::details::align_for_chunk(112)); + ASSERT_EQ(memory_resource::details::memory_chunk_tree::get_chunk_size(mem), memory_resource::details::align_for_chunk(112)); } auto *mem = mem_chunk_tree.extract(250); @@ -71,14 +70,12 @@ TEST(memory_chunk_tree_test, insert_extract) { mem = mem_chunk_tree.extract(1); ++extracted; ASSERT_TRUE(mem); - ASSERT_EQ(memory_resource::details::memory_chunk_tree::get_chunk_size(mem), - memory_resource::details::align_for_chunk(42)); + ASSERT_EQ(memory_resource::details::memory_chunk_tree::get_chunk_size(mem), memory_resource::details::align_for_chunk(42)); mem = mem_chunk_tree.extract(42); ++extracted; ASSERT_TRUE(mem); - ASSERT_EQ(memory_resource::details::memory_chunk_tree::get_chunk_size(mem), - memory_resource::details::align_for_chunk(45)); + ASSERT_EQ(memory_resource::details::memory_chunk_tree::get_chunk_size(mem), memory_resource::details::align_for_chunk(45)); size_t prev_size = 0; for (; extracted != chunk_sizes.size(); ++extracted) { @@ -92,20 +89,19 @@ TEST(memory_chunk_tree_test, insert_extract) { ASSERT_FALSE(mem); } - TEST(memory_chunk_tree_test, insert_extract_smallest) { memory_resource::details::memory_chunk_tree mem_chunk_tree; - char some_memory[1024 * 1024]; + std::array(1024 * 1024)> some_memory{}; auto chunk_sizes = prepare_test_sizes(); const auto chunk_offsets = make_offsets(chunk_sizes); for (size_t i = 0; i < chunk_sizes.size(); ++i) { - mem_chunk_tree.insert(some_memory + chunk_offsets[i], chunk_sizes[i]); + mem_chunk_tree.insert(some_memory.data() + chunk_offsets[i], chunk_sizes[i]); } std::sort(chunk_sizes.begin(), chunk_sizes.end()); - for (auto chunk_size: chunk_sizes) { + for (auto chunk_size : chunk_sizes) { auto *mem = mem_chunk_tree.extract_smallest(); ASSERT_TRUE(mem); ASSERT_EQ(memory_resource::details::memory_chunk_tree::get_chunk_size(mem), chunk_size); @@ -118,22 +114,22 @@ TEST(memory_chunk_tree_test, insert_extract_smallest) { TEST(memory_chunk_tree_test, flush_to) { memory_resource::details::memory_chunk_tree mem_chunk_tree; - char some_memory[1024 * 1024]; + std::array(1024 * 1024)> some_memory{}; const auto chunk_sizes = prepare_test_sizes(); const auto chunk_offsets = make_offsets(chunk_sizes); for (size_t i = 0; i < chunk_sizes.size(); ++i) { - mem_chunk_tree.insert(some_memory + chunk_offsets[i], chunk_sizes[i]); + mem_chunk_tree.insert(some_memory.data() + chunk_offsets[i], chunk_sizes[i]); } - memory_resource::details::memory_ordered_chunk_list ordered_list{some_memory}; + memory_resource::details::memory_ordered_chunk_list ordered_list{some_memory.data(), some_memory.data() + some_memory.size()}; mem_chunk_tree.flush_to(ordered_list); ASSERT_FALSE(mem_chunk_tree.extract_smallest()); auto *first_node = ordered_list.flush(); ASSERT_TRUE(first_node); ASSERT_EQ(first_node->size(), chunk_offsets.back()); - ASSERT_EQ(reinterpret_cast(first_node), some_memory); + ASSERT_EQ(reinterpret_cast(first_node), some_memory.data()); ASSERT_FALSE(ordered_list.get_next(first_node)); } diff --git a/tests/cpp/runtime/memory_resource/details/memory_ordered_chunk_list-test.cpp b/tests/cpp/runtime/memory_resource/details/memory_ordered_chunk_list-test.cpp index 9dc1d58687..b29acbfb3d 100644 --- a/tests/cpp/runtime/memory_resource/details/memory_ordered_chunk_list-test.cpp +++ b/tests/cpp/runtime/memory_resource/details/memory_ordered_chunk_list-test.cpp @@ -1,30 +1,31 @@ #include +#include +#include #include #include - #include "runtime-core/memory-resource/details/memory_ordered_chunk_list.h" #include "runtime-core/memory-resource/monotonic_buffer_resource.h" #include "tests/cpp/runtime/memory_resource/details/test-helpers.h" TEST(memory_ordered_chunk_list_test, empty) { - memory_resource::details::memory_ordered_chunk_list mem_list{nullptr}; + memory_resource::details::memory_ordered_chunk_list mem_list{nullptr, nullptr}; ASSERT_FALSE(mem_list.flush()); } TEST(memory_ordered_chunk_list_test, add_memory_and_flush_to) { - char some_memory[1024 * 1024]; + std::array(1024 * 1024)> some_memory{}; const auto chunk_sizes = prepare_test_sizes(); const auto chunk_offsets = make_offsets(chunk_sizes); - memory_resource::details::memory_ordered_chunk_list ordered_list{some_memory}; + memory_resource::details::memory_ordered_chunk_list ordered_list{some_memory.data(), some_memory.data() + some_memory.size()}; for (size_t i = 0; i < chunk_sizes.size(); ++i) { - ordered_list.add_memory(some_memory + chunk_offsets[i], chunk_sizes[i]); + ordered_list.add_memory(some_memory.data() + chunk_offsets[i], chunk_sizes[i]); } constexpr size_t gap1 = memory_resource::details::align_for_chunk(510250); ASSERT_GT(gap1, chunk_offsets.back()); - char *some_memory_with_gap1 = some_memory + gap1; + char *some_memory_with_gap1 = some_memory.data() + gap1; for (size_t i = 0; i < chunk_sizes.size(); ++i) { ordered_list.add_memory(some_memory_with_gap1 + chunk_offsets[i], chunk_sizes[i]); } @@ -32,7 +33,7 @@ TEST(memory_ordered_chunk_list_test, add_memory_and_flush_to) { constexpr size_t gap2 = memory_resource::details::align_for_chunk(235847); ASSERT_GT(gap2, chunk_offsets.back()); ASSERT_GT(gap1, chunk_offsets.back() + gap2); - char *some_memory_with_gap2 = some_memory + gap2; + char *some_memory_with_gap2 = some_memory.data() + gap2; for (size_t i = 0; i < chunk_sizes.size(); ++i) { ordered_list.add_memory(some_memory_with_gap2 + chunk_offsets[i], chunk_sizes[i]); } @@ -50,7 +51,7 @@ TEST(memory_ordered_chunk_list_test, add_memory_and_flush_to) { auto *no_gap_node = ordered_list.get_next(gap2_node); ASSERT_TRUE(no_gap_node); ASSERT_EQ(no_gap_node->size(), chunk_offsets.back()); - ASSERT_EQ(reinterpret_cast(no_gap_node), some_memory); + ASSERT_EQ(reinterpret_cast(no_gap_node), some_memory.data()); ASSERT_FALSE(ordered_list.get_next(no_gap_node)); } @@ -60,15 +61,14 @@ TEST(memory_ordered_chunk_list_test, add_random) { std::mt19937 gen(rd()); std::uniform_int_distribution dis(8, 63); - constexpr size_t memory_size = 1024 * 1024; - char some_memory[memory_size]; + std::array(1024 * 1024)> some_memory{}; memory_resource::monotonic_buffer_resource mem_resource; - mem_resource.init(some_memory, memory_size); + mem_resource.init(some_memory.data(), some_memory.size()); constexpr size_t total_chunks = 16 * 1024; std::array, total_chunks> chunks; - memory_resource::details::memory_ordered_chunk_list ordered_list{some_memory}; + memory_resource::details::memory_ordered_chunk_list ordered_list{some_memory.data(), some_memory.data() + some_memory.size()}; for (auto &chunk : chunks) { size_t mem_size = dis(gen); diff --git a/tests/cpp/runtime/memory_resource/unsynchronized_pool_resource-test.cpp b/tests/cpp/runtime/memory_resource/unsynchronized_pool_resource-test.cpp index 81dc799f2e..969fba48df 100644 --- a/tests/cpp/runtime/memory_resource/unsynchronized_pool_resource-test.cpp +++ b/tests/cpp/runtime/memory_resource/unsynchronized_pool_resource-test.cpp @@ -254,7 +254,7 @@ TEST(unsynchronized_pool_resource_test, test_auto_defragmentation_huge_piece) { TEST(unsynchronized_pool_resource_test, test_auto_defragmentation_small_piece) { - std::array some_memory{}; + std::array(1024 * 32)> some_memory{}; memory_resource::unsynchronized_pool_resource resource; resource.init(some_memory.data(), some_memory.size()); @@ -292,4 +292,46 @@ TEST(unsynchronized_pool_resource_test, test_auto_defragmentation_small_piece) { ASSERT_EQ(mem_stats.small_memory_pieces, 0); resource.deallocate(mem64, 64); -} \ No newline at end of file +} + +TEST(unsynchronized_pool_resource_test, test_auto_defragmentation_with_extra_memory_pool) { + std::array(18 * 1024)> some_memory{}; + memory_resource::unsynchronized_pool_resource resource; + resource.init(some_memory.data(), some_memory.size()); + + std::array(32 * 1024) + sizeof(memory_resource::extra_memory_pool)> extra_memory{}; + resource.add_extra_memory(new (extra_memory.data()) memory_resource::extra_memory_pool{extra_memory.size()}); + + std::array pieces1024{}; + for (auto &mem : pieces1024) { + mem = resource.allocate(1024); + } + for (auto *mem : pieces1024) { + resource.deallocate(mem, 1024); + } + + // got fragmentation + auto mem_stats = resource.get_memory_stats(); + ASSERT_EQ(mem_stats.real_memory_used, some_memory.size() - 1024); + ASSERT_EQ(mem_stats.memory_used, 0); + ASSERT_EQ(mem_stats.max_real_memory_used, some_memory.size()); + ASSERT_EQ(mem_stats.max_memory_used, some_memory.size() + extra_memory.size() - sizeof(memory_resource::extra_memory_pool)); + ASSERT_EQ(mem_stats.memory_limit, some_memory.size()); + ASSERT_EQ(mem_stats.defragmentation_calls, 0); + ASSERT_EQ(mem_stats.huge_memory_pieces, 0); + ASSERT_EQ(mem_stats.small_memory_pieces, 49); + + // auto defragmentation + void *mem2048 = resource.allocate(2048); + mem_stats = resource.get_memory_stats(); + ASSERT_EQ(mem_stats.real_memory_used, 2048); + ASSERT_EQ(mem_stats.memory_used, 2048); + ASSERT_EQ(mem_stats.max_real_memory_used, some_memory.size()); + ASSERT_EQ(mem_stats.max_memory_used, some_memory.size() + extra_memory.size() - sizeof(memory_resource::extra_memory_pool)); + ASSERT_EQ(mem_stats.memory_limit, some_memory.size()); + ASSERT_EQ(mem_stats.defragmentation_calls, 1); + ASSERT_EQ(mem_stats.huge_memory_pieces, 0); + ASSERT_EQ(mem_stats.small_memory_pieces, 0); + + resource.deallocate(mem2048, 2048); +}