From 2381e7181b7224f01340954d5a2fd78bbc5448a9 Mon Sep 17 00:00:00 2001 From: Andrey Arutiunian <110744283+andarut@users.noreply.github.com> Date: Thu, 25 Jul 2024 13:19:28 +0300 Subject: [PATCH 01/45] Fix mac os CI build (#1047) Installing jsonschema right way --- .github/workflows/Build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/Build.yml b/.github/workflows/Build.yml index d3c8d31e5f..462db84449 100644 --- a/.github/workflows/Build.yml +++ b/.github/workflows/Build.yml @@ -142,7 +142,7 @@ jobs: brew update brew install re2c cmake coreutils openssl libiconv re2 pcre yaml-cpp zstd googletest shivammathur/php/php@7.4 brew link --overwrite --force shivammathur/php/php@7.4 - /usr/local/Frameworks/Python.framework/Versions/3.12/bin/python3.12 -m pip install --upgrade pip --break-system-packages && /usr/local/Frameworks/Python.framework/Versions/3.12/bin/pip3 install jsonschema install --break-system-packages jsonschema + /usr/local/Frameworks/Python.framework/Versions/3.12/bin/python3.12 -m pip install --upgrade pip --break-system-packages && /usr/local/Frameworks/Python.framework/Versions/3.12/bin/pip3 install --break-system-packages jsonschema - name: Run cmake run: cmake -DCMAKE_CXX_COMPILER=${{matrix.compiler}} -DCMAKE_CXX_STANDARD=${{matrix.cpp}} -DDOWNLOAD_MISSING_LIBRARIES=On -S $GITHUB_WORKSPACE -B ${{runner.workspace}}/build From 0b6782ee92366525ac8dd5890a7ab75a717f1bbe Mon Sep 17 00:00:00 2001 From: Mikhail Kornaukhov Date: Wed, 3 Jul 2024 14:21:56 +0300 Subject: [PATCH 02/45] Rebase on a single commit & some minor fixes to be compilable --- cmake/init-global-vars.cmake | 1 + cmake/popular-common.cmake | 10 +- compiler/common_sources.h.in | 3 + compiler/compiler-core.cpp | 35 +++++ compiler/compiler-core.h | 9 ++ compiler/compiler-settings.cpp | 6 + compiler/compiler-settings.h | 2 + compiler/compiler.cmake | 8 ++ compiler/cpp-dest-dir-initializer.cpp | 4 + compiler/index.cpp | 44 +++++- compiler/index.h | 2 + compiler/kphp2cpp.cpp | 3 + compiler/make/make.cpp | 126 ++++++++++++++++-- compiler/make/objs-to-k2-component-target.h | 8 +- compiler/make/runtime-src-to-obj-target.h | 23 ++++ compiler/runtime_build_info.h | 5 + compiler/runtime_compile_flags.h.in | 3 + compiler/runtime_sources.h.in | 3 + runtime-light/allocator/allocator.cmake | 3 +- runtime-light/component/component.cmake | 1 + runtime-light/core/core.cmake | 4 +- .../memory-resource-impl.cmake | 1 + runtime-light/runtime-light.cmake | 54 +++++--- runtime-light/stdlib/stdlib.cmake | 2 +- runtime-light/streams/streams.cmake | 2 +- runtime-light/utils/utils.cmake | 2 +- 26 files changed, 322 insertions(+), 42 deletions(-) create mode 100644 compiler/common_sources.h.in create mode 100644 compiler/make/runtime-src-to-obj-target.h create mode 100644 compiler/runtime_build_info.h create mode 100644 compiler/runtime_compile_flags.h.in create mode 100644 compiler/runtime_sources.h.in create mode 100644 runtime-light/component/component.cmake create mode 100644 runtime-light/memory-resource-impl/memory-resource-impl.cmake diff --git a/cmake/init-global-vars.cmake b/cmake/init-global-vars.cmake index 2111150e05..e291e0d15e 100644 --- a/cmake/init-global-vars.cmake +++ b/cmake/init-global-vars.cmake @@ -6,6 +6,7 @@ set(OBJS_DIR ${BASE_DIR}/objs) set(BIN_DIR ${OBJS_DIR}/bin) set(GENERATED_DIR "${OBJS_DIR}/generated") set(AUTO_DIR "${GENERATED_DIR}/auto") +set(RUNTIME_LIGHT_DIR "${BASE_DIR}/runtime-light") if(APPLE) set(CURL_LIB curl) diff --git a/cmake/popular-common.cmake b/cmake/popular-common.cmake index 185a11efe9..998ad01aa4 100644 --- a/cmake/popular-common.cmake +++ b/cmake/popular-common.cmake @@ -1,9 +1,14 @@ include_guard(GLOBAL) -prepend(LIGHT_COMMON_SOURCES ${COMMON_DIR}/ +set(LIGHT_COMMON_SOURCES algorithms/simd-int-to-string.cpp ) +set(COMMON_SOURCES_FOR_COMP "${LIGHT_COMMON_SOURCES}") +configure_file(${BASE_DIR}/compiler/common_sources.h.in ${AUTO_DIR}/compiler/common_sources.h) + +prepend(LIGHT_COMMON_SOURCES ${COMMON_DIR}/ ${LIGHT_COMMON_SOURCES}) + prepend(POPULAR_COMMON_SOURCES ${COMMON_DIR}/ resolver.cpp precise-time.cpp @@ -36,6 +41,9 @@ endif() vk_add_library(light_common OBJECT ${LIGHT_COMMON_SOURCES}) set_property(TARGET light_common PROPERTY POSITION_INDEPENDENT_CODE ON) +target_compile_options(light_common PUBLIC -stdlib=libc++) +target_link_options(light_common PUBLIC -stdlib=libc++ -static-libstdc++) + vk_add_library(popular_common OBJECT ${POPULAR_COMMON_SOURCES} ${LIGHT_COMMON_SOURCES}) set_property(TARGET popular_common PROPERTY POSITION_INDEPENDENT_CODE ON) diff --git a/compiler/common_sources.h.in b/compiler/common_sources.h.in new file mode 100644 index 0000000000..dbb3678529 --- /dev/null +++ b/compiler/common_sources.h.in @@ -0,0 +1,3 @@ +#pragma once + +#define COMMON_SOURCES "${COMMON_SOURCES_FOR_COMP}" diff --git a/compiler/compiler-core.cpp b/compiler/compiler-core.cpp index 0c1fee7386..4ae005882c 100644 --- a/compiler/compiler-core.cpp +++ b/compiler/compiler-core.cpp @@ -634,6 +634,14 @@ const Index &CompilerCore::get_index() { return cpp_index; } +const Index &CompilerCore::get_runtime_index() { + return runtime_sources_index; +} + +const Index &CompilerCore::get_common_index() { + return common_sources_index; +} + File *CompilerCore::get_file_info(std::string &&file_name) { return cpp_index.insert_file(std::move(file_name)); } @@ -648,6 +656,33 @@ void CompilerCore::init_dest_dir() { cpp_dir = cpp_index.get_dir(); } +static std::vector get_runtime_sources() { +#ifdef RUNTIME_SOURCES + return split(RUNTIME_SOURCES, ';'); +#endif + return {}; +} + +static std::vector get_common_sources() { +#ifdef COMMON_SOURCES + return split(COMMON_SOURCES, ';'); +#endif + return {}; +} + +void CompilerCore::init_runtime_and_common_srcs_dir() { + runtime_sources_dir = settings().runtime_and_common_src.get() + "runtime-light/"; + runtime_sources_index.sync_with_dir(runtime_sources_dir); + runtime_sources_dir = runtime_sources_index.get_dir(); // As in init_dest_dir, IDK what is it for + runtime_sources_index.filter_with_whitelist(get_runtime_sources()); + + + common_sources_dir = settings().runtime_and_common_src.get() + "common/"; + common_sources_index.sync_with_dir(common_sources_dir); + common_sources_dir = common_sources_index.get_dir(); // As in init_dest_dir, IDK what is it for + common_sources_index.filter_with_whitelist(get_common_sources()); +} + bool CompilerCore::try_require_file(SrcFilePtr file) { return __sync_bool_compare_and_swap(&file->is_required, false, true); } diff --git a/compiler/compiler-core.h b/compiler/compiler-core.h index e3fd2dd805..e13fcbb814 100644 --- a/compiler/compiler-core.h +++ b/compiler/compiler-core.h @@ -34,6 +34,8 @@ enum class OutputMode { class CompilerCore { private: Index cpp_index; + Index runtime_sources_index; + Index common_sources_index; TSHashTable file_ht; TSHashTable dirs_ht; TSHashTable functions_ht; @@ -61,6 +63,10 @@ class CompilerCore { public: std::string cpp_dir; + // Don't like that, handle in another way + std::string runtime_sources_dir; + std::string common_sources_dir; + CompilerCore(); void start(); void finish(); @@ -130,9 +136,12 @@ class CompilerCore { void load_index(); void save_index(); const Index &get_index(); + const Index &get_runtime_index(); + const Index &get_common_index(); File *get_file_info(std::string &&file_name); void del_extra_files(); void init_dest_dir(); + void init_runtime_and_common_srcs_dir(); void try_load_tl_classes(); void init_composer_class_loader(); diff --git a/compiler/compiler-settings.cpp b/compiler/compiler-settings.cpp index 95f77c5735..f1d0bf14b6 100644 --- a/compiler/compiler-settings.cpp +++ b/compiler/compiler-settings.cpp @@ -316,6 +316,12 @@ void CompilerSettings::init() { #error unsupported __cplusplus value #endif + if (mode.get() == "k2-component") { + // for now k2-component must be compiled with clang and statically linked libc++ + ss << " -stdlib=libc++"; + } else { + kphp_error(!rt_from_sources.get(), "Building runtime from sources is available only for 'k2-component' mode"); + } std::string cxx_default_flags = ss.str(); cxx_toolchain_option.value_ = !cxx_toolchain_dir.value_.empty() ? ("-B" + cxx_toolchain_dir.value_) : ""; diff --git a/compiler/compiler-settings.h b/compiler/compiler-settings.h index 7410c3b66a..3a29e55e33 100644 --- a/compiler/compiler-settings.h +++ b/compiler/compiler-settings.h @@ -118,6 +118,8 @@ class CompilerSettings : vk::not_copyable { KphpOption mode; KphpOption link_file; KphpOption> includes; + KphpOption runtime_and_common_src; + KphpOption rt_from_sources; KphpOption dest_dir; KphpOption user_binary_path; diff --git a/compiler/compiler.cmake b/compiler/compiler.cmake index 6ae6aadadf..302f880d1e 100644 --- a/compiler/compiler.cmake +++ b/compiler/compiler.cmake @@ -3,6 +3,10 @@ set(KPHP_COMPILER_DIR ${BASE_DIR}/compiler) set(KPHP_COMPILER_AUTO_DIR ${AUTO_DIR}/compiler) set(KEYWORDS_SET ${KPHP_COMPILER_AUTO_DIR}/keywords_set.hpp) set(KEYWORDS_GPERF ${KPHP_COMPILER_DIR}/keywords.gperf) +prepend(RUNTIME_BUILD_INFO ${KPHP_COMPILER_AUTO_DIR}/ + common_sources.h + runtime_sources.h + runtime_compile_flags.h) prepend(KPHP_COMPILER_COMMON ${COMMON_DIR}/ dl-utils-lite.cpp @@ -224,6 +228,7 @@ endif() list(APPEND KPHP_COMPILER_SOURCES ${KPHP_COMPILER_COMMON} ${KEYWORDS_SET} + ${RUNTIME_BUILD_INFO} ${AUTO_DIR}/compiler/rewrite-rules/early_opt.cpp) vk_add_library(kphp2cpp_src OBJECT ${KPHP_COMPILER_SOURCES}) @@ -275,3 +280,6 @@ target_link_options(kphp2cpp PRIVATE ${NO_PIE}) set_target_properties(kphp2cpp PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${BIN_DIR}) add_dependencies(kphp2cpp_src auto_vertices_generation_target) +if(COMPILE_RUNTIME_LIGHT) + add_dependencies(kphp2cpp php_lib_version_sha_256) +endif() diff --git a/compiler/cpp-dest-dir-initializer.cpp b/compiler/cpp-dest-dir-initializer.cpp index 72bce6de9b..463b6a4230 100644 --- a/compiler/cpp-dest-dir-initializer.cpp +++ b/compiler/cpp-dest-dir-initializer.cpp @@ -14,6 +14,10 @@ void CppDestDirInitializer::initialize_sync() noexcept { AutoProfiler prof{*cache}; G->init_dest_dir(); G->load_index(); + + if (G->settings().rt_from_sources.get()) { + G->init_runtime_and_common_srcs_dir(); + } } void CppDestDirInitializer::initialize_async(int32_t thread_id) noexcept { diff --git a/compiler/index.cpp b/compiler/index.cpp index 37a768d4ee..3973394389 100644 --- a/compiler/index.cpp +++ b/compiler/index.cpp @@ -146,11 +146,49 @@ int Index::scan_dir_callback(const char *fpath, const struct stat *sb, int typef void Index::sync_with_dir(const std::string &dir) { kphp_assert(files_prev_launch.empty() && files_only_cur_launch.empty() && this->dir.empty()); set_dir(dir); - current_index = this; + current_index = this; // Presence of this global variable is strange. Used for traversal callback in NFTW function int err = nftw(dir.c_str(), scan_dir_callback, 10, FTW_PHYS/*ignore symbolic links*/); kphp_assert_msg(err == 0, fmt_format("ftw [{}] failed", dir)); } +void Index::filter_with_whitelist(const std::vector &white_list) { + subdirs.clear(); + + std::unordered_set white_set; + white_set.reserve(white_list.size()); + for (const auto &allowed : white_list) { + white_set.insert(allowed); + } + + for (auto it = files_prev_launch.begin(); it != files_prev_launch.end();) { + File *file = it->second; + if (!white_set.contains(file->name)) { + delete file; + it = files_prev_launch.erase(it); + } else { + if (!file->subdir.empty()) { + subdirs.insert(file->subdir); + } + ++it; + } + } +} + +void Index::del_all_files() { + for (File *file : get_files()) { + if (G->settings().verbosity.get() > 1) { + fprintf(stderr, "unlink %s\n", file->path.c_str()); + } + int err = unlink(file->path.c_str()); + if (err != 0) { + kphp_error (0, fmt_format("Failed to unlink file {}: {}", file->path, strerror(errno))); + kphp_fail(); + } + + delete file; + } +} + void Index::del_extra_files() { // delete files that were not emerged by the current launch // we need only to iterate through files_prev_codegen and unlink if !file->needed @@ -181,7 +219,7 @@ void Index::create_subdir(vk::string_view subdir) { return; } std::string full_path = get_dir() + subdir; - int ret = mkdir(full_path.c_str(), 0777); + int ret = mkdir_recursive(full_path.c_str(), 0777); kphp_assert_msg(ret != -1 || errno == EEXIST, full_path); if (errno == EEXIST && !is_dir(full_path)) { kphp_error (0, fmt_format("[{}] is not a directory", full_path.c_str())); @@ -224,7 +262,7 @@ File *Index::insert_file(std::string path) { } // printf("%s not found in index, creating\n", file_name.c_str()); - std::lock_guard guard{mutex_rw_cur_launch}; + std::lock_guard guard{mutex_rw_cur_launch}; // It's reasonable only for Php2Cpp f = new File(path); f->calc_name_ext_and_others(get_dir()); diff --git a/compiler/index.h b/compiler/index.h index 29c188013f..8421951ca8 100644 --- a/compiler/index.h +++ b/compiler/index.h @@ -80,7 +80,9 @@ class Index { void set_dir(const std::string &dir); const std::string &get_dir() const; void sync_with_dir(const std::string &dir); + void filter_with_whitelist(const std::vector &white_list); void del_extra_files(); + void del_all_files(); File *insert_file(std::string path); File *get_file(std::string path) const; diff --git a/compiler/kphp2cpp.cpp b/compiler/kphp2cpp.cpp index 30bc28c6a9..4c440864fa 100644 --- a/compiler/kphp2cpp.cpp +++ b/compiler/kphp2cpp.cpp @@ -219,6 +219,9 @@ int main(int argc, char *argv[]) { 'M', "mode", "KPHP_MODE", "server", {"server", "k2-component", "cli", "lib"}); parser.add("A runtime library for building the output binary", settings->link_file, 'l', "link-with", "KPHP_LINK_FILE"); + parser.add("Build runtime from sources", settings->rt_from_sources, + "rt-from-sources", "KPHP_RT_FROM_SOURCES"); + parser.add("Path to runtime sources", settings->runtime_and_common_src, "rt-path", "KPHP_RT_PATH", get_default_kphp_path()); parser.add("Directory where php files will be searched", settings->includes, 'I', "include-dir", "KPHP_INCLUDE_DIR"); parser.add("Destination directory", settings->dest_dir, diff --git a/compiler/make/make.cpp b/compiler/make/make.cpp index 2728dfd59e..b9c7a1d9ef 100644 --- a/compiler/make/make.cpp +++ b/compiler/make/make.cpp @@ -4,8 +4,10 @@ #include "compiler/make/make.h" +#include #include #include +#include #include #include @@ -16,6 +18,7 @@ #include "compiler/data/lib-data.h" #include "compiler/data/ffi-data.h" #include "compiler/make/cpp-to-obj-target.h" +#include "compiler/make/runtime-src-to-obj-target.h" #include "compiler/make/file-target.h" #include "compiler/make/h-to-pch-target.h" #include "compiler/make/hardlink-or-copy.h" @@ -24,6 +27,7 @@ #include "compiler/make/objs-to-obj-target.h" #include "compiler/make/objs-to-k2-component-target.h" #include "compiler/make/objs-to-static-lib-target.h" +#include "compiler/runtime_build_info.h" #include "compiler/stage.h" #include "compiler/threading/profiler.h" @@ -74,6 +78,10 @@ class MakeSetup { settings(compiler_settings) { } + Target *create_runtime_src2obj_target(File *cpp, File *obj, const std::string &options) { + return create_target(new RuntimeSrc2ObjTarget(options), to_targets(cpp), obj); + } + Target *create_cpp_target(File *cpp) { return create_target(new FileTarget(), std::vector(), cpp); } @@ -148,7 +156,9 @@ static std::forward_list collect_imported_libs() { stage::die_if_global_errors(); std::forward_list imported_libs; - imported_libs.emplace_front(new File{G->settings().link_file.get()}); + if (!G->settings().rt_from_sources.get()) { + imported_libs.emplace_front(new File{G->settings().link_file.get()}); + } for (const auto &lib: G->get_libs()) { if (lib && !lib->is_raw_php()) { std::string lib_runtime_sha256 = CompilerSettings::read_runtime_sha256_file(lib->runtime_lib_sha256_file()); @@ -182,6 +192,18 @@ static long long get_imported_header_mtime(const std::string &header_path, const return 0; } +static int unlink_cb(const char *fpath, [[maybe_unused]] const struct stat *sb, [[maybe_unused]] int typeflag, [[maybe_unused]] struct FTW *ftwbuf) { + int rv = remove(fpath); + + if (rv) + perror(fpath); + + return rv; +} + +static int rm_rf(const char *path) { + return nftw(path, unlink_cb, 64, FTW_DEPTH | FTW_PHYS); +} // prepare dir kphp_out/objs/pch_{flags} and make a target runtime-headers.h.gch inside it // in production, there will be two pch_ folders: with debug symbols and without them @@ -191,7 +213,16 @@ File *prepare_precompiled_header(Index *obj_dir, MakeSetup &make, File &runtime_ if (with_debug && flags == settings.cxx_flags_default) { return nullptr; } - if (access(flags.pch_dir.get().c_str(), F_OK) != -1) { // if a folder in /tmp exists, this pch was already made + + struct stat sb_pch_dir; + int stat_res = stat(flags.pch_dir.get().c_str(), &sb_pch_dir); + long long pch_dir_mtime = sb_pch_dir.st_mtime * 1000000000LL + sb_pch_dir.st_mtim.tv_nsec; + if (stat_res != -1 && runtime_headers_h.mtime > pch_dir_mtime) { // check for mtime because compiler fails when .h file has bigger mtime than .gch + rm_rf(flags.pch_dir.get().c_str()); + } + + if (access(flags.pch_dir.get().c_str(), F_OK) != -1) { + // if a folder in /tmp exists, this pch was already made return nullptr; } @@ -254,6 +285,8 @@ static bool kphp_make_precompiled_headers(Index *obj_dir, const CompilerSettings if (runtime_header_pch_files.empty()) { return true; } + + if (!make.make_targets(runtime_header_pch_files, "Compiling pch", settings.jobs_count.get())) { return false; } @@ -366,7 +399,7 @@ static std::vector create_obj_files(MakeSetup *make, Index &obj_dir, con } static std::vector kphp_make_target(Index &obj_dir, const Index &cpp_dir, - const std::forward_list &imported_headers, MakeSetup &make) { + const std::forward_list &imported_headers, MakeSetup &make) { std::vector lib_objs; auto imported_libs = collect_imported_libs(); for (File *link_file: imported_libs) { @@ -379,7 +412,7 @@ static std::vector kphp_make_target(Index &obj_dir, const Index &cpp_dir } static std::vector kphp_make_static_lib_target(Index &obj_dir, const Index &cpp_dir, - const std::forward_list &imported_headers, MakeSetup &make) { + const std::forward_list &imported_headers, MakeSetup &make) { return create_obj_files(&make, obj_dir, cpp_dir, imported_headers); } @@ -394,7 +427,63 @@ static std::forward_list collect_imported_headers() { return imported_headers; } -static std::vector run_pre_make(OutputMode output_mode, const CompilerSettings &settings, FILE *make_stats_file, MakeSetup &make, Index &obj_index, File &bin_file) { +static std::string get_light_runtime_compiler_options() { + std::stringstream s; + + std::vector black_list_substrings = {"debug-prefix-map"}; + std::vector options = split(RUNTIME_COMPILER_FLAGS, ';'); + + for (vk::string_view option : options) { + for (vk::string_view prohibit_substr : black_list_substrings) { + if (vk::contains(option, prohibit_substr)) continue; + s << option << " "; + } + } + s << "-std=c++20 "; + s << "-iquote " << G->settings().runtime_and_common_src.get() << " "; + s << "-fpic "; + s << "-stdlib=libc++ "; + + return s.str(); +} + +static std::vector build_runtime_and_common_from_sources(const std::string &compiler_flags, MakeSetup &make, Index &obj_dir) { + const Index & runtime_dir = G->get_runtime_index(); + const Index & common_dir = G->get_common_index(); + + std::vector objs; + objs.reserve(runtime_dir.get_files_count() + common_dir.get_files_count()); + + for (const auto *dir : std::vector{&runtime_dir, &common_dir}) { + for (File *cpp_file : dir->get_files()) { + File *obj_file = obj_dir.insert_file(static_cast(cpp_file->name_without_ext) + ".o"); + make.create_cpp_target(cpp_file); + make.create_runtime_src2obj_target(cpp_file, obj_file, compiler_flags); + objs.push_back(obj_file); + } + } + + return objs; +} + +static std::string get_parent_dir(const std::string &s) { + size_t end_i = s.size(); + if (end_i == 0) { + return ""; + } + end_i--; + while (end_i > 0 && s[end_i] == '/') { + end_i--; + } + if (end_i == 0) { + return ""; + } + + size_t last_slash_i = s.rfind('/', end_i); + return s.substr(0, last_slash_i); +} + +static std::vector run_pre_make(OutputMode output_mode, const CompilerSettings &settings, FILE *make_stats_file, MakeSetup &make, Index &obj_index, File &bin_file, Index &obj_rt_index) { AutoProfiler profiler{get_profiler("Prepare Targets For Build")}; G->del_extra_files(); @@ -405,14 +494,27 @@ static std::vector run_pre_make(OutputMode output_mode, const CompilerSe bin_file.unlink(); } + std::vector response; + + if (settings.rt_from_sources.get()) { + std::string obj_dir = get_parent_dir(obj_index.get_dir()) + "/runtime_and_common_objs/"; + obj_rt_index.sync_with_dir(obj_dir); + response = build_runtime_and_common_from_sources(get_light_runtime_compiler_options(), make, obj_rt_index); + } + const bool pch_allowed = !settings.no_pch.get(); if (pch_allowed) { kphp_error(kphp_make_precompiled_headers(&obj_index, settings, make_stats_file), "Make precompiled header failed"); } auto lib_header_dirs = collect_imported_headers(); - return output_mode == OutputMode::lib ? kphp_make_static_lib_target(obj_index, G->get_index(), lib_header_dirs, make) - : kphp_make_target(obj_index, G->get_index(), lib_header_dirs, make); + auto targets = output_mode == OutputMode::lib ? kphp_make_static_lib_target(obj_index, G->get_index(), lib_header_dirs, make) + : kphp_make_target(obj_index, G->get_index(), lib_header_dirs, make); + for (File * file : targets) { + response.push_back(file); + } + + return response; } void run_make() { @@ -427,15 +529,17 @@ void run_make() { stage::set_name("Make"); Index obj_index; + Index obj_rt_index; File bin_file(settings.binary_path.get()); kphp_assert(bin_file.read_stat() >= 0); MakeSetup make{make_stats_file, settings}; - auto objs = run_pre_make(output_mode, settings, make_stats_file, make, obj_index, bin_file); + auto objs = run_pre_make(output_mode, settings, make_stats_file, make, obj_index, bin_file, obj_rt_index); stage::die_if_global_errors(); if (output_mode == OutputMode::lib) { + // todo:k2 think about kphp libraries make.create_objs2static_lib_target(objs, &bin_file); } else if (output_mode == OutputMode::k2_component) { make.create_objs2k2_component_target(objs, &bin_file); @@ -461,6 +565,12 @@ void run_make() { stage::die_if_global_errors(); obj_index.del_extra_files(); + if (G->settings().rt_from_sources.get()) { + // It's hard to track dependencies for all .h/.cpp/.inl files of common and runtime + // To optimize time of compilation, you may use ccache/nocc + obj_rt_index.del_all_files(); + } + if (bin_file.read_stat() > 0) { G->stats.object_out_size = bin_file.file_size; } diff --git a/compiler/make/objs-to-k2-component-target.h b/compiler/make/objs-to-k2-component-target.h index 9c8cf75c43..adfecfefe2 100644 --- a/compiler/make/objs-to-k2-component-target.h +++ b/compiler/make/objs-to-k2-component-target.h @@ -29,7 +29,7 @@ class Objs2K2ComponentTarget : public Target { public: std::string get_cmd() final { std::stringstream ss; - ss << settings->cxx.get() << " -shared -o " << target() << " "; + ss << settings->cxx.get() << " -static-libgcc -stdlib=libc++ -static-libstdc++ -shared -o " << target() << " "; for (size_t i = 0; i + 1 < deps.size(); ++i) { ss << deps[i]->get_name() << " "; @@ -38,7 +38,11 @@ class Objs2K2ComponentTarget : public Target { // the last dep is runtime lib // todo:k2 think about kphp-libraries assert(deps.size() >= 1 && "There are should be at least one dependency. It's the runtime lib"); - ss << load_all_symbols_pre() << deps.back()->get_name() << load_all_symbols_post(); + if (!G->settings().rt_from_sources.get()) { + ss << load_all_symbols_pre() << deps.back()->get_name() << load_all_symbols_post(); + } else { + ss << deps.back()->get_name() << " "; + } return ss.str(); } }; diff --git a/compiler/make/runtime-src-to-obj-target.h b/compiler/make/runtime-src-to-obj-target.h new file mode 100644 index 0000000000..3b97b8212b --- /dev/null +++ b/compiler/make/runtime-src-to-obj-target.h @@ -0,0 +1,23 @@ +// Compiler for PHP (aka KPHP) +// Copyright (c) 2024 LLC «V Kontakte» +// Distributed under the GPL v3 License, see LICENSE.notice.txt + +#pragma once + +#include "compiler/make/target.h" + +class RuntimeSrc2ObjTarget : public Target { + std::string options; +public: + RuntimeSrc2ObjTarget(std::string options) + : options(std::move(options)) {} + std::string get_cmd() final { + std::stringstream ss; + const auto cpp_list = dep_list(); + ss << settings->cxx.get() << + " -c -o " << target(); + ss << " " << dep_list() << " " << options; + + return ss.str(); + } +}; \ No newline at end of file diff --git a/compiler/runtime_build_info.h b/compiler/runtime_build_info.h new file mode 100644 index 0000000000..ea4650b2f4 --- /dev/null +++ b/compiler/runtime_build_info.h @@ -0,0 +1,5 @@ +#pragma once + +#include "auto/compiler/common_sources.h" +#include "auto/compiler/runtime_sources.h" +#include "auto/compiler/runtime_compile_flags.h" diff --git a/compiler/runtime_compile_flags.h.in b/compiler/runtime_compile_flags.h.in new file mode 100644 index 0000000000..e93a2cf108 --- /dev/null +++ b/compiler/runtime_compile_flags.h.in @@ -0,0 +1,3 @@ +#pragma once + +#define RUNTIME_COMPILER_FLAGS "${RUNTIME_COMPILE_FLAGS}" diff --git a/compiler/runtime_sources.h.in b/compiler/runtime_sources.h.in new file mode 100644 index 0000000000..e8b4ca86ac --- /dev/null +++ b/compiler/runtime_sources.h.in @@ -0,0 +1,3 @@ +#pragma once + +#define RUNTIME_SOURCES "${RUNTIME_SOURCES_FOR_COMP}" diff --git a/runtime-light/allocator/allocator.cmake b/runtime-light/allocator/allocator.cmake index 7ec6677fe7..37ed46e427 100644 --- a/runtime-light/allocator/allocator.cmake +++ b/runtime-light/allocator/allocator.cmake @@ -1,2 +1 @@ -set(RUNTIME_ALLOCATOR_SRC - ${BASE_DIR}/runtime-light/allocator/runtime-light-allocator.cpp) +set(RUNTIME_ALLOCATOR_SRC allocator/allocator.cpp) \ No newline at end of file diff --git a/runtime-light/component/component.cmake b/runtime-light/component/component.cmake new file mode 100644 index 0000000000..0f4f84f903 --- /dev/null +++ b/runtime-light/component/component.cmake @@ -0,0 +1 @@ +prepend(RUNTIME_COMPONENT_SRC component/ component.cpp) \ No newline at end of file diff --git a/runtime-light/core/core.cmake b/runtime-light/core/core.cmake index 5ee7b94dac..bd8ff645da 100644 --- a/runtime-light/core/core.cmake +++ b/runtime-light/core/core.cmake @@ -1,7 +1,7 @@ -prepend(RUNTIME_LANGUAGE_SRC ${BASE_DIR}/runtime-light/core/globals/ +prepend(RUNTIME_LANGUAGE_SRC core/globals/ php-script-globals.cpp) -prepend(RUNTIME_KPHP_CORE_CONTEXT_SRC ${BASE_DIR}/runtime-light/core/kphp-core-impl/ +prepend(RUNTIME_KPHP_CORE_CONTEXT_SRC core/kphp-core-impl/ kphp-core-context.cpp) diff --git a/runtime-light/memory-resource-impl/memory-resource-impl.cmake b/runtime-light/memory-resource-impl/memory-resource-impl.cmake new file mode 100644 index 0000000000..5c73edf612 --- /dev/null +++ b/runtime-light/memory-resource-impl/memory-resource-impl.cmake @@ -0,0 +1 @@ +prepend(RUNTIME_MEMORY_RESOURCE_IMPL_SRC memory-resource-impl/ monotonic-light-buffer-resource.cpp) \ No newline at end of file diff --git a/runtime-light/runtime-light.cmake b/runtime-light/runtime-light.cmake index c8d6d1c831..e8ca81b21f 100644 --- a/runtime-light/runtime-light.cmake +++ b/runtime-light/runtime-light.cmake @@ -1,32 +1,35 @@ -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(${RUNTIME_LIGHT_DIR}/allocator/allocator.cmake) +include(${RUNTIME_LIGHT_DIR}/core/core.cmake) +include(${RUNTIME_LIGHT_DIR}/stdlib/stdlib.cmake) +include(${RUNTIME_LIGHT_DIR}/streams/streams.cmake) include(${BASE_DIR}/runtime-light/tl/tl.cmake) -include(${BASE_DIR}/runtime-light/utils/utils.cmake) +include(${RUNTIME_LIGHT_DIR}/utils/utils.cmake) +include(${RUNTIME_LIGHT_DIR}/component/component.cmake) +include(${RUNTIME_LIGHT_DIR}/memory-resource-impl/memory-resource-impl.cmake) -prepend(MONOTOINC_LIGHT_BUFFER_RESOURCE_SRC ${BASE_DIR}/runtime-light/memory-resource-impl/ - monotonic-light-buffer-resource.cpp) +set(RUNTIME_LIGHT_SRC ${RUNTIME_CORE_SRC} + ${RUNTIME_STDLIB_SRC} + ${RUNTIME_ALLOCATOR_SRC} + ${RUNTIME_COROUTINE_SRC} + ${RUNTIME_COMPONENT_SRC} + ${RUNTIME_STREAMS_SRC} + ${RUNTIME_TL_SRC} + ${RUNTIME_UTILS_SRC} + ${RUNTIME_LANGUAGE_SRC} + ${RUNTIME_MEMORY_RESOURCE_IMPL_SRC} + runtime-light.cpp) -prepend(RUNTIME_COMPONENT_SRC ${BASE_DIR}/runtime-light/ - component/component.cpp) +set(RUNTIME_SOURCES_FOR_COMP "${RUNTIME_LIGHT_SRC}") +configure_file(${BASE_DIR}/compiler/runtime_sources.h.in ${AUTO_DIR}/compiler/runtime_sources.h) -set(RUNTIME_LIGHT_SRC ${RUNTIME_CORE_SRC} - ${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) +prepend(RUNTIME_LIGHT_SRC ${RUNTIME_LIGHT_DIR}/ "${RUNTIME_LIGHT_SRC}") vk_add_library(runtime-light OBJECT ${RUNTIME_LIGHT_SRC}) set_property(TARGET runtime-light PROPERTY POSITION_INDEPENDENT_CODE ON) set_target_properties(runtime-light PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${BASE_DIR}/objs) +target_compile_options(runtime_light PUBLIC -stdlib=libc++) +target_link_options(runtime_light PUBLIC -stdlib=libc++ -static-libstdc++) vk_add_library(kphp-light-runtime STATIC) target_link_libraries(kphp-light-runtime PUBLIC vk::light_common vk::runtime-light vk::runtime-core) @@ -35,7 +38,7 @@ set_target_properties(kphp-light-runtime PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${O file(GLOB_RECURSE KPHP_RUNTIME_ALL_HEADERS RELATIVE ${BASE_DIR} CONFIGURE_DEPENDS - "${BASE_DIR}/runtime-light/*.h") + "${RUNTIME_LIGHT_DIR}/*.h") list(TRANSFORM KPHP_RUNTIME_ALL_HEADERS REPLACE "^(.+)$" [[#include "\1"]]) list(JOIN KPHP_RUNTIME_ALL_HEADERS "\n" MERGED_RUNTIME_HEADERS) file(WRITE ${AUTO_DIR}/runtime/runtime-headers.h "\ @@ -60,3 +63,12 @@ add_custom_command(OUTPUT ${OBJS_DIR}/php_lib_version.sha256 COMMAND tail -n +3 $ | sha256sum | awk '{print $$1}' > ${OBJS_DIR}/php_lib_version.sha256 DEPENDS php_lib_version_j $ COMMENT "php_lib_version.sha256 generation") + +add_custom_target(php_lib_version_sha_256 DEPENDS ${OBJS_DIR}/php_lib_version.sha256) + +get_property(RUNTIME_COMPILE_FLAGS TARGET runtime_light PROPERTY COMPILE_OPTIONS) +get_property(RUNTIME_INCLUDE_DIRS TARGET runtime_light PROPERTY INCLUDE_DIRECTORIES) + +list (JOIN RUNTIME_COMPILE_FLAGS "\;" RUNTIME_COMPILE_FLAGS) +string(REPLACE "\"" "\\\"" RUNTIME_COMPILE_FLAGS ${RUNTIME_COMPILE_FLAGS}) +configure_file(${BASE_DIR}/compiler/runtime_compile_flags.h.in ${AUTO_DIR}/compiler/runtime_compile_flags.h) diff --git a/runtime-light/stdlib/stdlib.cmake b/runtime-light/stdlib/stdlib.cmake index b2ea6ac270..74df2b9223 100644 --- a/runtime-light/stdlib/stdlib.cmake +++ b/runtime-light/stdlib/stdlib.cmake @@ -1,4 +1,4 @@ -prepend(RUNTIME_STDLIB_SRC ${BASE_DIR}/runtime-light/stdlib/ +prepend(RUNTIME_STDLIB_SRC stdlib/ interface.cpp misc.cpp output-control.cpp diff --git a/runtime-light/streams/streams.cmake b/runtime-light/streams/streams.cmake index c08ade1fec..205046fbd9 100644 --- a/runtime-light/streams/streams.cmake +++ b/runtime-light/streams/streams.cmake @@ -1,4 +1,4 @@ -prepend(RUNTIME_STREAMS_SRC ${BASE_DIR}/runtime-light/streams/ +prepend(RUNTIME_STREAMS_SRC streams/ interface.cpp streams.cpp component-stream.cpp diff --git a/runtime-light/utils/utils.cmake b/runtime-light/utils/utils.cmake index 2106bb3bb7..b25f9b6b3d 100644 --- a/runtime-light/utils/utils.cmake +++ b/runtime-light/utils/utils.cmake @@ -1,4 +1,4 @@ -prepend(RUNTIME_UTILS_SRC ${BASE_DIR}/runtime-light/utils/ +prepend(RUNTIME_UTILS_SRC utils/ php_assert.cpp json-functions.cpp context.cpp From 3480e9bb522e22114c4f920e4330664eb4fd1587 Mon Sep 17 00:00:00 2001 From: Mikhail Kornaukhov Date: Wed, 3 Jul 2024 16:49:23 +0300 Subject: [PATCH 03/45] Fix all, now works --- cmake/init-global-vars.cmake | 1 + compiler/compiler-core.cpp | 26 ++++++++++++++++++++++--- compiler/compiler-core.h | 3 +++ compiler/compiler.cmake | 1 + compiler/make/make.cpp | 7 ++++--- compiler/runtime_build_info.h | 3 ++- compiler/runtime_core_sources.h.in | 3 +++ runtime-core/runtime-core.cmake | 12 +++++++++--- runtime-light/allocator/allocator.cmake | 2 +- runtime-light/runtime-light.cmake | 13 +++++++++---- 10 files changed, 56 insertions(+), 15 deletions(-) create mode 100644 compiler/runtime_core_sources.h.in diff --git a/cmake/init-global-vars.cmake b/cmake/init-global-vars.cmake index e291e0d15e..779acb13e0 100644 --- a/cmake/init-global-vars.cmake +++ b/cmake/init-global-vars.cmake @@ -7,6 +7,7 @@ set(BIN_DIR ${OBJS_DIR}/bin) set(GENERATED_DIR "${OBJS_DIR}/generated") set(AUTO_DIR "${GENERATED_DIR}/auto") set(RUNTIME_LIGHT_DIR "${BASE_DIR}/runtime-light") +set(RUNTIME_CORE_DIR "${BASE_DIR}/runtime-core") if(APPLE) set(CURL_LIB curl) diff --git a/compiler/compiler-core.cpp b/compiler/compiler-core.cpp index 4ae005882c..921b2e7351 100644 --- a/compiler/compiler-core.cpp +++ b/compiler/compiler-core.cpp @@ -20,6 +20,7 @@ #include "compiler/data/src-dir.h" #include "compiler/data/src-file.h" #include "compiler/name-gen.h" +#include "compiler/runtime_build_info.h" namespace { @@ -638,6 +639,10 @@ const Index &CompilerCore::get_runtime_index() { return runtime_sources_index; } +const Index &CompilerCore::get_runtime_core_index() { + return runtime_core_sources_index; +} + const Index &CompilerCore::get_common_index() { return common_sources_index; } @@ -656,21 +661,36 @@ void CompilerCore::init_dest_dir() { cpp_dir = cpp_index.get_dir(); } +static std::vector get_runtime_core_sources() { +#if defined(RUNTIME_CORE_SOURCES) + return split(RUNTIME_CORE_SOURCES, ';'); +#else + return {}; +#endif +} + static std::vector get_runtime_sources() { -#ifdef RUNTIME_SOURCES +#if defined(RUNTIME_SOURCES) return split(RUNTIME_SOURCES, ';'); -#endif +#else return {}; +#endif } static std::vector get_common_sources() { #ifdef COMMON_SOURCES return split(COMMON_SOURCES, ';'); -#endif +#else return {}; +#endif } void CompilerCore::init_runtime_and_common_srcs_dir() { + runtime_core_sources_dir = settings().runtime_and_common_src.get() + "runtime-core/"; + runtime_core_sources_index.sync_with_dir(runtime_core_sources_dir); + runtime_core_sources_dir = runtime_core_sources_index.get_dir(); + runtime_core_sources_index.filter_with_whitelist(get_runtime_core_sources()); + runtime_sources_dir = settings().runtime_and_common_src.get() + "runtime-light/"; runtime_sources_index.sync_with_dir(runtime_sources_dir); runtime_sources_dir = runtime_sources_index.get_dir(); // As in init_dest_dir, IDK what is it for diff --git a/compiler/compiler-core.h b/compiler/compiler-core.h index e13fcbb814..4dbf3e7d4a 100644 --- a/compiler/compiler-core.h +++ b/compiler/compiler-core.h @@ -34,6 +34,7 @@ enum class OutputMode { class CompilerCore { private: Index cpp_index; + Index runtime_core_sources_index; Index runtime_sources_index; Index common_sources_index; TSHashTable file_ht; @@ -64,6 +65,7 @@ class CompilerCore { std::string cpp_dir; // Don't like that, handle in another way + std::string runtime_core_sources_dir; std::string runtime_sources_dir; std::string common_sources_dir; @@ -136,6 +138,7 @@ class CompilerCore { void load_index(); void save_index(); const Index &get_index(); + const Index &get_runtime_core_index(); const Index &get_runtime_index(); const Index &get_common_index(); File *get_file_info(std::string &&file_name); diff --git a/compiler/compiler.cmake b/compiler/compiler.cmake index 302f880d1e..cf7ba19e4e 100644 --- a/compiler/compiler.cmake +++ b/compiler/compiler.cmake @@ -6,6 +6,7 @@ set(KEYWORDS_GPERF ${KPHP_COMPILER_DIR}/keywords.gperf) prepend(RUNTIME_BUILD_INFO ${KPHP_COMPILER_AUTO_DIR}/ common_sources.h runtime_sources.h + runtime_core_sources.h runtime_compile_flags.h) prepend(KPHP_COMPILER_COMMON ${COMMON_DIR}/ diff --git a/compiler/make/make.cpp b/compiler/make/make.cpp index b9c7a1d9ef..b0ac8b3458 100644 --- a/compiler/make/make.cpp +++ b/compiler/make/make.cpp @@ -448,13 +448,14 @@ static std::string get_light_runtime_compiler_options() { } static std::vector build_runtime_and_common_from_sources(const std::string &compiler_flags, MakeSetup &make, Index &obj_dir) { - const Index & runtime_dir = G->get_runtime_index(); - const Index & common_dir = G->get_common_index(); + const Index &runtime_core_dir = G->get_runtime_core_index(); + const Index &runtime_dir = G->get_runtime_index(); + const Index &common_dir = G->get_common_index(); std::vector objs; objs.reserve(runtime_dir.get_files_count() + common_dir.get_files_count()); - for (const auto *dir : std::vector{&runtime_dir, &common_dir}) { + for (const auto *dir : std::vector{&runtime_core_dir, &runtime_dir, &common_dir}) { for (File *cpp_file : dir->get_files()) { File *obj_file = obj_dir.insert_file(static_cast(cpp_file->name_without_ext) + ".o"); make.create_cpp_target(cpp_file); diff --git a/compiler/runtime_build_info.h b/compiler/runtime_build_info.h index ea4650b2f4..df901f6c66 100644 --- a/compiler/runtime_build_info.h +++ b/compiler/runtime_build_info.h @@ -1,5 +1,6 @@ #pragma once #include "auto/compiler/common_sources.h" -#include "auto/compiler/runtime_sources.h" #include "auto/compiler/runtime_compile_flags.h" +#include "auto/compiler/runtime_core_sources.h" +#include "auto/compiler/runtime_sources.h" diff --git a/compiler/runtime_core_sources.h.in b/compiler/runtime_core_sources.h.in new file mode 100644 index 0000000000..c9829e6a13 --- /dev/null +++ b/compiler/runtime_core_sources.h.in @@ -0,0 +1,3 @@ +#pragma once + +#define RUNTIME_CORE_SOURCES "${RUNTIME_CORE_SOURCES_FOR_COMP}" diff --git a/runtime-core/runtime-core.cmake b/runtime-core/runtime-core.cmake index be50be3f42..b69b7d6604 100644 --- a/runtime-core/runtime-core.cmake +++ b/runtime-core/runtime-core.cmake @@ -1,15 +1,15 @@ -prepend(KPHP_CORE_RUNTIME_UTILS ${BASE_DIR}/runtime-core/utils/ +prepend(KPHP_CORE_RUNTIME_UTILS utils/ migration-php8.cpp ) -prepend(KPHP_CORE_TYPES ${BASE_DIR}/runtime-core/core-types/definition/ +prepend(KPHP_CORE_TYPES core-types/definition/ mixed.cpp string.cpp string_buffer.cpp string_cache.cpp ) -prepend(KPHP_CORE_MEMORY_RESOURCE ${BASE_DIR}/runtime-core/memory-resource/ +prepend(KPHP_CORE_MEMORY_RESOURCE memory-resource/ details/memory_chunk_tree.cpp details/memory_ordered_chunk_list.cpp monotonic_buffer_resource.cpp @@ -22,4 +22,10 @@ set(KPHP_CORE_SRC ${KPHP_CORE_MEMORY_RESOURCE} ) +if (COMPILE_RUNTIME_LIGHT) + set(RUNTIME_CORE_SOURCES_FOR_COMP "${KPHP_CORE_SRC}") + configure_file(${BASE_DIR}/compiler/runtime_core_sources.h.in ${AUTO_DIR}/compiler/runtime_core_sources.h) +endif() + +prepend(KPHP_CORE_SRC ${RUNTIME_CORE_DIR}/ "${KPHP_CORE_SRC}") vk_add_library(runtime-core OBJECT ${KPHP_CORE_SRC}) diff --git a/runtime-light/allocator/allocator.cmake b/runtime-light/allocator/allocator.cmake index 37ed46e427..95c4f11277 100644 --- a/runtime-light/allocator/allocator.cmake +++ b/runtime-light/allocator/allocator.cmake @@ -1 +1 @@ -set(RUNTIME_ALLOCATOR_SRC allocator/allocator.cpp) \ No newline at end of file +set(RUNTIME_ALLOCATOR_SRC allocator/runtime-light-allocator.cpp) \ No newline at end of file diff --git a/runtime-light/runtime-light.cmake b/runtime-light/runtime-light.cmake index e8ca81b21f..27dc295a3f 100644 --- a/runtime-light/runtime-light.cmake +++ b/runtime-light/runtime-light.cmake @@ -28,8 +28,8 @@ vk_add_library(runtime-light OBJECT ${RUNTIME_LIGHT_SRC}) set_property(TARGET runtime-light PROPERTY POSITION_INDEPENDENT_CODE ON) set_target_properties(runtime-light PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${BASE_DIR}/objs) -target_compile_options(runtime_light PUBLIC -stdlib=libc++) -target_link_options(runtime_light PUBLIC -stdlib=libc++ -static-libstdc++) +target_compile_options(runtime-light PUBLIC -stdlib=libc++) +target_link_options(runtime-light PUBLIC -stdlib=libc++ -static-libstdc++) vk_add_library(kphp-light-runtime STATIC) target_link_libraries(kphp-light-runtime PUBLIC vk::light_common vk::runtime-light vk::runtime-core) @@ -39,6 +39,11 @@ file(GLOB_RECURSE KPHP_RUNTIME_ALL_HEADERS RELATIVE ${BASE_DIR} CONFIGURE_DEPENDS "${RUNTIME_LIGHT_DIR}/*.h") +file(GLOB_RECURSE KPHP_RUNTIME_CORE_ALL_HEADERS + RELATIVE ${BASE_DIR} + CONFIGURE_DEPENDS + "${BASE_DIR}/runtime-core/*.h") +list(APPEND KPHP_RUNTIME_ALL_HEADERS ${KPHP_RUNTIME_CORE_ALL_HEADERS}) list(TRANSFORM KPHP_RUNTIME_ALL_HEADERS REPLACE "^(.+)$" [[#include "\1"]]) list(JOIN KPHP_RUNTIME_ALL_HEADERS "\n" MERGED_RUNTIME_HEADERS) file(WRITE ${AUTO_DIR}/runtime/runtime-headers.h "\ @@ -66,8 +71,8 @@ add_custom_command(OUTPUT ${OBJS_DIR}/php_lib_version.sha256 add_custom_target(php_lib_version_sha_256 DEPENDS ${OBJS_DIR}/php_lib_version.sha256) -get_property(RUNTIME_COMPILE_FLAGS TARGET runtime_light PROPERTY COMPILE_OPTIONS) -get_property(RUNTIME_INCLUDE_DIRS TARGET runtime_light PROPERTY INCLUDE_DIRECTORIES) +get_property(RUNTIME_COMPILE_FLAGS TARGET runtime-light PROPERTY COMPILE_OPTIONS) +get_property(RUNTIME_INCLUDE_DIRS TARGET runtime-light PROPERTY INCLUDE_DIRECTORIES) list (JOIN RUNTIME_COMPILE_FLAGS "\;" RUNTIME_COMPILE_FLAGS) string(REPLACE "\"" "\\\"" RUNTIME_COMPILE_FLAGS ${RUNTIME_COMPILE_FLAGS}) From 40204078af5fb94dc358c890cfe2eaa640ee46ee Mon Sep 17 00:00:00 2001 From: Mikhail Kornaukhov Date: Thu, 4 Jul 2024 13:21:09 +0300 Subject: [PATCH 04/45] Fix fail when build on full runtime --- cmake/popular-common.cmake | 13 +++++++++---- compiler/compiler-core.cpp | 6 +++--- compiler/compiler-settings.cpp | 1 + compiler/compiler.cmake | 6 +++++- compiler/index.cpp | 2 +- compiler/make/make.cpp | 2 ++ compiler/runtime_build_info.h | 4 ++++ 7 files changed, 25 insertions(+), 9 deletions(-) diff --git a/cmake/popular-common.cmake b/cmake/popular-common.cmake index 998ad01aa4..41fe692d1e 100644 --- a/cmake/popular-common.cmake +++ b/cmake/popular-common.cmake @@ -4,8 +4,10 @@ set(LIGHT_COMMON_SOURCES algorithms/simd-int-to-string.cpp ) -set(COMMON_SOURCES_FOR_COMP "${LIGHT_COMMON_SOURCES}") -configure_file(${BASE_DIR}/compiler/common_sources.h.in ${AUTO_DIR}/compiler/common_sources.h) +if (COMPILE_RUNTIME_LIGHT) + set(COMMON_SOURCES_FOR_COMP "${LIGHT_COMMON_SOURCES}") + configure_file(${BASE_DIR}/compiler/common_sources.h.in ${AUTO_DIR}/compiler/common_sources.h) +endif() prepend(LIGHT_COMMON_SOURCES ${COMMON_DIR}/ ${LIGHT_COMMON_SOURCES}) @@ -41,8 +43,11 @@ endif() vk_add_library(light_common OBJECT ${LIGHT_COMMON_SOURCES}) set_property(TARGET light_common PROPERTY POSITION_INDEPENDENT_CODE ON) -target_compile_options(light_common PUBLIC -stdlib=libc++) -target_link_options(light_common PUBLIC -stdlib=libc++ -static-libstdc++) + +if (COMPILE_RUNTIME_LIGHT) + target_compile_options(light_common PUBLIC -stdlib=libc++) + target_link_options(light_common PUBLIC -stdlib=libc++ -static-libstdc++) +endif() vk_add_library(popular_common OBJECT ${POPULAR_COMMON_SOURCES} ${LIGHT_COMMON_SOURCES}) set_property(TARGET popular_common PROPERTY POSITION_INDEPENDENT_CODE ON) diff --git a/compiler/compiler-core.cpp b/compiler/compiler-core.cpp index 921b2e7351..521ea99d41 100644 --- a/compiler/compiler-core.cpp +++ b/compiler/compiler-core.cpp @@ -662,7 +662,7 @@ void CompilerCore::init_dest_dir() { } static std::vector get_runtime_core_sources() { -#if defined(RUNTIME_CORE_SOURCES) +#ifdef RUNTIME_LIGHT return split(RUNTIME_CORE_SOURCES, ';'); #else return {}; @@ -670,7 +670,7 @@ static std::vector get_runtime_core_sources() { } static std::vector get_runtime_sources() { -#if defined(RUNTIME_SOURCES) +#ifdef RUNTIME_LIGHT return split(RUNTIME_SOURCES, ';'); #else return {}; @@ -678,7 +678,7 @@ static std::vector get_runtime_sources() { } static std::vector get_common_sources() { -#ifdef COMMON_SOURCES +#ifdef RUNTIME_LIGHT return split(COMMON_SOURCES, ';'); #else return {}; diff --git a/compiler/compiler-settings.cpp b/compiler/compiler-settings.cpp index f1d0bf14b6..ec19b6f4f1 100644 --- a/compiler/compiler-settings.cpp +++ b/compiler/compiler-settings.cpp @@ -319,6 +319,7 @@ void CompilerSettings::init() { if (mode.get() == "k2-component") { // for now k2-component must be compiled with clang and statically linked libc++ ss << " -stdlib=libc++"; + } else { kphp_error(!rt_from_sources.get(), "Building runtime from sources is available only for 'k2-component' mode"); } diff --git a/compiler/compiler.cmake b/compiler/compiler.cmake index cf7ba19e4e..17bbbb8967 100644 --- a/compiler/compiler.cmake +++ b/compiler/compiler.cmake @@ -3,11 +3,14 @@ set(KPHP_COMPILER_DIR ${BASE_DIR}/compiler) set(KPHP_COMPILER_AUTO_DIR ${AUTO_DIR}/compiler) set(KEYWORDS_SET ${KPHP_COMPILER_AUTO_DIR}/keywords_set.hpp) set(KEYWORDS_GPERF ${KPHP_COMPILER_DIR}/keywords.gperf) -prepend(RUNTIME_BUILD_INFO ${KPHP_COMPILER_AUTO_DIR}/ +if (COMPILE_RUNTIME_LIGHT) + prepend(RUNTIME_BUILD_INFO ${KPHP_COMPILER_AUTO_DIR}/ common_sources.h runtime_sources.h runtime_core_sources.h runtime_compile_flags.h) +endif() + prepend(KPHP_COMPILER_COMMON ${COMMON_DIR}/ dl-utils-lite.cpp @@ -282,5 +285,6 @@ set_target_properties(kphp2cpp PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${BIN_DIR}) add_dependencies(kphp2cpp_src auto_vertices_generation_target) if(COMPILE_RUNTIME_LIGHT) + add_compile_definitions(RUNTIME_LIGHT) add_dependencies(kphp2cpp php_lib_version_sha_256) endif() diff --git a/compiler/index.cpp b/compiler/index.cpp index 3973394389..a7cb6dfc58 100644 --- a/compiler/index.cpp +++ b/compiler/index.cpp @@ -162,7 +162,7 @@ void Index::filter_with_whitelist(const std::vector &white_list) { for (auto it = files_prev_launch.begin(); it != files_prev_launch.end();) { File *file = it->second; - if (!white_set.contains(file->name)) { + if (white_set.count(file->name) == 0) { delete file; it = files_prev_launch.erase(it); } else { diff --git a/compiler/make/make.cpp b/compiler/make/make.cpp index b0ac8b3458..02a42716f8 100644 --- a/compiler/make/make.cpp +++ b/compiler/make/make.cpp @@ -430,6 +430,7 @@ static std::forward_list collect_imported_headers() { static std::string get_light_runtime_compiler_options() { std::stringstream s; +#ifdef RUNTIME_LIGHT std::vector black_list_substrings = {"debug-prefix-map"}; std::vector options = split(RUNTIME_COMPILER_FLAGS, ';'); @@ -443,6 +444,7 @@ static std::string get_light_runtime_compiler_options() { s << "-iquote " << G->settings().runtime_and_common_src.get() << " "; s << "-fpic "; s << "-stdlib=libc++ "; +#endif return s.str(); } diff --git a/compiler/runtime_build_info.h b/compiler/runtime_build_info.h index df901f6c66..c3d2425b43 100644 --- a/compiler/runtime_build_info.h +++ b/compiler/runtime_build_info.h @@ -1,6 +1,10 @@ #pragma once +#ifdef RUNTIME_LIGHT + #include "auto/compiler/common_sources.h" #include "auto/compiler/runtime_compile_flags.h" #include "auto/compiler/runtime_core_sources.h" #include "auto/compiler/runtime_sources.h" + +#endif \ No newline at end of file From 045705606a2af94c5c315ba80a386ea27e23d6de Mon Sep 17 00:00:00 2001 From: Mikhail Kornaukhov Date: Thu, 4 Jul 2024 14:44:08 +0300 Subject: [PATCH 05/45] Small fixes --- compiler/make/runtime-src-to-obj-target.h | 2 +- compiler/runtime_build_info.h | 2 +- runtime-light/allocator/allocator.cmake | 2 +- runtime-light/component/component.cmake | 2 +- runtime-light/memory-resource-impl/memory-resource-impl.cmake | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/compiler/make/runtime-src-to-obj-target.h b/compiler/make/runtime-src-to-obj-target.h index 3b97b8212b..1e41c3702e 100644 --- a/compiler/make/runtime-src-to-obj-target.h +++ b/compiler/make/runtime-src-to-obj-target.h @@ -20,4 +20,4 @@ class RuntimeSrc2ObjTarget : public Target { return ss.str(); } -}; \ No newline at end of file +}; diff --git a/compiler/runtime_build_info.h b/compiler/runtime_build_info.h index c3d2425b43..482f9044a4 100644 --- a/compiler/runtime_build_info.h +++ b/compiler/runtime_build_info.h @@ -7,4 +7,4 @@ #include "auto/compiler/runtime_core_sources.h" #include "auto/compiler/runtime_sources.h" -#endif \ No newline at end of file +#endif diff --git a/runtime-light/allocator/allocator.cmake b/runtime-light/allocator/allocator.cmake index 95c4f11277..93e2e9f0c2 100644 --- a/runtime-light/allocator/allocator.cmake +++ b/runtime-light/allocator/allocator.cmake @@ -1 +1 @@ -set(RUNTIME_ALLOCATOR_SRC allocator/runtime-light-allocator.cpp) \ No newline at end of file +set(RUNTIME_ALLOCATOR_SRC allocator/runtime-light-allocator.cpp) diff --git a/runtime-light/component/component.cmake b/runtime-light/component/component.cmake index 0f4f84f903..e67a81992e 100644 --- a/runtime-light/component/component.cmake +++ b/runtime-light/component/component.cmake @@ -1 +1 @@ -prepend(RUNTIME_COMPONENT_SRC component/ component.cpp) \ No newline at end of file +prepend(RUNTIME_COMPONENT_SRC component/ component.cpp) diff --git a/runtime-light/memory-resource-impl/memory-resource-impl.cmake b/runtime-light/memory-resource-impl/memory-resource-impl.cmake index 5c73edf612..d4cd6fc842 100644 --- a/runtime-light/memory-resource-impl/memory-resource-impl.cmake +++ b/runtime-light/memory-resource-impl/memory-resource-impl.cmake @@ -1 +1 @@ -prepend(RUNTIME_MEMORY_RESOURCE_IMPL_SRC memory-resource-impl/ monotonic-light-buffer-resource.cpp) \ No newline at end of file +prepend(RUNTIME_MEMORY_RESOURCE_IMPL_SRC memory-resource-impl/ monotonic-light-buffer-resource.cpp) From 539827ca5be7ea21ed1c8fe8e0514dfef669e24c Mon Sep 17 00:00:00 2001 From: Mikhail Kornaukhov Date: Thu, 4 Jul 2024 15:47:57 +0300 Subject: [PATCH 06/45] Fix mac os build --- compiler/make/make.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/compiler/make/make.cpp b/compiler/make/make.cpp index 02a42716f8..488a94c4ec 100644 --- a/compiler/make/make.cpp +++ b/compiler/make/make.cpp @@ -4,12 +4,13 @@ #include "compiler/make/make.h" -#include +#include #include +#include #include +#include #include #include -#include #include "common/wrappers/mkdir_recursive.h" #include "common/wrappers/pathname.h" @@ -216,7 +217,12 @@ File *prepare_precompiled_header(Index *obj_dir, MakeSetup &make, File &runtime_ struct stat sb_pch_dir; int stat_res = stat(flags.pch_dir.get().c_str(), &sb_pch_dir); - long long pch_dir_mtime = sb_pch_dir.st_mtime * 1000000000LL + sb_pch_dir.st_mtim.tv_nsec; + long long pch_dir_mtime = sb_pch_dir.st_mtime * 1000000000LL + +#ifdef __APPLE__ + sb_pch_dir.st_mtimespec.tv_nsec; +#else + sb_pch_dir.st_mtim.tv_nsec; +#endif if (stat_res != -1 && runtime_headers_h.mtime > pch_dir_mtime) { // check for mtime because compiler fails when .h file has bigger mtime than .gch rm_rf(flags.pch_dir.get().c_str()); } From 1cdcb0f8d77ea17199edebab6b2e6b70cff00146 Mon Sep 17 00:00:00 2001 From: Mikhail Kornaukhov Date: Wed, 10 Jul 2024 20:51:07 +0300 Subject: [PATCH 07/45] more fixes --- cmake/init-compilation-flags.cmake | 2 +- runtime-light/runtime-light.cmake | 4 ++-- runtime-light/tl/tl.cmake | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cmake/init-compilation-flags.cmake b/cmake/init-compilation-flags.cmake index e019a35748..d76ead6413 100644 --- a/cmake/init-compilation-flags.cmake +++ b/cmake/init-compilation-flags.cmake @@ -106,7 +106,7 @@ 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) + add_compile_options(-Wno-vla-extension) endif() if(NOT APPLE) diff --git a/runtime-light/runtime-light.cmake b/runtime-light/runtime-light.cmake index 27dc295a3f..117ad2f0af 100644 --- a/runtime-light/runtime-light.cmake +++ b/runtime-light/runtime-light.cmake @@ -2,7 +2,7 @@ include(${RUNTIME_LIGHT_DIR}/allocator/allocator.cmake) include(${RUNTIME_LIGHT_DIR}/core/core.cmake) include(${RUNTIME_LIGHT_DIR}/stdlib/stdlib.cmake) include(${RUNTIME_LIGHT_DIR}/streams/streams.cmake) -include(${BASE_DIR}/runtime-light/tl/tl.cmake) +include(${RUNTIME_LIGHT_DIR}/tl/tl.cmake) include(${RUNTIME_LIGHT_DIR}/utils/utils.cmake) include(${RUNTIME_LIGHT_DIR}/component/component.cmake) include(${RUNTIME_LIGHT_DIR}/memory-resource-impl/memory-resource-impl.cmake) @@ -13,7 +13,7 @@ set(RUNTIME_LIGHT_SRC ${RUNTIME_CORE_SRC} ${RUNTIME_COROUTINE_SRC} ${RUNTIME_COMPONENT_SRC} ${RUNTIME_STREAMS_SRC} - ${RUNTIME_TL_SRC} +# ${RUNTIME_TL_SRC} ${RUNTIME_UTILS_SRC} ${RUNTIME_LANGUAGE_SRC} ${RUNTIME_MEMORY_RESOURCE_IMPL_SRC} diff --git a/runtime-light/tl/tl.cmake b/runtime-light/tl/tl.cmake index f80d755753..a9c6b8bf98 100644 --- a/runtime-light/tl/tl.cmake +++ b/runtime-light/tl/tl.cmake @@ -1,3 +1,3 @@ -prepend(RUNTIME_TL_SRC ${BASE_DIR}/runtime-light/tl/ +prepend(RUNTIME_TL_SRC tl/ tl-builtins.cpp ) From 36898baa571efcdb834bf3fceee773558dbc87a9 Mon Sep 17 00:00:00 2001 From: Mikhail Kornaukhov Date: Wed, 10 Jul 2024 21:55:37 +0300 Subject: [PATCH 08/45] build runtime from sources by default --- compiler/compiler-settings.cpp | 5 +++-- compiler/compiler-settings.h | 2 +- compiler/cpp-dest-dir-initializer.cpp | 2 +- compiler/kphp2cpp.cpp | 4 ++-- compiler/make/make.cpp | 6 +++--- compiler/make/objs-to-k2-component-target.h | 2 +- 6 files changed, 11 insertions(+), 10 deletions(-) diff --git a/compiler/compiler-settings.cpp b/compiler/compiler-settings.cpp index ec19b6f4f1..9432434b34 100644 --- a/compiler/compiler-settings.cpp +++ b/compiler/compiler-settings.cpp @@ -319,9 +319,10 @@ void CompilerSettings::init() { if (mode.get() == "k2-component") { // for now k2-component must be compiled with clang and statically linked libc++ ss << " -stdlib=libc++"; - } else { - kphp_error(!rt_from_sources.get(), "Building runtime from sources is available only for 'k2-component' mode"); + // default value is false + // when we build using full runtime, we should force to use runtime as static lib + force_link_runtime.set_option_arg_value("1"); } std::string cxx_default_flags = ss.str(); diff --git a/compiler/compiler-settings.h b/compiler/compiler-settings.h index 3a29e55e33..bd0222d278 100644 --- a/compiler/compiler-settings.h +++ b/compiler/compiler-settings.h @@ -119,7 +119,7 @@ class CompilerSettings : vk::not_copyable { KphpOption link_file; KphpOption> includes; KphpOption runtime_and_common_src; - KphpOption rt_from_sources; + KphpOption force_link_runtime; KphpOption dest_dir; KphpOption user_binary_path; diff --git a/compiler/cpp-dest-dir-initializer.cpp b/compiler/cpp-dest-dir-initializer.cpp index 463b6a4230..6e54b7a49b 100644 --- a/compiler/cpp-dest-dir-initializer.cpp +++ b/compiler/cpp-dest-dir-initializer.cpp @@ -15,7 +15,7 @@ void CppDestDirInitializer::initialize_sync() noexcept { G->init_dest_dir(); G->load_index(); - if (G->settings().rt_from_sources.get()) { + if (!G->settings().force_link_runtime.get()) { G->init_runtime_and_common_srcs_dir(); } } diff --git a/compiler/kphp2cpp.cpp b/compiler/kphp2cpp.cpp index 4c440864fa..033b515d55 100644 --- a/compiler/kphp2cpp.cpp +++ b/compiler/kphp2cpp.cpp @@ -219,8 +219,8 @@ int main(int argc, char *argv[]) { 'M', "mode", "KPHP_MODE", "server", {"server", "k2-component", "cli", "lib"}); parser.add("A runtime library for building the output binary", settings->link_file, 'l', "link-with", "KPHP_LINK_FILE"); - parser.add("Build runtime from sources", settings->rt_from_sources, - "rt-from-sources", "KPHP_RT_FROM_SOURCES"); + parser.add("Build runtime from sources", settings->force_link_runtime, + "force-link-runtime", "KPHP_FORCE_LINK_RUNTIME"); parser.add("Path to runtime sources", settings->runtime_and_common_src, "rt-path", "KPHP_RT_PATH", get_default_kphp_path()); parser.add("Directory where php files will be searched", settings->includes, 'I', "include-dir", "KPHP_INCLUDE_DIR"); diff --git a/compiler/make/make.cpp b/compiler/make/make.cpp index 488a94c4ec..bc583b26f0 100644 --- a/compiler/make/make.cpp +++ b/compiler/make/make.cpp @@ -157,7 +157,7 @@ static std::forward_list collect_imported_libs() { stage::die_if_global_errors(); std::forward_list imported_libs; - if (!G->settings().rt_from_sources.get()) { + if (G->settings().force_link_runtime.get()) { imported_libs.emplace_front(new File{G->settings().link_file.get()}); } for (const auto &lib: G->get_libs()) { @@ -505,7 +505,7 @@ static std::vector run_pre_make(OutputMode output_mode, const CompilerSe std::vector response; - if (settings.rt_from_sources.get()) { + if (!settings.force_link_runtime.get()) { std::string obj_dir = get_parent_dir(obj_index.get_dir()) + "/runtime_and_common_objs/"; obj_rt_index.sync_with_dir(obj_dir); response = build_runtime_and_common_from_sources(get_light_runtime_compiler_options(), make, obj_rt_index); @@ -574,7 +574,7 @@ void run_make() { stage::die_if_global_errors(); obj_index.del_extra_files(); - if (G->settings().rt_from_sources.get()) { + if (!G->settings().force_link_runtime.get()) { // It's hard to track dependencies for all .h/.cpp/.inl files of common and runtime // To optimize time of compilation, you may use ccache/nocc obj_rt_index.del_all_files(); diff --git a/compiler/make/objs-to-k2-component-target.h b/compiler/make/objs-to-k2-component-target.h index adfecfefe2..67ab97ca4a 100644 --- a/compiler/make/objs-to-k2-component-target.h +++ b/compiler/make/objs-to-k2-component-target.h @@ -38,7 +38,7 @@ class Objs2K2ComponentTarget : public Target { // the last dep is runtime lib // todo:k2 think about kphp-libraries assert(deps.size() >= 1 && "There are should be at least one dependency. It's the runtime lib"); - if (!G->settings().rt_from_sources.get()) { + if (G->settings().force_link_runtime.get()) { ss << load_all_symbols_pre() << deps.back()->get_name() << load_all_symbols_post(); } else { ss << deps.back()->get_name() << " "; From e3987ca647ff74bb07e91d6e72fb5192d97173ad Mon Sep 17 00:00:00 2001 From: Mikhail Kornaukhov Date: Thu, 11 Jul 2024 12:08:12 +0300 Subject: [PATCH 09/45] one more fix --- runtime-light/runtime-light.cmake | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/runtime-light/runtime-light.cmake b/runtime-light/runtime-light.cmake index 117ad2f0af..6100395c54 100644 --- a/runtime-light/runtime-light.cmake +++ b/runtime-light/runtime-light.cmake @@ -13,7 +13,7 @@ set(RUNTIME_LIGHT_SRC ${RUNTIME_CORE_SRC} ${RUNTIME_COROUTINE_SRC} ${RUNTIME_COMPONENT_SRC} ${RUNTIME_STREAMS_SRC} -# ${RUNTIME_TL_SRC} + ${RUNTIME_TL_SRC} ${RUNTIME_UTILS_SRC} ${RUNTIME_LANGUAGE_SRC} ${RUNTIME_MEMORY_RESOURCE_IMPL_SRC} @@ -39,11 +39,6 @@ file(GLOB_RECURSE KPHP_RUNTIME_ALL_HEADERS RELATIVE ${BASE_DIR} CONFIGURE_DEPENDS "${RUNTIME_LIGHT_DIR}/*.h") -file(GLOB_RECURSE KPHP_RUNTIME_CORE_ALL_HEADERS - RELATIVE ${BASE_DIR} - CONFIGURE_DEPENDS - "${BASE_DIR}/runtime-core/*.h") -list(APPEND KPHP_RUNTIME_ALL_HEADERS ${KPHP_RUNTIME_CORE_ALL_HEADERS}) list(TRANSFORM KPHP_RUNTIME_ALL_HEADERS REPLACE "^(.+)$" [[#include "\1"]]) list(JOIN KPHP_RUNTIME_ALL_HEADERS "\n" MERGED_RUNTIME_HEADERS) file(WRITE ${AUTO_DIR}/runtime/runtime-headers.h "\ From ed707cd46e9f9a080b6f63e30d4beab3bbf99f79 Mon Sep 17 00:00:00 2001 From: Mikhail Kornaukhov Date: Thu, 11 Jul 2024 12:41:43 +0300 Subject: [PATCH 10/45] one more fix --- compiler/compiler-settings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/compiler-settings.cpp b/compiler/compiler-settings.cpp index 9432434b34..6d17d6d0b0 100644 --- a/compiler/compiler-settings.cpp +++ b/compiler/compiler-settings.cpp @@ -322,7 +322,7 @@ void CompilerSettings::init() { } else { // default value is false // when we build using full runtime, we should force to use runtime as static lib - force_link_runtime.set_option_arg_value("1"); + force_link_runtime.value_ = true; } std::string cxx_default_flags = ss.str(); From abd4b7c48e1f545fd3f978be8d580507e25d53e1 Mon Sep 17 00:00:00 2001 From: Mikhail Kornaukhov Date: Mon, 24 Jun 2024 12:13:23 +0300 Subject: [PATCH 11/45] Add new exit codes and write some TODOs --- compiler/make/make.cpp | 2 ++ compiler/stage.cpp | 11 ++++++++++- compiler/stage.h | 9 +++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/compiler/make/make.cpp b/compiler/make/make.cpp index bc583b26f0..7fea192412 100644 --- a/compiler/make/make.cpp +++ b/compiler/make/make.cpp @@ -536,6 +536,7 @@ void run_make() { stage::die_if_global_errors(); } stage::set_name("Make"); + stage::set_exit_code(stage::CXX_STAGE_ERROR); Index obj_index; Index obj_rt_index; @@ -563,6 +564,7 @@ void run_make() { stage::die_if_global_errors(); const std::string build_stage{output_mode == OutputMode::lib ? "Compiling" : "Linking"}; + stage::set_exit_code(output_mode == OutputMode::lib ? stage::CXX_STAGE_ERROR : stage::LINKING_STAGE_ERROR); AutoProfiler profiler{get_profiler(build_stage)}; bool ok = make.make_target(&bin_file, build_stage, settings.jobs_count.get()); diff --git a/compiler/stage.cpp b/compiler/stage.cpp index 3d47150271..115ed6690e 100644 --- a/compiler/stage.cpp +++ b/compiler/stage.cpp @@ -59,6 +59,7 @@ void on_compilation_error(const char *description __attribute__((unused)), const fmt_fprintf(file, "Compilation failed.\n" "It is probably happened due to incorrect or unsupported PHP input.\n" "But it is still bug in compiler.\n"); + // TODO should we just call exit() with specific return code ot leave it as is? #ifdef __arm64__ __builtin_debugtrap(); // for easier debugging kphp_assert / kphp_fail #endif @@ -167,6 +168,10 @@ stage::StageInfo *stage::get_stage_info_ptr() { return &*stage_info; } +void stage::set_exit_code(int code) { + get_stage_info_ptr()->exit_code = code; +} + void stage::set_name(std::string &&name) { get_stage_info_ptr()->name = std::move(name); get_stage_info_ptr()->cnt_errors = 0; @@ -193,7 +198,7 @@ bool stage::has_global_error() { void stage::die_if_global_errors() { if (stage::has_global_error()) { fmt_print("Compilation terminated due to errors\n"); - exit(1); + exit(stage::get_exit_code()); } } @@ -201,6 +206,10 @@ const std::string &stage::get_name() { return get_stage_info_ptr()->name; } +int stage::get_exit_code() { + return get_stage_info_ptr()->exit_code; +} + Location *stage::get_location_ptr() { return &get_stage_info_ptr()->location; } diff --git a/compiler/stage.h b/compiler/stage.h index 9b14a947b0..9909195c3d 100644 --- a/compiler/stage.h +++ b/compiler/stage.h @@ -19,11 +19,17 @@ namespace stage { void set_warning_file(FILE *file) noexcept; +// TODO think about values +constexpr size_t KPHP_STAGE_ERROR = 101; +constexpr size_t CXX_STAGE_ERROR = 102; +constexpr size_t LINKING_STAGE_ERROR = 103; + struct StageInfo { std::string name; Location location; bool global_error_flag{false}; uint32_t cnt_errors{0}; + int exit_code = KPHP_STAGE_ERROR; }; StageInfo *get_stage_info_ptr(); @@ -42,6 +48,9 @@ void print_current_location_on_error(FILE *f); void set_name(std::string &&name); const std::string &get_name(); +void set_exit_code(int code); +int get_exit_code(); + void set_file(SrcFilePtr file); void set_function(FunctionPtr function); void set_line(int line); From b790499ac84cfaac5cd386c46a3d63a5df7d34cc Mon Sep 17 00:00:00 2001 From: Mikhail Kornaukhov Date: Mon, 24 Jun 2024 18:20:43 +0300 Subject: [PATCH 12/45] Handle signals, move to common --- common/exit-codes.h | 11 +++++++++++ common/server/signals.cpp | 15 +++++++++++++-- common/server/signals.h | 2 +- compiler/kphp2cpp.cpp | 2 +- compiler/make/make.cpp | 4 ++-- compiler/stage.cpp | 7 +++---- compiler/stage.h | 13 +++++-------- 7 files changed, 36 insertions(+), 18 deletions(-) create mode 100644 common/exit-codes.h diff --git a/common/exit-codes.h b/common/exit-codes.h new file mode 100644 index 0000000000..11e6dea3dc --- /dev/null +++ b/common/exit-codes.h @@ -0,0 +1,11 @@ +// Compiler for PHP (aka KPHP) +// Copyright (c) 2024 LLC «V Kontakte» +// Distributed under the GPL v3 License, see LICENSE.notice.txt + +#pragma once +enum class ExitCode { + KPHP_TO_CPP_STAGE = 100, + CPP_TO_OBJS_STAGE = 101, + OBJS_TO_BINARY_STAGE = 102, + SIGNAL_OFFSET = 128 +}; diff --git a/common/server/signals.cpp b/common/server/signals.cpp index 506bff7f3b..a9685fecc4 100644 --- a/common/server/signals.cpp +++ b/common/server/signals.cpp @@ -13,6 +13,7 @@ #include #include +#include "common/exit-codes.h" #include "common/kprintf.h" #include "common/macos-ports.h" #include "common/options.h" @@ -156,6 +157,15 @@ static void print_killing_process_description(int pid) { } } +static bool save_signal_in_exit_code = false; + +static int get_exit_code_from_signal(int sig) { + if (save_signal_in_exit_code) { + return static_cast(ExitCode::SIGNAL_OFFSET) + sig; + } + return EXIT_FAILURE; +} + static void generic_debug_handler(int sig, siginfo_t *info, void *ucontext) { ksignal(sig, SIG_DFL); if (sig == SIGABRT) { @@ -178,7 +188,7 @@ static void generic_debug_handler(int sig, siginfo_t *info, void *ucontext) { crash_dump_write(ucontext); print_backtrace(); kill_main(); - _exit(EXIT_FAILURE); + _exit(get_exit_code_from_signal(sig)); } static void ksignal_ext(int sig, void (*handler)(int), int sa_flags) { @@ -224,7 +234,8 @@ void ksignal_intr(int sig, void (*info)(int, siginfo_t *, void *)) { } -void set_debug_handlers() { +void set_debug_handlers(bool save_signal_in_exit_code_) { + save_signal_in_exit_code = save_signal_in_exit_code_; stack_t stack; int res = sigaltstack(nullptr, &stack); if (res < 0) { diff --git a/common/server/signals.h b/common/server/signals.h index 47e987487e..08b11f2372 100644 --- a/common/server/signals.h +++ b/common/server/signals.h @@ -15,7 +15,7 @@ extern int daemonize; void print_backtrace(); void ksignal(int sig, void (*handler)(int)); void ksignal_intr(int sig, void (*handler)(int)); -void set_debug_handlers(); +void set_debug_handlers(bool save_signal_in_exit_code = false); void setup_delayed_handlers(); int is_signal_pending(int sig); diff --git a/compiler/kphp2cpp.cpp b/compiler/kphp2cpp.cpp index 033b515d55..42b42272ae 100644 --- a/compiler/kphp2cpp.cpp +++ b/compiler/kphp2cpp.cpp @@ -200,7 +200,7 @@ int main(int argc, char *argv[]) { #endif init_version_string("kphp2cpp"); - set_debug_handlers(); + set_debug_handlers(true); auto settings = std::make_unique(); diff --git a/compiler/make/make.cpp b/compiler/make/make.cpp index 7fea192412..87da4cde6a 100644 --- a/compiler/make/make.cpp +++ b/compiler/make/make.cpp @@ -536,7 +536,7 @@ void run_make() { stage::die_if_global_errors(); } stage::set_name("Make"); - stage::set_exit_code(stage::CXX_STAGE_ERROR); + stage::set_exit_code(ExitCode::CPP_TO_OBJS_STAGE); Index obj_index; Index obj_rt_index; @@ -564,7 +564,7 @@ void run_make() { stage::die_if_global_errors(); const std::string build_stage{output_mode == OutputMode::lib ? "Compiling" : "Linking"}; - stage::set_exit_code(output_mode == OutputMode::lib ? stage::CXX_STAGE_ERROR : stage::LINKING_STAGE_ERROR); + stage::set_exit_code(output_mode == OutputMode::lib ? ExitCode::CPP_TO_OBJS_STAGE : ExitCode::OBJS_TO_BINARY_STAGE); AutoProfiler profiler{get_profiler(build_stage)}; bool ok = make.make_target(&bin_file, build_stage, settings.jobs_count.get()); diff --git a/compiler/stage.cpp b/compiler/stage.cpp index 115ed6690e..31db92603a 100644 --- a/compiler/stage.cpp +++ b/compiler/stage.cpp @@ -59,7 +59,6 @@ void on_compilation_error(const char *description __attribute__((unused)), const fmt_fprintf(file, "Compilation failed.\n" "It is probably happened due to incorrect or unsupported PHP input.\n" "But it is still bug in compiler.\n"); - // TODO should we just call exit() with specific return code ot leave it as is? #ifdef __arm64__ __builtin_debugtrap(); // for easier debugging kphp_assert / kphp_fail #endif @@ -168,7 +167,7 @@ stage::StageInfo *stage::get_stage_info_ptr() { return &*stage_info; } -void stage::set_exit_code(int code) { +void stage::set_exit_code(ExitCode code) { get_stage_info_ptr()->exit_code = code; } @@ -198,7 +197,7 @@ bool stage::has_global_error() { void stage::die_if_global_errors() { if (stage::has_global_error()) { fmt_print("Compilation terminated due to errors\n"); - exit(stage::get_exit_code()); + exit(static_cast(stage::get_exit_code())); } } @@ -206,7 +205,7 @@ const std::string &stage::get_name() { return get_stage_info_ptr()->name; } -int stage::get_exit_code() { +ExitCode stage::get_exit_code() { return get_stage_info_ptr()->exit_code; } diff --git a/compiler/stage.h b/compiler/stage.h index 9909195c3d..de1fea19bf 100644 --- a/compiler/stage.h +++ b/compiler/stage.h @@ -7,6 +7,8 @@ #include #include +#include "common/exit-codes.h" + #include "compiler/data/data_ptr.h" #include "compiler/kphp_assert.h" #include "compiler/location.h" @@ -19,17 +21,12 @@ namespace stage { void set_warning_file(FILE *file) noexcept; -// TODO think about values -constexpr size_t KPHP_STAGE_ERROR = 101; -constexpr size_t CXX_STAGE_ERROR = 102; -constexpr size_t LINKING_STAGE_ERROR = 103; - struct StageInfo { std::string name; Location location; bool global_error_flag{false}; uint32_t cnt_errors{0}; - int exit_code = KPHP_STAGE_ERROR; + ExitCode exit_code = ExitCode::KPHP_TO_CPP_STAGE; }; StageInfo *get_stage_info_ptr(); @@ -48,8 +45,8 @@ void print_current_location_on_error(FILE *f); void set_name(std::string &&name); const std::string &get_name(); -void set_exit_code(int code); -int get_exit_code(); +void set_exit_code(ExitCode code); +ExitCode get_exit_code(); void set_file(SrcFilePtr file); void set_function(FunctionPtr function); From 42ffba55717c3645498813736143b01f86cd6450 Mon Sep 17 00:00:00 2001 From: Mikhail Kornaukhov Date: Tue, 25 Jun 2024 13:16:19 +0300 Subject: [PATCH 13/45] Add a comment in the code --- common/exit-codes.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/common/exit-codes.h b/common/exit-codes.h index 11e6dea3dc..2e8e85dec0 100644 --- a/common/exit-codes.h +++ b/common/exit-codes.h @@ -3,6 +3,11 @@ // Distributed under the GPL v3 License, see LICENSE.notice.txt #pragma once + +// The exit codes were selected according to https://tldp.org/LDP/abs/html/exitcodes.html +// 100, 101, and 102 are free for statuses defined by the programmer and they look pretty +// If the app is going to exit because of a signal, then the return code is 128 + %signal_code%, +// which is also a common practice enum class ExitCode { KPHP_TO_CPP_STAGE = 100, CPP_TO_OBJS_STAGE = 101, From a4d9462382cf00e4e8b49d899af52ea0867538bd Mon Sep 17 00:00:00 2001 From: Vadim Sadokhov <65451602+astrophysik@users.noreply.github.com> Date: Fri, 2 Aug 2024 12:31:08 +0300 Subject: [PATCH 14/45] add reference counter equal to global const for classes (#1053) --- compiler/code-gen/files/const-vars-init.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/code-gen/files/const-vars-init.cpp b/compiler/code-gen/files/const-vars-init.cpp index 51c4f2de0b..39ad9de53d 100644 --- a/compiler/code-gen/files/const-vars-init.cpp +++ b/compiler/code-gen/files/const-vars-init.cpp @@ -113,7 +113,7 @@ void ConstVarsInit::compile_const_init_part(CodeGenerator &W, const ConstantsBat for (VarPtr var: other_const_vars.vars_by_dep_level(dep_level)) { W << InitConstVar(var); const TypeData *type_data = var->tinf_node.get_type(); - if (vk::any_of_equal(type_data->ptype(), tp_array, tp_mixed, tp_string)) { + if (vk::any_of_equal(type_data->ptype(), tp_array, tp_mixed, tp_string, tp_Class)) { W << var->name; if (type_data->use_optional()) { W << ".val()"; From 8ac7ded74c096f1705b16ae2a18621d453899c35 Mon Sep 17 00:00:00 2001 From: Alexander Polyakov Date: Fri, 2 Aug 2024 19:25:47 +0300 Subject: [PATCH 15/45] Runtime light: coroutine scheduler & forks (#1050) Main changes: * stream management: now all operations with streams are performed through ComponentState. ComponentState stores all opened streams, releases unneeded streams, etc; * cancellable awaitables: awaitables that can stop waiting for some event; * forks: task_t is now a handle to a fork. Forks can be started, waited on, and cancelled; * coroutine scheduler: a coroutine scheduler concept and its simple implementation are added. --- .clangd | 6 + builtin-functions/kphp-light/functions.txt | 14 +- compiler/code-gen/declarations.cpp | 7 +- compiler/code-gen/files/init-scripts.cpp | 2 +- compiler/code-gen/vertex-compiler.cpp | 14 +- compiler/data/function-data.h | 1 + compiler/pipes/calc-bad-vars.cpp | 22 +- runtime-core/core-types/decl/optional.h | 13 + .../memory-resource/resource_allocator.h | 23 +- runtime-core/utils/small-object-storage.h | 47 ++++ runtime-light/component/component.cpp | 155 ++++++++---- runtime-light/component/component.h | 91 +++---- runtime-light/core/globals/php-init-scripts.h | 2 +- .../core/globals/php-script-globals.cpp | 1 + runtime-light/coroutine/awaitable.h | 205 ++++++++++++---- runtime-light/coroutine/task.h | 17 +- runtime-light/runtime-light.cmake | 86 ++++--- runtime-light/runtime-light.cpp | 26 +- runtime-light/scheduler/scheduler.cmake | 1 + runtime-light/scheduler/scheduler.cpp | 124 ++++++++++ runtime-light/scheduler/scheduler.h | 133 +++++++++++ runtime-light/stdlib/fork/fork-api.cpp | 24 ++ runtime-light/stdlib/fork/fork-api.h | 45 ++++ runtime-light/stdlib/fork/fork-context.cpp | 12 + runtime-light/stdlib/fork/fork-context.h | 47 ++++ runtime-light/stdlib/fork/fork.h | 27 +++ runtime-light/stdlib/misc.cpp | 81 +++---- runtime-light/stdlib/misc.h | 7 +- runtime-light/stdlib/output-control.cpp | 3 +- runtime-light/stdlib/rpc/rpc-context.cpp | 1 + runtime-light/stdlib/stdlib.cmake | 33 +-- runtime-light/stdlib/string-functions.cpp | 1 + runtime-light/stdlib/superglobals.cpp | 1 + runtime-light/stdlib/superglobals.h | 3 - runtime-light/stdlib/timer/timer.h | 29 +++ runtime-light/stdlib/variable-handling.cpp | 4 +- runtime-light/streams/component-stream.cpp | 67 ++++-- runtime-light/streams/component-stream.h | 27 +-- runtime-light/streams/interface.cpp | 136 +++++------ runtime-light/streams/interface.h | 29 ++- runtime-light/streams/streams.cpp | 225 +++++++++--------- runtime-light/streams/streams.h | 23 +- runtime-light/utils/concepts.h | 7 + runtime-light/utils/panic.h | 15 +- runtime-light/utils/timer.cpp | 21 -- runtime-light/utils/timer.h | 20 -- runtime-light/utils/utils.cmake | 6 +- runtime/storage.cpp | 8 +- runtime/storage.h | 58 +---- tests/k2-components/yield_loop.php | 2 +- 50 files changed, 1325 insertions(+), 627 deletions(-) create mode 100644 .clangd create mode 100644 runtime-core/utils/small-object-storage.h create mode 100644 runtime-light/scheduler/scheduler.cmake create mode 100644 runtime-light/scheduler/scheduler.cpp create mode 100644 runtime-light/scheduler/scheduler.h create mode 100644 runtime-light/stdlib/fork/fork-api.cpp create mode 100644 runtime-light/stdlib/fork/fork-api.h create mode 100644 runtime-light/stdlib/fork/fork-context.cpp create mode 100644 runtime-light/stdlib/fork/fork-context.h create mode 100644 runtime-light/stdlib/fork/fork.h create mode 100644 runtime-light/stdlib/timer/timer.h delete mode 100644 runtime-light/utils/timer.cpp delete mode 100644 runtime-light/utils/timer.h diff --git a/.clangd b/.clangd new file mode 100644 index 0000000000..40962097a3 --- /dev/null +++ b/.clangd @@ -0,0 +1,6 @@ +CompileFlags: + CompilationDatabase: build/ # Search build/ directory for compile_commands.json + +Diagnostics: + Suppress: cppcoreguidelines-avoid-do-while + diff --git a/builtin-functions/kphp-light/functions.txt b/builtin-functions/kphp-light/functions.txt index d92b9dad24..c9f5980d70 100644 --- a/builtin-functions/kphp-light/functions.txt +++ b/builtin-functions/kphp-light/functions.txt @@ -76,6 +76,17 @@ function get_hash_of_class (object $klass) ::: int; function strlen ($str ::: string) ::: int; +// === Fork ======================================================================================= + +/** @kphp-extern-func-info interruptible cpp_template_call */ +function wait(future | false $id, float $timeout = -1.0) ::: ^1[*] | null; + +/** @kphp-extern-func-info interruptible */ +function sched_yield() ::: void; + +/** @kphp-extern-func-info interruptible */ +function sched_yield_sleep($timeout_ns ::: int) ::: void; + // === Rpc ======================================================================================== /** @kphp-tl-class */ @@ -198,8 +209,6 @@ function instance_cast(object $instance, $to_type ::: string) ::: instance<^2>; function make_clone ($x ::: any) ::: ^1; -/** @kphp-extern-func-info interruptible */ -function testyield() ::: void; function check_shutdown() ::: void; function warning($message ::: string) ::: void; @@ -211,4 +220,5 @@ function debug_print_string($str ::: string) ::: void; function byte_to_int($str ::: string) ::: ?int; function int_to_byte($v ::: int) ::: ?string; +/** @kphp-extern-func-info interruptible */ function set_timer(int $timeout, callable():void $callback) ::: void; diff --git a/compiler/code-gen/declarations.cpp b/compiler/code-gen/declarations.cpp index 31a3a97b6e..ecf571c3f7 100644 --- a/compiler/code-gen/declarations.cpp +++ b/compiler/code-gen/declarations.cpp @@ -72,7 +72,9 @@ void FunctionDeclaration::compile(CodeGenerator &W) const { switch (style) { case gen_out_style::tagger: case gen_out_style::cpp: { - if (function->is_interruptible) { + if (function->is_k2_fork) { + FunctionSignatureGenerator(W) << "task_t " << FunctionName(function) << "(" << params_gen << ")"; + } else if (function->is_interruptible) { FunctionSignatureGenerator(W) << "task_t<" << ret_type_gen << ">" << " " << FunctionName(function) << "(" << params_gen << ")"; } else { FunctionSignatureGenerator(W) << ret_type_gen << " " << FunctionName(function) << "(" << params_gen << ")"; @@ -115,7 +117,8 @@ void FunctionParams::declare_cpp_param(CodeGenerator &W, VertexAdaptor v auto var_ptr = var->var_id; if (var->ref_flag) { W << "&"; - } else if (var_ptr->marked_as_const || (!function->has_variadic_param && var_ptr->is_read_only)) { + } else if (!function->is_k2_fork && (var_ptr->marked_as_const || (!function->has_variadic_param && var_ptr->is_read_only))) { + // the top of k2 fork must take arguments by value (see C++ avoid reference parameters in coroutines) W << (!type.type->is_primitive_type() ? "const &" : ""); } W << VarName(var_ptr); diff --git a/compiler/code-gen/files/init-scripts.cpp b/compiler/code-gen/files/init-scripts.cpp index 388e2e6748..282b5bd71f 100644 --- a/compiler/code-gen/files/init-scripts.cpp +++ b/compiler/code-gen/files/init-scripts.cpp @@ -226,7 +226,7 @@ void InitScriptsCpp::compile(CodeGenerator &W) const { W << GlobalsResetFunction(main_file_id->main_function) << NL; if (G->is_output_mode_k2_component()) { - FunctionSignatureGenerator(W) << "void init_php_scripts_in_each_worker(" << PhpMutableGlobalsRefArgument() << ", task_t&run" ")" << BEGIN; + FunctionSignatureGenerator(W) << "void init_php_scripts_in_each_worker(" << PhpMutableGlobalsRefArgument() << ", task_t &run" ")" << BEGIN; } else { FunctionSignatureGenerator(W) << "void init_php_scripts_in_each_worker(" << PhpMutableGlobalsRefArgument() << ")" << BEGIN; } diff --git a/compiler/code-gen/vertex-compiler.cpp b/compiler/code-gen/vertex-compiler.cpp index 71ed0feed1..826e2456ab 100644 --- a/compiler/code-gen/vertex-compiler.cpp +++ b/compiler/code-gen/vertex-compiler.cpp @@ -844,7 +844,11 @@ void compile_func_call(VertexAdaptor root, CodeGenerator &W, func_ if (mode == func_call_mode::fork_call) { - W << FunctionForkName(func); + if (func->is_interruptible) { + W << "(co_await start_fork_and_reschedule_t{" << FunctionName(func); + } else { + W << FunctionForkName(func); + } } else { if (func->is_interruptible) { W << "(" << "co_await "; @@ -874,7 +878,13 @@ void compile_func_call(VertexAdaptor root, CodeGenerator &W, func_ W << JoinValues(args, ", "); W << ")"; if (func->is_interruptible) { - W << ")"; + if (mode == func_call_mode::fork_call) { + W << "})"; + } else if (func->is_k2_fork) { // k2 fork's return type is 'task_t' so we need to unpack actual result from fork_result + W << ").get_result<" << TypeName(tinf::get_type(root)) << ">()"; + } else { + W << ")"; + } } } diff --git a/compiler/data/function-data.h b/compiler/data/function-data.h index 3962e49b62..1a3662a74c 100644 --- a/compiler/data/function-data.h +++ b/compiler/data/function-data.h @@ -118,6 +118,7 @@ class FunctionData { bool cpp_variadic_call = false; bool is_resumable = false; bool is_interruptible = false; + bool is_k2_fork = false; bool can_be_implicitly_interrupted_by_other_resumable = false; bool is_virtual_method = false; bool is_overridden_method = false; diff --git a/compiler/pipes/calc-bad-vars.cpp b/compiler/pipes/calc-bad-vars.cpp index 032dec6f69..cac43595d2 100644 --- a/compiler/pipes/calc-bad-vars.cpp +++ b/compiler/pipes/calc-bad-vars.cpp @@ -2,13 +2,16 @@ // Copyright (c) 2020 LLC «V Kontakte» // Distributed under the GPL v3 License, see LICENSE.notice.txt -#include #include "compiler/pipes/calc-bad-vars.h" +#include +#include + #include "compiler/compiler-core.h" #include "compiler/data/class-data.h" #include "compiler/data/src-file.h" #include "compiler/function-pass.h" +#include "compiler/pipes/calc-func-dep.h" #include "compiler/utils/idmap.h" /*** Common algorithm ***/ @@ -548,6 +551,15 @@ class CalcBadVars { } } + static void calc_k2_fork(const FuncCallGraph &call_graph, const std::vector &dep_data) { + for (int i = 0; i < call_graph.n; ++i) { + for (const auto &fork : dep_data[i].forks) { + fork->is_interruptible = true; + fork->is_k2_fork = true; + } + } + } + static void calc_resumable(const FuncCallGraph &call_graph, const std::vector &dep_data) { for (int i = 0; i < call_graph.n; i++) { for (const auto &fork : dep_data[i].forks) { @@ -684,8 +696,12 @@ class CalcBadVars { { FuncCallGraph call_graph(std::move(functions), dep_datas); - calc_interruptible(call_graph); - calc_resumable(call_graph, dep_datas); + if (G->is_output_mode_k2_component()) { + calc_k2_fork(call_graph, dep_datas); + calc_interruptible(call_graph); + } else { + calc_resumable(call_graph, dep_datas); + } generate_bad_vars(call_graph, dep_datas); check_func_colors(call_graph); save_func_dep(call_graph); diff --git a/runtime-core/core-types/decl/optional.h b/runtime-core/core-types/decl/optional.h index 69dbde2a31..925549c56a 100644 --- a/runtime-core/core-types/decl/optional.h +++ b/runtime-core/core-types/decl/optional.h @@ -203,3 +203,16 @@ using enable_if_t_is_optional_t2 = std::enable_if_t template using enable_if_t_is_optional_string = enable_if_t_is_optional_t2; + +template +struct InternalOptionalType { + using type = T; +}; + +template +struct InternalOptionalType> { + using type = T; +}; + +template +using internal_optional_type_t = typename InternalOptionalType::type; diff --git a/runtime-core/memory-resource/resource_allocator.h b/runtime-core/memory-resource/resource_allocator.h index a4bab87535..eca8f3b1a1 100644 --- a/runtime-core/memory-resource/resource_allocator.h +++ b/runtime-core/memory-resource/resource_allocator.h @@ -3,9 +3,12 @@ // Distributed under the GPL v3 License, see LICENSE.notice.txt #pragma once + +#include #include #include #include +#include #include "common/wrappers/likely.h" @@ -19,19 +22,16 @@ class resource_allocator { using value_type = T; template - friend - class resource_allocator; + friend class resource_allocator; - explicit resource_allocator(MemoryResource &memory_resource) noexcept: - memory_resource_(memory_resource) { - } + explicit resource_allocator(MemoryResource &memory_resource) noexcept + : memory_resource_(memory_resource) {} template - explicit resource_allocator(const resource_allocator &other) noexcept: - memory_resource_(other.memory_resource_) { - } + explicit resource_allocator(const resource_allocator &other) noexcept + : memory_resource_(other.memory_resource_) {} - value_type *allocate(size_t size, void const * = nullptr) { + value_type *allocate(size_t size, [[maybe_unused]] void const *ptr = nullptr) { static_assert(sizeof(value_type) <= max_value_type_size(), "memory limit"); auto result = static_cast(memory_resource_.allocate(sizeof(value_type) * size)); if (unlikely(!result)) { @@ -46,7 +46,7 @@ class resource_allocator { } static constexpr size_t max_value_type_size() { - return 128u; + return 128U; } friend inline bool operator==(const resource_allocator &lhs, const resource_allocator &rhs) noexcept { @@ -65,6 +65,9 @@ namespace stl { template, class KeyEqual = std::equal_to> using unordered_map = std::unordered_map, Resource>>; +template, class KeyEqual = std::equal_to> +using unordered_set = std::unordered_set>; + template> using map = std::map, Resource>>; diff --git a/runtime-core/utils/small-object-storage.h b/runtime-core/utils/small-object-storage.h new file mode 100644 index 0000000000..2ea08dbfe7 --- /dev/null +++ b/runtime-core/utils/small-object-storage.h @@ -0,0 +1,47 @@ +// Compiler for PHP (aka KPHP) +// Copyright (c) 2020 LLC «V Kontakte» +// Distributed under the GPL v3 License, see LICENSE.notice.txt + +#pragma once + +#include +#include +#include +#include + +#include "runtime-core/runtime-core.h" + +template +union small_object_storage { + std::array storage_; + void *storage_ptr; + + template + std::enable_if_t emplace(Args &&...args) noexcept { + return new (storage_.data()) T(std::forward(args)...); + } + template + std::enable_if_t get() noexcept { + return reinterpret_cast(storage_.data()); + } + template + std::enable_if_t destroy() noexcept { + get()->~T(); + } + + template + std::enable_if_t < limit emplace(Args &&...args) noexcept { + storage_ptr = RuntimeAllocator::current().alloc_script_memory(sizeof(T)); + return new (storage_ptr) T(std::forward(args)...); + } + template + std::enable_if_t < limit get() noexcept { + return static_cast(storage_ptr); + } + template + std::enable_if_t < limit destroy() noexcept { + T *mem = get(); + mem->~T(); + RuntimeAllocator::current().free_script_memory(mem, sizeof(T)); + } +}; diff --git a/runtime-light/component/component.cpp b/runtime-light/component/component.cpp index 72f282f749..e1a81c95e1 100644 --- a/runtime-light/component/component.cpp +++ b/runtime-light/component/component.cpp @@ -3,64 +3,137 @@ // Distributed under the GPL v3 License, see LICENSE.notice.txt #include "runtime-light/component/component.h" + +#include +#include +#include + +#include "runtime-core/utils/kphp-assert-core.h" #include "runtime-light/core/globals/php-init-scripts.h" +#include "runtime-light/header.h" +#include "runtime-light/scheduler/scheduler.h" +#include "runtime-light/utils/context.h" -void ComponentState::resume_if_was_rescheduled() { - if (poll_status == PollStatus::PollReschedule) { - // If component was suspended by please yield and there is no awaitable streams - main_thread(); - } +void ComponentState::init_script_execution() noexcept { + kphp_core_context.init(); + init_php_scripts_in_each_worker(php_script_mutable_globals_singleton, main_task); + scheduler.suspend(std::make_pair(main_task.get_handle(), WaitEvent::Rechedule{})); } -bool ComponentState::is_stream_already_being_processed(uint64_t stream_d) { - return opened_streams.contains(stream_d); -} +void ComponentState::process_platform_updates() noexcept { + const auto &platform_ctx{*get_platform_context()}; + + for (;;) { + // check if platform asked for yield + if (static_cast(platform_ctx.please_yield.load())) { // tell the scheduler that we are about to yield + const auto schedule_status{scheduler.schedule(ScheduleEvent::Yield{})}; + poll_status = schedule_status == ScheduleStatus::Error ? PollStatus::PollFinishedError : PollStatus::PollReschedule; + return; + } -void ComponentState::resume_if_wait_stream(uint64_t stream_d, StreamStatus status) { - if (is_stream_timer(stream_d)) { - process_timer(stream_d); - } else { - process_stream(stream_d, status); + // try taking update from the platform + if (uint64_t stream_d{}; static_cast(platform_ctx.take_update(std::addressof(stream_d)))) { + if (opened_streams_.contains(stream_d)) { // update on opened stream + switch (scheduler.schedule(ScheduleEvent::UpdateOnStream{.stream_d = stream_d})) { + case ScheduleStatus::Resumed: { // scheduler's resumed a coroutine waiting for update + break; + } + case ScheduleStatus::Skipped: { // no one is waiting for the event yet, so just save it + pending_updates_.insert(stream_d); + break; + } + case ScheduleStatus::Error: { // something bad's happened, stop execution + poll_status = PollStatus::PollFinishedError; + return; + } + } + } else { // update on incoming stream + if (standard_stream_ != INVALID_PLATFORM_DESCRIPTOR) { + php_warning("skip new incoming stream since previous one is not closed"); + release_stream(stream_d); + continue; + } // TODO: multiple incoming streams (except for http queries) + standard_stream_ = stream_d; + incoming_streams_.push_back(stream_d); + opened_streams_.insert(stream_d); + if (const auto schedule_status{scheduler.schedule(ScheduleEvent::IncomingStream{.stream_d = stream_d})}; schedule_status == ScheduleStatus::Error) { + poll_status = PollStatus::PollFinishedError; + return; + } + } + } else { // we'are out of updates so let the scheduler do whatever it wants + switch (scheduler.schedule(ScheduleEvent::NoEvent{})) { + case ScheduleStatus::Resumed: { // scheduler's resumed some coroutine, so let's continue scheduling + break; + } + case ScheduleStatus::Skipped: { // scheduler's done nothing, so it's either scheduled all coroutines or is waiting for events + poll_status = scheduler.done() ? PollStatus::PollFinishedOk : PollStatus::PollBlocked; + return; + } + case ScheduleStatus::Error: { // something bad's happened, stop execution + poll_status = PollStatus::PollFinishedError; + return; + } + } + } } + // unreachable code + poll_status = PollStatus::PollFinishedError; } -void ComponentState::process_new_input_stream(uint64_t stream_d) { - bool already_pending = std::find(incoming_pending_queries.begin(), incoming_pending_queries.end(), stream_d) != incoming_pending_queries.end(); - if (!already_pending) { - php_debug("got new pending query %lu", stream_d); - incoming_pending_queries.push_back(stream_d); - } - if (wait_incoming_stream) { - php_debug("start process pending query %lu", stream_d); - main_thread(); +uint64_t ComponentState::take_incoming_stream() noexcept { + if (incoming_streams_.empty()) { + php_warning("can't take incoming stream cause we don't have them"); + return INVALID_PLATFORM_DESCRIPTOR; } + const auto stream_d{incoming_streams_.front()}; + incoming_streams_.pop_front(); + php_debug("take incoming stream %" PRIu64, stream_d); + return stream_d; } -void ComponentState::init_script_execution() { - kphp_core_context.init(); - init_php_scripts_in_each_worker(php_script_mutable_globals_singleton, k_main); - main_thread = k_main.get_handle(); +uint64_t ComponentState::open_stream(const string &component_name) noexcept { + uint64_t stream_d{}; + if (const auto open_stream_res{get_platform_context()->open(component_name.size(), component_name.c_str(), std::addressof(stream_d))}; + open_stream_res != OpenStreamResult::OpenStreamOk) { + php_warning("can't open stream to %s", component_name.c_str()); + return INVALID_PLATFORM_DESCRIPTOR; + } + opened_streams_.insert(stream_d); + php_debug("opened a stream %" PRIu64 " to %s", stream_d, component_name.c_str()); + return stream_d; } -bool ComponentState::is_stream_timer(uint64_t stream_d) { - return timer_callbacks.contains(stream_d); +uint64_t ComponentState::set_timer(std::chrono::nanoseconds duration) noexcept { + uint64_t timer_d{}; + if (const auto set_timer_res{get_platform_context()->set_timer(std::addressof(timer_d), static_cast(duration.count()))}; + set_timer_res != SetTimerResult::SetTimerOk) { + php_warning("can't set timer for %.9f sec", std::chrono::duration(duration).count()); + return INVALID_PLATFORM_DESCRIPTOR; + } + opened_streams_.insert(timer_d); + php_debug("set timer %" PRIu64 " for %.9f sec", timer_d, std::chrono::duration(duration).count()); + return timer_d; } -void ComponentState::process_timer(uint64_t stream_d) { +void ComponentState::release_stream(uint64_t stream_d) noexcept { + if (stream_d == standard_stream_) { + standard_stream_ = INVALID_PLATFORM_DESCRIPTOR; + } + opened_streams_.erase(stream_d); + pending_updates_.erase(stream_d); // also erase pending updates if exists get_platform_context()->free_descriptor(stream_d); - timer_callbacks[stream_d](); - timer_callbacks.erase(stream_d); - opened_streams.erase(stream_d); + php_debug("released a stream %" PRIu64, stream_d); } -void ComponentState::process_stream(uint64_t stream_d, StreamStatus status) { - auto expected_status = opened_streams[stream_d]; - if ((expected_status == StreamRuntimeStatus::WBlocked && status.write_status != IOBlocked) - || (expected_status == StreamRuntimeStatus::RBlocked && status.read_status != IOBlocked)) { - php_debug("resume on waited query %lu", stream_d); - auto suspend_point = awaiting_coroutines[stream_d]; - awaiting_coroutines.erase(stream_d); - php_assert(awaiting_coroutines.empty()); - suspend_point(); +void ComponentState::release_all_streams() noexcept { + const auto &platform_ctx{*get_platform_context()}; + standard_stream_ = INVALID_PLATFORM_DESCRIPTOR; + for (const auto stream_d : opened_streams_) { + platform_ctx.free_descriptor(stream_d); + php_debug("released a stream %" PRIu64, stream_d); } + opened_streams_.clear(); + pending_updates_.clear(); + incoming_streams_.clear(); } diff --git a/runtime-light/component/component.h b/runtime-light/component/component.h index 7057f6dd5a..65a9ca173d 100644 --- a/runtime-light/component/component.h +++ b/runtime-light/component/component.h @@ -4,78 +4,87 @@ #pragma once -#include +#include #include #include -#include -#include +#include #include "runtime-core/memory-resource/resource_allocator.h" #include "runtime-core/memory-resource/unsynchronized_pool_resource.h" #include "runtime-core/runtime-core.h" - #include "runtime-light/core/globals/php-script-globals.h" #include "runtime-light/coroutine/task.h" +#include "runtime-light/header.h" +#include "runtime-light/scheduler/scheduler.h" +#include "runtime-light/stdlib/fork/fork-context.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" + +constexpr uint64_t INVALID_PLATFORM_DESCRIPTOR = 0; + +// Coroutine scheduler type. Change it here if you want to use another scheduler +using CoroutineScheduler = SimpleCoroutineScheduler; +static_assert(CoroutineSchedulerConcept); struct ComponentState { - template - using unordered_map = memory_resource::stl::unordered_map; + template + using unordered_set = memory_resource::stl::unordered_set; + template using deque = memory_resource::stl::deque; - static constexpr auto INIT_RUNTIME_ALLOCATOR_SIZE = static_cast(512U * 1024U); // 512KB - ComponentState() + ComponentState() noexcept : runtime_allocator(INIT_RUNTIME_ALLOCATOR_SIZE, 0) + , scheduler(runtime_allocator.memory_resource) + , fork_component_context(runtime_allocator.memory_resource) , php_script_mutable_globals_singleton(runtime_allocator.memory_resource) - , 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}) - , rpc_component_context(runtime_allocator.memory_resource) {} + , rpc_component_context(runtime_allocator.memory_resource) + , incoming_streams_(deque::allocator_type{runtime_allocator.memory_resource}) + , opened_streams_(unordered_set::allocator_type{runtime_allocator.memory_resource}) + , pending_updates_(unordered_set::allocator_type{runtime_allocator.memory_resource}) {} ~ComponentState() = default; - bool not_finished() const noexcept { - return poll_status != PollStatus::PollFinishedOk && poll_status != PollStatus::PollFinishedError; - } - - void resume_if_was_rescheduled(); - - bool is_stream_already_being_processed(uint64_t stream_d); - - void resume_if_wait_stream(uint64_t stream_d, StreamStatus status); + void init_script_execution() noexcept; + void process_platform_updates() noexcept; - void process_new_input_stream(uint64_t stream_d); - - void init_script_execution(); + bool stream_updated(uint64_t stream_d) const noexcept { + return pending_updates_.contains(stream_d); + } + const unordered_set &opened_streams() const noexcept { + return opened_streams_; + } + const deque &incoming_streams() const noexcept { + return incoming_streams_; + } + uint64_t standard_stream() const noexcept { + return standard_stream_; + } + uint64_t take_incoming_stream() noexcept; + uint64_t open_stream(const string &) noexcept; + uint64_t set_timer(std::chrono::nanoseconds) noexcept; + void release_stream(uint64_t) noexcept; + void release_all_streams() noexcept; RuntimeAllocator runtime_allocator; - task_t k_main; - Response response; - PhpScriptMutableGlobals php_script_mutable_globals_singleton; + CoroutineScheduler scheduler; + ForkComponentContext fork_component_context; PollStatus poll_status = PollStatus::PollReschedule; - uint64_t standard_stream = 0; - std::coroutine_handle<> main_thread; - bool wait_incoming_stream = false; - unordered_map opened_streams; // подумать про необходимость opened_streams. Объединить с awaiting_coroutines - unordered_map> awaiting_coroutines; - unordered_map> timer_callbacks; - deque incoming_pending_queries; + Response response; + PhpScriptMutableGlobals php_script_mutable_globals_singleton; KphpCoreContext kphp_core_context; RpcComponentContext rpc_component_context; private: - bool is_stream_timer(uint64_t stream_d); + task_t main_task; - void process_timer(uint64_t stream_d); + uint64_t standard_stream_{INVALID_PLATFORM_DESCRIPTOR}; + deque incoming_streams_; + unordered_set opened_streams_; + unordered_set pending_updates_; - void process_stream(uint64_t stream_d, StreamStatus status); + static constexpr auto INIT_RUNTIME_ALLOCATOR_SIZE = static_cast(512U * 1024U); // 512KB }; diff --git a/runtime-light/core/globals/php-init-scripts.h b/runtime-light/core/globals/php-init-scripts.h index 6acf8111cc..f9530da589 100644 --- a/runtime-light/core/globals/php-init-scripts.h +++ b/runtime-light/core/globals/php-init-scripts.h @@ -4,7 +4,7 @@ #pragma once -#include +#include "runtime-light/coroutine/task.h" class PhpScriptMutableGlobals; diff --git a/runtime-light/core/globals/php-script-globals.cpp b/runtime-light/core/globals/php-script-globals.cpp index d9f2421d82..afd86c307f 100644 --- a/runtime-light/core/globals/php-script-globals.cpp +++ b/runtime-light/core/globals/php-script-globals.cpp @@ -5,6 +5,7 @@ #include "php-script-globals.h" #include "runtime-light/component/component.h" +#include "runtime-light/utils/context.h" PhpScriptMutableGlobals &PhpScriptMutableGlobals::current() noexcept { return get_component_context()->php_script_mutable_globals_singleton; diff --git a/runtime-light/coroutine/awaitable.h b/runtime-light/coroutine/awaitable.h index e239cd559d..f468796117 100644 --- a/runtime-light/coroutine/awaitable.h +++ b/runtime-light/coroutine/awaitable.h @@ -4,75 +4,198 @@ #pragma once +#include +#include #include +#include +#include +#include +#include "runtime-core/core-types/decl/optional.h" +#include "runtime-core/utils/kphp-assert-core.h" #include "runtime-light/component/component.h" -#include "runtime-light/utils/logs.h" +#include "runtime-light/coroutine/task.h" +#include "runtime-light/stdlib/fork/fork-context.h" +#include "runtime-light/stdlib/fork/fork.h" +#include "runtime-light/header.h" +#include "runtime-light/scheduler/scheduler.h" +#include "runtime-light/utils/context.h" -struct blocked_operation_t { - uint64_t awaited_stream; +template +concept Awaitable = requires(T && awaitable, std::coroutine_handle<> coro) { + { awaitable.await_ready() } noexcept -> std::convertible_to; + { awaitable.await_suspend(coro) } noexcept; + { awaitable.await_resume() } noexcept; +}; - blocked_operation_t(uint64_t stream_d) - : awaited_stream(stream_d) {} +template +concept CancellableAwaitable = Awaitable && requires(T && awaitable) { + { awaitable.cancel() } noexcept -> std::same_as; +}; - constexpr bool await_ready() const noexcept { - return false; +// === Awaitables ================================================================================= + +class wait_for_update_t { + uint64_t stream_d; + SuspendToken suspend_token_; + +public: + explicit wait_for_update_t(uint64_t stream_d_) noexcept + : stream_d(stream_d_) + , suspend_token_(std::noop_coroutine(), WaitEvent::UpdateOnStream{.stream_d = stream_d}) {} + + bool await_ready() const noexcept { + return get_component_context()->stream_updated(stream_d); } - void await_resume() const noexcept { - ComponentState &ctx = *get_component_context(); - ctx.opened_streams[awaited_stream] = StreamRuntimeStatus::NotBlocked; + void await_suspend(std::coroutine_handle<> coro) noexcept { + suspend_token_.first = coro; + CoroutineScheduler::get().suspend(suspend_token_); } -}; -struct read_blocked_t : blocked_operation_t { - void await_suspend(std::coroutine_handle<> h) const noexcept { - php_debug("blocked read on stream %lu", awaited_stream); - ComponentState &ctx = *get_component_context(); - ctx.poll_status = PollStatus::PollBlocked; - ctx.opened_streams[awaited_stream] = StreamRuntimeStatus::RBlocked; - ctx.awaiting_coroutines[awaited_stream] = h; + constexpr void await_resume() const noexcept {} + + void cancel() const noexcept { + CoroutineScheduler::get().cancel(suspend_token_); } }; -struct write_blocked_t : blocked_operation_t { - void await_suspend(std::coroutine_handle<> h) const noexcept { - php_debug("blocked write on stream %lu", awaited_stream); - ComponentState &ctx = *get_component_context(); - ctx.poll_status = PollStatus::PollBlocked; - ctx.opened_streams[awaited_stream] = StreamRuntimeStatus::WBlocked; - ctx.awaiting_coroutines[awaited_stream] = h; +// ================================================================================================ + +class wait_for_incoming_stream_t { + SuspendToken suspend_token_{std::noop_coroutine(), WaitEvent::IncomingStream{}}; + +public: + bool await_ready() const noexcept { + return !get_component_context()->incoming_streams().empty(); + } + + void await_suspend(std::coroutine_handle<> coro) noexcept { + suspend_token_.first = coro; + CoroutineScheduler::get().suspend(suspend_token_); + } + + uint64_t await_resume() const noexcept { + const auto incoming_stream_d{get_component_context()->take_incoming_stream()}; + php_assert(incoming_stream_d != INVALID_PLATFORM_DESCRIPTOR); + return incoming_stream_d; + } + + void cancel() const noexcept { + CoroutineScheduler::get().cancel(suspend_token_); } }; -struct test_yield_t { - bool await_ready() const noexcept { - return !get_platform_context()->please_yield.load(); +// ================================================================================================ + +class wait_for_reschedule_t { + SuspendToken suspend_token_{std::noop_coroutine(), WaitEvent::Rechedule{}}; + +public: + constexpr bool await_ready() const noexcept { + return false; } - void await_suspend(std::coroutine_handle<> h) const noexcept { - ComponentState &ctx = *get_component_context(); - ctx.poll_status = PollStatus::PollReschedule; - ctx.main_thread = h; + void await_suspend(std::coroutine_handle<> coro) noexcept { + suspend_token_.first = coro; + CoroutineScheduler::get().suspend(suspend_token_); } constexpr void await_resume() const noexcept {} + + void cancel() const noexcept { + CoroutineScheduler::get().cancel(suspend_token_); + } }; -struct wait_incoming_query_t { +// ================================================================================================ + +class wait_for_timer_t { + uint64_t timer_d{}; + SuspendToken suspend_token_; + +public: + explicit wait_for_timer_t(std::chrono::nanoseconds duration) noexcept + : timer_d(get_component_context()->set_timer(duration)) + , suspend_token_(std::noop_coroutine(), WaitEvent::UpdateOnTimer{.timer_d = timer_d}) {} + bool await_ready() const noexcept { - return !get_component_context()->incoming_pending_queries.empty(); + TimePoint tp{}; + return timer_d == INVALID_PLATFORM_DESCRIPTOR || get_platform_context()->get_timer_status(timer_d, std::addressof(tp)) == TimerStatus::TimerStatusElapsed; } - void await_suspend(std::coroutine_handle<> h) const noexcept { - ComponentState &ctx = *get_component_context(); - php_assert(ctx.standard_stream == 0); - ctx.main_thread = h; - ctx.wait_incoming_stream = true; - ctx.poll_status = PollBlocked; + void await_suspend(std::coroutine_handle<> coro) noexcept { + suspend_token_.first = coro; + CoroutineScheduler::get().suspend(suspend_token_); } void await_resume() const noexcept { - get_component_context()->wait_incoming_stream = false; + get_component_context()->release_stream(timer_d); + } + + void cancel() const noexcept { + get_component_context()->release_stream(timer_d); + CoroutineScheduler::get().cancel(suspend_token_); + } +}; + +// ================================================================================================ + +class start_fork_and_reschedule_t { + std::coroutine_handle<> fork_coro; + int64_t fork_id{}; + SuspendToken suspend_token_{std::noop_coroutine(), WaitEvent::Rechedule{}}; + +public: + explicit start_fork_and_reschedule_t(task_t &&task_) noexcept + : fork_coro(task_.get_handle()) + , fork_id(ForkComponentContext::get().push_fork(std::move(task_))) {} + + constexpr bool await_ready() const noexcept { + return false; + } + + std::coroutine_handle<> await_suspend(std::coroutine_handle<> current_coro) noexcept { + suspend_token_.first = current_coro; + CoroutineScheduler::get().suspend(suspend_token_); + return fork_coro; + } + + int64_t await_resume() const noexcept { + return fork_id; + } +}; + +// ================================================================================================ + +template +class wait_fork_t { + task_t task; + wait_for_timer_t timer_awaiter; + task_t::awaiter_t fork_awaiter; + +public: + wait_fork_t(task_t &&task_, std::chrono::nanoseconds timeout_) noexcept + : task(std::move(task_)) + , timer_awaiter(timeout_) + , fork_awaiter(std::addressof(task)) {} + + bool await_ready() const noexcept { + return task.done(); + } + + void await_suspend(std::coroutine_handle<> coro) noexcept { + fork_awaiter.await_suspend(coro); + timer_awaiter.await_suspend(coro); + } + + Optional await_resume() noexcept { + if (task.done()) { + timer_awaiter.cancel(); + return {fork_awaiter.await_resume().get_result()}; + } else { + fork_awaiter.cancel(); + return {}; + } } }; diff --git a/runtime-light/coroutine/task.h b/runtime-light/coroutine/task.h index cc4b990e0c..b9c13b7cf6 100644 --- a/runtime-light/coroutine/task.h +++ b/runtime-light/coroutine/task.h @@ -10,6 +10,7 @@ #include #include "common/containers/final_action.h" +#include "runtime-core/utils/kphp-assert-core.h" #include "runtime-light/utils/context.h" #if __clang_major__ > 7 @@ -108,7 +109,7 @@ struct task_t : public task_base_t { std::exception_ptr exception; static task_t get_return_object_on_allocation_failure() { - throw std::bad_alloc(); + php_critical_error("cannot allocate memory for task_t"); } template @@ -143,14 +144,14 @@ struct task_t : public task_base_t { get_handle().resume(); } - T get_result() { + T get_result() noexcept { if (get_handle().promise().exception) { std::rethrow_exception(std::move(get_handle().promise().exception)); } 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 *t; + return std::move(*t); } } @@ -171,7 +172,7 @@ struct task_t : public task_base_t { explicit awaiter_t(task_t *task) : task{task} {} - bool await_ready() const { + constexpr bool await_ready() const noexcept { return false; } @@ -181,7 +182,7 @@ struct task_t : public task_base_t { #else bool #endif - await_suspend(std::coroutine_handle h) { + await_suspend(std::coroutine_handle h) noexcept { #ifdef CPPCORO_COMPILER_SUPPORTS_SYMMETRIC_TRANSFER task->get_handle().promise().next = h.address(); return task->get_handle(); @@ -197,10 +198,14 @@ struct task_t : public task_base_t { #endif } - T await_resume() { + T await_resume() noexcept { return task->get_result(); } + void cancel() const noexcept { + task->get_handle().promise().next = nullptr; + } + task_t *task; }; diff --git a/runtime-light/runtime-light.cmake b/runtime-light/runtime-light.cmake index 6100395c54..7487d40ee7 100644 --- a/runtime-light/runtime-light.cmake +++ b/runtime-light/runtime-light.cmake @@ -1,5 +1,6 @@ include(${RUNTIME_LIGHT_DIR}/allocator/allocator.cmake) include(${RUNTIME_LIGHT_DIR}/core/core.cmake) +include(${RUNTIME_LIGHT_DIR}/scheduler/scheduler.cmake) include(${RUNTIME_LIGHT_DIR}/stdlib/stdlib.cmake) include(${RUNTIME_LIGHT_DIR}/streams/streams.cmake) include(${RUNTIME_LIGHT_DIR}/tl/tl.cmake) @@ -7,41 +8,48 @@ include(${RUNTIME_LIGHT_DIR}/utils/utils.cmake) include(${RUNTIME_LIGHT_DIR}/component/component.cmake) include(${RUNTIME_LIGHT_DIR}/memory-resource-impl/memory-resource-impl.cmake) -set(RUNTIME_LIGHT_SRC ${RUNTIME_CORE_SRC} - ${RUNTIME_STDLIB_SRC} - ${RUNTIME_ALLOCATOR_SRC} - ${RUNTIME_COROUTINE_SRC} - ${RUNTIME_COMPONENT_SRC} - ${RUNTIME_STREAMS_SRC} - ${RUNTIME_TL_SRC} - ${RUNTIME_UTILS_SRC} - ${RUNTIME_LANGUAGE_SRC} - ${RUNTIME_MEMORY_RESOURCE_IMPL_SRC} - runtime-light.cpp) +set(RUNTIME_LIGHT_SRC + ${RUNTIME_CORE_SRC} + ${RUNTIME_STDLIB_SRC} + ${RUNTIME_SCHEDULER_SRC} + ${RUNTIME_ALLOCATOR_SRC} + ${RUNTIME_COROUTINE_SRC} + ${RUNTIME_COMPONENT_SRC} + ${RUNTIME_STREAMS_SRC} + ${RUNTIME_TL_SRC} + ${RUNTIME_UTILS_SRC} + ${RUNTIME_LANGUAGE_SRC} + ${RUNTIME_MEMORY_RESOURCE_IMPL_SRC} + runtime-light.cpp) set(RUNTIME_SOURCES_FOR_COMP "${RUNTIME_LIGHT_SRC}") -configure_file(${BASE_DIR}/compiler/runtime_sources.h.in ${AUTO_DIR}/compiler/runtime_sources.h) +configure_file(${BASE_DIR}/compiler/runtime_sources.h.in + ${AUTO_DIR}/compiler/runtime_sources.h) prepend(RUNTIME_LIGHT_SRC ${RUNTIME_LIGHT_DIR}/ "${RUNTIME_LIGHT_SRC}") vk_add_library(runtime-light OBJECT ${RUNTIME_LIGHT_SRC}) set_property(TARGET runtime-light PROPERTY POSITION_INDEPENDENT_CODE ON) -set_target_properties(runtime-light PROPERTIES - LIBRARY_OUTPUT_DIRECTORY ${BASE_DIR}/objs) +set_target_properties(runtime-light PROPERTIES LIBRARY_OUTPUT_DIRECTORY + ${BASE_DIR}/objs) target_compile_options(runtime-light PUBLIC -stdlib=libc++) target_link_options(runtime-light PUBLIC -stdlib=libc++ -static-libstdc++) vk_add_library(kphp-light-runtime STATIC) -target_link_libraries(kphp-light-runtime PUBLIC vk::light_common vk::runtime-light vk::runtime-core) -set_target_properties(kphp-light-runtime PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${OBJS_DIR}) +target_link_libraries( + kphp-light-runtime PUBLIC vk::light_common vk::runtime-light vk::runtime-core) +set_target_properties(kphp-light-runtime PROPERTIES ARCHIVE_OUTPUT_DIRECTORY + ${OBJS_DIR}) -file(GLOB_RECURSE KPHP_RUNTIME_ALL_HEADERS - RELATIVE ${BASE_DIR} - CONFIGURE_DEPENDS - "${RUNTIME_LIGHT_DIR}/*.h") +file( + GLOB_RECURSE KPHP_RUNTIME_ALL_HEADERS + RELATIVE ${BASE_DIR} + CONFIGURE_DEPENDS "${RUNTIME_LIGHT_DIR}/*.h") list(TRANSFORM KPHP_RUNTIME_ALL_HEADERS REPLACE "^(.+)$" [[#include "\1"]]) list(JOIN KPHP_RUNTIME_ALL_HEADERS "\n" MERGED_RUNTIME_HEADERS) -file(WRITE ${AUTO_DIR}/runtime/runtime-headers.h "\ +file( + WRITE ${AUTO_DIR}/runtime/runtime-headers.h + "\ #ifndef MERGED_RUNTIME_LIGHT_HEADERS_H #define MERGED_RUNTIME_LIGHT_HEADERS_H @@ -50,25 +58,37 @@ ${MERGED_RUNTIME_HEADERS} #endif ") -file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/php_lib_version.cpp - [[ +file( + WRITE ${CMAKE_CURRENT_BINARY_DIR}/php_lib_version.cpp + [[ #include "auto/runtime/runtime-headers.h" ]]) -add_library(php_lib_version_j OBJECT ${CMAKE_CURRENT_BINARY_DIR}/php_lib_version.cpp) +add_library(php_lib_version_j OBJECT + ${CMAKE_CURRENT_BINARY_DIR}/php_lib_version.cpp) target_compile_options(php_lib_version_j PRIVATE -I. -E) add_dependencies(php_lib_version_j kphp-light-runtime) -add_custom_command(OUTPUT ${OBJS_DIR}/php_lib_version.sha256 - COMMAND tail -n +3 $ | sha256sum | awk '{print $$1}' > ${OBJS_DIR}/php_lib_version.sha256 - DEPENDS php_lib_version_j $ - COMMENT "php_lib_version.sha256 generation") +add_custom_command( + OUTPUT ${OBJS_DIR}/php_lib_version.sha256 + COMMAND tail -n +3 $ | sha256sum | awk + '{print $$1}' > ${OBJS_DIR}/php_lib_version.sha256 + DEPENDS php_lib_version_j $ + COMMENT "php_lib_version.sha256 generation") -add_custom_target(php_lib_version_sha_256 DEPENDS ${OBJS_DIR}/php_lib_version.sha256) +add_custom_target(php_lib_version_sha_256 + DEPENDS ${OBJS_DIR}/php_lib_version.sha256) -get_property(RUNTIME_COMPILE_FLAGS TARGET runtime-light PROPERTY COMPILE_OPTIONS) -get_property(RUNTIME_INCLUDE_DIRS TARGET runtime-light PROPERTY INCLUDE_DIRECTORIES) +get_property( + RUNTIME_COMPILE_FLAGS + TARGET runtime-light + PROPERTY COMPILE_OPTIONS) +get_property( + RUNTIME_INCLUDE_DIRS + TARGET runtime-light + PROPERTY INCLUDE_DIRECTORIES) -list (JOIN RUNTIME_COMPILE_FLAGS "\;" RUNTIME_COMPILE_FLAGS) +list(JOIN RUNTIME_COMPILE_FLAGS "\;" RUNTIME_COMPILE_FLAGS) string(REPLACE "\"" "\\\"" RUNTIME_COMPILE_FLAGS ${RUNTIME_COMPILE_FLAGS}) -configure_file(${BASE_DIR}/compiler/runtime_compile_flags.h.in ${AUTO_DIR}/compiler/runtime_compile_flags.h) +configure_file(${BASE_DIR}/compiler/runtime_compile_flags.h.in + ${AUTO_DIR}/compiler/runtime_compile_flags.h) diff --git a/runtime-light/runtime-light.cpp b/runtime-light/runtime-light.cpp index 9725c64520..9ffc1a2bf9 100644 --- a/runtime-light/runtime-light.cpp +++ b/runtime-light/runtime-light.cpp @@ -2,9 +2,11 @@ // Copyright (c) 2024 LLC «V Kontakte» // Distributed under the GPL v3 License, see LICENSE.notice.txt +#include "runtime-core/utils/kphp-assert-core.h" #include "runtime-light/component/component.h" #include "runtime-light/component/image.h" #include "runtime-light/core/globals/php-init-scripts.h" +#include "runtime-light/utils/context.h" ImageState *vk_k2_create_image_state(const struct PlatformCtx *pt_ctx) { // Note that in vk_k2_create_image_state available only allocator and logs from pt_ctx @@ -47,26 +49,10 @@ PollStatus vk_k2_poll(const ImageState *image_state, const PlatformCtx *pt_ctx, platformCtx = pt_ctx; componentState = component_ctx; - componentState->resume_if_was_rescheduled(); - uint64_t stream_d = 0; - while (platformCtx->take_update(&stream_d) && componentState->not_finished()) { - php_debug("take update on stream %lu", stream_d); - StreamStatus status; - GetStatusResult res = platformCtx->get_stream_status(stream_d, &status); - if (res != GetStatusOk) { - php_warning("get stream status %d", res); - } - php_debug("stream status %d, %d, %d", status.read_status, status.write_status, status.please_shutdown_write); - php_debug("opened_streams size %zu", componentState->opened_streams.size()); - if (componentState->is_stream_already_being_processed(stream_d)) { - php_debug("update on processed stream %lu", stream_d); - componentState->resume_if_wait_stream(stream_d, status); - } else { - componentState->process_new_input_stream(stream_d); - } - } - - PollStatus poll_status = componentState->poll_status; + php_debug("vk_k2_poll started..."); + componentState->process_platform_updates(); + const auto poll_status = componentState->poll_status; + php_debug("vk_k2_poll finished with status: %d", poll_status); reset_thread_locals(); return poll_status; } diff --git a/runtime-light/scheduler/scheduler.cmake b/runtime-light/scheduler/scheduler.cmake new file mode 100644 index 0000000000..fab00a2389 --- /dev/null +++ b/runtime-light/scheduler/scheduler.cmake @@ -0,0 +1 @@ +prepend(RUNTIME_SCHEDULER_SRC scheduler/ scheduler.cpp) diff --git a/runtime-light/scheduler/scheduler.cpp b/runtime-light/scheduler/scheduler.cpp new file mode 100644 index 0000000000..c0bbe95a20 --- /dev/null +++ b/runtime-light/scheduler/scheduler.cpp @@ -0,0 +1,124 @@ +// Compiler for PHP (aka KPHP) +// Copyright (c) 2024 LLC «V Kontakte» +// Distributed under the GPL v3 License, see LICENSE.notice.txt + +#include "runtime-light/scheduler/scheduler.h" + +#include +#include +#include +#include +#include + +#include "runtime-light/component/component.h" +#include "runtime-light/utils/context.h" + +// === SimpleCoroutineScheduler =================================================================== + +SimpleCoroutineScheduler &SimpleCoroutineScheduler::get() noexcept { + return get_component_context()->scheduler; +} + +ScheduleStatus SimpleCoroutineScheduler::scheduleOnNoEvent() noexcept { + if (yield_coros.empty()) { + return ScheduleStatus::Skipped; + } + const auto coro{yield_coros.front()}; + yield_coros.pop_front(); + coro.resume(); + return ScheduleStatus::Resumed; +} + +ScheduleStatus SimpleCoroutineScheduler::scheduleOnIncomingStream() noexcept { + if (awaiting_for_stream_coros.empty()) { + return ScheduleStatus::Skipped; + } + const auto coro{awaiting_for_stream_coros.front()}; + awaiting_for_stream_coros.pop_front(); + coro.resume(); + return ScheduleStatus::Resumed; +} + +ScheduleStatus SimpleCoroutineScheduler::scheduleOnStreamUpdate(uint64_t stream_d) noexcept { + if (stream_d == INVALID_PLATFORM_DESCRIPTOR) { + return ScheduleStatus::Error; + } else if (const auto it_coro{awaiting_for_update_coros.find(stream_d)}; it_coro != awaiting_for_update_coros.cend()) { + const auto coro{it_coro->second}; + awaiting_for_update_coros.erase(it_coro); + coro.resume(); + return ScheduleStatus::Resumed; + } else { + return ScheduleStatus::Skipped; + } +} + +ScheduleStatus SimpleCoroutineScheduler::scheduleOnYield() noexcept { + return ScheduleStatus::Skipped; +} + +ScheduleStatus SimpleCoroutineScheduler::schedule(ScheduleEvent::EventT event) noexcept { + return std::visit( + [this](auto &&event) { + using event_t = std::remove_cvref_t; + if constexpr (std::is_same_v) { + return scheduleOnNoEvent(); + } else if constexpr (std::is_same_v) { + return scheduleOnIncomingStream(); + } else if constexpr (std::is_same_v) { + return scheduleOnStreamUpdate(event.stream_d); + } else if constexpr (std::is_same_v) { + return scheduleOnStreamUpdate(event.timer_d); + } else if constexpr (std::is_same_v) { + return scheduleOnYield(); + } else { + static_assert(false, "non-exhaustive visitor"); + } + }, + event); +} + +void SimpleCoroutineScheduler::suspend(SuspendToken token) noexcept { + const auto [coro, event]{token}; + std::visit( + [this, coro](auto &&event) { + using event_t = std::remove_cvref_t; + if constexpr (std::is_same_v) { + yield_coros.push_back(coro); + } else if constexpr (std::is_same_v) { + awaiting_for_stream_coros.push_back(coro); + } else if constexpr (std::is_same_v) { + if (event.stream_d == INVALID_PLATFORM_DESCRIPTOR) { + return; + } + awaiting_for_update_coros.emplace(event.stream_d, coro); + } else if constexpr (std::is_same_v) { + if (event.timer_d == INVALID_PLATFORM_DESCRIPTOR) { + return; + } + awaiting_for_update_coros.emplace(event.timer_d, coro); + } else { + static_assert(false, "non-exhaustive visitor"); + } + }, + event); +} + +void SimpleCoroutineScheduler::cancel(SuspendToken token) noexcept { + const auto [coro, event]{token}; + std::visit( + [this, coro](auto &&event) { + using event_t = std::remove_cvref_t; + if constexpr (std::is_same_v) { + yield_coros.erase(std::find(yield_coros.cbegin(), yield_coros.cend(), coro)); + } else if constexpr (std::is_same_v) { + awaiting_for_stream_coros.erase(std::find(awaiting_for_stream_coros.cbegin(), awaiting_for_stream_coros.cend(), coro)); + } else if constexpr (std::is_same_v) { + awaiting_for_update_coros.erase(event.stream_d); + } else if constexpr (std::is_same_v) { + awaiting_for_update_coros.erase(event.timer_d); + } else { + static_assert(false, "non-exhaustive visitor"); + } + }, + event); +} diff --git a/runtime-light/scheduler/scheduler.h b/runtime-light/scheduler/scheduler.h new file mode 100644 index 0000000000..c03723bf8f --- /dev/null +++ b/runtime-light/scheduler/scheduler.h @@ -0,0 +1,133 @@ +// 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 +#include + +#include "runtime-core/memory-resource/resource_allocator.h" +#include "runtime-core/memory-resource/unsynchronized_pool_resource.h" +#include "runtime-light/utils/concepts.h" + +/** + * Supported types of updates: + * 1. NoEvent: there was not an update event, so it's up to scheduler what to do; + * 2. IncomingStream(uint64_t): there is a new stream; + * 3. UpdateOnStream(uint64_t): there is an update on some stream; + * 4. UpdateOnTimer(uint64_t): there is an update event on timer; + * 5. Yield: request to yield execution received. + */ +namespace ScheduleEvent { + +struct NoEvent {}; + +struct Yield {}; + +struct IncomingStream { + uint64_t stream_d{}; +}; + +struct UpdateOnStream { + uint64_t stream_d{}; +}; + +struct UpdateOnTimer { + uint64_t timer_d{}; +}; + +using EventT = std::variant; + +} // namespace ScheduleEvent + +enum class ScheduleStatus : uint8_t { Resumed, Skipped, Error }; + +/** + * Supported types of awaitable events: + * 1. Reschedule: yield execution, it's up to scheduler when the coroutine will continue its execution; + * 2. IncomingStream: wait for incoming stream; + * 3. UpdateOnStream(uint64_t): wait for update on specified stream; + * 4. UpdateOnTimer(uint64_t): wait for update on specified timer. + */ +namespace WaitEvent { + +struct Rechedule {}; + +struct IncomingStream {}; + +struct UpdateOnStream { + uint64_t stream_d{}; +}; + +struct UpdateOnTimer { + uint64_t timer_d{}; +}; + +using EventT = std::variant; + +}; // namespace WaitEvent + +/** + * SuspendToken type that binds an event and a coroutine waiting for that event. + */ +using SuspendToken = std::pair, WaitEvent::EventT>; + +/** + * Coroutine scheduler concept. + * + * Any type that is supposed to be used as a coroutine scheduler should conform to following interface: + * 1. be constructible from `memory_resource::unsyncrhonized_pool_resource&`; + * 2. have static `get` function that returns a reference to scheduler instance; + * 3. have `done` method that returns whether scheduler's scheduled all coroutines; + * 4. have `schedule` method that takes an event and schedules coroutines for execution; + * 5. have `suspend` method that suspends specified coroutine; + * 6. have `cancel` method that cancels specified SuspendToken. + */ +template +concept CoroutineSchedulerConcept = std::constructible_from + && requires(scheduler_t && s, ScheduleEvent::EventT schedule_event, SuspendToken token) { + { scheduler_t::get() } noexcept -> std::same_as; + { s.done() } noexcept -> std::convertible_to; + { s.schedule(schedule_event) } noexcept -> std::same_as; + { s.suspend(token) } noexcept -> std::same_as; + { s.cancel(token) } noexcept -> std::same_as; +}; + +// === SimpleCoroutineScheduler =================================================================== + +class SimpleCoroutineScheduler { + template + using unordered_map = memory_resource::stl::unordered_map; + + template + using deque = memory_resource::stl::deque; + + deque> yield_coros; + deque> awaiting_for_stream_coros; + unordered_map> awaiting_for_update_coros; + + ScheduleStatus scheduleOnNoEvent() noexcept; + ScheduleStatus scheduleOnIncomingStream() noexcept; + ScheduleStatus scheduleOnStreamUpdate(uint64_t) noexcept; + ScheduleStatus scheduleOnYield() noexcept; + +public: + explicit SimpleCoroutineScheduler(memory_resource::unsynchronized_pool_resource &memory_resource) noexcept + : yield_coros(deque>::allocator_type{memory_resource}) + , awaiting_for_stream_coros(deque>::allocator_type{memory_resource}) + , awaiting_for_update_coros(unordered_map>::allocator_type{memory_resource}) {} + + static SimpleCoroutineScheduler &get() noexcept; + + bool done() const noexcept { + return yield_coros.empty() && awaiting_for_stream_coros.empty() && awaiting_for_update_coros.empty(); + } + + ScheduleStatus schedule(ScheduleEvent::EventT) noexcept; + void suspend(SuspendToken) noexcept; + void cancel(SuspendToken) noexcept; +}; diff --git a/runtime-light/stdlib/fork/fork-api.cpp b/runtime-light/stdlib/fork/fork-api.cpp new file mode 100644 index 0000000000..b8e68746ce --- /dev/null +++ b/runtime-light/stdlib/fork/fork-api.cpp @@ -0,0 +1,24 @@ +// 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/fork/fork-api.h" + +#include +#include + +#include "runtime-core/utils/kphp-assert-core.h" +#include "runtime-light/coroutine/awaitable.h" +#include "runtime-light/coroutine/task.h" + +task_t f$sched_yield() noexcept { + co_await wait_for_reschedule_t{}; +} + +task_t f$sched_yield_sleep(int64_t duration_ns) noexcept { + if (duration_ns < 0) { + php_warning("can't sleep for negative duration %" PRId64, duration_ns); + co_return; + } + co_await wait_for_timer_t{std::chrono::nanoseconds{static_cast(duration_ns)}}; +} diff --git a/runtime-light/stdlib/fork/fork-api.h b/runtime-light/stdlib/fork/fork-api.h new file mode 100644 index 0000000000..9ccb268e64 --- /dev/null +++ b/runtime-light/stdlib/fork/fork-api.h @@ -0,0 +1,45 @@ +// 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/core-types/decl/optional.h" +#include "runtime-core/utils/kphp-assert-core.h" +#include "runtime-light/coroutine/awaitable.h" +#include "runtime-light/coroutine/task.h" +#include "runtime-light/stdlib/fork/fork-context.h" + +namespace fork_api_impl_ { + +constexpr double WAIT_FORK_MAX_TIMEOUT = 86400.0; + +} // namespace fork_api_impl_ + +constexpr int64_t INVALID_FORK_ID = -1; + +template +requires(is_optional::value) task_t f$wait(int64_t fork_id, double timeout = -1.0) noexcept { + if (timeout < 0.0) { + timeout = fork_api_impl_::WAIT_FORK_MAX_TIMEOUT; + } + auto task_opt{ForkComponentContext::get().pop_fork(fork_id)}; + if (!task_opt.has_value()) { + php_warning("can't find fork %" PRId64, fork_id); + co_return T{}; + } + const auto timeout_ns{std::chrono::duration_cast(std::chrono::duration{timeout})}; + co_return co_await wait_fork_t>{*std::move(task_opt), timeout_ns}; +} + +template +requires(is_optional::value) task_t f$wait(Optional fork_id_opt, double timeout = -1.0) noexcept { + co_return co_await f$wait(fork_id_opt.has_value() ? fork_id_opt.val() : INVALID_FORK_ID, timeout); +} + +task_t f$sched_yield() noexcept; + +task_t f$sched_yield_sleep(int64_t duration_ns) noexcept; diff --git a/runtime-light/stdlib/fork/fork-context.cpp b/runtime-light/stdlib/fork/fork-context.cpp new file mode 100644 index 0000000000..9006243eac --- /dev/null +++ b/runtime-light/stdlib/fork/fork-context.cpp @@ -0,0 +1,12 @@ +// 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/fork/fork-context.h" + +#include "runtime-light/component/component.h" +#include "runtime-light/utils/context.h" + +ForkComponentContext &ForkComponentContext::get() noexcept { + return get_component_context()->fork_component_context; +} diff --git a/runtime-light/stdlib/fork/fork-context.h b/runtime-light/stdlib/fork/fork-context.h new file mode 100644 index 0000000000..79bd0f6f24 --- /dev/null +++ b/runtime-light/stdlib/fork/fork-context.h @@ -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 + +#pragma once + +#include +#include + +#include "runtime-core/memory-resource/unsynchronized_pool_resource.h" +#include "runtime-core/utils/kphp-assert-core.h" +#include "runtime-light/coroutine/task.h" +#include "runtime-light/stdlib/fork/fork.h" +#include "runtime-light/utils/concepts.h" + +class ForkComponentContext { + template + using unordered_map = memory_resource::stl::unordered_map; + + static constexpr auto FORK_ID_INIT = 1; + + unordered_map> forks_; + int64_t next_fork_id_{FORK_ID_INIT}; + +public: + explicit ForkComponentContext(memory_resource::unsynchronized_pool_resource &memory_resource) noexcept + : forks_(unordered_map>::allocator_type{memory_resource}) {} + + static ForkComponentContext &get() noexcept; + + int64_t push_fork(task_t &&task) noexcept { + const auto fork_id{next_fork_id_++}; + forks_.emplace(fork_id, std::move(task)); + php_debug("ForkComponentContext: push fork %" PRId64, fork_id); + return fork_id; + } + + std::optional> pop_fork(int64_t fork_id) noexcept { + if (const auto it_fork{forks_.find(fork_id)}; it_fork != forks_.cend()) { + php_debug("ForkComponentContext: pop fork %" PRId64, fork_id); + auto fork{std::move(it_fork->second)}; + forks_.erase(it_fork); + return {std::move(fork)}; + } + return std::nullopt; + } +}; diff --git a/runtime-light/stdlib/fork/fork.h b/runtime-light/stdlib/fork/fork.h new file mode 100644 index 0000000000..0837de8d38 --- /dev/null +++ b/runtime-light/stdlib/fork/fork.h @@ -0,0 +1,27 @@ +// 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-core/utils/small-object-storage.h" + +class fork_result { + small_object_storage storage{}; + +public: + template + requires(!std::same_as) explicit fork_result(T &&t) noexcept { + storage.emplace>(std::forward(t)); + } + + template + T get_result() noexcept { + return *storage.get(); + } +}; diff --git a/runtime-light/stdlib/misc.cpp b/runtime-light/stdlib/misc.cpp index aae4bca717..daca639574 100644 --- a/runtime-light/stdlib/misc.cpp +++ b/runtime-light/stdlib/misc.cpp @@ -4,73 +4,74 @@ #include "runtime-light/stdlib/misc.h" +#include + #include "runtime-light/component/component.h" #include "runtime-light/coroutine/awaitable.h" -#include "runtime-light/utils/json-functions.h" +#include "runtime-light/header.h" +#include "runtime-light/stdlib/superglobals.h" +#include "runtime-light/streams/streams.h" +#include "runtime-light/utils/context.h" #include "runtime-light/utils/panic.h" -static int ob_merge_buffers() { +namespace { + +int32_t ob_merge_buffers() { Response &response = get_component_context()->response; php_assert(response.current_buffer >= 0); - int ob_first_not_empty = 0; + int32_t ob_first_not_empty = 0; while (ob_first_not_empty < response.current_buffer && response.output_buffers[ob_first_not_empty].size() == 0) { ob_first_not_empty++; } - for (int i = ob_first_not_empty + 1; i <= response.current_buffer; i++) { + for (auto i = ob_first_not_empty + 1; i <= response.current_buffer; i++) { response.output_buffers[ob_first_not_empty].append(response.output_buffers[i].c_str(), response.output_buffers[i].size()); } return ob_first_not_empty; } -task_t parse_input_query(QueryType query_type) { - ComponentState &ctx = *get_component_context(); - php_assert(ctx.standard_stream == 0); - co_await wait_incoming_query_t{}; - ctx.standard_stream = ctx.incoming_pending_queries.front(); - ctx.incoming_pending_queries.pop_front(); - ctx.opened_streams[ctx.standard_stream] = StreamRuntimeStatus::NotBlocked; +} // namespace - if (query_type == QueryType::HTTP) { - auto [buffer, size] = co_await read_all_from_stream(ctx.standard_stream); - init_http_superglobals(buffer, size); - get_platform_context()->allocator.free(buffer); - } else if (query_type == QueryType::COMPONENT) { - // Processing takes place in the calling function - } else { - php_critical_error("unexpected query type %d in parse_input_query", static_cast(query_type)); +task_t wait_and_process_incoming_stream(QueryType query_type) { + const auto incoming_stream_d{co_await wait_for_incoming_stream_t{}}; + switch (query_type) { + case QueryType::HTTP: { + const auto [buffer, size] = co_await read_all_from_stream(incoming_stream_d); + init_http_superglobals(buffer, size); + get_platform_context()->allocator.free(buffer); + break; + } + case QueryType::COMPONENT: { // processing takes place in a component + break; + } } - co_return; + co_return incoming_stream_d; } -task_t finish(int64_t exit_code, bool from_exit) { +task_t finish(int64_t exit_code, bool from_exit) { // TODO: use exit code (void)from_exit; (void)exit_code; - // todo:k2 use exit_code - ComponentState &ctx = *get_component_context(); - if (ctx.standard_stream == 0) { + auto &component_ctx{*get_component_context()}; + const auto standard_stream{component_ctx.standard_stream()}; + if (standard_stream == INVALID_PLATFORM_DESCRIPTOR) { + component_ctx.poll_status = PollStatus::PollFinishedError; co_return; } - int ob_total_buffer = ob_merge_buffers(); - Response &response = ctx.response; + + const auto ob_total_buffer = ob_merge_buffers(); + Response &response = component_ctx.response; auto &buffer = response.output_buffers[ob_total_buffer]; - bool ok = co_await write_all_to_stream(ctx.standard_stream, buffer.c_str(), buffer.size()); - if (!ok) { - php_warning("cannot write component result to input stream %lu", ctx.standard_stream); + if (co_await write_all_to_stream(standard_stream, buffer.c_str(), buffer.size())) { + php_warning("can't write component result to stream %" PRIu64, standard_stream); } - free_all_descriptors(); - ctx.poll_status = PollStatus::PollFinishedOk; - co_return; -} - -task_t f$testyield() { - co_await test_yield_t{}; + component_ctx.release_all_streams(); + component_ctx.poll_status = PollStatus::PollFinishedOk; } void f$check_shutdown() { - const PlatformCtx &ptx = *get_platform_context(); - if (get_platform_context()->please_graceful_shutdown.load()) { + const auto &platform_ctx{*get_platform_context()}; + if (static_cast(get_platform_context()->please_graceful_shutdown.load())) { php_notice("script was graceful shutdown"); - ptx.abort(); + platform_ctx.abort(); } } @@ -88,4 +89,4 @@ task_t f$exit(const mixed &v) { void f$die([[maybe_unused]] const mixed &v) { get_component_context()->poll_status = PollStatus::PollFinishedOk; critical_error_handler(); -} \ No newline at end of file +} diff --git a/runtime-light/stdlib/misc.h b/runtime-light/stdlib/misc.h index 6fabb051a5..1194e781ff 100644 --- a/runtime-light/stdlib/misc.h +++ b/runtime-light/stdlib/misc.h @@ -4,12 +4,12 @@ #pragma once +#include + #include "runtime-core/runtime-core.h" #include "runtime-light/coroutine/task.h" #include "runtime-light/stdlib/superglobals.h" -task_t f$testyield(); - void f$check_shutdown(); task_t f$exit(const mixed &v = 0); @@ -18,5 +18,6 @@ void f$die(const mixed &v = 0); void reset(); -task_t parse_input_query(QueryType query_type); +task_t wait_and_process_incoming_stream(QueryType query_type); + task_t finish(int64_t exit_code, bool from_exit); diff --git a/runtime-light/stdlib/output-control.cpp b/runtime-light/stdlib/output-control.cpp index 8aa7eeb6aa..e6637044f2 100644 --- a/runtime-light/stdlib/output-control.cpp +++ b/runtime-light/stdlib/output-control.cpp @@ -6,6 +6,7 @@ #include "runtime-light/component/component.h" #include "runtime-light/stdlib/string-functions.h" +#include "runtime-light/utils/context.h" static constexpr int system_level_buffer = 0; @@ -41,7 +42,7 @@ string f$ob_get_content() { void f$ob_start(const string &callback) { Response &httpResponse = get_component_context()->response; - if (httpResponse.current_buffer + 1 == httpResponse.ob_max_buffers) { + if (httpResponse.current_buffer + 1 == Response::ob_max_buffers) { php_warning("Maximum nested level of output buffering reached. Can't do ob_start(%s)", callback.c_str()); return; } diff --git a/runtime-light/stdlib/rpc/rpc-context.cpp b/runtime-light/stdlib/rpc/rpc-context.cpp index 6f3c667329..14b305d2bd 100644 --- a/runtime-light/stdlib/rpc/rpc-context.cpp +++ b/runtime-light/stdlib/rpc/rpc-context.cpp @@ -6,6 +6,7 @@ #include "runtime-light/component/component.h" #include "runtime-light/component/image.h" +#include "runtime-light/utils/context.h" RpcComponentContext::RpcComponentContext(memory_resource::unsynchronized_pool_resource &memory_resource) : current_query() diff --git a/runtime-light/stdlib/stdlib.cmake b/runtime-light/stdlib/stdlib.cmake index 74df2b9223..d7956932a3 100644 --- a/runtime-light/stdlib/stdlib.cmake +++ b/runtime-light/stdlib/stdlib.cmake @@ -1,15 +1,18 @@ -prepend(RUNTIME_STDLIB_SRC stdlib/ - interface.cpp - misc.cpp - output-control.cpp - 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 -) +prepend( + RUNTIME_STDLIB_SRC + stdlib/ + interface.cpp + misc.cpp + output-control.cpp + string-functions.cpp + variable-handling.cpp + superglobals.cpp + fork/fork-api.cpp + fork/fork-context.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/stdlib/string-functions.cpp b/runtime-light/stdlib/string-functions.cpp index 055919c34f..3a839a66d1 100644 --- a/runtime-light/stdlib/string-functions.cpp +++ b/runtime-light/stdlib/string-functions.cpp @@ -5,6 +5,7 @@ #include "runtime-light/stdlib/string-functions.h" #include "runtime-light/component/component.h" +#include "runtime-light/utils/context.h" void print(const char *s, size_t s_len) { Response &response = get_component_context()->response; diff --git a/runtime-light/stdlib/superglobals.cpp b/runtime-light/stdlib/superglobals.cpp index 9ca0be6126..d53a5efe90 100644 --- a/runtime-light/stdlib/superglobals.cpp +++ b/runtime-light/stdlib/superglobals.cpp @@ -5,6 +5,7 @@ #include "runtime-light/stdlib/superglobals.h" #include "runtime-light/component/component.h" +#include "runtime-light/utils/context.h" #include "runtime-light/utils/json-functions.h" void init_http_superglobals(const char *buffer, int size) { diff --git a/runtime-light/stdlib/superglobals.h b/runtime-light/stdlib/superglobals.h index 74ae2e802e..9bace21303 100644 --- a/runtime-light/stdlib/superglobals.h +++ b/runtime-light/stdlib/superglobals.h @@ -4,9 +4,6 @@ #pragma once -#include "runtime-core/runtime-core.h" -#include "runtime-light/coroutine/task.h" - enum class QueryType { HTTP, COMPONENT }; void init_http_superglobals(const char *buffer, int size); diff --git a/runtime-light/stdlib/timer/timer.h b/runtime-light/stdlib/timer/timer.h new file mode 100644 index 0000000000..fb34dd28d3 --- /dev/null +++ b/runtime-light/stdlib/timer/timer.h @@ -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 + +#pragma once + +#include +#include +#include +#include + +#include "runtime-core/utils/kphp-assert-core.h" +#include "runtime-light/coroutine/awaitable.h" +#include "runtime-light/coroutine/task.h" + +template +task_t f$set_timer(int64_t timeout_ms, T &&on_timer_callback) noexcept { + if (timeout_ms < 0) { + php_warning("can't set timer for negative duration %" PRId64 "ms", timeout_ms); + co_return; + } + const auto fork_f{[](std::chrono::nanoseconds duration, T &&on_timer_callback) -> task_t { + co_await wait_for_timer_t{duration}; + on_timer_callback(); + co_return 0; + }}; // TODO: someone should pop that fork from ForkComponentContext since it will stay there unless we perform f$wait on fork + const auto duration_ms{std::chrono::milliseconds{static_cast(timeout_ms)}}; + co_await start_fork_and_reschedule_t(fork_f(std::chrono::duration_cast(duration_ms), std::forward(on_timer_callback))); +} diff --git a/runtime-light/stdlib/variable-handling.cpp b/runtime-light/stdlib/variable-handling.cpp index 7b2ee44ab8..dba94a95b3 100644 --- a/runtime-light/stdlib/variable-handling.cpp +++ b/runtime-light/stdlib/variable-handling.cpp @@ -7,7 +7,7 @@ #include "runtime-core/runtime-core.h" #include "runtime-light/component/component.h" #include "runtime-light/stdlib/output-control.h" -#include "runtime-light/utils/php_assert.h" +#include "runtime-light/utils/context.h" void do_print_r(const mixed &v, int depth) { if (depth == 10) { @@ -208,4 +208,4 @@ string f$print_r(const mixed &v, bool buffered) { void f$var_dump(const mixed &v) { do_var_dump(v, 0); -} \ No newline at end of file +} diff --git a/runtime-light/streams/component-stream.cpp b/runtime-light/streams/component-stream.cpp index b7c9a757e1..2956c949a6 100644 --- a/runtime-light/streams/component-stream.cpp +++ b/runtime-light/streams/component-stream.cpp @@ -2,40 +2,60 @@ // Copyright (c) 2024 LLC «V Kontakte» // Distributed under the GPL v3 License, see LICENSE.notice.txt +#include + +#include "runtime-light/component/component.h" +#include "runtime-light/header.h" #include "runtime-light/streams/component-stream.h" +#include "runtime-light/utils/context.h" + +const char *C$ComponentStream::get_class() const noexcept { + return "ComponentStream"; +} + +int32_t C$ComponentStream::get_hash() const noexcept { + return static_cast(vk::std_hash(vk::string_view(C$ComponentStream::get_class()))); +} + +C$ComponentStream::~C$ComponentStream() { + auto &component_ctx{*get_component_context()}; + if (component_ctx.opened_streams().contains(stream_d)) { + component_ctx.release_stream(stream_d); + } +} bool f$ComponentStream$$is_read_closed(const class_instance &stream) { - StreamStatus status; - GetStatusResult res = get_platform_context()->get_stream_status(stream.get()->stream_d, &status); - if (res != GetStatusOk) { - php_warning("cannot get stream status error %d", res); + StreamStatus status{}; + if (const auto status_res{get_platform_context()->get_stream_status(stream.get()->stream_d, std::addressof(status))}; + status_res != GetStatusResult::GetStatusOk) { + php_warning("stream status error %d", status_res); return true; } - return status.read_status == IOClosed; + return status.read_status == IOStatus::IOClosed; } bool f$ComponentStream$$is_write_closed(const class_instance &stream) { - StreamStatus status; - GetStatusResult res = get_platform_context()->get_stream_status(stream.get()->stream_d, &status); - if (res != GetStatusOk) { - php_warning("cannot get stream status error %d", res); + StreamStatus status{}; + if (const auto status_res{get_platform_context()->get_stream_status(stream.get()->stream_d, std::addressof(status))}; + status_res != GetStatusResult::GetStatusOk) { + php_warning("stream status error %d", status_res); return true; } - return status.write_status == IOClosed; + return status.write_status == IOStatus::IOClosed; } bool f$ComponentStream$$is_please_shutdown_write(const class_instance &stream) { - StreamStatus status; - GetStatusResult res = get_platform_context()->get_stream_status(stream.get()->stream_d, &status); - if (res != GetStatusOk) { - php_warning("cannot get stream status error %d", res); + StreamStatus status{}; + if (const auto status_res{get_platform_context()->get_stream_status(stream.get()->stream_d, std::addressof(status))}; + status_res != GetStatusResult::GetStatusOk) { + php_warning("stream status error %d", status_res); return true; } return status.please_shutdown_write; } void f$ComponentStream$$close(const class_instance &stream) { - free_descriptor(stream->stream_d); + get_component_context()->release_stream(stream->stream_d); } void f$ComponentStream$$shutdown_write(const class_instance &stream) { @@ -45,3 +65,20 @@ void f$ComponentStream$$shutdown_write(const class_instance & void f$ComponentStream$$please_shutdown_write(const class_instance &stream) { get_platform_context()->please_shutdown_write(stream->stream_d); } + +// ================================================================================================ + +const char *C$ComponentQuery::get_class() const noexcept { + return "ComponentQuery"; +} + +int32_t C$ComponentQuery::get_hash() const noexcept { + return static_cast(vk::std_hash(vk::string_view(C$ComponentQuery::get_class()))); +} + +C$ComponentQuery::~C$ComponentQuery() { + auto &component_ctx{*get_component_context()}; + if (component_ctx.opened_streams().contains(stream_d)) { + component_ctx.release_stream(stream_d); + } +} diff --git a/runtime-light/streams/component-stream.h b/runtime-light/streams/component-stream.h index ee0f8b7b0d..f5edb1315c 100644 --- a/runtime-light/streams/component-stream.h +++ b/runtime-light/streams/component-stream.h @@ -4,41 +4,26 @@ #pragma once -#include "common/algorithms/hashes.h" -#include "common/wrappers/string_view.h" #include "runtime-core/class-instance/refcountable-php-classes.h" -#include "runtime-light/streams/streams.h" struct C$ComponentStream final : public refcountable_php_classes { uint64_t stream_d{}; - const char *get_class() const noexcept { - return "ComponentStream"; - } + const char *get_class() const noexcept; - int32_t get_hash() const noexcept { - return static_cast(vk::std_hash(vk::string_view(C$ComponentStream::get_class()))); - } + int32_t get_hash() const noexcept; - ~C$ComponentStream() { - free_descriptor(stream_d); - } + ~C$ComponentStream(); }; struct C$ComponentQuery final : public refcountable_php_classes { uint64_t stream_d{}; - const char *get_class() const noexcept { - return "ComponentQuery"; - } + const char *get_class() const noexcept; - int32_t get_hash() const noexcept { - return static_cast(vk::std_hash(vk::string_view(C$ComponentQuery::get_class()))); - } + int32_t get_hash() const noexcept; - ~C$ComponentQuery() { - free_descriptor(stream_d); - } + ~C$ComponentQuery(); }; bool f$ComponentStream$$is_read_closed(const class_instance &stream); diff --git a/runtime-light/streams/interface.cpp b/runtime-light/streams/interface.cpp index ad9fddf1cc..0bf69dea59 100644 --- a/runtime-light/streams/interface.cpp +++ b/runtime-light/streams/interface.cpp @@ -4,106 +4,91 @@ #include "runtime-light/streams/interface.h" +#include + +#include "runtime-core/runtime-core.h" +#include "runtime-core/utils/kphp-assert-core.h" #include "runtime-light/component/component.h" -#include "runtime-light/coroutine/awaitable.h" +#include "runtime-light/header.h" #include "runtime-light/stdlib/misc.h" +#include "runtime-light/streams/component-stream.h" #include "runtime-light/streams/streams.h" +#include "runtime-light/utils/context.h" task_t f$component_get_http_query() { - ComponentState &ctx = *get_component_context(); - if (ctx.standard_stream != 0) { - php_warning("previous incoming stream does not closed"); - ctx.standard_stream = 0; - } - co_await parse_input_query(QueryType::HTTP); + std::ignore = co_await wait_and_process_incoming_stream(QueryType::HTTP); } -task_t> f$component_client_send_query(const string &name, const string &message) { +task_t> f$component_client_send_query(string name, string message) { class_instance query; - const PlatformCtx &ptx = *get_platform_context(); - uint64_t stream_d{}; - OpenStreamResult res = ptx.open(name.size(), name.c_str(), &stream_d); - if (res != OpenStreamOk) { - php_warning("cannot open stream"); + const auto stream_d{get_component_context()->open_stream(name)}; + if (stream_d == INVALID_PLATFORM_DESCRIPTOR) { + php_warning("can't send client query"); co_return query; } - int writed = co_await write_all_to_stream(stream_d, message.c_str(), message.size()); - ptx.shutdown_write(stream_d); - php_debug("send %d bytes from %d to \"%s\" on stream %lu", writed, message.size(), name.c_str(), stream_d); + + int32_t written{co_await write_all_to_stream(stream_d, message.c_str(), message.size())}; + get_platform_context()->shutdown_write(stream_d); query.alloc(); query.get()->stream_d = stream_d; + php_debug("send %d bytes from %d to \"%s\" on stream %" PRIu64, written, message.size(), name.c_str(), stream_d); co_return query; } task_t f$component_client_get_result(class_instance query) { php_assert(!query.is_null()); - uint64_t stream_d = query.get()->stream_d; - if (stream_d == 0) { - php_warning("cannot get component client result"); - co_return string(); + uint64_t stream_d{query.get()->stream_d}; + if (stream_d == INVALID_PLATFORM_DESCRIPTOR) { + php_warning("can't get component client result"); + co_return string{}; } - auto [buffer, size] = co_await read_all_from_stream(stream_d); - string result; - result.assign(buffer, size); - free_descriptor(stream_d); - query.get()->stream_d = 0; - php_debug("read %d bytes from stream %lu", size, stream_d); + const auto [buffer, size]{co_await read_all_from_stream(stream_d)}; + string result{buffer, static_cast(size)}; + get_platform_context()->allocator.free(buffer); + get_component_context()->release_stream(stream_d); + query.get()->stream_d = INVALID_PLATFORM_DESCRIPTOR; + php_debug("read %d bytes from stream %" PRIu64, size, stream_d); co_return result; } -task_t f$component_server_send_result(const string &message) { - ComponentState &ctx = *get_component_context(); - bool ok = co_await write_all_to_stream(ctx.standard_stream, message.c_str(), message.size()); - if (!ok) { - php_warning("cannot send component result"); +task_t f$component_server_send_result(string message) { + auto &component_ctx{*get_component_context()}; + const auto standard_stream{component_ctx.standard_stream()}; + if (!co_await write_all_to_stream(standard_stream, message.c_str(), message.size())) { + php_warning("can't send component result"); } else { php_debug("send result \"%s\"", message.c_str()); } - free_descriptor(ctx.standard_stream); - ctx.standard_stream = 0; + component_ctx.release_stream(standard_stream); } task_t f$component_server_get_query() { - ComponentState &ctx = *get_component_context(); - if (ctx.standard_stream != 0) { - ctx.standard_stream = 0; - } - co_await parse_input_query(QueryType::COMPONENT); - auto [buffer, size] = co_await read_all_from_stream(ctx.standard_stream); - string query = string(buffer, size); + const auto incoming_stream_d{co_await wait_and_process_incoming_stream(QueryType::COMPONENT)}; + const auto [buffer, size] = co_await read_all_from_stream(incoming_stream_d); + string result{buffer, static_cast(size)}; get_platform_context()->allocator.free(buffer); - co_return query; + co_return result; } task_t> f$component_accept_stream() { - ComponentState &ctx = *get_component_context(); - if (ctx.standard_stream != 0) { - php_warning("previous stream does not closed"); - free_descriptor(ctx.standard_stream); - ctx.standard_stream = 0; - } - co_await parse_input_query(QueryType::COMPONENT); + const auto incoming_stream_d{co_await wait_and_process_incoming_stream(QueryType::COMPONENT)}; class_instance stream; stream.alloc(); - stream.get()->stream_d = ctx.standard_stream; + stream.get()->stream_d = incoming_stream_d; co_return stream; } class_instance f$component_open_stream(const string &name) { + auto &component_ctx = *get_component_context(); + class_instance query; - const PlatformCtx &ptx = *get_platform_context(); - ComponentState &ctx = *get_component_context(); - uint64_t stream_d{}; - OpenStreamResult res = ptx.open(name.size(), name.c_str(), &stream_d); - if (res != OpenStreamOk) { - php_warning("cannot open stream"); + const auto stream_d{component_ctx.open_stream(name)}; + if (stream_d == INVALID_PLATFORM_DESCRIPTOR) { return query; } - ctx.opened_streams[stream_d] = StreamRuntimeStatus::NotBlocked; query.alloc(); query.get()->stream_d = stream_d; - php_debug("open stream %lu to %s", stream_d, name.c_str()); return query; } @@ -112,37 +97,38 @@ int64_t f$component_stream_write_nonblock(const class_instance &stream) { - auto [ptr, size] = read_nonblock_from_stream(stream.get()->stream_d); - string result(ptr, size); - get_platform_context()->allocator.free(ptr); + const auto [buffer, size] = read_nonblock_from_stream(stream.get()->stream_d); + string result{buffer, static_cast(size)}; + get_platform_context()->allocator.free(buffer); // FIXME: do we need platform memory? return result; } -task_t f$component_stream_write_exact(const class_instance &stream, const string &message) { - int write = co_await write_exact_to_stream(stream->stream_d, message.c_str(), message.size()); - php_debug("write exact %d bytes to stream %lu", write, stream->stream_d); - co_return write; +task_t f$component_stream_write_exact(class_instance stream, string message) { + const auto written = co_await write_exact_to_stream(stream->stream_d, message.c_str(), message.size()); + php_debug("wrote exact %d bytes to stream %" PRIu64, written, stream->stream_d); + co_return written; } -task_t f$component_stream_read_exact(const class_instance &stream, int64_t len) { - char *buffer = static_cast(RuntimeAllocator::current().alloc_script_memory(len)); - int read = co_await read_exact_from_stream(stream->stream_d, buffer, len); - string result(buffer, read); +task_t f$component_stream_read_exact(class_instance stream, int64_t len) { + auto *buffer = static_cast(RuntimeAllocator::current().alloc_script_memory(len)); + const auto read = co_await read_exact_from_stream(stream->stream_d, buffer, len); + string result{buffer, static_cast(read)}; RuntimeAllocator::current().free_script_memory(buffer, len); - php_debug("read exact %d bytes from stream %lu", read, stream->stream_d); + php_debug("read exact %d bytes from stream %" PRIu64, read, stream->stream_d); co_return result; } void f$component_close_stream(const class_instance &stream) { - free_descriptor(stream->stream_d); + get_component_context()->release_stream(stream.get()->stream_d); + stream.get()->stream_d = INVALID_PLATFORM_DESCRIPTOR; // TODO: convert stream object to null? } void f$component_finish_stream_processing(const class_instance &stream) { - ComponentState &ctx = *get_component_context(); - if (stream->stream_d != ctx.standard_stream) { - php_warning("call server finish query on non server stream %lu", stream->stream_d); + auto &component_ctx = *get_component_context(); + if (stream.get()->stream_d != component_ctx.standard_stream()) { + php_warning("call server finish query on non server stream %lu", stream.get()->stream_d); return; } - free_descriptor(ctx.standard_stream); - ctx.standard_stream = 0; + component_ctx.release_stream(component_ctx.standard_stream()); + stream.get()->stream_d = INVALID_PLATFORM_DESCRIPTOR; } diff --git a/runtime-light/streams/interface.h b/runtime-light/streams/interface.h index 8e48b67539..3890e6930b 100644 --- a/runtime-light/streams/interface.h +++ b/runtime-light/streams/interface.h @@ -13,29 +13,32 @@ constexpr int64_t v$COMPONENT_ERROR = -1; task_t f$component_get_http_query(); -/** - * component query client blocked interface - * */ -task_t> f$component_client_send_query(const string &name, const string &message); +// === component query client blocked interface =================================================== + +task_t> f$component_client_send_query(string name, string message); + task_t f$component_client_get_result(class_instance query); -/** - * component query server blocked interface - * */ +// === component query server blocked interface =================================================== + task_t f$component_server_get_query(); -task_t f$component_server_send_result(const string &message); -/** - * component query low-level interface - * */ +task_t f$component_server_send_result(string message); + +// === component stream low-level interface ======================================================= + class_instance f$component_open_stream(const string &name); + task_t> f$component_accept_stream(); int64_t f$component_stream_write_nonblock(const class_instance &stream, const string &message); + string f$component_stream_read_nonblock(const class_instance &stream); -task_t f$component_stream_write_exact(const class_instance &stream, const string &message); -task_t f$component_stream_read_exact(const class_instance &stream, int64_t len); +task_t f$component_stream_write_exact(class_instance stream, string message); + +task_t f$component_stream_read_exact(class_instance stream, int64_t len); void f$component_close_stream(const class_instance &stream); + void f$component_finish_stream_processing(const class_instance &stream); diff --git a/runtime-light/streams/streams.cpp b/runtime-light/streams/streams.cpp index 66d24bdb1d..158e9fda00 100644 --- a/runtime-light/streams/streams.cpp +++ b/runtime-light/streams/streams.cpp @@ -4,93 +4,99 @@ #include "runtime-light/streams/streams.h" -#include "runtime-light/component/component.h" +#include +#include +#include +#include + +#include "runtime-core/utils/kphp-assert-core.h" #include "runtime-light/coroutine/awaitable.h" +#include "runtime-light/header.h" #include "runtime-light/utils/context.h" -task_t> read_all_from_stream(uint64_t stream_d) { - co_await test_yield_t{}; +task_t> read_all_from_stream(uint64_t stream_d) { + const auto &platform_ctx = *get_platform_context(); + constexpr int32_t batch_size = 32; - constexpr int batch_size = 32; - const PlatformCtx &ptx = *get_platform_context(); - int buffer_capacity = batch_size; - char *buffer = static_cast(ptx.allocator.alloc(buffer_capacity)); - int buffer_size = 0; - StreamStatus status; + int32_t buffer_capacity = batch_size; + auto *buffer = static_cast(platform_ctx.allocator.alloc(buffer_capacity)); + int32_t buffer_size = 0; + StreamStatus status{}; do { - GetStatusResult res = ptx.get_stream_status(stream_d, &status); - if (res != GetStatusOk) { - php_warning("get stream status return status %d", res); + GetStatusResult res = platform_ctx.get_stream_status(stream_d, std::addressof(status)); + if (res != GetStatusResult::GetStatusOk) { + php_warning("get stream status returned status %d", res); co_return std::make_pair(nullptr, 0); } - if (status.read_status == IOAvailable) { + if (status.read_status == IOStatus::IOAvailable) { if (buffer_capacity - buffer_size < batch_size) { - char *new_buffer = static_cast(ptx.allocator.alloc(buffer_capacity * 2)); - memcpy(new_buffer, buffer, buffer_size); - ptx.allocator.free(buffer); + auto *new_buffer = static_cast(platform_ctx.allocator.alloc(static_cast(buffer_capacity) * 2)); + std::memcpy(new_buffer, buffer, buffer_size); + platform_ctx.allocator.free(buffer); buffer_capacity = buffer_capacity * 2; buffer = new_buffer; } - buffer_size += ptx.read(stream_d, batch_size, buffer + buffer_size); - } else if (status.read_status == IOBlocked) { - co_await read_blocked_t{stream_d}; + buffer_size += platform_ctx.read(stream_d, batch_size, buffer + buffer_size); + } else if (status.read_status == IOStatus::IOBlocked) { + co_await wait_for_update_t{stream_d}; } - } while (status.read_status != IOClosed); + } while (status.read_status != IOStatus::IOClosed); co_return std::make_pair(buffer, buffer_size); } -std::pair read_nonblock_from_stream(uint64_t stream_d) { - constexpr int batch_size = 32; - const PlatformCtx &ptx = *get_platform_context(); - int buffer_capacity = batch_size; - char *buffer = static_cast(ptx.allocator.alloc(buffer_capacity)); - int buffer_size = 0; - StreamStatus status; +std::pair read_nonblock_from_stream(uint64_t stream_d) { + const auto &platform_ctx = *get_platform_context(); + constexpr int32_t batch_size = 32; + + int32_t buffer_capacity = batch_size; + auto *buffer = static_cast(platform_ctx.allocator.alloc(buffer_capacity)); + int32_t buffer_size = 0; + + StreamStatus status{}; do { - GetStatusResult res = ptx.get_stream_status(stream_d, &status); - if (res != GetStatusOk) { - php_warning("get stream status return status %d", res); + GetStatusResult res = platform_ctx.get_stream_status(stream_d, std::addressof(status)); + if (res != GetStatusResult::GetStatusOk) { + php_warning("get stream status returned status %d", res); return std::make_pair(nullptr, 0); } - if (status.read_status == IOAvailable) { + if (status.read_status == IOStatus::IOAvailable) { if (buffer_capacity - buffer_size < batch_size) { - char *new_buffer = static_cast(ptx.allocator.alloc(buffer_capacity * 2)); - memcpy(new_buffer, buffer, buffer_size); - ptx.allocator.free(buffer); + auto *new_buffer = static_cast(platform_ctx.allocator.alloc(static_cast(buffer_capacity) * 2)); + std::memcpy(new_buffer, buffer, buffer_size); + platform_ctx.allocator.free(buffer); buffer_capacity = buffer_capacity * 2; buffer = new_buffer; } - buffer_size += ptx.read(stream_d, batch_size, buffer + buffer_size); + buffer_size += platform_ctx.read(stream_d, batch_size, buffer + buffer_size); } else { break; } - } while (status.read_status != IOClosed); + } while (status.read_status != IOStatus::IOClosed); return std::make_pair(buffer, buffer_size); } -task_t read_exact_from_stream(uint64_t stream_d, char *buffer, int len) { - co_await test_yield_t{}; +task_t read_exact_from_stream(uint64_t stream_d, char *buffer, int32_t len) { + const PlatformCtx &platform_ctx = *get_platform_context(); - const PlatformCtx &ptx = *get_platform_context(); - int read = 0; - StreamStatus status{IOAvailable, IOAvailable, 0}; + int32_t read = 0; - while (read != len && status.read_status != IOClosed) { - GetStatusResult res = ptx.get_stream_status(stream_d, &status); - if (res != GetStatusOk) { - php_warning("get stream status return status %d", res); + StreamStatus status{IOStatus::IOAvailable, IOStatus::IOAvailable, 0}; + while (read != len && status.read_status != IOStatus::IOClosed) { + GetStatusResult res = platform_ctx.get_stream_status(stream_d, std::addressof(status)); + if (res != GetStatusResult::GetStatusOk) { + php_warning("get stream status returned status %d", res); co_return 0; } - if (status.read_status == IOAvailable) { - read += ptx.read(stream_d, len - read, buffer + read); - } else if (status.read_status == IOBlocked) { - co_await read_blocked_t{stream_d}; + if (status.read_status == IOStatus::IOAvailable) { + read += platform_ctx.read(stream_d, len - read, buffer + read); + } else if (status.read_status == IOStatus::IOBlocked) { + co_await wait_for_update_t{stream_d}; } else { co_return read; } @@ -99,103 +105,84 @@ task_t read_exact_from_stream(uint64_t stream_d, char *buffer, int len) { co_return read; } -task_t write_all_to_stream(uint64_t stream_d, const char *buffer, int len) { - co_await test_yield_t{}; +task_t write_all_to_stream(uint64_t stream_d, const char *buffer, int32_t len) { + const auto &platform_ctx = *get_platform_context(); - StreamStatus status; - const PlatformCtx &ptx = *get_platform_context(); - int writed = 0; + int32_t written = 0; + + StreamStatus status{}; do { - GetStatusResult res = ptx.get_stream_status(stream_d, &status); - if (res != GetStatusOk) { - php_warning("get stream status return status %d", res); - co_return writed; + GetStatusResult res = platform_ctx.get_stream_status(stream_d, std::addressof(status)); + if (res != GetStatusResult::GetStatusOk) { + php_warning("get stream status returned status %d", res); + co_return written; } if (status.please_shutdown_write) { - php_debug("stream %lu set please_shutdown_write. Stop writing", stream_d); - co_return writed; - } else if (status.write_status == IOAvailable) { - writed += ptx.write(stream_d, len - writed, buffer + writed); - } else if (status.write_status == IOBlocked) { - co_await write_blocked_t{stream_d}; + php_debug("stream %" PRIu64 " set please_shutdown_write. Stop writing", stream_d); + co_return written; + } else if (status.write_status == IOStatus::IOAvailable) { + written += platform_ctx.write(stream_d, len - written, buffer + written); + } else if (status.write_status == IOStatus::IOBlocked) { + co_await wait_for_update_t{stream_d}; } else { - php_warning("stream closed while writing. Writed %d. Size %d. Stream %lu", writed, len, stream_d); - co_return writed; + php_warning("stream closed while writing. Wrote %d. Size %d. Stream %" PRIu64, written, len, stream_d); + co_return written; } - } while (writed != len); + } while (written != len); - php_debug("write %d bytes to stream %lu", len, stream_d); - co_return writed; + php_debug("wrote %d bytes to stream %" PRIu64, len, stream_d); + co_return written; } -int write_nonblock_to_stream(uint64_t stream_d, const char *buffer, int len) { - StreamStatus status; - const PlatformCtx &ptx = *get_platform_context(); - int writed = 0; +int32_t write_nonblock_to_stream(uint64_t stream_d, const char *buffer, int32_t len) { + const auto &platform_ctx = *get_platform_context(); + + int32_t written = 0; + + StreamStatus status{}; do { - GetStatusResult res = ptx.get_stream_status(stream_d, &status); - if (res != GetStatusOk) { - php_warning("get stream status return status %d", res); + GetStatusResult res = platform_ctx.get_stream_status(stream_d, std::addressof(status)); + if (res != GetStatusResult::GetStatusOk) { + php_warning("get stream status returned status %d", res); return 0; } - if (status.write_status == IOAvailable) { - writed += ptx.write(stream_d, len - writed, buffer + writed); + if (status.write_status == IOStatus::IOAvailable) { + written += platform_ctx.write(stream_d, len - written, buffer + written); } else { break; } - } while (writed != len); + } while (written != len); - php_debug("write %d bytes from %d to stream %lu", writed, len, stream_d); - return writed; + php_debug("write %d bytes from %d to stream %" PRIu64, written, len, stream_d); + return written; } -task_t write_exact_to_stream(uint64_t stream_d, const char *buffer, int len) { - co_await test_yield_t{}; +task_t write_exact_to_stream(uint64_t stream_d, const char *buffer, int32_t len) { + const auto &platform_ctx = *get_platform_context(); + + int written = 0; StreamStatus status{IOAvailable, IOAvailable, 0}; - const PlatformCtx &ptx = *get_platform_context(); - int writed = 0; - while (writed != len && status.write_status != IOClosed) { - GetStatusResult res = ptx.get_stream_status(stream_d, &status); - if (res != GetStatusOk) { - php_warning("get stream status return status %d", res); - co_return writed; + while (written != len && status.write_status != IOStatus::IOClosed) { + GetStatusResult res = platform_ctx.get_stream_status(stream_d, std::addressof(status)); + if (res != GetStatusResult::GetStatusOk) { + php_warning("get stream status returned status %d", res); + co_return written; } if (status.please_shutdown_write) { - php_debug("stream %lu set please_shutdown_write. Stop writing", stream_d); - co_return writed; - } else if (status.write_status == IOAvailable) { - writed += ptx.write(stream_d, len - writed, buffer + writed); - } else if (status.write_status == IOBlocked) { - co_await write_blocked_t{stream_d}; + php_debug("stream %" PRIu64 " set please_shutdown_write. Stop writing", stream_d); + co_return written; + } else if (status.write_status == IOStatus::IOAvailable) { + written += platform_ctx.write(stream_d, len - written, buffer + written); + } else if (status.write_status == IOStatus::IOBlocked) { + co_await wait_for_update_t{stream_d}; } else { - co_return writed; + co_return written; } } - co_return writed; -} - -void free_all_descriptors() { - php_debug("free all descriptors"); - ComponentState &ctx = *get_component_context(); - const PlatformCtx &ptx = *get_platform_context(); - for (auto &processed_query : ctx.opened_streams) { - ptx.free_descriptor(processed_query.first); - } - ctx.opened_streams.clear(); - ctx.awaiting_coroutines.clear(); - ptx.free_descriptor(ctx.standard_stream); - ctx.standard_stream = 0; -} - -void free_descriptor(uint64_t stream_d) { - php_debug("free descriptor %lu", stream_d); - ComponentState &ctx = *get_component_context(); - get_platform_context()->free_descriptor(stream_d); - ctx.opened_streams.erase(stream_d); - ctx.awaiting_coroutines.erase(stream_d); + co_return written; } diff --git a/runtime-light/streams/streams.h b/runtime-light/streams/streams.h index 692983ef44..5721a85d00 100644 --- a/runtime-light/streams/streams.h +++ b/runtime-light/streams/streams.h @@ -7,19 +7,20 @@ #include #include -#include "runtime-core/runtime-core.h" - #include "runtime-light/coroutine/task.h" -enum class StreamRuntimeStatus { WBlocked, RBlocked, NotBlocked, Timer }; +// === read ======================================================================================= + +task_t> read_all_from_stream(uint64_t stream_d); + +std::pair read_nonblock_from_stream(uint64_t stream_d); + +task_t read_exact_from_stream(uint64_t stream_d, char *buffer, int32_t len); + +// === write ====================================================================================== -task_t> read_all_from_stream(uint64_t stream_d); -std::pair read_nonblock_from_stream(uint64_t stream_d); -task_t read_exact_from_stream(uint64_t stream_d, char *buffer, int len); +task_t write_all_to_stream(uint64_t stream_d, const char *buffer, int32_t len); -task_t write_all_to_stream(uint64_t stream_d, const char *buffer, int len); -int write_nonblock_to_stream(uint64_t stream_d, const char *buffer, int len); -task_t write_exact_to_stream(uint64_t stream_d, const char *buffer, int len); +int32_t write_nonblock_to_stream(uint64_t stream_d, const char *buffer, int32_t len); -void free_all_descriptors(); -void free_descriptor(uint64_t stream_d); +task_t write_exact_to_stream(uint64_t stream_d, const char *buffer, int32_t len); diff --git a/runtime-light/utils/concepts.h b/runtime-light/utils/concepts.h index 963a9c6b95..4969436e7d 100644 --- a/runtime-light/utils/concepts.h +++ b/runtime-light/utils/concepts.h @@ -5,6 +5,13 @@ #pragma once #include +#include +#include template concept standard_layout = std::is_standard_layout_v; + +template +concept hashable = requires(T a) { + { std::hash{}(a) } -> std::convertible_to; +}; diff --git a/runtime-light/utils/panic.h b/runtime-light/utils/panic.h index d11fbed624..88fc2e6b1b 100644 --- a/runtime-light/utils/panic.h +++ b/runtime-light/utils/panic.h @@ -8,17 +8,18 @@ #include "context.h" #include "runtime-light/component/component.h" +#include "runtime-light/header.h" #include "runtime-light/utils/logs.h" inline void critical_error_handler() { - constexpr const char * message = "script panic"; - const PlatformCtx & ptx = *get_platform_context(); - ComponentState & ctx = *get_component_context(); - ptx.log(Debug, strlen(message), message); + constexpr const char *message = "script panic"; + const auto &platform_ctx = *get_platform_context(); + auto &component_ctx = *get_component_context(); + platform_ctx.log(Debug, strlen(message), message); - if (ctx.not_finished()) { - ctx.poll_status = PollStatus::PollFinishedError; + if (component_ctx.poll_status != PollStatus::PollFinishedOk && component_ctx.poll_status != PollStatus::PollFinishedError) { + component_ctx.poll_status = PollStatus::PollFinishedError; } - ptx.abort(); + platform_ctx.abort(); exit(1); } diff --git a/runtime-light/utils/timer.cpp b/runtime-light/utils/timer.cpp deleted file mode 100644 index 3cb712012d..0000000000 --- a/runtime-light/utils/timer.cpp +++ /dev/null @@ -1,21 +0,0 @@ -// Compiler for PHP (aka KPHP) -// Copyright (c) 2024 LLC «V Kontakte» -// Distributed under the GPL v3 License, see LICENSE.notice.txt - -#include "runtime-light/utils/timer.h" - -void set_timer_impl(int64_t timeout_ms, on_timer_callback_t &&callback) { - const PlatformCtx &ptx = *get_platform_context(); - ComponentState &ctx = *get_component_context(); - uint64_t nanoseconds = static_cast(timeout_ms * 1e6); - uint64_t timer_d = 0; - SetTimerResult res = ptx.set_timer(&timer_d, nanoseconds); - if (res != SetTimerOk) { - php_warning("timer limit exceeded"); - return; - } - php_debug("set up timer %lu for %lu ms", timer_d, timeout_ms); - - ctx.opened_streams[timer_d] = StreamRuntimeStatus::Timer; - ctx.timer_callbacks[timer_d] = callback; -} \ No newline at end of file diff --git a/runtime-light/utils/timer.h b/runtime-light/utils/timer.h deleted file mode 100644 index e7ba0b5202..0000000000 --- a/runtime-light/utils/timer.h +++ /dev/null @@ -1,20 +0,0 @@ -// 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/memory-resource/resource_allocator.h" -#include "runtime-light/component/component.h" - -// todo:k2 std::function use heap -using on_timer_callback_t = std::function; - -void set_timer_impl(int64_t timeout_ms, on_timer_callback_t &&callback); - -template -void f$set_timer(int64_t timeout, CallBack &&callback) { - set_timer_impl(timeout, on_timer_callback_t(std::forward(callback))); -} \ No newline at end of file diff --git a/runtime-light/utils/utils.cmake b/runtime-light/utils/utils.cmake index b25f9b6b3d..fee0426cd6 100644 --- a/runtime-light/utils/utils.cmake +++ b/runtime-light/utils/utils.cmake @@ -1,5 +1 @@ -prepend(RUNTIME_UTILS_SRC utils/ - php_assert.cpp - json-functions.cpp - context.cpp - timer.cpp) +prepend(RUNTIME_UTILS_SRC utils/ php_assert.cpp json-functions.cpp context.cpp) diff --git a/runtime/storage.cpp b/runtime/storage.cpp index b2a161ac9c..19f1201aed 100644 --- a/runtime/storage.cpp +++ b/runtime/storage.cpp @@ -4,9 +4,9 @@ #include "runtime/resumable.h" -Storage::Storage() noexcept : - tag(0) { - memset(storage_.storage_, 0, sizeof(mixed)); +Storage::Storage() noexcept + : tag(0) { + std::memset(storage_.storage_.data(), 0, sizeof(mixed)); } void Storage::save_void() noexcept { @@ -18,7 +18,7 @@ void Storage::save_void() noexcept { } void Storage::save_exception() noexcept { - php_assert (!CurException.is_null()); + php_assert(!CurException.is_null()); Throwable exception = std::move(CurException); save(thrown_exception{exception}); } diff --git a/runtime/storage.h b/runtime/storage.h index b778b8b75b..2f905618ff 100644 --- a/runtime/storage.h +++ b/runtime/storage.h @@ -7,6 +7,7 @@ #include #include "runtime-core/runtime-core.h" +#include "runtime-core/utils/small-object-storage.h" #include "runtime/exception.h" extern const char *last_wait_error; @@ -14,47 +15,13 @@ extern const char *last_wait_error; struct thrown_exception { Throwable exception; thrown_exception() = default; - explicit thrown_exception(Throwable exception) noexcept : exception(std::move(exception)) {} -}; - -template -union small_obect_ptr { - char storage_[limit]; - void *storage_ptr; - - template - std::enable_if_t emplace(Args&& ...args) noexcept { - return new (storage_) T(std::forward(args)...); - } - template - std::enable_if_t get() noexcept { - return reinterpret_cast(storage_); - } - template - std::enable_if_t destroy() noexcept { - get()->~T(); - } - - template - std::enable_if_t emplace(Args&& ...args) noexcept { - storage_ptr = dl::allocate(sizeof(T)); - return new (storage_ptr) T(std::forward(args)...); - } - template - std::enable_if_t get() noexcept { - return static_cast(storage_ptr); - } - template - std::enable_if_t destroy() noexcept { - T *mem = get(); - mem->~T(); - dl::deallocate(mem, sizeof(T)); - } + explicit thrown_exception(Throwable exception) noexcept + : exception(std::move(exception)) {} }; class Storage { private: - using storage_ptr = small_obect_ptr; + using storage_ptr = small_object_storage; storage_ptr storage_; template::type> @@ -63,7 +30,6 @@ class Storage { void save_exception() noexcept; public: - // this class specializations are generated by kphp compiler template @@ -75,7 +41,7 @@ class Storage { template struct loader { static_assert(!std::is_same{}, "int is forbidden"); - using loader_fun = T(*)(storage_ptr &); + using loader_fun = T (*)(storage_ptr &); static loader_fun get_function(int tag) noexcept; }; @@ -100,11 +66,10 @@ class Storage { X load_as() noexcept; }; - template struct Storage::load_implementation_helper { static Y load(storage_ptr &) noexcept { - php_assert(0); // should be never called in runtime, used just to prevent compilation errors + php_assert(0); // should be never called in runtime, used just to prevent compilation errors return Y(); } }; @@ -141,7 +106,7 @@ struct Storage::load_implementation_helper static_assert(!std::is_same{}, "int is forbidden"); static Y load(storage_ptr &storage) noexcept { - php_assert (CurException.is_null()); + php_assert(CurException.is_null()); CurException = load_implementation_helper::load(storage).exception; return Y(); } @@ -150,13 +115,11 @@ struct Storage::load_implementation_helper template<> struct Storage::load_implementation_helper { static void load(storage_ptr &storage) noexcept { - php_assert (CurException.is_null()); + php_assert(CurException.is_null()); CurException = load_implementation_helper::load(storage).exception; } }; - - template void Storage::save(std::enable_if_t x) noexcept { static_assert(!std::is_same{}, "int is forbidden"); @@ -173,7 +136,7 @@ template X Storage::load() noexcept { static_assert(!std::is_same{}, "int is forbidden"); - php_assert (tag != 0); + php_assert(tag != 0); if (tag == tagger::get_tag()) { tag = 0; return load_implementation_helper::load(storage_); @@ -184,12 +147,11 @@ X Storage::load() noexcept { return load_implementation_helper::load(storage_); } - template X Storage::load_as() noexcept { static_assert(!std::is_same{}, "int is forbidden"); - php_assert (tag != 0); + php_assert(tag != 0); int tag_save = tag; tag = 0; diff --git a/tests/k2-components/yield_loop.php b/tests/k2-components/yield_loop.php index f58783acfe..397a38eadc 100644 --- a/tests/k2-components/yield_loop.php +++ b/tests/k2-components/yield_loop.php @@ -1,5 +1,5 @@ Date: Fri, 2 Aug 2024 19:27:08 +0300 Subject: [PATCH 16/45] TL fetch: optimize tl_Dictionary (#1051) Reserve enough space to prevent array reallocations --- .gitignore | 1 + runtime/tl/tl_builtins.h | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index 6437597584..51a74d35cc 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ .idea .vscode +.cache build *.iml /cmake-build-* diff --git a/runtime/tl/tl_builtins.h b/runtime/tl/tl_builtins.h index 417b331553..caf0a927dc 100644 --- a/runtime/tl/tl_builtins.h +++ b/runtime/tl/tl_builtins.h @@ -4,12 +4,11 @@ #pragma once +#include #include #include "common/tl/constants/common.h" - -#include "runtime-core/include.h" -#include "runtime/interface.h" +#include "runtime-core/runtime-core.h" #include "runtime/rpc.h" #include "runtime/tl/rpc_function.h" #include "runtime/tl/rpc_tl_query.h" @@ -508,12 +507,12 @@ struct tl_Dictionary_impl { array fetch() { CHECK_EXCEPTION(return array()); - array result; int32_t n = rpc_fetch_int(); if (n < 0) { CurrentTlQuery::get().raise_fetching_error("Dictionary size is negative"); - return result; + return {}; } + array result{array_size{n, false}}; for (int32_t i = 0; i < n; ++i) { const auto &key = KeyT().fetch(); fetch_magic_if_not_bare(inner_value_magic, "Incorrect magic of inner type of some Dictionary"); @@ -547,6 +546,7 @@ struct tl_Dictionary_impl { CurrentTlQuery::get().raise_fetching_error("Dictionary size is negative"); return; } + out.reserve(n, false); for (int32_t i = 0; i < n; ++i) { typename KeyT::PhpType key; KeyT().typed_fetch_to(key); From 08e4a29839860e4283efce775c52fca2360b98d2 Mon Sep 17 00:00:00 2001 From: Vadim Sadokhov <65451602+astrophysik@users.noreply.github.com> Date: Mon, 5 Aug 2024 14:13:30 +0300 Subject: [PATCH 17/45] add support for phpt tests with k2 (#1042) --- compiler/code-gen/files/init-scripts.cpp | 9 +++ tests/kphp_tester.py | 69 ++++++++++++++++--- tests/phpt/array/001_array_combine.php | 2 +- tests/phpt/array/002_array_fill_keys.php | 2 +- tests/phpt/array/003_array_flip.php | 2 +- tests/phpt/array/004.array_merge_into.php | 2 +- tests/phpt/array/005_array_replace.php | 2 +- tests/phpt/array/006_array_sum.php | 2 +- tests/phpt/array/008_array_find.php | 2 +- tests/phpt/array/009_array_filter_by_key.php | 2 +- tests/phpt/array/010_array_swap_int_keys.php | 2 +- tests/phpt/array/011_array_reserve_vector.php | 2 +- .../array/012_array_reserve_map_int_keys.php | 2 +- .../013_array_reserve_map_string_keys.php | 2 +- tests/phpt/array/014_array_rand.php | 2 +- .../phpt/array/015_array_replace_neg_keys.php | 2 +- .../phpt/array/016_array_merge_recursive.php | 2 +- tests/phpt/array/017_array_unique.php | 2 +- tests/phpt/array/019_array_indexing_ok.php | 2 +- tests/phpt/array/024_array_unset.php | 2 +- tests/phpt/array/025_array_pop.php | 2 +- tests/phpt/array/026_implode.php | 2 +- tests/phpt/array/027_explode.php | 2 +- tests/phpt/array/028_explode_error.php | 2 +- tests/phpt/array/029_explode_error.php | 2 +- tests/phpt/array/030_explode_error.php | 2 +- tests/phpt/array/031_explode_error.php | 2 +- tests/phpt/by_name/01_by_name_construct.php | 2 +- tests/phpt/by_name/02_by_name_call.php | 2 +- tests/phpt/by_name/03_by_name_get_const.php | 2 +- tests/phpt/by_name/04_by_name_get_field.php | 2 +- .../by_name/100_by_name_non_constexpr.php | 2 +- tests/phpt/by_name/102_get_unresolved.php | 2 +- tests/phpt/by_name/103_by_name_private_1.php | 2 +- tests/phpt/by_name/103_by_name_private_2.php | 2 +- .../phpt/by_name/105_direct_by_name_call.php | 2 +- tests/phpt/by_name/106_by_name_wrong_args.php | 2 +- tests/phpt/by_name/107_by_name_wrong_tinf.php | 2 +- tests/phpt/cl/001_base_class.php | 2 +- tests/phpt/cl/002_use.php | 2 +- tests/phpt/cl/003_class_in_class.php | 2 +- tests/phpt/cl/005_cast_to_array_debug.php | 2 +- tests/phpt/cl/006_interface_cast_to_array.php | 2 +- .../cl/007_to_array_debug_with_extends.php | 2 +- tests/phpt/cl/008_fields_default_value.php | 2 +- .../009_to_array_debug_with_class_names.php | 2 +- .../cl/027_ternary_operator_assumptions.php | 2 +- tests/phpt/cl/030_resolve_from_phpdoc.php | 2 +- tests/phpt/cl/110_Exception.php | 2 +- tests/phpt/cl/111_Memcache.php | 2 +- tests/phpt/cl/116_wa_serialize.php | 2 +- tests/phpt/cl/128_prop_of_same_class.php | 2 +- tests/phpt/cl/133_array_merge_instances.php | 2 +- tests/phpt/cl/139_class_inside_fork.php | 2 +- tests/phpt/cl/140_class_from_fork.php | 2 +- tests/phpt/cl/156_recursive_assumptions.php | 2 +- tests/phpt/cl/162_mix_class_and_null.php | 2 +- .../cl/178_fail_pass_class_to_hll_stats.php | 2 +- tests/phpt/cl/197_compile_time_location.php | 2 +- tests/phpt/cl/198_concatenation.php | 2 +- .../class_inheritance/010_to_array_debug.php | 2 +- tests/phpt/class_inheritance/022_ref_cnt.php | 2 +- .../040_call_virt_method_benchmark.php | 2 +- ...method_with_exception_but_never_use_it.php | 2 +- .../clone_keyword/006_reference_count.php | 2 +- .../clone_keyword/007_clone_interfaces.php | 2 +- tests/phpt/colors/015_colored_resumable.php | 2 +- tests/phpt/colors/016_colored_resumable_2.php | 2 +- tests/phpt/curl/10_curl_multi_info_read.php | 2 +- tests/phpt/curl/11_curl_multi_error.php | 2 +- tests/phpt/curl/12_curl_reset.php | 2 +- tests/phpt/curl/1_curl_init.php | 2 +- tests/phpt/curl/2_curl_setopt.php | 2 +- tests/phpt/curl/3_curl_error.php | 2 +- tests/phpt/curl/4_curl_get_info.php | 2 +- tests/phpt/curl/5_curl_multi_init.php | 2 +- tests/phpt/curl/6_curl_multi_add_handle.php | 2 +- tests/phpt/curl/7_curl_multi_setopt.php | 2 +- tests/phpt/curl/8_curl_multi_exec.php | 2 +- tests/phpt/curl/9_curl_multi_select.php | 2 +- tests/phpt/datetime/01_format.php | 2 +- tests/phpt/datetime/02_create.php | 2 +- tests/phpt/datetime/03_set_timestamp.php | 2 +- tests/phpt/datetime/04_modify.php | 2 +- .../datetime/05_modify_wrong_modifier.php | 2 +- tests/phpt/datetime/06_set_date.php | 2 +- tests/phpt/datetime/07_set_time.php | 2 +- tests/phpt/datetime/08_date_time_zone.php | 2 +- tests/phpt/datetime/09_create_from_format.php | 2 +- tests/phpt/datetime/10_get_offset.php | 2 +- .../datetime/11_mutability_conversions.php | 2 +- tests/phpt/datetime/12_create_interval.php | 2 +- .../13_create_interval_from_date_string.php | 2 +- .../14_create_interval_wrong_date.php | 2 +- tests/phpt/datetime/15_interval_format.php | 2 +- .../datetime/16_add_sub_date_interval.php | 2 +- .../17_sub_relative_interval_warning.php | 2 +- tests/phpt/datetime/18_diff_date_interval.php | 2 +- tests/phpt/declare/01_strict_types.php | 2 +- tests/phpt/declare/02_strict_types2.php | 2 +- tests/phpt/declare/03_strict_types_error.php | 2 +- .../declare/09_strict_types_not_enabled.php | 2 +- .../phpt/declare/10_strict_types_enabled.php | 2 +- .../declare/11_strict_types_enabled_error.php | 2 +- tests/phpt/dl/1003_array_pad.php | 2 +- tests/phpt/dl/1005_op_pow.php | 2 +- tests/phpt/dl/1006_array_diff_assoc.php | 2 +- tests/phpt/dl/1007_array_intersect_assoc.php | 2 +- tests/phpt/dl/1008_array_column.php | 2 +- tests/phpt/dl/1010_const_vars.php | 2 +- .../dl/1011_cast_const_array_init_value.php | 2 +- tests/phpt/dl/1012_array_key_exists.php | 2 +- tests/phpt/dl/1015_parse_url.php | 2 +- tests/phpt/dl/1016_array_first_key.php | 2 +- tests/phpt/dl/1018_regex_many_groups.php | 2 +- ...22_change_order_for_assignment_to_list.php | 2 +- tests/phpt/dl/1023_short_list_assignment.php | 2 +- .../phpt/dl/1025_fail_sprintf_dollar_sign.php | 2 +- tests/phpt/dl/1025_sprintf_dollar_sign.php | 2 +- tests/phpt/dl/1026_hrtime.php | 2 +- tests/phpt/dl/1027_math.php | 2 +- tests/phpt/dl/1028_math_constants.php | 2 +- tests/phpt/dl/1029_inf.php | 2 +- tests/phpt/dl/1034_to_array_debug_shape.php | 2 +- tests/phpt/dl/1035_to_array_debug_tuple.php | 2 +- .../dl/1036_to_array_debug_shape_nullable.php | 2 +- .../dl/1037_to_array_debug_shape_define.php | 2 +- tests/phpt/dl/1040_exec.php | 2 +- tests/phpt/dl/1040_nohint_always_throw.php | 2 +- tests/phpt/dl/1041_exec_warnings.php | 2 +- tests/phpt/dl/1041_getenv.php | 2 +- tests/phpt/dl/1042_escapeshell.php | 2 +- tests/phpt/dl/1043_some_globals.php | 2 +- tests/phpt/dl/385_random_bytes.php | 2 +- tests/phpt/dl/386_random_int.php | 2 +- tests/phpt/dl/387_mt_rand.php | 2 +- tests/phpt/dl/388_bindechex.php | 2 +- tests/phpt/dl/389_regexp.php | 2 +- tests/phpt/dl/390_int_to_string.php | 2 +- tests/phpt/dl/392_int64_indexing.php | 2 +- tests/phpt/dl/393_kphp_backtrace.php | 2 +- tests/phpt/dl/394_similar_text.php | 2 +- .../phpt/dl/395_subnormal_doubles_in_json.php | 2 +- tests/phpt/dl/402_isnan.php | 2 +- tests/phpt/dl/403_shutdown.php | 2 +- tests/phpt/dl/414_exception.php | 2 +- tests/phpt/dl/417_unset.php | 2 +- tests/phpt/dl/429_addslashes.php | 2 +- tests/phpt/dl/430_htmlentities.php | 2 +- tests/phpt/dl/431_mb_check_encoding.php | 2 +- tests/phpt/dl/432_rawurlencode.php | 2 +- tests/phpt/dl/433_nl2br.php | 2 +- tests/phpt/dl/435_base64_encode.php | 2 +- tests/phpt/dl/440_str_replace_char.php | 2 +- tests/phpt/dl/441_vk_json.php | 2 +- tests/phpt/dl/443_vector.php | 2 +- tests/phpt/dl/444_serialize.php | 2 +- tests/phpt/dl/445_divide.php | 2 +- tests/phpt/dl/452_isset.php | 2 +- tests/phpt/dl/455_inc_xor_str.php | 2 +- tests/phpt/dl/457_eq2.php | 2 +- tests/phpt/dl/459_strtotime.php | 2 +- tests/phpt/dl/460_OrFalse.php | 2 +- tests/phpt/dl/462_foreach.php | 2 +- tests/phpt/dl/463_replace.php | 2 +- tests/phpt/dl/464_gzip.php | 2 +- tests/phpt/dl/465_pack.php | 2 +- tests/phpt/dl/465_pack64bit.php | 2 +- tests/phpt/dl/467_rawid2long.php | 2 +- tests/phpt/dl/468_convertToXML.php | 2 +- tests/phpt/dl/469_str_replace.php | 2 +- tests/phpt/dl/470_fasta.php | 2 +- tests/phpt/dl/471_pfannkuchen.php | 2 +- tests/phpt/dl/472_nbody.php | 2 +- tests/phpt/dl/473_file.php | 2 +- tests/phpt/dl/475_convert_to_win.php | 2 +- tests/phpt/dl/476_getimagesize.php | 2 +- tests/phpt/dl/477_json.php | 2 +- tests/phpt/dl/478_strip_tags.php | 2 +- tests/phpt/dl/480_old_test.php | 2 +- tests/phpt/dl/481_parse_str.php | 2 +- .../dl/481_parse_str_with_empty_string.php | 2 +- tests/phpt/dl/482_ob.php | 2 +- tests/phpt/dl/483_bcmath.php | 2 +- tests/phpt/dl/483_bcmod_warnings.php | 2 +- tests/phpt/dl/483_bcpow_warnings.php | 2 +- tests/phpt/dl/483_bcsqrt_warnings.php | 2 +- tests/phpt/dl/484_strftime.php | 2 +- tests/phpt/dl/485_str_pad.php | 2 +- tests/phpt/dl/486_base_convert.php | 2 +- tests/phpt/dl/487_strtr.php | 2 +- tests/phpt/dl/488_ipv6.php | 2 +- tests/phpt/dl/489_hexdec.php | 2 +- tests/phpt/dl/490_serialize.php | 2 +- tests/phpt/dl/491_strpos.php | 2 +- tests/phpt/dl/492_array_map.php | 2 +- tests/phpt/dl/493_number_format.php | 2 +- tests/phpt/dl/495_sort.php | 2 +- tests/phpt/dl/496_regex.php | 2 +- tests/phpt/dl/497_preg_split.php | 2 +- tests/phpt/dl/499_vk_json_encode_safe.php | 2 +- tests/phpt/dl/503_strrchr.php | 2 +- tests/phpt/dl/504_array_keys_as.php | 2 +- tests/phpt/dl/667_geoip.php | 2 +- tests/phpt/dl/668_bcpow.php | 2 +- tests/phpt/dl/669_bcmod.php | 2 +- tests/phpt/dl/948_hashmap.php | 2 +- tests/phpt/dl/949.php | 2 +- tests/phpt/dl/951_infer.php | 2 +- tests/phpt/dl/952_cfg.php | 2 +- tests/phpt/dl/952_cfg_uninited.php | 2 +- tests/phpt/dl/954_infer.php | 2 +- tests/phpt/dl/956_dna_regex.php | 2 +- tests/phpt/dl/957_concat.php | 2 +- tests/phpt/dl/959_default_args.php | 2 +- tests/phpt/dl/960_push_back.php | 2 +- tests/phpt/dl/961_const_data.php | 2 +- tests/phpt/dl/964_try.php | 2 +- tests/phpt/dl/965_bin_func.php | 2 +- tests/phpt/dl/968_valist.php | 2 +- tests/phpt/dl/970_line.php | 2 +- tests/phpt/dl/974_callback.php | 2 +- tests/phpt/dl/975_bug.php | 2 +- tests/phpt/dl/976_sprintf.php | 2 +- tests/phpt/dl/980_push.php | 2 +- tests/phpt/dl/981_chr.php | 2 +- .../dl/985_benchmark_sootout_01_lists.php | 2 +- tests/phpt/dl/993_benchmark_primes.php | 2 +- tests/phpt/dl/999_misc_001.php | 2 +- tests/phpt/dl/switch/03_string.php | 2 +- .../phpt/errors/004_fork_builtin_function.php | 2 +- tests/phpt/errors/005_sort_tuple_array.php | 2 +- tests/phpt/exceptions/01_catched_as_arg.php | 2 +- .../02_catch_non_exception_error.php | 2 +- .../exceptions/04_catch_nonexisting_error.php | 2 +- .../05_catch_nonexisting_error2.php | 2 +- .../exceptions/06_deeply_nested_rethrow.php | 2 +- tests/phpt/exceptions/07_error_methods.php | 2 +- .../phpt/exceptions/08_exception_methods.php | 2 +- tests/phpt/exceptions/11_mixed_message.php | 2 +- .../phpt/exceptions/12_throwable_methods.php | 2 +- tests/phpt/exceptions/13_throw_from_arg.php | 2 +- .../phpt/exceptions/14_throw_from_foreach.php | 2 +- .../16_throw_from_parent_constructor.php | 2 +- .../phpt/exceptions/18_set_location_error.php | 2 +- .../phpt/exceptions/examples/01_example4.php | 2 +- .../phpt/exceptions/examples/02_example7.php | 2 +- .../inheritance/01_abstract_exception.php | 2 +- .../inheritance/02_access_inherited_props.php | 2 +- .../03_catch_derived_interface.php | 2 +- .../inheritance/04_catch_throwable.php | 2 +- .../inheritance/05_catch_throwable2.php | 2 +- .../exceptions/inheritance/06_clone_error.php | 2 +- .../exceptions/inheritance/07_contrived.php | 2 +- .../inheritance/08_contrived_global.php | 2 +- .../inheritance/09_cross_call_bail.php | 2 +- .../inheritance/10_cross_call_bail2.php | 2 +- .../inheritance/11_cross_call_bail3.php | 2 +- .../inheritance/12_cross_call_bail4.php | 2 +- .../exceptions/inheritance/13_custom_ctor.php | 2 +- .../inheritance/14_default_ctor.php | 2 +- .../exceptions/inheritance/15_get_class.php | 2 +- .../exceptions/inheritance/16_inner_outer.php | 2 +- .../exceptions/inheritance/17_instanceof.php | 2 +- .../inheritance/18_instanceof_in_catch.php | 2 +- .../inheritance/19_override_clone_error.php | 2 +- .../inheritance/20_override_custom_method.php | 2 +- .../inheritance/21_override_method_error.php | 2 +- .../inheritance/22_rethrow_derived.php | 2 +- .../inheritance/23_static_var_throw.php | 2 +- .../exceptions/inheritance/24_throw_error.php | 2 +- .../inheritance/25_throw_from_catch.php | 2 +- .../inheritance/26_throw_from_constructor.php | 2 +- .../inheritance/27_throw_from_lambda.php | 2 +- .../inheritance/28_throw_from_lambda2.php | 2 +- .../inheritance/29_throw_from_lambda3.php | 2 +- .../inheritance/30_throw_func_result.php | 2 +- .../32_throw_non_exception_error2.php | 2 +- .../inheritance/33_throw_throwable.php | 2 +- .../exceptions/inheritance/34_throw_var.php | 2 +- .../inheritance/36_try_in_catch.php | 2 +- .../inheritance/37_with_implements.php | 2 +- .../inheritance/38_with_implements2.php | 2 +- .../inheritance/39_with_implements3.php | 2 +- .../inheritance/40_with_instanceof.php | 2 +- .../multicatch/01_catch_interface.php | 2 +- .../exceptions/multicatch/02_catch_same.php | 2 +- .../multicatch/03_catch_unused_class.php | 2 +- .../multicatch/04_catch_unused_class2.php | 2 +- .../multicatch/05_catch_unused_class3.php | 2 +- .../multicatch/06_catch_unused_class4.php | 2 +- .../multicatch/07_catch_unused_class5.php | 2 +- .../exceptions/multicatch/08_multi_catch.php | 2 +- .../exceptions/multicatch/09_multi_catch2.php | 2 +- .../multicatch/10_throw_from_catch2.php | 2 +- .../multicatch/11_var_shadowing.php | 2 +- .../multicatch/12_var_shadowing_2.php | 2 +- .../exceptions/nothrow/01_catches_all.php | 2 +- .../exceptions/nothrow/02_catches_all2.php | 2 +- .../exceptions/nothrow/03_catches_all3.php | 2 +- .../exceptions/nothrow/04_catches_all4.php | 2 +- .../exceptions/nothrow/05_nothrow_error.php | 2 +- .../exceptions/nothrow/06_nothrow_error2.php | 2 +- .../phpt/exceptions/spl/01_builtin_errors.php | 2 +- .../exceptions/spl/02_builtin_exceptions.php | 2 +- tests/phpt/exceptions/spl/03_extend_spl.php | 2 +- .../test_attr_throws/01_test_attr_throws.php | 2 +- .../test_attr_throws/02_test_attr_throws2.php | 2 +- .../test_attr_throws/03_test_attr_throws3.php | 2 +- .../test_attr_throws/04_test_attr_throws4.php | 2 +- .../test_attr_throws/05_test_attr_throws5.php | 2 +- .../test_attr_throws/06_test_attr_throws6.php | 2 +- .../test_attr_throws/07_test_attr_throws7.php | 2 +- .../test_attr_throws/08_test_attr_throws8.php | 2 +- .../test_attr_throws/09_test_attr_throws9.php | 2 +- .../10_test_attr_throws10.php | 2 +- .../11_test_attr_throws_error.php | 2 +- .../12_test_attr_throws_error2.php | 2 +- .../phpt/ffi/001_bad_struct_in_new_error.php | 2 +- .../ffi/002_bad_type_in_static_new_error.php | 2 +- tests/phpt/ffi/004_to_array_debug_error.php | 2 +- .../phpt/ffi/005_capturing_callback_error.php | 2 +- .../phpt/ffi/006_capturing_callback_error.php | 2 +- .../phpt/ffi/007_capturing_callback_error.php | 2 +- .../phpt/ffi/008_capturing_callback_error.php | 2 +- .../phpt/ffi/009_throwing_callback_error.php | 2 +- .../phpt/ffi/010_throwing_callback_error.php | 2 +- .../phpt/ffi/011_resumable_callback_error.php | 2 +- .../parsing/001_bad_syntax_in_new_error.php | 2 +- .../002_bad_syntax_in_static_new_error.php | 2 +- .../phpt/ffi/typing/001_addr_to_int_error.php | 2 +- .../ffi/typing/002_addr_to_int_error2.php | 2 +- .../ffi/typing/003_addr_to_int_error3.php | 2 +- .../ffi/typing/004_addr_to_int_error4.php | 2 +- .../phpt/ffi/typing/005_assign_void_error.php | 2 +- .../ffi/typing/008_cdata_field_read_error.php | 2 +- .../typing/009_cdata_field_write_error.php | 2 +- .../ffi/typing/011_cstr_field_write_error.php | 2 +- .../typing/012_func_call_argcount_error.php | 2 +- .../typing/013_func_call_argcount_error2.php | 2 +- .../typing/014_func_call_arg_types_error.php | 2 +- .../typing/015_func_call_arg_types_error2.php | 2 +- .../typing/016_func_call_arg_types_error3.php | 2 +- .../017_func_call_unnamed_arg_types_error.php | 2 +- ...018_func_call_unnamed_arg_types_error2.php | 2 +- .../019_incompatible_num_types_error.php | 2 +- .../020_incompatible_num_types_error2.php | 2 +- .../021_incompatible_num_types_error3.php | 2 +- .../022_incompatible_num_types_error4.php | 2 +- .../023_incompatible_ptr_types_error.php | 2 +- .../024_incompatible_ptr_types_error2.php | 2 +- .../025_incompatible_ptr_types_error3.php | 2 +- .../026_incompatible_ptr_types_error4.php | 2 +- .../027_incompatible_ptr_types_error5.php | 2 +- .../028_incompatible_ptr_types_error6.php | 2 +- .../029_incompatible_struct_types_error.php | 2 +- .../030_incompatible_struct_types_error2.php | 2 +- .../031_nonexisting_field_access_error.php | 2 +- .../032_nonexisting_func_call_error.php | 2 +- .../033_nonexisting_func_call_error2.php | 2 +- .../034_nonexisting_func_call_error3.php | 2 +- .../phpt/ffi/typing/035_ref_passing_error.php | 2 +- .../ffi/typing/036_ref_passing_error2.php | 2 +- .../037_incompatible_cdata_type_error.php | 2 +- .../038_incompatible_cdata_type_error.php | 2 +- .../042_mixed_union_with_struct_error.php | 2 +- .../043_mixed_union_with_struct_error.php | 2 +- .../ffi/typing/046_invalid_cast_error.php | 2 +- .../ffi/typing/047_invalid_cast_error.php | 2 +- .../ffi/typing/048_invalid_cast_error.php | 2 +- .../ffi/typing/049_invalid_cast_error.php | 2 +- .../ffi/typing/050_cast_ref_passing_error.php | 2 +- .../ffi/typing/051_cast_ref_passing_error.php | 2 +- .../phpt/ffi/typing/052_string_arg_error.php | 2 +- .../ffi/typing/053_bad_field_in_tpl_error.php | 2 +- .../ffi/typing/054_array_index_type_error.php | 2 +- tests/phpt/ffi/typing/054_const_ptr_error.php | 2 +- .../ffi/typing/055_array_set_type_error.php | 2 +- tests/phpt/ffi/typing/055_const_ptr_error.php | 2 +- .../ffi/typing/056_array_set_type_error.php | 2 +- tests/phpt/ffi/typing/056_const_ptr_error.php | 2 +- .../ffi/typing/057_array_set_ref_error.php | 2 +- .../phpt/ffi/typing/058_array_cast_error.php | 2 +- .../typing/059_array_set_index_type_error.php | 2 +- tests/phpt/ffi/typing/060_array_get_error.php | 2 +- tests/phpt/ffi/typing/061_array_get_error.php | 2 +- tests/phpt/ffi/typing/062_array_set_error.php | 2 +- .../ffi/typing/063_nullable_cstr_error.php | 2 +- .../ffi/typing/064_nullable_cstr_error.php | 2 +- .../ffi/typing/065_optional_php2c_error.php | 2 +- .../ffi/typing/066_callback_return_error.php | 2 +- .../ffi/typing/067_callback_return_error.php | 2 +- .../ffi/typing/068_callback_return_error.php | 2 +- .../ffi/typing/069_callback_return_error.php | 2 +- .../ffi/typing/070_callback_params_error.php | 2 +- .../ffi/typing/071_callback_params_error.php | 2 +- .../ffi/typing/072_callback_params_error.php | 2 +- .../ffi/typing/073_array_ptr_cast_error.php | 2 +- .../ffi/typing/074_array_ptr_cast_error.php | 2 +- .../ffi/typing/075_callback_arg_error.php | 2 +- .../phpt/ffi/warnings/001_zero_size_array.php | 2 +- tests/phpt/fork/001_basic.php | 2 +- tests/phpt/fork/002_wait_multiple.php | 2 +- tests/phpt/fork/003_sched_yield.php | 2 +- tests/phpt/fork/004_sched_yield_sleep.php | 2 +- tests/phpt/fork/005_fork_primitives.php | 2 +- tests/phpt/fork/006_fork_tuple.php | 2 +- tests/phpt/fork/007_fork_instance.php | 2 +- tests/phpt/fork/008_fork_exception.php | 2 +- tests/phpt/fork/008_fork_exception2.php | 2 +- tests/phpt/fork/009_fork_void.php | 2 +- tests/phpt/fork/010_wait.php | 2 +- tests/phpt/fork/011_queue.php | 2 +- tests/phpt/fork/012_exceptions.php | 2 +- tests/phpt/fork/013_variadic.php | 2 +- tests/phpt/fork/014_wait_global.php | 2 +- tests/phpt/fork/015_save_to_instance_prop.php | 2 +- tests/phpt/fork/017_exit_from_fork.php | 2 +- tests/phpt/fork/018_exit_from_fork.php | 2 +- ...019_ternary_operator_in_resumable_expr.php | 2 +- ...20_logical_operators_in_resumable_expr.php | 2 +- .../021_null_coalesce_in_resumable_expr.php | 2 +- .../fork/023_op_set_in_resumable_expr.php | 2 +- .../025_foreach_by_global_var_ref_error.php | 2 +- ..._foreach_by_class_static_var_ref_error.php | 2 +- .../027_foreach_by_static_var_ref_error.php | 2 +- ...28_foreach_by_instance_field_ref_error.php | 2 +- ...s_resumable_lambda_to_builtin_function.php | 2 +- ...e_resumable_lambda_to_builtin_function.php | 2 +- tests/phpt/fork/041_wait_with_timeout.php | 2 +- tests/phpt/func_specializations/hrtime.php | 2 +- .../func_specializations/hrtime_error1.php | 2 +- .../func_specializations/hrtime_error2.php | 2 +- tests/phpt/func_specializations/microtime.php | 2 +- .../func_specializations/microtime_error1.php | 2 +- .../func_specializations/microtime_error2.php | 2 +- .../generics/016_kphp_param_depends_T.php | 2 +- .../generics/017_templates_primitives.php | 2 +- tests/phpt/generics/018_classof_keyword.php | 2 +- .../generics/019_templates_and_lambdas.php | 2 +- .../023_generic_method_in_interface.php | 2 +- tests/phpt/generics/024_wrappers_for_json.php | 2 +- tests/phpt/generics/026_variadic_generics.php | 2 +- tests/phpt/generics/027_constexpr_switch.php | 2 +- .../generics/028_constexpr_services_cont.php | 2 +- .../generics/123_no_use_in_lambda_typeof.php | 2 +- .../10_instance_cache_abstract_error.php | 2 +- .../11_instance_cache_polymprphic_field.php | 2 +- ...instance_cache_polymprphic_field_error.php | 2 +- .../phpt/instance_cache/1_instance_cache.php | 2 +- .../2_instance_cache_mutable_store_error.php | 2 +- .../3_instance_cache_mutable_fetch_error.php | 2 +- .../4_instance_cache_non_instance_error.php | 2 +- .../5_instance_cache_polymorphic_simple.php | 2 +- .../6_instance_cache_polymorphic_error.php | 2 +- .../7_instance_cache_interface.php | 2 +- .../8_instance_cache_interface_error.php | 2 +- .../9_instance_cache_abstract.php | 2 +- .../interfaces/007_varargs_in_interface.php | 2 +- .../023_variadic_and_kphp_infer.php | 2 +- .../024_instanceof_inside_static_method.php | 2 +- .../job_workers/1_interface_compilation.php | 2 +- ...d_immutable_messages_basic_compilation.php | 2 +- .../3_mutable_shared_memory_piece.php | 2 +- ..._not_single_shared_memory_piece_member.php | 2 +- tests/phpt/json/102_encoder_warnings.php | 2 +- .../json/108_tuples_and_shapes_forbidden.php | 2 +- tests/phpt/json/10_array_nested.php | 2 +- tests/phpt/json/114_field_reorder.php | 2 +- tests/phpt/json/11_array_of_mixed.php | 2 +- .../12_array_string_int_key_conversions.php | 2 +- tests/phpt/json/13_nan_inf.php | 2 +- tests/phpt/json/14_override_json_keys.php | 2 +- tests/phpt/json/15_default_values.php | 2 +- tests/phpt/json/16_json_obj_keys_order.php | 2 +- tests/phpt/json/18_rename_field_policy.php | 2 +- tests/phpt/json/19_skip_field.php | 2 +- tests/phpt/json/1_basic_types.php | 2 +- tests/phpt/json/20_fields_rename.php | 2 +- tests/phpt/json/21_visibility_policy.php | 2 +- tests/phpt/json/22_skip_if_default.php | 2 +- tests/phpt/json/23_float_precision.php | 2 +- tests/phpt/json/24_flatten_classes.php | 2 +- tests/phpt/json/25_pretty_print.php | 2 +- tests/phpt/json/26_raw_string.php | 2 +- tests/phpt/json/27_encoder_namespaces.php | 2 +- tests/phpt/json/28_strings_escape.php | 2 +- tests/phpt/json/29_derived_encode.php | 2 +- tests/phpt/json/2_null_object.php | 2 +- tests/phpt/json/30_more_encode_array.php | 2 +- tests/phpt/json/31_russian_cp1251.php | 2 +- tests/phpt/json/31_russian_utf8.php | 2 +- tests/phpt/json/32_preserve_zero_fraction.php | 2 +- tests/phpt/json/33_required_fields.php | 2 +- tests/phpt/json/34_specified_fields.php | 2 +- tests/phpt/json/35_for_encoder.php | 2 +- tests/phpt/json/36_wakeup_after_decode.php | 2 +- tests/phpt/json/3_nested_objects.php | 2 +- tests/phpt/json/4_mixed_values.php | 2 +- tests/phpt/json/5_optional_values.php | 2 +- tests/phpt/json/6_invalid_json.php | 2 +- tests/phpt/json/7_conversion_errors.php | 2 +- tests/phpt/json/8_array_vector.php | 2 +- tests/phpt/json/9_array_map.php | 2 +- tests/phpt/lambdas/004_return_lambda.php | 2 +- .../lambdas/007_use_lambda_inside_class.php | 2 +- .../010_pass_lambda_to_internal_functions.php | 2 +- tests/phpt/lambdas/016_typed_array_filter.php | 2 +- tests/phpt/lambdas/017_typed_array_map.php | 2 +- tests/phpt/lambdas/018_typed_array_reduce.php | 2 +- tests/phpt/lambdas/019_unreachable_lambda.php | 2 +- .../022_implicit_capturing_of_this.php | 2 +- .../lambdas/023_deduce_typed_callable.php | 2 +- ...026_pass_string_into_callable_argument.php | 2 +- ..._as_argument_of_lambda_without_php_doc.php | 2 +- ...through_variable_to_internal_functions.php | 2 +- ...pass_class_method_as_lambda_to_builtin.php | 2 +- ...mplate_internal_functions_as_callbacks.php | 2 +- tests/phpt/lambdas/049_forks_and_lambdas.php | 2 +- .../lambdas/051_lambdas_and_variadics.php | 2 +- .../phpt/lambdas/052_modify_captured_vars.php | 2 +- .../054_lambdas_capture_smart_casts.php | 2 +- .../104_empty_array_in_extern_function.php | 2 +- .../lambdas/112_invalid_callbacks_invoked.php | 2 +- .../113_array_map_void_callback_fail.php | 2 +- .../115_use_refs_in_array_map_fail.php | 2 +- ...25_callback_with_complicated_type_rule.php | 2 +- tests/phpt/memory_usage/10_arrays.php | 2 +- tests/phpt/memory_usage/11_shapes.php | 2 +- tests/phpt/memory_usage/1_primitive_types.php | 2 +- tests/phpt/memory_usage/2_classes.php | 2 +- tests/phpt/memory_usage/3_lambdas.php | 2 +- tests/phpt/memory_usage/4_interfaces.php | 2 +- .../memory_usage/5_polymorphic_classes.php | 2 +- .../6_get_global_vars_memory_stats.php | 2 +- .../7_auto_memory_defragmentation.php | 2 +- ...lobal_vars_memory_stats_disabled_error.php | 2 +- tests/phpt/memory_usage/9_memory_allocs.php | 2 +- ...03_string_number_comparison_array_func.php | 2 +- .../001_simple_yaml_project.php | 2 +- .../005_inheritance/005_inheritance.php | 2 +- .../007_composer_ok/007_composer_ok.php | 2 +- .../123_call_composer_submod.php | 2 +- .../phpt/msgpack_serialize/000_primitives.php | 2 +- .../000_primitives_hints.php | 2 +- .../msgpack_serialize/001_non_primitives.php | 2 +- .../002_class_inside_class.php | 2 +- .../003_exception_handling.php | 2 +- tests/phpt/msgpack_serialize/004_none_tag.php | 2 +- .../msgpack_serialize/005_with_namespaces.php | 2 +- .../005_with_namespaces_hints.php | 2 +- .../006_recursive_instances.php | 2 +- .../007_msgpack_serialize_var.php | 2 +- .../008_constructor_inside_serializable.php | 2 +- .../msgpack_serialize/009_private_fields.php | 2 +- .../msgpack_serialize/010_with_traits.php | 2 +- .../msgpack_serialize/011_russian_letters.php | 2 +- tests/phpt/msgpack_serialize/013_map.php | 2 +- .../014_deserialize_safe.php | 2 +- .../015_serialize_with_base.php | 2 +- .../016_comments_with_field_tag.php | 2 +- .../017_deserialize_huge_array.php | 2 +- ...018_serialize_class_with_or_null_field.php | 2 +- ...018_skip_fields_during_deserialization.php | 2 +- .../019_serialize_vector_instead_of_map.php | 2 +- .../msgpack_serialize/020_int64_as_mixed.php | 2 +- .../021_serialize_float32.php | 2 +- .../022_cmp_float32_float.php | 2 +- .../023_compound_float32.php | 2 +- .../024_serialize_vector.php | 2 +- .../msgpack_serialize/025_bool_or_null.php | 2 +- .../msgpack_serialize/100_duplicated_tags.php | 2 +- .../msgpack_serialize/101_bad_tag_value.php | 2 +- .../103_serialize_with_base.php | 2 +- .../msgpack_serialize/105_skipped_tag.php | 2 +- .../106_kphp_tag_static_field.php | 2 +- .../107_serialize_pure_interface.php | 2 +- .../msgpack_serialize/108_empty_field_id.php | 2 +- .../msgpack_serialize/109_wrong_field_id.php | 2 +- ...0_deserialize_class_without_annotation.php | 2 +- .../111_non_serializable_inner_class.php | 2 +- .../msgpack_serialize/112_field_reorder.php | 2 +- tests/phpt/nn/002_array_reduce.php | 2 +- .../phpt/nn/005_json_encode_force_object.php | 2 +- tests/phpt/nn/006_substr_replace.php | 2 +- tests/phpt/nn/007_preg_match_offset.php | 2 +- tests/phpt/nn/008_fgetcsv_fputcsv.php | 2 +- tests/phpt/nn/009_strnatcmp.php | 2 +- tests/phpt/nn/010_natsort.php | 2 +- tests/phpt/nn/011_substr_compare.php | 2 +- tests/phpt/null_coalescing/10_shapes.php | 2 +- tests/phpt/null_coalescing/11_assumptions.php | 2 +- tests/phpt/null_coalescing/12_assignment.php | 2 +- .../13_assignment_call_rhs.php | 2 +- .../14_assignment_throw_exceptions.php | 2 +- tests/phpt/null_coalescing/1_variables.php | 2 +- tests/phpt/null_coalescing/2_arrays.php | 2 +- tests/phpt/null_coalescing/3_string.php | 2 +- tests/phpt/null_coalescing/4_classes.php | 2 +- tests/phpt/null_coalescing/5_type_infer.php | 2 +- .../null_coalescing/8_throw_exceptions.php | 2 +- tests/phpt/null_coalescing/9_tuples.php | 2 +- tests/phpt/openssl/1_openssl_basic.php | 2 +- tests/phpt/openssl/2_openssl_verify.php | 2 +- tests/phpt/openssl/3_openssl_x509_parse.php | 2 +- tests/phpt/openssl/4_openssl_cipher.php | 2 +- tests/phpt/openssl/5_hash.php | 2 +- tests/phpt/openssl/6_openssl_pkcs7_sign.php | 2 +- tests/phpt/openssl/7_openssl_cipher_gcm.php | 2 +- tests/phpt/openssl/8_openssl_x509_verify.php | 2 +- tests/phpt/optimizations/010_str_concat.php | 2 +- tests/phpt/optimizations/011_md5concat.php | 2 +- tests/phpt/optional/001_simple_operations.php | 2 +- tests/phpt/optional/002_array_operations.php | 2 +- tests/phpt/optional/003_serialization.php | 2 +- tests/phpt/optional/004_operations.php | 2 +- tests/phpt/optional/005_string_functions.php | 2 +- .../008_return_optional_from_fork.php | 2 +- .../10_constant_execution_in_loop_no_warn.php | 2 +- .../13_warn_tag_for_class.php | 2 +- .../14_warn_tag_for_interface.php | 2 +- .../5_array_merge_into_warn.php | 2 +- .../6_array_merge_into_no_warn.php | 2 +- .../8_array_reserve_no_warn.php | 2 +- .../9_constant_execution_in_loop_warn.php | 2 +- .../phpt/phc/parsing/allowed_offset_types.php | 2 +- tests/phpt/phc/parsing/assignments.php | 2 +- tests/phpt/phc/parsing/binops1.php | 2 +- tests/phpt/phc/parsing/binops3.php | 2 +- tests/phpt/phc/parsing/binops4.php | 2 +- tests/phpt/phc/parsing/die.php | 2 +- tests/phpt/phc/parsing/impure.php | 2 +- tests/phpt/phc/parsing/negative_literals.php | 2 +- .../parsing/php7_4/arglist_trailing_comma.php | 2 +- tests/phpt/phc/parsing/php7_4/arrowfunc.php | 2 +- .../php7_4/arrowfunc_undef_refparam_error.php | 2 +- tests/phpt/phpdocs/021_failing_callbacks.php | 2 +- .../phpt/phpdocs/026_mix_assum_and_subkey.php | 2 +- .../phpdocs/027_type_hints_with_phpdocs.php | 2 +- .../phpdocs/030_hint_but_always_throw.php | 2 +- tests/phpt/pk/004_OrFalse.php | 2 +- tests/phpt/pk/005_date.php | 2 +- tests/phpt/pk/006_http_build.php | 2 +- tests/phpt/pk/010_array_search.php | 2 +- tests/phpt/pk/011_substr.php | 2 +- tests/phpt/pk/012_array_fill.php | 2 +- tests/phpt/pk/015_header.php | 2 +- tests/phpt/pk/016_gzip.php | 2 +- tests/phpt/pk/021_minmax.php | 2 +- tests/phpt/pk/025_strtotime_next_week_day.php | 2 +- tests/phpt/pk/026_no_return.php | 2 +- .../phpt/pk/029_date_default_timezone_get.php | 2 +- tests/phpt/regexp/001_string_result.php | 2 +- tests/phpt/regexp/002_null_error.php | 2 +- .../phpt/regexp/003_preg_match_neg_offset.php | 2 +- .../regexp/004_preg_match_high_offset.php | 2 +- tests/phpt/regexp/005_regexp_match2.php | 2 +- tests/phpt/regexp/006_preg_quote.php | 2 +- .../007_preg_offset_capture_padding.php | 2 +- tests/phpt/regexp/008_preg_errors.php | 2 +- tests/phpt/shapes/013_shape_inside_fork.php | 2 +- .../shapes/014_concat_arrays_of_shapes.php | 2 +- tests/phpt/shapes/015_shape_from_fork.php | 2 +- tests/phpt/shapes/019_shape_to_array.php | 2 +- .../shutdown_functions/no_leak_in_lambda.php | 2 +- .../no_leak_in_lambda_error.php | 2 +- .../phpt/shutdown_functions/throw_error1.php | 2 +- .../phpt/shutdown_functions/throw_error2.php | 2 +- .../phpt/shutdown_functions/throw_error3.php | 2 +- .../phpt/smart_casts/05_inside_operators.php | 2 +- tests/phpt/spl/array_iterator.php | 2 +- tests/phpt/spread/001_spread.php | 2 +- tests/phpt/sprintf/001_sprintf.php | 2 +- tests/phpt/sprintf/002_vprintf.php | 2 +- tests/phpt/stacktrace/10_stacktrace.php | 2 +- tests/phpt/streams/fgetcsv.php | 2 +- tests/phpt/streams/fputcsv.php | 2 +- .../string_functions/001_strip_tags_7_4.php | 2 +- .../string_functions/002_substr_replace.php | 2 +- .../string_functions/003_str_ireplace.php | 2 +- .../003_strip_tags_mixed_arg.php | 2 +- .../string_functions/004_vk_win_to_utf.php | 2 +- tests/phpt/string_functions/007_strspn.php | 2 +- tests/phpt/string_functions/008_misc.php | 2 +- .../phpt/string_functions/009_tmp_string.php | 2 +- tests/phpt/tcp/basic_connections.php | 2 +- tests/phpt/tcp/connection_options.php | 2 +- tests/phpt/timelib/checkdate.php | 2 +- tests/phpt/timelib/strtotime.php | 2 +- tests/phpt/timelib/strtotime_relative.php | 2 +- tests/phpt/tup/013_tuple_inside_fork.php | 2 +- tests/phpt/tup/015_tuple_from_fork.php | 2 +- .../params/08_classes_and_scalars.php | 2 +- .../typehints/params/variadic/02_classes.php | 2 +- .../typehints/params/variadic/03_nullable.php | 2 +- .../return_types/01_scalar_typehints.php | 2 +- tests/phpt/uber_h3/1_indexing.php | 2 +- tests/phpt/uber_h3/2_index_inspection.php | 2 +- tests/phpt/uber_h3/3_grid_traversal.php | 2 +- tests/phpt/uber_h3/4_hierarchical_grid.php | 2 +- tests/phpt/uber_h3/5_region.php | 2 +- .../utils/01_kphp_should_not_throw_phpdoc.php | 2 +- ...register_on_kphp_warning_callback_test.php | 2 +- .../000_simple_fun_with_variadic_args.php | 2 +- .../001_fun_with_some_args_before.php | 2 +- .../002_method_with_variadics.php | 2 +- .../003_modification_variadic.php | 2 +- .../004_simple_unpacking_array.php | 2 +- .../005_unpacking_with_positional_args.php | 2 +- .../006_unpacking_several_variadics.php | 2 +- .../variadic_args/007_forward_varargs.php | 2 +- .../008_unpacking_not_variables.php | 2 +- .../variadic_args/009_variadic_lambdas.php | 2 +- .../010_variadic_with_callable.php | 2 +- ..._pass_callable_with_variadic_as_string.php | 2 +- .../variadic_args/012_php_doc_variadic.php | 2 +- .../phpt/variadic_args/013_varg_phpdoc_1.php | 2 +- .../phpt/variadic_args/014_varg_phpdoc_2.php | 2 +- tests/phpt/variadic_args/015_constructor.php | 2 +- .../variadic_args/016_unpack_positional.php | 2 +- .../112_bad_count_after_unpack.php | 2 +- .../warnings/1_ub_foreach_var_trycatch.php | 2 +- .../warnings/2_ub_foreach_var_trycatch2.php | 2 +- tests/phpt/warnings/3_ub_var_trycatch.php | 2 +- tests/phpt/zstd/1_compress.php | 2 +- tests/phpt/zstd/2_uncompress.php | 2 +- tests/phpt/zstd/3_compress_uncompress.php | 2 +- tests/python/lib/kphp_run_once.py | 42 ++++++++++- 728 files changed, 833 insertions(+), 737 deletions(-) diff --git a/compiler/code-gen/files/init-scripts.cpp b/compiler/code-gen/files/init-scripts.cpp index 282b5bd71f..de4e91952d 100644 --- a/compiler/code-gen/files/init-scripts.cpp +++ b/compiler/code-gen/files/init-scripts.cpp @@ -113,8 +113,17 @@ struct RunInterruptedFunction { void compile(CodeGenerator &W) const { std::string await_prefix = function->is_interruptible ? "co_await " : ""; + /** + * Oneshot components work the same way as php scripts: + * 1) Start when the request came in + * 2) Collecting output buffer after script finished + **/ + std::string script_start = G->settings().k2_component_is_oneshot.get() ? "co_await f$component_get_http_query();" : ""; + std::string script_finish = G->settings().k2_component_is_oneshot.get() ? "co_await finish(0, false);" : ""; FunctionSignatureGenerator(W) << "task_t " << FunctionName(function) << "$run() " << BEGIN + << script_start << NL << await_prefix << FunctionName(function) << "();" << NL + << script_finish << NL << "co_return;" << END; W << NL; diff --git a/tests/kphp_tester.py b/tests/kphp_tester.py index 36cd561735..efce2a3966 100755 --- a/tests/kphp_tester.py +++ b/tests/kphp_tester.py @@ -9,7 +9,7 @@ from functools import partial from multiprocessing.dummy import Pool as ThreadPool -from python.lib.colors import red, green, yellow, blue +from python.lib.colors import red, green, yellow, blue, cyan from python.lib.file_utils import search_php_bin from python.lib.nocc_for_kphp_tester import nocc_start_daemon_in_background from python.lib.kphp_run_once import KphpRunOnce @@ -46,7 +46,10 @@ def is_kphp_runtime_should_not_warn(self): def is_php8(self): return self.php_version.startswith("php8") - def make_kphp_once_runner(self, use_nocc, cxx_name): + def is_available_for_k2(self): + return "k2_skip" not in self.tags + + def make_kphp_once_runner(self, use_nocc, cxx_name, k2_bin): tester_dir = os.path.abspath(os.path.dirname(__file__)) return KphpRunOnce( php_script_path=self.file_path, @@ -57,8 +60,18 @@ def make_kphp_once_runner(self, use_nocc, cxx_name): vkext_dir=os.path.abspath(os.path.join(tester_dir, os.path.pardir, "objs", "vkext")), use_nocc=use_nocc, cxx_name=cxx_name, + k2_bin=k2_bin ) + def set_up_env_for_k2(self): + self.env_vars["KPHP_MODE"] = "k2-component" + self.env_vars["KPHP_USER_BINARY_PATH"] = "component.so" + self.env_vars["KPHP_ENABLE_GLOBAL_VARS_MEMORY_STATS"] = "0" + self.env_vars["KPHP_PROFILER"] = "0" + self.env_vars["KPHP_CXX"] = "clang++" + self.env_vars["KPHP_K2_COMPONENT_IS_ONESHOT"] = "1" + self.env_vars["KPHP_FORCE_LINK_RUNTIME"] = "1" + def make_test_file(file_path, test_tmp_dir, test_tags): # if file doesn't exist it will fail late @@ -162,7 +175,11 @@ def passed(test_file, artifacts): def skipped(test_file): return TestResult(yellow("skipped"), test_file, None, None) - def __init__(self, status, test_file, artifacts, failed_stage): + @staticmethod + def k2_skipped(test_file): + return TestResult(cyan("k2-skipped"), test_file, None, None, True) + + def __init__(self, status, test_file, artifacts, failed_stage, started_with_k2 = False): self.status = status self.test_file_path = test_file.file_path self.artifacts = None @@ -174,6 +191,8 @@ def __init__(self, status, test_file, artifacts, failed_stage): if failed_stage: self.failed_stage_msg = red("({})".format(failed_stage)) + self.started_with_k2 = started_with_k2 + def _print_artifacts(self): if self.artifacts: for file_type, artifact in self.artifacts: @@ -208,6 +227,9 @@ def print_fail_report(self): def is_skipped(self): return self.artifacts is None + def is_k2_skipped(self): + return self.artifacts is None and self.started_with_k2 + def is_failed(self): return self.failed_stage_msg is not None @@ -261,6 +283,7 @@ def run_warn_test(test: TestFile, runner): runner.kphp_build_stderr_artifact.error_priority = -1 return TestResult.passed(test, runner.artifacts) + def run_runtime_not_warn_test(test: TestFile, runner): if not runner.compile_with_kphp(test.env_vars): return TestResult.failed(test, runner.artifacts, "got kphp build error") @@ -276,6 +299,7 @@ def run_runtime_not_warn_test(test: TestFile, runner): return TestResult.passed(test, runner.artifacts) + def run_runtime_warn_test(test: TestFile, runner): if not runner.compile_with_kphp(test.env_vars): return TestResult.failed(test, runner.artifacts, "got kphp build error") @@ -283,9 +307,14 @@ def run_runtime_warn_test(test: TestFile, runner): if not runner.run_with_kphp(): return TestResult.failed(test, runner.artifacts, "got kphp run error") + if runner.k2_bin is not None: + warning_pattern = "WARN {2}component-log" + else: + warning_pattern = "Warning: " + with open(runner.kphp_runtime_stderr.file) as f: stderr_log = f.read() - if not re.search("Warning: ", stderr_log): + if not re.search(warning_pattern, stderr_log): return TestResult.failed(test, runner.artifacts, "can't find kphp runtime warnings") for index, msg_regex in enumerate(test.out_regexps, start=1): if not msg_regex.search(stderr_log): @@ -316,14 +345,18 @@ def run_ok_test(test: TestFile, runner): return TestResult.passed(test, runner.artifacts) -def run_test(use_nocc, cxx_name, test: TestFile): +def run_test(use_nocc, cxx_name, k2_bin, test: TestFile): if not os.path.exists(test.file_path): return TestResult.failed(test, None, "can't find test file") - runner = test.make_kphp_once_runner(use_nocc, cxx_name) + runner = test.make_kphp_once_runner(use_nocc, cxx_name, k2_bin) runner.remove_artifacts_dir() + if k2_bin is not None: + test.set_up_env_for_k2() - if test.is_php8() and runner._php_bin is None: # if php8 doesn't exist on a machine + if k2_bin is not None and not test.is_available_for_k2(): + test_result = TestResult.k2_skipped(test) + elif test.is_php8() and runner._php_bin is None: # if php8 doesn't exist on a machine test_result = TestResult.skipped(test) elif test.is_kphp_should_fail(): test_result = run_fail_test(test, runner) @@ -343,7 +376,7 @@ def run_test(use_nocc, cxx_name, test: TestFile): return test_result -def run_all_tests(tests_dir, jobs, test_tags, no_report, passed_list, test_list, use_nocc, cxx_name): +def run_all_tests(tests_dir, jobs, test_tags, no_report, passed_list, test_list, use_nocc, cxx_name, k2_bin): hack_reference_exit = [] signal.signal(signal.SIGINT, lambda sig, frame: hack_reference_exit.append(1)) @@ -357,7 +390,7 @@ def run_all_tests(tests_dir, jobs, test_tags, no_report, passed_list, test_list, results = [] with ThreadPool(jobs) as pool: tests_completed = 0 - for test_result in pool.imap_unordered(partial(run_test, use_nocc, cxx_name), tests): + for test_result in pool.imap_unordered(partial(run_test, use_nocc, cxx_name, k2_bin), tests): if hack_reference_exit: print(yellow("Testing process was interrupted"), flush=True) break @@ -369,9 +402,12 @@ def run_all_tests(tests_dir, jobs, test_tags, no_report, passed_list, test_list, skipped = len(tests) - len(results) failed = 0 + k2_skipped = 0 passed = [] for test_result in results: - if test_result.is_skipped(): + if test_result.is_k2_skipped(): + k2_skipped = k2_skipped + 1 + elif test_result.is_skipped(): skipped = skipped + 1 elif test_result.is_failed(): failed = failed + 1 @@ -384,6 +420,8 @@ def run_all_tests(tests_dir, jobs, test_tags, no_report, passed_list, test_list, with open(passed_list, "w") as f: passed.sort() f.writelines("{}\n".format(l) for l in passed) + if k2_bin is not None and k2_skipped: + print(" {}{}".format(cyan("k2-skipped: "), k2_skipped)) if skipped: print(" {}{}".format(yellow("skipped: "), skipped)) if failed: @@ -456,6 +494,14 @@ def parse_args(): default="g++", help="specify cxx compiler, default g++") + parser.add_argument( + "--k2-bin", + type=str, + dest='k2_bin', + default=None, + help="specify the path to the k2-run-once binary file to be used for tests" + ) + return parser.parse_args() @@ -484,7 +530,8 @@ def main(): passed_list=args.passed_list, test_list=args.test_list, use_nocc=args.use_nocc, - cxx_name=args.cxx_name) + cxx_name=args.cxx_name, + k2_bin=args.k2_bin) if __name__ == "__main__": diff --git a/tests/phpt/array/001_array_combine.php b/tests/phpt/array/001_array_combine.php index 32ed61555f..eac9ae40a0 100644 --- a/tests/phpt/array/001_array_combine.php +++ b/tests/phpt/array/001_array_combine.php @@ -1,4 +1,4 @@ -@ok +@ok k2_skip / /\$class_name::sf\(\);/ /Too few arguments in call to A::sf\(\), expected 1, have 0/ diff --git a/tests/phpt/by_name/107_by_name_wrong_tinf.php b/tests/phpt/by_name/107_by_name_wrong_tinf.php index 650f883493..9759be538a 100644 --- a/tests/phpt/by_name/107_by_name_wrong_tinf.php +++ b/tests/phpt/by_name/107_by_name_wrong_tinf.php @@ -1,4 +1,4 @@ -@kphp_should_fail +@kphp_should_fail k2_skip KPHP_SHOW_ALL_TYPE_ERRORS=1 /in callByName/ /pass string to argument \$a of A::sf/ diff --git a/tests/phpt/cl/001_base_class.php b/tests/phpt/cl/001_base_class.php index 3ea87267b5..092107f95e 100644 --- a/tests/phpt/cl/001_base_class.php +++ b/tests/phpt/cl/001_base_class.php @@ -1,4 +1,4 @@ -@ok +@ok k2_skip resum -> resum2 -> callurl@has-curl/ ',"'bar'",'"baz"','&blong&', "\xc3\xa9", "\xF4\x8F\xBF\xBF", "\xF4\x90\x80\x80", "\xc3\xa9\xF4\x8F\xBF\xBF\xF4\x90\x80\x80", "\xD0\x9A", "\xE0\x90\x9A", "\xF0\x80\x90\x9A", "\xF8\x80\x80\x90\x9A", "\xFC\x80\x80\x80\x90\x9A",//character and its overlong encodings diff --git a/tests/phpt/dl/432_rawurlencode.php b/tests/phpt/dl/432_rawurlencode.php index 0c615e5ba9..be425c7edc 100644 --- a/tests/phpt/dl/432_rawurlencode.php +++ b/tests/phpt/dl/432_rawurlencode.php @@ -1,4 +1,4 @@ -@ok benchmark +@ok benchmark k2_skip <:<"><:><"><">:"<">"<:><>"<||||{}|{}|{}|[]\\[]\\[]\\[]\[\]\[\]\[\]\[]\\[\]\[]\[\]\\[]\[\\\\\\\\\\\\\\\\\\\\'); diff --git a/tests/phpt/dl/443_vector.php b/tests/phpt/dl/443_vector.php index 749b055f2e..637d2af188 100644 --- a/tests/phpt/dl/443_vector.php +++ b/tests/phpt/dl/443_vector.php @@ -1,4 +1,4 @@ -@ok benchmark +@ok benchmark k2_skip 1, 1 => false, 3 => null); $b = array (1, false, null); diff --git a/tests/phpt/dl/455_inc_xor_str.php b/tests/phpt/dl/455_inc_xor_str.php index a15f82037d..30cd712b4d 100644 --- a/tests/phpt/dl/455_inc_xor_str.php +++ b/tests/phpt/dl/455_inc_xor_str.php @@ -1,4 +1,4 @@ -@ok +@ok k2_skip array ("result" => 1, "_" => "resultTrue"), "_" => "_") === array ("result" => array ("result" => 2, "_" => "resultTrue"), "_" => "_")); diff --git a/tests/phpt/dl/459_strtotime.php b/tests/phpt/dl/459_strtotime.php index 620620263e..9b3bc6c750 100644 --- a/tests/phpt/dl/459_strtotime.php +++ b/tests/phpt/dl/459_strtotime.php @@ -1,4 +1,4 @@ -@ok +@ok k2_skip test'; diff --git a/tests/phpt/dl/480_old_test.php b/tests/phpt/dl/480_old_test.php index 09594009bf..068471d5ce 100644 --- a/tests/phpt/dl/480_old_test.php +++ b/tests/phpt/dl/480_old_test.php @@ -1,4 +1,4 @@ -@ok benchmark +@ok benchmark k2_skip array()), 'foo', array ('bar[' => array()), array ('bar[' => array())); $query = http_build_query ($array); diff --git a/tests/phpt/dl/481_parse_str_with_empty_string.php b/tests/phpt/dl/481_parse_str_with_empty_string.php index 1ac45cf7ac..370e8c0cc2 100644 --- a/tests/phpt/dl/481_parse_str_with_empty_string.php +++ b/tests/phpt/dl/481_parse_str_with_empty_string.php @@ -1,4 +1,4 @@ -@ok +@ok k2_skip $key) + $fields; diff --git a/tests/phpt/dl/952_cfg_uninited.php b/tests/phpt/dl/952_cfg_uninited.php index bd3f657173..b80976bc8b 100644 --- a/tests/phpt/dl/952_cfg_uninited.php +++ b/tests/phpt/dl/952_cfg_uninited.php @@ -1,4 +1,4 @@ -@kphp_should_warn +@kphp_should_warn k2_skip /Variable \$a1 may be used uninitialized/ /Variable \$a2 may be used uninitialized/ /Variable \$b2 may be used uninitialized/ diff --git a/tests/phpt/dl/954_infer.php b/tests/phpt/dl/954_infer.php index 8ca0d02b48..6e547af7a1 100644 --- a/tests/phpt/dl/954_infer.php +++ b/tests/phpt/dl/954_infer.php @@ -1,4 +1,4 @@ -@ok +@ok k2_skip -%d--%s--%f\n", 1, "123", 0.2); diff --git a/tests/phpt/dl/980_push.php b/tests/phpt/dl/980_push.php index 718dd18c89..b2614c2231 100644 --- a/tests/phpt/dl/980_push.php +++ b/tests/phpt/dl/980_push.php @@ -1,4 +1,4 @@ -@ok +@ok k2_skip new\(\): line 1: syntax error, unexpected IDENTIFIER/ throwing/ callable\(\):int -> function\(\) -> function\(\) -> throwing/ new\(\): line 1: syntax error, unexpected INT_CONSTANT/ const char\*/ int64_t/ const char\*/ struct Foo/ char\*/ char/ double/ double/ float/ int8_t/ int16_t\*/ int8_t\*\*/ int8_t\*/ struct Bar/ / / / /Method g2\(\) not found in class ffi_scope/ / / int32_t\*/ uint8_t/ uint8_t/ struct Example/ int64_t/ diff --git a/tests/phpt/fork/005_fork_primitives.php b/tests/phpt/fork/005_fork_primitives.php index 8c5130239e..6bb522e98c 100644 --- a/tests/phpt/fork/005_fork_primitives.php +++ b/tests/phpt/fork/005_fork_primitives.php @@ -1,4 +1,4 @@ -@ok +@ok k2_skip / /classof\(\) used incorrectly: it's a keyword to be used only for a single variable which is a generic parameter/ =0 and < than 127/ /kphp-serialized-field\(128\) must be >=0 and < than 127/ \) can be replaced with array_merge_into\(\$x, <...>\)/ diff --git a/tests/phpt/performance_inspections/14_warn_tag_for_interface.php b/tests/phpt/performance_inspections/14_warn_tag_for_interface.php index b39d3204bf..0d75c15343 100644 --- a/tests/phpt/performance_inspections/14_warn_tag_for_interface.php +++ b/tests/phpt/performance_inspections/14_warn_tag_for_interface.php @@ -1,4 +1,4 @@ -@kphp_should_warn +@kphp_should_warn k2_skip /variable \$y1 can be reserved with array_reserve functions family out of loop/ /Performance inspection 'array\-reserve' enabled by: TestInterface::interface_function \-> TestInterfaceImpl1::interface_function/ /expression \$x = array_merge\(\$x, <...>\) can be replaced with array_merge_into\(\$x, <...>\)/ diff --git a/tests/phpt/performance_inspections/5_array_merge_into_warn.php b/tests/phpt/performance_inspections/5_array_merge_into_warn.php index 6eb3cc1b65..84bf9b1389 100644 --- a/tests/phpt/performance_inspections/5_array_merge_into_warn.php +++ b/tests/phpt/performance_inspections/5_array_merge_into_warn.php @@ -1,4 +1,4 @@ -@kphp_should_warn +@kphp_should_warn k2_skip /expression \$x = array_merge\(\$x, <...>\) can be replaced with array_merge_into\(\$x, <...>\)/ /expression \$y\['hello'\] = array_merge\(\$y\['hello'\], <...>\) can be replaced with array_merge_into\(\$y\['hello'\], <...>\)/ /expression \$z\['x'\]\[\$y\['hello'\]\[0\]\] = array_merge\(\$z\['x'\]\[\$y\['hello'\]\[0\]\], <...>\) can be replaced with array_merge_into\(\$z\['x'\]\[\$y\['hello'\]\[0\]\], <...>\)/ diff --git a/tests/phpt/performance_inspections/6_array_merge_into_no_warn.php b/tests/phpt/performance_inspections/6_array_merge_into_no_warn.php index 1a271de2d6..43c42e5c9e 100644 --- a/tests/phpt/performance_inspections/6_array_merge_into_no_warn.php +++ b/tests/phpt/performance_inspections/6_array_merge_into_no_warn.php @@ -1,4 +1,4 @@ -@ok +@ok k2_skip KPHP_ERROR_ON_WARNINGS=1 Date: Fri, 9 Aug 2024 17:43:48 +0300 Subject: [PATCH 18/45] Classes in mixed (#1010) Support objects inside mixed variables --- builtin-functions/kphp-full/_functions.txt | 5 +- builtin-functions/kphp-light/functions.txt | 3 + compiler/code-gen/declarations.cpp | 21 +- compiler/code-gen/naming.h | 19 +- compiler/code-gen/vertex-compiler.cpp | 6 +- compiler/data/class-data.cpp | 28 +- compiler/data/class-data.h | 7 +- compiler/pipes/final-check.cpp | 18 +- runtime-core/allocator/runtime-allocator.h | 31 ++ .../allocator/script-allocator-managed.h | 2 +- .../class-instance/refcountable-php-classes.h | 13 +- .../core-types/comparison_operators.inl | 16 ++ runtime-core/core-types/decl/mixed_decl.inl | 52 ++++ runtime-core/core-types/decl/string_decl.inl | 1 - runtime-core/core-types/definition/array.inl | 15 + runtime-core/core-types/definition/mixed.cpp | 5 + runtime-core/core-types/definition/mixed.inl | 269 +++++++++++++++--- runtime-core/core-types/definition/string.inl | 11 + runtime-core/core-types/kphp_type_traits.h | 3 + runtime-core/runtime-core-context.h | 22 -- runtime-core/runtime-core.h | 11 + runtime/json-functions.cpp | 3 + runtime/misc.cpp | 15 + runtime/msgpack/adaptors.h | 3 + runtime/serialize-functions.cpp | 3 + runtime/to-json-processor.h | 3 + .../class_inheritance/123_instanceof_fail.php | 3 +- .../interfaces/125_instanceof_on_int_fail.php | 2 +- .../mixed/non_primitive/001_store_class.php | 27 ++ .../mixed/non_primitive/002_instanceof.php | 47 +++ .../mixed/non_primitive/003_load_class.php | 22 ++ .../non_primitive/004_array_of_classes.php | 32 +++ .../mixed/non_primitive/005_polymorphism.php | 69 +++++ .../mixed/non_primitive/006_inheritance.php | 144 ++++++++++ tests/phpt/mixed/non_primitive/007_fields.php | 45 +++ tests/phpt/mixed/non_primitive/008_forks.php | 49 ++++ tests/phpt/mixed/non_primitive/009_empty.php | 104 +++++++ .../010_to_mixed_with_base_class.php | 70 +++++ tests/phpt/mixed/non_primitive/011_equals.php | 36 +++ .../mixed/non_primitive/012_class_as_bool.php | 35 +++ .../non_primitive/013_class_as_string.php | 39 +++ .../mixed/non_primitive/014_class_as_int.php | 50 ++++ .../non_primitive/015_class_as_array.php | 38 +++ .../non_primitive/101_without_to_mixed.php | 18 ++ 44 files changed, 1316 insertions(+), 99 deletions(-) create mode 100644 runtime-core/allocator/runtime-allocator.h create mode 100644 tests/phpt/mixed/non_primitive/001_store_class.php create mode 100644 tests/phpt/mixed/non_primitive/002_instanceof.php create mode 100644 tests/phpt/mixed/non_primitive/003_load_class.php create mode 100644 tests/phpt/mixed/non_primitive/004_array_of_classes.php create mode 100644 tests/phpt/mixed/non_primitive/005_polymorphism.php create mode 100644 tests/phpt/mixed/non_primitive/006_inheritance.php create mode 100644 tests/phpt/mixed/non_primitive/007_fields.php create mode 100644 tests/phpt/mixed/non_primitive/008_forks.php create mode 100644 tests/phpt/mixed/non_primitive/009_empty.php create mode 100644 tests/phpt/mixed/non_primitive/010_to_mixed_with_base_class.php create mode 100644 tests/phpt/mixed/non_primitive/011_equals.php create mode 100644 tests/phpt/mixed/non_primitive/012_class_as_bool.php create mode 100644 tests/phpt/mixed/non_primitive/013_class_as_string.php create mode 100644 tests/phpt/mixed/non_primitive/014_class_as_int.php create mode 100644 tests/phpt/mixed/non_primitive/015_class_as_array.php create mode 100644 tests/phpt/mixed/non_primitive/101_without_to_mixed.php diff --git a/builtin-functions/kphp-full/_functions.txt b/builtin-functions/kphp-full/_functions.txt index 8c4f5e10e0..7846b8c4b0 100644 --- a/builtin-functions/kphp-full/_functions.txt +++ b/builtin-functions/kphp-full/_functions.txt @@ -1378,7 +1378,7 @@ function raise_sigsegv () ::: void; function make_clone ($x ::: any) ::: ^1; /** @kphp-extern-func-info cpp_template_call */ -function instance_cast(object $instance, $to_type ::: string) ::: instance<^2>; +function instance_cast(any $instance, $to_type ::: string) ::: instance<^2>; function instance_to_array(object $instance, $with_class_names ::: bool = false) ::: mixed[]; function to_array_debug(any $instance, bool $with_class_names = false) ::: mixed[]; @@ -1678,3 +1678,6 @@ class DateTimeImmutable implements DateTimeInterface { } function getenv(string $varname = '', bool $local_only = false): mixed; + +// builtin that allows to store objects inside a mixed +function to_mixed(object $instance) ::: mixed; diff --git a/builtin-functions/kphp-light/functions.txt b/builtin-functions/kphp-light/functions.txt index c9f5980d70..e2e776dce8 100644 --- a/builtin-functions/kphp-light/functions.txt +++ b/builtin-functions/kphp-light/functions.txt @@ -222,3 +222,6 @@ function int_to_byte($v ::: int) ::: ?string; /** @kphp-extern-func-info interruptible */ function set_timer(int $timeout, callable():void $callback) ::: void; + +// builtin that allows to store objects inside a mixed +function to_mixed(object $instance) ::: mixed; diff --git a/compiler/code-gen/declarations.cpp b/compiler/code-gen/declarations.cpp index ecf571c3f7..79c441ee2a 100644 --- a/compiler/code-gen/declarations.cpp +++ b/compiler/code-gen/declarations.cpp @@ -412,7 +412,7 @@ void InterfaceDeclaration::compile(CodeGenerator &W) const { auto transform_to_src_name = [](CodeGenerator &W, const InterfacePtr &i) { W << i->src_name; }; W << JoinValues(interface->implements, ", public ", join_mode::one_line, transform_to_src_name); } else { - W << (interface->need_virtual_modifier() ? "virtual " : "") << "abstract_refcountable_php_interface"; + W << (interface->need_virtual_modifier() ? "virtual " : "") << (interface->may_be_mixed ? "may_be_mixed_base" : "abstract_refcountable_php_interface"); } W << " " << BEGIN; @@ -495,10 +495,13 @@ void ClassDeclaration::compile(CodeGenerator &W) const { W << ": public refcountable_polymorphic_php_classes<" << get_all_interfaces() << ">"; } else if (!klass->derived_classes.empty()) { if (klass->need_virtual_modifier()) { - W << ": public refcountable_polymorphic_php_classes_virt<>"; + W << ": public refcountable_polymorphic_php_classes_virt<>" << (klass->may_be_mixed ? ", virtual may_be_mixed_base" : ""); } else { - W << ": public refcountable_polymorphic_php_classes"; + W << ": public refcountable_polymorphic_php_classes<" << + (klass->may_be_mixed ? "may_be_mixed_base" : "abstract_refcountable_php_interface") << ">"; } + } else if(klass->may_be_mixed) { + W << ": public refcountable_polymorphic_php_classes"; } else { // not polymorphic W << ": public refcountable_php_classes<" << klass->src_name << ">"; } @@ -510,7 +513,7 @@ void ClassDeclaration::compile(CodeGenerator &W) const { compile_fields(W, klass); - if (!klass->derived_classes.empty()) { + if (klass->may_be_mixed || !klass->derived_classes.empty()) { W << "virtual ~" << klass->src_name << "() = default;" << NL; } @@ -539,6 +542,10 @@ void ClassDeclaration::compile(CodeGenerator &W) const { W << CloseFile(); } +static bool is_may_be_mixed_virtual_method(vk::string_view method_signature) { + return method_signature == "const char *get_class()"; +} + template void ClassDeclaration::compile_class_method(FunctionSignatureGenerator &&W, ClassPtr klass, vk::string_view method_signature, const ReturnValueT &return_value) { const bool has_parent = (klass->parent_class && klass->parent_class->does_need_codegen()) || @@ -547,6 +554,12 @@ void ClassDeclaration::compile_class_method(FunctionSignatureGenerator &&W, Clas const bool is_overridden = has_parent && has_derived; const bool is_final = has_parent && !has_derived; const bool is_pure_virtual = klass->class_type == ClassType::interface; + const bool may_be_mixed = klass->may_be_mixed; + + if (klass->is_interface() && may_be_mixed && is_may_be_mixed_virtual_method(method_signature)) { + std::move(W).clear_all(); + return; + } FunctionSignatureGenerator &&signature = std::move(W) .set_is_virtual(is_pure_virtual || has_derived) diff --git a/compiler/code-gen/naming.h b/compiler/code-gen/naming.h index e17f9150f1..815159db22 100644 --- a/compiler/code-gen/naming.h +++ b/compiler/code-gen/naming.h @@ -95,6 +95,11 @@ class FunctionSignatureGenerator { return std::move(*this); } + FunctionSignatureGenerator &&set_noexcept(bool new_value = true) && noexcept { + noexcept_ = new_value; + return std::move(*this); + } + FunctionSignatureGenerator &&set_const_this(bool new_value = true) && noexcept { const_this_ = new_value; return std::move(*this); @@ -125,6 +130,18 @@ class FunctionSignatureGenerator { return std::move(*this); } + FunctionSignatureGenerator &&clear_all() &&noexcept { + return std::move(*this) + .set_is_virtual(false) + .set_noexcept(false) + .set_const_this(false) + .set_overridden(false) + .set_final(false) + .set_pure_virtual(false) + .set_inline(false) + .set_definition(false); + } + private: template FunctionSignatureGenerator &&generate_specifiers(const T &value) noexcept { @@ -164,7 +181,7 @@ class FunctionSignatureGenerator { bool virtual_ = false; bool const_this_ = false; - const bool noexcept_ = true; + bool noexcept_ = true; bool overridden_ = false; bool final_ = false; bool pure_virtual_ = false; diff --git a/compiler/code-gen/vertex-compiler.cpp b/compiler/code-gen/vertex-compiler.cpp index 826e2456ab..0c8b517ac6 100644 --- a/compiler/code-gen/vertex-compiler.cpp +++ b/compiler/code-gen/vertex-compiler.cpp @@ -512,7 +512,11 @@ void compile_binary_op(VertexAdaptor root, CodeGenerator &W) { const auto *rhs_tp = tinf::get_type(rhs); if (auto instanceof = root.try_as()) { - W << "f$is_a<" << instanceof->derived_class->src_name << ">(" << lhs << ")"; + if (lhs_tp->ptype() == tp_mixed && !instanceof->derived_class->may_be_mixed.load(std::memory_order_relaxed)) { + W << "(false)"; + } else { + W << "f$is_a<" << instanceof->derived_class->src_name << ">(" << lhs << ")"; + } return; } diff --git a/compiler/data/class-data.cpp b/compiler/data/class-data.cpp index e97b8a7b73..25f289fbc5 100644 --- a/compiler/data/class-data.cpp +++ b/compiler/data/class-data.cpp @@ -376,31 +376,33 @@ void ClassData::mark_as_used() { } template ClassData::*field_ptr> -void ClassData::set_atomic_field_deeply() { +void ClassData::set_atomic_field_deeply(bool on_fields) { if (this->*field_ptr) { return; } this->*field_ptr = true; - members.for_each([](ClassMemberInstanceField &field) { - std::unordered_set sub_classes; - field.var->tinf_node.get_type()->get_all_class_types_inside(sub_classes); - for (auto klass : sub_classes) { - klass->set_atomic_field_deeply(); - } - }); + if (on_fields) { + members.for_each([](ClassMemberInstanceField &field) { + std::unordered_set sub_classes; + field.var->tinf_node.get_type()->get_all_class_types_inside(sub_classes); + for (auto klass : sub_classes) { + klass->set_atomic_field_deeply(); + } + }); + } for (auto child : derived_classes) { - child->set_atomic_field_deeply(); + child->set_atomic_field_deeply(on_fields); } // todo why we are setting it to parents, not only to child classes? for (auto parent_interface : implements) { - parent_interface->set_atomic_field_deeply(); + parent_interface->set_atomic_field_deeply(on_fields); } if (parent_class) { - parent_class->set_atomic_field_deeply(); + parent_class->set_atomic_field_deeply(on_fields); } } @@ -420,6 +422,10 @@ void ClassData::deeply_require_virtual_builtin_functions() { set_atomic_field_deeply<&ClassData::need_virtual_builtin_functions>(); } +void ClassData::deeply_require_may_be_mixed_base() { + set_atomic_field_deeply<&ClassData::may_be_mixed>(false); +} + void ClassData::add_str_dependent(FunctionPtr cur_function, ClassType type, vk::string_view class_name) { auto full_class_name = resolve_uses(cur_function, static_cast(class_name)); str_dependents.emplace_back(type, full_class_name); diff --git a/compiler/data/class-data.h b/compiler/data/class-data.h index b3cd0c98c8..063ec6d2b0 100644 --- a/compiler/data/class-data.h +++ b/compiler/data/class-data.h @@ -84,6 +84,8 @@ class ClassData : public Lockable { std::atomic need_instance_cache_visitors{false}; std::atomic need_instance_memory_estimate_visitor{false}; std::atomic need_virtual_builtin_functions{false}; + std::atomic may_be_mixed{false}; + // need_json_visitors doesn't exist: instead, we use json_encoders with a list of classes ClassModifiers modifiers; @@ -124,7 +126,7 @@ class ClassData : public Lockable { bool is_polymorphic_class() const { - return !derived_classes.empty() || !implements.empty() || parent_class; + return !derived_classes.empty() || !implements.empty() || parent_class || may_be_mixed; } bool is_empty_class() const { @@ -201,6 +203,7 @@ class ClassData : public Lockable { void deeply_require_instance_cache_visitor(); void deeply_require_instance_memory_estimate_visitor(); void deeply_require_virtual_builtin_functions(); + void deeply_require_may_be_mixed_base(); void add_str_dependent(FunctionPtr cur_function, ClassType type, vk::string_view class_name); const std::vector &get_str_dependents() const { @@ -232,7 +235,7 @@ class ClassData : public Lockable { } template ClassData:: *field_ptr> - void set_atomic_field_deeply(); + void set_atomic_field_deeply(bool on_fields = true); // extends/implements/use trait during the parsing (before ptr is assigned) std::vector str_dependents; diff --git a/compiler/pipes/final-check.cpp b/compiler/pipes/final-check.cpp index 7593c94b0b..3b0e1a2d14 100644 --- a/compiler/pipes/final-check.cpp +++ b/compiler/pipes/final-check.cpp @@ -288,6 +288,17 @@ void check_instance_deserialize_call(VertexAdaptor call) { kphp_error(type->class_type()->is_serializable, fmt_format("Called instance_deserialize() for class {}, but it's not marked with @kphp-serializable", type->class_type()->name)); } +void to_mixed_on_class(ClassPtr klass) { + klass->deeply_require_may_be_mixed_base(); +} + +void check_to_mixed_call(VertexAdaptor call) { + const auto *type = tinf::get_type(call->args().front()); + kphp_assert(type->ptype() == tp_Class); + + to_mixed_on_class(type->class_type()); +} + void check_estimate_memory_usage_call(VertexAdaptor call) { const auto *type = tinf::get_type(call->args()[0]); std::unordered_set classes_inside; @@ -721,6 +732,9 @@ VertexPtr FinalCheckPass::on_enter_vertex(VertexPtr vertex) { kphp_error(lhs_type->ptype() == tp_Class, fmt_format("Accessing ->property of non-instance {}", lhs_type->as_human_readable())); } + if (auto instanceof = vertex.try_as()) { + instanceof->derived_class->mark_as_used(); + } if (vertex->type() == op_throw) { const TypeData *thrown_type = tinf::get_type(vertex.as()->exception()); @@ -780,7 +794,7 @@ void FinalCheckPass::check_instanceof(VertexAdaptor instanceof_ve return; } - kphp_error(instanceof_var_type->class_type(), fmt_format("left operand of 'instanceof' should be an instance, but passed {}", instanceof_var_type->as_human_readable())); + kphp_error(instanceof_var_type->class_type() || instanceof_var_type->ptype() == tp_mixed, fmt_format("left operand of 'instanceof' should be an instance or mixed, but passed {}", instanceof_var_type->as_human_readable())); } static void check_indexing_violation(vk::string_view allowed_types_string, const std::vector &allowed_types, vk::string_view what_indexing, @@ -855,6 +869,8 @@ void FinalCheckPass::check_op_func_call(VertexAdaptor call) { kphp_error(arg_type->can_store_null(), fmt_format("is_null() will be always false for {}", arg_type->as_human_readable())); } else if (function_name == "register_shutdown_function") { check_register_shutdown_functions(call); + } else if (function_name == "to_mixed") { + check_to_mixed_call(call); } else if (vk::string_view{function_name}.starts_with("rpc_tl_query")) { G->set_untyped_rpc_tl_used(); } else if (vk::string_view{function_name}.starts_with("FFI$$")) { diff --git a/runtime-core/allocator/runtime-allocator.h b/runtime-core/allocator/runtime-allocator.h new file mode 100644 index 0000000000..9b31e1d7a8 --- /dev/null +++ b/runtime-core/allocator/runtime-allocator.h @@ -0,0 +1,31 @@ +// 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/memory-resource/unsynchronized_pool_resource.h" + + +struct RuntimeAllocator { + static RuntimeAllocator& current() noexcept; + + RuntimeAllocator() = default; + RuntimeAllocator(size_t script_mem_size, size_t oom_handling_mem_size); + + void init(void * buffer, size_t script_mem_size, size_t oom_handling_mem_size); + void free(); + + void * alloc_script_memory(size_t size) noexcept; + void * alloc0_script_memory(size_t size) noexcept; + void * realloc_script_memory(void *mem, size_t new_size, size_t old_size) noexcept; + void free_script_memory(void *mem, size_t size) noexcept; + + void * alloc_global_memory(size_t size) noexcept; + void * alloc0_global_memory(size_t size) noexcept; + void * realloc_global_memory(void *mem, size_t new_size, size_t old_size) noexcept; + void free_global_memory(void *mem, size_t size) noexcept; + + memory_resource::unsynchronized_pool_resource memory_resource; +}; diff --git a/runtime-core/allocator/script-allocator-managed.h b/runtime-core/allocator/script-allocator-managed.h index 35231dd273..67598cc89a 100644 --- a/runtime-core/allocator/script-allocator-managed.h +++ b/runtime-core/allocator/script-allocator-managed.h @@ -6,7 +6,7 @@ #include -#include "runtime-core/runtime-core.h" +#include "runtime-core/allocator/runtime-allocator.h" class ScriptAllocatorManaged { public: diff --git a/runtime-core/class-instance/refcountable-php-classes.h b/runtime-core/class-instance/refcountable-php-classes.h index 09b37fafdc..92db4d16a7 100644 --- a/runtime-core/class-instance/refcountable-php-classes.h +++ b/runtime-core/class-instance/refcountable-php-classes.h @@ -15,7 +15,7 @@ class abstract_refcountable_php_interface : public ScriptAllocatorManaged { virtual ~abstract_refcountable_php_interface() noexcept __attribute__((always_inline)) = default; virtual void add_ref() noexcept = 0; virtual void release() noexcept = 0; - virtual uint32_t get_refcnt() noexcept = 0; + virtual uint32_t get_refcnt() const noexcept = 0; virtual void set_refcnt(uint32_t new_refcnt) noexcept = 0; virtual void *get_instance_data_raw_ptr() noexcept = 0; @@ -30,7 +30,7 @@ class refcountable_polymorphic_php_classes : public Bases... { } } - uint32_t get_refcnt() noexcept final { + uint32_t get_refcnt() const noexcept final { return refcnt; } @@ -72,7 +72,7 @@ class refcountable_polymorphic_php_classes_virt<> : public virtual abstract_refc } } - uint32_t get_refcnt() noexcept final { + uint32_t get_refcnt() const noexcept final { return refcnt; } @@ -106,7 +106,7 @@ class refcountable_php_classes : public ScriptAllocatorManaged { } } - uint32_t get_refcnt() noexcept { + uint32_t get_refcnt() const noexcept { return refcnt; } @@ -142,3 +142,8 @@ class refcountable_empty_php_classes { static void add_ref() noexcept {} static void release() noexcept {} }; + +struct may_be_mixed_base : public virtual abstract_refcountable_php_interface { + virtual ~may_be_mixed_base() = default; + virtual const char *get_class() const noexcept = 0; +}; diff --git a/runtime-core/core-types/comparison_operators.inl b/runtime-core/core-types/comparison_operators.inl index 12e62d797e..9589e9bf78 100644 --- a/runtime-core/core-types/comparison_operators.inl +++ b/runtime-core/core-types/comparison_operators.inl @@ -46,6 +46,10 @@ inline bool eq2(const string &lhs, const string &rhs) { } inline bool eq2(const mixed &lhs, const mixed &rhs) { + if (lhs.is_object() || rhs.is_object()) { + php_warning("operators ==, != are not supported for %s and %s", lhs.get_type_or_class_name(), rhs.get_type_or_class_name()); + return false; + } return lhs.compare(rhs) == 0; } @@ -254,6 +258,9 @@ inline bool eq2(double lhs, const mixed &rhs) { case mixed::type::ARRAY: php_warning("Unsupported operand types for operator == (float and array)"); return false; + case mixed::type::OBJECT: + php_warning("Unsupported operand types for operator == (float and %s)", rhs.as_object()->get_class()); + return false; default: __builtin_unreachable(); } @@ -277,6 +284,9 @@ inline bool eq2(int64_t lhs, const mixed &rhs) { case mixed::type::ARRAY: php_warning("Unsupported operand types for operator == (int and array)"); return false; + case mixed::type::OBJECT: + php_warning("Unsupported operand types for operator == (int and %s)", rhs.as_object()->get_class()); + return false; default: __builtin_unreachable(); } @@ -409,6 +419,10 @@ inline bool equals(const array &lhs, const array &rhs) { return true; } +inline bool equals(const may_be_mixed_base *lhs, const may_be_mixed_base* rhs) { + return lhs == rhs; +} + inline bool equals(bool lhs, const mixed &rhs) { return rhs.is_bool() && equals(lhs, rhs.as_bool()); } @@ -464,6 +478,8 @@ inline bool equals(const mixed &lhs, const mixed &rhs) { return equals(lhs.as_string(), rhs.as_string()); case mixed::type::ARRAY: return equals(lhs.as_array(), rhs.as_array()); + case mixed::type::OBJECT: + return equals(lhs.as_object(), rhs.as_object()); default: __builtin_unreachable(); } diff --git a/runtime-core/core-types/decl/mixed_decl.inl b/runtime-core/core-types/decl/mixed_decl.inl index cad5f864fc..6509f6a85e 100644 --- a/runtime-core/core-types/decl/mixed_decl.inl +++ b/runtime-core/core-types/decl/mixed_decl.inl @@ -4,6 +4,10 @@ #pragma once +#include "common/smart_ptrs/intrusive_ptr.h" +#include "runtime-core/class-instance/refcountable-php-classes.h" + + #ifndef INCLUDED_FROM_KPHP_CORE #error "this file must be included only from runtime-core.h" #endif @@ -16,6 +20,10 @@ template struct is_type_acceptable_for_mixed> : is_constructible_or_unknown { }; +template +struct is_type_acceptable_for_mixed> : std::is_base_of { +}; + class mixed { public: enum class type { @@ -25,6 +33,7 @@ public: FLOAT, STRING, ARRAY, + OBJECT, }; mixed(const void *) = delete; // deprecate conversion from pointer to boolean @@ -154,6 +163,29 @@ public: inline array &as_array() __attribute__((always_inline)); inline const array &as_array() const __attribute__((always_inline)); + inline vk::intrusive_ptr as_object() __attribute__((always_inline)); + inline const vk::intrusive_ptr as_object() const __attribute__((always_inline)); + + + // TODO is it ok to return pointer to mutable from const method? + // I need it just to pass such a pointer into class_instance. Mutability is needed because + // class_instance do ref-counting + template + inline T *as_object_ptr() const { + auto ptr_to_object = vk::dynamic_pointer_cast(*reinterpret_cast *>(&storage_)); + return ptr_to_object.get(); + } + + template + inline bool is_a() const { + if (type_ != type::OBJECT) { + return false; + } + + auto ptr = *reinterpret_cast*>(&storage_); + return static_cast(vk::dynamic_pointer_cast(ptr)); + } + inline int64_t safe_to_int() const; inline void convert_to_numeric(); @@ -184,9 +216,11 @@ public: inline bool is_float() const; inline bool is_string() const; inline bool is_array() const; + inline bool is_object() const; inline const string get_type_str() const; inline const char *get_type_c_str() const; + inline const char *get_type_or_class_name() const; inline bool empty() const; inline int64_t count() const; @@ -230,6 +264,8 @@ private: auto get_type_and_value_ptr(const int &) { return std::make_pair(type::INTEGER, &as_int()); } auto get_type_and_value_ptr(const double &) { return std::make_pair(type::FLOAT , &as_double()); } auto get_type_and_value_ptr(const string &) { return std::make_pair(type::STRING , &as_string()); } + template + auto get_type_and_value_ptr(const InstanceClass &) {return std::make_pair(type::OBJECT, as_object_ptr()); } template static T &empty_value() noexcept; @@ -238,3 +274,19 @@ private: uint64_t storage_{0}; }; +template +inline mixed f$to_mixed(const class_instance &instance) noexcept { + mixed m; + m = instance; + return m; +} + +template +inline ResultClass from_mixed(const mixed &m, const string &) noexcept { + if constexpr (!std::is_polymorphic_v) { + php_error("Internal error. Class inside a mixed is not polymorphic"); + return {}; + } else { + return ResultClass::create_from_base_raw_ptr(dynamic_cast(m.as_object_ptr())); + } +} diff --git a/runtime-core/core-types/decl/string_decl.inl b/runtime-core/core-types/decl/string_decl.inl index b3d1c0eaa3..5a2bb01d40 100644 --- a/runtime-core/core-types/decl/string_decl.inl +++ b/runtime-core/core-types/decl/string_decl.inl @@ -166,7 +166,6 @@ public: template inline string &append_unsafe(const Optional &v) __attribute__((always_inline)); - inline void push_back(char c); inline string &assign(const string &str); diff --git a/runtime-core/core-types/definition/array.inl b/runtime-core/core-types/definition/array.inl index 7db12ae63f..9c8e08f744 100644 --- a/runtime-core/core-types/definition/array.inl +++ b/runtime-core/core-types/definition/array.inl @@ -1098,6 +1098,9 @@ T &array::operator[](const mixed &v) { case mixed::type::ARRAY: php_warning("Illegal offset type array"); return (*this)[v.as_array().to_int()]; + case mixed::type::OBJECT: + php_warning("Illegal offset type %s", v.get_type_or_class_name()); + return (*this)[string()]; default: __builtin_unreachable(); } @@ -1238,6 +1241,9 @@ void array::emplace_value(const mixed &var_key, Args &&... args) noexcept { case mixed::type::ARRAY: php_warning("Illegal offset type array"); return emplace_value(var_key.as_array().to_int(), std::forward(args)...); + case mixed::type::OBJECT: + php_warning("Illegal offset type %s", var_key.get_type_or_class_name()); + return emplace_value(string(), std::forward(args)...); default: __builtin_unreachable(); } @@ -1336,6 +1342,9 @@ const T *array::find_value(const mixed &v) const noexcept { case mixed::type::ARRAY: php_warning("Illegal offset type array"); return find_value(v.as_array().to_int()); + case mixed::type::OBJECT: + php_warning("Illegal offset type %s", v.get_type_or_class_name()); + return find_value(string()); default: __builtin_unreachable(); } @@ -1413,6 +1422,9 @@ typename array::iterator array::find_no_mutate(const mixed &v) noexcept { case mixed::type::ARRAY: php_warning("Illegal offset type array"); return find_no_mutate(v.as_array().to_int()); + case mixed::type::OBJECT: + php_warning("Illegal offset type %s", v.get_type_or_class_name()); + return find_no_mutate(string()); default: __builtin_unreachable(); } @@ -1515,6 +1527,9 @@ T array::unset(const mixed &v) { case mixed::type::ARRAY: php_warning("Illegal offset type array"); return unset(v.as_array().to_int()); + case mixed::type::OBJECT: + php_warning("Illegal offset type %s", v.get_type_or_class_name()); + return unset(string()); default: __builtin_unreachable(); } diff --git a/runtime-core/core-types/definition/mixed.cpp b/runtime-core/core-types/definition/mixed.cpp index 2ead9df246..bc1deb1420 100644 --- a/runtime-core/core-types/definition/mixed.cpp +++ b/runtime-core/core-types/definition/mixed.cpp @@ -12,6 +12,11 @@ void mixed::destroy() noexcept { case type::ARRAY: as_array().~array(); break; + case type::OBJECT: { + reinterpret_cast*>(&storage_)->~intrusive_ptr(); + storage_ = 0; + break; + } default: { } } diff --git a/runtime-core/core-types/definition/mixed.inl b/runtime-core/core-types/definition/mixed.inl index 9252a31fcf..bb6b6d42a8 100644 --- a/runtime-core/core-types/definition/mixed.inl +++ b/runtime-core/core-types/definition/mixed.inl @@ -5,8 +5,10 @@ #pragma once #include "common/algorithms/find.h" +#include "common/smart_ptrs/intrusive_ptr.h" #include "runtime-core/utils/migration-php8.h" +#include "runtime-core/class-instance/refcountable-php-classes.h" #ifndef INCLUDED_FROM_KPHP_CORE #error "this file must be included only from runtime-core.h" @@ -22,6 +24,10 @@ void mixed::copy_from(const mixed &other) { case type::ARRAY: new(&as_array()) array(other.as_array()); break; + case type::OBJECT: { + new (&storage_) vk::intrusive_ptr(*reinterpret_cast *>(&other.storage_)); + break; + } default: storage_ = other.storage_; } @@ -36,6 +42,11 @@ void mixed::copy_from(mixed &&other) { case type::ARRAY: new(&as_array()) array(std::move(other.as_array())); break; + case type::OBJECT: { + storage_ = other.storage_; + other.storage_ = 0; + break; + } default: storage_ = other.storage_; } @@ -44,22 +55,38 @@ void mixed::copy_from(mixed &&other) { template void mixed::init_from(T &&v) { - auto type_and_value_ref = get_type_and_value_ptr(v); - type_ = type_and_value_ref.first; - auto *value_ptr = type_and_value_ref.second; - using ValueType = std::decay_t; - new(value_ptr) ValueType(std::forward(v)); + if constexpr (is_class_instance_v>) { + static_assert(sizeof(storage_) >= sizeof(vk::intrusive_ptr)); + auto ptr_to_obj = new(&storage_) vk::intrusive_ptr(dynamic_cast(v.get())); + if (unlikely(!ptr_to_obj)) { + php_error("Internal error. Trying to set invalid object to mixed"); + } + type_ = type::OBJECT; + } else { + auto type_and_value_ref = get_type_and_value_ptr(v); + type_ = type_and_value_ref.first; + auto *value_ptr = type_and_value_ref.second; + using ValueType = std::decay_t; + new(value_ptr) ValueType(std::forward(v)); + } } template mixed &mixed::assign_from(T &&v) { - auto type_and_value_ref = get_type_and_value_ptr(v); - if (get_type() == type_and_value_ref.first) { - *type_and_value_ref.second = std::forward(v); - } else { + if constexpr(is_class_instance_v>) { + static_assert(std::is_base_of_v::ClassType>); destroy(); init_from(std::forward(v)); + } else { + auto type_and_value_ref = get_type_and_value_ptr(v); + if (get_type() == type_and_value_ref.first) { + *type_and_value_ref.second = std::forward(v); + } else { + destroy(); + init_from(std::forward(v)); + } } + return *this; } @@ -368,6 +395,9 @@ mixed &mixed::operator++() { case type::ARRAY: php_warning("Can't apply operator ++ to array"); return *this; + case type::OBJECT: + php_warning("Can't apply operator ++ to %s", get_type_or_class_name()); + return *this; default: __builtin_unreachable(); } @@ -401,6 +431,9 @@ const mixed mixed::operator++(int) { case type::ARRAY: php_warning("Can't apply operator ++ to array"); return as_array(); + case type::OBJECT: + php_warning("Can't apply operator ++ to %s", get_type_or_class_name()); + return *this; default: __builtin_unreachable(); } @@ -431,6 +464,9 @@ mixed &mixed::operator--() { case type::ARRAY: php_warning("Can't apply operator -- to array"); return *this; + case type::OBJECT: + php_warning("Can't apply operator -- to %s", get_type_or_class_name()); + return *this; default: __builtin_unreachable(); } @@ -469,6 +505,9 @@ const mixed mixed::operator--(int) { case type::ARRAY: php_warning("Can't apply operator -- to array"); return as_array(); + case type::OBJECT: + php_warning("Can't apply operator -- to %s", get_type_or_class_name()); + return *this; default: __builtin_unreachable(); } @@ -511,6 +550,9 @@ const mixed mixed::to_numeric() const { case type::ARRAY: php_warning("Wrong conversion from array to number"); return as_array().to_int(); + case type::OBJECT: + php_warning("Wrong conversion from %s to number", get_type_or_class_name()); + return (as_object() ? 1 : 0); default: __builtin_unreachable(); } @@ -531,6 +573,8 @@ bool mixed::to_bool() const { return as_string().to_bool(); case type::ARRAY: return !as_array().empty(); + case type::OBJECT: + return (bool)as_object(); default: __builtin_unreachable(); } @@ -551,6 +595,9 @@ int64_t mixed::to_int() const { case type::ARRAY: php_warning("Wrong conversion from array to int"); return as_array().to_int(); + case type::OBJECT: + php_warning("Wrong conversion from %s to int", get_type_or_class_name()); + return (as_object() ? 1 : 0); default: __builtin_unreachable(); } @@ -571,6 +618,33 @@ double mixed::to_float() const { case type::ARRAY: php_warning("Wrong conversion from array to float"); return as_array().to_float(); + case type::OBJECT: { + php_warning("Wrong conversion from %s to float", get_type_or_class_name()); + return (as_object() ? 1.0 : 0.0); + } + default: + __builtin_unreachable(); + } +} + +static string to_string_without_warning(const mixed &m) { + switch (m.get_type()) { + case mixed::type::NUL: + return string(); + case mixed::type::BOOLEAN: + return (m.as_bool() ? string("1", 1) : string()); + case mixed::type::INTEGER: + return string(m.as_int()); + case mixed::type::FLOAT: + return string(m.as_double()); + case mixed::type::STRING: + return m.as_string(); + case mixed::type::ARRAY: + return string("Array", 5); + case mixed::type::OBJECT: { + const char *s = m.get_type_or_class_name(); + return string(s, strlen(s)); + } default: __builtin_unreachable(); } @@ -578,22 +652,23 @@ double mixed::to_float() const { const string mixed::to_string() const { switch (get_type()) { - case type::NUL: - return string(); - case type::BOOLEAN: - return (as_bool() ? string("1", 1) : string()); - case type::INTEGER: - return string(as_int()); - case type::FLOAT: - return string(as_double()); - case type::STRING: - return as_string(); + case mixed::type::NUL: + case mixed::type::BOOLEAN: + case mixed::type::INTEGER: + case mixed::type::FLOAT: + case mixed::type::STRING: + break; case type::ARRAY: php_warning("Conversion from array to string"); - return string("Array", 5); + break; + case type::OBJECT: { + php_warning("Wrong conversion from %s to string", get_type_or_class_name()); + break; + } default: __builtin_unreachable(); } + return to_string_without_warning(*this); } const array mixed::to_array() const { @@ -603,7 +678,8 @@ const array mixed::to_array() const { case type::BOOLEAN: case type::INTEGER: case type::FLOAT: - case type::STRING: { + case type::STRING: + case type::OBJECT: { array res(array_size(1, true)); res.push_back(*this); return res; @@ -630,6 +706,8 @@ const string &mixed::as_string() const { return *reinterpret_cast &mixed::as_array() { return *reinterpret_cast *>(&storage_); } const array &mixed::as_array() const { return *reinterpret_cast *>(&storage_); } +vk::intrusive_ptr mixed::as_object() { return *reinterpret_cast*>(&storage_); } +const vk::intrusive_ptr mixed::as_object() const { return *reinterpret_cast*>(&storage_); } int64_t mixed::safe_to_int() const { switch (get_type()) { @@ -651,6 +729,10 @@ int64_t mixed::safe_to_int() const { case type::ARRAY: php_warning("Wrong conversion from array to int"); return as_array().to_int(); + case type::OBJECT: { + php_warning("Wrong conversion from %s to int", get_type_or_class_name()); + return (as_object() ? 1 : 0); + } default: __builtin_unreachable(); } @@ -681,6 +763,14 @@ void mixed::convert_to_numeric() { as_int() = int_val; return; } + case type::OBJECT: { + php_warning("Wrong conversion from %s to number", get_type_or_class_name()); + const int64_t int_val = (as_object() ? 1 : 0); + destroy(); + type_ = type::INTEGER; + as_int() = int_val; + return; + } default: __builtin_unreachable(); } @@ -716,6 +806,13 @@ void mixed::convert_to_bool() { as_bool() = bool_val; return; } + case type::OBJECT: { + const bool bool_val = static_cast(as_object()); + destroy(); + type_ = type::BOOLEAN; + as_bool() = bool_val; + return; + } default: __builtin_unreachable(); } @@ -752,6 +849,14 @@ void mixed::convert_to_int() { as_int() = int_val; return; } + case type::OBJECT: { + php_warning("Wrong conversion from %s to int", get_type_or_class_name()); + const int64_t int_val = (as_object() ? 1 : 0); + destroy(); + type_ = type::INTEGER; + as_int() = int_val; + return; + } default: __builtin_unreachable(); } @@ -788,6 +893,14 @@ void mixed::convert_to_float() { as_double() = float_val; return; } + case type::OBJECT: { + php_warning("Wrong conversion from %s to float", get_type_or_class_name()); + const double float_val = (as_object() ? 1.0 : 0.0); + destroy(); + type_ = type::FLOAT; + as_double() = float_val; + return; + } default: __builtin_unreachable(); } @@ -823,6 +936,14 @@ void mixed::convert_to_string() { type_ = type::STRING; new(&as_string()) string("Array", 5); return; + case type::OBJECT: { + php_warning("Wrong conversion from %s to string", get_type_or_class_name()); + string s = string(get_type_or_class_name(), strlen(get_type_or_class_name())); + destroy(); + type_ = type::STRING; + new (&as_string()) string(std::move(s)); + return; + } default: __builtin_unreachable(); } @@ -833,7 +954,7 @@ const bool &mixed::as_bool(const char *function) const { case type::BOOLEAN: return as_bool(); default: - php_warning("%s() expects parameter to be boolean, %s is given", function, get_type_c_str()); + php_warning("%s() expects parameter to be boolean, %s is given", function, get_type_or_class_name()); return empty_value(); } } @@ -843,7 +964,7 @@ const int64_t &mixed::as_int(const char *function) const { case type::INTEGER: return as_int(); default: - php_warning("%s() expects parameter to be int, %s is given", function, get_type_c_str()); + php_warning("%s() expects parameter to be int, %s is given", function, get_type_or_class_name()); return empty_value(); } } @@ -853,7 +974,7 @@ const double &mixed::as_float(const char *function) const { case type::FLOAT: return as_double(); default: - php_warning("%s() expects parameter to be float, %s is given", function, get_type_c_str()); + php_warning("%s() expects parameter to be float, %s is given", function, get_type_or_class_name()); return empty_value(); } } @@ -863,7 +984,7 @@ const string &mixed::as_string(const char *function) const { case type::STRING: return as_string(); default: - php_warning("%s() expects parameter to be string, %s is given", function, get_type_c_str()); + php_warning("%s() expects parameter to be string, %s is given", function, get_type_or_class_name()); return empty_value(); } } @@ -873,7 +994,7 @@ const array &mixed::as_array(const char *function) const { case type::ARRAY: return as_array(); default: - php_warning("%s() expects parameter to be array, %s is given", function, get_type_c_str()); + php_warning("%s() expects parameter to be array, %s is given", function, get_type_or_class_name()); return empty_value>(); } } @@ -886,7 +1007,7 @@ bool &mixed::as_bool(const char *function) { case type::BOOLEAN: return as_bool(); default: - php_warning("%s() expects parameter to be boolean, %s is given", function, get_type_c_str()); + php_warning("%s() expects parameter to be boolean, %s is given", function, get_type_or_class_name()); return empty_value(); } } @@ -901,7 +1022,7 @@ int64_t &mixed::as_int(const char *function) { case type::INTEGER: return as_int(); default: - php_warning("%s() expects parameter to be int, %s is given", function, get_type_c_str()); + php_warning("%s() expects parameter to be int, %s is given", function, get_type_or_class_name()); return empty_value(); } } @@ -916,7 +1037,7 @@ double &mixed::as_float(const char *function) { case type::FLOAT: return as_double(); default: - php_warning("%s() expects parameter to be float, %s is given", function, get_type_c_str()); + php_warning("%s() expects parameter to be float, %s is given", function, get_type_or_class_name()); return empty_value(); } } @@ -931,7 +1052,7 @@ string &mixed::as_string(const char *function) { case type::STRING: return as_string(); default: - php_warning("%s() expects parameter to be string, %s is given", function, get_type_c_str()); + php_warning("%s() expects parameter to be string, %s is given", function, get_type_or_class_name()); return empty_value(); } } @@ -941,7 +1062,7 @@ array &mixed::as_array(const char *function) { case type::ARRAY: return as_array(); default: - php_warning("%s() expects parameter to be array, %s is given", function, get_type_c_str()); + php_warning("%s() expects parameter to be array, %s is given", function, get_type_or_class_name()); return empty_value>(); } } @@ -960,7 +1081,7 @@ bool mixed::is_numeric() const { } bool mixed::is_scalar() const { - return get_type() != type::NUL && get_type() != type::ARRAY; + return get_type() != type::NUL && get_type() != type::ARRAY && get_type() != type::OBJECT; } @@ -992,6 +1113,9 @@ bool mixed::is_array() const { return get_type() == type::ARRAY; } +bool mixed::is_object() const { + return get_type() == type::OBJECT; +} inline const char *mixed::get_type_c_str() const { switch (get_type()) { @@ -1007,11 +1131,22 @@ inline const char *mixed::get_type_c_str() const { return "string"; case type::ARRAY: return "array"; + case type::OBJECT: + return "object"; default: __builtin_unreachable(); } } +inline const char *mixed::get_type_or_class_name() const { + switch (get_type()) { + case type::OBJECT: + return as_object()->get_class(); + default: + return get_type_c_str(); + } +} + inline const string mixed::get_type_str() const { return string(get_type_c_str()); } @@ -1034,6 +1169,9 @@ int64_t mixed::count() const { return 1; case type::ARRAY: return as_array().count(); + case type::OBJECT: + php_warning("count(): Parameter is %s, but an array expected", get_type_or_class_name()); + return (as_object() ? 1 : 0); default: __builtin_unreachable(); } @@ -1066,6 +1204,10 @@ int64_t mixed::compare(const mixed &rhs) const { php_warning("Unsupported operand types for operator < or <= (%s and %s)", get_type_c_str(), rhs.get_type_c_str()); return is_array() ? 1 : -1; } + if (unlikely(is_object() || rhs.is_object())) { + php_warning("Unsupported operand types for operator < or <= (%s and %s)", get_type_or_class_name(), get_type_or_class_name()); + return is_object() ? 1 : -1; + } return three_way_comparison(to_float(), rhs.to_float()); } @@ -1087,7 +1229,7 @@ mixed &mixed::operator[](int64_t int_key) { type_ = type::ARRAY; new(&as_array()) array(); } else { - php_warning("Cannot use a value \"%s\" of type %s as an array, index = %" PRIi64, to_string().c_str(), get_type_c_str(), int_key); + php_warning("Cannot use a value \"%s\" of type %s as an array, index = %" PRIi64, to_string_without_warning(*this).c_str(), get_type_or_class_name(), int_key); return empty_value(); } } @@ -1105,7 +1247,7 @@ mixed &mixed::operator[](const string &string_key) { type_ = type::ARRAY; new(&as_array()) array(); } else { - php_warning("Cannot use a value \"%s\" of type %s as an array, index = %s", to_string().c_str(), get_type_c_str(), string_key.c_str()); + php_warning("Cannot use a value \"%s\" of type %s as an array, index = %s", to_string_without_warning(*this).c_str(), get_type_or_class_name(), string_key.c_str()); return empty_value(); } } @@ -1135,6 +1277,9 @@ mixed &mixed::operator[](const mixed &v) { case type::ARRAY: php_warning("Illegal offset type %s", v.get_type_c_str()); return (*this)[v.as_array().to_int()]; + case type::OBJECT: + php_warning("Illegal offset type %s", v.get_type_or_class_name()); + return (*this)[(as_object() ? 1 : 0)]; default: __builtin_unreachable(); } @@ -1183,7 +1328,7 @@ void mixed::set_value(int64_t int_key, const mixed &v) { type_ = type::ARRAY; new(&as_array()) array(); } else { - php_warning("Cannot use a value \"%s\" of type %s as an array, index = %" PRIi64, to_string().c_str(), get_type_c_str(), int_key); + php_warning("Cannot use a value \"%s\" of type %s as an array, index = %" PRIi64, to_string_without_warning(*this).c_str(), get_type_or_class_name(), int_key); return; } } @@ -1219,7 +1364,7 @@ void mixed::set_value(const string &string_key, const mixed &v) { type_ = type::ARRAY; new(&as_array()) array(); } else { - php_warning("Cannot use a value \"%s\" of type %s as an array, index = %s", to_string().c_str(), get_type_c_str(), string_key.c_str()); + php_warning("Cannot use a value \"%s\" of type %s as an array, index = %s", to_string_without_warning(*this).c_str(), get_type_or_class_name(), string_key.c_str()); return; } } @@ -1251,6 +1396,9 @@ void mixed::set_value(const mixed &v, const mixed &value) { case type::ARRAY: php_warning("Illegal offset type array"); return; + case type::OBJECT: + php_warning("Illegal offset type %s", v.get_type_or_class_name()); + return; default: __builtin_unreachable(); } @@ -1279,7 +1427,7 @@ const mixed mixed::get_value(int64_t int_key) const { } if (get_type() != type::NUL && (get_type() != type::BOOLEAN || as_bool())) { - php_warning("Cannot use a value \"%s\" of type %s as an array, index = %" PRIi64, to_string().c_str(), get_type_c_str(), int_key); + php_warning("Cannot use a value \"%s\" of type %s as an array, index = %" PRIi64, to_string_without_warning(*this).c_str(), get_type_or_class_name(), int_key); } return mixed(); } @@ -1302,7 +1450,7 @@ const mixed mixed::get_value(const string &string_key) const { } if (get_type() != type::NUL && (get_type() != type::BOOLEAN || as_bool())) { - php_warning("Cannot use a value \"%s\" of type %s as an array, index = %s", to_string().c_str(), get_type_c_str(), string_key.c_str()); + php_warning("Cannot use a value \"%s\" of type %s as an array, index = %s", to_string_without_warning(*this).c_str(), get_type_or_class_name(), string_key.c_str()); } return mixed(); } @@ -1338,6 +1486,9 @@ const mixed mixed::get_value(const mixed &v) const { case type::ARRAY: php_warning("Illegal offset type %s", v.get_type_c_str()); return mixed(); + case type::OBJECT: + php_warning("Illegal offset type %s", v.get_type_or_class_name()); + return mixed(); default: __builtin_unreachable(); } @@ -1362,7 +1513,7 @@ void mixed::push_back(const mixed &v) { type_ = type::ARRAY; new(&as_array()) array(); } else { - php_warning("[] operator not supported for type %s", get_type_c_str()); + php_warning("[] operator not supported for type %s", get_type_or_class_name()); return; } } @@ -1376,7 +1527,7 @@ const mixed mixed::push_back_return(const mixed &v) { type_ = type::ARRAY; new(&as_array()) array(); } else { - php_warning("[] operator not supported for type %s", get_type_c_str()); + php_warning("[] operator not supported for type %s", get_type_or_class_name()); return empty_value(); } } @@ -1393,7 +1544,7 @@ bool mixed::isset(int64_t int_key) const { } if (get_type() != type::NUL && (get_type() != type::BOOLEAN || as_bool())) { - php_warning("Cannot use variable of type %s as array in isset", get_type_c_str()); + php_warning("Cannot use variable of type %s as array in isset", get_type_or_class_name()); } return false; } @@ -1433,6 +1584,9 @@ bool mixed::isset(const mixed &v) const { case type::ARRAY: php_warning("Illegal offset type array"); return false; + case type::OBJECT: + php_warning("Illegal offset type %s", get_type_or_class_name()); + return false; default: __builtin_unreachable(); } @@ -1445,7 +1599,7 @@ bool mixed::isset(double double_key) const { void mixed::unset(int64_t int_key) { if (unlikely (get_type() != type::ARRAY)) { if (get_type() != type::NUL && (get_type() != type::BOOLEAN || as_bool())) { - php_warning("Cannot use variable of type %s as array in unset", get_type_c_str()); + php_warning("Cannot use variable of type %s as array in unset", get_type_or_class_name()); } return; } @@ -1457,7 +1611,7 @@ template void mixed::unset(const string &string_key, MaybeHash ...maybe_hash) { if (unlikely (get_type() != type::ARRAY)) { if (get_type() != type::NUL && (get_type() != type::BOOLEAN || as_bool())) { - php_warning("Cannot use variable of type %s as array in unset", get_type_c_str()); + php_warning("Cannot use variable of type %s as array in unset", get_type_or_class_name()); } return; } @@ -1468,7 +1622,7 @@ void mixed::unset(const string &string_key, MaybeHash ...maybe_hash) { void mixed::unset(const mixed &v) { if (unlikely (get_type() != type::ARRAY)) { if (get_type() != type::NUL && (get_type() != type::BOOLEAN || as_bool())) { - php_warning("Cannot use variable of type %s as array in unset", get_type_c_str()); + php_warning("Cannot use variable of type %s as array in unset", get_type_or_class_name()); } return; } @@ -1492,6 +1646,9 @@ void mixed::unset(const mixed &v) { case type::ARRAY: php_warning("Illegal offset type array"); break; + case type::OBJECT: + php_warning("Illegal offset type %s", v.get_type_or_class_name()); + break; default: __builtin_unreachable(); } @@ -1505,7 +1662,7 @@ array::const_iterator mixed::begin() const { if (likely (get_type() == type::ARRAY)) { return as_array().begin(); } - php_warning("Invalid argument supplied for foreach(), %s (string representation - \"%s\") is given", get_type_c_str(), to_string().c_str()); + php_warning("Invalid argument supplied for foreach(), %s (string representation - \"%s\") is given", get_type_or_class_name(), to_string_without_warning(*this).c_str()); return array::const_iterator(); } @@ -1521,7 +1678,7 @@ array::iterator mixed::begin() { if (likely (get_type() == type::ARRAY)) { return as_array().begin(); } - php_warning("Invalid argument supplied for foreach(), %s (string representation - \"%s\") is given", get_type_c_str(), to_string().c_str()); + php_warning("Invalid argument supplied for foreach(), %s (string representation - \"%s\") is given", get_type_or_class_name(), to_string_without_warning(*this).c_str()); return array::iterator(); } @@ -1547,6 +1704,8 @@ int64_t mixed::get_reference_counter() const { return as_string().get_reference_counter(); case type::ARRAY: return as_array().get_reference_counter(); + case type::OBJECT: + return as_object()->get_refcnt(); default: __builtin_unreachable(); } @@ -1563,6 +1722,8 @@ void mixed::set_reference_counter_to(ExtraRefCnt ref_cnt_value) noexcept { return as_string().set_reference_counter_to(ref_cnt_value); case type::ARRAY: return as_array().set_reference_counter_to(ref_cnt_value); + case type::OBJECT: + return as_object()->set_refcnt(ref_cnt_value); default: __builtin_unreachable(); } @@ -1579,6 +1740,8 @@ inline bool mixed::is_reference_counter(ExtraRefCnt ref_cnt_value) const noexcep return as_string().is_reference_counter(ref_cnt_value); case type::ARRAY: return as_array().is_reference_counter(ref_cnt_value); + case type::OBJECT: + return static_cast(as_object()->get_refcnt()) == ref_cnt_value; default: __builtin_unreachable(); } @@ -1592,6 +1755,9 @@ inline void mixed::force_destroy(ExtraRefCnt expected_ref_cnt) noexcept { case type::ARRAY: as_array().force_destroy(expected_ref_cnt); break; + case type::OBJECT: + php_warning("Objects (%s) are not supported in confdata", get_type_or_class_name()); + break; default: __builtin_unreachable(); } @@ -1608,6 +1774,9 @@ size_t mixed::estimate_memory_usage() const { return as_string().estimate_memory_usage(); case type::ARRAY: return as_array().estimate_memory_usage(); + case type::OBJECT: + php_warning("Objects (%s) are not supported in confdata", get_type_or_class_name()); + return 0; default: __builtin_unreachable(); } @@ -1624,7 +1793,8 @@ void mixed::reset_empty_values() noexcept { template T &mixed::empty_value() noexcept { - static_assert(vk::is_type_in_list>{}, "unsupported type"); + static_assert(vk::is_type_in_list>{} || is_type_acceptable_for_mixed::value, "unsupported type"); + static T value; value = T{}; return value; @@ -1843,6 +2013,11 @@ inline string_buffer &operator<<(string_buffer &sb, const mixed &v) { case mixed::type::ARRAY: php_warning("Conversion from array to string"); return sb.append("Array", 5); + case mixed::type::OBJECT: { + const char *s = v.get_type_or_class_name(); + php_warning("Conversion from %s to string", s); + return sb.append(s, strlen(s)); + } default: __builtin_unreachable(); } diff --git a/runtime-core/core-types/definition/string.inl b/runtime-core/core-types/definition/string.inl index e8819e7865..7732241f2c 100644 --- a/runtime-core/core-types/definition/string.inl +++ b/runtime-core/core-types/definition/string.inl @@ -482,6 +482,9 @@ string &string::append(const mixed &v) { case mixed::type::ARRAY: php_warning("Conversion from array to string"); return append("Array", 5); + case mixed::type::OBJECT: + php_warning("Conversion from %s to string", v.get_type_or_class_name()); + return append(v.get_type_or_class_name(), strlen(v.get_type_or_class_name())); default: __builtin_unreachable(); } @@ -564,6 +567,9 @@ string &string::append_unsafe(const mixed &v) { return append_unsafe(v.as_string()); case mixed::type::ARRAY: return append_unsafe(v.as_array()); + case mixed::type::OBJECT: + php_warning("Conversion from %s to string", v.get_type_or_class_name()); + return append(v.get_type_or_class_name(), strlen(v.get_type_or_class_name())); default: __builtin_unreachable(); } @@ -1031,6 +1037,9 @@ const string string::get_value(const mixed &v) const { case mixed::type::ARRAY: php_warning("Illegal offset type %s", v.get_type_c_str()); return string(); + case mixed::type::OBJECT: + php_warning("Illegal offset type %s", v.get_type_or_class_name()); + return string(); default: __builtin_unreachable(); } @@ -1181,6 +1190,8 @@ string::size_type max_string_size(const mixed &v) { return max_string_size(v.as_string()); case mixed::type::ARRAY: return max_string_size(v.as_array()); + case mixed::type::OBJECT: + php_warning("Cannot use objects (%s) in string builder", v.get_type_or_class_name()); default: __builtin_unreachable(); } diff --git a/runtime-core/core-types/kphp_type_traits.h b/runtime-core/core-types/kphp_type_traits.h index 0917cb0aea..4ef0223afe 100644 --- a/runtime-core/core-types/kphp_type_traits.h +++ b/runtime-core/core-types/kphp_type_traits.h @@ -42,6 +42,9 @@ template struct is_class_instance> : std::true_type { }; +template +inline constexpr bool is_class_instance_v = is_class_instance::value; + template struct is_class_instance_inside : is_class_instance { }; diff --git a/runtime-core/runtime-core-context.h b/runtime-core/runtime-core-context.h index a6ae90598f..426ef70c07 100644 --- a/runtime-core/runtime-core-context.h +++ b/runtime-core/runtime-core-context.h @@ -5,33 +5,11 @@ #pragma once #include -#include "runtime-core/memory-resource/unsynchronized_pool_resource.h" #ifndef INCLUDED_FROM_KPHP_CORE #error "this file must be included only from runtime-core.h" #endif -struct RuntimeAllocator { - static RuntimeAllocator& current() noexcept; - - RuntimeAllocator() = default; - RuntimeAllocator(size_t script_mem_size, size_t oom_handling_mem_size); - - void init(void * buffer, size_t script_mem_size, size_t oom_handling_mem_size); - void free(); - - void * alloc_script_memory(size_t size) noexcept; - void * alloc0_script_memory(size_t size) noexcept; - void * realloc_script_memory(void *mem, size_t new_size, size_t old_size) noexcept; - void free_script_memory(void *mem, size_t size) noexcept; - - void * alloc_global_memory(size_t size) noexcept; - void * alloc0_global_memory(size_t size) noexcept; - void * realloc_global_memory(void *mem, size_t new_size, size_t old_size) noexcept; - void free_global_memory(void *mem, size_t size) noexcept; - - memory_resource::unsynchronized_pool_resource memory_resource; -}; struct KphpCoreContext { /** diff --git a/runtime-core/runtime-core.h b/runtime-core/runtime-core.h index cc262a2f09..549203f99c 100644 --- a/runtime-core/runtime-core.h +++ b/runtime-core/runtime-core.h @@ -32,6 +32,7 @@ #include "runtime-core/core-types/decl/mixed_decl.inl" #include "runtime-core/core-types/decl/string_buffer_decl.inl" +#include "runtime-core/allocator/runtime-allocator.h" #include "runtime-core/runtime-core-context.h" #include "runtime-core/core-types/definition/string.inl" @@ -375,11 +376,21 @@ inline bool f$is_a(const class_instance &base) { return base.template is_a(); } +template +inline bool f$is_a(const mixed &base) { + return base.is_a(); +} + template inline ClassInstanceDerived f$instance_cast(const class_instance &base, const string &) { return base.template cast_to(); } +template +inline ClassInstanceDerived f$instance_cast(const mixed &base, const string &s) { + return from_mixed(base, s); +} + inline const char *get_type_c_str(bool); inline const char *get_type_c_str(int64_t); inline const char *get_type_c_str(double); diff --git a/runtime/json-functions.cpp b/runtime/json-functions.cpp index c066304e23..fcb920602f 100644 --- a/runtime/json-functions.cpp +++ b/runtime/json-functions.cpp @@ -303,6 +303,9 @@ bool JsonEncoder::encode(const mixed &v) noexcept { return encode(v.as_string()); case mixed::type::ARRAY: return encode(v.as_array()); + case mixed::type::OBJECT: + php_warning("Objects (%s) are not supported in JsonEncoder", v.get_type_or_class_name()); + return false; default: __builtin_unreachable(); } diff --git a/runtime/misc.cpp b/runtime/misc.cpp index ad82d4f385..a208ea55de 100644 --- a/runtime/misc.cpp +++ b/runtime/misc.cpp @@ -533,6 +533,11 @@ void do_print_r(const mixed &v, int depth) { *coub << shift << ")\n"; break; } + case mixed::type::OBJECT: { + php_warning("print_r used on object"); + *coub << v.as_object()->get_class(); + break; + } default: __builtin_unreachable(); } @@ -579,6 +584,12 @@ void do_var_dump(const mixed &v, int depth) { *coub << shift << "}"; break; } + case mixed::type::OBJECT: { + php_warning("var_dump used on object"); + auto s = string(v.as_object()->get_class(), (string::size_type)strlen(v.as_object()->get_class())); + *coub << shift << "string(" << (int)s.size() << ") \"" << s << '"'; + break; + } default: __builtin_unreachable(); } @@ -654,6 +665,10 @@ void do_var_export(const mixed &v, int depth, char endc = 0) { *coub << shift << ")"; break; } + case mixed::type::OBJECT: { + *coub << shift << v.get_type_or_class_name(); + break; + } default: __builtin_unreachable(); } diff --git a/runtime/msgpack/adaptors.h b/runtime/msgpack/adaptors.h index fa2070635b..911347d660 100644 --- a/runtime/msgpack/adaptors.h +++ b/runtime/msgpack/adaptors.h @@ -498,6 +498,9 @@ struct pack { case mixed::type::ARRAY: packer.pack(v.as_array()); break; + case mixed::type::OBJECT: + php_warning("Objects (%s) are not supported in msgpack", v.get_type_or_class_name()); + break; default: __builtin_unreachable(); } diff --git a/runtime/serialize-functions.cpp b/runtime/serialize-functions.cpp index ba7cadd15b..15cc4fcf27 100644 --- a/runtime/serialize-functions.cpp +++ b/runtime/serialize-functions.cpp @@ -60,6 +60,9 @@ void impl_::PhpSerializer::serialize(const mixed &v) noexcept { return serialize(v.as_string()); case mixed::type::ARRAY: return serialize(v.as_array()); + case mixed::type::OBJECT: + php_warning("Cannot serialize object of type %s", v.get_type_or_class_name()); + return; default: __builtin_unreachable(); } diff --git a/runtime/to-json-processor.h b/runtime/to-json-processor.h index aa3856362f..c2d502fb6d 100644 --- a/runtime/to-json-processor.h +++ b/runtime/to-json-processor.h @@ -99,6 +99,9 @@ class ToJsonVisitor { case mixed::type::ARRAY: process_impl(value.as_array()); break; + case mixed::type::OBJECT: + php_warning("Objects (%s) in mixed cannot be written in JSON", value.get_type_or_class_name()); + break; } } diff --git a/tests/phpt/class_inheritance/123_instanceof_fail.php b/tests/phpt/class_inheritance/123_instanceof_fail.php index 32b73a6c28..2d60f42e93 100644 --- a/tests/phpt/class_inheritance/123_instanceof_fail.php +++ b/tests/phpt/class_inheritance/123_instanceof_fail.php @@ -1,6 +1,5 @@ @kphp_should_fail -/pass int to argument \$instance of instance_cast/ -/but it's declared as @param object/ +/left operand of 'instanceof' should be an instance or mixed, but passed int/ data = $x; + } + public function print() { + echo "A::data = " . $this->data . "\n"; + } +} + +function foo() { + /** @var mixed */ + $x; + + $a = to_mixed(new A(42)); + $x = $a; + + /** @var mixed */ + $b = to_mixed(new A(123)); +} + +foo(); diff --git a/tests/phpt/mixed/non_primitive/002_instanceof.php b/tests/phpt/mixed/non_primitive/002_instanceof.php new file mode 100644 index 0000000000..f1f6b48abb --- /dev/null +++ b/tests/phpt/mixed/non_primitive/002_instanceof.php @@ -0,0 +1,47 @@ +@ok +data = $x; + } + public function print() { + echo "A::data = " . $this->data . "\n"; + } +} + +class C { + +} + +/** + * @param mixed $x + */ +function check_instance_of($x) { + if ($x instanceof A) { + echo "instanceof A\n"; + } else { + echo "not instanceof A\n"; + } + + if ($x instanceof C) { + echo "instanceof C\n"; + } else { + echo "not instanceof C\n"; + } +} + +function foo() { + $obj_as_mixed = to_mixed(new A(123)); + check_instance_of($obj_as_mixed); + + /** @var mixed */ + $int_as_mixed = 42; + check_instance_of($int_as_mixed); + +} + +foo(); diff --git a/tests/phpt/mixed/non_primitive/003_load_class.php b/tests/phpt/mixed/non_primitive/003_load_class.php new file mode 100644 index 0000000000..bd9b011ecb --- /dev/null +++ b/tests/phpt/mixed/non_primitive/003_load_class.php @@ -0,0 +1,22 @@ +@ok +data = $x; + } + public function print() { + echo "A::data = " . $this->data . "\n"; + } +} + +function foo() { + $a = to_mixed(new A(123)); + if ($a instanceof A) { + $a->print(); + } +} + +foo(); diff --git a/tests/phpt/mixed/non_primitive/004_array_of_classes.php b/tests/phpt/mixed/non_primitive/004_array_of_classes.php new file mode 100644 index 0000000000..a08765baf7 --- /dev/null +++ b/tests/phpt/mixed/non_primitive/004_array_of_classes.php @@ -0,0 +1,32 @@ +@ok +data = $x; + } + public function print() { + echo "A::data = " . $this->data . "\n"; + } +} + +function foo() { + $x = to_mixed(new A(0)); + $arr = [to_mixed(new A(123)), $x, 123, "abcd", [null, $x], $x, to_mixed(new A(456))]; + foreach($arr as $item) { + if ($item instanceof A) { + $item->print(); + } + if (is_array($item)) { + foreach ($item as $inner_item) { + if ($inner_item instanceof A) { + $inner_item->print(); + } + } + } + } +} + +foo(); diff --git a/tests/phpt/mixed/non_primitive/005_polymorphism.php b/tests/phpt/mixed/non_primitive/005_polymorphism.php new file mode 100644 index 0000000000..54abbdece9 --- /dev/null +++ b/tests/phpt/mixed/non_primitive/005_polymorphism.php @@ -0,0 +1,69 @@ +@ok +data = $x; + } + public function print() { + echo "A::data = " . $this->data . "\n"; + } + public function foo(int $x) { + echo "From foo: A::data = " . $this->data . "; arg = " . $x . "\n"; + } +} + +class X { + public int $x_data = 0; +} + +class B extends X { + public function __construct(int $x) { + $this->x_data = $x; + } +} + +class C extends X implements Printer, Fooable { + public int $data; + public function __construct(int $x) { + $this->data = $x; + $this->x_data = $x * 3 + 42; + } + public function print() { + echo "C::print (" . $this->data . ", " . $this->x_data . ")\n"; + } + public function foo(int $x) { + echo "C::foo (" . $this->data . ", " . $this->x_data . ", " . $x . ")\n"; + } +} + +/** @param mixed $m */ +function check($m) { + if ($m instanceof Printer) { + $m->print(); + } + if ($m instanceof Fooable) { + $m->foo(42); + } + if ($m instanceof X) { + echo "X data = " . $m->x_data . "\n"; + } +} + +function main() { + check(to_mixed(new A(42))); + check(to_mixed(new B(123))); + check(to_mixed(new C(456))); +} + +main(); diff --git a/tests/phpt/mixed/non_primitive/006_inheritance.php b/tests/phpt/mixed/non_primitive/006_inheritance.php new file mode 100644 index 0000000000..649f639969 --- /dev/null +++ b/tests/phpt/mixed/non_primitive/006_inheritance.php @@ -0,0 +1,144 @@ +@ok +just_base_data = [1, "data", 42.42]; + } + public function print() { + var_dump($this->just_base_data); + } +} + +class IntBase extends JustBase { + public int $int_base_data1; + public int $int_base_data2; + public function __construct() { + parent::__construct(); + $this->int_base_data1 = 123; + $this->int_base_data2 = 456; + } + public function print() { + parent::print(); + var_dump($this->int_base_data1); + var_dump($this->int_base_data2); + } +} + +class ID1 extends IntBase { + public string $s; + public function __construct(string $s2) { + parent::__construct(); + $this->s = $s2; + } + public function print() { + parent::print(); + echo "ID1::print and s = " . $this->s . "\n"; + } +} + +class ID2 extends IntBase { + public string $s1; + public string $s2; + public string $s3; + public string $s4; + + public function __construct(string $p) { + parent::__construct(); + $this->s1 = $p . " first"; + $this->s2 = $p . " second"; + $this->s3 = $p . " third"; + $this->s4 = $p . " fourth"; + } + public function print() { + parent::print(); + echo "ID2::print and s1 = " . $this->s1 . " and s2 = ". $this->s2 . "and s3 = " . $this->s3 . "and s4 = " . $this->s4 ."\n"; + } +} + +class StringBase extends JustBase { + public string $string_base_data1; + public string $string_base_data2; + public function __construct() { + parent::__construct(); + $this->string_base_data1 = "lupa"; + $this->string_base_data2 = "pupa"; + } + public function print() { + parent::print(); + var_dump($this->string_base_data1); + var_dump($this->string_base_data2); + } +} + +class SD1 extends StringBase { + public int $kek; + public float $lol; + public function __construct(int $x, float $y) { + parent::__construct(); + $this->kek = $x; + $this->lol = $y; + } + public function print() { + parent::print(); + var_dump($this->kek); + var_dump($this->lol); + } +} + +class SD2 extends StringBase { + public function __construct() { + parent::__construct(); + } + public function print() { + parent::print(); + echo "From SD2::print\n"; + } +} + +/** @param mixed $m */ +function check($m) { + if ($m instanceof JustBase) { + echo "instanceof JustBase!\n"; + $m->print(); + } + if ($m instanceof IntBase) { + echo "instanceof IntBase!\n"; + $m->print(); + } + if ($m instanceof ID1) { + echo "instanceof ID1!\n"; + $m->print(); + } + if ($m instanceof ID2) { + echo "instanceof ID2!\n"; + $m->print(); + } + if ($m instanceof StringBase) { + echo "instanceof StringBase!\n"; + $m->print(); + } + if ($m instanceof SD1) { + echo "instanceof SD1!\n"; + $m->print(); + } + if ($m instanceof SD2) { + echo "instanceof SD2!\n"; + $m->print(); + } +} + +function main() { + check(to_mixed(new JustBase())); + check(to_mixed(new IntBase())); + check(to_mixed(new ID1("kitten"))); + check(to_mixed(new ID2("puppy"))); + check(to_mixed(new StringBase())); + check(to_mixed(new SD1(1234, 4.04))); + check(to_mixed(new SD2())); + +} + +main(); diff --git a/tests/phpt/mixed/non_primitive/007_fields.php b/tests/phpt/mixed/non_primitive/007_fields.php new file mode 100644 index 0000000000..a624ebaf8d --- /dev/null +++ b/tests/phpt/mixed/non_primitive/007_fields.php @@ -0,0 +1,45 @@ +@ok +x = $y; + } +} + +class A { + static $static_data = [1, 2, 5]; + public $data; + public function __construct($x) { + $this->data = $x; + } +} + +function traverse($m) { + if ($m instanceof IntWrapper) { + echo "data = " . $m->x . "\n"; + } + if (is_array($m)) { + foreach ($m as $inner) { + traverse($inner); + } + } +} + +function check(A $a) { + traverse($a->data); + traverse(A::$static_data); +} + +function main() { + A::$static_data = 52; + check(new A(123)); + check(new A(to_mixed(new IntWrapper(432)))); + A::$static_data = [[[[to_mixed(new IntWrapper(12)), ["pppp" => to_mixed(new IntWrapper(123)), [1, 2, 3]]], null]], to_mixed(new IntWrapper(555))]; + check(new A("abobus")); + check(new A([[[[to_mixed(new IntWrapper(555)), to_mixed(new IntWrapper(444))]], to_mixed(new IntWrapper(11111))]])); +} + +main(); diff --git a/tests/phpt/mixed/non_primitive/008_forks.php b/tests/phpt/mixed/non_primitive/008_forks.php new file mode 100644 index 0000000000..5a4d1cecc6 --- /dev/null +++ b/tests/phpt/mixed/non_primitive/008_forks.php @@ -0,0 +1,49 @@ +@ok +data = $x; + } +} + +/** @return mixed */ +function print_and_get(int $x) { + $res = 0; + for ($i = 0; $i < $x; $i++) { + $res = (103 * $i + 101 + $res) % 1000000007; + } + echo $res . "\n"; + if ($res % 3 == 0) { + return $res; + } + if ($res % 4 == 1) { + return (string)$res; + } + + return to_mixed(new IntWrapper($res)); +} + +function check(int $x) { + $fut = fork(print_and_get($x)); + echo "Waiting...\n"; + $res = wait($fut); + if ($res instanceof IntWrapper) { + echo "IntWrapper!\n"; + } + if (is_string($res)) { + echo "string!\n"; + } else if (is_int($res)) { + echo "int!\n"; + } +} + +function main() { + check(0); + check(1); + check(5); +} + +main(); diff --git a/tests/phpt/mixed/non_primitive/009_empty.php b/tests/phpt/mixed/non_primitive/009_empty.php new file mode 100644 index 0000000000..b5b2cd47d3 --- /dev/null +++ b/tests/phpt/mixed/non_primitive/009_empty.php @@ -0,0 +1,104 @@ +@ok +data = $x; + } + public function print() { + echo "NonEmptyBase::data = " . $this->data . "\n"; + } +} + +class EmptyFromEmpty extends EmptyBase { + public function print() { + echo "EmptyFromEmpty\n"; + } +} + +class EmptyFromNonEmpty extends NonEmptyBase { + public function print() { + echo "EmptyFromNonEmpty::"; + parent::print(); + } +} + +class NonEmptyFromEmpty extends EmptyBase { + public string $str; + public function __construct(string $x) { + $this->str = $x; + } + public function print() { + echo "EmptyFromNonEmpty::str = " . $this->str . "\n"; + parent::print(); + } +} + +class NonEmptyFromNonEmpty extends NonEmptyBase { + public string $str; + public function __construct(string $x) { + $this->str = $x; + } + public function print() { + echo "NonEmptyFromNonEmpty::str = " . $this->str . "\n"; + parent::print(); + } +} + +/** @param mixed $m */ +function check($m) { + if ($m instanceof EmptyNoHierarchy) { + echo "instanceof EmptyNoHierarchy!\n"; + $m->print(); + } + if ($m instanceof EmptyBase) { + echo "instanceof EmptyBase!\n"; + $m->print(); + } + if ($m instanceof NonEmptyBase) { + echo "instanceof NonEmptyBase!\n"; + $m->print(); + } + if ($m instanceof EmptyFromEmpty) { + echo "instanceof EmptyFromEmpty!\n"; + $m->print(); + } + if ($m instanceof NonEmptyFromEmpty) { + echo "instanceof NonEmptyFromEmpty!\n"; + $m->print(); + } + if ($m instanceof EmptyFromNonEmpty) { + echo "instanceof EmptyFromNonEmpty!\n"; + $m->print(); + } + if ($m instanceof NonEmptyFromNonEmpty) { + echo "instanceof NonEmptyFromNonEmpty!\n"; + $m->print(); + } +} + +function foo() { + check(to_mixed(new EmptyNoHierarchy())); + check(to_mixed(new EmptyBase())); + check(to_mixed(new NonEmptyBase(0))); + check(to_mixed(new EmptyFromEmpty())); + check(to_mixed(new EmptyFromNonEmpty(42))); + check(to_mixed(new NonEmptyFromEmpty("lupa"))); + check(to_mixed(new NonEmptyFromNonEmpty("pupa"))); +} + +foo(); diff --git a/tests/phpt/mixed/non_primitive/010_to_mixed_with_base_class.php b/tests/phpt/mixed/non_primitive/010_to_mixed_with_base_class.php new file mode 100644 index 0000000000..3372bdaa0b --- /dev/null +++ b/tests/phpt/mixed/non_primitive/010_to_mixed_with_base_class.php @@ -0,0 +1,70 @@ +@ok +data = $x; + } + public function print() { + echo "In A::print. data = " . $this->data . "\n"; + } +}; + +interface Fooable { + public function foo(); +} + +class B extends A implements Fooable { + public string $str_data; + public function __construct(int $x, string $y) { + parent::__construct($x); + $this->str_data = $y; + } + public function print() { + echo "In B::print. data = " . $this->data . "; str_data = " . $this->str_data . "\n"; + } + public function foo() { + echo "In B::foo.\n"; + } +}; + +/** @param mixed $x */ +function check($x) { + if ($x instanceof A) { + echo "instanceof A\n"; + $x->print(); + } + if ($x instanceof B) { + echo "instanceof B\n"; + $x->print(); + $x->foo(); + } + if ($x instanceof Fooable) { + echo "instanceof C\n"; + $x->foo(); + } +} + +/** @param A $a + * @return mixed +*/ +function convert_from_a($a) { + return to_mixed($a); +} + +/** @param Fooable $obj + * @return mixed +*/ +function convert_from_Fooable($obj) { + return to_mixed($obj); +} + +function main() { + check(convert_from_a(new A(42))); + check(convert_from_a(new B(200, "OK"))); + check(convert_from_Fooable(new B(404, "Not Found"))); +} + +main(); diff --git a/tests/phpt/mixed/non_primitive/011_equals.php b/tests/phpt/mixed/non_primitive/011_equals.php new file mode 100644 index 0000000000..2ee3cc2fae --- /dev/null +++ b/tests/phpt/mixed/non_primitive/011_equals.php @@ -0,0 +1,36 @@ +@ok +$x = $y; + } +} + +/** @param mixed $a + * @param mixed $b + */ +function check($a, $b) { + if ($a === $b) { + echo "EQ3!\n"; + } else { + echo "NEQ3!\n"; + } +} + +function main() { + $x = new A(123); + $y = to_mixed($x); + /** @var mixed */ + $arr = [to_mixed(new A(42)), to_mixed(new A(52)), [to_mixed($x)], to_mixed($x), + to_mixed($x), [$y], $y, null, [1, 2, 3], "abobus", "a"."b"]; + foreach ($arr as $item1) { + foreach ($arr as $item2) { + check($item1, $item2); + } + } +} + +main(); diff --git a/tests/phpt/mixed/non_primitive/012_class_as_bool.php b/tests/phpt/mixed/non_primitive/012_class_as_bool.php new file mode 100644 index 0000000000..c107422990 --- /dev/null +++ b/tests/phpt/mixed/non_primitive/012_class_as_bool.php @@ -0,0 +1,35 @@ +@ok +$x = $y; + } +} + +/** @param mixed $a */ +function check($a) { + if ($a) { + echo "Not Null!\n"; + } else { + echo "Null!\n"; + } +} + +function main() { + /** @var mixed */ + $a = to_mixed(new A(123)); + check($a); + if (1 + 1 === 2) { + $a = null; + } + check($a); + if (1 + 2 == 4) { + $a = to_mixed(new A(42)); + } + check($a); +} + +main(); diff --git a/tests/phpt/mixed/non_primitive/013_class_as_string.php b/tests/phpt/mixed/non_primitive/013_class_as_string.php new file mode 100644 index 0000000000..bad83f59fd --- /dev/null +++ b/tests/phpt/mixed/non_primitive/013_class_as_string.php @@ -0,0 +1,39 @@ +@ok + "zero", "" => "empty_key", 42 => "answer"]; + ensure($arr[$a] === "empty_key"); + + // just check that doesn't crash + $a[1234] = 123; + } +} + +function main() { + /** @var mixed */ + $a = to_mixed(new MyLongClassName()); + check($a); +} + +main(); diff --git a/tests/phpt/mixed/non_primitive/101_without_to_mixed.php b/tests/phpt/mixed/non_primitive/101_without_to_mixed.php new file mode 100644 index 0000000000..c90c78ae6d --- /dev/null +++ b/tests/phpt/mixed/non_primitive/101_without_to_mixed.php @@ -0,0 +1,18 @@ +@kphp_should_fail +/pass A to argument \$x of check/ +/but it's declared as @param mixed/ + Date: Tue, 13 Aug 2024 16:09:06 +0300 Subject: [PATCH 19/45] Split TL and RPC stuff (#1060) Split TL and RPC introducing TLBuffer instead of RpcBuffer --- runtime-light/stdlib/rpc/rpc-api.cpp | 121 +++---------------------- runtime-light/stdlib/rpc/rpc-context.h | 4 +- runtime-light/tl/tl-core.cpp | 97 ++++++++++++++++++++ runtime-light/tl/tl-core.h | 101 +++++++++++++++++++++ runtime-light/tl/tl.cmake | 1 + 5 files changed, 213 insertions(+), 111 deletions(-) create mode 100644 runtime-light/tl/tl-core.cpp create mode 100644 runtime-light/tl/tl-core.h diff --git a/runtime-light/stdlib/rpc/rpc-api.cpp b/runtime-light/stdlib/rpc/rpc-api.cpp index b7ca4f4d0a..1a7462e82b 100644 --- a/runtime-light/stdlib/rpc/rpc-api.cpp +++ b/runtime-light/stdlib/rpc/rpc-api.cpp @@ -4,7 +4,6 @@ #include "runtime-light/stdlib/rpc/rpc-api.h" -#include #include #include #include @@ -15,26 +14,15 @@ #include "common/rpc-error-codes.h" #include "runtime-core/utils/kphp-assert-core.h" #include "runtime-light/allocator/allocator.h" -#include "runtime-light/stdlib/rpc/rpc-buffer.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/tl/tl-core.h" namespace rpc_impl_ { constexpr int32_t MAX_TIMEOUT_S = 86400; -constexpr auto SMALL_STRING_SIZE_LEN = 1; -constexpr auto MEIDUM_STRING_SIZE_LEN = 3; -constexpr auto LARGE_STRING_SIZE_LEN = 7; - -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 {}; @@ -244,7 +232,7 @@ task_t> rpc_tl_query_result_one_impl(int64_t query_id) noexcept { } rpc_ctx.rpc_buffer.clean(); - rpc_ctx.rpc_buffer.store(data.c_str(), data.size()); + rpc_ctx.rpc_buffer.store_bytes(data.c_str(), data.size()); co_return fetch_function_untyped(rpc_query); } @@ -295,7 +283,7 @@ task_t> typed_rpc_tl_query_result_one_impl(i } rpc_ctx.rpc_buffer.clean(); - rpc_ctx.rpc_buffer.store(data.c_str(), data.size()); + rpc_ctx.rpc_buffer.store_bytes(data.c_str(), data.size()); co_return fetch_function_typed(rpc_query, error_factory); } @@ -308,53 +296,27 @@ 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)); } - RpcComponentContext::get().rpc_buffer.store_trivial(static_cast(v)); + RpcComponentContext::get().rpc_buffer.store_trivial(v); return true; } bool f$store_long(int64_t v) noexcept { - RpcComponentContext::get().rpc_buffer.store_trivial(v); + RpcComponentContext::get().rpc_buffer.store_trivial(v); return true; } bool f$store_float(double v) noexcept { - RpcComponentContext::get().rpc_buffer.store_trivial(static_cast(v)); + RpcComponentContext::get().rpc_buffer.store_trivial(v); return true; } bool f$store_double(double v) noexcept { - RpcComponentContext::get().rpc_buffer.store_trivial(v); + RpcComponentContext::get().rpc_buffer.store_trivial(v); return true; } bool f$store_string(const string &v) noexcept { - auto &rpc_buf{RpcComponentContext::get().rpc_buffer}; - - uint8_t size_len{}; - uint64_t string_len{v.size()}; - if (string_len <= rpc_impl_::SMALL_STRING_MAX_LEN) { - size_len = rpc_impl_::SMALL_STRING_SIZE_LEN; - rpc_buf.store_trivial(static_cast(string_len)); - } else if (string_len <= rpc_impl_::MEDIUM_STRING_MAX_LEN) { - size_len = rpc_impl_::MEIDUM_STRING_SIZE_LEN + 1; - rpc_buf.store_trivial(static_cast(rpc_impl_::MEDIUM_STRING_MAGIC)); - rpc_buf.store_trivial(static_cast(string_len & 0xff)); - rpc_buf.store_trivial(static_cast((string_len >> 8) & 0xff)); - rpc_buf.store_trivial(static_cast((string_len >> 16) & 0xff)); - } else { - php_warning("large strings aren't supported"); - size_len = rpc_impl_::SMALL_STRING_SIZE_LEN; - string_len = 0; - rpc_buf.store_trivial(static_cast(string_len)); - } - rpc_buf.store(v.c_str(), 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'}; - rpc_buf.store(padding_array.data(), padding); + RpcComponentContext::get().rpc_buffer.store_string(std::string_view{v.c_str(), v.size()}); return true; } @@ -373,71 +335,12 @@ double f$fetch_double() noexcept { } double f$fetch_float() noexcept { - return static_cast(RpcComponentContext::get().rpc_buffer.fetch_trivial().value_or(0.0)); + return static_cast(RpcComponentContext::get().rpc_buffer.fetch_trivial().value_or(0)); } string f$fetch_string() noexcept { - auto &rpc_buf{RpcComponentContext::get().rpc_buffer}; - - uint8_t first_byte{}; - if (const auto opt_first_byte{rpc_buf.fetch_trivial()}; opt_first_byte) { - first_byte = opt_first_byte.value(); - } else { - return {}; // TODO: error handling - } - - uint8_t size_len{}; - uint64_t string_len{}; - switch (first_byte) { - case rpc_impl_::LARGE_STRING_MAGIC: { - if (rpc_buf.remaining() < rpc_impl_::LARGE_STRING_SIZE_LEN) { - return {}; // TODO: error handling - } - size_len = rpc_impl_::LARGE_STRING_SIZE_LEN + 1; - const auto first{static_cast(rpc_buf.fetch_trivial().value())}; - const auto second{static_cast(rpc_buf.fetch_trivial().value()) << 8}; - const auto third{static_cast(rpc_buf.fetch_trivial().value()) << 16}; - const auto fourth{static_cast(rpc_buf.fetch_trivial().value()) << 24}; - const auto fifth{static_cast(rpc_buf.fetch_trivial().value()) << 32}; - const auto sixth{static_cast(rpc_buf.fetch_trivial().value()) << 40}; - const auto seventh{static_cast(rpc_buf.fetch_trivial().value()) << 48}; - string_len = first | second | third | fourth | fifth | sixth | seventh; - - const auto total_len_with_padding{(size_len + string_len + 3) & ~static_cast(3)}; - rpc_buf.adjust(total_len_with_padding - size_len); - php_warning("large strings aren't supported"); - return {}; - } - case rpc_impl_::MEDIUM_STRING_MAGIC: { - if (rpc_buf.remaining() < rpc_impl_::MEIDUM_STRING_SIZE_LEN) { - return {}; // TODO: error handling - } - size_len = rpc_impl_::MEIDUM_STRING_SIZE_LEN + 1; - const auto first{static_cast(rpc_buf.fetch_trivial().value())}; - const auto second{static_cast(rpc_buf.fetch_trivial().value()) << 8}; - const auto third{static_cast(rpc_buf.fetch_trivial().value()) << 16}; - string_len = first | second | third; - - if (string_len <= rpc_impl_::SMALL_STRING_MAX_LEN) { - php_warning("long string's length is less than 254"); - } - break; - } - default: { - size_len = rpc_impl_::SMALL_STRING_SIZE_LEN; - string_len = static_cast(first_byte); - break; - } - } - - const auto total_len_with_padding{(size_len + string_len + 3) & ~static_cast(3)}; - if (rpc_buf.remaining() < total_len_with_padding - size_len) { - return {}; // TODO: error handling - } - - string res{rpc_buf.data() + rpc_buf.pos(), static_cast(string_len)}; - rpc_buf.adjust(total_len_with_padding - size_len); - return res; + const std::string_view str{RpcComponentContext::get().rpc_buffer.fetch_string()}; + return {str.data(), static_cast(str.length())}; } // === Rpc Query ================================================================================== @@ -489,7 +392,7 @@ bool is_int32_overflow(int64_t v) noexcept { } void store_raw_vector_double(const array &vector) noexcept { // TODO: didn't we forget vector's length? - RpcComponentContext::get().rpc_buffer.store(reinterpret_cast(vector.get_const_vector_pointer()), sizeof(double) * vector.count()); + RpcComponentContext::get().rpc_buffer.store_bytes(reinterpret_cast(vector.get_const_vector_pointer()), sizeof(double) * vector.count()); } void fetch_raw_vector_double(array &vector, int64_t num_elems) noexcept { diff --git a/runtime-light/stdlib/rpc/rpc-context.h b/runtime-light/stdlib/rpc/rpc-context.h index 8a4a0ca203..b2e9d7fcf0 100644 --- a/runtime-light/stdlib/rpc/rpc-context.h +++ b/runtime-light/stdlib/rpc/rpc-context.h @@ -10,17 +10,17 @@ #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-buffer.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" +#include "runtime-light/tl/tl-core.h" struct RpcComponentContext final : private vk::not_copyable { template using unordered_map = memory_resource::stl::unordered_map; - RpcBuffer rpc_buffer; + tl::TLBuffer rpc_buffer; int64_t current_query_id{0}; CurrentTlQuery current_query; unordered_map> pending_component_queries; diff --git a/runtime-light/tl/tl-core.cpp b/runtime-light/tl/tl-core.cpp new file mode 100644 index 0000000000..b9e4cfa07f --- /dev/null +++ b/runtime-light/tl/tl-core.cpp @@ -0,0 +1,97 @@ +// 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-core.h" + +namespace tl { +void TLBuffer::store_string(std::string_view str) noexcept { + const char *str_buf{str.data()}; + size_t str_len{str.size()}; + uint8_t size_len{}; + if (str_len <= SMALL_STRING_MAX_LEN) { + size_len = SMALL_STRING_SIZE_LEN; + store_trivial(str_len); + } else if (str_len <= MEDIUM_STRING_MAX_LEN) { + size_len = MEDIUM_STRING_SIZE_LEN + 1; + store_trivial(MEDIUM_STRING_MAGIC); + store_trivial(str_len & 0xff); + store_trivial((str_len >> 8) & 0xff); + store_trivial((str_len >> 16) & 0xff); + } else { + php_warning("large strings aren't supported"); + size_len = SMALL_STRING_SIZE_LEN; + str_len = 0; + store_trivial(str_len); + } + store_bytes(str_buf, str_len); + + const auto total_len{size_len + str_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'}; + store_bytes(padding_array.data(), padding); +} + +std::string_view TLBuffer::fetch_string() noexcept { + uint8_t first_byte{}; + if (const auto opt_first_byte{fetch_trivial()}; opt_first_byte) { + first_byte = opt_first_byte.value(); + } else { + return {}; // TODO: error handling + } + + uint8_t size_len{}; + uint64_t string_len{}; + switch (first_byte) { + case LARGE_STRING_MAGIC: { + if (remaining() < LARGE_STRING_SIZE_LEN) { + return {}; // TODO: error handling + } + size_len = LARGE_STRING_SIZE_LEN + 1; + const auto first{static_cast(fetch_trivial().value())}; + const auto second{static_cast(fetch_trivial().value()) << 8}; + const auto third{static_cast(fetch_trivial().value()) << 16}; + const auto fourth{static_cast(fetch_trivial().value()) << 24}; + const auto fifth{static_cast(fetch_trivial().value()) << 32}; + const auto sixth{static_cast(fetch_trivial().value()) << 40}; + const auto seventh{static_cast(fetch_trivial().value()) << 48}; + string_len = first | second | third | fourth | fifth | sixth | seventh; + + const auto total_len_with_padding{(size_len + string_len + 3) & ~static_cast(3)}; + adjust(total_len_with_padding - size_len); + php_warning("large strings aren't supported"); + return {}; + } + case MEDIUM_STRING_MAGIC: { + if (remaining() < MEDIUM_STRING_SIZE_LEN) { + return {}; // TODO: error handling + } + size_len = MEDIUM_STRING_SIZE_LEN + 1; + const auto first{static_cast(fetch_trivial().value())}; + const auto second{static_cast(fetch_trivial().value()) << 8}; + const auto third{static_cast(fetch_trivial().value()) << 16}; + string_len = first | second | third; + + if (string_len <= SMALL_STRING_MAX_LEN) { + php_warning("long string's length is less than 254"); + } + break; + } + default: { + size_len = SMALL_STRING_SIZE_LEN; + string_len = static_cast(first_byte); + break; + } + } + const auto total_len_with_padding{(size_len + string_len + 3) & ~static_cast(3)}; + if (m_remaining < total_len_with_padding - size_len) { + return {}; // TODO: error handling + } + + std::string_view response{data() + m_pos, static_cast(string_len)}; + adjust(total_len_with_padding - size_len); + return response; +} +} // namespace tl diff --git a/runtime-light/tl/tl-core.h b/runtime-light/tl/tl-core.h new file mode 100644 index 0000000000..ba179db59c --- /dev/null +++ b/runtime-light/tl/tl-core.h @@ -0,0 +1,101 @@ +// 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 + +#include "common/mixin/not_copyable.h" +#include "runtime-core/runtime-core.h" +#include "runtime-core/utils/kphp-assert-core.h" +#include "runtime-light/utils/concepts.h" + +namespace tl { +constexpr auto SMALL_STRING_SIZE_LEN = 1; +constexpr auto MEDIUM_STRING_SIZE_LEN = 3; +constexpr auto LARGE_STRING_SIZE_LEN = 7; + +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; + +class TLBuffer : private vk::not_copyable { + string_buffer m_buffer; + size_t m_pos{0}; + size_t m_remaining{0}; + +public: + TLBuffer() = default; + + const char *data() const noexcept { + return m_buffer.buffer(); + } + + size_t size() const noexcept { + return static_cast(m_buffer.size()); + } + + size_t pos() const noexcept { + return m_pos; + } + + size_t remaining() const noexcept { + return m_remaining; + } + + void clean() noexcept { + m_buffer.clean(); + m_pos = 0; + m_remaining = 0; + } + + void reset(size_t pos) noexcept { + php_assert(pos >= 0 && pos <= size()); + m_pos = pos; + m_remaining = size() - m_pos; + } + + void adjust(size_t len) noexcept { + php_assert(m_pos + len <= size()); + m_pos += len; + m_remaining -= len; + } + + void store_bytes(const char *src, size_t len) noexcept { + m_buffer.append(src, len); + m_remaining += len; + } + + template + requires std::convertible_to + void store_trivial(const U &t) noexcept { + // Here we rely on that endianness of architecture is Little Endian + store_bytes(reinterpret_cast(std::addressof(t)), sizeof(T)); + } + + void store_string(std::string_view s) noexcept; + + template + std::optional fetch_trivial() noexcept { + if (m_remaining < sizeof(T)) { + return std::nullopt; + } + + // Here we rely on that endianness of architecture is Little Endian + const auto t{*reinterpret_cast(m_buffer.c_str() + m_pos)}; + m_pos += sizeof(T); + m_remaining -= sizeof(T); + return t; + } + + std::string_view fetch_string() noexcept; +}; + +} // namespace tl diff --git a/runtime-light/tl/tl.cmake b/runtime-light/tl/tl.cmake index a9c6b8bf98..59a7ae7772 100644 --- a/runtime-light/tl/tl.cmake +++ b/runtime-light/tl/tl.cmake @@ -1,3 +1,4 @@ prepend(RUNTIME_TL_SRC tl/ tl-builtins.cpp + tl-core.cpp ) From f8139d05e67f55fa0533b5eab09c8356f1ce2db4 Mon Sep 17 00:00:00 2001 From: Alexander Polyakov Date: Tue, 13 Aug 2024 17:06:20 +0300 Subject: [PATCH 20/45] Add cancellations (#1055) * add cancellable awaitables * add start fork policy * make RPC use forks --- compiler/code-gen/vertex-compiler.cpp | 4 +- runtime-core/utils/hash.h | 13 + runtime-light/coroutine/awaitable.h | 369 +++++++++++++++++++---- runtime-light/coroutine/task.h | 9 + runtime-light/scheduler/scheduler.cpp | 64 ++-- runtime-light/scheduler/scheduler.h | 99 +++++- runtime-light/stdlib/fork/fork-api.h | 22 +- runtime-light/stdlib/fork/fork-context.h | 47 +-- runtime-light/stdlib/rpc/rpc-api.cpp | 148 ++++----- runtime-light/stdlib/rpc/rpc-context.cpp | 6 - runtime-light/stdlib/rpc/rpc-context.h | 12 +- runtime-light/stdlib/timer/timer.h | 3 +- runtime-light/streams/interface.cpp | 2 +- runtime-light/streams/interface.h | 1 + 14 files changed, 581 insertions(+), 218 deletions(-) create mode 100644 runtime-core/utils/hash.h diff --git a/compiler/code-gen/vertex-compiler.cpp b/compiler/code-gen/vertex-compiler.cpp index 0c8b517ac6..b13f5b062f 100644 --- a/compiler/code-gen/vertex-compiler.cpp +++ b/compiler/code-gen/vertex-compiler.cpp @@ -849,7 +849,7 @@ void compile_func_call(VertexAdaptor root, CodeGenerator &W, func_ if (mode == func_call_mode::fork_call) { if (func->is_interruptible) { - W << "(co_await start_fork_and_reschedule_t{" << FunctionName(func); + W << "(co_await start_fork_t{" << FunctionName(func); } else { W << FunctionForkName(func); } @@ -883,7 +883,7 @@ void compile_func_call(VertexAdaptor root, CodeGenerator &W, func_ W << ")"; if (func->is_interruptible) { if (mode == func_call_mode::fork_call) { - W << "})"; + W << ", start_fork_t::execution::fork})"; } else if (func->is_k2_fork) { // k2 fork's return type is 'task_t' so we need to unpack actual result from fork_result W << ").get_result<" << TypeName(tinf::get_type(root)) << ">()"; } else { diff --git a/runtime-core/utils/hash.h b/runtime-core/utils/hash.h new file mode 100644 index 0000000000..8df06cee7d --- /dev/null +++ b/runtime-core/utils/hash.h @@ -0,0 +1,13 @@ +// Compiler for PHP (aka KPHP) +// Copyright (c) 2024 LLC «V Kontakte» +// Distributed under the GPL v3 License, see LICENSE.notice.txt + +#include +#include + +// from boost +// see https://www.boost.org/doc/libs/1_55_0/doc/html/hash/reference.html#boost.hash_combine +template +void hash_combine(size_t &seed, const T &v) noexcept { + seed ^= std::hash{}(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); +} diff --git a/runtime-light/coroutine/awaitable.h b/runtime-light/coroutine/awaitable.h index f468796117..c27ab80eb3 100644 --- a/runtime-light/coroutine/awaitable.h +++ b/runtime-light/coroutine/awaitable.h @@ -9,159 +9,316 @@ #include #include #include +#include +#include #include -#include "runtime-core/core-types/decl/optional.h" #include "runtime-core/utils/kphp-assert-core.h" #include "runtime-light/component/component.h" #include "runtime-light/coroutine/task.h" -#include "runtime-light/stdlib/fork/fork-context.h" -#include "runtime-light/stdlib/fork/fork.h" #include "runtime-light/header.h" #include "runtime-light/scheduler/scheduler.h" +#include "runtime-light/stdlib/fork/fork-context.h" +#include "runtime-light/stdlib/fork/fork.h" #include "runtime-light/utils/context.h" template -concept Awaitable = requires(T && awaitable, std::coroutine_handle<> coro) { +concept Awaitable = requires(T awaitable, std::coroutine_handle<> coro) { { awaitable.await_ready() } noexcept -> std::convertible_to; { awaitable.await_suspend(coro) } noexcept; { awaitable.await_resume() } noexcept; }; template -concept CancellableAwaitable = Awaitable && requires(T && awaitable) { +concept CancellableAwaitable = Awaitable && requires(T awaitable) { + // semantics: returns 'true' if it's safe to call 'await_resume', 'false' otherwise. + { awaitable.resumable() } noexcept -> std::convertible_to; + // semantics: following resume of the awaitable should not have any effect on the awaiter. + // semantics (optional): stop useless calculations. { awaitable.cancel() } noexcept -> std::same_as; }; // === Awaitables ================================================================================= +// +// ***Important*** +// Below awaitables are not supposed to be co_awaited on more than once. + +namespace awaitable_impl_ { + +enum class State : uint8_t { Init, Suspend, Ready, End }; + +} // namespace awaitable_impl_ class wait_for_update_t { uint64_t stream_d; - SuspendToken suspend_token_; + SuspendToken suspend_token; + awaitable_impl_::State state{awaitable_impl_::State::Init}; public: explicit wait_for_update_t(uint64_t stream_d_) noexcept : stream_d(stream_d_) - , suspend_token_(std::noop_coroutine(), WaitEvent::UpdateOnStream{.stream_d = stream_d}) {} + , suspend_token(std::noop_coroutine(), WaitEvent::UpdateOnStream{.stream_d = stream_d}) {} - bool await_ready() const noexcept { - return get_component_context()->stream_updated(stream_d); + wait_for_update_t(wait_for_update_t &&other) noexcept + : stream_d(std::exchange(other.stream_d, INVALID_PLATFORM_DESCRIPTOR)) + , suspend_token(std::exchange(other.suspend_token, std::make_pair(std::noop_coroutine(), WaitEvent::Rechedule{}))) + , state(std::exchange(other.state, awaitable_impl_::State::End)) {} + + wait_for_update_t(const wait_for_update_t &) = delete; + wait_for_update_t &operator=(const wait_for_update_t &) = delete; + wait_for_update_t &operator=(wait_for_update_t &&) = delete; + + ~wait_for_update_t() { + if (state == awaitable_impl_::State::Suspend) { + cancel(); + } + } + + bool await_ready() noexcept { + php_assert(state == awaitable_impl_::State::Init); + state = get_component_context()->stream_updated(stream_d) ? awaitable_impl_::State::Ready : awaitable_impl_::State::Init; + return state == awaitable_impl_::State::Ready; } void await_suspend(std::coroutine_handle<> coro) noexcept { - suspend_token_.first = coro; - CoroutineScheduler::get().suspend(suspend_token_); + state = awaitable_impl_::State::Suspend; + suspend_token.first = coro; + CoroutineScheduler::get().suspend(suspend_token); } - constexpr void await_resume() const noexcept {} + constexpr void await_resume() noexcept { + state = awaitable_impl_::State::End; + } - void cancel() const noexcept { - CoroutineScheduler::get().cancel(suspend_token_); + bool resumable() const noexcept { + return state == awaitable_impl_::State::Ready || (state == awaitable_impl_::State::Suspend && !CoroutineScheduler::get().contains(suspend_token)); + } + + void cancel() noexcept { + state = awaitable_impl_::State::End; + CoroutineScheduler::get().cancel(suspend_token); } }; // ================================================================================================ class wait_for_incoming_stream_t { - SuspendToken suspend_token_{std::noop_coroutine(), WaitEvent::IncomingStream{}}; + SuspendToken suspend_token{std::noop_coroutine(), WaitEvent::IncomingStream{}}; + awaitable_impl_::State state{awaitable_impl_::State::Init}; public: - bool await_ready() const noexcept { - return !get_component_context()->incoming_streams().empty(); + wait_for_incoming_stream_t() noexcept = default; + + wait_for_incoming_stream_t(wait_for_incoming_stream_t &&other) noexcept + : suspend_token(std::exchange(other.suspend_token, std::make_pair(std::noop_coroutine(), WaitEvent::Rechedule{}))) + , state(std::exchange(other.state, awaitable_impl_::State::End)) {} + + wait_for_incoming_stream_t(const wait_for_incoming_stream_t &) = delete; + wait_for_incoming_stream_t &operator=(const wait_for_incoming_stream_t &) = delete; + wait_for_incoming_stream_t &operator=(wait_for_incoming_stream_t &&) = delete; + + ~wait_for_incoming_stream_t() { + if (state == awaitable_impl_::State::Suspend) { + cancel(); + } + } + + bool await_ready() noexcept { + php_assert(state == awaitable_impl_::State::Init); + state = !get_component_context()->incoming_streams().empty() ? awaitable_impl_::State::Ready : awaitable_impl_::State::Init; + return state == awaitable_impl_::State::Ready; } void await_suspend(std::coroutine_handle<> coro) noexcept { - suspend_token_.first = coro; - CoroutineScheduler::get().suspend(suspend_token_); + state = awaitable_impl_::State::Suspend; + suspend_token.first = coro; + CoroutineScheduler::get().suspend(suspend_token); } - uint64_t await_resume() const noexcept { + uint64_t await_resume() noexcept { + state = awaitable_impl_::State::End; const auto incoming_stream_d{get_component_context()->take_incoming_stream()}; php_assert(incoming_stream_d != INVALID_PLATFORM_DESCRIPTOR); return incoming_stream_d; } - void cancel() const noexcept { - CoroutineScheduler::get().cancel(suspend_token_); + bool resumable() const noexcept { + return state == awaitable_impl_::State::Ready || (state == awaitable_impl_::State::Suspend && !CoroutineScheduler::get().contains(suspend_token)); + } + + void cancel() noexcept { + state = awaitable_impl_::State::End; + CoroutineScheduler::get().cancel(suspend_token); } }; // ================================================================================================ class wait_for_reschedule_t { - SuspendToken suspend_token_{std::noop_coroutine(), WaitEvent::Rechedule{}}; + SuspendToken suspend_token{std::noop_coroutine(), WaitEvent::Rechedule{}}; + awaitable_impl_::State state{awaitable_impl_::State::Init}; public: + wait_for_reschedule_t() noexcept = default; + + wait_for_reschedule_t(wait_for_reschedule_t &&other) noexcept + : suspend_token(std::exchange(other.suspend_token, std::make_pair(std::noop_coroutine(), WaitEvent::Rechedule{}))) + , state(std::exchange(other.state, awaitable_impl_::State::End)) {} + + wait_for_reschedule_t(const wait_for_reschedule_t &) = delete; + wait_for_reschedule_t &operator=(const wait_for_reschedule_t &) = delete; + wait_for_reschedule_t &operator=(wait_for_reschedule_t &&) = delete; + + ~wait_for_reschedule_t() { + if (state == awaitable_impl_::State::Suspend) { + cancel(); + } + } + constexpr bool await_ready() const noexcept { + php_assert(state == awaitable_impl_::State::Init); return false; } void await_suspend(std::coroutine_handle<> coro) noexcept { - suspend_token_.first = coro; - CoroutineScheduler::get().suspend(suspend_token_); + state = awaitable_impl_::State::Suspend; + suspend_token.first = coro; + CoroutineScheduler::get().suspend(suspend_token); } - constexpr void await_resume() const noexcept {} + constexpr void await_resume() noexcept { + state = awaitable_impl_::State::End; + } - void cancel() const noexcept { - CoroutineScheduler::get().cancel(suspend_token_); + bool resumable() const noexcept { + return state == awaitable_impl_::State::Suspend && !CoroutineScheduler::get().contains(suspend_token); + } + + void cancel() noexcept { + state = awaitable_impl_::State::End; + CoroutineScheduler::get().cancel(suspend_token); } }; // ================================================================================================ class wait_for_timer_t { - uint64_t timer_d{}; - SuspendToken suspend_token_; + std::chrono::nanoseconds duration; + uint64_t timer_d{INVALID_PLATFORM_DESCRIPTOR}; + SuspendToken suspend_token{std::noop_coroutine(), WaitEvent::Rechedule{}}; + awaitable_impl_::State state{awaitable_impl_::State::Init}; public: - explicit wait_for_timer_t(std::chrono::nanoseconds duration) noexcept - : timer_d(get_component_context()->set_timer(duration)) - , suspend_token_(std::noop_coroutine(), WaitEvent::UpdateOnTimer{.timer_d = timer_d}) {} + explicit wait_for_timer_t(std::chrono::nanoseconds duration_) noexcept + : duration(duration_) {} + + wait_for_timer_t(wait_for_timer_t &&other) noexcept + : duration(std::exchange(other.duration, std::chrono::nanoseconds{0})) + , timer_d(std::exchange(other.timer_d, INVALID_PLATFORM_DESCRIPTOR)) + , suspend_token(std::exchange(other.suspend_token, std::make_pair(std::noop_coroutine(), WaitEvent::Rechedule{}))) + , state(std::exchange(other.state, awaitable_impl_::State::End)) {} + + wait_for_timer_t(const wait_for_timer_t &) = delete; + wait_for_timer_t &operator=(const wait_for_timer_t &) = delete; + wait_for_timer_t &operator=(wait_for_timer_t &&) = delete; + + ~wait_for_timer_t() { + if (state == awaitable_impl_::State::Suspend) { + cancel(); + } + if (timer_d != INVALID_PLATFORM_DESCRIPTOR) { + get_component_context()->release_stream(timer_d); + } + } - bool await_ready() const noexcept { - TimePoint tp{}; - return timer_d == INVALID_PLATFORM_DESCRIPTOR || get_platform_context()->get_timer_status(timer_d, std::addressof(tp)) == TimerStatus::TimerStatusElapsed; + constexpr bool await_ready() const noexcept { + php_assert(state == awaitable_impl_::State::Init); + return false; } void await_suspend(std::coroutine_handle<> coro) noexcept { - suspend_token_.first = coro; - CoroutineScheduler::get().suspend(suspend_token_); + state = awaitable_impl_::State::Suspend; + timer_d = get_component_context()->set_timer(duration); + if (timer_d != INVALID_PLATFORM_DESCRIPTOR) { + suspend_token = std::make_pair(coro, WaitEvent::UpdateOnTimer{.timer_d = timer_d}); + } + CoroutineScheduler::get().suspend(suspend_token); + } + + constexpr void await_resume() noexcept { + state = awaitable_impl_::State::End; } - void await_resume() const noexcept { - get_component_context()->release_stream(timer_d); + bool resumable() const noexcept { + return state == awaitable_impl_::State::Suspend && !CoroutineScheduler::get().contains(suspend_token); } - void cancel() const noexcept { - get_component_context()->release_stream(timer_d); - CoroutineScheduler::get().cancel(suspend_token_); + void cancel() noexcept { + state = awaitable_impl_::State::End; + CoroutineScheduler::get().cancel(suspend_token); } }; // ================================================================================================ -class start_fork_and_reschedule_t { +class start_fork_t { +public: + /** + * Fork start policy: + * 1. self: create fork, suspend fork coroutine, continue current coroutine; + * 2. fork: create fork, suspend current coroutine, continue fork coroutine. + */ + enum class execution : uint8_t { self, fork }; + +private: + execution exec_policy; std::coroutine_handle<> fork_coro; int64_t fork_id{}; - SuspendToken suspend_token_{std::noop_coroutine(), WaitEvent::Rechedule{}}; + SuspendToken suspend_token{std::noop_coroutine(), WaitEvent::Rechedule{}}; public: - explicit start_fork_and_reschedule_t(task_t &&task_) noexcept - : fork_coro(task_.get_handle()) + explicit start_fork_t(task_t &&task_, execution exec_policy_) noexcept + : exec_policy(exec_policy_) + , fork_coro(task_.get_handle()) , fork_id(ForkComponentContext::get().push_fork(std::move(task_))) {} + start_fork_t(start_fork_t &&other) noexcept + : exec_policy(other.exec_policy) + , fork_coro(std::exchange(other.fork_coro, std::noop_coroutine())) + , fork_id(std::exchange(other.fork_id, INVALID_FORK_ID)) + , suspend_token(std::exchange(other.suspend_token, std::make_pair(std::noop_coroutine(), WaitEvent::Rechedule{}))) {} + + start_fork_t(const start_fork_t &) = delete; + start_fork_t &operator=(const start_fork_t &) = delete; + start_fork_t &operator=(start_fork_t &&) = delete; + ~start_fork_t() = default; + constexpr bool await_ready() const noexcept { return false; } std::coroutine_handle<> await_suspend(std::coroutine_handle<> current_coro) noexcept { - suspend_token_.first = current_coro; - CoroutineScheduler::get().suspend(suspend_token_); - return fork_coro; + std::coroutine_handle<> continuation{}; + switch (exec_policy) { + case execution::fork: { + suspend_token.first = current_coro; + continuation = fork_coro; + break; + } + case execution::self: { + suspend_token.first = fork_coro; + continuation = current_coro; + break; + } + } + CoroutineScheduler::get().suspend(suspend_token); + // reset fork_coro and suspend_token to guarantee that the same fork will be started only once + fork_coro = std::noop_coroutine(); + suspend_token = std::make_pair(std::noop_coroutine(), WaitEvent::Rechedule{}); + return continuation; } - int64_t await_resume() const noexcept { + constexpr int64_t await_resume() const noexcept { return fork_id; } }; @@ -170,32 +327,112 @@ class start_fork_and_reschedule_t { template class wait_fork_t { - task_t task; - wait_for_timer_t timer_awaiter; + int64_t fork_id; + task_t fork_task; task_t::awaiter_t fork_awaiter; public: - wait_fork_t(task_t &&task_, std::chrono::nanoseconds timeout_) noexcept - : task(std::move(task_)) - , timer_awaiter(timeout_) - , fork_awaiter(std::addressof(task)) {} + explicit wait_fork_t(int64_t fork_id_) noexcept + : fork_id(fork_id_) + , fork_task(ForkComponentContext::get().pop_fork(fork_id)) + , fork_awaiter(std::addressof(fork_task)) {} + + wait_fork_t(wait_fork_t &&other) noexcept + : fork_id(std::exchange(other.fork_id, INVALID_FORK_ID)) + , fork_task(std::move(other.fork_task)) + , fork_awaiter(std::addressof(fork_task)) {} + + wait_fork_t(const wait_fork_t &) = delete; + wait_fork_t &operator=(const wait_fork_t &) = delete; + wait_fork_t &operator=(wait_fork_t &&) = delete; + ~wait_fork_t() = default; bool await_ready() const noexcept { - return task.done(); + return fork_awaiter.resumable(); } - void await_suspend(std::coroutine_handle<> coro) noexcept { + constexpr void await_suspend(std::coroutine_handle<> coro) noexcept { fork_awaiter.await_suspend(coro); - timer_awaiter.await_suspend(coro); } - Optional await_resume() noexcept { - if (task.done()) { - timer_awaiter.cancel(); - return {fork_awaiter.await_resume().get_result()}; + T await_resume() noexcept { + return fork_awaiter.await_resume().get_result(); + } + + constexpr bool resumable() const noexcept { + return fork_awaiter.resumable(); + } + + constexpr void cancel() const noexcept { + fork_awaiter.cancel(); + } +}; + +// ================================================================================================ + +template +class wait_with_timeout_t { + T awaitable; + wait_for_timer_t timer_awaitable; + + static_assert(CancellableAwaitable); + static_assert(std::is_void_v{}))>); + using awaitable_suspend_t = decltype(awaitable.await_suspend(std::coroutine_handle<>{})); + using await_suspend_return_t = awaitable_suspend_t; + using awaitable_resume_t = decltype(awaitable.await_resume()); + using await_resume_return_t = std::conditional_t, void, std::optional>; + +public: + wait_with_timeout_t(T &&awaitable_, std::chrono::nanoseconds timeout) noexcept + : awaitable(std::move(awaitable_)) + , timer_awaitable(timeout) {} + + wait_with_timeout_t(wait_with_timeout_t &&other) noexcept + : awaitable(std::move(other.awaitable)) + , timer_awaitable(std::move(other.timer_awaitable)) {} + + wait_with_timeout_t(const wait_with_timeout_t &) = delete; + wait_with_timeout_t &operator=(const wait_with_timeout_t &) = delete; + wait_with_timeout_t &operator=(wait_with_timeout_t &&) = delete; + ~wait_with_timeout_t() = default; + + constexpr bool await_ready() const noexcept { + return awaitable.await_ready() || timer_awaitable.await_ready(); + } + + // according to C++ standard, there can be 3 possible cases: + // 1. await_suspend returns void; + // 2. await_suspend returns bool; + // 3. await_suspend returns std::coroutine_handle<>. + // we must guarantee that 'co_await wait_with_timeout_t{awaitable, timeout}' behaves like 'co_await awaitable' except + // it may cancel 'co_await awaitable' if the timeout has elapsed. + await_suspend_return_t await_suspend(std::coroutine_handle<> coro) noexcept { + // as we don't rely on coroutine scheduler implementation, let's always suspend awaitable first. in case of some smart scheduler + // it won't have any effect, but it will have an effect if our scheduler is quite simple. + if constexpr (std::is_void_v) { + awaitable.await_suspend(coro); + timer_awaitable.await_suspend(coro); } else { - fork_awaiter.cancel(); - return {}; + const auto awaitable_suspend_res{awaitable.await_suspend(coro)}; + timer_awaitable.await_suspend(coro); + return awaitable_suspend_res; + } + } + + await_resume_return_t await_resume() noexcept { + if (awaitable.resumable()) { + timer_awaitable.cancel(); + if constexpr (!std::is_void_v) { + return awaitable.await_resume(); + } + } else { + awaitable.cancel(); + if constexpr (!std::is_void_v) { + return std::nullopt; + } } } }; + +template +wait_with_timeout_t(T &&, std::chrono::nanoseconds) -> wait_with_timeout_t; diff --git a/runtime-light/coroutine/task.h b/runtime-light/coroutine/task.h index b9c13b7cf6..ae368e9b63 100644 --- a/runtime-light/coroutine/task.h +++ b/runtime-light/coroutine/task.h @@ -50,6 +50,11 @@ struct task_base_t { void *handle_address{nullptr}; }; +/** + * Please, read following documents before trying to understand what's going on here: + * 1. C++20 coroutines — https://en.cppreference.com/w/cpp/language/coroutines + * 2. C++ coroutines: understanding symmetric stransfer — https://lewissbaker.github.io/2020/05/11/understanding_symmetric_transfer + */ template struct task_t : public task_base_t { template F> @@ -202,6 +207,10 @@ struct task_t : public task_base_t { return task->get_result(); } + bool resumable() const noexcept { + return task->done(); + } + void cancel() const noexcept { task->get_handle().promise().next = nullptr; } diff --git a/runtime-light/scheduler/scheduler.cpp b/runtime-light/scheduler/scheduler.cpp index c0bbe95a20..491d1d3d1b 100644 --- a/runtime-light/scheduler/scheduler.cpp +++ b/runtime-light/scheduler/scheduler.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include "runtime-light/component/component.h" @@ -20,32 +21,35 @@ SimpleCoroutineScheduler &SimpleCoroutineScheduler::get() noexcept { } ScheduleStatus SimpleCoroutineScheduler::scheduleOnNoEvent() noexcept { - if (yield_coros.empty()) { + if (yield_tokens.empty()) { return ScheduleStatus::Skipped; } - const auto coro{yield_coros.front()}; - yield_coros.pop_front(); - coro.resume(); + const auto token{yield_tokens.front()}; + yield_tokens.pop_front(); + suspend_tokens.erase(token); + token.first.resume(); return ScheduleStatus::Resumed; } ScheduleStatus SimpleCoroutineScheduler::scheduleOnIncomingStream() noexcept { - if (awaiting_for_stream_coros.empty()) { + if (awaiting_for_stream_tokens.empty()) { return ScheduleStatus::Skipped; } - const auto coro{awaiting_for_stream_coros.front()}; - awaiting_for_stream_coros.pop_front(); - coro.resume(); + const auto token{awaiting_for_stream_tokens.front()}; + awaiting_for_stream_tokens.pop_front(); + suspend_tokens.erase(token); + token.first.resume(); return ScheduleStatus::Resumed; } ScheduleStatus SimpleCoroutineScheduler::scheduleOnStreamUpdate(uint64_t stream_d) noexcept { if (stream_d == INVALID_PLATFORM_DESCRIPTOR) { return ScheduleStatus::Error; - } else if (const auto it_coro{awaiting_for_update_coros.find(stream_d)}; it_coro != awaiting_for_update_coros.cend()) { - const auto coro{it_coro->second}; - awaiting_for_update_coros.erase(it_coro); - coro.resume(); + } else if (const auto it_token{awaiting_for_update_tokens.find(stream_d)}; it_token != awaiting_for_update_tokens.cend()) { + const auto token{it_token->second}; + awaiting_for_update_tokens.erase(it_token); + suspend_tokens.erase(token); + token.first.resume(); return ScheduleStatus::Resumed; } else { return ScheduleStatus::Skipped; @@ -58,7 +62,7 @@ ScheduleStatus SimpleCoroutineScheduler::scheduleOnYield() noexcept { ScheduleStatus SimpleCoroutineScheduler::schedule(ScheduleEvent::EventT event) noexcept { return std::visit( - [this](auto &&event) { + [this](auto &&event) noexcept { using event_t = std::remove_cvref_t; if constexpr (std::is_same_v) { return scheduleOnNoEvent(); @@ -78,47 +82,53 @@ ScheduleStatus SimpleCoroutineScheduler::schedule(ScheduleEvent::EventT event) n } void SimpleCoroutineScheduler::suspend(SuspendToken token) noexcept { - const auto [coro, event]{token}; + suspend_tokens.emplace(token); std::visit( - [this, coro](auto &&event) { + [this, token](auto &&event) noexcept { using event_t = std::remove_cvref_t; if constexpr (std::is_same_v) { - yield_coros.push_back(coro); + yield_tokens.push_back(token); } else if constexpr (std::is_same_v) { - awaiting_for_stream_coros.push_back(coro); + awaiting_for_stream_tokens.push_back(token); } else if constexpr (std::is_same_v) { if (event.stream_d == INVALID_PLATFORM_DESCRIPTOR) { return; } - awaiting_for_update_coros.emplace(event.stream_d, coro); + awaiting_for_update_tokens.emplace(event.stream_d, token); } else if constexpr (std::is_same_v) { if (event.timer_d == INVALID_PLATFORM_DESCRIPTOR) { return; } - awaiting_for_update_coros.emplace(event.timer_d, coro); + awaiting_for_update_tokens.emplace(event.timer_d, token); } else { static_assert(false, "non-exhaustive visitor"); } }, - event); + token.second); } void SimpleCoroutineScheduler::cancel(SuspendToken token) noexcept { - const auto [coro, event]{token}; + suspend_tokens.erase(token); std::visit( - [this, coro](auto &&event) { + [this, token](auto &&event) noexcept { using event_t = std::remove_cvref_t; if constexpr (std::is_same_v) { - yield_coros.erase(std::find(yield_coros.cbegin(), yield_coros.cend(), coro)); + const auto it_token{std::find(yield_tokens.cbegin(), yield_tokens.cend(), token)}; + if (it_token != yield_tokens.cend()) { + yield_tokens.erase(it_token); + } } else if constexpr (std::is_same_v) { - awaiting_for_stream_coros.erase(std::find(awaiting_for_stream_coros.cbegin(), awaiting_for_stream_coros.cend(), coro)); + const auto it_token{std::find(awaiting_for_stream_tokens.cbegin(), awaiting_for_stream_tokens.cend(), token)}; + if (it_token != awaiting_for_stream_tokens.cend()) { + awaiting_for_stream_tokens.erase(it_token); + } } else if constexpr (std::is_same_v) { - awaiting_for_update_coros.erase(event.stream_d); + awaiting_for_update_tokens.erase(event.stream_d); } else if constexpr (std::is_same_v) { - awaiting_for_update_coros.erase(event.timer_d); + awaiting_for_update_tokens.erase(event.timer_d); } else { static_assert(false, "non-exhaustive visitor"); } }, - event); + token.second); } diff --git a/runtime-light/scheduler/scheduler.h b/runtime-light/scheduler/scheduler.h index c03723bf8f..89b96cba16 100644 --- a/runtime-light/scheduler/scheduler.h +++ b/runtime-light/scheduler/scheduler.h @@ -6,12 +6,15 @@ #include #include +#include #include +#include #include #include #include "runtime-core/memory-resource/resource_allocator.h" #include "runtime-core/memory-resource/unsynchronized_pool_resource.h" +#include "runtime-core/utils/hash.h" #include "runtime-light/utils/concepts.h" /** @@ -55,27 +58,84 @@ enum class ScheduleStatus : uint8_t { Resumed, Skipped, Error }; */ namespace WaitEvent { -struct Rechedule {}; +struct Rechedule { + static constexpr size_t HASH_VALUE = 4001; + bool operator==([[maybe_unused]] const Rechedule &other) const noexcept { + return true; + } +}; -struct IncomingStream {}; +struct IncomingStream { + static constexpr size_t HASH_VALUE = 4003; + bool operator==([[maybe_unused]] const IncomingStream &other) const noexcept { + return true; + } +}; struct UpdateOnStream { uint64_t stream_d{}; + + bool operator==(const UpdateOnStream &other) const noexcept { + return stream_d == other.stream_d; + } }; struct UpdateOnTimer { uint64_t timer_d{}; + + bool operator==(const UpdateOnTimer &other) const noexcept { + return timer_d == other.timer_d; + } }; using EventT = std::variant; }; // namespace WaitEvent +// std::hash specializations for WaitEvent types + +template<> +struct std::hash { + size_t operator()([[maybe_unused]] const WaitEvent::Rechedule &v) const noexcept { + return WaitEvent::Rechedule::HASH_VALUE; + } +}; + +template<> +struct std::hash { + size_t operator()([[maybe_unused]] const WaitEvent::IncomingStream &v) const noexcept { + return WaitEvent::IncomingStream::HASH_VALUE; + } +}; + +template<> +struct std::hash { + size_t operator()(const WaitEvent::UpdateOnStream &v) const noexcept { + return v.stream_d; + } +}; + +template<> +struct std::hash { + size_t operator()(const WaitEvent::UpdateOnTimer &v) const noexcept { + return v.timer_d; + } +}; + /** * SuspendToken type that binds an event and a coroutine waiting for that event. */ using SuspendToken = std::pair, WaitEvent::EventT>; +template<> +struct std::hash { + size_t operator()(const SuspendToken &token) const noexcept { + size_t suspend_token_hash{std::hash>{}(token.first)}; + hash_combine(suspend_token_hash, token.second); + return suspend_token_hash; + } +}; + /** * Coroutine scheduler concept. * @@ -84,8 +144,9 @@ using SuspendToken = std::pair, WaitEvent::EventT>; * 2. have static `get` function that returns a reference to scheduler instance; * 3. have `done` method that returns whether scheduler's scheduled all coroutines; * 4. have `schedule` method that takes an event and schedules coroutines for execution; - * 5. have `suspend` method that suspends specified coroutine; - * 6. have `cancel` method that cancels specified SuspendToken. + * 5. have `contains` method returns whether specified SuspendToken is in schedule queue; + * 6. have `suspend` method that suspends specified coroutine; + * 7. have `cancel` method that cancels specified SuspendToken. */ template concept CoroutineSchedulerConcept = std::constructible_from @@ -93,22 +154,29 @@ concept CoroutineSchedulerConcept = std::constructible_from std::same_as; { s.done() } noexcept -> std::convertible_to; { s.schedule(schedule_event) } noexcept -> std::same_as; + { s.contains(token) } noexcept -> std::convertible_to; { s.suspend(token) } noexcept -> std::same_as; { s.cancel(token) } noexcept -> std::same_as; }; // === SimpleCoroutineScheduler =================================================================== +// This scheduler doesn't support waiting for the same event from multiple coroutines. +// We need to finalize our decision whether we allow to do it from PHP code or not. class SimpleCoroutineScheduler { template using unordered_map = memory_resource::stl::unordered_map; - template - using deque = memory_resource::stl::deque; + template + using unordered_set = memory_resource::stl::unordered_set; - deque> yield_coros; - deque> awaiting_for_stream_coros; - unordered_map> awaiting_for_update_coros; + template + using deque = memory_resource::stl::deque; + + deque yield_tokens; + deque awaiting_for_stream_tokens; + unordered_map awaiting_for_update_tokens; + unordered_set suspend_tokens; ScheduleStatus scheduleOnNoEvent() noexcept; ScheduleStatus scheduleOnIncomingStream() noexcept; @@ -117,17 +185,22 @@ class SimpleCoroutineScheduler { public: explicit SimpleCoroutineScheduler(memory_resource::unsynchronized_pool_resource &memory_resource) noexcept - : yield_coros(deque>::allocator_type{memory_resource}) - , awaiting_for_stream_coros(deque>::allocator_type{memory_resource}) - , awaiting_for_update_coros(unordered_map>::allocator_type{memory_resource}) {} + : yield_tokens(deque::allocator_type{memory_resource}) + , awaiting_for_stream_tokens(deque::allocator_type{memory_resource}) + , awaiting_for_update_tokens(unordered_map::allocator_type{memory_resource}) + , suspend_tokens(unordered_set::allocator_type{memory_resource}) {} static SimpleCoroutineScheduler &get() noexcept; bool done() const noexcept { - return yield_coros.empty() && awaiting_for_stream_coros.empty() && awaiting_for_update_coros.empty(); + return suspend_tokens.empty(); } ScheduleStatus schedule(ScheduleEvent::EventT) noexcept; + + bool contains(SuspendToken token) const noexcept { + return suspend_tokens.contains(token); + } void suspend(SuspendToken) noexcept; void cancel(SuspendToken) noexcept; }; diff --git a/runtime-light/stdlib/fork/fork-api.h b/runtime-light/stdlib/fork/fork-api.h index 9ccb268e64..99b3fa2167 100644 --- a/runtime-light/stdlib/fork/fork-api.h +++ b/runtime-light/stdlib/fork/fork-api.h @@ -15,24 +15,26 @@ namespace fork_api_impl_ { -constexpr double WAIT_FORK_MAX_TIMEOUT = 86400.0; +constexpr double MAX_TIMEOUT_S = 86400.0; +constexpr double DEFAULT_TIMEOUT_S = MAX_TIMEOUT_S; +constexpr auto MAX_TIMEOUT_NS = std::chrono::duration_cast(std::chrono::duration{MAX_TIMEOUT_S}); +constexpr auto DEFAULT_TIMEOUT_NS = std::chrono::duration_cast(std::chrono::duration{DEFAULT_TIMEOUT_S}); } // namespace fork_api_impl_ -constexpr int64_t INVALID_FORK_ID = -1; - template requires(is_optional::value) task_t f$wait(int64_t fork_id, double timeout = -1.0) noexcept { - if (timeout < 0.0) { - timeout = fork_api_impl_::WAIT_FORK_MAX_TIMEOUT; - } - auto task_opt{ForkComponentContext::get().pop_fork(fork_id)}; - if (!task_opt.has_value()) { + auto &fork_ctx{ForkComponentContext::get()}; + if (!fork_ctx.contains(fork_id)) { php_warning("can't find fork %" PRId64, fork_id); co_return T{}; } - const auto timeout_ns{std::chrono::duration_cast(std::chrono::duration{timeout})}; - co_return co_await wait_fork_t>{*std::move(task_opt), timeout_ns}; + // normalize timeout + const auto timeout_ns{timeout > 0 && timeout <= fork_api_impl_::MAX_TIMEOUT_S + ? std::chrono::duration_cast(std::chrono::duration{timeout}) + : fork_api_impl_::DEFAULT_TIMEOUT_NS}; + auto result_opt{co_await wait_with_timeout_t{wait_fork_t>{fork_id}, timeout_ns}}; + co_return result_opt.has_value() ? T{std::move(result_opt.value())} : T{}; } template diff --git a/runtime-light/stdlib/fork/fork-context.h b/runtime-light/stdlib/fork/fork-context.h index 79bd0f6f24..57c67ec1af 100644 --- a/runtime-light/stdlib/fork/fork-context.h +++ b/runtime-light/stdlib/fork/fork-context.h @@ -5,7 +5,7 @@ #pragma once #include -#include +#include #include "runtime-core/memory-resource/unsynchronized_pool_resource.h" #include "runtime-core/utils/kphp-assert-core.h" @@ -13,35 +13,42 @@ #include "runtime-light/stdlib/fork/fork.h" #include "runtime-light/utils/concepts.h" +constexpr int64_t INVALID_FORK_ID = -1; + class ForkComponentContext { template using unordered_map = memory_resource::stl::unordered_map; - static constexpr auto FORK_ID_INIT = 1; + static constexpr auto FORK_ID_INIT = 0; + + unordered_map> forks; + int64_t next_fork_id{FORK_ID_INIT + 1}; + + int64_t push_fork(task_t &&task) noexcept { + return forks.emplace(next_fork_id, std::move(task)), next_fork_id++; + } + + task_t pop_fork(int64_t fork_id) noexcept { + const auto it_fork{forks.find(fork_id)}; + if (it_fork == forks.end()) { + php_critical_error("can't find fork %" PRId64, fork_id); + } + auto fork{std::move(it_fork->second)}; + forks.erase(it_fork); + return fork; + } - unordered_map> forks_; - int64_t next_fork_id_{FORK_ID_INIT}; + friend class start_fork_t; + template + friend class wait_fork_t; public: explicit ForkComponentContext(memory_resource::unsynchronized_pool_resource &memory_resource) noexcept - : forks_(unordered_map>::allocator_type{memory_resource}) {} + : forks(unordered_map>::allocator_type{memory_resource}) {} static ForkComponentContext &get() noexcept; - int64_t push_fork(task_t &&task) noexcept { - const auto fork_id{next_fork_id_++}; - forks_.emplace(fork_id, std::move(task)); - php_debug("ForkComponentContext: push fork %" PRId64, fork_id); - return fork_id; - } - - std::optional> pop_fork(int64_t fork_id) noexcept { - if (const auto it_fork{forks_.find(fork_id)}; it_fork != forks_.cend()) { - php_debug("ForkComponentContext: pop fork %" PRId64, fork_id); - auto fork{std::move(it_fork->second)}; - forks_.erase(it_fork); - return {std::move(fork)}; - } - return std::nullopt; + bool contains(int64_t fork_id) const noexcept { + return forks.contains(fork_id); } }; diff --git a/runtime-light/stdlib/rpc/rpc-api.cpp b/runtime-light/stdlib/rpc/rpc-api.cpp index 1a7462e82b..3fd36bbeae 100644 --- a/runtime-light/stdlib/rpc/rpc-api.cpp +++ b/runtime-light/stdlib/rpc/rpc-api.cpp @@ -7,21 +7,28 @@ #include #include #include -#include +#include #include #include "common/algorithms/find.h" #include "common/rpc-error-codes.h" +#include "runtime-core/runtime-core.h" #include "runtime-core/utils/kphp-assert-core.h" #include "runtime-light/allocator/allocator.h" +#include "runtime-light/coroutine/awaitable.h" +#include "runtime-light/coroutine/task.h" #include "runtime-light/stdlib/rpc/rpc-context.h" #include "runtime-light/stdlib/rpc/rpc-extra-headers.h" +#include "runtime-light/stdlib/rpc/rpc-extra-info.h" #include "runtime-light/streams/interface.h" #include "runtime-light/tl/tl-core.h" namespace rpc_impl_ { -constexpr int32_t MAX_TIMEOUT_S = 86400; +constexpr double MAX_TIMEOUT_S = 86400.0; +constexpr double DEFAULT_TIMEOUT_S = 0.3; +constexpr auto MAX_TIMEOUT_NS = std::chrono::duration_cast(std::chrono::duration{MAX_TIMEOUT_S}); +constexpr auto DEFAULT_TIMEOUT_NS = std::chrono::duration_cast(std::chrono::duration{DEFAULT_TIMEOUT_S}); mixed mixed_array_get_value(const mixed &arr, const string &str_key, int64_t num_key) noexcept { if (!arr.is_array()) { @@ -89,42 +96,65 @@ class_instance store_function(const mixed &tl_object) noexcept { return rpc_tl_query; } -task_t rpc_send_impl(string actor, double timeout, bool ignore_answer) noexcept { - if (timeout <= 0 || timeout > MAX_TIMEOUT_S) { // TODO: handle timeouts - // timeout = conn.get()->timeout_ms * 0.001; - } - - auto &rpc_ctx = RpcComponentContext::get(); +task_t rpc_send_impl(string actor, double timeout, bool ignore_answer, bool collect_responses_extra_info) noexcept { + auto &rpc_ctx{RpcComponentContext::get()}; + // prepare RPC request string request_buf{}; size_t request_size{rpc_ctx.rpc_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.rpc_buffer.data(), 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)}; + const auto new_extra_header_size{sizeof(std::remove_cvref_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.reserve_at_least(request_size); + request_buf.append(reinterpret_cast(std::addressof(new_extra_header)), new_extra_header_size); request_buf.append(rpc_ctx.rpc_buffer.data() + cur_extra_header_size, rpc_ctx.rpc_buffer.size() - cur_extra_header_size); } else { request_buf.append(rpc_ctx.rpc_buffer.data(), request_size); } - - // get timestamp before co_await to also count the time we were waiting for runtime to resume this coroutine + // send RPC request + const auto query_id{rpc_ctx.current_query_id++}; 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()); + php_error("can't 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 + // create response extra info + if (collect_responses_extra_info) { + rpc_ctx.rpc_responses_extra_info.emplace(query_id, std::make_pair(rpc_response_extra_info_status_t::NOT_READY, rpc_response_extra_info_t{0, timestamp})); + } + // normalize timeout + const auto timeout_ns{timeout > 0 && timeout <= MAX_TIMEOUT_S ? std::chrono::duration_cast(std::chrono::duration{timeout}) + : DEFAULT_TIMEOUT_NS}; + // create fork to wait for RPC response. we need to do it even if 'ignore_answer' is 'true' to make sure + // that the stream will not be closed too early. otherwise, platform may even not send RPC request + auto waiter_task{[](int64_t query_id, auto comp_query, std::chrono::nanoseconds timeout, bool collect_responses_extra_info) noexcept -> task_t { + auto fetch_task{f$component_client_get_result(std::move(comp_query))}; + const auto response{(co_await wait_with_timeout_t{task_t::awaiter_t{std::addressof(fetch_task)}, timeout}).value_or(string{})}; + // update response extra info if needed + if (collect_responses_extra_info) { + auto &extra_info_map{RpcComponentContext::get().rpc_responses_extra_info}; + if (const auto it_extra_info{extra_info_map.find(query_id)}; it_extra_info != extra_info_map.end()) { + const auto timestamp{std::chrono::duration{std::chrono::system_clock::now().time_since_epoch()}.count()}; + it_extra_info->second.second = std::make_tuple(response.size(), timestamp - std::get<1>(it_extra_info->second.second)); + it_extra_info->second.first = rpc_response_extra_info_status_t::READY; + } else { + php_warning("can't find extra info for RPC query %" PRId64, query_id); + } + } + co_return response; + }(query_id, std::move(comp_query), timeout_ns, collect_responses_extra_info)}; + // start waiter fork + const auto waiter_fork_id{co_await start_fork_t{std::move(waiter_task), start_fork_t::execution::self}}; + + if (ignore_answer) { 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)); + rpc_ctx.response_waiter_forks.emplace(query_id, waiter_fork_id); co_return RpcQueryInfo{.id = query_id, .request_size = request_size, .timestamp = timestamp}; } @@ -142,15 +172,10 @@ task_t rpc_tl_query_one_impl(string actor, mixed tl_object, double co_return RpcQueryInfo{}; } - const auto query_info{co_await rpc_send_impl(actor, timeout, ignore_answer)}; + const auto query_info{co_await rpc_send_impl(actor, timeout, ignore_answer, collect_resp_extra_info)}; 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})); + rpc_ctx.response_fetcher_instances.emplace(query_info.id, std::move(rpc_tl_query)); } - co_return query_info; } @@ -170,19 +195,14 @@ task_t typed_rpc_tl_query_one_impl(string actor, const RpcRequest co_return RpcQueryInfo{}; } - const auto query_info{co_await rpc_send_impl(actor, timeout, ignore_answer)}; + const auto query_info{co_await rpc_send_impl(actor, timeout, ignore_answer, collect_responses_extra_info)}; 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})); + rpc_ctx.response_fetcher_instances.emplace(query_info.id, std::move(rpc_tl_query)); } - co_return query_info; } @@ -193,22 +213,22 @@ task_t> rpc_tl_query_result_one_impl(int64_t query_id) noexcept { auto &rpc_ctx{RpcComponentContext::get()}; class_instance rpc_query{}; - class_instance component_query{}; + int64_t response_waiter_fork_id{INVALID_FORK_ID}; { - 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)}; + const auto it_rpc_query{rpc_ctx.response_fetcher_instances.find(query_id)}; + const auto it_response_fetcher_fork_id{rpc_ctx.response_waiter_forks.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); + vk::final_action finalizer{[&rpc_ctx, it_rpc_query, it_response_fetcher_fork_id]() { + rpc_ctx.response_fetcher_instances.erase(it_rpc_query); + rpc_ctx.response_waiter_forks.erase(it_response_fetcher_fork_id); }}; - if (it_rpc_query == rpc_ctx.pending_rpc_queries.end() || it_component_query == rpc_ctx.pending_component_queries.end()) { + if (it_rpc_query == rpc_ctx.response_fetcher_instances.end() || it_response_fetcher_fork_id == rpc_ctx.response_waiter_forks.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); + response_waiter_fork_id = it_response_fetcher_fork_id->second; } if (rpc_query.is_null()) { @@ -220,20 +240,16 @@ task_t> rpc_tl_query_result_one_impl(int64_t query_id) noexcept { 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; + if (response_waiter_fork_id == INVALID_FORK_ID) { + co_return make_fetch_error(string{"can't find fetcher fork"}, TL_ERROR_INTERNAL); } + const auto data{(co_await wait_with_timeout_t{wait_fork_t{response_waiter_fork_id}, MAX_TIMEOUT_NS}).value()}; + if (data.empty()) { + co_return make_fetch_error(string{"rpc response timeout"}, TL_ERROR_QUERY_TIMEOUT); + } rpc_ctx.rpc_buffer.clean(); rpc_ctx.rpc_buffer.store_bytes(data.c_str(), data.size()); - co_return fetch_function_untyped(rpc_query); } @@ -244,22 +260,22 @@ task_t> typed_rpc_tl_query_result_one_impl(i auto &rpc_ctx{RpcComponentContext::get()}; class_instance rpc_query{}; - class_instance component_query{}; + int64_t response_waiter_fork_id{INVALID_FORK_ID}; { - 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)}; + const auto it_rpc_query{rpc_ctx.response_fetcher_instances.find(query_id)}; + const auto it_response_fetcher_fork_id{rpc_ctx.response_waiter_forks.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); + vk::final_action finalizer{[&rpc_ctx, it_rpc_query, it_response_fetcher_fork_id]() { + rpc_ctx.response_fetcher_instances.erase(it_rpc_query); + rpc_ctx.response_waiter_forks.erase(it_response_fetcher_fork_id); }}; - if (it_rpc_query == rpc_ctx.pending_rpc_queries.end() || it_component_query == rpc_ctx.pending_component_queries.end()) { + if (it_rpc_query == rpc_ctx.response_fetcher_instances.end() || it_response_fetcher_fork_id == rpc_ctx.response_waiter_forks.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); + response_waiter_fork_id = it_response_fetcher_fork_id->second; } if (rpc_query.is_null()) { @@ -271,20 +287,16 @@ task_t> typed_rpc_tl_query_result_one_impl(i 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; + if (response_waiter_fork_id == INVALID_FORK_ID) { + co_return error_factory.make_error(string{"can't find fetcher fork"}, TL_ERROR_INTERNAL); } + const auto data{(co_await wait_with_timeout_t{wait_fork_t{response_waiter_fork_id}, MAX_TIMEOUT_NS}).value()}; + if (data.empty()) { + co_return error_factory.make_error(string{"rpc response timeout"}, TL_ERROR_QUERY_TIMEOUT); + } rpc_ctx.rpc_buffer.clean(); rpc_ctx.rpc_buffer.store_bytes(data.c_str(), data.size()); - co_return fetch_function_typed(rpc_query, error_factory); } diff --git a/runtime-light/stdlib/rpc/rpc-context.cpp b/runtime-light/stdlib/rpc/rpc-context.cpp index 14b305d2bd..0c73fcd547 100644 --- a/runtime-light/stdlib/rpc/rpc-context.cpp +++ b/runtime-light/stdlib/rpc/rpc-context.cpp @@ -8,12 +8,6 @@ #include "runtime-light/component/image.h" #include "runtime-light/utils/context.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; } diff --git a/runtime-light/stdlib/rpc/rpc-context.h b/runtime-light/stdlib/rpc/rpc-context.h index b2e9d7fcf0..a2fcd7e962 100644 --- a/runtime-light/stdlib/rpc/rpc-context.h +++ b/runtime-light/stdlib/rpc/rpc-context.h @@ -13,7 +13,6 @@ #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" #include "runtime-light/tl/tl-core.h" struct RpcComponentContext final : private vk::not_copyable { @@ -23,11 +22,16 @@ struct RpcComponentContext final : private vk::not_copyable { tl::TLBuffer rpc_buffer; int64_t current_query_id{0}; CurrentTlQuery current_query; - unordered_map> pending_component_queries; - unordered_map> pending_rpc_queries; + unordered_map response_waiter_forks; + unordered_map> response_fetcher_instances; unordered_map> rpc_responses_extra_info; - explicit RpcComponentContext(memory_resource::unsynchronized_pool_resource &memory_resource); + explicit RpcComponentContext(memory_resource::unsynchronized_pool_resource &memory_resource) noexcept + : current_query() + , response_waiter_forks(unordered_map::allocator_type{memory_resource}) + , response_fetcher_instances(unordered_map>::allocator_type{memory_resource}) + , rpc_responses_extra_info( + unordered_map>::allocator_type{memory_resource}) {} static RpcComponentContext &get() noexcept; }; diff --git a/runtime-light/stdlib/timer/timer.h b/runtime-light/stdlib/timer/timer.h index fb34dd28d3..b771d05fcf 100644 --- a/runtime-light/stdlib/timer/timer.h +++ b/runtime-light/stdlib/timer/timer.h @@ -25,5 +25,6 @@ task_t f$set_timer(int64_t timeout_ms, T &&on_timer_callback) noexcept { co_return 0; }}; // TODO: someone should pop that fork from ForkComponentContext since it will stay there unless we perform f$wait on fork const auto duration_ms{std::chrono::milliseconds{static_cast(timeout_ms)}}; - co_await start_fork_and_reschedule_t(fork_f(std::chrono::duration_cast(duration_ms), std::forward(on_timer_callback))); + co_await start_fork_t{fork_f(std::chrono::duration_cast(duration_ms), std::forward(on_timer_callback)), + start_fork_t::execution::fork}; } diff --git a/runtime-light/streams/interface.cpp b/runtime-light/streams/interface.cpp index 0bf69dea59..bde0b4667d 100644 --- a/runtime-light/streams/interface.cpp +++ b/runtime-light/streams/interface.cpp @@ -46,9 +46,9 @@ task_t f$component_client_get_result(class_instance qu const auto [buffer, size]{co_await read_all_from_stream(stream_d)}; string result{buffer, static_cast(size)}; get_platform_context()->allocator.free(buffer); + php_debug("read %d bytes from stream %" PRIu64, size, stream_d); get_component_context()->release_stream(stream_d); query.get()->stream_d = INVALID_PLATFORM_DESCRIPTOR; - php_debug("read %d bytes from stream %" PRIu64, size, stream_d); co_return result; } diff --git a/runtime-light/streams/interface.h b/runtime-light/streams/interface.h index 3890e6930b..51915bef5f 100644 --- a/runtime-light/streams/interface.h +++ b/runtime-light/streams/interface.h @@ -6,6 +6,7 @@ #include +#include "runtime-core/runtime-core.h" #include "runtime-light/coroutine/task.h" #include "runtime-light/streams/component-stream.h" From 49415b23424208799f8155f85e68da0a83ea04d4 Mon Sep 17 00:00:00 2001 From: Denis Vaksman Date: Thu, 15 Aug 2024 18:15:19 +0300 Subject: [PATCH 21/45] Send `kphp_version` StatsHouse metric as count (not value) metric (#1066) --- runtime/php_assert.cpp | 2 ++ runtime/php_assert.h | 2 ++ server/php-engine.cpp | 1 + server/php-master.cpp | 4 ++-- server/statshouse/statshouse-manager.cpp | 3 ++- 5 files changed, 9 insertions(+), 3 deletions(-) diff --git a/runtime/php_assert.cpp b/runtime/php_assert.cpp index 80decabd90..a41b6d1a8b 100644 --- a/runtime/php_assert.cpp +++ b/runtime/php_assert.cpp @@ -32,6 +32,8 @@ #include "server/php-engine-vars.h" const char *engine_tag = "["; +long long engine_tag_number = 0; + const char *engine_pid = "] "; int php_disable_warnings = 0; diff --git a/runtime/php_assert.h b/runtime/php_assert.h index 0532199bac..e35ded1eef 100644 --- a/runtime/php_assert.h +++ b/runtime/php_assert.h @@ -16,6 +16,8 @@ extern int die_on_fail; extern const char *engine_tag; +extern long long engine_tag_number; + extern const char *engine_pid; extern int php_disable_warnings; diff --git a/server/php-engine.cpp b/server/php-engine.cpp index 61d1ce3bf1..1dc11ca64f 100644 --- a/server/php-engine.cpp +++ b/server/php-engine.cpp @@ -1834,6 +1834,7 @@ int main_args_handler(int i, const char *long_option) { } case 'E': { read_engine_tag(optarg); + engine_tag_number = atoll(engine_tag); return 0; } case 'm': { diff --git a/server/php-master.cpp b/server/php-master.cpp index 80d5d851c0..611c647c8c 100644 --- a/server/php-master.cpp +++ b/server/php-master.cpp @@ -959,7 +959,7 @@ std::string php_master_prepare_stats(bool add_worker_pids) { oss << "uptime\t" << get_uptime() << "\n"; if (engine_tag) { // engine_tag may be ended with "[" - oss << "kphp_version\t" << atoll(engine_tag) << "\n"; + oss << "kphp_version\t" << engine_tag_number << "\n"; } const auto &config = vk::singleton::get(); if (!config.get_environment().empty()) { @@ -1105,7 +1105,7 @@ static long long int instance_cache_memory_swaps_fail = 0; STATS_PROVIDER_TAGGED(kphp_stats, 100, stats_tag_kphp_server) { if (engine_tag) { - stats->add_gauge_stat("kphp_version", atoll(engine_tag)); + stats->add_gauge_stat("kphp_version", engine_tag_number); } stats->add_gauge_stat("uptime", get_uptime()); diff --git a/server/statshouse/statshouse-manager.cpp b/server/statshouse/statshouse-manager.cpp index 9af11021d0..78b492ebb5 100644 --- a/server/statshouse/statshouse-manager.cpp +++ b/server/statshouse/statshouse-manager.cpp @@ -5,6 +5,7 @@ #include "server/statshouse/statshouse-manager.h" #include +#include #include "common/precise-time.h" #include "common/resolver.h" @@ -181,7 +182,7 @@ void StatsHouseManager::add_common_master_stats(const workers_stats_t &workers_s double cpu_s_usage, double cpu_u_usage, long long int instance_cache_memory_swaps_ok, long long int instance_cache_memory_swaps_fail) { if (engine_tag) { - client.metric("kphp_version").write_value(atoll(engine_tag)); + client.metric("kphp_version").tag(std::to_string(engine_tag_number)).write_count(1); } client.metric("kphp_uptime").write_value(get_uptime()); From c265c18bda83b6a0f8820767bfbc2ec69560539c Mon Sep 17 00:00:00 2001 From: Dmitry Solomennikov <144122424+soloth@users.noreply.github.com> Date: Thu, 15 Aug 2024 22:13:26 +0300 Subject: [PATCH 22/45] Do not remove empty default values for string and array respectively (#1028) * Do not remove empty string and empty array as default values for string and array respectively * Save flag initial value for field was eliminated by optimization * Add test case * Mark test as correct Signed-off-by: dsolomennikov --- compiler/data/kphp-json-tags.cpp | 2 +- compiler/data/var-data.h | 1 + compiler/pipes/optimization.cpp | 1 + tests/phpt/json/38_optimized_init_val.php | 23 +++++++++++++++++++++++ 4 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 tests/phpt/json/38_optimized_init_val.php diff --git a/compiler/data/kphp-json-tags.cpp b/compiler/data/kphp-json-tags.cpp index fd04ca8fac..6d5d02244f 100644 --- a/compiler/data/kphp-json-tags.cpp +++ b/compiler/data/kphp-json-tags.cpp @@ -403,7 +403,7 @@ FieldJsonSettings merge_and_inherit_json_tags(const ClassMemberInstanceField &fi // for 'public int $id;' — no default and non-nullable type — set 'required' so that decode() would fire unless exists // it could be overridden with `@kphp-json required = false` - if (field.type_hint && !field.var->init_val && !does_type_hint_allow_null(field.type_hint)) { + if (field.type_hint && !field.var->init_val && !field.var->had_user_assigned_val && !does_type_hint_allow_null(field.type_hint)) { s.required = true; } diff --git a/compiler/data/var-data.h b/compiler/data/var-data.h index 2da4b9de33..17599dddf8 100644 --- a/compiler/data/var-data.h +++ b/compiler/data/var-data.h @@ -32,6 +32,7 @@ class VarData { std::string name; tinf::VarNode tinf_node; VertexPtr init_val; + bool had_user_assigned_val = false; FunctionPtr holder_func; ClassPtr class_id; std::unordered_set *bad_vars = nullptr; diff --git a/compiler/pipes/optimization.cpp b/compiler/pipes/optimization.cpp index 5fb9503ae4..9bc407896e 100644 --- a/compiler/pipes/optimization.cpp +++ b/compiler/pipes/optimization.cpp @@ -358,6 +358,7 @@ void OptimizationPass::on_finish() { if (can_init_value_be_removed(class_field.var->init_val, class_field.var)) { class_field.var->init_val = {}; + class_field.var->had_user_assigned_val = true; } else { explicit_cast_array_type(class_field.var->init_val, tinf::get_type(class_field.var)); } diff --git a/tests/phpt/json/38_optimized_init_val.php b/tests/phpt/json/38_optimized_init_val.php new file mode 100644 index 0000000000..9bb7536129 --- /dev/null +++ b/tests/phpt/json/38_optimized_init_val.php @@ -0,0 +1,23 @@ +@ok + Date: Mon, 19 Aug 2024 20:23:41 +0300 Subject: [PATCH 23/45] Refactor mixed file structure (#1068) Move some definitions from mixed.inl to mixed.cpp. There are some purposes: make debug sections smaller, disable unwanted inlining, compile some core functions with runtime target --- runtime-core/core-types/decl/mixed_decl.inl | 327 +-- runtime-core/core-types/definition/mixed.cpp | 1757 ++++++++++++++++ runtime-core/core-types/definition/mixed.inl | 1937 +----------------- 3 files changed, 2028 insertions(+), 1993 deletions(-) diff --git a/runtime-core/core-types/decl/mixed_decl.inl b/runtime-core/core-types/decl/mixed_decl.inl index 6509f6a85e..63f58c99f6 100644 --- a/runtime-core/core-types/decl/mixed_decl.inl +++ b/runtime-core/core-types/decl/mixed_decl.inl @@ -37,147 +37,147 @@ public: }; mixed(const void *) = delete; // deprecate conversion from pointer to boolean - inline mixed() = default; - inline mixed(const Unknown &u) noexcept; - inline mixed(const char *s, string::size_type len) noexcept; - inline mixed(const mixed &v) noexcept; - inline mixed(mixed &&v) noexcept; + mixed() = default; + mixed(const Unknown &u) noexcept; + mixed(const char *s, string::size_type len) noexcept; + mixed(const mixed &v) noexcept; + mixed(mixed &&v) noexcept; template>::value>> - inline mixed(T &&v) noexcept; + mixed(T &&v) noexcept; template::value>> - inline mixed(const Optional &v) noexcept; + mixed(const Optional &v) noexcept; template::value>> - inline mixed(Optional &&v) noexcept; + mixed(Optional &&v) noexcept; - inline mixed &operator=(const mixed &other) noexcept; - inline mixed &operator=(mixed &&other) noexcept; + mixed &operator=(const mixed &other) noexcept; + mixed &operator=(mixed &&other) noexcept; template>::value>> - inline mixed &operator=(T &&v) noexcept; + mixed &operator=(T &&v) noexcept; template::value>> - inline mixed &operator=(const Optional &v) noexcept; + mixed &operator=(const Optional &v) noexcept; template::value>> - inline mixed &operator=(Optional &&v) noexcept; - - inline mixed &assign(const char *other, string::size_type len); - - inline const mixed operator-() const; - inline const mixed operator+() const; - - inline int64_t operator~() const; - - inline mixed &operator+=(const mixed &other); - inline mixed &operator-=(const mixed &other); - inline mixed &operator*=(const mixed &other); - inline mixed &operator/=(const mixed &other); - inline mixed &operator%=(const mixed &other); - - inline mixed &operator&=(const mixed &other); - inline mixed &operator|=(const mixed &other); - inline mixed &operator^=(const mixed &other); - inline mixed &operator<<=(const mixed &other); - inline mixed &operator>>=(const mixed &other); - - inline mixed &operator++(); - inline const mixed operator++(int); - - inline mixed &operator--(); - inline const mixed operator--(int); - - inline bool operator!() const; - - inline mixed &append(const string &v); - inline mixed &append(tmp_string v); - - inline mixed &operator[](int64_t int_key); - inline mixed &operator[](int32_t key) { return (*this)[int64_t{key}]; } - inline mixed &operator[](const string &string_key); - inline mixed &operator[](tmp_string string_key); - inline mixed &operator[](const mixed &v); - inline mixed &operator[](double double_key); - inline mixed &operator[](const array::const_iterator &it); - inline mixed &operator[](const array::iterator &it); - - inline void set_value(int64_t int_key, const mixed &v); - inline void set_value(int32_t key, const mixed &value) { set_value(int64_t{key}, value); } - inline void set_value(const string &string_key, const mixed &v); - inline void set_value(const string &string_key, const mixed &v, int64_t precomuted_hash); - inline void set_value(tmp_string string_key, const mixed &v); - inline void set_value(const mixed &v, const mixed &value); - inline void set_value(double double_key, const mixed &value); - inline void set_value(const array::const_iterator &it); - inline void set_value(const array::iterator &it); - - inline const mixed get_value(int64_t int_key) const; - inline const mixed get_value(int32_t key) const { return get_value(int64_t{key}); } - inline const mixed get_value(const string &string_key) const; - inline const mixed get_value(const string &string_key, int64_t precomuted_hash) const; - inline const mixed get_value(tmp_string string_key) const; - inline const mixed get_value(const mixed &v) const; - inline const mixed get_value(double double_key) const; - inline const mixed get_value(const array::const_iterator &it) const; - inline const mixed get_value(const array::iterator &it) const; - - inline void push_back(const mixed &v); - inline const mixed push_back_return(const mixed &v); - - inline bool isset(int64_t int_key) const; - inline bool isset(int32_t key) const { return isset(int64_t{key}); } + mixed &operator=(Optional &&v) noexcept; + + mixed &assign(const char *other, string::size_type len); + + const mixed operator-() const; + const mixed operator+() const; + + int64_t operator~() const; + + mixed &operator+=(const mixed &other); + mixed &operator-=(const mixed &other); + mixed &operator*=(const mixed &other); + mixed &operator/=(const mixed &other); + mixed &operator%=(const mixed &other); + + mixed &operator&=(const mixed &other); + mixed &operator|=(const mixed &other); + mixed &operator^=(const mixed &other); + mixed &operator<<=(const mixed &other); + mixed &operator>>=(const mixed &other); + + mixed &operator++(); + const mixed operator++(int); + + mixed &operator--(); + const mixed operator--(int); + + bool operator!() const; + + mixed &append(const string &v); + mixed &append(tmp_string v); + + mixed &operator[](int64_t int_key); + mixed &operator[](int32_t key) { return (*this)[int64_t{key}]; } + mixed &operator[](const string &string_key); + mixed &operator[](tmp_string string_key); + mixed &operator[](const mixed &v); + mixed &operator[](double double_key); + mixed &operator[](const array::const_iterator &it); + mixed &operator[](const array::iterator &it); + + void set_value(int64_t int_key, const mixed &v); + void set_value(int32_t key, const mixed &value) { set_value(int64_t{key}, value); } + void set_value(const string &string_key, const mixed &v); + void set_value(const string &string_key, const mixed &v, int64_t precomuted_hash); + void set_value(tmp_string string_key, const mixed &v); + void set_value(const mixed &v, const mixed &value); + void set_value(double double_key, const mixed &value); + void set_value(const array::const_iterator &it); + void set_value(const array::iterator &it); + + const mixed get_value(int64_t int_key) const; + const mixed get_value(int32_t key) const { return get_value(int64_t{key}); } + const mixed get_value(const string &string_key) const; + const mixed get_value(const string &string_key, int64_t precomuted_hash) const; + const mixed get_value(tmp_string string_key) const; + const mixed get_value(const mixed &v) const; + const mixed get_value(double double_key) const; + const mixed get_value(const array::const_iterator &it) const; + const mixed get_value(const array::iterator &it) const; + + void push_back(const mixed &v); + const mixed push_back_return(const mixed &v); + + bool isset(int64_t int_key) const; + bool isset(int32_t key) const { return isset(int64_t{key}); } template - inline bool isset(const string &string_key, MaybeHash ...maybe_hash) const; - inline bool isset(const mixed &v) const; - inline bool isset(double double_key) const; + bool isset(const string &string_key, MaybeHash ...maybe_hash) const; + bool isset(const mixed &v) const; + bool isset(double double_key) const; - inline void unset(int64_t int_key); - inline void unset(int32_t key) { unset(int64_t{key}); } + void unset(int64_t int_key); + void unset(int32_t key) { unset(int64_t{key}); } template - inline void unset(const string &string_key, MaybeHash ...maybe_hash); - inline void unset(const mixed &v); - inline void unset(double double_key); + void unset(const string &string_key, MaybeHash ...maybe_hash); + void unset(const mixed &v); + void unset(double double_key); void destroy() noexcept; ~mixed() noexcept; void clear() noexcept; - inline const mixed to_numeric() const; - inline bool to_bool() const; - inline int64_t to_int() const; - inline double to_float() const; - inline const string to_string() const; - inline const array to_array() const; + const mixed to_numeric() const; + bool to_bool() const; + int64_t to_int() const; + double to_float() const; + const string to_string() const; + const array to_array() const; - inline bool &as_bool() __attribute__((always_inline)); - inline const bool &as_bool() const __attribute__((always_inline)); + bool &as_bool() __attribute__((always_inline)) { return *reinterpret_cast(&storage_); } + const bool &as_bool() const __attribute__((always_inline)) { return *reinterpret_cast(&storage_); } - inline int64_t &as_int() __attribute__((always_inline)); - inline const int64_t &as_int() const __attribute__((always_inline)); + int64_t &as_int() __attribute__((always_inline)) { return *reinterpret_cast(&storage_); } + const int64_t &as_int() const __attribute__((always_inline)) { return *reinterpret_cast(&storage_); } - inline double &as_double() __attribute__((always_inline)); - inline const double &as_double() const __attribute__((always_inline)); + double &as_double() __attribute__((always_inline)) { return *reinterpret_cast(&storage_); } + const double &as_double() const __attribute__((always_inline)) { return *reinterpret_cast(&storage_); } - inline string &as_string() __attribute__((always_inline)); - inline const string &as_string() const __attribute__((always_inline)); + string &as_string() __attribute__((always_inline)) { return *reinterpret_cast(&storage_); } + const string &as_string() const __attribute__((always_inline)) { return *reinterpret_cast(&storage_); } - inline array &as_array() __attribute__((always_inline)); - inline const array &as_array() const __attribute__((always_inline)); + array &as_array() __attribute__((always_inline)) { return *reinterpret_cast *>(&storage_); } + const array &as_array() const __attribute__((always_inline)) { return *reinterpret_cast *>(&storage_); } - inline vk::intrusive_ptr as_object() __attribute__((always_inline)); - inline const vk::intrusive_ptr as_object() const __attribute__((always_inline)); + vk::intrusive_ptr as_object() __attribute__((always_inline)) { return *reinterpret_cast*>(&storage_); } + const vk::intrusive_ptr as_object() const __attribute__((always_inline)) { return *reinterpret_cast*>(&storage_); } // TODO is it ok to return pointer to mutable from const method? // I need it just to pass such a pointer into class_instance. Mutability is needed because // class_instance do ref-counting template - inline T *as_object_ptr() const { + T *as_object_ptr() const { auto ptr_to_object = vk::dynamic_pointer_cast(*reinterpret_cast *>(&storage_)); return ptr_to_object.get(); } template - inline bool is_a() const { + bool is_a() const { if (type_ != type::OBJECT) { return false; } @@ -186,28 +186,28 @@ public: return static_cast(vk::dynamic_pointer_cast(ptr)); } - inline int64_t safe_to_int() const; + int64_t safe_to_int() const; - inline void convert_to_numeric(); - inline void convert_to_bool(); - inline void convert_to_int(); - inline void convert_to_float(); - inline void convert_to_string(); + void convert_to_numeric(); + void convert_to_bool(); + void convert_to_int(); + void convert_to_float(); + void convert_to_string(); - inline const bool &as_bool(const char *function) const; - inline const int64_t &as_int(const char *function) const; - inline const double &as_float(const char *function) const; - inline const string &as_string(const char *function) const; - inline const array &as_array(const char *function) const; + const bool &as_bool(const char *function) const; + const int64_t &as_int(const char *function) const; + const double &as_float(const char *function) const; + const string &as_string(const char *function) const; + const array &as_array(const char *function) const; - inline bool &as_bool(const char *function); - inline int64_t &as_int(const char *function); - inline double &as_float(const char *function); - inline string &as_string(const char *function); - inline array &as_array(const char *function); + bool &as_bool(const char *function); + int64_t &as_int(const char *function); + double &as_float(const char *function); + string &as_string(const char *function); + array &as_array(const char *function); - inline bool is_numeric() const; - inline bool is_scalar() const; + bool is_numeric() const; + bool is_scalar() const; inline type get_type() const; inline bool is_null() const; @@ -218,43 +218,43 @@ public: inline bool is_array() const; inline bool is_object() const; - inline const string get_type_str() const; - inline const char *get_type_c_str() const; - inline const char *get_type_or_class_name() const; + const string get_type_str() const; + const char *get_type_c_str() const; + const char *get_type_or_class_name() const; - inline bool empty() const; - inline int64_t count() const; - inline int64_t compare(const mixed &rhs) const; + bool empty() const; + int64_t count() const; + int64_t compare(const mixed &rhs) const; - inline array::const_iterator begin() const; - inline array::const_iterator end() const; + array::const_iterator begin() const; + array::const_iterator end() const; - inline array::iterator begin(); - inline array::iterator end(); + array::iterator begin(); + array::iterator end(); inline void swap(mixed &other); - inline int64_t get_reference_counter() const; + int64_t get_reference_counter() const; - inline void set_reference_counter_to(ExtraRefCnt ref_cnt_value) noexcept; - inline bool is_reference_counter(ExtraRefCnt ref_cnt_value) const noexcept; - inline void force_destroy(ExtraRefCnt expected_ref_cnt) noexcept; + void set_reference_counter_to(ExtraRefCnt ref_cnt_value) noexcept; + bool is_reference_counter(ExtraRefCnt ref_cnt_value) const noexcept; + void force_destroy(ExtraRefCnt expected_ref_cnt) noexcept; - inline size_t estimate_memory_usage() const; + size_t estimate_memory_usage() const; static inline void reset_empty_values() noexcept; private: - inline void copy_from(const mixed &other); - inline void copy_from(mixed &&other); + void copy_from(const mixed &other); + void copy_from(mixed &&other); template inline void init_from(T &&v); - inline void init_from(mixed v) { copy_from(std::move(v)); } + void init_from(mixed v) { copy_from(std::move(v)); } template inline mixed &assign_from(T &&v); - inline mixed &assign_from(mixed v) { return (*this = std::move(v)); } + mixed &assign_from(mixed v) { return (*this = std::move(v)); } template auto get_type_and_value_ptr(const array &) { return std::make_pair(type::ARRAY , &as_array()); } @@ -274,19 +274,32 @@ private: uint64_t storage_{0}; }; -template -inline mixed f$to_mixed(const class_instance &instance) noexcept { - mixed m; - m = instance; - return m; -} +mixed operator+(const mixed &lhs, const mixed &rhs); +mixed operator-(const mixed &lhs, const mixed &rhs); +mixed operator*(const mixed &lhs, const mixed &rhs); +mixed operator-(const string &lhs); +mixed operator+(const string &lhs); +int64_t operator&(const mixed &lhs, const mixed &rhs); +int64_t operator|(const mixed &lhs, const mixed &rhs); +int64_t operator^(const mixed &lhs, const mixed &rhs); +int64_t operator<<(const mixed &lhs, const mixed &rhs); +int64_t operator>>(const mixed &lhs, const mixed &rhs); +bool operator<(const mixed &lhs, const mixed &rhs); +bool operator<=(const mixed &lhs, const mixed &rhs); +void swap(mixed &lhs, mixed &rhs); + +string_buffer &operator<<(string_buffer &sb, const mixed &v); + +template +bool less_number_string_as_php8_impl(T lhs, const string &rhs); +template +bool less_string_number_as_php8_impl(const string &lhs, T rhs); +template +bool less_number_string_as_php8(bool php7_result, T lhs, const string &rhs); +template +inline bool less_string_number_as_php8(bool php7_result, const string &lhs, T rhs); +template +mixed f$to_mixed(const class_instance &instance) noexcept; template -inline ResultClass from_mixed(const mixed &m, const string &) noexcept { - if constexpr (!std::is_polymorphic_v) { - php_error("Internal error. Class inside a mixed is not polymorphic"); - return {}; - } else { - return ResultClass::create_from_base_raw_ptr(dynamic_cast(m.as_object_ptr())); - } -} +ResultClass from_mixed(const mixed &m, const string &) noexcept; \ No newline at end of file diff --git a/runtime-core/core-types/definition/mixed.cpp b/runtime-core/core-types/definition/mixed.cpp index bc1deb1420..d22451ae67 100644 --- a/runtime-core/core-types/definition/mixed.cpp +++ b/runtime-core/core-types/definition/mixed.cpp @@ -4,6 +4,1763 @@ #include "runtime-core/runtime-core.h" +void mixed::copy_from(const mixed &other) { + switch (other.get_type()) { + case type::STRING: + new(&as_string()) string(other.as_string()); + break; + case type::ARRAY: + new(&as_array()) array(other.as_array()); + break; + case type::OBJECT: { + new (&storage_) vk::intrusive_ptr(*reinterpret_cast *>(&other.storage_)); + break; + } + default: + storage_ = other.storage_; + } + type_ = other.get_type(); +} + +void mixed::copy_from(mixed &&other) { + switch (other.get_type()) { + case type::STRING: + new(&as_string()) string(std::move(other.as_string())); + break; + case type::ARRAY: + new(&as_array()) array(std::move(other.as_array())); + break; + case type::OBJECT: { + storage_ = other.storage_; + other.storage_ = 0; + break; + } + default: + storage_ = other.storage_; + } + type_ = other.get_type(); +} + +mixed::mixed(const mixed &v) noexcept { + copy_from(v); +} + +mixed::mixed(mixed &&v) noexcept { + copy_from(std::move(v)); +} + +mixed::mixed(const Unknown &u __attribute__((unused))) noexcept { + php_assert ("Unknown used!!!" && 0); +} + +mixed::mixed(const char *s, string::size_type len) noexcept : + mixed(string{s, len}){ +} + + +mixed &mixed::assign(const char *other, string::size_type len) { + if (get_type() == type::STRING) { + as_string().assign(other, len); + } else { + destroy(); + type_ = type::STRING; + new(&as_string()) string(other, len); + } + return *this; +} + +const mixed mixed::operator-() const { + mixed arg1 = to_numeric(); + + if (arg1.get_type() == type::INTEGER) { + arg1.as_int() = -arg1.as_int(); + } else { + arg1.as_double() = -arg1.as_double(); + } + return arg1; +} + +const mixed mixed::operator+() const { + return to_numeric(); +} + + +int64_t mixed::operator~() const { + return ~to_int(); +} + + +mixed &mixed::operator+=(const mixed &other) { + if (likely (get_type() == type::INTEGER && other.get_type() == type::INTEGER)) { + as_int() += other.as_int(); + return *this; + } + + if (unlikely (get_type() == type::ARRAY || other.get_type() == type::ARRAY)) { + if (get_type() == type::ARRAY && other.get_type() == type::ARRAY) { + as_array() += other.as_array(); + } else { + php_warning("Unsupported operand types for operator += (%s and %s)", get_type_c_str(), other.get_type_c_str()); + } + return *this; + } + + convert_to_numeric(); + const mixed arg2 = other.to_numeric(); + + if (get_type() == type::INTEGER) { + if (arg2.get_type() == type::INTEGER) { + as_int() += arg2.as_int(); + } else { + type_ = type::FLOAT; + as_double() = static_cast(as_int()) + arg2.as_double(); + } + } else { + if (arg2.get_type() == type::INTEGER) { + as_double() += static_cast(arg2.as_int()); + } else { + as_double() += arg2.as_double(); + } + } + + return *this; +} + +mixed &mixed::operator-=(const mixed &other) { + if (likely (get_type() == type::INTEGER && other.get_type() == type::INTEGER)) { + as_int() -= other.as_int(); + return *this; + } + + convert_to_numeric(); + const mixed arg2 = other.to_numeric(); + + if (get_type() == type::INTEGER) { + if (arg2.get_type() == type::INTEGER) { + as_int() -= arg2.as_int(); + } else { + type_ = type::FLOAT; + as_double() = static_cast(as_int()) - arg2.as_double(); + } + } else { + if (arg2.get_type() == type::INTEGER) { + as_double() -= static_cast(arg2.as_int()); + } else { + as_double() -= arg2.as_double(); + } + } + + return *this; +} + +mixed &mixed::operator*=(const mixed &other) { + if (likely (get_type() == type::INTEGER && other.get_type() == type::INTEGER)) { + as_int() *= other.as_int(); + return *this; + } + + convert_to_numeric(); + const mixed arg2 = other.to_numeric(); + + if (get_type() == type::INTEGER) { + if (arg2.get_type() == type::INTEGER) { + as_int() *= arg2.as_int(); + } else { + type_ = type::FLOAT; + as_double() = static_cast(as_int()) * arg2.as_double(); + } + } else { + if (arg2.get_type() == type::INTEGER) { + as_double() *= static_cast(arg2.as_int()); + } else { + as_double() *= arg2.as_double(); + } + } + + return *this; +} + +mixed &mixed::operator/=(const mixed &other) { + if (likely (get_type() == type::INTEGER && other.get_type() == type::INTEGER)) { + if (as_int() % other.as_int() == 0) { + as_int() /= other.as_int(); + } else { + type_ = type::FLOAT; + as_double() = static_cast(as_int()) / static_cast(other.as_int()); + } + return *this; + } + + convert_to_numeric(); + const mixed arg2 = other.to_numeric(); + + if (arg2.get_type() == type::INTEGER) { + if (arg2.as_int() == 0) { + php_warning("Integer division by zero"); + type_ = type::BOOLEAN; + as_bool() = false; + return *this; + } + + if (get_type() == type::INTEGER) { + if (as_int() % arg2.as_int() == 0) { + as_int() /= arg2.as_int(); + } else { + type_ = type::FLOAT; + as_double() = static_cast(as_int()) / static_cast(other.as_int()); + } + } else { + as_double() /= static_cast(arg2.as_int()); + } + } else { + if (arg2.as_double() == 0) { + php_warning("Float division by zero"); + type_ = type::BOOLEAN; + as_bool() = false; + return *this; + } + + if (get_type() == type::INTEGER) { + type_ = type::FLOAT; + as_double() = static_cast(as_int()) / arg2.as_double(); + } else { + as_double() /= arg2.as_double(); + } + } + + return *this; +} + +mixed &mixed::operator%=(const mixed &other) { + int64_t div = other.to_int(); + if (div == 0) { + php_warning("Modulo by zero"); + *this = false; + return *this; + } + convert_to_int(); + as_int() %= div; + + return *this; +} + + +mixed &mixed::operator&=(const mixed &other) { + convert_to_int(); + as_int() &= other.to_int(); + return *this; +} + +mixed &mixed::operator|=(const mixed &other) { + convert_to_int(); + as_int() |= other.to_int(); + return *this; +} + +mixed &mixed::operator^=(const mixed &other) { + convert_to_int(); + as_int() ^= other.to_int(); + return *this; +} + +mixed &mixed::operator<<=(const mixed &other) { + convert_to_int(); + as_int() <<= other.to_int(); + return *this; +} + +mixed &mixed::operator>>=(const mixed &other) { + convert_to_int(); + as_int() >>= other.to_int(); + return *this; +} + + +mixed &mixed::operator++() { + switch (get_type()) { + case type::NUL: + type_ = type::INTEGER; + as_int() = 1; + return *this; + case type::BOOLEAN: + php_warning("Can't apply operator ++ to boolean"); + return *this; + case type::INTEGER: + ++as_int(); + return *this; + case type::FLOAT: + as_double() += 1; + return *this; + case type::STRING: + *this = as_string().to_numeric(); + return ++(*this); + case type::ARRAY: + php_warning("Can't apply operator ++ to array"); + return *this; + case type::OBJECT: + php_warning("Can't apply operator ++ to %s", get_type_or_class_name()); + return *this; + default: + __builtin_unreachable(); + } +} + +const mixed mixed::operator++(int) { + switch (get_type()) { + case type::NUL: + type_ = type::INTEGER; + as_int() = 1; + return mixed(); + case type::BOOLEAN: + php_warning("Can't apply operator ++ to boolean"); + return as_bool(); + case type::INTEGER: { + mixed res(as_int()); + ++as_int(); + return res; + } + case type::FLOAT: { + mixed res(as_double()); + as_double() += 1; + return res; + } + case type::STRING: { + mixed res(as_string()); + *this = as_string().to_numeric(); + (*this)++; + return res; + } + case type::ARRAY: + php_warning("Can't apply operator ++ to array"); + return as_array(); + case type::OBJECT: + php_warning("Can't apply operator ++ to %s", get_type_or_class_name()); + return *this; + default: + __builtin_unreachable(); + } +} + +mixed &mixed::operator--() { + if (likely (get_type() == type::INTEGER)) { + --as_int(); + return *this; + } + + switch (get_type()) { + case type::NUL: + php_warning("Can't apply operator -- to null"); + return *this; + case type::BOOLEAN: + php_warning("Can't apply operator -- to boolean"); + return *this; + case type::INTEGER: + --as_int(); + return *this; + case type::FLOAT: + as_double() -= 1; + return *this; + case type::STRING: + *this = as_string().to_numeric(); + return --(*this); + case type::ARRAY: + php_warning("Can't apply operator -- to array"); + return *this; + case type::OBJECT: + php_warning("Can't apply operator -- to %s", get_type_or_class_name()); + return *this; + default: + __builtin_unreachable(); + } +} + +const mixed mixed::operator--(int) { + if (likely (get_type() == type::INTEGER)) { + mixed res(as_int()); + --as_int(); + return res; + } + + switch (get_type()) { + case type::NUL: + php_warning("Can't apply operator -- to null"); + return mixed(); + case type::BOOLEAN: + php_warning("Can't apply operator -- to boolean"); + return as_bool(); + case type::INTEGER: { + mixed res(as_int()); + --as_int(); + return res; + } + case type::FLOAT: { + mixed res(as_double()); + as_double() -= 1; + return res; + } + case type::STRING: { + mixed res(as_string()); + *this = as_string().to_numeric(); + (*this)--; + return res; + } + case type::ARRAY: + php_warning("Can't apply operator -- to array"); + return as_array(); + case type::OBJECT: + php_warning("Can't apply operator -- to %s", get_type_or_class_name()); + return *this; + default: + __builtin_unreachable(); + } +} + + +bool mixed::operator!() const { + return !to_bool(); +} + + +mixed &mixed::append(const string &v) { + if (unlikely (get_type() != type::STRING)) { + convert_to_string(); + } + as_string().append(v); + return *this; +} + +mixed &mixed::append(tmp_string v) { + if (unlikely (get_type() != type::STRING)) { + convert_to_string(); + } + as_string().append(v.data, v.size); + return *this; +} + +const mixed mixed::to_numeric() const { + switch (get_type()) { + case type::NUL: + return 0; + case type::BOOLEAN: + return (as_bool() ? 1 : 0); + case type::INTEGER: + return as_int(); + case type::FLOAT: + return as_double(); + case type::STRING: + return as_string().to_numeric(); + case type::ARRAY: + php_warning("Wrong conversion from array to number"); + return as_array().to_int(); + case type::OBJECT: + php_warning("Wrong conversion from %s to number", get_type_or_class_name()); + return (as_object() ? 1 : 0); + default: + __builtin_unreachable(); + } +} + + +bool mixed::to_bool() const { + switch (get_type()) { + case type::NUL: + return false; + case type::BOOLEAN: + return as_bool(); + case type::INTEGER: + return (bool)as_int(); + case type::FLOAT: + return (bool)as_double(); + case type::STRING: + return as_string().to_bool(); + case type::ARRAY: + return !as_array().empty(); + case type::OBJECT: + return (bool)as_object(); + default: + __builtin_unreachable(); + } +} + +int64_t mixed::to_int() const { + switch (get_type()) { + case type::NUL: + return 0; + case type::BOOLEAN: + return static_cast(as_bool()); + case type::INTEGER: + return as_int(); + case type::FLOAT: + return static_cast(as_double()); + case type::STRING: + return as_string().to_int(); + case type::ARRAY: + php_warning("Wrong conversion from array to int"); + return as_array().to_int(); + case type::OBJECT: + php_warning("Wrong conversion from %s to int", get_type_or_class_name()); + return (as_object() ? 1 : 0); + default: + __builtin_unreachable(); + } +} + +double mixed::to_float() const { + switch (get_type()) { + case type::NUL: + return 0.0; + case type::BOOLEAN: + return (as_bool() ? 1.0 : 0.0); + case type::INTEGER: + return (double)as_int(); + case type::FLOAT: + return as_double(); + case type::STRING: + return as_string().to_float(); + case type::ARRAY: + php_warning("Wrong conversion from array to float"); + return as_array().to_float(); + case type::OBJECT: { + php_warning("Wrong conversion from %s to float", get_type_or_class_name()); + return (as_object() ? 1.0 : 0.0); + } + default: + __builtin_unreachable(); + } +} + +static string to_string_without_warning(const mixed &m) { + switch (m.get_type()) { + case mixed::type::NUL: + return string(); + case mixed::type::BOOLEAN: + return (m.as_bool() ? string("1", 1) : string()); + case mixed::type::INTEGER: + return string(m.as_int()); + case mixed::type::FLOAT: + return string(m.as_double()); + case mixed::type::STRING: + return m.as_string(); + case mixed::type::ARRAY: + return string("Array", 5); + case mixed::type::OBJECT: { + const char *s = m.get_type_or_class_name(); + return string(s, strlen(s)); + } + default: + __builtin_unreachable(); + } +} + +const string mixed::to_string() const { + switch (get_type()) { + case mixed::type::NUL: + case mixed::type::BOOLEAN: + case mixed::type::INTEGER: + case mixed::type::FLOAT: + case mixed::type::STRING: + break; + case type::ARRAY: + php_warning("Conversion from array to string"); + break; + case type::OBJECT: { + php_warning("Wrong conversion from %s to string", get_type_or_class_name()); + break; + } + default: + __builtin_unreachable(); + } + return to_string_without_warning(*this); +} + +const array mixed::to_array() const { + switch (get_type()) { + case type::NUL: + return array(); + case type::BOOLEAN: + case type::INTEGER: + case type::FLOAT: + case type::STRING: + case type::OBJECT: { + array res(array_size(1, true)); + res.push_back(*this); + return res; + } + case type::ARRAY: + return as_array(); + default: + __builtin_unreachable(); + } +} + +int64_t mixed::safe_to_int() const { + switch (get_type()) { + case type::NUL: + return 0; + case type::BOOLEAN: + return static_cast(as_bool()); + case type::INTEGER: + return as_int(); + case type::FLOAT: { + constexpr auto max_int = static_cast(static_cast(std::numeric_limits::max()) + 1); + if (fabs(as_double()) > max_int) { + php_warning("Wrong conversion from double %.6lf to int", as_double()); + } + return static_cast(as_double()); + } + case type::STRING: + return as_string().safe_to_int(); + case type::ARRAY: + php_warning("Wrong conversion from array to int"); + return as_array().to_int(); + case type::OBJECT: { + php_warning("Wrong conversion from %s to int", get_type_or_class_name()); + return (as_object() ? 1 : 0); + } + default: + __builtin_unreachable(); + } +} + + +void mixed::convert_to_numeric() { + switch (get_type()) { + case type::NUL: + type_ = type::INTEGER; + as_int() = 0; + return; + case type::BOOLEAN: + type_ = type::INTEGER; + as_int() = as_bool(); + return; + case type::INTEGER: + case type::FLOAT: + return; + case type::STRING: + *this = as_string().to_numeric(); + return; + case type::ARRAY: { + php_warning("Wrong conversion from array to number"); + const int64_t int_val = as_array().to_int(); + as_array().~array(); + type_ = type::INTEGER; + as_int() = int_val; + return; + } + case type::OBJECT: { + php_warning("Wrong conversion from %s to number", get_type_or_class_name()); + const int64_t int_val = (as_object() ? 1 : 0); + destroy(); + type_ = type::INTEGER; + as_int() = int_val; + return; + } + default: + __builtin_unreachable(); + } +} + +void mixed::convert_to_bool() { + switch (get_type()) { + case type::NUL: + type_ = type::BOOLEAN; + as_bool() = 0; + return; + case type::BOOLEAN: + return; + case type::INTEGER: + type_ = type::BOOLEAN; + as_bool() = (bool)as_int(); + return; + case type::FLOAT: + type_ = type::BOOLEAN; + as_bool() = (bool)as_double(); + return; + case type::STRING: { + const bool bool_val = as_string().to_bool(); + as_string().~string(); + type_ = type::BOOLEAN; + as_bool() = bool_val; + return; + } + case type::ARRAY: { + const bool bool_val = as_array().to_bool(); + as_array().~array(); + type_ = type::BOOLEAN; + as_bool() = bool_val; + return; + } + case type::OBJECT: { + const bool bool_val = static_cast(as_object()); + destroy(); + type_ = type::BOOLEAN; + as_bool() = bool_val; + return; + } + default: + __builtin_unreachable(); + } +} + +void mixed::convert_to_int() { + switch (get_type()) { + case type::NUL: + type_ = type::INTEGER; + as_int() = 0; + return; + case type::BOOLEAN: + type_ = type::INTEGER; + as_int() = as_bool(); + return; + case type::INTEGER: + return; + case type::FLOAT: + type_ = type::INTEGER; + as_int() = static_cast(as_double()); + return; + case type::STRING: { + const int64_t int_val = as_string().to_int(); + as_string().~string(); + type_ = type::INTEGER; + as_int() = int_val; + return; + } + case type::ARRAY: { + php_warning("Wrong conversion from array to int"); + const int64_t int_val = as_array().to_int(); + as_array().~array(); + type_ = type::INTEGER; + as_int() = int_val; + return; + } + case type::OBJECT: { + php_warning("Wrong conversion from %s to int", get_type_or_class_name()); + const int64_t int_val = (as_object() ? 1 : 0); + destroy(); + type_ = type::INTEGER; + as_int() = int_val; + return; + } + default: + __builtin_unreachable(); + } +} + +void mixed::convert_to_float() { + switch (get_type()) { + case type::NUL: + type_ = type::FLOAT; + as_double() = 0.0; + return; + case type::BOOLEAN: + type_ = type::FLOAT; + as_double() = as_bool(); + return; + case type::INTEGER: + type_ = type::FLOAT; + as_double() = (double)as_int(); + return; + case type::FLOAT: + return; + case type::STRING: { + const double float_val = as_string().to_float(); + as_string().~string(); + type_ = type::FLOAT; + as_double() = float_val; + return; + } + case type::ARRAY: { + php_warning("Wrong conversion from array to float"); + const double float_val = as_array().to_float(); + as_array().~array(); + type_ = type::FLOAT; + as_double() = float_val; + return; + } + case type::OBJECT: { + php_warning("Wrong conversion from %s to float", get_type_or_class_name()); + const double float_val = (as_object() ? 1.0 : 0.0); + destroy(); + type_ = type::FLOAT; + as_double() = float_val; + return; + } + default: + __builtin_unreachable(); + } +} + +void mixed::convert_to_string() { + switch (get_type()) { + case type::NUL: + type_ = type::STRING; + new(&as_string()) string(); + return; + case type::BOOLEAN: + type_ = type::STRING; + if (as_bool()) { + new(&as_string()) string("1", 1); + } else { + new(&as_string()) string(); + } + return; + case type::INTEGER: + type_ = type::STRING; + new(&as_string()) string(as_int()); + return; + case type::FLOAT: + type_ = type::STRING; + new(&as_string()) string(as_double()); + return; + case type::STRING: + return; + case type::ARRAY: + php_warning("Converting from array to string"); + as_array().~array(); + type_ = type::STRING; + new(&as_string()) string("Array", 5); + return; + case type::OBJECT: { + php_warning("Wrong conversion from %s to string", get_type_or_class_name()); + string s = string(get_type_or_class_name(), strlen(get_type_or_class_name())); + destroy(); + type_ = type::STRING; + new (&as_string()) string(std::move(s)); + return; + } + default: + __builtin_unreachable(); + } +} + +const bool &mixed::as_bool(const char *function) const { + switch (get_type()) { + case type::BOOLEAN: + return as_bool(); + default: + php_warning("%s() expects parameter to be boolean, %s is given", function, get_type_or_class_name()); + return empty_value(); + } +} + +const int64_t &mixed::as_int(const char *function) const { + switch (get_type()) { + case type::INTEGER: + return as_int(); + default: + php_warning("%s() expects parameter to be int, %s is given", function, get_type_or_class_name()); + return empty_value(); + } +} + +const double &mixed::as_float(const char *function) const { + switch (get_type()) { + case type::FLOAT: + return as_double(); + default: + php_warning("%s() expects parameter to be float, %s is given", function, get_type_or_class_name()); + return empty_value(); + } +} + +const string &mixed::as_string(const char *function) const { + switch (get_type()) { + case type::STRING: + return as_string(); + default: + php_warning("%s() expects parameter to be string, %s is given", function, get_type_or_class_name()); + return empty_value(); + } +} + +const array &mixed::as_array(const char *function) const { + switch (get_type()) { + case type::ARRAY: + return as_array(); + default: + php_warning("%s() expects parameter to be array, %s is given", function, get_type_or_class_name()); + return empty_value>(); + } +} + + +bool &mixed::as_bool(const char *function) { + switch (get_type()) { + case type::NUL: + convert_to_bool(); + [[fallthrough]]; + case type::BOOLEAN: + return as_bool(); + default: + php_warning("%s() expects parameter to be boolean, %s is given", function, get_type_or_class_name()); + return empty_value(); + } +} + +int64_t &mixed::as_int(const char *function) { + switch (get_type()) { + case type::NUL: + [[fallthrough]]; + case type::BOOLEAN: + [[fallthrough]]; + case type::FLOAT: + [[fallthrough]]; + case type::STRING: + convert_to_int(); + [[fallthrough]]; + case type::INTEGER: + return as_int(); + default: + php_warning("%s() expects parameter to be int, %s is given", function, get_type_or_class_name()); + return empty_value(); + } +} + +double &mixed::as_float(const char *function) { + switch (get_type()) { + case type::NUL: + [[fallthrough]]; + case type::BOOLEAN: + [[fallthrough]]; + case type::INTEGER: + [[fallthrough]]; + case type::STRING: + convert_to_float(); + [[fallthrough]]; + case type::FLOAT: + return as_double(); + default: + php_warning("%s() expects parameter to be float, %s is given", function, get_type_or_class_name()); + return empty_value(); + } +} + +string &mixed::as_string(const char *function) { + switch (get_type()) { + case type::NUL: + [[fallthrough]]; + case type::BOOLEAN: + [[fallthrough]]; + case type::INTEGER: + [[fallthrough]]; + case type::FLOAT: + convert_to_string(); + [[fallthrough]]; + case type::STRING: + return as_string(); + default: + php_warning("%s() expects parameter to be string, %s is given", function, get_type_or_class_name()); + return empty_value(); + } +} + +array &mixed::as_array(const char *function) { + switch (get_type()) { + case type::ARRAY: + return as_array(); + default: + php_warning("%s() expects parameter to be array, %s is given", function, get_type_or_class_name()); + return empty_value>(); + } +} + + +bool mixed::is_numeric() const { + switch (get_type()) { + case type::INTEGER: + [[fallthrough]]; + case type::FLOAT: + return true; + case type::STRING: + return as_string().is_numeric(); + default: + return false; + } +} + +bool mixed::is_scalar() const { + return get_type() != type::NUL && get_type() != type::ARRAY && get_type() != type::OBJECT; +} + + + +const char *mixed::get_type_c_str() const { + switch (get_type()) { + case type::NUL: + return "NULL"; + case type::BOOLEAN: + return "boolean"; + case type::INTEGER: + return "integer"; + case type::FLOAT: + return "double"; + case type::STRING: + return "string"; + case type::ARRAY: + return "array"; + case type::OBJECT: + return "object"; + default: + __builtin_unreachable(); + } +} + +const char *mixed::get_type_or_class_name() const { + switch (get_type()) { + case type::OBJECT: + return as_object()->get_class(); + default: + return get_type_c_str(); + } +} + +const string mixed::get_type_str() const { + return string(get_type_c_str()); +} + + +bool mixed::empty() const { + return !to_bool(); +} + +int64_t mixed::count() const { + switch (get_type()) { + case type::NUL: + php_warning("count(): Parameter is null, but an array expected"); + return 0; + case type::BOOLEAN: + case type::INTEGER: + case type::FLOAT: + case type::STRING: + php_warning("count(): Parameter is %s, but an array expected", get_type_c_str()); + return 1; + case type::ARRAY: + return as_array().count(); + case type::OBJECT: + php_warning("count(): Parameter is %s, but an array expected", get_type_or_class_name()); + return (as_object() ? 1 : 0); + default: + __builtin_unreachable(); + } +} + +mixed &mixed::operator=(const mixed &other) noexcept { + if (this != &other) { + destroy(); + copy_from(other); + } + return *this; +} + +mixed &mixed::operator=(mixed &&other) noexcept { + if (this != &other) { + destroy(); + copy_from(std::move(other)); + } + return *this; +} + + +int64_t mixed::compare(const mixed &rhs) const { + if (unlikely(is_string())) { + if (likely(rhs.is_string())) { + return compare_strings_php_order(as_string(), rhs.as_string()); + } else if (unlikely(rhs.is_null())) { + return as_string().empty() ? 0 : 1; + } + } else if (unlikely(rhs.is_string())) { + if (unlikely(is_null())) { + return rhs.as_string().empty() ? 0 : -1; + } + } + if (is_bool() || rhs.is_bool() || is_null() || rhs.is_null()) { + return three_way_comparison(to_bool(), rhs.to_bool()); + } + + if (unlikely(is_array() || rhs.is_array())) { + if (likely(is_array() && rhs.is_array())) { + return spaceship(as_array(), rhs.as_array()); + } + + php_warning("Unsupported operand types for operator < or <= (%s and %s)", get_type_c_str(), rhs.get_type_c_str()); + return is_array() ? 1 : -1; + } + if (unlikely(is_object() || rhs.is_object())) { + php_warning("Unsupported operand types for operator < or <= (%s and %s)", get_type_or_class_name(), get_type_or_class_name()); + return is_object() ? 1 : -1; + } + + return three_way_comparison(to_float(), rhs.to_float()); +} + + + + +mixed &mixed::operator[](int64_t int_key) { + if (unlikely (get_type() != type::ARRAY)) { + if (get_type() == type::STRING) { + php_warning("Writing to string by offset is't supported"); + return empty_value(); + } + + if (get_type() == type::NUL || (get_type() == type::BOOLEAN && !as_bool())) { + type_ = type::ARRAY; + new(&as_array()) array(); + } else { + php_warning("Cannot use a value \"%s\" of type %s as an array, index = %" PRIi64, to_string_without_warning(*this).c_str(), get_type_or_class_name(), int_key); + return empty_value(); + } + } + return as_array()[int_key]; +} + +mixed &mixed::operator[](const string &string_key) { + if (unlikely (get_type() != type::ARRAY)) { + if (get_type() == type::STRING) { + php_warning("Writing to string by offset is't supported"); + return empty_value(); + } + + if (get_type() == type::NUL || (get_type() == type::BOOLEAN && !as_bool())) { + type_ = type::ARRAY; + new(&as_array()) array(); + } else { + php_warning("Cannot use a value \"%s\" of type %s as an array, index = %s", to_string_without_warning(*this).c_str(), get_type_or_class_name(), string_key.c_str()); + return empty_value(); + } + } + + return as_array()[string_key]; +} + +mixed &mixed::operator[](tmp_string string_key) { + if (get_type() == type::ARRAY) { + return as_array()[string_key]; + } + return (*this)[materialize_tmp_string(string_key)]; +} + +mixed &mixed::operator[](const mixed &v) { + switch (v.get_type()) { + case type::NUL: + return (*this)[string()]; + case type::BOOLEAN: + return (*this)[v.as_bool()]; + case type::INTEGER: + return (*this)[v.as_int()]; + case type::FLOAT: + return (*this)[static_cast(v.as_double())]; + case type::STRING: + return (*this)[v.as_string()]; + case type::ARRAY: + php_warning("Illegal offset type %s", v.get_type_c_str()); + return (*this)[v.as_array().to_int()]; + case type::OBJECT: + php_warning("Illegal offset type %s", v.get_type_or_class_name()); + return (*this)[(as_object() ? 1 : 0)]; + default: + __builtin_unreachable(); + } +} + +mixed &mixed::operator[](double double_key) { + return (*this)[static_cast(double_key)]; +} + +mixed &mixed::operator[](const array::const_iterator &it) { + return as_array()[it]; +} + +mixed &mixed::operator[](const array::iterator &it) { + return as_array()[it]; +} + + +void mixed::set_value(int64_t int_key, const mixed &v) { + if (unlikely (get_type() != type::ARRAY)) { + if (get_type() == type::STRING) { + auto rhs_string = v.to_string(); + if (rhs_string.empty()) { + php_warning("Cannot assign an empty string to a string offset, index = %" PRIi64, int_key); + return; + } + + const char c = rhs_string[0]; + + if (int_key >= 0) { + const string::size_type l = as_string().size(); + if (int_key >= l) { + as_string().append(string::unsafe_cast_to_size_type(int_key + 1 - l), ' '); + } else { + as_string().make_not_shared(); + } + + as_string()[static_cast(int_key)] = c; + } else { + php_warning("%" PRIi64 " is illegal offset for string", int_key); + } + return; + } + + if (get_type() == type::NUL || (get_type() == type::BOOLEAN && !as_bool())) { + type_ = type::ARRAY; + new(&as_array()) array(); + } else { + php_warning("Cannot use a value \"%s\" of type %s as an array, index = %" PRIi64, to_string_without_warning(*this).c_str(), get_type_or_class_name(), int_key); + return; + } + } + return as_array().set_value(int_key, v); +} + +void mixed::set_value(const string &string_key, const mixed &v) { + if (unlikely (get_type() != type::ARRAY)) { + if (get_type() == type::STRING) { + int64_t int_val = 0; + if (!string_key.try_to_int(&int_val)) { + php_warning("\"%s\" is illegal offset for string", string_key.c_str()); + int_val = string_key.to_int(); + } + if (int_val < 0) { + return; + } + + char c = (v.to_string())[0]; + + const string::size_type l = as_string().size(); + if (int_val >= l) { + as_string().append(string::unsafe_cast_to_size_type(int_val + 1 - l), ' '); + } else { + as_string().make_not_shared(); + } + + as_string()[static_cast(int_val)] = c; + return; + } + + if (get_type() == type::NUL || (get_type() == type::BOOLEAN && !as_bool())) { + type_ = type::ARRAY; + new(&as_array()) array(); + } else { + php_warning("Cannot use a value \"%s\" of type %s as an array, index = %s", to_string_without_warning(*this).c_str(), get_type_or_class_name(), string_key.c_str()); + return; + } + } + + return as_array().set_value(string_key, v); +} + +void mixed::set_value(const string &string_key, const mixed &v, int64_t precomuted_hash) { + return get_type() == type::ARRAY ? as_array().set_value(string_key, v, precomuted_hash) : set_value(string_key, v); +} + +void mixed::set_value(tmp_string string_key, const mixed &v) { + // TODO: as with arrays, avoid eager tmp_string->string conversion + set_value(materialize_tmp_string(string_key), v); +} + +void mixed::set_value(const mixed &v, const mixed &value) { + switch (v.get_type()) { + case type::NUL: + return set_value(string(), value); + case type::BOOLEAN: + return set_value(static_cast(v.as_bool()), value); + case type::INTEGER: + return set_value(v.as_int(), value); + case type::FLOAT: + return set_value(static_cast(v.as_double()), value); + case type::STRING: + return set_value(v.as_string(), value); + case type::ARRAY: + php_warning("Illegal offset type array"); + return; + case type::OBJECT: + php_warning("Illegal offset type %s", v.get_type_or_class_name()); + return; + default: + __builtin_unreachable(); + } +} + +void mixed::set_value(double double_key, const mixed &value) { + set_value(static_cast(double_key), value); +} + +void mixed::set_value(const array::const_iterator &it) { + return as_array().set_value(it); +} + +void mixed::set_value(const array::iterator &it) { + return as_array().set_value(it); +} + + +const mixed mixed::get_value(int64_t int_key) const { + if (unlikely (get_type() != type::ARRAY)) { + if (get_type() == type::STRING) { + if (int_key < 0 || int_key >= as_string().size()) { + return string(); + } + return string(1, as_string()[static_cast(int_key)]); + } + + if (get_type() != type::NUL && (get_type() != type::BOOLEAN || as_bool())) { + php_warning("Cannot use a value \"%s\" of type %s as an array, index = %" PRIi64, to_string_without_warning(*this).c_str(), get_type_or_class_name(), int_key); + } + return mixed(); + } + + return as_array().get_value(int_key); +} + +const mixed mixed::get_value(const string &string_key) const { + if (unlikely (get_type() != type::ARRAY)) { + if (get_type() == type::STRING) { + int64_t int_val = 0; + if (!string_key.try_to_int(&int_val)) { + php_warning("\"%s\" is illegal offset for string", string_key.c_str()); + int_val = string_key.to_int(); + } + if (int_val < 0 || int_val >= as_string().size()) { + return string(); + } + return string(1, as_string()[static_cast(int_val)]); + } + + if (get_type() != type::NUL && (get_type() != type::BOOLEAN || as_bool())) { + php_warning("Cannot use a value \"%s\" of type %s as an array, index = %s", to_string_without_warning(*this).c_str(), get_type_or_class_name(), string_key.c_str()); + } + return mixed(); + } + + return as_array().get_value(string_key); +} + +const mixed mixed::get_value(const string &string_key, int64_t precomuted_hash) const { + return get_type() == type::ARRAY ? as_array().get_value(string_key, precomuted_hash) : get_value(string_key); +} + +const mixed mixed::get_value(tmp_string string_key) const { + if (get_type() == type::ARRAY) { + // fast path: arrays can handle a tmp_string lookup efficiently + return as_array().get_value(string_key); + } + // TODO: make other lookups efficient too (like string from numeric tmp_string)? + return get_value(materialize_tmp_string(string_key)); +} + +const mixed mixed::get_value(const mixed &v) const { + switch (v.get_type()) { + case type::NUL: + return get_value(string()); + case type::BOOLEAN: + return get_value(static_cast(v.as_bool())); + case type::INTEGER: + return get_value(v.as_int()); + case type::FLOAT: + return get_value(static_cast(v.as_double())); + case type::STRING: + return get_value(v.as_string()); + case type::ARRAY: + php_warning("Illegal offset type %s", v.get_type_c_str()); + return mixed(); + case type::OBJECT: + php_warning("Illegal offset type %s", v.get_type_or_class_name()); + return mixed(); + default: + __builtin_unreachable(); + } +} + +const mixed mixed::get_value(double double_key) const { + return get_value(static_cast(double_key)); +} + +const mixed mixed::get_value(const array::const_iterator &it) const { + return as_array().get_value(it); +} + +const mixed mixed::get_value(const array::iterator &it) const { + return as_array().get_value(it); +} + + +void mixed::push_back(const mixed &v) { + if (unlikely (get_type() != type::ARRAY)) { + if (get_type() == type::NUL || (get_type() == type::BOOLEAN && !as_bool())) { + type_ = type::ARRAY; + new(&as_array()) array(); + } else { + php_warning("[] operator not supported for type %s", get_type_or_class_name()); + return; + } + } + + return as_array().push_back(v); +} + +const mixed mixed::push_back_return(const mixed &v) { + if (unlikely (get_type() != type::ARRAY)) { + if (get_type() == type::NUL || (get_type() == type::BOOLEAN && !as_bool())) { + type_ = type::ARRAY; + new(&as_array()) array(); + } else { + php_warning("[] operator not supported for type %s", get_type_or_class_name()); + return empty_value(); + } + } + + return as_array().push_back_return(v); +} + +bool mixed::isset(int64_t int_key) const { + if (unlikely (get_type() != type::ARRAY)) { + if (get_type() == type::STRING) { + int_key = as_string().get_correct_index(int_key); + return as_string().isset(int_key); + } + + if (get_type() != type::NUL && (get_type() != type::BOOLEAN || as_bool())) { + php_warning("Cannot use variable of type %s as array in isset", get_type_or_class_name()); + } + return false; + } + + return as_array().isset(int_key); +} + + +bool mixed::isset(const mixed &v) const { + switch (v.get_type()) { + case type::NUL: + return isset(string()); + case type::BOOLEAN: + return isset(static_cast(v.as_bool())); + case type::INTEGER: + return isset(v.as_int()); + case type::FLOAT: + return isset(static_cast(v.as_double())); + case type::STRING: + return isset(v.as_string()); + case type::ARRAY: + php_warning("Illegal offset type array"); + return false; + case type::OBJECT: + php_warning("Illegal offset type %s", get_type_or_class_name()); + return false; + default: + __builtin_unreachable(); + } +} + +bool mixed::isset(double double_key) const { + return isset(static_cast(double_key)); +} + +void mixed::unset(int64_t int_key) { + if (unlikely (get_type() != type::ARRAY)) { + if (get_type() != type::NUL && (get_type() != type::BOOLEAN || as_bool())) { + php_warning("Cannot use variable of type %s as array in unset", get_type_or_class_name()); + } + return; + } + + as_array().unset(int_key); +} + + +void mixed::unset(const mixed &v) { + if (unlikely (get_type() != type::ARRAY)) { + if (get_type() != type::NUL && (get_type() != type::BOOLEAN || as_bool())) { + php_warning("Cannot use variable of type %s as array in unset", get_type_or_class_name()); + } + return; + } + + switch (v.get_type()) { + case type::NUL: + as_array().unset(string()); + break; + case type::BOOLEAN: + as_array().unset(static_cast(v.as_bool())); + break; + case type::INTEGER: + as_array().unset(v.as_int()); + break; + case type::FLOAT: + as_array().unset(static_cast(v.as_double())); + break; + case type::STRING: + as_array().unset(v.as_string()); + break; + case type::ARRAY: + php_warning("Illegal offset type array"); + break; + case type::OBJECT: + php_warning("Illegal offset type %s", v.get_type_or_class_name()); + break; + default: + __builtin_unreachable(); + } +} + +void mixed::unset(double double_key) { + unset(static_cast(double_key)); +} + +array::const_iterator mixed::begin() const { + if (likely (get_type() == type::ARRAY)) { + return as_array().begin(); + } + php_warning("Invalid argument supplied for foreach(), %s (string representation - \"%s\") is given", get_type_or_class_name(), to_string_without_warning(*this).c_str()); + return array::const_iterator(); +} + +array::const_iterator mixed::end() const { + if (likely (get_type() == type::ARRAY)) { + return as_array().end(); + } + return array::const_iterator(); +} + + +array::iterator mixed::begin() { + if (likely (get_type() == type::ARRAY)) { + return as_array().begin(); + } + php_warning("Invalid argument supplied for foreach(), %s (string representation - \"%s\") is given", get_type_or_class_name(), to_string_without_warning(*this).c_str()); + return array::iterator(); +} + +array::iterator mixed::end() { + if (likely (get_type() == type::ARRAY)) { + return as_array().end(); + } + return array::iterator(); +} + + +int64_t mixed::get_reference_counter() const { + switch (get_type()) { + case type::NUL: + return -1; + case type::BOOLEAN: + return -2; + case type::INTEGER: + return -3; + case type::FLOAT: + return -4; + case type::STRING: + return as_string().get_reference_counter(); + case type::ARRAY: + return as_array().get_reference_counter(); + case type::OBJECT: + return as_object()->get_refcnt(); + default: + __builtin_unreachable(); + } +} + +void mixed::set_reference_counter_to(ExtraRefCnt ref_cnt_value) noexcept { + switch (get_type()) { + case type::NUL: + case type::BOOLEAN: + case type::INTEGER: + case type::FLOAT: + return; + case type::STRING: + return as_string().set_reference_counter_to(ref_cnt_value); + case type::ARRAY: + return as_array().set_reference_counter_to(ref_cnt_value); + case type::OBJECT: + return as_object()->set_refcnt(ref_cnt_value); + default: + __builtin_unreachable(); + } +} + +bool mixed::is_reference_counter(ExtraRefCnt ref_cnt_value) const noexcept { + switch (get_type()) { + case type::NUL: + case type::BOOLEAN: + case type::INTEGER: + case type::FLOAT: + return false; + case type::STRING: + return as_string().is_reference_counter(ref_cnt_value); + case type::ARRAY: + return as_array().is_reference_counter(ref_cnt_value); + case type::OBJECT: + return static_cast(as_object()->get_refcnt()) == ref_cnt_value; + default: + __builtin_unreachable(); + } +} + +void mixed::force_destroy(ExtraRefCnt expected_ref_cnt) noexcept { + switch (get_type()) { + case type::STRING: + as_string().force_destroy(expected_ref_cnt); + break; + case type::ARRAY: + as_array().force_destroy(expected_ref_cnt); + break; + case type::OBJECT: + php_warning("Objects (%s) are not supported in confdata", get_type_or_class_name()); + break; + default: + __builtin_unreachable(); + } +} + +size_t mixed::estimate_memory_usage() const { + switch (get_type()) { + case type::NUL: + case type::BOOLEAN: + case type::INTEGER: + case type::FLOAT: + return 0; + case type::STRING: + return as_string().estimate_memory_usage(); + case type::ARRAY: + return as_array().estimate_memory_usage(); + case type::OBJECT: + php_warning("Objects (%s) are not supported in confdata", get_type_or_class_name()); + return 0; + default: + __builtin_unreachable(); + } +} + +namespace impl_ { + +template +mixed do_math_op_on_vars(const mixed &lhs, const mixed &rhs, MathOperation &&math_op) { + if (likely(lhs.is_int() && rhs.is_int())) { + return math_op(lhs.as_int(), rhs.as_int()); + } + + const mixed arg1 = lhs.to_numeric(); + const mixed arg2 = rhs.to_numeric(); + + if (arg1.is_int()) { + if (arg2.is_int()) { + return math_op(arg1.as_int(), arg2.as_int()); + } else { + return math_op(static_cast(arg1.as_int()), arg2.as_double()); + } + } else { + if (arg2.is_int()) { + return math_op(arg1.as_double(), static_cast(arg2.as_int())); + } else { + return math_op(arg1.as_double(), arg2.as_double()); + } + } +} + +} // namespace impl_ + +mixed operator+(const mixed &lhs, const mixed &rhs) { + if (lhs.is_array() && rhs.is_array()) { + return lhs.as_array() + rhs.as_array(); + } + + return impl_::do_math_op_on_vars(lhs, rhs, [](const auto &arg1, const auto &arg2) { return arg1 + arg2; }); +} + +mixed operator-(const mixed &lhs, const mixed &rhs) { + return impl_::do_math_op_on_vars(lhs, rhs, [](const auto &arg1, const auto &arg2) { return arg1 - arg2; }); +} + +mixed operator*(const mixed &lhs, const mixed &rhs) { + return impl_::do_math_op_on_vars(lhs, rhs, [](const auto &arg1, const auto &arg2) { return arg1 * arg2; }); +} + +mixed operator-(const string &lhs) { + mixed arg1 = lhs.to_numeric(); + + if (arg1.is_int()) { + arg1.as_int() = -arg1.as_int(); + } else { + arg1.as_double() = -arg1.as_double(); + } + return arg1; +} + +mixed operator+(const string &lhs) { + return lhs.to_numeric(); +} + +int64_t operator&(const mixed &lhs, const mixed &rhs) { + return lhs.to_int() & rhs.to_int(); +} + +int64_t operator|(const mixed &lhs, const mixed &rhs) { + return lhs.to_int() | rhs.to_int(); +} + +int64_t operator^(const mixed &lhs, const mixed &rhs) { + return lhs.to_int() ^ rhs.to_int(); +} + +int64_t operator<<(const mixed &lhs, const mixed &rhs) { + return lhs.to_int() << rhs.to_int(); +} + +int64_t operator>>(const mixed &lhs, const mixed &rhs) { + return lhs.to_int() >> rhs.to_int(); +} + + +bool operator<(const mixed &lhs, const mixed &rhs) { + const auto res = lhs.compare(rhs) < 0; + + if (rhs.is_string()) { + if (lhs.is_int()) { + return less_number_string_as_php8(res, lhs.to_int(), rhs.to_string()); + } else if (lhs.is_float()) { + return less_number_string_as_php8(res, lhs.to_float(), rhs.to_string()); + } + } else if (lhs.is_string()) { + if (rhs.is_int()) { + return less_string_number_as_php8(res, lhs.to_string(), rhs.to_int()); + } else if (rhs.is_float()) { + return less_string_number_as_php8(res, lhs.to_string(), rhs.to_float()); + } + } + + return res; +} + +bool operator<=(const mixed &lhs, const mixed &rhs) { + return !(rhs < lhs); +} + +string_buffer &operator<<(string_buffer &sb, const mixed &v) { + switch (v.get_type()) { + case mixed::type::NUL: + return sb; + case mixed::type::BOOLEAN: + return sb << v.as_bool(); + case mixed::type::INTEGER: + return sb << v.as_int(); + case mixed::type::FLOAT: + return sb << string(v.as_double()); + case mixed::type::STRING: + return sb << v.as_string(); + case mixed::type::ARRAY: + php_warning("Conversion from array to string"); + return sb.append("Array", 5); + case mixed::type::OBJECT: { + const char *s = v.get_type_or_class_name(); + php_warning("Conversion from %s to string", s); + return sb.append(s, strlen(s)); + } + default: + __builtin_unreachable(); + } +} + void mixed::destroy() noexcept { switch (get_type()) { case type::STRING: diff --git a/runtime-core/core-types/definition/mixed.inl b/runtime-core/core-types/definition/mixed.inl index bb6b6d42a8..4d667dfe82 100644 --- a/runtime-core/core-types/definition/mixed.inl +++ b/runtime-core/core-types/definition/mixed.inl @@ -16,1541 +16,79 @@ static_assert(vk::all_of_equal(sizeof(string), sizeof(double), sizeof(array)), "sizeof of array, string and double must be equal"); -void mixed::copy_from(const mixed &other) { - switch (other.get_type()) { - case type::STRING: - new(&as_string()) string(other.as_string()); - break; - case type::ARRAY: - new(&as_array()) array(other.as_array()); - break; - case type::OBJECT: { - new (&storage_) vk::intrusive_ptr(*reinterpret_cast *>(&other.storage_)); - break; - } - default: - storage_ = other.storage_; - } - type_ = other.get_type(); -} - -void mixed::copy_from(mixed &&other) { - switch (other.get_type()) { - case type::STRING: - new(&as_string()) string(std::move(other.as_string())); - break; - case type::ARRAY: - new(&as_array()) array(std::move(other.as_array())); - break; - case type::OBJECT: { - storage_ = other.storage_; - other.storage_ = 0; - break; - } - default: - storage_ = other.storage_; - } - type_ = other.get_type(); -} - -template -void mixed::init_from(T &&v) { - if constexpr (is_class_instance_v>) { - static_assert(sizeof(storage_) >= sizeof(vk::intrusive_ptr)); - auto ptr_to_obj = new(&storage_) vk::intrusive_ptr(dynamic_cast(v.get())); - if (unlikely(!ptr_to_obj)) { - php_error("Internal error. Trying to set invalid object to mixed"); - } - type_ = type::OBJECT; - } else { - auto type_and_value_ref = get_type_and_value_ptr(v); - type_ = type_and_value_ref.first; - auto *value_ptr = type_and_value_ref.second; - using ValueType = std::decay_t; - new(value_ptr) ValueType(std::forward(v)); - } -} - -template -mixed &mixed::assign_from(T &&v) { - if constexpr(is_class_instance_v>) { - static_assert(std::is_base_of_v::ClassType>); - destroy(); - init_from(std::forward(v)); - } else { - auto type_and_value_ref = get_type_and_value_ptr(v); - if (get_type() == type_and_value_ref.first) { - *type_and_value_ref.second = std::forward(v); - } else { - destroy(); - init_from(std::forward(v)); - } - } - - return *this; -} - -template -mixed::mixed(T &&v) noexcept { - init_from(std::forward(v)); -} - -mixed::mixed(const Unknown &u __attribute__((unused))) noexcept { - php_assert ("Unknown used!!!" && 0); -} - -mixed::mixed(const char *s, string::size_type len) noexcept : - mixed(string{s, len}){ -} - -template -mixed::mixed(const Optional &v) noexcept { - auto init_from_lambda = [this](const auto &v) { this->init_from(v); }; - call_fun_on_optional_value(init_from_lambda, v); -} - -template -mixed::mixed(Optional &&v) noexcept { - auto init_from_lambda = [this](auto &&v) { this->init_from(std::move(v)); }; - call_fun_on_optional_value(init_from_lambda, std::move(v)); -} - -mixed::mixed(const mixed &v) noexcept { - copy_from(v); -} - -mixed::mixed(mixed &&v) noexcept { - copy_from(std::move(v)); -} - -mixed &mixed::operator=(const mixed &other) noexcept { - if (this != &other) { - destroy(); - copy_from(other); - } - return *this; -} - -mixed &mixed::operator=(mixed &&other) noexcept { - if (this != &other) { - destroy(); - copy_from(std::move(other)); - } - return *this; -} - -template -mixed &mixed::operator=(T &&v) noexcept { - return assign_from(std::forward(v)); -} - -template -mixed &mixed::operator=(const Optional &v) noexcept { - auto assign_from_lambda = [this](const auto &v) -> mixed& { return this->assign_from(v); }; - return call_fun_on_optional_value(assign_from_lambda, v); -} - -template -mixed &mixed::operator=(Optional &&v) noexcept { - auto assign_from_lambda = [this](auto &&v) -> mixed& { return this->assign_from(std::move(v)); }; - return call_fun_on_optional_value(assign_from_lambda, std::move(v)); -} - -mixed &mixed::assign(const char *other, string::size_type len) { - if (get_type() == type::STRING) { - as_string().assign(other, len); - } else { - destroy(); - type_ = type::STRING; - new(&as_string()) string(other, len); - } - return *this; -} - -const mixed mixed::operator-() const { - mixed arg1 = to_numeric(); - - if (arg1.get_type() == type::INTEGER) { - arg1.as_int() = -arg1.as_int(); - } else { - arg1.as_double() = -arg1.as_double(); - } - return arg1; -} - -const mixed mixed::operator+() const { - return to_numeric(); -} - - -int64_t mixed::operator~() const { - return ~to_int(); -} - - -mixed &mixed::operator+=(const mixed &other) { - if (likely (get_type() == type::INTEGER && other.get_type() == type::INTEGER)) { - as_int() += other.as_int(); - return *this; - } - - if (unlikely (get_type() == type::ARRAY || other.get_type() == type::ARRAY)) { - if (get_type() == type::ARRAY && other.get_type() == type::ARRAY) { - as_array() += other.as_array(); - } else { - php_warning("Unsupported operand types for operator += (%s and %s)", get_type_c_str(), other.get_type_c_str()); - } - return *this; - } - - convert_to_numeric(); - const mixed arg2 = other.to_numeric(); - - if (get_type() == type::INTEGER) { - if (arg2.get_type() == type::INTEGER) { - as_int() += arg2.as_int(); - } else { - type_ = type::FLOAT; - as_double() = static_cast(as_int()) + arg2.as_double(); - } - } else { - if (arg2.get_type() == type::INTEGER) { - as_double() += static_cast(arg2.as_int()); - } else { - as_double() += arg2.as_double(); - } - } - - return *this; -} - -mixed &mixed::operator-=(const mixed &other) { - if (likely (get_type() == type::INTEGER && other.get_type() == type::INTEGER)) { - as_int() -= other.as_int(); - return *this; - } - - convert_to_numeric(); - const mixed arg2 = other.to_numeric(); - - if (get_type() == type::INTEGER) { - if (arg2.get_type() == type::INTEGER) { - as_int() -= arg2.as_int(); - } else { - type_ = type::FLOAT; - as_double() = static_cast(as_int()) - arg2.as_double(); - } - } else { - if (arg2.get_type() == type::INTEGER) { - as_double() -= static_cast(arg2.as_int()); - } else { - as_double() -= arg2.as_double(); - } - } - - return *this; -} - -mixed &mixed::operator*=(const mixed &other) { - if (likely (get_type() == type::INTEGER && other.get_type() == type::INTEGER)) { - as_int() *= other.as_int(); - return *this; - } - - convert_to_numeric(); - const mixed arg2 = other.to_numeric(); - - if (get_type() == type::INTEGER) { - if (arg2.get_type() == type::INTEGER) { - as_int() *= arg2.as_int(); - } else { - type_ = type::FLOAT; - as_double() = static_cast(as_int()) * arg2.as_double(); - } - } else { - if (arg2.get_type() == type::INTEGER) { - as_double() *= static_cast(arg2.as_int()); - } else { - as_double() *= arg2.as_double(); - } - } - - return *this; -} - -mixed &mixed::operator/=(const mixed &other) { - if (likely (get_type() == type::INTEGER && other.get_type() == type::INTEGER)) { - if (as_int() % other.as_int() == 0) { - as_int() /= other.as_int(); - } else { - type_ = type::FLOAT; - as_double() = static_cast(as_int()) / static_cast(other.as_int()); - } - return *this; - } - - convert_to_numeric(); - const mixed arg2 = other.to_numeric(); - - if (arg2.get_type() == type::INTEGER) { - if (arg2.as_int() == 0) { - php_warning("Integer division by zero"); - type_ = type::BOOLEAN; - as_bool() = false; - return *this; - } - - if (get_type() == type::INTEGER) { - if (as_int() % arg2.as_int() == 0) { - as_int() /= arg2.as_int(); - } else { - type_ = type::FLOAT; - as_double() = static_cast(as_int()) / static_cast(other.as_int()); - } - } else { - as_double() /= static_cast(arg2.as_int()); - } - } else { - if (arg2.as_double() == 0) { - php_warning("Float division by zero"); - type_ = type::BOOLEAN; - as_bool() = false; - return *this; - } - - if (get_type() == type::INTEGER) { - type_ = type::FLOAT; - as_double() = static_cast(as_int()) / arg2.as_double(); - } else { - as_double() /= arg2.as_double(); - } - } - - return *this; -} - -mixed &mixed::operator%=(const mixed &other) { - int64_t div = other.to_int(); - if (div == 0) { - php_warning("Modulo by zero"); - *this = false; - return *this; - } - convert_to_int(); - as_int() %= div; - - return *this; -} - - -mixed &mixed::operator&=(const mixed &other) { - convert_to_int(); - as_int() &= other.to_int(); - return *this; -} - -mixed &mixed::operator|=(const mixed &other) { - convert_to_int(); - as_int() |= other.to_int(); - return *this; -} - -mixed &mixed::operator^=(const mixed &other) { - convert_to_int(); - as_int() ^= other.to_int(); - return *this; -} - -mixed &mixed::operator<<=(const mixed &other) { - convert_to_int(); - as_int() <<= other.to_int(); - return *this; -} - -mixed &mixed::operator>>=(const mixed &other) { - convert_to_int(); - as_int() >>= other.to_int(); - return *this; -} - - -mixed &mixed::operator++() { - switch (get_type()) { - case type::NUL: - type_ = type::INTEGER; - as_int() = 1; - return *this; - case type::BOOLEAN: - php_warning("Can't apply operator ++ to boolean"); - return *this; - case type::INTEGER: - ++as_int(); - return *this; - case type::FLOAT: - as_double() += 1; - return *this; - case type::STRING: - *this = as_string().to_numeric(); - return ++(*this); - case type::ARRAY: - php_warning("Can't apply operator ++ to array"); - return *this; - case type::OBJECT: - php_warning("Can't apply operator ++ to %s", get_type_or_class_name()); - return *this; - default: - __builtin_unreachable(); - } -} - -const mixed mixed::operator++(int) { - switch (get_type()) { - case type::NUL: - type_ = type::INTEGER; - as_int() = 1; - return mixed(); - case type::BOOLEAN: - php_warning("Can't apply operator ++ to boolean"); - return as_bool(); - case type::INTEGER: { - mixed res(as_int()); - ++as_int(); - return res; - } - case type::FLOAT: { - mixed res(as_double()); - as_double() += 1; - return res; - } - case type::STRING: { - mixed res(as_string()); - *this = as_string().to_numeric(); - (*this)++; - return res; - } - case type::ARRAY: - php_warning("Can't apply operator ++ to array"); - return as_array(); - case type::OBJECT: - php_warning("Can't apply operator ++ to %s", get_type_or_class_name()); - return *this; - default: - __builtin_unreachable(); - } -} - -mixed &mixed::operator--() { - if (likely (get_type() == type::INTEGER)) { - --as_int(); - return *this; - } - - switch (get_type()) { - case type::NUL: - php_warning("Can't apply operator -- to null"); - return *this; - case type::BOOLEAN: - php_warning("Can't apply operator -- to boolean"); - return *this; - case type::INTEGER: - --as_int(); - return *this; - case type::FLOAT: - as_double() -= 1; - return *this; - case type::STRING: - *this = as_string().to_numeric(); - return --(*this); - case type::ARRAY: - php_warning("Can't apply operator -- to array"); - return *this; - case type::OBJECT: - php_warning("Can't apply operator -- to %s", get_type_or_class_name()); - return *this; - default: - __builtin_unreachable(); - } -} - -const mixed mixed::operator--(int) { - if (likely (get_type() == type::INTEGER)) { - mixed res(as_int()); - --as_int(); - return res; - } - - switch (get_type()) { - case type::NUL: - php_warning("Can't apply operator -- to null"); - return mixed(); - case type::BOOLEAN: - php_warning("Can't apply operator -- to boolean"); - return as_bool(); - case type::INTEGER: { - mixed res(as_int()); - --as_int(); - return res; - } - case type::FLOAT: { - mixed res(as_double()); - as_double() -= 1; - return res; - } - case type::STRING: { - mixed res(as_string()); - *this = as_string().to_numeric(); - (*this)--; - return res; - } - case type::ARRAY: - php_warning("Can't apply operator -- to array"); - return as_array(); - case type::OBJECT: - php_warning("Can't apply operator -- to %s", get_type_or_class_name()); - return *this; - default: - __builtin_unreachable(); - } -} - - -bool mixed::operator!() const { - return !to_bool(); -} - - -mixed &mixed::append(const string &v) { - if (unlikely (get_type() != type::STRING)) { - convert_to_string(); - } - as_string().append(v); - return *this; -} - -mixed &mixed::append(tmp_string v) { - if (unlikely (get_type() != type::STRING)) { - convert_to_string(); - } - as_string().append(v.data, v.size); - return *this; -} - -const mixed mixed::to_numeric() const { - switch (get_type()) { - case type::NUL: - return 0; - case type::BOOLEAN: - return (as_bool() ? 1 : 0); - case type::INTEGER: - return as_int(); - case type::FLOAT: - return as_double(); - case type::STRING: - return as_string().to_numeric(); - case type::ARRAY: - php_warning("Wrong conversion from array to number"); - return as_array().to_int(); - case type::OBJECT: - php_warning("Wrong conversion from %s to number", get_type_or_class_name()); - return (as_object() ? 1 : 0); - default: - __builtin_unreachable(); - } -} - - -bool mixed::to_bool() const { - switch (get_type()) { - case type::NUL: - return false; - case type::BOOLEAN: - return as_bool(); - case type::INTEGER: - return (bool)as_int(); - case type::FLOAT: - return (bool)as_double(); - case type::STRING: - return as_string().to_bool(); - case type::ARRAY: - return !as_array().empty(); - case type::OBJECT: - return (bool)as_object(); - default: - __builtin_unreachable(); - } -} - -int64_t mixed::to_int() const { - switch (get_type()) { - case type::NUL: - return 0; - case type::BOOLEAN: - return static_cast(as_bool()); - case type::INTEGER: - return as_int(); - case type::FLOAT: - return static_cast(as_double()); - case type::STRING: - return as_string().to_int(); - case type::ARRAY: - php_warning("Wrong conversion from array to int"); - return as_array().to_int(); - case type::OBJECT: - php_warning("Wrong conversion from %s to int", get_type_or_class_name()); - return (as_object() ? 1 : 0); - default: - __builtin_unreachable(); - } -} - -double mixed::to_float() const { - switch (get_type()) { - case type::NUL: - return 0.0; - case type::BOOLEAN: - return (as_bool() ? 1.0 : 0.0); - case type::INTEGER: - return (double)as_int(); - case type::FLOAT: - return as_double(); - case type::STRING: - return as_string().to_float(); - case type::ARRAY: - php_warning("Wrong conversion from array to float"); - return as_array().to_float(); - case type::OBJECT: { - php_warning("Wrong conversion from %s to float", get_type_or_class_name()); - return (as_object() ? 1.0 : 0.0); - } - default: - __builtin_unreachable(); - } -} - -static string to_string_without_warning(const mixed &m) { - switch (m.get_type()) { - case mixed::type::NUL: - return string(); - case mixed::type::BOOLEAN: - return (m.as_bool() ? string("1", 1) : string()); - case mixed::type::INTEGER: - return string(m.as_int()); - case mixed::type::FLOAT: - return string(m.as_double()); - case mixed::type::STRING: - return m.as_string(); - case mixed::type::ARRAY: - return string("Array", 5); - case mixed::type::OBJECT: { - const char *s = m.get_type_or_class_name(); - return string(s, strlen(s)); - } - default: - __builtin_unreachable(); - } -} - -const string mixed::to_string() const { - switch (get_type()) { - case mixed::type::NUL: - case mixed::type::BOOLEAN: - case mixed::type::INTEGER: - case mixed::type::FLOAT: - case mixed::type::STRING: - break; - case type::ARRAY: - php_warning("Conversion from array to string"); - break; - case type::OBJECT: { - php_warning("Wrong conversion from %s to string", get_type_or_class_name()); - break; - } - default: - __builtin_unreachable(); - } - return to_string_without_warning(*this); -} - -const array mixed::to_array() const { - switch (get_type()) { - case type::NUL: - return array(); - case type::BOOLEAN: - case type::INTEGER: - case type::FLOAT: - case type::STRING: - case type::OBJECT: { - array res(array_size(1, true)); - res.push_back(*this); - return res; - } - case type::ARRAY: - return as_array(); - default: - __builtin_unreachable(); - } -} - -bool &mixed::as_bool() { return *reinterpret_cast(&storage_); } -const bool &mixed::as_bool() const { return *reinterpret_cast(&storage_); } - -int64_t &mixed::as_int() { return *reinterpret_cast(&storage_); } -const int64_t &mixed::as_int() const { return *reinterpret_cast(&storage_); } - -double &mixed::as_double() { return *reinterpret_cast(&storage_); } -const double &mixed::as_double() const { return *reinterpret_cast(&storage_); } - -string &mixed::as_string() { return *reinterpret_cast(&storage_); } -const string &mixed::as_string() const { return *reinterpret_cast(&storage_); } - -array &mixed::as_array() { return *reinterpret_cast *>(&storage_); } -const array &mixed::as_array() const { return *reinterpret_cast *>(&storage_); } - -vk::intrusive_ptr mixed::as_object() { return *reinterpret_cast*>(&storage_); } -const vk::intrusive_ptr mixed::as_object() const { return *reinterpret_cast*>(&storage_); } - -int64_t mixed::safe_to_int() const { - switch (get_type()) { - case type::NUL: - return 0; - case type::BOOLEAN: - return static_cast(as_bool()); - case type::INTEGER: - return as_int(); - case type::FLOAT: { - constexpr auto max_int = static_cast(static_cast(std::numeric_limits::max()) + 1); - if (fabs(as_double()) > max_int) { - php_warning("Wrong conversion from double %.6lf to int", as_double()); - } - return static_cast(as_double()); - } - case type::STRING: - return as_string().safe_to_int(); - case type::ARRAY: - php_warning("Wrong conversion from array to int"); - return as_array().to_int(); - case type::OBJECT: { - php_warning("Wrong conversion from %s to int", get_type_or_class_name()); - return (as_object() ? 1 : 0); - } - default: - __builtin_unreachable(); - } -} - - -void mixed::convert_to_numeric() { - switch (get_type()) { - case type::NUL: - type_ = type::INTEGER; - as_int() = 0; - return; - case type::BOOLEAN: - type_ = type::INTEGER; - as_int() = as_bool(); - return; - case type::INTEGER: - case type::FLOAT: - return; - case type::STRING: - *this = as_string().to_numeric(); - return; - case type::ARRAY: { - php_warning("Wrong conversion from array to number"); - const int64_t int_val = as_array().to_int(); - as_array().~array(); - type_ = type::INTEGER; - as_int() = int_val; - return; - } - case type::OBJECT: { - php_warning("Wrong conversion from %s to number", get_type_or_class_name()); - const int64_t int_val = (as_object() ? 1 : 0); - destroy(); - type_ = type::INTEGER; - as_int() = int_val; - return; - } - default: - __builtin_unreachable(); - } -} - -void mixed::convert_to_bool() { - switch (get_type()) { - case type::NUL: - type_ = type::BOOLEAN; - as_bool() = 0; - return; - case type::BOOLEAN: - return; - case type::INTEGER: - type_ = type::BOOLEAN; - as_bool() = (bool)as_int(); - return; - case type::FLOAT: - type_ = type::BOOLEAN; - as_bool() = (bool)as_double(); - return; - case type::STRING: { - const bool bool_val = as_string().to_bool(); - as_string().~string(); - type_ = type::BOOLEAN; - as_bool() = bool_val; - return; - } - case type::ARRAY: { - const bool bool_val = as_array().to_bool(); - as_array().~array(); - type_ = type::BOOLEAN; - as_bool() = bool_val; - return; - } - case type::OBJECT: { - const bool bool_val = static_cast(as_object()); - destroy(); - type_ = type::BOOLEAN; - as_bool() = bool_val; - return; - } - default: - __builtin_unreachable(); - } -} - -void mixed::convert_to_int() { - switch (get_type()) { - case type::NUL: - type_ = type::INTEGER; - as_int() = 0; - return; - case type::BOOLEAN: - type_ = type::INTEGER; - as_int() = as_bool(); - return; - case type::INTEGER: - return; - case type::FLOAT: - type_ = type::INTEGER; - as_int() = static_cast(as_double()); - return; - case type::STRING: { - const int64_t int_val = as_string().to_int(); - as_string().~string(); - type_ = type::INTEGER; - as_int() = int_val; - return; - } - case type::ARRAY: { - php_warning("Wrong conversion from array to int"); - const int64_t int_val = as_array().to_int(); - as_array().~array(); - type_ = type::INTEGER; - as_int() = int_val; - return; - } - case type::OBJECT: { - php_warning("Wrong conversion from %s to int", get_type_or_class_name()); - const int64_t int_val = (as_object() ? 1 : 0); - destroy(); - type_ = type::INTEGER; - as_int() = int_val; - return; - } - default: - __builtin_unreachable(); - } -} - -void mixed::convert_to_float() { - switch (get_type()) { - case type::NUL: - type_ = type::FLOAT; - as_double() = 0.0; - return; - case type::BOOLEAN: - type_ = type::FLOAT; - as_double() = as_bool(); - return; - case type::INTEGER: - type_ = type::FLOAT; - as_double() = (double)as_int(); - return; - case type::FLOAT: - return; - case type::STRING: { - const double float_val = as_string().to_float(); - as_string().~string(); - type_ = type::FLOAT; - as_double() = float_val; - return; - } - case type::ARRAY: { - php_warning("Wrong conversion from array to float"); - const double float_val = as_array().to_float(); - as_array().~array(); - type_ = type::FLOAT; - as_double() = float_val; - return; - } - case type::OBJECT: { - php_warning("Wrong conversion from %s to float", get_type_or_class_name()); - const double float_val = (as_object() ? 1.0 : 0.0); - destroy(); - type_ = type::FLOAT; - as_double() = float_val; - return; - } - default: - __builtin_unreachable(); - } -} - -void mixed::convert_to_string() { - switch (get_type()) { - case type::NUL: - type_ = type::STRING; - new(&as_string()) string(); - return; - case type::BOOLEAN: - type_ = type::STRING; - if (as_bool()) { - new(&as_string()) string("1", 1); - } else { - new(&as_string()) string(); - } - return; - case type::INTEGER: - type_ = type::STRING; - new(&as_string()) string(as_int()); - return; - case type::FLOAT: - type_ = type::STRING; - new(&as_string()) string(as_double()); - return; - case type::STRING: - return; - case type::ARRAY: - php_warning("Converting from array to string"); - as_array().~array(); - type_ = type::STRING; - new(&as_string()) string("Array", 5); - return; - case type::OBJECT: { - php_warning("Wrong conversion from %s to string", get_type_or_class_name()); - string s = string(get_type_or_class_name(), strlen(get_type_or_class_name())); - destroy(); - type_ = type::STRING; - new (&as_string()) string(std::move(s)); - return; - } - default: - __builtin_unreachable(); - } -} - -const bool &mixed::as_bool(const char *function) const { - switch (get_type()) { - case type::BOOLEAN: - return as_bool(); - default: - php_warning("%s() expects parameter to be boolean, %s is given", function, get_type_or_class_name()); - return empty_value(); - } -} - -const int64_t &mixed::as_int(const char *function) const { - switch (get_type()) { - case type::INTEGER: - return as_int(); - default: - php_warning("%s() expects parameter to be int, %s is given", function, get_type_or_class_name()); - return empty_value(); - } -} - -const double &mixed::as_float(const char *function) const { - switch (get_type()) { - case type::FLOAT: - return as_double(); - default: - php_warning("%s() expects parameter to be float, %s is given", function, get_type_or_class_name()); - return empty_value(); - } -} - -const string &mixed::as_string(const char *function) const { - switch (get_type()) { - case type::STRING: - return as_string(); - default: - php_warning("%s() expects parameter to be string, %s is given", function, get_type_or_class_name()); - return empty_value(); - } -} - -const array &mixed::as_array(const char *function) const { - switch (get_type()) { - case type::ARRAY: - return as_array(); - default: - php_warning("%s() expects parameter to be array, %s is given", function, get_type_or_class_name()); - return empty_value>(); - } -} - - -bool &mixed::as_bool(const char *function) { - switch (get_type()) { - case type::NUL: - convert_to_bool(); - case type::BOOLEAN: - return as_bool(); - default: - php_warning("%s() expects parameter to be boolean, %s is given", function, get_type_or_class_name()); - return empty_value(); - } -} - -int64_t &mixed::as_int(const char *function) { - switch (get_type()) { - case type::NUL: - case type::BOOLEAN: - case type::FLOAT: - case type::STRING: - convert_to_int(); - case type::INTEGER: - return as_int(); - default: - php_warning("%s() expects parameter to be int, %s is given", function, get_type_or_class_name()); - return empty_value(); - } -} - -double &mixed::as_float(const char *function) { - switch (get_type()) { - case type::NUL: - case type::BOOLEAN: - case type::INTEGER: - case type::STRING: - convert_to_float(); - case type::FLOAT: - return as_double(); - default: - php_warning("%s() expects parameter to be float, %s is given", function, get_type_or_class_name()); - return empty_value(); - } -} - -string &mixed::as_string(const char *function) { - switch (get_type()) { - case type::NUL: - case type::BOOLEAN: - case type::INTEGER: - case type::FLOAT: - convert_to_string(); - case type::STRING: - return as_string(); - default: - php_warning("%s() expects parameter to be string, %s is given", function, get_type_or_class_name()); - return empty_value(); - } -} - -array &mixed::as_array(const char *function) { - switch (get_type()) { - case type::ARRAY: - return as_array(); - default: - php_warning("%s() expects parameter to be array, %s is given", function, get_type_or_class_name()); - return empty_value>(); - } -} - - -bool mixed::is_numeric() const { - switch (get_type()) { - case type::INTEGER: - case type::FLOAT: - return true; - case type::STRING: - return as_string().is_numeric(); - default: - return false; - } -} - -bool mixed::is_scalar() const { - return get_type() != type::NUL && get_type() != type::ARRAY && get_type() != type::OBJECT; -} - - -mixed::type mixed::get_type() const { - return type_; -} - -bool mixed::is_null() const { - return get_type() == type::NUL; -} - -bool mixed::is_bool() const { - return get_type() == type::BOOLEAN; -} - -bool mixed::is_int() const { - return get_type() == type::INTEGER; -} - -bool mixed::is_float() const { - return get_type() == type::FLOAT; -} - -bool mixed::is_string() const { - return get_type() == type::STRING; -} - -bool mixed::is_array() const { - return get_type() == type::ARRAY; -} - -bool mixed::is_object() const { - return get_type() == type::OBJECT; -} - -inline const char *mixed::get_type_c_str() const { - switch (get_type()) { - case type::NUL: - return "NULL"; - case type::BOOLEAN: - return "boolean"; - case type::INTEGER: - return "integer"; - case type::FLOAT: - return "double"; - case type::STRING: - return "string"; - case type::ARRAY: - return "array"; - case type::OBJECT: - return "object"; - default: - __builtin_unreachable(); - } -} - -inline const char *mixed::get_type_or_class_name() const { - switch (get_type()) { - case type::OBJECT: - return as_object()->get_class(); - default: - return get_type_c_str(); - } -} - -inline const string mixed::get_type_str() const { - return string(get_type_c_str()); -} - - -bool mixed::empty() const { - return !to_bool(); -} - -int64_t mixed::count() const { - switch (get_type()) { - case type::NUL: - php_warning("count(): Parameter is null, but an array expected"); - return 0; - case type::BOOLEAN: - case type::INTEGER: - case type::FLOAT: - case type::STRING: - php_warning("count(): Parameter is %s, but an array expected", get_type_c_str()); - return 1; - case type::ARRAY: - return as_array().count(); - case type::OBJECT: - php_warning("count(): Parameter is %s, but an array expected", get_type_or_class_name()); - return (as_object() ? 1 : 0); - default: - __builtin_unreachable(); - } -} - -template -inline int64_t spaceship(const T1 &lhs, const T2 &rhs); - -int64_t mixed::compare(const mixed &rhs) const { - if (unlikely(is_string())) { - if (likely(rhs.is_string())) { - return compare_strings_php_order(as_string(), rhs.as_string()); - } else if (unlikely(rhs.is_null())) { - return as_string().empty() ? 0 : 1; - } - } else if (unlikely(rhs.is_string())) { - if (unlikely(is_null())) { - return rhs.as_string().empty() ? 0 : -1; - } - } - if (is_bool() || rhs.is_bool() || is_null() || rhs.is_null()) { - return three_way_comparison(to_bool(), rhs.to_bool()); - } - - if (unlikely(is_array() || rhs.is_array())) { - if (likely(is_array() && rhs.is_array())) { - return spaceship(as_array(), rhs.as_array()); - } - - php_warning("Unsupported operand types for operator < or <= (%s and %s)", get_type_c_str(), rhs.get_type_c_str()); - return is_array() ? 1 : -1; - } - if (unlikely(is_object() || rhs.is_object())) { - php_warning("Unsupported operand types for operator < or <= (%s and %s)", get_type_or_class_name(), get_type_or_class_name()); - return is_object() ? 1 : -1; - } - - return three_way_comparison(to_float(), rhs.to_float()); -} - -void mixed::swap(mixed &other) { - ::swap(type_, other.type_); - ::swap(as_double(), other.as_double()); -} - - -mixed &mixed::operator[](int64_t int_key) { - if (unlikely (get_type() != type::ARRAY)) { - if (get_type() == type::STRING) { - php_warning("Writing to string by offset is't supported"); - return empty_value(); - } - - if (get_type() == type::NUL || (get_type() == type::BOOLEAN && !as_bool())) { - type_ = type::ARRAY; - new(&as_array()) array(); - } else { - php_warning("Cannot use a value \"%s\" of type %s as an array, index = %" PRIi64, to_string_without_warning(*this).c_str(), get_type_or_class_name(), int_key); - return empty_value(); - } - } - return as_array()[int_key]; -} - -mixed &mixed::operator[](const string &string_key) { - if (unlikely (get_type() != type::ARRAY)) { - if (get_type() == type::STRING) { - php_warning("Writing to string by offset is't supported"); - return empty_value(); - } - - if (get_type() == type::NUL || (get_type() == type::BOOLEAN && !as_bool())) { - type_ = type::ARRAY; - new(&as_array()) array(); - } else { - php_warning("Cannot use a value \"%s\" of type %s as an array, index = %s", to_string_without_warning(*this).c_str(), get_type_or_class_name(), string_key.c_str()); - return empty_value(); - } - } - - return as_array()[string_key]; -} - -mixed &mixed::operator[](tmp_string string_key) { - if (get_type() == type::ARRAY) { - return as_array()[string_key]; - } - return (*this)[materialize_tmp_string(string_key)]; -} - -mixed &mixed::operator[](const mixed &v) { - switch (v.get_type()) { - case type::NUL: - return (*this)[string()]; - case type::BOOLEAN: - return (*this)[v.as_bool()]; - case type::INTEGER: - return (*this)[v.as_int()]; - case type::FLOAT: - return (*this)[static_cast(v.as_double())]; - case type::STRING: - return (*this)[v.as_string()]; - case type::ARRAY: - php_warning("Illegal offset type %s", v.get_type_c_str()); - return (*this)[v.as_array().to_int()]; - case type::OBJECT: - php_warning("Illegal offset type %s", v.get_type_or_class_name()); - return (*this)[(as_object() ? 1 : 0)]; - default: - __builtin_unreachable(); - } -} - -mixed &mixed::operator[](double double_key) { - return (*this)[static_cast(double_key)]; -} - -mixed &mixed::operator[](const array::const_iterator &it) { - return as_array()[it]; -} - -mixed &mixed::operator[](const array::iterator &it) { - return as_array()[it]; -} - - -void mixed::set_value(int64_t int_key, const mixed &v) { - if (unlikely (get_type() != type::ARRAY)) { - if (get_type() == type::STRING) { - auto rhs_string = v.to_string(); - if (rhs_string.empty()) { - php_warning("Cannot assign an empty string to a string offset, index = %" PRIi64, int_key); - return; - } - - const char c = rhs_string[0]; - - if (int_key >= 0) { - const string::size_type l = as_string().size(); - if (int_key >= l) { - as_string().append(string::unsafe_cast_to_size_type(int_key + 1 - l), ' '); - } else { - as_string().make_not_shared(); - } - - as_string()[static_cast(int_key)] = c; - } else { - php_warning("%" PRIi64 " is illegal offset for string", int_key); - } - return; - } - - if (get_type() == type::NUL || (get_type() == type::BOOLEAN && !as_bool())) { - type_ = type::ARRAY; - new(&as_array()) array(); - } else { - php_warning("Cannot use a value \"%s\" of type %s as an array, index = %" PRIi64, to_string_without_warning(*this).c_str(), get_type_or_class_name(), int_key); - return; +template +void mixed::init_from(T &&v) { + if constexpr (is_class_instance_v>) { + static_assert(sizeof(storage_) >= sizeof(vk::intrusive_ptr)); + auto ptr_to_obj = new(&storage_) vk::intrusive_ptr(dynamic_cast(v.get())); + if (unlikely(!ptr_to_obj)) { + php_error("Internal error. Trying to set invalid object to mixed"); } + type_ = type::OBJECT; + } else { + auto type_and_value_ref = get_type_and_value_ptr(v); + type_ = type_and_value_ref.first; + auto *value_ptr = type_and_value_ref.second; + using ValueType = std::decay_t; + new(value_ptr) ValueType(std::forward(v)); } - return as_array().set_value(int_key, v); } -void mixed::set_value(const string &string_key, const mixed &v) { - if (unlikely (get_type() != type::ARRAY)) { - if (get_type() == type::STRING) { - int64_t int_val = 0; - if (!string_key.try_to_int(&int_val)) { - php_warning("\"%s\" is illegal offset for string", string_key.c_str()); - int_val = string_key.to_int(); - } - if (int_val < 0) { - return; - } - - char c = (v.to_string())[0]; - - const string::size_type l = as_string().size(); - if (int_val >= l) { - as_string().append(string::unsafe_cast_to_size_type(int_val + 1 - l), ' '); - } else { - as_string().make_not_shared(); - } - - as_string()[static_cast(int_val)] = c; - return; - } - - if (get_type() == type::NUL || (get_type() == type::BOOLEAN && !as_bool())) { - type_ = type::ARRAY; - new(&as_array()) array(); +template +mixed &mixed::assign_from(T &&v) { + if constexpr(is_class_instance_v>) { + static_assert(std::is_base_of_v::ClassType>); + destroy(); + init_from(std::forward(v)); + } else { + auto type_and_value_ref = get_type_and_value_ptr(v); + if (get_type() == type_and_value_ref.first) { + *type_and_value_ref.second = std::forward(v); } else { - php_warning("Cannot use a value \"%s\" of type %s as an array, index = %s", to_string_without_warning(*this).c_str(), get_type_or_class_name(), string_key.c_str()); - return; - } - } - - return as_array().set_value(string_key, v); -} - -void mixed::set_value(const string &string_key, const mixed &v, int64_t precomuted_hash) { - return get_type() == type::ARRAY ? as_array().set_value(string_key, v, precomuted_hash) : set_value(string_key, v); -} - -void mixed::set_value(tmp_string string_key, const mixed &v) { - // TODO: as with arrays, avoid eager tmp_string->string conversion - set_value(materialize_tmp_string(string_key), v); -} - -void mixed::set_value(const mixed &v, const mixed &value) { - switch (v.get_type()) { - case type::NUL: - return set_value(string(), value); - case type::BOOLEAN: - return set_value(static_cast(v.as_bool()), value); - case type::INTEGER: - return set_value(v.as_int(), value); - case type::FLOAT: - return set_value(static_cast(v.as_double()), value); - case type::STRING: - return set_value(v.as_string(), value); - case type::ARRAY: - php_warning("Illegal offset type array"); - return; - case type::OBJECT: - php_warning("Illegal offset type %s", v.get_type_or_class_name()); - return; - default: - __builtin_unreachable(); - } -} - -void mixed::set_value(double double_key, const mixed &value) { - set_value(static_cast(double_key), value); -} - -void mixed::set_value(const array::const_iterator &it) { - return as_array().set_value(it); -} - -void mixed::set_value(const array::iterator &it) { - return as_array().set_value(it); -} - - -const mixed mixed::get_value(int64_t int_key) const { - if (unlikely (get_type() != type::ARRAY)) { - if (get_type() == type::STRING) { - if (int_key < 0 || int_key >= as_string().size()) { - return string(); - } - return string(1, as_string()[static_cast(int_key)]); - } - - if (get_type() != type::NUL && (get_type() != type::BOOLEAN || as_bool())) { - php_warning("Cannot use a value \"%s\" of type %s as an array, index = %" PRIi64, to_string_without_warning(*this).c_str(), get_type_or_class_name(), int_key); - } - return mixed(); - } - - return as_array().get_value(int_key); -} - -const mixed mixed::get_value(const string &string_key) const { - if (unlikely (get_type() != type::ARRAY)) { - if (get_type() == type::STRING) { - int64_t int_val = 0; - if (!string_key.try_to_int(&int_val)) { - php_warning("\"%s\" is illegal offset for string", string_key.c_str()); - int_val = string_key.to_int(); - } - if (int_val < 0 || int_val >= as_string().size()) { - return string(); - } - return string(1, as_string()[static_cast(int_val)]); - } - - if (get_type() != type::NUL && (get_type() != type::BOOLEAN || as_bool())) { - php_warning("Cannot use a value \"%s\" of type %s as an array, index = %s", to_string_without_warning(*this).c_str(), get_type_or_class_name(), string_key.c_str()); + destroy(); + init_from(std::forward(v)); } - return mixed(); } - return as_array().get_value(string_key); -} - -const mixed mixed::get_value(const string &string_key, int64_t precomuted_hash) const { - return get_type() == type::ARRAY ? as_array().get_value(string_key, precomuted_hash) : get_value(string_key); -} - -const mixed mixed::get_value(tmp_string string_key) const { - if (get_type() == type::ARRAY) { - // fast path: arrays can handle a tmp_string lookup efficiently - return as_array().get_value(string_key); - } - // TODO: make other lookups efficient too (like string from numeric tmp_string)? - return get_value(materialize_tmp_string(string_key)); + return *this; } -const mixed mixed::get_value(const mixed &v) const { - switch (v.get_type()) { - case type::NUL: - return get_value(string()); - case type::BOOLEAN: - return get_value(static_cast(v.as_bool())); - case type::INTEGER: - return get_value(v.as_int()); - case type::FLOAT: - return get_value(static_cast(v.as_double())); - case type::STRING: - return get_value(v.as_string()); - case type::ARRAY: - php_warning("Illegal offset type %s", v.get_type_c_str()); - return mixed(); - case type::OBJECT: - php_warning("Illegal offset type %s", v.get_type_or_class_name()); - return mixed(); - default: - __builtin_unreachable(); - } +template +mixed::mixed(T &&v) noexcept { + init_from(std::forward(v)); } -const mixed mixed::get_value(double double_key) const { - return get_value(static_cast(double_key)); +template +mixed::mixed(const Optional &v) noexcept { + auto init_from_lambda = [this](const auto &v) { this->init_from(v); }; + call_fun_on_optional_value(init_from_lambda, v); } -const mixed mixed::get_value(const array::const_iterator &it) const { - return as_array().get_value(it); +template +mixed::mixed(Optional &&v) noexcept { + auto init_from_lambda = [this](auto &&v) { this->init_from(std::move(v)); }; + call_fun_on_optional_value(init_from_lambda, std::move(v)); } -const mixed mixed::get_value(const array::iterator &it) const { - return as_array().get_value(it); +template +mixed &mixed::operator=(T &&v) noexcept { + return assign_from(std::forward(v)); } - -void mixed::push_back(const mixed &v) { - if (unlikely (get_type() != type::ARRAY)) { - if (get_type() == type::NUL || (get_type() == type::BOOLEAN && !as_bool())) { - type_ = type::ARRAY; - new(&as_array()) array(); - } else { - php_warning("[] operator not supported for type %s", get_type_or_class_name()); - return; - } - } - - return as_array().push_back(v); +template +mixed &mixed::operator=(const Optional &v) noexcept { + auto assign_from_lambda = [this](const auto &v) -> mixed& { return this->assign_from(v); }; + return call_fun_on_optional_value(assign_from_lambda, v); } -const mixed mixed::push_back_return(const mixed &v) { - if (unlikely (get_type() != type::ARRAY)) { - if (get_type() == type::NUL || (get_type() == type::BOOLEAN && !as_bool())) { - type_ = type::ARRAY; - new(&as_array()) array(); - } else { - php_warning("[] operator not supported for type %s", get_type_or_class_name()); - return empty_value(); - } - } - - return as_array().push_back_return(v); +template +mixed &mixed::operator=(Optional &&v) noexcept { + auto assign_from_lambda = [this](auto &&v) -> mixed& { return this->assign_from(std::move(v)); }; + return call_fun_on_optional_value(assign_from_lambda, std::move(v)); } - -bool mixed::isset(int64_t int_key) const { - if (unlikely (get_type() != type::ARRAY)) { - if (get_type() == type::STRING) { - int_key = as_string().get_correct_index(int_key); - return as_string().isset(int_key); - } - - if (get_type() != type::NUL && (get_type() != type::BOOLEAN || as_bool())) { - php_warning("Cannot use variable of type %s as array in isset", get_type_or_class_name()); - } - return false; - } - - return as_array().isset(int_key); -} +template +int64_t spaceship(const T1 &lhs, const T2 &rhs); template bool mixed::isset(const string &string_key, MaybeHash ...maybe_hash) const { @@ -1569,44 +107,6 @@ bool mixed::isset(const string &string_key, MaybeHash ...maybe_hash) const { return as_array().isset(string_key, maybe_hash...); } -bool mixed::isset(const mixed &v) const { - switch (v.get_type()) { - case type::NUL: - return isset(string()); - case type::BOOLEAN: - return isset(static_cast(v.as_bool())); - case type::INTEGER: - return isset(v.as_int()); - case type::FLOAT: - return isset(static_cast(v.as_double())); - case type::STRING: - return isset(v.as_string()); - case type::ARRAY: - php_warning("Illegal offset type array"); - return false; - case type::OBJECT: - php_warning("Illegal offset type %s", get_type_or_class_name()); - return false; - default: - __builtin_unreachable(); - } -} - -bool mixed::isset(double double_key) const { - return isset(static_cast(double_key)); -} - -void mixed::unset(int64_t int_key) { - if (unlikely (get_type() != type::ARRAY)) { - if (get_type() != type::NUL && (get_type() != type::BOOLEAN || as_bool())) { - php_warning("Cannot use variable of type %s as array in unset", get_type_or_class_name()); - } - return; - } - - as_array().unset(int_key); -} - template void mixed::unset(const string &string_key, MaybeHash ...maybe_hash) { if (unlikely (get_type() != type::ARRAY)) { @@ -1619,176 +119,46 @@ void mixed::unset(const string &string_key, MaybeHash ...maybe_hash) { as_array().unset(string_key, maybe_hash...); } -void mixed::unset(const mixed &v) { - if (unlikely (get_type() != type::ARRAY)) { - if (get_type() != type::NUL && (get_type() != type::BOOLEAN || as_bool())) { - php_warning("Cannot use variable of type %s as array in unset", get_type_or_class_name()); - } - return; - } - - switch (v.get_type()) { - case type::NUL: - as_array().unset(string()); - break; - case type::BOOLEAN: - as_array().unset(static_cast(v.as_bool())); - break; - case type::INTEGER: - as_array().unset(v.as_int()); - break; - case type::FLOAT: - as_array().unset(static_cast(v.as_double())); - break; - case type::STRING: - as_array().unset(v.as_string()); - break; - case type::ARRAY: - php_warning("Illegal offset type array"); - break; - case type::OBJECT: - php_warning("Illegal offset type %s", v.get_type_or_class_name()); - break; - default: - __builtin_unreachable(); - } -} - -void mixed::unset(double double_key) { - unset(static_cast(double_key)); -} -array::const_iterator mixed::begin() const { - if (likely (get_type() == type::ARRAY)) { - return as_array().begin(); - } - php_warning("Invalid argument supplied for foreach(), %s (string representation - \"%s\") is given", get_type_or_class_name(), to_string_without_warning(*this).c_str()); - return array::const_iterator(); +inline mixed::type mixed::get_type() const { + return type_; } -array::const_iterator mixed::end() const { - if (likely (get_type() == type::ARRAY)) { - return as_array().end(); - } - return array::const_iterator(); +inline bool mixed::is_null() const { + return get_type() == type::NUL; } - -array::iterator mixed::begin() { - if (likely (get_type() == type::ARRAY)) { - return as_array().begin(); - } - php_warning("Invalid argument supplied for foreach(), %s (string representation - \"%s\") is given", get_type_or_class_name(), to_string_without_warning(*this).c_str()); - return array::iterator(); +inline bool mixed::is_bool() const { + return get_type() == type::BOOLEAN; } -array::iterator mixed::end() { - if (likely (get_type() == type::ARRAY)) { - return as_array().end(); - } - return array::iterator(); +inline bool mixed::is_int() const { + return get_type() == type::INTEGER; } - -int64_t mixed::get_reference_counter() const { - switch (get_type()) { - case type::NUL: - return -1; - case type::BOOLEAN: - return -2; - case type::INTEGER: - return -3; - case type::FLOAT: - return -4; - case type::STRING: - return as_string().get_reference_counter(); - case type::ARRAY: - return as_array().get_reference_counter(); - case type::OBJECT: - return as_object()->get_refcnt(); - default: - __builtin_unreachable(); - } +inline bool mixed::is_float() const { + return get_type() == type::FLOAT; } -void mixed::set_reference_counter_to(ExtraRefCnt ref_cnt_value) noexcept { - switch (get_type()) { - case type::NUL: - case type::BOOLEAN: - case type::INTEGER: - case type::FLOAT: - return; - case type::STRING: - return as_string().set_reference_counter_to(ref_cnt_value); - case type::ARRAY: - return as_array().set_reference_counter_to(ref_cnt_value); - case type::OBJECT: - return as_object()->set_refcnt(ref_cnt_value); - default: - __builtin_unreachable(); - } +inline bool mixed::is_string() const { + return get_type() == type::STRING; } -inline bool mixed::is_reference_counter(ExtraRefCnt ref_cnt_value) const noexcept { - switch (get_type()) { - case type::NUL: - case type::BOOLEAN: - case type::INTEGER: - case type::FLOAT: - return false; - case type::STRING: - return as_string().is_reference_counter(ref_cnt_value); - case type::ARRAY: - return as_array().is_reference_counter(ref_cnt_value); - case type::OBJECT: - return static_cast(as_object()->get_refcnt()) == ref_cnt_value; - default: - __builtin_unreachable(); - } +inline bool mixed::is_array() const { + return get_type() == type::ARRAY; } -inline void mixed::force_destroy(ExtraRefCnt expected_ref_cnt) noexcept { - switch (get_type()) { - case type::STRING: - as_string().force_destroy(expected_ref_cnt); - break; - case type::ARRAY: - as_array().force_destroy(expected_ref_cnt); - break; - case type::OBJECT: - php_warning("Objects (%s) are not supported in confdata", get_type_or_class_name()); - break; - default: - __builtin_unreachable(); - } +inline bool mixed::is_object() const { + return get_type() == type::OBJECT; } -size_t mixed::estimate_memory_usage() const { - switch (get_type()) { - case type::NUL: - case type::BOOLEAN: - case type::INTEGER: - case type::FLOAT: - return 0; - case type::STRING: - return as_string().estimate_memory_usage(); - case type::ARRAY: - return as_array().estimate_memory_usage(); - case type::OBJECT: - php_warning("Objects (%s) are not supported in confdata", get_type_or_class_name()); - return 0; - default: - __builtin_unreachable(); - } +inline void mixed::swap(mixed &other) { + ::swap(type_, other.type_); + ::swap(as_double(), other.as_double()); } -void mixed::reset_empty_values() noexcept { - empty_value(); - empty_value(); - empty_value(); - empty_value(); - empty_value(); - empty_value>(); +inline void swap(mixed &lhs, mixed &rhs) { + lhs.swap(rhs); } template @@ -1800,87 +170,24 @@ T &mixed::empty_value() noexcept { return value; } -namespace impl_ { - -template -inline mixed do_math_op_on_vars(const mixed &lhs, const mixed &rhs, MathOperation &&math_op) { - if (likely(lhs.is_int() && rhs.is_int())) { - return math_op(lhs.as_int(), rhs.as_int()); - } - - const mixed arg1 = lhs.to_numeric(); - const mixed arg2 = rhs.to_numeric(); - - if (arg1.is_int()) { - if (arg2.is_int()) { - return math_op(arg1.as_int(), arg2.as_int()); - } else { - return math_op(static_cast(arg1.as_int()), arg2.as_double()); - } - } else { - if (arg2.is_int()) { - return math_op(arg1.as_double(), static_cast(arg2.as_int())); - } else { - return math_op(arg1.as_double(), arg2.as_double()); - } - } -} - -} // namespace impl_ - -inline mixed operator+(const mixed &lhs, const mixed &rhs) { - if (lhs.is_array() && rhs.is_array()) { - return lhs.as_array() + rhs.as_array(); - } - - return impl_::do_math_op_on_vars(lhs, rhs, [](const auto &arg1, const auto &arg2) { return arg1 + arg2; }); -} - -inline mixed operator-(const mixed &lhs, const mixed &rhs) { - return impl_::do_math_op_on_vars(lhs, rhs, [](const auto &arg1, const auto &arg2) { return arg1 - arg2; }); -} - -inline mixed operator*(const mixed &lhs, const mixed &rhs) { - return impl_::do_math_op_on_vars(lhs, rhs, [](const auto &arg1, const auto &arg2) { return arg1 * arg2; }); -} - -inline mixed operator-(const string &lhs) { - mixed arg1 = lhs.to_numeric(); - - if (arg1.is_int()) { - arg1.as_int() = -arg1.as_int(); - } else { - arg1.as_double() = -arg1.as_double(); - } - return arg1; -} - -inline mixed operator+(const string &lhs) { - return lhs.to_numeric(); -} - -inline int64_t operator&(const mixed &lhs, const mixed &rhs) { - return lhs.to_int() & rhs.to_int(); -} - -inline int64_t operator|(const mixed &lhs, const mixed &rhs) { - return lhs.to_int() | rhs.to_int(); -} - -inline int64_t operator^(const mixed &lhs, const mixed &rhs) { - return lhs.to_int() ^ rhs.to_int(); +inline void mixed::reset_empty_values() noexcept { + empty_value(); + empty_value(); + empty_value(); + empty_value(); + empty_value(); + empty_value>(); } -inline int64_t operator<<(const mixed &lhs, const mixed &rhs) { - return lhs.to_int() << rhs.to_int(); +template +string_buffer &operator<<(string_buffer &sb, const Optional &v) { + auto write_lambda = [&sb](const auto &v) -> string_buffer& { return sb << v; }; + return call_fun_on_optional_value(write_lambda, v); } -inline int64_t operator>>(const mixed &lhs, const mixed &rhs) { - return lhs.to_int() >> rhs.to_int(); -} template -inline const char *conversion_php_warning_string() { +const char *conversion_php_warning_string() { return ""; } @@ -1905,7 +212,7 @@ inline const char *conversion_php_warning_string() { } template -inline bool less_number_string_as_php8_impl(T lhs, const string &rhs) { +bool less_number_string_as_php8_impl(T lhs, const string &rhs) { auto rhs_float = 0.0; const auto rhs_is_string_number = rhs.try_to_float(&rhs_float); @@ -1917,7 +224,7 @@ inline bool less_number_string_as_php8_impl(T lhs, const string &rhs) { } template -inline bool less_string_number_as_php8_impl(const string &lhs, T rhs) { +bool less_string_number_as_php8_impl(const string &lhs, T rhs) { auto lhs_float = 0.0; const auto lhs_is_string_number = lhs.try_to_float(&lhs_float); @@ -1929,7 +236,7 @@ inline bool less_string_number_as_php8_impl(const string &lhs, T rhs) { } template -inline bool less_number_string_as_php8(bool php7_result, T lhs, const string &rhs) { +bool less_number_string_as_php8(bool php7_result, T lhs, const string &rhs) { if (KphpCoreContext::current().show_migration_php8_warning & MIGRATION_PHP8_STRING_COMPARISON_FLAG) { const auto php8_result = less_number_string_as_php8_impl(lhs, rhs); if (php7_result == php8_result) { @@ -1947,7 +254,7 @@ inline bool less_number_string_as_php8(bool php7_result, T lhs, const string &rh } template -inline bool less_string_number_as_php8(bool php7_result, const string &lhs, T rhs) { +bool less_string_number_as_php8(bool php7_result, const string &lhs, T rhs) { if (KphpCoreContext::current().show_migration_php8_warning & MIGRATION_PHP8_STRING_COMPARISON_FLAG) { const auto php8_result = less_string_number_as_php8_impl(lhs, rhs); if (php7_result == php8_result) { @@ -1964,61 +271,19 @@ inline bool less_string_number_as_php8(bool php7_result, const string &lhs, T rh return php7_result; } -inline bool operator<(const mixed &lhs, const mixed &rhs) { - const auto res = lhs.compare(rhs) < 0; - - if (rhs.is_string()) { - if (lhs.is_int()) { - return less_number_string_as_php8(res, lhs.to_int(), rhs.to_string()); - } else if (lhs.is_float()) { - return less_number_string_as_php8(res, lhs.to_float(), rhs.to_string()); - } - } else if (lhs.is_string()) { - if (rhs.is_int()) { - return less_string_number_as_php8(res, lhs.to_string(), rhs.to_int()); - } else if (rhs.is_float()) { - return less_string_number_as_php8(res, lhs.to_string(), rhs.to_float()); - } - } - - return res; -} - -inline bool operator<=(const mixed &lhs, const mixed &rhs) { - return !(rhs < lhs); -} - -inline void swap(mixed &lhs, mixed &rhs) { - lhs.swap(rhs); -} - -template -inline string_buffer &operator<<(string_buffer &sb, const Optional &v) { - auto write_lambda = [&sb](const auto &v) -> string_buffer& { return sb << v; }; - return call_fun_on_optional_value(write_lambda, v); +template +mixed f$to_mixed(const class_instance &instance) noexcept { + mixed m; + m = instance; + return m; } -inline string_buffer &operator<<(string_buffer &sb, const mixed &v) { - switch (v.get_type()) { - case mixed::type::NUL: - return sb; - case mixed::type::BOOLEAN: - return sb << v.as_bool(); - case mixed::type::INTEGER: - return sb << v.as_int(); - case mixed::type::FLOAT: - return sb << string(v.as_double()); - case mixed::type::STRING: - return sb << v.as_string(); - case mixed::type::ARRAY: - php_warning("Conversion from array to string"); - return sb.append("Array", 5); - case mixed::type::OBJECT: { - const char *s = v.get_type_or_class_name(); - php_warning("Conversion from %s to string", s); - return sb.append(s, strlen(s)); - } - default: - __builtin_unreachable(); +template +ResultClass from_mixed(const mixed &m, const string &) noexcept { + if constexpr (!std::is_polymorphic_v) { + php_error("Internal error. Class inside a mixed is not polymorphic"); + return {}; + } else { + return ResultClass::create_from_base_raw_ptr(dynamic_cast(m.as_object_ptr())); } -} +} \ No newline at end of file From 4b4a94af1141e3b4ee8be65864fec7de8bcee9ed Mon Sep 17 00:00:00 2001 From: Andrey Arutiunian <110744283+andarut@users.noreply.github.com> Date: Tue, 20 Aug 2024 14:00:06 +0300 Subject: [PATCH 24/45] Fix CI docker caching images (#1048) --- .github/workflows/Build.yml | 9 ++++++--- .github/workflows/debian.yml | 11 ++++++++--- .github/workflows/ubuntu.yml | 11 ++++++++--- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/.github/workflows/Build.yml b/.github/workflows/Build.yml index 462db84449..71ee5631b5 100644 --- a/.github/workflows/Build.yml +++ b/.github/workflows/Build.yml @@ -60,8 +60,11 @@ jobs: - name: Build and save docker image if: steps.docker-image-cache.outputs.cache-hit != 'true' run: | - docker build -f $GITHUB_WORKSPACE/.github/workflows/Dockerfile.${{matrix.os}} $GITHUB_WORKSPACE -t kphp-build-img-${{matrix.os}} - docker save --output kphp-build-env-${{matrix.os}}.tar kphp-build-img-${{matrix.os}} + docker build -f $GITHUB_WORKSPACE/.github/workflows/Dockerfile.${{matrix.os}} $GITHUB_WORKSPACE \ + -t kphp-build-img-${{matrix.os}} \ + --cache-from=type=local,src=kphp-build-img-${{matrix.os}}-cache + docker tag kphp-build-img-${{matrix.os}} kphp-build-img-${{matrix.os}}-cache + docker save kphp-build-img-${{matrix.os}}-cache -o kphp-build-env-${{matrix.os}}.tar - name: Load docker image from cache if: steps.docker-image-cache.outputs.cache-hit == 'true' @@ -69,7 +72,7 @@ jobs: - name: Start docker container run: | - docker run -dt --name kphp-build-container-${{matrix.os}} kphp-build-img-${{matrix.os}} + docker run -dt --name kphp-build-container-${{matrix.os}} kphp-build-img-${{matrix.os}}-cache docker cp $GITHUB_WORKSPACE/. kphp-build-container-${{matrix.os}}:${{env.kphp_root_dir}} - name: Add git safe directory diff --git a/.github/workflows/debian.yml b/.github/workflows/debian.yml index 2d507d8110..1b6981ad69 100644 --- a/.github/workflows/debian.yml +++ b/.github/workflows/debian.yml @@ -43,15 +43,20 @@ jobs: - name: Build and save docker image if: steps.docker-image-cache.outputs.cache-hit != 'true' run: | - docker build -f $GITHUB_WORKSPACE/.github/workflows/Dockerfile.${{matrix.os}} $GITHUB_WORKSPACE -t kphp-build-img-${{matrix.os}} - docker save --output kphp-build-env-${{matrix.os}}.tar kphp-build-img-${{matrix.os}} + docker build -f $GITHUB_WORKSPACE/.github/workflows/Dockerfile.${{matrix.os}} $GITHUB_WORKSPACE \ + -t kphp-build-img-${{matrix.os}} \ + --cache-from=type=local,src=kphp-build-img-${{matrix.os}}-cache + docker tag kphp-build-img-${{matrix.os}} kphp-build-img-${{matrix.os}}-cache + docker save kphp-build-img-${{matrix.os}}-cache -o kphp-build-env-${{matrix.os}}.tar - name: Load docker image from cache if: steps.docker-image-cache.outputs.cache-hit == 'true' run: docker load --input kphp-build-env-${{matrix.os}}.tar - name: Start docker container - run: docker run -dt --name kphp-build-container-${{matrix.os}} --volume $GITHUB_WORKSPACE:${{env.kphp_root_dir}} kphp-build-img-${{matrix.os}} + run: | + docker run -dt --name kphp-build-container-${{matrix.os}} kphp-build-img-${{matrix.os}}-cache + docker cp $GITHUB_WORKSPACE/. kphp-build-container-${{matrix.os}}:${{env.kphp_root_dir}} - name: Change repo rights run: docker exec kphp-build-container-${{matrix.os}} bash -c diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 49af16bd9f..6655443856 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -53,15 +53,20 @@ jobs: - name: Build and save docker image if: steps.docker-image-cache.outputs.cache-hit != 'true' run: | - docker build -f $GITHUB_WORKSPACE/.github/workflows/Dockerfile.${{matrix.os}} $GITHUB_WORKSPACE -t kphp-build-img-${{matrix.os}} - docker save --output kphp-build-env-${{matrix.os}}.tar kphp-build-img-${{matrix.os}} + docker build -f $GITHUB_WORKSPACE/.github/workflows/Dockerfile.${{matrix.os}} $GITHUB_WORKSPACE \ + -t kphp-build-img-${{matrix.os}} \ + --cache-from=type=local,src=kphp-build-img-${{matrix.os}}-cache + docker tag kphp-build-img-${{matrix.os}} kphp-build-img-${{matrix.os}}-cache + docker save kphp-build-img-${{matrix.os}}-cache -o kphp-build-env-${{matrix.os}}.tar - name: Load docker image from cache if: steps.docker-image-cache.outputs.cache-hit == 'true' run: docker load --input kphp-build-env-${{matrix.os}}.tar - name: Start docker container - run: docker run -dt --name kphp-build-container-${{matrix.os}} --volume $GITHUB_WORKSPACE:${{env.kphp_root_dir}} kphp-build-img-${{matrix.os}} + run: | + docker run -dt --name kphp-build-container-${{matrix.os}} kphp-build-img-${{matrix.os}}-cache + docker cp $GITHUB_WORKSPACE/. kphp-build-container-${{matrix.os}}:${{env.kphp_root_dir}} - name: Change repo rights run: docker exec kphp-build-container-${{matrix.os}} bash -c From 081ee6b8edb061ce3efce1ab980304f31bb24df7 Mon Sep 17 00:00:00 2001 From: Dmitry Solomennikov <144122424+soloth@users.noreply.github.com> Date: Tue, 20 Aug 2024 21:59:13 +0300 Subject: [PATCH 25/45] Fix #1028 build (#1072) Add missed line for tests to work. Signed-off-by: dsolomennikov --- tests/phpt/json/38_optimized_init_val.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/phpt/json/38_optimized_init_val.php b/tests/phpt/json/38_optimized_init_val.php index 9bb7536129..96283c12cf 100644 --- a/tests/phpt/json/38_optimized_init_val.php +++ b/tests/phpt/json/38_optimized_init_val.php @@ -1,5 +1,6 @@ @ok Date: Wed, 21 Aug 2024 15:59:17 +0300 Subject: [PATCH 26/45] Fix objects in mixed in light runtime (#1075) Fix UB with reaching unreachable builtin when do var_dump() when mixed stores object and some fixes --- builtin-functions/kphp-light/functions.txt | 2 +- runtime-core/runtime-core.cmake | 1 + runtime-light/runtime-light.cmake | 1 + runtime-light/stdlib/variable-handling.cpp | 15 +++++++++++++ tests/kphp_tester.py | 6 +++--- tests/phpt/mixed/non_primitive/008_forks.php | 2 +- .../mixed/non_primitive/014_class_as_int.php | 2 +- .../phpt/mixed/non_primitive/016_var_dump.php | 21 +++++++++++++++++++ 8 files changed, 44 insertions(+), 6 deletions(-) create mode 100644 tests/phpt/mixed/non_primitive/016_var_dump.php diff --git a/builtin-functions/kphp-light/functions.txt b/builtin-functions/kphp-light/functions.txt index e2e776dce8..40fa259d2e 100644 --- a/builtin-functions/kphp-light/functions.txt +++ b/builtin-functions/kphp-light/functions.txt @@ -205,7 +205,7 @@ 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>; +function instance_cast(any $instance, $to_type ::: string) ::: instance<^2>; function make_clone ($x ::: any) ::: ^1; diff --git a/runtime-core/runtime-core.cmake b/runtime-core/runtime-core.cmake index b69b7d6604..ed48cf6bf5 100644 --- a/runtime-core/runtime-core.cmake +++ b/runtime-core/runtime-core.cmake @@ -29,3 +29,4 @@ endif() prepend(KPHP_CORE_SRC ${RUNTIME_CORE_DIR}/ "${KPHP_CORE_SRC}") vk_add_library(runtime-core OBJECT ${KPHP_CORE_SRC}) +target_compile_options(runtime-core PUBLIC -fPIC) diff --git a/runtime-light/runtime-light.cmake b/runtime-light/runtime-light.cmake index 7487d40ee7..debf858e2e 100644 --- a/runtime-light/runtime-light.cmake +++ b/runtime-light/runtime-light.cmake @@ -34,6 +34,7 @@ set_target_properties(runtime-light PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${BASE_DIR}/objs) target_compile_options(runtime-light PUBLIC -stdlib=libc++) target_link_options(runtime-light PUBLIC -stdlib=libc++ -static-libstdc++) +target_compile_options(runtime-light PUBLIC -fPIC) vk_add_library(kphp-light-runtime STATIC) target_link_libraries( diff --git a/runtime-light/stdlib/variable-handling.cpp b/runtime-light/stdlib/variable-handling.cpp index dba94a95b3..4c5e7fc646 100644 --- a/runtime-light/stdlib/variable-handling.cpp +++ b/runtime-light/stdlib/variable-handling.cpp @@ -49,6 +49,11 @@ void do_print_r(const mixed &v, int depth) { *coub << shift << ")\n"; break; } + case mixed::type::OBJECT: { + php_warning("print_r used on object"); + *coub << v.as_object()->get_class(); + break; + } default: __builtin_unreachable(); } @@ -97,6 +102,12 @@ static void do_var_dump(const mixed &v, int depth) { *coub << shift << "}"; break; } + case mixed::type::OBJECT: { + php_warning("var_dump used on object"); + auto s = string(v.as_object()->get_class(), static_cast(strlen(v.as_object()->get_class()))); + *coub << shift << "string(" << static_cast(s.size()) << ") \"" << s << '"'; + break; + } default: __builtin_unreachable(); } @@ -176,6 +187,10 @@ static void do_var_export(const mixed &v, int depth, char endc = 0) { *coub << shift << ")"; break; } + case mixed::type::OBJECT: { + *coub << shift << v.get_type_or_class_name(); + break; + } default: __builtin_unreachable(); } diff --git a/tests/kphp_tester.py b/tests/kphp_tester.py index efce2a3966..82016c0e9d 100755 --- a/tests/kphp_tester.py +++ b/tests/kphp_tester.py @@ -63,12 +63,12 @@ def make_kphp_once_runner(self, use_nocc, cxx_name, k2_bin): k2_bin=k2_bin ) - def set_up_env_for_k2(self): + def set_up_env_for_k2(self, cxx_name="clang++"): self.env_vars["KPHP_MODE"] = "k2-component" self.env_vars["KPHP_USER_BINARY_PATH"] = "component.so" self.env_vars["KPHP_ENABLE_GLOBAL_VARS_MEMORY_STATS"] = "0" self.env_vars["KPHP_PROFILER"] = "0" - self.env_vars["KPHP_CXX"] = "clang++" + self.env_vars["KPHP_CXX"] = cxx_name self.env_vars["KPHP_K2_COMPONENT_IS_ONESHOT"] = "1" self.env_vars["KPHP_FORCE_LINK_RUNTIME"] = "1" @@ -352,7 +352,7 @@ def run_test(use_nocc, cxx_name, k2_bin, test: TestFile): runner = test.make_kphp_once_runner(use_nocc, cxx_name, k2_bin) runner.remove_artifacts_dir() if k2_bin is not None: - test.set_up_env_for_k2() + test.set_up_env_for_k2(cxx_name) if k2_bin is not None and not test.is_available_for_k2(): test_result = TestResult.k2_skipped(test) diff --git a/tests/phpt/mixed/non_primitive/008_forks.php b/tests/phpt/mixed/non_primitive/008_forks.php index 5a4d1cecc6..17a3c62325 100644 --- a/tests/phpt/mixed/non_primitive/008_forks.php +++ b/tests/phpt/mixed/non_primitive/008_forks.php @@ -1,4 +1,4 @@ -@ok +@ok k2_skip Date: Thu, 22 Aug 2024 13:33:02 +0300 Subject: [PATCH 27/45] Support type erasure on task_t (#1074) We use type erasure to store forks' tasks in a ForkComponentContext --- builtin-functions/kphp-light/functions.txt | 2 +- compiler/code-gen/declarations.cpp | 4 +-- compiler/code-gen/vertex-compiler.cpp | 6 ++--- runtime-core/utils/small-object-storage.h | 2 +- .../allocator/runtime-light-allocator.cpp | 5 ++-- runtime-light/coroutine/awaitable.h | 20 +++++++++----- runtime-light/coroutine/task.h | 14 +++++++++- runtime-light/stdlib/fork/fork-api.cpp | 9 +++---- runtime-light/stdlib/fork/fork-api.h | 8 +++--- runtime-light/stdlib/fork/fork-context.h | 11 ++++---- runtime-light/stdlib/fork/fork.h | 27 ------------------- runtime-light/stdlib/misc.cpp | 4 +-- runtime-light/stdlib/rpc/rpc-api.cpp | 4 +-- runtime-light/stdlib/string-functions.cpp | 4 +-- runtime-light/stdlib/string-functions.h | 1 - runtime-light/stdlib/timer/timer.h | 3 +-- runtime-light/utils/{panic.h => panic.cpp} | 7 +++-- runtime-light/utils/php_assert.cpp | 8 +++--- runtime-light/utils/php_assert.h | 13 --------- runtime-light/utils/utils.cmake | 2 +- tests/kphp_tester.py | 3 +-- tests/phpt/dl/474_flex.php | 2 +- .../spl/02_builtin_exceptions_1.php | 2 +- tests/phpt/fork/004_sched_yield_sleep.php | 14 +++++----- .../002_string_number_comparison.php | 2 +- ...tring_number_comparison_enable_disable.php | 2 +- tests/phpt/migration_php8/006_is_numeric.php | 2 +- .../007_string_string_comparison.php | 2 +- .../008_string_number_spaceship.php | 2 +- .../migration_php8/009_blank_no_warning.php | 2 +- tests/phpt/mixed/non_primitive/008_forks.php | 2 +- tests/python/lib/kphp_builder.py | 1 + tests/python/lib/kphp_run_once.py | 4 +-- 33 files changed, 81 insertions(+), 113 deletions(-) delete mode 100644 runtime-light/stdlib/fork/fork.h rename runtime-light/utils/{panic.h => panic.cpp} (85%) delete mode 100644 runtime-light/utils/php_assert.h diff --git a/builtin-functions/kphp-light/functions.txt b/builtin-functions/kphp-light/functions.txt index 40fa259d2e..0ab5a3dfee 100644 --- a/builtin-functions/kphp-light/functions.txt +++ b/builtin-functions/kphp-light/functions.txt @@ -85,7 +85,7 @@ function wait(future | false $id, float $timeout = -1.0) ::: ^1[*] | null; function sched_yield() ::: void; /** @kphp-extern-func-info interruptible */ -function sched_yield_sleep($timeout_ns ::: int) ::: void; +function sched_yield_sleep($duration ::: float) ::: void; // === Rpc ======================================================================================== diff --git a/compiler/code-gen/declarations.cpp b/compiler/code-gen/declarations.cpp index 79c441ee2a..c3b0359b83 100644 --- a/compiler/code-gen/declarations.cpp +++ b/compiler/code-gen/declarations.cpp @@ -72,9 +72,7 @@ void FunctionDeclaration::compile(CodeGenerator &W) const { switch (style) { case gen_out_style::tagger: case gen_out_style::cpp: { - if (function->is_k2_fork) { - FunctionSignatureGenerator(W) << "task_t " << FunctionName(function) << "(" << params_gen << ")"; - } else if (function->is_interruptible) { + if (function->is_interruptible) { FunctionSignatureGenerator(W) << "task_t<" << ret_type_gen << ">" << " " << FunctionName(function) << "(" << params_gen << ")"; } else { FunctionSignatureGenerator(W) << ret_type_gen << " " << FunctionName(function) << "(" << params_gen << ")"; diff --git a/compiler/code-gen/vertex-compiler.cpp b/compiler/code-gen/vertex-compiler.cpp index b13f5b062f..177251ae87 100644 --- a/compiler/code-gen/vertex-compiler.cpp +++ b/compiler/code-gen/vertex-compiler.cpp @@ -849,7 +849,7 @@ void compile_func_call(VertexAdaptor root, CodeGenerator &W, func_ if (mode == func_call_mode::fork_call) { if (func->is_interruptible) { - W << "(co_await start_fork_t{" << FunctionName(func); + W << "(co_await start_fork_t{static_cast>(" << FunctionName(func); } else { W << FunctionForkName(func); } @@ -883,9 +883,7 @@ void compile_func_call(VertexAdaptor root, CodeGenerator &W, func_ W << ")"; if (func->is_interruptible) { if (mode == func_call_mode::fork_call) { - W << ", start_fork_t::execution::fork})"; - } else if (func->is_k2_fork) { // k2 fork's return type is 'task_t' so we need to unpack actual result from fork_result - W << ").get_result<" << TypeName(tinf::get_type(root)) << ">()"; + W << "), start_fork_t::execution::fork})"; } else { W << ")"; } diff --git a/runtime-core/utils/small-object-storage.h b/runtime-core/utils/small-object-storage.h index 2ea08dbfe7..efa7f9ecf6 100644 --- a/runtime-core/utils/small-object-storage.h +++ b/runtime-core/utils/small-object-storage.h @@ -9,7 +9,7 @@ #include #include -#include "runtime-core/runtime-core.h" +#include "runtime-core/allocator/runtime-allocator.h" template union small_object_storage { diff --git a/runtime-light/allocator/runtime-light-allocator.cpp b/runtime-light/allocator/runtime-light-allocator.cpp index 50ac7625c0..6895e1a1e2 100644 --- a/runtime-light/allocator/runtime-light-allocator.cpp +++ b/runtime-light/allocator/runtime-light-allocator.cpp @@ -5,9 +5,8 @@ #include #include -#include "runtime-core/runtime-core.h" +#include "runtime-core/utils/kphp-assert-core.h" #include "runtime-light/component/component.h" -#include "runtime-light/utils/panic.h" namespace { // TODO: make it depend on max chunk size, e.g. MIN_EXTRA_MEM_SIZE = f(MAX_CHUNK_SIZE); @@ -136,4 +135,4 @@ void *RuntimeAllocator::realloc_global_memory(void *mem, size_t new_size, size_t void RuntimeAllocator::free_global_memory(void *mem, size_t) noexcept { get_platform_context()->allocator.free(mem); -} \ No newline at end of file +} diff --git a/runtime-light/coroutine/awaitable.h b/runtime-light/coroutine/awaitable.h index c27ab80eb3..f55d2f1228 100644 --- a/runtime-light/coroutine/awaitable.h +++ b/runtime-light/coroutine/awaitable.h @@ -19,7 +19,6 @@ #include "runtime-light/header.h" #include "runtime-light/scheduler/scheduler.h" #include "runtime-light/stdlib/fork/fork-context.h" -#include "runtime-light/stdlib/fork/fork.h" #include "runtime-light/utils/context.h" template @@ -277,7 +276,7 @@ class start_fork_t { SuspendToken suspend_token{std::noop_coroutine(), WaitEvent::Rechedule{}}; public: - explicit start_fork_t(task_t &&task_, execution exec_policy_) noexcept + explicit start_fork_t(task_t task_, execution exec_policy_) noexcept : exec_policy(exec_policy_) , fork_coro(task_.get_handle()) , fork_id(ForkComponentContext::get().push_fork(std::move(task_))) {} @@ -328,13 +327,16 @@ class start_fork_t { template class wait_fork_t { int64_t fork_id; - task_t fork_task; - task_t::awaiter_t fork_awaiter; + task_t fork_task; + task_t::awaiter_t fork_awaiter; + + using fork_resume_t = decltype(fork_awaiter.await_resume()); + using await_resume_t = fork_resume_t; public: explicit wait_fork_t(int64_t fork_id_) noexcept : fork_id(fork_id_) - , fork_task(ForkComponentContext::get().pop_fork(fork_id)) + , fork_task(static_cast>(ForkComponentContext::get().pop_fork(fork_id))) , fork_awaiter(std::addressof(fork_task)) {} wait_fork_t(wait_fork_t &&other) noexcept @@ -355,8 +357,12 @@ class wait_fork_t { fork_awaiter.await_suspend(coro); } - T await_resume() noexcept { - return fork_awaiter.await_resume().get_result(); + await_resume_t await_resume() noexcept { + if constexpr (std::is_void_v) { + fork_awaiter.await_resume(); + } else { + return fork_awaiter.await_resume(); + } } constexpr bool resumable() const noexcept { diff --git a/runtime-light/coroutine/task.h b/runtime-light/coroutine/task.h index ae368e9b63..db8dff5c08 100644 --- a/runtime-light/coroutine/task.h +++ b/runtime-light/coroutine/task.h @@ -5,6 +5,7 @@ #pragma once #include +#include #include #include #include @@ -62,7 +63,6 @@ struct task_t : public task_base_t { struct promise_void_t; using promise_type = std::conditional_t{}, promise_non_void_t, promise_void_t>; - using task_base_t::task_base_t; struct promise_base_t { @@ -225,4 +225,16 @@ struct task_t : public task_base_t { std::coroutine_handle get_handle() { return std::coroutine_handle::from_address(handle_address); } + + // conversion functions + // + // erase type + explicit operator task_t() && noexcept { + return task_t{std::coroutine_handle<>::from_address(std::exchange(handle_address, nullptr))}; + } + // restore erased type + template + requires(std::same_as) explicit operator task_t() && noexcept { + return task_t{std::coroutine_handle<>::from_address(std::exchange(handle_address, nullptr))}; + } }; diff --git a/runtime-light/stdlib/fork/fork-api.cpp b/runtime-light/stdlib/fork/fork-api.cpp index b8e68746ce..fca25d4357 100644 --- a/runtime-light/stdlib/fork/fork-api.cpp +++ b/runtime-light/stdlib/fork/fork-api.cpp @@ -5,7 +5,6 @@ #include "runtime-light/stdlib/fork/fork-api.h" #include -#include #include "runtime-core/utils/kphp-assert-core.h" #include "runtime-light/coroutine/awaitable.h" @@ -15,10 +14,10 @@ task_t f$sched_yield() noexcept { co_await wait_for_reschedule_t{}; } -task_t f$sched_yield_sleep(int64_t duration_ns) noexcept { - if (duration_ns < 0) { - php_warning("can't sleep for negative duration %" PRId64, duration_ns); +task_t f$sched_yield_sleep(double duration) noexcept { + if (duration <= 0) { + php_warning("can't sleep for negative or zero duration %.9f", duration); co_return; } - co_await wait_for_timer_t{std::chrono::nanoseconds{static_cast(duration_ns)}}; + co_await wait_for_timer_t{std::chrono::duration_cast(std::chrono::duration{duration})}; } diff --git a/runtime-light/stdlib/fork/fork-api.h b/runtime-light/stdlib/fork/fork-api.h index 99b3fa2167..55e05d0fe4 100644 --- a/runtime-light/stdlib/fork/fork-api.h +++ b/runtime-light/stdlib/fork/fork-api.h @@ -5,9 +5,11 @@ #pragma once #include +#include #include #include "runtime-core/core-types/decl/optional.h" +#include "runtime-core/runtime-core.h" #include "runtime-core/utils/kphp-assert-core.h" #include "runtime-light/coroutine/awaitable.h" #include "runtime-light/coroutine/task.h" @@ -23,7 +25,7 @@ constexpr auto DEFAULT_TIMEOUT_NS = std::chrono::duration_cast -requires(is_optional::value) task_t f$wait(int64_t fork_id, double timeout = -1.0) noexcept { +requires(is_optional::value || std::same_as) task_t f$wait(int64_t fork_id, double timeout = -1.0) noexcept { auto &fork_ctx{ForkComponentContext::get()}; if (!fork_ctx.contains(fork_id)) { php_warning("can't find fork %" PRId64, fork_id); @@ -38,10 +40,10 @@ requires(is_optional::value) task_t f$wait(int64_t fork_id, double timeout } template -requires(is_optional::value) task_t f$wait(Optional fork_id_opt, double timeout = -1.0) noexcept { +requires(is_optional::value || std::same_as) task_t f$wait(Optional fork_id_opt, double timeout = -1.0) noexcept { co_return co_await f$wait(fork_id_opt.has_value() ? fork_id_opt.val() : INVALID_FORK_ID, timeout); } task_t f$sched_yield() noexcept; -task_t f$sched_yield_sleep(int64_t duration_ns) noexcept; +task_t f$sched_yield_sleep(double duration) noexcept; diff --git a/runtime-light/stdlib/fork/fork-context.h b/runtime-light/stdlib/fork/fork-context.h index 57c67ec1af..0788204439 100644 --- a/runtime-light/stdlib/fork/fork-context.h +++ b/runtime-light/stdlib/fork/fork-context.h @@ -10,7 +10,6 @@ #include "runtime-core/memory-resource/unsynchronized_pool_resource.h" #include "runtime-core/utils/kphp-assert-core.h" #include "runtime-light/coroutine/task.h" -#include "runtime-light/stdlib/fork/fork.h" #include "runtime-light/utils/concepts.h" constexpr int64_t INVALID_FORK_ID = -1; @@ -20,15 +19,15 @@ class ForkComponentContext { using unordered_map = memory_resource::stl::unordered_map; static constexpr auto FORK_ID_INIT = 0; - - unordered_map> forks; + // type erased tasks that represent forks + unordered_map> forks; int64_t next_fork_id{FORK_ID_INIT + 1}; - int64_t push_fork(task_t &&task) noexcept { + int64_t push_fork(task_t task) noexcept { return forks.emplace(next_fork_id, std::move(task)), next_fork_id++; } - task_t pop_fork(int64_t fork_id) noexcept { + task_t pop_fork(int64_t fork_id) noexcept { const auto it_fork{forks.find(fork_id)}; if (it_fork == forks.end()) { php_critical_error("can't find fork %" PRId64, fork_id); @@ -44,7 +43,7 @@ class ForkComponentContext { public: explicit ForkComponentContext(memory_resource::unsynchronized_pool_resource &memory_resource) noexcept - : forks(unordered_map>::allocator_type{memory_resource}) {} + : forks(unordered_map>::allocator_type{memory_resource}) {} static ForkComponentContext &get() noexcept; diff --git a/runtime-light/stdlib/fork/fork.h b/runtime-light/stdlib/fork/fork.h deleted file mode 100644 index 0837de8d38..0000000000 --- a/runtime-light/stdlib/fork/fork.h +++ /dev/null @@ -1,27 +0,0 @@ -// 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-core/utils/small-object-storage.h" - -class fork_result { - small_object_storage storage{}; - -public: - template - requires(!std::same_as) explicit fork_result(T &&t) noexcept { - storage.emplace>(std::forward(t)); - } - - template - T get_result() noexcept { - return *storage.get(); - } -}; diff --git a/runtime-light/stdlib/misc.cpp b/runtime-light/stdlib/misc.cpp index daca639574..a0f342f522 100644 --- a/runtime-light/stdlib/misc.cpp +++ b/runtime-light/stdlib/misc.cpp @@ -6,13 +6,13 @@ #include +#include "runtime-core/utils/kphp-assert-core.h" #include "runtime-light/component/component.h" #include "runtime-light/coroutine/awaitable.h" #include "runtime-light/header.h" #include "runtime-light/stdlib/superglobals.h" #include "runtime-light/streams/streams.h" #include "runtime-light/utils/context.h" -#include "runtime-light/utils/panic.h" namespace { @@ -60,7 +60,7 @@ task_t finish(int64_t exit_code, bool from_exit) { // TODO: use exit code const auto ob_total_buffer = ob_merge_buffers(); Response &response = component_ctx.response; auto &buffer = response.output_buffers[ob_total_buffer]; - if (co_await write_all_to_stream(standard_stream, buffer.c_str(), buffer.size())) { + if ((co_await write_all_to_stream(standard_stream, buffer.c_str(), buffer.size())) != buffer.size()) { php_warning("can't write component result to stream %" PRIu64, standard_stream); } component_ctx.release_all_streams(); diff --git a/runtime-light/stdlib/rpc/rpc-api.cpp b/runtime-light/stdlib/rpc/rpc-api.cpp index 3fd36bbeae..ea727da862 100644 --- a/runtime-light/stdlib/rpc/rpc-api.cpp +++ b/runtime-light/stdlib/rpc/rpc-api.cpp @@ -132,7 +132,7 @@ task_t rpc_send_impl(string actor, double timeout, bool ignore_ans : DEFAULT_TIMEOUT_NS}; // create fork to wait for RPC response. we need to do it even if 'ignore_answer' is 'true' to make sure // that the stream will not be closed too early. otherwise, platform may even not send RPC request - auto waiter_task{[](int64_t query_id, auto comp_query, std::chrono::nanoseconds timeout, bool collect_responses_extra_info) noexcept -> task_t { + auto waiter_task{[](int64_t query_id, auto comp_query, std::chrono::nanoseconds timeout, bool collect_responses_extra_info) noexcept -> task_t { auto fetch_task{f$component_client_get_result(std::move(comp_query))}; const auto response{(co_await wait_with_timeout_t{task_t::awaiter_t{std::addressof(fetch_task)}, timeout}).value_or(string{})}; // update response extra info if needed @@ -149,7 +149,7 @@ task_t rpc_send_impl(string actor, double timeout, bool ignore_ans co_return response; }(query_id, std::move(comp_query), timeout_ns, collect_responses_extra_info)}; // start waiter fork - const auto waiter_fork_id{co_await start_fork_t{std::move(waiter_task), start_fork_t::execution::self}}; + const auto waiter_fork_id{co_await start_fork_t{static_cast>(std::move(waiter_task)), start_fork_t::execution::self}}; if (ignore_answer) { co_return RpcQueryInfo{.id = RPC_IGNORED_ANSWER_QUERY_ID, .request_size = request_size, .timestamp = timestamp}; diff --git a/runtime-light/stdlib/string-functions.cpp b/runtime-light/stdlib/string-functions.cpp index 3a839a66d1..5039cef93e 100644 --- a/runtime-light/stdlib/string-functions.cpp +++ b/runtime-light/stdlib/string-functions.cpp @@ -35,14 +35,14 @@ void f$debug_print_string(const string &s) { Optional f$byte_to_int(const string &s) { if (s.size() != 1) { php_warning("Cannot convert non-byte string to int"); - return Optional(); + return {}; } return *s.c_str(); } Optional f$int_to_byte(int64_t v) { if (v > 127 || v < -128) { - php_warning("Cannot convert too big int to byte %ld", v); + php_warning("Cannot convert too big int to byte %" PRId64, v); return false; } char c = v; diff --git a/runtime-light/stdlib/string-functions.h b/runtime-light/stdlib/string-functions.h index c2ba29b38f..a2264a2fc4 100644 --- a/runtime-light/stdlib/string-functions.h +++ b/runtime-light/stdlib/string-functions.h @@ -1,7 +1,6 @@ #pragma once #include "runtime-core/runtime-core.h" -#include void print(const char *s, size_t s_len); diff --git a/runtime-light/stdlib/timer/timer.h b/runtime-light/stdlib/timer/timer.h index b771d05fcf..e3c96883c7 100644 --- a/runtime-light/stdlib/timer/timer.h +++ b/runtime-light/stdlib/timer/timer.h @@ -19,10 +19,9 @@ task_t f$set_timer(int64_t timeout_ms, T &&on_timer_callback) noexcept { php_warning("can't set timer for negative duration %" PRId64 "ms", timeout_ms); co_return; } - const auto fork_f{[](std::chrono::nanoseconds duration, T &&on_timer_callback) -> task_t { + const auto fork_f{[](std::chrono::nanoseconds duration, T &&on_timer_callback) -> task_t { co_await wait_for_timer_t{duration}; on_timer_callback(); - co_return 0; }}; // TODO: someone should pop that fork from ForkComponentContext since it will stay there unless we perform f$wait on fork const auto duration_ms{std::chrono::milliseconds{static_cast(timeout_ms)}}; co_await start_fork_t{fork_f(std::chrono::duration_cast(duration_ms), std::forward(on_timer_callback)), diff --git a/runtime-light/utils/panic.h b/runtime-light/utils/panic.cpp similarity index 85% rename from runtime-light/utils/panic.h rename to runtime-light/utils/panic.cpp index 88fc2e6b1b..059db31fd8 100644 --- a/runtime-light/utils/panic.h +++ b/runtime-light/utils/panic.cpp @@ -2,16 +2,15 @@ // Copyright (c) 2024 LLC «V Kontakte» // Distributed under the GPL v3 License, see LICENSE.notice.txt -#pragma once - #include -#include "context.h" +#include "runtime-core/utils/kphp-assert-core.h" #include "runtime-light/component/component.h" #include "runtime-light/header.h" +#include "runtime-light/utils/context.h" #include "runtime-light/utils/logs.h" -inline void critical_error_handler() { +void critical_error_handler() { constexpr const char *message = "script panic"; const auto &platform_ctx = *get_platform_context(); auto &component_ctx = *get_component_context(); diff --git a/runtime-light/utils/php_assert.cpp b/runtime-light/utils/php_assert.cpp index 0d869406bd..190caca8d3 100644 --- a/runtime-light/utils/php_assert.cpp +++ b/runtime-light/utils/php_assert.cpp @@ -2,9 +2,6 @@ // Copyright (c) 2020 LLC «V Kontakte» // Distributed under the GPL v3 License, see LICENSE.notice.txt -#include "runtime-light/utils/php_assert.h" - -#include #include #include #include @@ -16,7 +13,10 @@ #include #include -#include "runtime-light/utils/panic.h" +#include "runtime-core/utils/kphp-assert-core.h" +#include "runtime-light/header.h" +#include "runtime-light/utils/context.h" +#include "runtime-light/utils/logs.h" static void php_warning_impl(bool out_of_memory, int error_type, char const *message, va_list args) { (void)out_of_memory; diff --git a/runtime-light/utils/php_assert.h b/runtime-light/utils/php_assert.h deleted file mode 100644 index 41d30d4f7b..0000000000 --- a/runtime-light/utils/php_assert.h +++ /dev/null @@ -1,13 +0,0 @@ -// Compiler for PHP (aka KPHP) -// Copyright (c) 2020 LLC «V Kontakte» -// Distributed under the GPL v3 License, see LICENSE.notice.txt - -#pragma once - -#include -#include - -#include "common/wrappers/likely.h" -#include "common/mixin/not_copyable.h" - -#include "runtime-core/utils/kphp-assert-core.h" diff --git a/runtime-light/utils/utils.cmake b/runtime-light/utils/utils.cmake index fee0426cd6..f74079da0d 100644 --- a/runtime-light/utils/utils.cmake +++ b/runtime-light/utils/utils.cmake @@ -1 +1 @@ -prepend(RUNTIME_UTILS_SRC utils/ php_assert.cpp json-functions.cpp context.cpp) +prepend(RUNTIME_UTILS_SRC utils/ panic.cpp php_assert.cpp json-functions.cpp context.cpp) diff --git a/tests/kphp_tester.py b/tests/kphp_tester.py index 82016c0e9d..abb6253249 100755 --- a/tests/kphp_tester.py +++ b/tests/kphp_tester.py @@ -60,7 +60,7 @@ def make_kphp_once_runner(self, use_nocc, cxx_name, k2_bin): vkext_dir=os.path.abspath(os.path.join(tester_dir, os.path.pardir, "objs", "vkext")), use_nocc=use_nocc, cxx_name=cxx_name, - k2_bin=k2_bin + k2_bin=os.path.abspath(k2_bin) ) def set_up_env_for_k2(self, cxx_name="clang++"): @@ -68,7 +68,6 @@ def set_up_env_for_k2(self, cxx_name="clang++"): self.env_vars["KPHP_USER_BINARY_PATH"] = "component.so" self.env_vars["KPHP_ENABLE_GLOBAL_VARS_MEMORY_STATS"] = "0" self.env_vars["KPHP_PROFILER"] = "0" - self.env_vars["KPHP_CXX"] = cxx_name self.env_vars["KPHP_K2_COMPONENT_IS_ONESHOT"] = "1" self.env_vars["KPHP_FORCE_LINK_RUNTIME"] = "1" diff --git a/tests/phpt/dl/474_flex.php b/tests/phpt/dl/474_flex.php index e1e26791df..ce61cef0e9 100644 --- a/tests/phpt/dl/474_flex.php +++ b/tests/phpt/dl/474_flex.php @@ -1,4 +1,4 @@ -@ok benchmark +@ok k2_skip benchmark 1 2 3 -waited 1123456789 -waited 1123456790 -waited 1123456791 +waited 0.03 +waited 0.01 +waited 0.02 Date: Tue, 27 Aug 2024 18:00:16 +0300 Subject: [PATCH 28/45] add get_running_fork_id (#1054) --- builtin-functions/kphp-light/functions.txt | 2 ++ runtime-light/component/component.cpp | 3 +++ runtime-light/coroutine/awaitable.h | 30 +++++++++++++++++----- runtime-light/stdlib/fork/fork-api.h | 9 +++++++ runtime-light/stdlib/fork/fork-context.h | 2 ++ 5 files changed, 39 insertions(+), 7 deletions(-) diff --git a/builtin-functions/kphp-light/functions.txt b/builtin-functions/kphp-light/functions.txt index 0ab5a3dfee..65fa664efa 100644 --- a/builtin-functions/kphp-light/functions.txt +++ b/builtin-functions/kphp-light/functions.txt @@ -78,6 +78,8 @@ function strlen ($str ::: string) ::: int; // === Fork ======================================================================================= +function get_running_fork_id() ::: future ; + /** @kphp-extern-func-info interruptible cpp_template_call */ function wait(future | false $id, float $timeout = -1.0) ::: ^1[*] | null; diff --git a/runtime-light/component/component.cpp b/runtime-light/component/component.cpp index e1a81c95e1..4d7f4b678c 100644 --- a/runtime-light/component/component.cpp +++ b/runtime-light/component/component.cpp @@ -26,6 +26,7 @@ void ComponentState::process_platform_updates() noexcept { for (;;) { // check if platform asked for yield if (static_cast(platform_ctx.please_yield.load())) { // tell the scheduler that we are about to yield + php_debug("platform asked for yield"); const auto schedule_status{scheduler.schedule(ScheduleEvent::Yield{})}; poll_status = schedule_status == ScheduleStatus::Error ? PollStatus::PollFinishedError : PollStatus::PollReschedule; return; @@ -34,6 +35,7 @@ void ComponentState::process_platform_updates() noexcept { // try taking update from the platform if (uint64_t stream_d{}; static_cast(platform_ctx.take_update(std::addressof(stream_d)))) { if (opened_streams_.contains(stream_d)) { // update on opened stream + php_debug("took update on stream %" PRIu64, stream_d); switch (scheduler.schedule(ScheduleEvent::UpdateOnStream{.stream_d = stream_d})) { case ScheduleStatus::Resumed: { // scheduler's resumed a coroutine waiting for update break; @@ -48,6 +50,7 @@ void ComponentState::process_platform_updates() noexcept { } } } else { // update on incoming stream + php_debug("got new incoming stream %" PRIu64, stream_d); if (standard_stream_ != INVALID_PLATFORM_DESCRIPTOR) { php_warning("skip new incoming stream since previous one is not closed"); release_stream(stream_d); diff --git a/runtime-light/coroutine/awaitable.h b/runtime-light/coroutine/awaitable.h index f55d2f1228..2d5f04f256 100644 --- a/runtime-light/coroutine/awaitable.h +++ b/runtime-light/coroutine/awaitable.h @@ -46,9 +46,18 @@ namespace awaitable_impl_ { enum class State : uint8_t { Init, Suspend, Ready, End }; +class fork_id_watcher_t { + int64_t fork_id{ForkComponentContext::get().running_fork_id}; + +protected: + void await_resume() const noexcept { + ForkComponentContext::get().running_fork_id = fork_id; + } +}; + } // namespace awaitable_impl_ -class wait_for_update_t { +class wait_for_update_t : public awaitable_impl_::fork_id_watcher_t { uint64_t stream_d; SuspendToken suspend_token; awaitable_impl_::State state{awaitable_impl_::State::Init}; @@ -86,6 +95,7 @@ class wait_for_update_t { } constexpr void await_resume() noexcept { + fork_id_watcher_t::await_resume(); state = awaitable_impl_::State::End; } @@ -101,7 +111,7 @@ class wait_for_update_t { // ================================================================================================ -class wait_for_incoming_stream_t { +class wait_for_incoming_stream_t : awaitable_impl_::fork_id_watcher_t { SuspendToken suspend_token{std::noop_coroutine(), WaitEvent::IncomingStream{}}; awaitable_impl_::State state{awaitable_impl_::State::Init}; @@ -135,6 +145,7 @@ class wait_for_incoming_stream_t { } uint64_t await_resume() noexcept { + fork_id_watcher_t::await_resume(); state = awaitable_impl_::State::End; const auto incoming_stream_d{get_component_context()->take_incoming_stream()}; php_assert(incoming_stream_d != INVALID_PLATFORM_DESCRIPTOR); @@ -153,7 +164,7 @@ class wait_for_incoming_stream_t { // ================================================================================================ -class wait_for_reschedule_t { +class wait_for_reschedule_t : awaitable_impl_::fork_id_watcher_t { SuspendToken suspend_token{std::noop_coroutine(), WaitEvent::Rechedule{}}; awaitable_impl_::State state{awaitable_impl_::State::Init}; @@ -186,6 +197,7 @@ class wait_for_reschedule_t { } constexpr void await_resume() noexcept { + fork_id_watcher_t::await_resume(); state = awaitable_impl_::State::End; } @@ -201,7 +213,7 @@ class wait_for_reschedule_t { // ================================================================================================ -class wait_for_timer_t { +class wait_for_timer_t : awaitable_impl_::fork_id_watcher_t { std::chrono::nanoseconds duration; uint64_t timer_d{INVALID_PLATFORM_DESCRIPTOR}; SuspendToken suspend_token{std::noop_coroutine(), WaitEvent::Rechedule{}}; @@ -245,6 +257,7 @@ class wait_for_timer_t { } constexpr void await_resume() noexcept { + fork_id_watcher_t::await_resume(); state = awaitable_impl_::State::End; } @@ -260,7 +273,7 @@ class wait_for_timer_t { // ================================================================================================ -class start_fork_t { +class start_fork_t : awaitable_impl_::fork_id_watcher_t { public: /** * Fork start policy: @@ -302,6 +315,7 @@ class start_fork_t { case execution::fork: { suspend_token.first = current_coro; continuation = fork_coro; + ForkComponentContext::get().running_fork_id = fork_id; break; } case execution::self: { @@ -317,7 +331,8 @@ class start_fork_t { return continuation; } - constexpr int64_t await_resume() const noexcept { + int64_t await_resume() const noexcept { + fork_id_watcher_t::await_resume(); return fork_id; } }; @@ -325,7 +340,7 @@ class start_fork_t { // ================================================================================================ template -class wait_fork_t { +class wait_fork_t : awaitable_impl_::fork_id_watcher_t { int64_t fork_id; task_t fork_task; task_t::awaiter_t fork_awaiter; @@ -358,6 +373,7 @@ class wait_fork_t { } await_resume_t await_resume() noexcept { + fork_id_watcher_t::await_resume(); if constexpr (std::is_void_v) { fork_awaiter.await_resume(); } else { diff --git a/runtime-light/stdlib/fork/fork-api.h b/runtime-light/stdlib/fork/fork-api.h index 55e05d0fe4..20b1fcc1b4 100644 --- a/runtime-light/stdlib/fork/fork-api.h +++ b/runtime-light/stdlib/fork/fork-api.h @@ -24,6 +24,9 @@ constexpr auto DEFAULT_TIMEOUT_NS = std::chrono::duration_cast requires(is_optional::value || std::same_as) task_t f$wait(int64_t fork_id, double timeout = -1.0) noexcept { auto &fork_ctx{ForkComponentContext::get()}; @@ -44,6 +47,12 @@ requires(is_optional::value || std::same_as) task_t f$wait(Optio co_return co_await f$wait(fork_id_opt.has_value() ? fork_id_opt.val() : INVALID_FORK_ID, timeout); } +// === Non-blocking API ============================================================================ + +inline int64_t f$get_running_fork_id() noexcept { + return ForkComponentContext::get().running_fork_id; +} + task_t f$sched_yield() noexcept; task_t f$sched_yield_sleep(double duration) noexcept; diff --git a/runtime-light/stdlib/fork/fork-context.h b/runtime-light/stdlib/fork/fork-context.h index 0788204439..153e34fdd0 100644 --- a/runtime-light/stdlib/fork/fork-context.h +++ b/runtime-light/stdlib/fork/fork-context.h @@ -42,6 +42,8 @@ class ForkComponentContext { friend class wait_fork_t; public: + int64_t running_fork_id{FORK_ID_INIT}; + explicit ForkComponentContext(memory_resource::unsynchronized_pool_resource &memory_resource) noexcept : forks(unordered_map>::allocator_type{memory_resource}) {} From a6b6d898c79c884e1ab9b4fdb57dc7e3e0928d60 Mon Sep 17 00:00:00 2001 From: Alexander Polyakov Date: Tue, 27 Aug 2024 18:33:25 +0300 Subject: [PATCH 29/45] Rework K2 stream API (#1077) --- builtin-functions/kphp-light/functions.txt | 43 ++---- compiler/code-gen/files/init-scripts.cpp | 4 +- runtime-light/component/component.h | 2 +- .../stdlib/component/component-api.cpp | 74 ++++++++++ .../stdlib/component/component-api.h | 59 ++++++++ runtime-light/stdlib/exit/exit-functions.cpp | 65 +++++++++ .../{interface.h => exit/exit-functions.h} | 9 +- runtime-light/stdlib/fork/fork-api.cpp | 23 --- .../fork/{fork-api.h => fork-functions.h} | 17 ++- runtime-light/stdlib/interface.cpp | 19 --- runtime-light/stdlib/math/random-functions.h | 10 ++ runtime-light/stdlib/misc.cpp | 92 ------------ runtime-light/stdlib/misc.h | 23 --- runtime-light/stdlib/output-control.h | 33 ----- .../output-buffer.cpp} | 92 ++++++------ runtime-light/stdlib/output/output-buffer.h | 35 +++++ .../print-functions.cpp} | 133 ++++++++--------- runtime-light/stdlib/output/print-functions.h | 71 ++++++++++ runtime-light/stdlib/rpc/rpc-api.cpp | 8 +- runtime-light/stdlib/stdlib.cmake | 14 +- .../concat.cpp} | 69 ++------- .../{string-functions.h => string/concat.h} | 33 +---- .../stdlib/string/string-functions.h | 11 ++ runtime-light/stdlib/superglobals.cpp | 17 --- .../{timer/timer.h => time/timer-functions.h} | 0 .../value-functions.h} | 7 +- runtime-light/stdlib/variable-handling.h | 32 ----- runtime-light/streams/component-stream.cpp | 84 ----------- runtime-light/streams/component-stream.h | 35 ----- runtime-light/streams/interface.cpp | 134 ------------------ runtime-light/streams/interface.h | 45 ------ runtime-light/streams/streams.cmake | 6 +- runtime-light/streams/streams.cpp | 33 ++++- runtime-light/streams/streams.h | 18 ++- tests/k2-components/dev_null.php | 2 +- tests/k2-components/dev_random.php | 24 ---- tests/k2-components/echo.php | 5 +- tests/k2-components/forward.php | 9 +- tests/k2-components/test_rpc_memcached.php | 6 +- tests/k2-components/tight_loop.php | 2 + tests/k2-components/yield_loop.php | 2 + tests/phpt/json/38_optimized_init_val.php | 2 +- 42 files changed, 556 insertions(+), 846 deletions(-) create mode 100644 runtime-light/stdlib/component/component-api.cpp create mode 100644 runtime-light/stdlib/component/component-api.h create mode 100644 runtime-light/stdlib/exit/exit-functions.cpp rename runtime-light/stdlib/{interface.h => exit/exit-functions.h} (57%) delete mode 100644 runtime-light/stdlib/fork/fork-api.cpp rename runtime-light/stdlib/fork/{fork-api.h => fork-functions.h} (85%) delete mode 100644 runtime-light/stdlib/interface.cpp create mode 100644 runtime-light/stdlib/math/random-functions.h delete mode 100644 runtime-light/stdlib/misc.cpp delete mode 100644 runtime-light/stdlib/misc.h delete mode 100644 runtime-light/stdlib/output-control.h rename runtime-light/stdlib/{output-control.cpp => output/output-buffer.cpp} (56%) create mode 100644 runtime-light/stdlib/output/output-buffer.h rename runtime-light/stdlib/{variable-handling.cpp => output/print-functions.cpp} (54%) create mode 100644 runtime-light/stdlib/output/print-functions.h rename runtime-light/stdlib/{string-functions.cpp => string/concat.cpp} (52%) rename runtime-light/stdlib/{string-functions.h => string/concat.h} (77%) create mode 100644 runtime-light/stdlib/string/string-functions.h delete mode 100644 runtime-light/stdlib/superglobals.cpp rename runtime-light/stdlib/{timer/timer.h => time/timer-functions.h} (100%) rename runtime-light/stdlib/{superglobals.h => value/value-functions.h} (59%) delete mode 100644 runtime-light/stdlib/variable-handling.h delete mode 100644 runtime-light/streams/component-stream.cpp delete mode 100644 runtime-light/streams/component-stream.h delete mode 100644 runtime-light/streams/interface.cpp delete mode 100644 runtime-light/streams/interface.h delete mode 100644 tests/k2-components/dev_random.php diff --git a/builtin-functions/kphp-light/functions.txt b/builtin-functions/kphp-light/functions.txt index 65fa664efa..199ac70837 100644 --- a/builtin-functions/kphp-light/functions.txt +++ b/builtin-functions/kphp-light/functions.txt @@ -31,6 +31,7 @@ function strval ($v ::: mixed) ::: string; /** @kphp-extern-func-info interruptible */ function exit($code = 0) ::: void; +/** @kphp-extern-func-info interruptible */ function die($code = 0) ::: void; function ob_clean() ::: void; @@ -76,13 +77,15 @@ function get_hash_of_class (object $klass) ::: int; function strlen ($str ::: string) ::: int; -// === Fork ======================================================================================= +// === Future ===================================================================================== function get_running_fork_id() ::: future ; /** @kphp-extern-func-info interruptible cpp_template_call */ function wait(future | false $id, float $timeout = -1.0) ::: ^1[*] | null; +// === Fork ======================================================================================= + /** @kphp-extern-func-info interruptible */ function sched_yield() ::: void; @@ -141,42 +144,19 @@ class ComponentQuery { } /** @kphp-extern-func-info interruptible */ -function component_get_http_query() ::: void; +function component_client_send_request($name ::: string, $message ::: string) ::: ComponentQuery; /** @kphp-extern-func-info interruptible */ -function component_client_send_query($name ::: string, $message ::: string) ::: ComponentQuery; -/** @kphp-extern-func-info interruptible */ -function component_client_get_result($query ::: ComponentQuery) ::: string; +function component_client_fetch_response($query ::: ComponentQuery) ::: string; /** @kphp-extern-func-info interruptible */ -function component_server_get_query() ::: string; -/** @kphp-extern-func-info interruptible */ -function component_server_send_result($message ::: string) ::: void; - -class ComponentStream { - private function __construct() ::: \ComponentStream; +function component_server_accept_query() ::: ComponentQuery; - public function is_read_closed() ::: bool; - public function is_write_closed() ::: bool; - public function is_please_shutdown_write() ::: bool; - - public function shutdown_write() ::: void; - public function please_shutdown_write() ::: void; -} - -function component_open_stream($name ::: string) ::: ComponentStream; /** @kphp-extern-func-info interruptible */ -function component_accept_stream() ::: ComponentStream; +function component_server_fetch_request($query ::: ComponentQuery) ::: string; -function component_stream_write_nonblock($stream ::: ComponentStream, $message ::: string) ::: int; -function component_stream_read_nonblock($stream ::: ComponentStream) ::: string; -/** @kphp-extern-func-info interruptible */ -function component_stream_write_exact($stream ::: ComponentStream, $message ::: string) ::: int; /** @kphp-extern-func-info interruptible */ -function component_stream_read_exact($stream ::: ComponentStream, $len ::: int) ::: string; - -function component_close_stream($stream ::: ComponentStream) ::: void; -function component_finish_stream_processing($stream ::: ComponentStream) ::: void; +function component_server_send_response($query ::: ComponentQuery, $message ::: string) ::: void; // === Json ======================================================================================= @@ -217,11 +197,6 @@ function warning($message ::: string) ::: void; /** @kphp-no-return */ function critical_error($message ::: string) ::: void; -function debug_print_string($str ::: string) ::: void; - -function byte_to_int($str ::: string) ::: ?int; -function int_to_byte($v ::: int) ::: ?string; - /** @kphp-extern-func-info interruptible */ function set_timer(int $timeout, callable():void $callback) ::: void; diff --git a/compiler/code-gen/files/init-scripts.cpp b/compiler/code-gen/files/init-scripts.cpp index de4e91952d..3ec4d17c72 100644 --- a/compiler/code-gen/files/init-scripts.cpp +++ b/compiler/code-gen/files/init-scripts.cpp @@ -118,8 +118,8 @@ struct RunInterruptedFunction { * 1) Start when the request came in * 2) Collecting output buffer after script finished **/ - std::string script_start = G->settings().k2_component_is_oneshot.get() ? "co_await f$component_get_http_query();" : ""; - std::string script_finish = G->settings().k2_component_is_oneshot.get() ? "co_await finish(0, false);" : ""; + std::string script_start = G->settings().k2_component_is_oneshot.get() ? "co_await accept_initial_stream();" : ""; + std::string script_finish = G->settings().k2_component_is_oneshot.get() ? "co_await shutdown_script();" : ""; FunctionSignatureGenerator(W) << "task_t " << FunctionName(function) << "$run() " << BEGIN << script_start << NL << await_prefix << FunctionName(function) << "();" << NL diff --git a/runtime-light/component/component.h b/runtime-light/component/component.h index 65a9ca173d..b58a81dca6 100644 --- a/runtime-light/component/component.h +++ b/runtime-light/component/component.h @@ -17,7 +17,7 @@ #include "runtime-light/header.h" #include "runtime-light/scheduler/scheduler.h" #include "runtime-light/stdlib/fork/fork-context.h" -#include "runtime-light/stdlib/output-control.h" +#include "runtime-light/stdlib/output/output-buffer.h" #include "runtime-light/stdlib/rpc/rpc-context.h" constexpr uint64_t INVALID_PLATFORM_DESCRIPTOR = 0; diff --git a/runtime-light/stdlib/component/component-api.cpp b/runtime-light/stdlib/component/component-api.cpp new file mode 100644 index 0000000000..e1e046bcb5 --- /dev/null +++ b/runtime-light/stdlib/component/component-api.cpp @@ -0,0 +1,74 @@ +// 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/component/component-api.h" + +#include + +#include "runtime-core/runtime-core.h" +#include "runtime-core/utils/kphp-assert-core.h" +#include "runtime-light/component/component.h" +#include "runtime-light/coroutine/awaitable.h" +#include "runtime-light/coroutine/task.h" +#include "runtime-light/streams/streams.h" +#include "runtime-light/utils/context.h" + +// === component query client interface =========================================================== + +task_t> f$component_client_send_request(string name, string message) noexcept { + const auto stream_d{get_component_context()->open_stream(name)}; + if (stream_d == INVALID_PLATFORM_DESCRIPTOR) { + co_return class_instance{}; + } + + int32_t written{co_await write_all_to_stream(stream_d, message.c_str(), message.size())}; + if (written != message.size()) { + php_warning("can't send request to component '%s'", name.c_str()); + co_return class_instance{}; + } + + get_platform_context()->shutdown_write(stream_d); + php_debug("sent %d bytes from %d to '%s' on stream %" PRIu64, written, message.size(), name.c_str(), stream_d); + co_return make_instance(stream_d); +} + +task_t f$component_client_fetch_response(class_instance query) noexcept { + uint64_t stream_d{query.is_null() ? INVALID_PLATFORM_DESCRIPTOR : query.get()->stream_d}; + if (stream_d == INVALID_PLATFORM_DESCRIPTOR) { + php_warning("can't fetch component response from stream %" PRIu64, stream_d); + co_return string{}; + } + + const auto [buffer, size]{co_await read_all_from_stream(stream_d)}; + string result{buffer, static_cast(size)}; + get_platform_context()->allocator.free(buffer); + php_debug("read %d bytes from stream %" PRIu64, size, stream_d); + get_component_context()->release_stream(stream_d); + query.get()->stream_d = INVALID_PLATFORM_DESCRIPTOR; + co_return result; +} + +// === component query server interface =========================================================== + +task_t> f$component_server_accept_query() noexcept { + co_return make_instance(co_await wait_for_incoming_stream_t{}); +} + +task_t f$component_server_fetch_request(class_instance query) noexcept { + uint64_t stream_d{query.is_null() ? INVALID_PLATFORM_DESCRIPTOR : query.get()->stream_d}; + const auto [buffer, size]{co_await read_all_from_stream(stream_d)}; + string result{buffer, static_cast(size)}; + get_platform_context()->allocator.free(buffer); + co_return result; +} + +task_t f$component_server_send_response(class_instance query, string message) noexcept { + uint64_t stream_d{query.is_null() ? INVALID_PLATFORM_DESCRIPTOR : query.get()->stream_d}; + if ((co_await write_all_to_stream(stream_d, message.c_str(), message.size())) != message.size()) { + php_warning("can't send component response to stream %" PRIu64, stream_d); + } else { + php_debug("sent %d bytes as response to stream %" PRIu64, message.size(), stream_d); + } + get_component_context()->release_stream(stream_d); +} diff --git a/runtime-light/stdlib/component/component-api.h b/runtime-light/stdlib/component/component-api.h new file mode 100644 index 0000000000..5cf5ffd86c --- /dev/null +++ b/runtime-light/stdlib/component/component-api.h @@ -0,0 +1,59 @@ +// 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/class-instance/refcountable-php-classes.h" +#include "runtime-core/runtime-core.h" +#include "runtime-light/component/component.h" +#include "runtime-light/coroutine/task.h" +#include "runtime-light/utils/context.h" + +// === ComponentQuery ============================================================================= + +struct C$ComponentQuery final : public refcountable_php_classes { + uint64_t stream_d{INVALID_PLATFORM_DESCRIPTOR}; + + explicit constexpr C$ComponentQuery(uint64_t stream_d_) noexcept + : stream_d(stream_d_) {} + constexpr C$ComponentQuery(C$ComponentQuery &&other) noexcept + : stream_d(std::exchange(other.stream_d, INVALID_PLATFORM_DESCRIPTOR)) {}; + + C$ComponentQuery(const C$ComponentQuery &) = delete; + C$ComponentQuery &operator=(const C$ComponentQuery &) = delete; + C$ComponentQuery &operator=(C$ComponentQuery &&other) = delete; + + constexpr const char *get_class() const noexcept { + return "ComponentQuery"; + } + + constexpr int32_t get_hash() const noexcept { + return static_cast(std::hash{}(get_class())); + } + + ~C$ComponentQuery() { + auto &component_ctx{*get_component_context()}; + if (component_ctx.opened_streams().contains(stream_d)) { + component_ctx.release_stream(stream_d); + } + } +}; + +// === component query client interface =========================================================== + +task_t> f$component_client_send_request(string name, string message) noexcept; + +task_t f$component_client_fetch_response(class_instance query) noexcept; + +// === component query server interface =========================================================== + +task_t> f$component_server_accept_query() noexcept; + +task_t f$component_server_fetch_request(class_instance query) noexcept; + +task_t f$component_server_send_response(class_instance query, string message) noexcept; diff --git a/runtime-light/stdlib/exit/exit-functions.cpp b/runtime-light/stdlib/exit/exit-functions.cpp new file mode 100644 index 0000000000..c097812977 --- /dev/null +++ b/runtime-light/stdlib/exit/exit-functions.cpp @@ -0,0 +1,65 @@ +// 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/exit/exit-functions.h" + +#include + +#include "runtime-core/utils/kphp-assert-core.h" +#include "runtime-light/component/component.h" +#include "runtime-light/header.h" +#include "runtime-light/streams/streams.h" +#include "runtime-light/utils/context.h" + +namespace { + +int32_t ob_merge_buffers() noexcept { + Response &response{get_component_context()->response}; + php_assert(response.current_buffer >= 0); + + int32_t ob_first_not_empty{}; + while (ob_first_not_empty < response.current_buffer && response.output_buffers[ob_first_not_empty].size() == 0) { + ++ob_first_not_empty; + } + for (auto i = ob_first_not_empty + 1; i <= response.current_buffer; i++) { + response.output_buffers[ob_first_not_empty].append(response.output_buffers[i].c_str(), response.output_buffers[i].size()); + } + return ob_first_not_empty; +} + +} // namespace + +task_t shutdown_script() noexcept { + auto &component_ctx{*get_component_context()}; + const auto standard_stream{component_ctx.standard_stream()}; + if (standard_stream == INVALID_PLATFORM_DESCRIPTOR) { + component_ctx.poll_status = PollStatus::PollFinishedError; + co_return; + } + + const auto &buffer{component_ctx.response.output_buffers[ob_merge_buffers()]}; + if ((co_await write_all_to_stream(standard_stream, buffer.buffer(), buffer.size())) != buffer.size()) { + php_warning("can't write component result to stream %" PRIu64, standard_stream); + } +} + +task_t f$exit(const mixed &v) noexcept { // TODO: make it synchronous + int64_t exit_code{}; + if (v.is_string()) { + Response &response{get_component_context()->response}; + response.output_buffers[response.current_buffer] << v; + } else if (v.is_int()) { + int64_t v_code{v.to_int()}; + // valid PHP exit codes: [0..254] + exit_code = v_code >= 0 && v_code <= 254 ? v_code : 1; + } else { + exit_code = 1; + } + co_await shutdown_script(); + auto &component_ctx{*get_component_context()}; + component_ctx.poll_status = + component_ctx.poll_status != PollStatus::PollFinishedError && exit_code == 0 ? PollStatus::PollFinishedOk : PollStatus::PollFinishedError; + component_ctx.release_all_streams(); + get_platform_context()->abort(); +} diff --git a/runtime-light/stdlib/interface.h b/runtime-light/stdlib/exit/exit-functions.h similarity index 57% rename from runtime-light/stdlib/interface.h rename to runtime-light/stdlib/exit/exit-functions.h index e527145578..90ecab5a0a 100644 --- a/runtime-light/stdlib/interface.h +++ b/runtime-light/stdlib/exit/exit-functions.h @@ -7,9 +7,10 @@ #include "runtime-core/runtime-core.h" #include "runtime-light/coroutine/task.h" -int64_t f$rand(); +task_t shutdown_script() noexcept; -template -T f$make_clone(const T &x) { - return x; +task_t f$exit(const mixed &v = 0) noexcept; + +inline task_t f$die(const mixed &v = 0) noexcept { + co_await f$exit(v); } diff --git a/runtime-light/stdlib/fork/fork-api.cpp b/runtime-light/stdlib/fork/fork-api.cpp deleted file mode 100644 index fca25d4357..0000000000 --- a/runtime-light/stdlib/fork/fork-api.cpp +++ /dev/null @@ -1,23 +0,0 @@ -// 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/fork/fork-api.h" - -#include - -#include "runtime-core/utils/kphp-assert-core.h" -#include "runtime-light/coroutine/awaitable.h" -#include "runtime-light/coroutine/task.h" - -task_t f$sched_yield() noexcept { - co_await wait_for_reschedule_t{}; -} - -task_t f$sched_yield_sleep(double duration) noexcept { - if (duration <= 0) { - php_warning("can't sleep for negative or zero duration %.9f", duration); - co_return; - } - co_await wait_for_timer_t{std::chrono::duration_cast(std::chrono::duration{duration})}; -} diff --git a/runtime-light/stdlib/fork/fork-api.h b/runtime-light/stdlib/fork/fork-functions.h similarity index 85% rename from runtime-light/stdlib/fork/fork-api.h rename to runtime-light/stdlib/fork/fork-functions.h index 20b1fcc1b4..6ee65fa110 100644 --- a/runtime-light/stdlib/fork/fork-api.h +++ b/runtime-light/stdlib/fork/fork-functions.h @@ -24,7 +24,6 @@ constexpr auto DEFAULT_TIMEOUT_NS = std::chrono::duration_cast @@ -47,12 +46,20 @@ requires(is_optional::value || std::same_as) task_t f$wait(Optio co_return co_await f$wait(fork_id_opt.has_value() ? fork_id_opt.val() : INVALID_FORK_ID, timeout); } +inline task_t f$sched_yield() noexcept { + co_await wait_for_reschedule_t{}; +} + +inline task_t f$sched_yield_sleep(double duration) noexcept { + if (duration <= 0) { + php_warning("can't sleep for negative or zero duration %.9f", duration); + co_return; + } + co_await wait_for_timer_t{std::chrono::duration_cast(std::chrono::duration{duration})}; +} + // === Non-blocking API ============================================================================ inline int64_t f$get_running_fork_id() noexcept { return ForkComponentContext::get().running_fork_id; } - -task_t f$sched_yield() noexcept; - -task_t f$sched_yield_sleep(double duration) noexcept; diff --git a/runtime-light/stdlib/interface.cpp b/runtime-light/stdlib/interface.cpp deleted file mode 100644 index f99c7ad954..0000000000 --- a/runtime-light/stdlib/interface.cpp +++ /dev/null @@ -1,19 +0,0 @@ -// 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/interface.h" - -#include -#include -#include - -#include "runtime-core/runtime-core.h" -#include "runtime-light/component/component.h" -#include "runtime-light/coroutine/awaitable.h" - -int64_t f$rand() { - std::random_device rd; - int64_t dice_roll = rd(); - return dice_roll; -} diff --git a/runtime-light/stdlib/math/random-functions.h b/runtime-light/stdlib/math/random-functions.h new file mode 100644 index 0000000000..bc14fb1cad --- /dev/null +++ b/runtime-light/stdlib/math/random-functions.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 + +#include +#include + +inline int64_t f$rand() noexcept { + return static_cast(std::random_device{}()); +} diff --git a/runtime-light/stdlib/misc.cpp b/runtime-light/stdlib/misc.cpp deleted file mode 100644 index a0f342f522..0000000000 --- a/runtime-light/stdlib/misc.cpp +++ /dev/null @@ -1,92 +0,0 @@ -// 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/misc.h" - -#include - -#include "runtime-core/utils/kphp-assert-core.h" -#include "runtime-light/component/component.h" -#include "runtime-light/coroutine/awaitable.h" -#include "runtime-light/header.h" -#include "runtime-light/stdlib/superglobals.h" -#include "runtime-light/streams/streams.h" -#include "runtime-light/utils/context.h" - -namespace { - -int32_t ob_merge_buffers() { - Response &response = get_component_context()->response; - php_assert(response.current_buffer >= 0); - int32_t ob_first_not_empty = 0; - while (ob_first_not_empty < response.current_buffer && response.output_buffers[ob_first_not_empty].size() == 0) { - ob_first_not_empty++; - } - for (auto i = ob_first_not_empty + 1; i <= response.current_buffer; i++) { - response.output_buffers[ob_first_not_empty].append(response.output_buffers[i].c_str(), response.output_buffers[i].size()); - } - return ob_first_not_empty; -} - -} // namespace - -task_t wait_and_process_incoming_stream(QueryType query_type) { - const auto incoming_stream_d{co_await wait_for_incoming_stream_t{}}; - switch (query_type) { - case QueryType::HTTP: { - const auto [buffer, size] = co_await read_all_from_stream(incoming_stream_d); - init_http_superglobals(buffer, size); - get_platform_context()->allocator.free(buffer); - break; - } - case QueryType::COMPONENT: { // processing takes place in a component - break; - } - } - co_return incoming_stream_d; -} - -task_t finish(int64_t exit_code, bool from_exit) { // TODO: use exit code - (void)from_exit; - (void)exit_code; - auto &component_ctx{*get_component_context()}; - const auto standard_stream{component_ctx.standard_stream()}; - if (standard_stream == INVALID_PLATFORM_DESCRIPTOR) { - component_ctx.poll_status = PollStatus::PollFinishedError; - co_return; - } - - const auto ob_total_buffer = ob_merge_buffers(); - Response &response = component_ctx.response; - auto &buffer = response.output_buffers[ob_total_buffer]; - if ((co_await write_all_to_stream(standard_stream, buffer.c_str(), buffer.size())) != buffer.size()) { - php_warning("can't write component result to stream %" PRIu64, standard_stream); - } - component_ctx.release_all_streams(); - component_ctx.poll_status = PollStatus::PollFinishedOk; -} - -void f$check_shutdown() { - const auto &platform_ctx{*get_platform_context()}; - if (static_cast(get_platform_context()->please_graceful_shutdown.load())) { - php_notice("script was graceful shutdown"); - platform_ctx.abort(); - } -} - -task_t f$exit(const mixed &v) { - if (v.is_string()) { - Response &response = get_component_context()->response; - response.output_buffers[response.current_buffer] << v; - co_await finish(0, true); - } else { - co_await finish(v.to_int(), true); - } - critical_error_handler(); -} - -void f$die([[maybe_unused]] const mixed &v) { - get_component_context()->poll_status = PollStatus::PollFinishedOk; - critical_error_handler(); -} diff --git a/runtime-light/stdlib/misc.h b/runtime-light/stdlib/misc.h deleted file mode 100644 index 1194e781ff..0000000000 --- a/runtime-light/stdlib/misc.h +++ /dev/null @@ -1,23 +0,0 @@ -// 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/coroutine/task.h" -#include "runtime-light/stdlib/superglobals.h" - -void f$check_shutdown(); - -task_t f$exit(const mixed &v = 0); - -void f$die(const mixed &v = 0); - -void reset(); - -task_t wait_and_process_incoming_stream(QueryType query_type); - -task_t finish(int64_t exit_code, bool from_exit); diff --git a/runtime-light/stdlib/output-control.h b/runtime-light/stdlib/output-control.h deleted file mode 100644 index d4a90aabc4..0000000000 --- a/runtime-light/stdlib/output-control.h +++ /dev/null @@ -1,33 +0,0 @@ -// 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/runtime-core.h" - -struct Response { - int static constexpr ob_max_buffers = 50; - string_buffer output_buffers[ob_max_buffers]; - int current_buffer = 0; -}; - -void f$ob_clean(); - -bool f$ob_end_clean(); - -Optional f$ob_get_clean(); - -string f$ob_get_contents(); - -void f$ob_start(const string &callback = string()); - -void f$ob_flush(); - -bool f$ob_end_flush(); - -Optional f$ob_get_flush(); - -Optional f$ob_get_length(); - -int64_t f$ob_get_level(); diff --git a/runtime-light/stdlib/output-control.cpp b/runtime-light/stdlib/output/output-buffer.cpp similarity index 56% rename from runtime-light/stdlib/output-control.cpp rename to runtime-light/stdlib/output/output-buffer.cpp index e6637044f2..4529abd8e6 100644 --- a/runtime-light/stdlib/output-control.cpp +++ b/runtime-light/stdlib/output/output-buffer.cpp @@ -2,21 +2,48 @@ // Copyright (c) 2024 LLC «V Kontakte» // Distributed under the GPL v3 License, see LICENSE.notice.txt -#include "runtime-light/stdlib/output-control.h" +#include "runtime-light/stdlib/output/output-buffer.h" + +#include #include "runtime-light/component/component.h" -#include "runtime-light/stdlib/string-functions.h" +#include "runtime-light/stdlib/output/print-functions.h" #include "runtime-light/utils/context.h" -static constexpr int system_level_buffer = 0; +static constexpr int32_t system_level_buffer = 0; + +void f$ob_start(const string &callback) noexcept { + Response &httpResponse{get_component_context()->response}; + if (httpResponse.current_buffer + 1 == Response::ob_max_buffers) { + php_warning("Maximum nested level of output buffering reached. Can't do ob_start(%s)", callback.c_str()); + return; + } + + if (!callback.empty()) { + php_critical_error("unsupported callback %s at buffering level %d", callback.c_str(), httpResponse.current_buffer + 1); + } + ++httpResponse.current_buffer; +} + +Optional f$ob_get_length() noexcept { + Response &httpResponse{get_component_context()->response}; + if (httpResponse.current_buffer == 0) { + return false; + } + return httpResponse.output_buffers[httpResponse.current_buffer].size(); +} + +int64_t f$ob_get_level() noexcept { + return get_component_context()->response.current_buffer; +} -void f$ob_clean() { - Response &httpResponse = get_component_context()->response; +void f$ob_clean() noexcept { + Response &httpResponse{get_component_context()->response}; httpResponse.output_buffers[httpResponse.current_buffer].clean(); } -bool f$ob_end_clean() { - Response &httpResponse = get_component_context()->response; +bool f$ob_end_clean() noexcept { + Response &httpResponse{get_component_context()->response}; if (httpResponse.current_buffer == system_level_buffer) { return false; } @@ -25,37 +52,21 @@ bool f$ob_end_clean() { return true; } -Optional f$ob_get_clean() { - Response &httpResponse = get_component_context()->response; +Optional f$ob_get_clean() noexcept { + Response &httpResponse{get_component_context()->response}; if (httpResponse.current_buffer == system_level_buffer) { return false; } - - string result = httpResponse.output_buffers[httpResponse.current_buffer].str(); - return result; + return httpResponse.output_buffers[httpResponse.current_buffer].str(); } string f$ob_get_content() { - Response &httpResponse = get_component_context()->response; + Response &httpResponse{get_component_context()->response}; return httpResponse.output_buffers[httpResponse.current_buffer].str(); } -void f$ob_start(const string &callback) { - Response &httpResponse = get_component_context()->response; - if (httpResponse.current_buffer + 1 == Response::ob_max_buffers) { - php_warning("Maximum nested level of output buffering reached. Can't do ob_start(%s)", callback.c_str()); - return; - } - - if (!callback.empty()) { - php_critical_error("unsupported callback %s at buffering level %d", callback.c_str(), httpResponse.current_buffer + 1); - } - - ++httpResponse.current_buffer; -} - -void f$ob_flush() { - Response &httpResponse = get_component_context()->response; +void f$ob_flush() noexcept { + Response &httpResponse{get_component_context()->response}; if (httpResponse.current_buffer == 0) { php_warning("ob_flush with no buffer opented"); return; @@ -66,8 +77,8 @@ void f$ob_flush() { f$ob_clean(); } -bool f$ob_end_flush() { - Response &httpResponse = get_component_context()->response; +bool f$ob_end_flush() noexcept { + Response &httpResponse{get_component_context()->response}; if (httpResponse.current_buffer == 0) { return false; } @@ -75,26 +86,13 @@ bool f$ob_end_flush() { return f$ob_end_clean(); } -Optional f$ob_get_flush() { - Response &httpResponse = get_component_context()->response; +Optional f$ob_get_flush() noexcept { + Response &httpResponse{get_component_context()->response}; if (httpResponse.current_buffer == 0) { return false; } - string result = httpResponse.output_buffers[httpResponse.current_buffer].str(); + string result{httpResponse.output_buffers[httpResponse.current_buffer].str()}; f$ob_flush(); f$ob_end_clean(); return result; } - -Optional f$ob_get_length() { - Response &httpResponse = get_component_context()->response; - if (httpResponse.current_buffer == 0) { - return false; - } - return httpResponse.output_buffers[httpResponse.current_buffer].size(); -} - -int64_t f$ob_get_level() { - Response &httpResponse = get_component_context()->response; - return httpResponse.current_buffer; -} diff --git a/runtime-light/stdlib/output/output-buffer.h b/runtime-light/stdlib/output/output-buffer.h new file mode 100644 index 0000000000..f1052edd15 --- /dev/null +++ b/runtime-light/stdlib/output/output-buffer.h @@ -0,0 +1,35 @@ +// 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" + +struct Response { + static constexpr int32_t ob_max_buffers{50}; + string_buffer output_buffers[ob_max_buffers]; + int32_t current_buffer{}; +}; + +void f$ob_start(const string &callback = string()) noexcept; + +Optional f$ob_get_length() noexcept; + +int64_t f$ob_get_level() noexcept; + +void f$ob_clean() noexcept; + +bool f$ob_end_clean() noexcept; + +Optional f$ob_get_clean() noexcept; + +string f$ob_get_contents() noexcept; + +void f$ob_flush() noexcept; + +bool f$ob_end_flush() noexcept; + +Optional f$ob_get_flush() noexcept; diff --git a/runtime-light/stdlib/variable-handling.cpp b/runtime-light/stdlib/output/print-functions.cpp similarity index 54% rename from runtime-light/stdlib/variable-handling.cpp rename to runtime-light/stdlib/output/print-functions.cpp index 4c5e7fc646..01f386014a 100644 --- a/runtime-light/stdlib/variable-handling.cpp +++ b/runtime-light/stdlib/output/print-functions.cpp @@ -2,179 +2,182 @@ // Copyright (c) 2024 LLC «V Kontakte» // Distributed under the GPL v3 License, see LICENSE.notice.txt -#include "runtime-light/stdlib/variable-handling.h" +#include "runtime-light/stdlib/output/print-functions.h" + +#include #include "runtime-core/runtime-core.h" +#include "runtime-core/utils/kphp-assert-core.h" #include "runtime-light/component/component.h" -#include "runtime-light/stdlib/output-control.h" +#include "runtime-light/stdlib/output/output-buffer.h" #include "runtime-light/utils/context.h" -void do_print_r(const mixed &v, int depth) { +namespace { + +void do_print_r(const mixed &v, int32_t depth) noexcept { if (depth == 10) { php_warning("Depth %d reached. Recursion?", depth); return; } - Response &httpResponse = get_component_context()->response; - string_buffer *coub = &httpResponse.output_buffers[httpResponse.current_buffer]; + Response &httpResponse{get_component_context()->response}; + string_buffer &coub{httpResponse.output_buffers[httpResponse.current_buffer]}; switch (v.get_type()) { case mixed::type::NUL: break; case mixed::type::BOOLEAN: if (v.as_bool()) { - *coub << '1'; + coub << '1'; } break; case mixed::type::INTEGER: - *coub << v.as_int(); + coub << v.as_int(); break; case mixed::type::FLOAT: - *coub << v.as_double(); + coub << v.as_double(); break; case mixed::type::STRING: - *coub << v.as_string(); + coub << v.as_string(); break; case mixed::type::ARRAY: { - *coub << "Array\n"; + coub << "Array\n"; string shift(depth << 3, ' '); - *coub << shift << "(\n"; + coub << shift << "(\n"; for (array::const_iterator it = v.as_array().begin(); it != v.as_array().end(); ++it) { - *coub << shift << " [" << it.get_key() << "] => "; + coub << shift << " [" << it.get_key() << "] => "; do_print_r(it.get_value(), depth + 1); - *coub << '\n'; + coub << '\n'; } - *coub << shift << ")\n"; + coub << shift << ")\n"; break; } case mixed::type::OBJECT: { php_warning("print_r used on object"); - *coub << v.as_object()->get_class(); + coub << v.as_object()->get_class(); break; } default: - __builtin_unreachable(); + php_critical_error("non-exhaustive switch"); } } -static void do_var_dump(const mixed &v, int depth) { +void do_var_dump(const mixed &v, int32_t depth) noexcept { if (depth == 10) { php_warning("Depth %d reached. Recursion?", depth); return; } string shift(depth * 2, ' '); - Response &httpResponse = get_component_context()->response; - string_buffer *coub = &httpResponse.output_buffers[httpResponse.current_buffer]; - + Response &httpResponse{get_component_context()->response}; + string_buffer &coub{httpResponse.output_buffers[httpResponse.current_buffer]}; switch (v.get_type()) { case mixed::type::NUL: - *coub << shift << "NULL"; + coub << shift << "NULL"; break; case mixed::type::BOOLEAN: - *coub << shift << "bool(" << (v.as_bool() ? "true" : "false") << ')'; + coub << shift << "bool(" << (v.as_bool() ? "true" : "false") << ')'; break; case mixed::type::INTEGER: - *coub << shift << "int(" << v.as_int() << ')'; + coub << shift << "int(" << v.as_int() << ')'; break; case mixed::type::FLOAT: - *coub << shift << "float(" << v.as_double() << ')'; + coub << shift << "float(" << v.as_double() << ')'; break; case mixed::type::STRING: - *coub << shift << "string(" << (int)v.as_string().size() << ") \"" << v.as_string() << '"'; + coub << shift << "string(" << static_cast(v.as_string().size()) << ") \"" << v.as_string() << '"'; break; case mixed::type::ARRAY: { - *coub << shift << "array(" << v.as_array().count() << ") {\n"; + coub << shift << "array(" << v.as_array().count() << ") {\n"; for (array::const_iterator it = v.as_array().begin(); it != v.as_array().end(); ++it) { - *coub << shift << " ["; + coub << shift << " ["; if (array::is_int_key(it.get_key())) { - *coub << it.get_key(); + coub << it.get_key(); } else { - *coub << '"' << it.get_key() << '"'; + coub << '"' << it.get_key() << '"'; } - *coub << "]=>\n"; + coub << "]=>\n"; do_var_dump(it.get_value(), depth + 1); } - *coub << shift << "}"; + coub << shift << "}"; break; } case mixed::type::OBJECT: { php_warning("var_dump used on object"); auto s = string(v.as_object()->get_class(), static_cast(strlen(v.as_object()->get_class()))); - *coub << shift << "string(" << static_cast(s.size()) << ") \"" << s << '"'; + coub << shift << "string(" << static_cast(s.size()) << ") \"" << s << '"'; break; } default: - __builtin_unreachable(); + php_critical_error("non-exhaustive switch"); } - *coub << '\n'; + coub << '\n'; } -static void var_export_escaped_string(const string &s) { - Response &httpResponse = get_component_context()->response; - string_buffer *coub = &httpResponse.output_buffers[httpResponse.current_buffer]; +void var_export_escaped_string(const string &s) noexcept { + Response &httpResponse{get_component_context()->response}; + string_buffer &coub{httpResponse.output_buffers[httpResponse.current_buffer]}; for (string::size_type i = 0; i < s.size(); i++) { switch (s[i]) { case '\'': case '\\': - *coub << "\\" << s[i]; + coub << "\\" << s[i]; break; case '\0': - *coub << "\' . \"\\0\" . \'"; + coub << R"(' . "\0" . ')"; break; default: - *coub << s[i]; + coub << s[i]; } } } -static void do_var_export(const mixed &v, int depth, char endc = 0) { +void do_var_export(const mixed &v, int32_t depth, char endc = 0) noexcept { if (depth == 10) { php_warning("Depth %d reached. Recursion?", depth); return; } string shift(depth * 2, ' '); - Response &httpResponse = get_component_context()->response; - string_buffer *coub = &httpResponse.output_buffers[httpResponse.current_buffer]; - + Response &httpResponse{get_component_context()->response}; + string_buffer &coub{httpResponse.output_buffers[httpResponse.current_buffer]}; switch (v.get_type()) { case mixed::type::NUL: - *coub << shift << "NULL"; + coub << shift << "NULL"; break; case mixed::type::BOOLEAN: - *coub << shift << (v.as_bool() ? "true" : "false"); + coub << shift << (v.as_bool() ? "true" : "false"); break; case mixed::type::INTEGER: - *coub << shift << v.as_int(); + coub << shift << v.as_int(); break; case mixed::type::FLOAT: - *coub << shift << v.as_double(); + coub << shift << v.as_double(); break; case mixed::type::STRING: - *coub << shift << '\''; + coub << shift << '\''; var_export_escaped_string(v.as_string()); - *coub << '\''; + coub << '\''; break; case mixed::type::ARRAY: { const bool is_vector = v.as_array().is_vector(); - *coub << shift << "array(\n"; + coub << shift << "array(\n"; for (array::const_iterator it = v.as_array().begin(); it != v.as_array().end(); ++it) { if (!is_vector) { - *coub << shift; + coub << shift; if (array::is_int_key(it.get_key())) { - *coub << it.get_key(); + coub << it.get_key(); } else { - *coub << '\'' << it.get_key() << '\''; + coub << '\'' << it.get_key() << '\''; } - *coub << " =>"; + coub << " =>"; if (it.get_value().is_array()) { - *coub << "\n"; + coub << "\n"; do_var_export(it.get_value(), depth + 1, ','); } else { do_var_export(it.get_value(), 1, ','); @@ -184,23 +187,25 @@ static void do_var_export(const mixed &v, int depth, char endc = 0) { } } - *coub << shift << ")"; + coub << shift << ")"; break; } case mixed::type::OBJECT: { - *coub << shift << v.get_type_or_class_name(); + coub << shift << v.get_type_or_class_name(); break; } default: - __builtin_unreachable(); + php_critical_error("non-exhaustive switch"); } if (endc != 0) { - *coub << endc; + coub << endc; } - *coub << '\n'; + coub << '\n'; } -string f$var_export(const mixed &v, bool buffered) { +} // namespace + +string f$var_export(const mixed &v, bool buffered) noexcept { if (buffered) { f$ob_start(); do_var_export(v, 0); @@ -210,7 +215,7 @@ string f$var_export(const mixed &v, bool buffered) { return {}; } -string f$print_r(const mixed &v, bool buffered) { +string f$print_r(const mixed &v, bool buffered) noexcept { if (buffered) { f$ob_start(); do_print_r(v, 0); @@ -221,6 +226,6 @@ string f$print_r(const mixed &v, bool buffered) { return {}; } -void f$var_dump(const mixed &v) { +void f$var_dump(const mixed &v) noexcept { do_var_dump(v, 0); } diff --git a/runtime-light/stdlib/output/print-functions.h b/runtime-light/stdlib/output/print-functions.h new file mode 100644 index 0000000000..6b26308f66 --- /dev/null +++ b/runtime-light/stdlib/output/print-functions.h @@ -0,0 +1,71 @@ +// 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/component/component.h" +#include "runtime-light/utils/context.h" + +// === print ====================================================================================== + +inline void print(const char *s, size_t len) noexcept { + Response &response{get_component_context()->response}; + response.output_buffers[response.current_buffer].append(s, len); +} + +inline void print(const char *s) noexcept { + print(s, strlen(s)); +} + +inline void print(const string_buffer &sb) noexcept { + print(sb.buffer(), sb.size()); +} + +inline void print(const string &s) noexcept { + print(s.c_str(), s.size()); +} + +inline int64_t f$print(const string &s) noexcept { + print(s); + return 1; +} + +// === print_r ==================================================================================== + +string f$print_r(const mixed &v, bool buffered = false) noexcept; + +template +string f$print_r(const class_instance &v, bool buffered = false) noexcept { + php_warning("print_r used on object"); + return f$print_r(string{v.get_class(), static_cast(strlen(v.get_class()))}, buffered); +} + +// === var_export ================================================================================= + +string f$var_export(const mixed &v, bool buffered = false) noexcept; + +template +string f$var_export(const class_instance &v, bool buffered = false) noexcept { + php_warning("print_r used on object"); + return f$var_export(string{v.get_class(), static_cast(strlen(v.get_class()))}, buffered); +} + +// === var_dump =================================================================================== + +void f$var_dump(const mixed &v) noexcept; + +template +void f$var_dump(const class_instance &v) noexcept { + php_warning("print_r used on object"); + return f$var_dump(string{v.get_class(), static_cast(strlen(v.get_class()))}); +} + +// ================================================================================================ + +inline void f$echo(const string &s) noexcept { + print(s); +} diff --git a/runtime-light/stdlib/rpc/rpc-api.cpp b/runtime-light/stdlib/rpc/rpc-api.cpp index ea727da862..1a038e3ba2 100644 --- a/runtime-light/stdlib/rpc/rpc-api.cpp +++ b/runtime-light/stdlib/rpc/rpc-api.cpp @@ -17,10 +17,10 @@ #include "runtime-light/allocator/allocator.h" #include "runtime-light/coroutine/awaitable.h" #include "runtime-light/coroutine/task.h" +#include "runtime-light/stdlib/component/component-api.h" #include "runtime-light/stdlib/rpc/rpc-context.h" #include "runtime-light/stdlib/rpc/rpc-extra-headers.h" #include "runtime-light/stdlib/rpc/rpc-extra-info.h" -#include "runtime-light/streams/interface.h" #include "runtime-light/tl/tl-core.h" namespace rpc_impl_ { @@ -117,9 +117,9 @@ task_t rpc_send_impl(string actor, double timeout, bool ignore_ans // send RPC request const auto query_id{rpc_ctx.current_query_id++}; 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)}; + auto comp_query{co_await f$component_client_send_request(actor, request_buf)}; if (comp_query.is_null()) { - php_error("can't send rpc query to %s", actor.c_str()); + php_warning("can't send rpc query to %s", actor.c_str()); co_return RpcQueryInfo{.id = RPC_INVALID_QUERY_ID, .request_size = request_size, .timestamp = timestamp}; } @@ -133,7 +133,7 @@ task_t rpc_send_impl(string actor, double timeout, bool ignore_ans // create fork to wait for RPC response. we need to do it even if 'ignore_answer' is 'true' to make sure // that the stream will not be closed too early. otherwise, platform may even not send RPC request auto waiter_task{[](int64_t query_id, auto comp_query, std::chrono::nanoseconds timeout, bool collect_responses_extra_info) noexcept -> task_t { - auto fetch_task{f$component_client_get_result(std::move(comp_query))}; + auto fetch_task{f$component_client_fetch_response(std::move(comp_query))}; const auto response{(co_await wait_with_timeout_t{task_t::awaiter_t{std::addressof(fetch_task)}, timeout}).value_or(string{})}; // update response extra info if needed if (collect_responses_extra_info) { diff --git a/runtime-light/stdlib/stdlib.cmake b/runtime-light/stdlib/stdlib.cmake index d7956932a3..065909959c 100644 --- a/runtime-light/stdlib/stdlib.cmake +++ b/runtime-light/stdlib/stdlib.cmake @@ -1,18 +1,16 @@ prepend( RUNTIME_STDLIB_SRC stdlib/ - interface.cpp - misc.cpp - output-control.cpp - string-functions.cpp - variable-handling.cpp - superglobals.cpp - fork/fork-api.cpp + component/component-api.cpp + exit/exit-functions.cpp fork/fork-context.cpp + output/output-buffer.cpp + output/print-functions.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) + rpc/rpc-tl-request.cpp + string/concat.cpp) diff --git a/runtime-light/stdlib/string-functions.cpp b/runtime-light/stdlib/string/concat.cpp similarity index 52% rename from runtime-light/stdlib/string-functions.cpp rename to runtime-light/stdlib/string/concat.cpp index 5039cef93e..c2b929ddd6 100644 --- a/runtime-light/stdlib/string-functions.cpp +++ b/runtime-light/stdlib/string/concat.cpp @@ -2,53 +2,9 @@ // Copyright (c) 2024 LLC «V Kontakte» // Distributed under the GPL v3 License, see LICENSE.notice.txt -#include "runtime-light/stdlib/string-functions.h" +#include "runtime-light/stdlib/string/concat.h" -#include "runtime-light/component/component.h" -#include "runtime-light/utils/context.h" - -void print(const char *s, size_t s_len) { - Response &response = get_component_context()->response; - response.output_buffers[response.current_buffer].append(s, s_len); -} - -void print(const char *s) { - print(s, strlen(s)); -} - -void print(const string_buffer &sb) { - print(sb.buffer(), sb.size()); -} - -void print(const string &s) { - print(s.c_str(), s.size()); -} - -void f$debug_print_string(const string &s) { - printf("debug_print_string ["); - for (int i = 0; i < s.size(); ++i) { - printf("%d, ", s.c_str()[i]); - } - printf("]\n"); -} - -Optional f$byte_to_int(const string &s) { - if (s.size() != 1) { - php_warning("Cannot convert non-byte string to int"); - return {}; - } - return *s.c_str(); -} - -Optional f$int_to_byte(int64_t v) { - if (v > 127 || v < -128) { - php_warning("Cannot convert too big int to byte %" PRId64, v); - return false; - } - char c = v; - string result(&c, 1); - return result; -} +#include "runtime-core/runtime-core.h" string str_concat(const string &s1, const string &s2) { // for 2 argument concatenation it's not so uncommon to have at least one empty string argument; @@ -66,23 +22,24 @@ string str_concat(const string &s1, const string &s2) { if (s2.empty()) { return s1; } - auto new_size = s1.size() + s2.size(); - return string(new_size, true).append_unsafe(s1).append_unsafe(s2).finish_append(); + + const auto new_size{s1.size() + s2.size()}; + return string{new_size, true}.append_unsafe(s1).append_unsafe(s2).finish_append(); } string str_concat(str_concat_arg s1, str_concat_arg s2) { - auto new_size = s1.size + s2.size; - return string(new_size, true).append_unsafe(s1.as_tmp_string()).append_unsafe(s2.as_tmp_string()).finish_append(); + const auto new_size{s1.size + s2.size}; + return string{new_size, true}.append_unsafe(s1.as_tmp_string()).append_unsafe(s2.as_tmp_string()).finish_append(); } string str_concat(str_concat_arg s1, str_concat_arg s2, str_concat_arg s3) { - auto new_size = s1.size + s2.size + s3.size; - return string(new_size, true).append_unsafe(s1.as_tmp_string()).append_unsafe(s2.as_tmp_string()).append_unsafe(s3.as_tmp_string()).finish_append(); + const auto new_size{s1.size + s2.size + s3.size}; + return string{new_size, true}.append_unsafe(s1.as_tmp_string()).append_unsafe(s2.as_tmp_string()).append_unsafe(s3.as_tmp_string()).finish_append(); } string str_concat(str_concat_arg s1, str_concat_arg s2, str_concat_arg s3, str_concat_arg s4) { - auto new_size = s1.size + s2.size + s3.size + s4.size; - return string(new_size, true) + const auto new_size{s1.size + s2.size + s3.size + s4.size}; + return string{new_size, true} .append_unsafe(s1.as_tmp_string()) .append_unsafe(s2.as_tmp_string()) .append_unsafe(s3.as_tmp_string()) @@ -91,8 +48,8 @@ string str_concat(str_concat_arg s1, str_concat_arg s2, str_concat_arg s3, str_c } string str_concat(str_concat_arg s1, str_concat_arg s2, str_concat_arg s3, str_concat_arg s4, str_concat_arg s5) { - auto new_size = s1.size + s2.size + s3.size + s4.size + s5.size; - return string(new_size, true) + const auto new_size{s1.size + s2.size + s3.size + s4.size + s5.size}; + return string{new_size, true} .append_unsafe(s1.as_tmp_string()) .append_unsafe(s2.as_tmp_string()) .append_unsafe(s3.as_tmp_string()) diff --git a/runtime-light/stdlib/string-functions.h b/runtime-light/stdlib/string/concat.h similarity index 77% rename from runtime-light/stdlib/string-functions.h rename to runtime-light/stdlib/string/concat.h index a2264a2fc4..6d0a2fbd41 100644 --- a/runtime-light/stdlib/string-functions.h +++ b/runtime-light/stdlib/string/concat.h @@ -1,36 +1,11 @@ +// 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/runtime-core.h" -void print(const char *s, size_t s_len); - -void print(const char *s); - -void print(const string &s); - -void print(const string_buffer &sb); - -inline void f$echo(const string &s) { - print(s); -} - -inline int64_t f$print(const string &s) { - print(s); - return 1; -} - -inline int64_t f$strlen(const string &s) { - return s.size(); -} - -void f$debug_print_string(const string &s); - -string f$increment_byte(const string &s); - -Optional f$byte_to_int(const string &s); - -Optional f$int_to_byte(int64_t v); - // str_concat_arg generalizes both tmp_string and string arguments; // it can be constructed from both of them, so concat functions can operate // on both tmp_string and string types diff --git a/runtime-light/stdlib/string/string-functions.h b/runtime-light/stdlib/string/string-functions.h new file mode 100644 index 0000000000..ca1b237c6a --- /dev/null +++ b/runtime-light/stdlib/string/string-functions.h @@ -0,0 +1,11 @@ +// Compiler for PHP (aka KPHP) +// Copyright (c) 2024 LLC «V Kontakte» +// Distributed under the GPL v3 License, see LICENSE.notice.txt + +#include + +#include "runtime-core/runtime-core.h" + +inline int64_t f$strlen(const string &s) noexcept { + return s.size(); +} diff --git a/runtime-light/stdlib/superglobals.cpp b/runtime-light/stdlib/superglobals.cpp deleted file mode 100644 index d53a5efe90..0000000000 --- a/runtime-light/stdlib/superglobals.cpp +++ /dev/null @@ -1,17 +0,0 @@ -// 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/superglobals.h" - -#include "runtime-light/component/component.h" -#include "runtime-light/utils/context.h" -#include "runtime-light/utils/json-functions.h" - -void init_http_superglobals(const char *buffer, int size) { - ComponentState &ctx = *get_component_context(); - string query = string(buffer, size); - mixed http = f$json_decode(query, true); - ctx.php_script_mutable_globals_singleton.get_superglobals().v$_SERVER.set_value(string("QUERY_TYPE"), string("http")); - ctx.php_script_mutable_globals_singleton.get_superglobals().v$_POST = http; -} diff --git a/runtime-light/stdlib/timer/timer.h b/runtime-light/stdlib/time/timer-functions.h similarity index 100% rename from runtime-light/stdlib/timer/timer.h rename to runtime-light/stdlib/time/timer-functions.h diff --git a/runtime-light/stdlib/superglobals.h b/runtime-light/stdlib/value/value-functions.h similarity index 59% rename from runtime-light/stdlib/superglobals.h rename to runtime-light/stdlib/value/value-functions.h index 9bace21303..cf8de8deb7 100644 --- a/runtime-light/stdlib/superglobals.h +++ b/runtime-light/stdlib/value/value-functions.h @@ -4,6 +4,7 @@ #pragma once -enum class QueryType { HTTP, COMPONENT }; - -void init_http_superglobals(const char *buffer, int size); +template +T f$make_clone(const T &x) noexcept { + return x; +} diff --git a/runtime-light/stdlib/variable-handling.h b/runtime-light/stdlib/variable-handling.h deleted file mode 100644 index 1bf7c83ad8..0000000000 --- a/runtime-light/stdlib/variable-handling.h +++ /dev/null @@ -1,32 +0,0 @@ -// 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/runtime-core.h" -#include "runtime-light/stdlib/string-functions.h" - -string f$print_r(const mixed &v, bool buffered = false); - -template -string f$print_r(const class_instance &v, bool buffered = false) { - php_warning("print_r used on object"); - return f$print_r(string(v.get_class(), (string::size_type)strlen(v.get_class())), buffered); -} - -string f$var_export(const mixed &v, bool buffered = false); - -template -string f$var_export(const class_instance &v, bool buffered = false) { - php_warning("print_r used on object"); - return f$var_export(string(v.get_class(), (string::size_type)strlen(v.get_class())), buffered); -} - -void f$var_dump(const mixed &v); - -template -void f$var_dump(const class_instance &v) { - php_warning("print_r used on object"); - return f$var_dump(string(v.get_class(), (string::size_type)strlen(v.get_class()))); -} diff --git a/runtime-light/streams/component-stream.cpp b/runtime-light/streams/component-stream.cpp deleted file mode 100644 index 2956c949a6..0000000000 --- a/runtime-light/streams/component-stream.cpp +++ /dev/null @@ -1,84 +0,0 @@ -// Compiler for PHP (aka KPHP) -// Copyright (c) 2024 LLC «V Kontakte» -// Distributed under the GPL v3 License, see LICENSE.notice.txt - -#include - -#include "runtime-light/component/component.h" -#include "runtime-light/header.h" -#include "runtime-light/streams/component-stream.h" -#include "runtime-light/utils/context.h" - -const char *C$ComponentStream::get_class() const noexcept { - return "ComponentStream"; -} - -int32_t C$ComponentStream::get_hash() const noexcept { - return static_cast(vk::std_hash(vk::string_view(C$ComponentStream::get_class()))); -} - -C$ComponentStream::~C$ComponentStream() { - auto &component_ctx{*get_component_context()}; - if (component_ctx.opened_streams().contains(stream_d)) { - component_ctx.release_stream(stream_d); - } -} - -bool f$ComponentStream$$is_read_closed(const class_instance &stream) { - StreamStatus status{}; - if (const auto status_res{get_platform_context()->get_stream_status(stream.get()->stream_d, std::addressof(status))}; - status_res != GetStatusResult::GetStatusOk) { - php_warning("stream status error %d", status_res); - return true; - } - return status.read_status == IOStatus::IOClosed; -} - -bool f$ComponentStream$$is_write_closed(const class_instance &stream) { - StreamStatus status{}; - if (const auto status_res{get_platform_context()->get_stream_status(stream.get()->stream_d, std::addressof(status))}; - status_res != GetStatusResult::GetStatusOk) { - php_warning("stream status error %d", status_res); - return true; - } - return status.write_status == IOStatus::IOClosed; -} - -bool f$ComponentStream$$is_please_shutdown_write(const class_instance &stream) { - StreamStatus status{}; - if (const auto status_res{get_platform_context()->get_stream_status(stream.get()->stream_d, std::addressof(status))}; - status_res != GetStatusResult::GetStatusOk) { - php_warning("stream status error %d", status_res); - return true; - } - return status.please_shutdown_write; -} - -void f$ComponentStream$$close(const class_instance &stream) { - get_component_context()->release_stream(stream->stream_d); -} - -void f$ComponentStream$$shutdown_write(const class_instance &stream) { - get_platform_context()->shutdown_write(stream->stream_d); -} - -void f$ComponentStream$$please_shutdown_write(const class_instance &stream) { - get_platform_context()->please_shutdown_write(stream->stream_d); -} - -// ================================================================================================ - -const char *C$ComponentQuery::get_class() const noexcept { - return "ComponentQuery"; -} - -int32_t C$ComponentQuery::get_hash() const noexcept { - return static_cast(vk::std_hash(vk::string_view(C$ComponentQuery::get_class()))); -} - -C$ComponentQuery::~C$ComponentQuery() { - auto &component_ctx{*get_component_context()}; - if (component_ctx.opened_streams().contains(stream_d)) { - component_ctx.release_stream(stream_d); - } -} diff --git a/runtime-light/streams/component-stream.h b/runtime-light/streams/component-stream.h deleted file mode 100644 index f5edb1315c..0000000000 --- a/runtime-light/streams/component-stream.h +++ /dev/null @@ -1,35 +0,0 @@ -// 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/class-instance/refcountable-php-classes.h" - -struct C$ComponentStream final : public refcountable_php_classes { - uint64_t stream_d{}; - - const char *get_class() const noexcept; - - int32_t get_hash() const noexcept; - - ~C$ComponentStream(); -}; - -struct C$ComponentQuery final : public refcountable_php_classes { - uint64_t stream_d{}; - - const char *get_class() const noexcept; - - int32_t get_hash() const noexcept; - - ~C$ComponentQuery(); -}; - -bool f$ComponentStream$$is_read_closed(const class_instance &stream); -bool f$ComponentStream$$is_write_closed(const class_instance &stream); -bool f$ComponentStream$$is_please_shutdown_write(const class_instance &stream); - -void f$ComponentStream$$close(const class_instance &stream); -void f$ComponentStream$$shutdown_write(const class_instance &stream); -void f$ComponentStream$$please_shutdown_write(const class_instance &stream); diff --git a/runtime-light/streams/interface.cpp b/runtime-light/streams/interface.cpp deleted file mode 100644 index bde0b4667d..0000000000 --- a/runtime-light/streams/interface.cpp +++ /dev/null @@ -1,134 +0,0 @@ -// Compiler for PHP (aka KPHP) -// Copyright (c) 2024 LLC «V Kontakte» -// Distributed under the GPL v3 License, see LICENSE.notice.txt - -#include "runtime-light/streams/interface.h" - -#include - -#include "runtime-core/runtime-core.h" -#include "runtime-core/utils/kphp-assert-core.h" -#include "runtime-light/component/component.h" -#include "runtime-light/header.h" -#include "runtime-light/stdlib/misc.h" -#include "runtime-light/streams/component-stream.h" -#include "runtime-light/streams/streams.h" -#include "runtime-light/utils/context.h" - -task_t f$component_get_http_query() { - std::ignore = co_await wait_and_process_incoming_stream(QueryType::HTTP); -} - -task_t> f$component_client_send_query(string name, string message) { - class_instance query; - const auto stream_d{get_component_context()->open_stream(name)}; - if (stream_d == INVALID_PLATFORM_DESCRIPTOR) { - php_warning("can't send client query"); - co_return query; - } - - int32_t written{co_await write_all_to_stream(stream_d, message.c_str(), message.size())}; - get_platform_context()->shutdown_write(stream_d); - query.alloc(); - query.get()->stream_d = stream_d; - php_debug("send %d bytes from %d to \"%s\" on stream %" PRIu64, written, message.size(), name.c_str(), stream_d); - co_return query; -} - -task_t f$component_client_get_result(class_instance query) { - php_assert(!query.is_null()); - uint64_t stream_d{query.get()->stream_d}; - if (stream_d == INVALID_PLATFORM_DESCRIPTOR) { - php_warning("can't get component client result"); - co_return string{}; - } - - const auto [buffer, size]{co_await read_all_from_stream(stream_d)}; - string result{buffer, static_cast(size)}; - get_platform_context()->allocator.free(buffer); - php_debug("read %d bytes from stream %" PRIu64, size, stream_d); - get_component_context()->release_stream(stream_d); - query.get()->stream_d = INVALID_PLATFORM_DESCRIPTOR; - co_return result; -} - -task_t f$component_server_send_result(string message) { - auto &component_ctx{*get_component_context()}; - const auto standard_stream{component_ctx.standard_stream()}; - if (!co_await write_all_to_stream(standard_stream, message.c_str(), message.size())) { - php_warning("can't send component result"); - } else { - php_debug("send result \"%s\"", message.c_str()); - } - component_ctx.release_stream(standard_stream); -} - -task_t f$component_server_get_query() { - const auto incoming_stream_d{co_await wait_and_process_incoming_stream(QueryType::COMPONENT)}; - const auto [buffer, size] = co_await read_all_from_stream(incoming_stream_d); - string result{buffer, static_cast(size)}; - get_platform_context()->allocator.free(buffer); - co_return result; -} - -task_t> f$component_accept_stream() { - const auto incoming_stream_d{co_await wait_and_process_incoming_stream(QueryType::COMPONENT)}; - class_instance stream; - stream.alloc(); - stream.get()->stream_d = incoming_stream_d; - co_return stream; -} - -class_instance f$component_open_stream(const string &name) { - auto &component_ctx = *get_component_context(); - - class_instance query; - const auto stream_d{component_ctx.open_stream(name)}; - if (stream_d == INVALID_PLATFORM_DESCRIPTOR) { - return query; - } - query.alloc(); - query.get()->stream_d = stream_d; - return query; -} - -int64_t f$component_stream_write_nonblock(const class_instance &stream, const string &message) { - return write_nonblock_to_stream(stream.get()->stream_d, message.c_str(), message.size()); -} - -string f$component_stream_read_nonblock(const class_instance &stream) { - const auto [buffer, size] = read_nonblock_from_stream(stream.get()->stream_d); - string result{buffer, static_cast(size)}; - get_platform_context()->allocator.free(buffer); // FIXME: do we need platform memory? - return result; -} - -task_t f$component_stream_write_exact(class_instance stream, string message) { - const auto written = co_await write_exact_to_stream(stream->stream_d, message.c_str(), message.size()); - php_debug("wrote exact %d bytes to stream %" PRIu64, written, stream->stream_d); - co_return written; -} - -task_t f$component_stream_read_exact(class_instance stream, int64_t len) { - auto *buffer = static_cast(RuntimeAllocator::current().alloc_script_memory(len)); - const auto read = co_await read_exact_from_stream(stream->stream_d, buffer, len); - string result{buffer, static_cast(read)}; - RuntimeAllocator::current().free_script_memory(buffer, len); - php_debug("read exact %d bytes from stream %" PRIu64, read, stream->stream_d); - co_return result; -} - -void f$component_close_stream(const class_instance &stream) { - get_component_context()->release_stream(stream.get()->stream_d); - stream.get()->stream_d = INVALID_PLATFORM_DESCRIPTOR; // TODO: convert stream object to null? -} - -void f$component_finish_stream_processing(const class_instance &stream) { - auto &component_ctx = *get_component_context(); - if (stream.get()->stream_d != component_ctx.standard_stream()) { - php_warning("call server finish query on non server stream %lu", stream.get()->stream_d); - return; - } - component_ctx.release_stream(component_ctx.standard_stream()); - stream.get()->stream_d = INVALID_PLATFORM_DESCRIPTOR; -} diff --git a/runtime-light/streams/interface.h b/runtime-light/streams/interface.h deleted file mode 100644 index 51915bef5f..0000000000 --- a/runtime-light/streams/interface.h +++ /dev/null @@ -1,45 +0,0 @@ -// 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/coroutine/task.h" -#include "runtime-light/streams/component-stream.h" - -constexpr int64_t v$COMPONENT_ERROR = -1; - -task_t f$component_get_http_query(); - -// === component query client blocked interface =================================================== - -task_t> f$component_client_send_query(string name, string message); - -task_t f$component_client_get_result(class_instance query); - -// === component query server blocked interface =================================================== - -task_t f$component_server_get_query(); - -task_t f$component_server_send_result(string message); - -// === component stream low-level interface ======================================================= - -class_instance f$component_open_stream(const string &name); - -task_t> f$component_accept_stream(); - -int64_t f$component_stream_write_nonblock(const class_instance &stream, const string &message); - -string f$component_stream_read_nonblock(const class_instance &stream); - -task_t f$component_stream_write_exact(class_instance stream, string message); - -task_t f$component_stream_read_exact(class_instance stream, int64_t len); - -void f$component_close_stream(const class_instance &stream); - -void f$component_finish_stream_processing(const class_instance &stream); diff --git a/runtime-light/streams/streams.cmake b/runtime-light/streams/streams.cmake index 205046fbd9..ee1acd1d8c 100644 --- a/runtime-light/streams/streams.cmake +++ b/runtime-light/streams/streams.cmake @@ -1,5 +1 @@ -prepend(RUNTIME_STREAMS_SRC streams/ - interface.cpp - streams.cpp - component-stream.cpp -) +prepend(RUNTIME_STREAMS_SRC streams/ streams.cpp) diff --git a/runtime-light/streams/streams.cpp b/runtime-light/streams/streams.cpp index 158e9fda00..2d19628090 100644 --- a/runtime-light/streams/streams.cpp +++ b/runtime-light/streams/streams.cpp @@ -9,12 +9,33 @@ #include #include +#include "runtime-core/runtime-core.h" #include "runtime-core/utils/kphp-assert-core.h" #include "runtime-light/coroutine/awaitable.h" +#include "runtime-light/coroutine/task.h" #include "runtime-light/header.h" #include "runtime-light/utils/context.h" +#include "runtime-light/utils/json-functions.h" -task_t> read_all_from_stream(uint64_t stream_d) { +namespace { + +void init_http_superglobals(const string &http_query) { + auto &component_ctx{*get_component_context()}; + component_ctx.php_script_mutable_globals_singleton.get_superglobals().v$_SERVER.set_value(string{"QUERY_TYPE"}, string{"http"}); + component_ctx.php_script_mutable_globals_singleton.get_superglobals().v$_POST = f$json_decode(http_query, true); +} + +} // namespace + +task_t accept_initial_stream() noexcept { + const auto incoming_stream_d{co_await wait_for_incoming_stream_t{}}; + const auto [buffer, size]{co_await read_all_from_stream(incoming_stream_d)}; + init_http_superglobals(string{buffer, static_cast(size)}); + get_platform_context()->allocator.free(buffer); + co_return incoming_stream_d; +} + +task_t> read_all_from_stream(uint64_t stream_d) noexcept { const auto &platform_ctx = *get_platform_context(); constexpr int32_t batch_size = 32; @@ -47,7 +68,7 @@ task_t> read_all_from_stream(uint64_t stream_d) { co_return std::make_pair(buffer, buffer_size); } -std::pair read_nonblock_from_stream(uint64_t stream_d) { +std::pair read_nonblock_from_stream(uint64_t stream_d) noexcept { const auto &platform_ctx = *get_platform_context(); constexpr int32_t batch_size = 32; @@ -80,7 +101,7 @@ std::pair read_nonblock_from_stream(uint64_t stream_d) { return std::make_pair(buffer, buffer_size); } -task_t read_exact_from_stream(uint64_t stream_d, char *buffer, int32_t len) { +task_t read_exact_from_stream(uint64_t stream_d, char *buffer, int32_t len) noexcept { const PlatformCtx &platform_ctx = *get_platform_context(); int32_t read = 0; @@ -105,7 +126,7 @@ task_t read_exact_from_stream(uint64_t stream_d, char *buffer, int32_t co_return read; } -task_t write_all_to_stream(uint64_t stream_d, const char *buffer, int32_t len) { +task_t write_all_to_stream(uint64_t stream_d, const char *buffer, int32_t len) noexcept { const auto &platform_ctx = *get_platform_context(); int32_t written = 0; @@ -135,7 +156,7 @@ task_t write_all_to_stream(uint64_t stream_d, const char *buffer, int32 co_return written; } -int32_t write_nonblock_to_stream(uint64_t stream_d, const char *buffer, int32_t len) { +int32_t write_nonblock_to_stream(uint64_t stream_d, const char *buffer, int32_t len) noexcept { const auto &platform_ctx = *get_platform_context(); int32_t written = 0; @@ -159,7 +180,7 @@ int32_t write_nonblock_to_stream(uint64_t stream_d, const char *buffer, int32_t return written; } -task_t write_exact_to_stream(uint64_t stream_d, const char *buffer, int32_t len) { +task_t write_exact_to_stream(uint64_t stream_d, const char *buffer, int32_t len) noexcept { const auto &platform_ctx = *get_platform_context(); int written = 0; diff --git a/runtime-light/streams/streams.h b/runtime-light/streams/streams.h index 5721a85d00..007cb1dbc9 100644 --- a/runtime-light/streams/streams.h +++ b/runtime-light/streams/streams.h @@ -9,18 +9,24 @@ #include "runtime-light/coroutine/task.h" +/** + * **Oneshot component only** + * Wait for initial stream and process it. There can be 2 types of initial (or starter) streams: 1. http; 2. job worker. + */ +task_t accept_initial_stream() noexcept; + // === read ======================================================================================= -task_t> read_all_from_stream(uint64_t stream_d); +task_t> read_all_from_stream(uint64_t stream_d) noexcept; -std::pair read_nonblock_from_stream(uint64_t stream_d); +std::pair read_nonblock_from_stream(uint64_t stream_d) noexcept; -task_t read_exact_from_stream(uint64_t stream_d, char *buffer, int32_t len); +task_t read_exact_from_stream(uint64_t stream_d, char *buffer, int32_t len) noexcept; // === write ====================================================================================== -task_t write_all_to_stream(uint64_t stream_d, const char *buffer, int32_t len); +task_t write_all_to_stream(uint64_t stream_d, const char *buffer, int32_t len) noexcept; -int32_t write_nonblock_to_stream(uint64_t stream_d, const char *buffer, int32_t len); +int32_t write_nonblock_to_stream(uint64_t stream_d, const char *buffer, int32_t len) noexcept; -task_t write_exact_to_stream(uint64_t stream_d, const char *buffer, int32_t len); +task_t write_exact_to_stream(uint64_t stream_d, const char *buffer, int32_t len) noexcept; diff --git a/tests/k2-components/dev_null.php b/tests/k2-components/dev_null.php index 99a368411c..3da6391230 100644 --- a/tests/k2-components/dev_null.php +++ b/tests/k2-components/dev_null.php @@ -1,3 +1,3 @@ is_write_closed() && !$stream_to_out->is_please_shutdown_write()) { - component_stream_write_exact($stream_to_out, "a"); -} - -component_close_stream($stream_to_out); -component_finish_stream_processing($input); - -$input = component_accept_stream(); - -while(!$input->is_write_closed() && !$input->is_please_shutdown_write()) { - component_stream_write_exact($input, "a"); -} - diff --git a/tests/k2-components/echo.php b/tests/k2-components/echo.php index c5f3cf1bf5..5631a08e58 100644 --- a/tests/k2-components/echo.php +++ b/tests/k2-components/echo.php @@ -6,7 +6,8 @@ function process_work($arg) { $arr[] = $arg; } -$str = component_server_get_query(); +$query = component_server_accept_query(); +$str = component_server_fetch_request($query); process_work($str); process_work('123213'); -component_server_send_result($str); +component_server_send_response($query, $str); diff --git a/tests/k2-components/forward.php b/tests/k2-components/forward.php index c74bd90d2e..7a739566de 100644 --- a/tests/k2-components/forward.php +++ b/tests/k2-components/forward.php @@ -1,10 +1,11 @@ Date: Wed, 28 Aug 2024 13:32:24 +0300 Subject: [PATCH 30/45] Fix error with k2-bin path (#1081) --- tests/kphp_tester.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/kphp_tester.py b/tests/kphp_tester.py index abb6253249..ce2cb96f2c 100755 --- a/tests/kphp_tester.py +++ b/tests/kphp_tester.py @@ -60,7 +60,7 @@ def make_kphp_once_runner(self, use_nocc, cxx_name, k2_bin): vkext_dir=os.path.abspath(os.path.join(tester_dir, os.path.pardir, "objs", "vkext")), use_nocc=use_nocc, cxx_name=cxx_name, - k2_bin=os.path.abspath(k2_bin) + k2_bin=None if k2_bin is None else os.path.abspath(k2_bin) ) def set_up_env_for_k2(self, cxx_name="clang++"): From 6e4dcaf4371a8d0b4035cb8cfdea5730611fc172 Mon Sep 17 00:00:00 2001 From: Mikhail Kornaukhov <66915223+mkornaukhov03@users.noreply.github.com> Date: Wed, 28 Aug 2024 17:25:30 +0300 Subject: [PATCH 31/45] Initial implementation of crypto for runtime-light (#1058) * Implementation of openssl_random_pseudo_bytes() and openssl_x509_parse() using crypto component that is provided by K2-platform * Initial version of updating TL-schema describing interface to interact with crypto component * Fix old and add new tests --- builtin-functions/kphp-light/functions.txt | 8 + common/tl/constants/common.h | 2 + .../stdlib/crypto/crypto-functions.cpp | 146 ++++++++++++++++++ .../stdlib/crypto/crypto-functions.h | 11 ++ runtime-light/stdlib/crypto/crypto_schema.tl | 34 ++++ runtime-light/stdlib/rpc/rpc-buffer.h | 78 ---------- .../openssl/10_openssl_x509_parse_light.php | 76 +++++++++ .../openssl/9_openssl_random_pseudo_bytes.php | 38 ++--- .../phpt/string_functions/010_xor_strings.php | 15 +- tests/python/lib/kphp_run_once.py | 2 +- 10 files changed, 299 insertions(+), 111 deletions(-) create mode 100644 runtime-light/stdlib/crypto/crypto-functions.cpp create mode 100644 runtime-light/stdlib/crypto/crypto-functions.h create mode 100644 runtime-light/stdlib/crypto/crypto_schema.tl delete mode 100644 runtime-light/stdlib/rpc/rpc-buffer.h create mode 100644 tests/phpt/openssl/10_openssl_x509_parse_light.php diff --git a/builtin-functions/kphp-light/functions.txt b/builtin-functions/kphp-light/functions.txt index 199ac70837..98369a5fae 100644 --- a/builtin-functions/kphp-light/functions.txt +++ b/builtin-functions/kphp-light/functions.txt @@ -184,6 +184,14 @@ function json_encode ($v ::: mixed, $options ::: int = 0) ::: string | false; function json_decode ($v ::: string, $assoc ::: bool = false) ::: mixed; +// === Crypto====================================================================================== + +/** @kphp-extern-func-info interruptible */ +function openssl_random_pseudo_bytes ($length ::: int) ::: string | false; + +/** @kphp-extern-func-info interruptible */ +function openssl_x509_parse ($x509cert ::: string, $shortnames ::: bool = true) ::: mixed[] | false; + // === Misc ======================================================================================= /** @kphp-extern-func-info cpp_template_call */ diff --git a/common/tl/constants/common.h b/common/tl/constants/common.h index 6933cc7703..70e41edf1f 100644 --- a/common/tl/constants/common.h +++ b/common/tl/constants/common.h @@ -2,6 +2,7 @@ // Copyright (c) 2020 LLC «V Kontakte» // Distributed under the GPL v3 License, see LICENSE.notice.txt +// TODO get rid of defines and rewrite on enum or constexpr inline /* Autogenerated from common.tl and left only used constants */ #pragma once #define TL__ 0x840e0eccU @@ -37,6 +38,7 @@ #define TL_TUPLE 0x9770768aU #define TL_VECTOR 0x1cb5c415U #define TL_VECTOR_TOTAL 0x10133f47U +#define TL_ZERO 0x00000000U #include namespace vk { diff --git a/runtime-light/stdlib/crypto/crypto-functions.cpp b/runtime-light/stdlib/crypto/crypto-functions.cpp new file mode 100644 index 0000000000..48b8a2b9b7 --- /dev/null +++ b/runtime-light/stdlib/crypto/crypto-functions.cpp @@ -0,0 +1,146 @@ +// 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/crypto/crypto-functions.h" + +#include "common/tl/constants/common.h" +#include "runtime-light/stdlib/component/component-api.h" +#include "runtime-light/tl/tl-core.h" + +namespace { + +// Crypto-Specific TL magics + +constexpr uint32_t TL_CERT_INFO_ITEM_LONG = 0x533f'f89f; +constexpr uint32_t TL_CERT_INFO_ITEM_STR = 0xc427'feef; +constexpr uint32_t TL_CERT_INFO_ITEM_DICT = 0x1ea8'a774; + +constexpr uint32_t TL_GET_PEM_CERT_INFO = 0xa50c'fd6c; +constexpr uint32_t TL_GET_CRYPTOSECURE_PSEUDORANDOM_BYTES = 0x2491'b81d; +} // namespace + +task_t> f$openssl_random_pseudo_bytes(int64_t length) noexcept { + if (length <= 0 || length > string::max_size()) { + co_return false; + } + + tl::TLBuffer buffer; + buffer.store_trivial(TL_GET_CRYPTOSECURE_PSEUDORANDOM_BYTES); + buffer.store_trivial(length); + + // TODO think about performance when transferring data + + string request_buf; + request_buf.append(buffer.data(), buffer.size()); + + auto query = f$component_client_send_request(string("crypto"), request_buf); + string resp = co_await f$component_client_fetch_response(co_await query); + + buffer.clean(); + buffer.store_bytes(resp.c_str(), resp.size()); + + std::optional magic = buffer.fetch_trivial(); + if (!magic.has_value() || *magic != TL_MAYBE_TRUE) { + co_return false; + } + std::string_view str_view = buffer.fetch_string(); + co_return string(str_view.data(), str_view.size()); +} + +task_t>> f$openssl_x509_parse(const string &data, bool shortnames) noexcept { + tl::TLBuffer buffer; + buffer.store_trivial(TL_GET_PEM_CERT_INFO); + buffer.store_trivial(shortnames ? TL_BOOL_TRUE : TL_BOOL_FALSE); + buffer.store_string(std::string_view(data.c_str(), data.size())); + + string request_buf; + request_buf.append(buffer.data(), buffer.size()); + + auto query = f$component_client_send_request(string("crypto"), request_buf); + string resp_from_platform = co_await f$component_client_fetch_response(co_await query); + + buffer.clean(); + buffer.store_bytes(resp_from_platform.c_str(), resp_from_platform.size()); + + if (const auto magic = buffer.fetch_trivial(); magic.value_or(TL_ZERO) != TL_MAYBE_TRUE) { + co_return false; + } + + if (const auto magic = buffer.fetch_trivial(); magic.value_or(TL_ZERO) != TL_DICTIONARY) { + co_return false; + } + + const std::optional size = buffer.fetch_trivial(); + if (!size.has_value()) { + co_return false; + } + + auto response = array::create(); + + for (uint32_t i = 0; i < size; ++i) { + const auto key_view = buffer.fetch_string(); + if (key_view.empty()) { + co_return false; + } + + const auto key = string(key_view.data(), key_view.length()); + + const std::optional magic = buffer.fetch_trivial(); + if (!magic.has_value()) { + co_return false; + } + + switch (*magic) { + case TL_CERT_INFO_ITEM_LONG: { + const std::optional val = buffer.fetch_trivial(); + if (!val.has_value()) { + co_return false; + } + response[key] = *val; + break; + } + case TL_CERT_INFO_ITEM_STR: { + const auto value_view = buffer.fetch_string(); + if (value_view.empty()) { + co_return false; + } + const auto value = string(value_view.data(), value_view.size()); + + response[key] = string(value_view.data(), value_view.size()); + break; + } + case TL_CERT_INFO_ITEM_DICT: { + auto sub_array = array::create(); + const std::optional sub_size = buffer.fetch_trivial(); + + if (!sub_size.has_value()) { + co_return false; + } + + for (size_t j = 0; j < sub_size; ++j) { + const auto sub_key_view = buffer.fetch_string(); + if (sub_key_view.empty()) { + co_return false; + } + const auto sub_key = string(sub_key_view.data(), sub_key_view.size()); + + const auto sub_value_view = buffer.fetch_string(); + if (sub_value_view.empty()) { + co_return false; + } + const auto sub_value = string(sub_value_view.data(), sub_value_view.size()); + + sub_array[sub_key] = sub_value; + } + response[key] = sub_array; + + break; + } + default: + co_return false; + } + } + + co_return response; +} diff --git a/runtime-light/stdlib/crypto/crypto-functions.h b/runtime-light/stdlib/crypto/crypto-functions.h new file mode 100644 index 0000000000..c8776625ef --- /dev/null +++ b/runtime-light/stdlib/crypto/crypto-functions.h @@ -0,0 +1,11 @@ +// 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/runtime-core.h" +#include "runtime-light/coroutine/task.h" + +task_t> f$openssl_random_pseudo_bytes(int64_t length) noexcept; +task_t>> f$openssl_x509_parse(const string &data, bool shortnames = true) noexcept; diff --git a/runtime-light/stdlib/crypto/crypto_schema.tl b/runtime-light/stdlib/crypto/crypto_schema.tl new file mode 100644 index 0000000000..6eb087fd7c --- /dev/null +++ b/runtime-light/stdlib/crypto/crypto_schema.tl @@ -0,0 +1,34 @@ +---types--- + +// Boolean +boolFalse#bc799737 = Bool; +boolTrue#997275b5 = Bool; + +// Maybe +resultFalse#27930a7b {t:Type} = Maybe t; +resultTrue#3f9c8ef8 {t:Type} result:t = Maybe t; + +// Vector +vector#1cb5c415 {t:Type} # [t] = Vector t; + +// Tuple +tuple#9770768a {t:Type} {n:#} [t] = Tuple t n; + +// Dictionary +dictionaryField {t:Type} key:string value:t = DictionaryField t; +dictionary#1f4c618f {t:Type} %(Vector %(DictionaryField t)) = Dictionary t; + +certInfoItemLong#533ff89f l:long = CertInfoItem; +certInfoItemStr#c427feef s:string = CertInfoItem; +certInfoItemDict#1ea8a774 d: %(Dictionary string) = CertInfoItem; + +---functions--- + +getCryptosecurePseudorandomBytes#2491b81d + size : int + = Maybe string; + +getPemCertInfo#a50cfd6c + short : Bool + bytes : string + = Maybe (Dictionary CertInfoItem); diff --git a/runtime-light/stdlib/rpc/rpc-buffer.h b/runtime-light/stdlib/rpc/rpc-buffer.h deleted file mode 100644 index 3d8f0b0f3e..0000000000 --- a/runtime-light/stdlib/rpc/rpc-buffer.h +++ /dev/null @@ -1,78 +0,0 @@ -// 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/runtime-core.h" -#include "runtime-core/utils/kphp-assert-core.h" -#include "runtime-light/utils/concepts.h" - -class RpcBuffer : private vk::not_copyable { - string_buffer m_buffer; - size_t m_pos{0}; - size_t m_remaining{0}; - -public: - RpcBuffer() = default; - - const char *data() const noexcept { - return m_buffer.buffer(); - } - - size_t size() const noexcept { - return static_cast(m_buffer.size()); - } - - size_t remaining() const noexcept { - return m_remaining; - } - - size_t pos() const noexcept { - return m_pos; - } - - void clean() noexcept { - m_buffer.clean(); - m_pos = 0; - m_remaining = 0; - } - - void reset(size_t pos) noexcept { - php_assert(pos >= 0 && pos <= size()); - m_pos = pos; - m_remaining = size() - m_pos; - } - - void adjust(size_t len) noexcept { - php_assert(m_pos + len <= size()); - m_pos += len; - m_remaining -= len; - } - - void store(const char *src, size_t len) noexcept { - m_buffer.append(src, len); - m_remaining += len; - } - - template - void store_trivial(const T &t) noexcept { - store(reinterpret_cast(&t), sizeof(T)); - } - - template - std::optional fetch_trivial() noexcept { - if (m_remaining < sizeof(T)) { - return std::nullopt; - } - - const auto t{*reinterpret_cast(m_buffer.c_str() + m_pos)}; - m_pos += sizeof(T); - m_remaining -= sizeof(T); - return t; - } -}; diff --git a/tests/phpt/openssl/10_openssl_x509_parse_light.php b/tests/phpt/openssl/10_openssl_x509_parse_light.php new file mode 100644 index 0000000000..a311f97a39 --- /dev/null +++ b/tests/phpt/openssl/10_openssl_x509_parse_light.php @@ -0,0 +1,76 @@ +@ok + Date: Thu, 29 Aug 2024 14:42:59 +0300 Subject: [PATCH 32/45] add stabs for unsupported whitelisted functions (#1078) Add new @kphp-extern-func-info tag `generate-stub` to ask compiler to generate body of function with call to php_critical_error Also add some template stubs in runtime headers --- builtin-functions/kphp-light/functions.txt | 12 +- .../kphp-light/unsupported-functions.txt | 22 + .../kphp-light/unsupported/arrays.txt | 117 +++++ .../kphp-light/unsupported/crypto.txt | 46 ++ .../kphp-light/unsupported/curl.txt | 45 ++ .../kphp-light/unsupported/error.txt | 61 +++ .../kphp-light/unsupported/file.txt | 86 ++++ .../kphp-light/unsupported/fork.txt | 16 + .../kphp-light/unsupported/hash.txt | 41 ++ .../kphp-light/unsupported/job-worker.txt | 44 ++ .../kphp-light/unsupported/kml.txt | 6 + .../kphp-light/unsupported/kphp-toggles.txt | 16 + .../kphp-light/unsupported/kphp-tracing.txt | 57 +++ .../kphp-light/unsupported/kphp_internal.txt | 35 ++ .../kphp-light/unsupported/math.txt | 137 ++++++ .../kphp-light/unsupported/misc.txt | 78 ++++ .../kphp-light/unsupported/regex.txt | 33 ++ .../kphp-light/unsupported/rpc.txt | 68 +++ .../kphp-light/unsupported/serialize.txt | 29 ++ .../kphp-light/unsupported/server.txt | 118 +++++ .../kphp-light/unsupported/string.txt | 149 ++++++ .../kphp-light/unsupported/time.txt | 126 +++++ .../kphp-light/unsupported/vkext.txt | 25 + compiler/code-gen/includes.cpp | 2 +- compiler/code-gen/vertex-compiler.cpp | 13 + compiler/data/function-data.cpp | 5 +- compiler/data/function-data.h | 1 + compiler/pipes/calc-func-dep.cpp | 3 +- compiler/pipes/collect-main-edges.cpp | 1 - compiler/pipes/parse-and-apply-phpdoc.cpp | 2 + runtime-light/component/component.h | 3 + runtime-light/stdlib/array/array-functions.h | 430 ++++++++++++++++++ .../stdlib/instance-cache/instance-cache.h | 17 + runtime-light/stdlib/math/math.h | 37 ++ .../stdlib/regex/regex-functions.cpp | 11 + runtime-light/stdlib/regex/regex-functions.h | 50 ++ runtime-light/stdlib/rpc/rpc-api.h | 10 + runtime-light/stdlib/stdlib.cmake | 31 +- runtime-light/stdlib/string/concat.h | 10 + 39 files changed, 1970 insertions(+), 23 deletions(-) create mode 100644 builtin-functions/kphp-light/unsupported-functions.txt create mode 100644 builtin-functions/kphp-light/unsupported/arrays.txt create mode 100644 builtin-functions/kphp-light/unsupported/crypto.txt create mode 100644 builtin-functions/kphp-light/unsupported/curl.txt create mode 100644 builtin-functions/kphp-light/unsupported/error.txt create mode 100644 builtin-functions/kphp-light/unsupported/file.txt create mode 100644 builtin-functions/kphp-light/unsupported/fork.txt create mode 100644 builtin-functions/kphp-light/unsupported/hash.txt create mode 100644 builtin-functions/kphp-light/unsupported/job-worker.txt create mode 100644 builtin-functions/kphp-light/unsupported/kml.txt create mode 100644 builtin-functions/kphp-light/unsupported/kphp-toggles.txt create mode 100644 builtin-functions/kphp-light/unsupported/kphp-tracing.txt create mode 100644 builtin-functions/kphp-light/unsupported/kphp_internal.txt create mode 100644 builtin-functions/kphp-light/unsupported/math.txt create mode 100644 builtin-functions/kphp-light/unsupported/misc.txt create mode 100644 builtin-functions/kphp-light/unsupported/regex.txt create mode 100644 builtin-functions/kphp-light/unsupported/rpc.txt create mode 100644 builtin-functions/kphp-light/unsupported/serialize.txt create mode 100644 builtin-functions/kphp-light/unsupported/server.txt create mode 100644 builtin-functions/kphp-light/unsupported/string.txt create mode 100644 builtin-functions/kphp-light/unsupported/time.txt create mode 100644 builtin-functions/kphp-light/unsupported/vkext.txt create mode 100644 runtime-light/stdlib/array/array-functions.h create mode 100644 runtime-light/stdlib/instance-cache/instance-cache.h create mode 100644 runtime-light/stdlib/math/math.h create mode 100644 runtime-light/stdlib/regex/regex-functions.cpp create mode 100644 runtime-light/stdlib/regex/regex-functions.h diff --git a/builtin-functions/kphp-light/functions.txt b/builtin-functions/kphp-light/functions.txt index 98369a5fae..043afe81dd 100644 --- a/builtin-functions/kphp-light/functions.txt +++ b/builtin-functions/kphp-light/functions.txt @@ -1,9 +1,14 @@ ; +function array_column ($a ::: array, $column_key, $index_key = null) ::: array< ^1[*][*] > | false; +function array_unset (&$a ::: array, any $key) ::: ^1[*]; + +function array_filter ($a ::: array, callable(^1[*] $x):bool $callback = TODO) ::: ^1; +function array_filter_by_key ($a ::: array, callable(mixed $key):bool $callback) ::: ^1; +function array_map (callable(^2[*] $x):any $callback, $a ::: array) ::: ^1() []; +/** @kphp-extern-func-info cpp_template_call */ +function array_reduce ($a ::: array, callable(^3 | ^2() $carry, ^1[*] $item):any $callback, $initial ::: any) ::: ^2() | ^3; +function array_reserve (&$a ::: array, $int_size ::: int, $string_size ::: int, $make_vector_if_possible ::: bool) ::: void; +function array_reserve_vector (&$a ::: array, $size ::: int) ::: void; +function array_reserve_map_int_keys (&$a ::: array, $size ::: int) ::: void; +function array_reserve_map_string_keys (&$a ::: array, $size ::: int) ::: void; +function array_reserve_from (&$a ::: array, $base ::: array) ::: void; +function array_is_vector ($a ::: array) ::: bool; +function array_is_list ($a ::: array) ::: bool; + +define('SORT_REGULAR', 0); +define('SORT_NUMERIC', 1); +define('SORT_STRING', 2); + +function asort (&$a ::: array, $flag ::: int = SORT_REGULAR) ::: void; +function arsort (&$a ::: array, $flag ::: int = SORT_REGULAR) ::: void; +function ksort (&$a ::: array, $flag ::: int = SORT_REGULAR) ::: void; +function krsort (&$a ::: array, $flag ::: int = SORT_REGULAR) ::: void; +function natsort (&$a ::: array) ::: void; +function rsort (&$a ::: array, $flag ::: int = SORT_REGULAR) ::: void; +function shuffle (&$a ::: array) ::: void; +function sort (&$a ::: array, $flag ::: int = SORT_REGULAR) ::: void; +function uasort (&$a ::: array, callable(^1[*] $x, ^1[*] $y):int $callback) ::: void; +function uksort (&$a ::: array, callable(mixed $x, mixed $y):int $callback) ::: void; +function usort (&$a ::: array, callable(^1[*] $x, ^1[*] $y):int $callback) ::: void; + + +/** @kphp-extern-func-info cpp_template_call */ +function vk_dot_product ($a ::: array, $b ::: array) ::: ^1[*] | ^2[*]; + + +function to_array_debug(any $instance, bool $with_class_names = false) ::: mixed[]; + +function implode ($s ::: string, $v ::: array) ::: string; + +/** @kphp-extern-func-info generate-stub */ +function explode ($delimiter ::: string, $str ::: string, $limit ::: int = PHP_INT_MAX) ::: string[]; \ No newline at end of file diff --git a/builtin-functions/kphp-light/unsupported/crypto.txt b/builtin-functions/kphp-light/unsupported/crypto.txt new file mode 100644 index 0000000000..54341483c7 --- /dev/null +++ b/builtin-functions/kphp-light/unsupported/crypto.txt @@ -0,0 +1,46 @@ +) ::: bool; + +/** @kphp-extern-func-info can_throw generate-stub cpp_template_call */ +function wait_multi (future[] $resumables) ::: (^1[*][*] | null)[]; + +/** @kphp-extern-func-info generate-stub */ +function wait_queue_create (array< future | false > $request_ids = []) ::: future_queue<^1[*][*]>; +/** @kphp-extern-func-info generate-stub */ +function wait_queue_push (future_queue &$queue_id, future | false $request_ids) ::: void; +/** @kphp-extern-func-info generate-stub */ +function wait_queue_empty (future_queue $queue_id) ::: bool; +/** @kphp-extern-func-info generate-stub */ +function wait_queue_next (future_queue $queue_id, $timeout ::: float = -1.0) ::: future<^1[*]> | false; diff --git a/builtin-functions/kphp-light/unsupported/hash.txt b/builtin-functions/kphp-light/unsupported/hash.txt new file mode 100644 index 0000000000..746ec07096 --- /dev/null +++ b/builtin-functions/kphp-light/unsupported/hash.txt @@ -0,0 +1,41 @@ + | false; +/** @kphp-extern-func-info generate-stub */ +function kphp_job_worker_start_no_reply(KphpJobWorkerRequest $request, float $timeout) ::: bool; +/** @kphp-extern-func-info generate-stub */ +function kphp_job_worker_start_multi(KphpJobWorkerRequest[] $request, float $timeout) ::: (future | false)[]; +/** @kphp-extern-func-info generate-stub */ +function kphp_job_worker_store_response(KphpJobWorkerResponse $response) ::: int; + diff --git a/builtin-functions/kphp-light/unsupported/kml.txt b/builtin-functions/kphp-light/unsupported/kml.txt new file mode 100644 index 0000000000..6f8ef6affd --- /dev/null +++ b/builtin-functions/kphp-light/unsupported/kml.txt @@ -0,0 +1,6 @@ +; +/** @kphp-extern-func-info cpp_template_call can_throw */ +function instance_deserialize_safe($serialized ::: string, $to_type ::: string) ::: instance<^2>; + + + diff --git a/builtin-functions/kphp-light/unsupported/server.txt b/builtin-functions/kphp-light/unsupported/server.txt new file mode 100644 index 0000000000..2c9cefd99d --- /dev/null +++ b/builtin-functions/kphp-light/unsupported/server.txt @@ -0,0 +1,118 @@ +; + +function instance_cache_store(string $key, object $value, int $ttl = 0) ::: bool; +/** @kphp-extern-func-info generate-stub */ +function instance_cache_update_ttl(string $key, int $ttl = 0) ::: bool; +/** @kphp-extern-func-info generate-stub */ +function instance_cache_delete(string $key) ::: bool; + + +/** @kphp-extern-func-info generate-stub */ +function ip2long ($ip ::: string) ::: int | false; +/** @kphp-extern-func-info generate-stub */ +function ip2ulong ($ip ::: string) ::: string | false; +/** @kphp-extern-func-info generate-stub */ +function long2ip ($ip ::: int) ::: string; +/** @kphp-extern-func-info generate-stub */ +function thread_pool_test_load($size ::: int, $n ::: int, $a ::: float, $b ::: float) ::: float; +/** @kphp-extern-func-info generate-stub */ +function gethostbynamel ($name ::: string) ::: string[] | false; +/** @kphp-extern-func-info generate-stub */ +function inet_pton ($address ::: string) ::: string | false; + + + +/** @kphp-extern-func-info generate-stub */ +function kphp_get_runtime_config() ::: mixed; + + +/** @kphp-extern-func-info generate-stub */ +function memory_get_usage ($real_usage ::: bool = false) ::: int; +/** @kphp-extern-func-info generate-stub */ +function memory_get_peak_usage ($real_usage ::: bool = false) ::: int; +/** @kphp-extern-func-info generate-stub */ +function memory_get_total_usage() ::: int; +/** @kphp-extern-func-info generate-stub */ +function memory_get_static_usage() ::: int; +/** @kphp-extern-func-info generate-stub */ +function memory_get_detailed_stats() ::: int[]; + +/** @kphp-extern-func-info generate-stub */ +function posix_getpid() ::: int; +/** @kphp-extern-func-info generate-stub */ +function posix_getuid() ::: int; +/** @kphp-extern-func-info generate-stub */ +function posix_getpwuid($uid ::: int) ::: mixed[] | false; + +function kphp_extended_instance_cache_metrics_init(callable(string $key):string $normalization_function) ::: void; + + +function register_shutdown_function (callable():void $function) ::: void; +function register_kphp_on_warning_callback(callable(string $warning_message, string[] $stacktrace):void $stacktrace) ::: void; +function register_kphp_on_oom_callback(callable():void $callback) ::: bool; + + +function getopt ($options ::: string, $longopt ::: array = array(), ?int &$rest_index = null) ::: mixed[] | false; + +/** @kphp-extern-func-info generate-stub */ +function profiler_set_function_label($label ::: string) ::: void; + +/** @kphp-extern-func-info generate-stub */ +function setlocale ($category ::: int, $locale ::: string) ::: string | false; + +/** @kphp-extern-func-info generate-stub */ +function debug_backtrace() ::: string[][]; + +/** @kphp-extern-func-info generate-stub */ +function estimate_memory_usage($value ::: any) ::: int; + +/** @kphp-extern-func-info generate-stub */ +function is_confdata_loaded() ::: bool; +/** @kphp-extern-func-info generate-stub */ +function confdata_get_value($key ::: string) ::: mixed; +/** @kphp-extern-func-info generate-stub */ +function confdata_get_values_by_predefined_wildcard($wildcard ::: string) ::: mixed[]; + +/** @kphp-extern-func-info generate-stub */ +function header ($str ::: string, $replace ::: bool = true, $http_response_code ::: int = 0) ::: void; +/** @kphp-extern-func-info generate-stub */ +function headers_list () ::: string[]; +/** @kphp-extern-func-info generate-stub */ +function send_http_103_early_hints($headers ::: string[]) ::: void; +/** @kphp-extern-func-info generate-stub */ +function ignore_user_abort ($enable ::: ?bool = null) ::: int; +/** @kphp-extern-func-info generate-stub */ +function flush() ::: void; + +define('PHP_QUERY_RFC1738', 1); +define('PHP_QUERY_RFC3986', 2); + + +/** @kphp-extern-func-info generate-stub */ +function prepare_search_query ($query ::: string) ::: string; + +function http_build_query ($str ::: array, $numeric_prefix ::: string = '', $arg_separator ::: string = '&', $enc_type ::: int = PHP_QUERY_RFC1738) ::: string; diff --git a/builtin-functions/kphp-light/unsupported/string.txt b/builtin-functions/kphp-light/unsupported/string.txt new file mode 100644 index 0000000000..fd07eac1ea --- /dev/null +++ b/builtin-functions/kphp-light/unsupported/string.txt @@ -0,0 +1,149 @@ +settings().dest_cpp_dir.get() + function->header_full_name; auto relative_path = make_relative_path(source_full_path, to_include->header_full_name); lib_headers_.emplace(to_include->header_full_name, std::move(relative_path)); - } else if (!to_include->is_extern()) { + } else if (!to_include->is_extern() || to_include->need_generated_stub) { kphp_assert(!to_include->header_full_name.empty()); internal_headers_.emplace(to_include->header_full_name); } diff --git a/compiler/code-gen/vertex-compiler.cpp b/compiler/code-gen/vertex-compiler.cpp index 177251ae87..892a62d11e 100644 --- a/compiler/code-gen/vertex-compiler.cpp +++ b/compiler/code-gen/vertex-compiler.cpp @@ -1362,6 +1362,14 @@ bool compile_tracing_profiler(FunctionPtr func, CodeGenerator &W) { return true; } +void compile_generated_stub(VertexAdaptor func_root, CodeGenerator &W) { + FunctionPtr func = func_root->func_id; + W << FunctionDeclaration(func, false) << " " << + BEGIN; + W << "php_critical_error(\"call to unsupported function : " << func->name << "\");" << NL; + W << END << NL; +} + void compile_function_resumable(VertexAdaptor func_root, CodeGenerator &W) { FunctionPtr func = func_root->func_id; W << "//RESUMABLE FUNCTION IMPLEMENTATION" << NL; @@ -1473,6 +1481,11 @@ void compile_function(VertexAdaptor func_root, CodeGenerator &W) { W.get_context().resumable_flag = func->is_resumable; W.get_context().interruptible_flag = func->is_interruptible; + if (func->need_generated_stub) { + compile_generated_stub(func_root, W); + return; + } + if (func->is_resumable) { compile_function_resumable(func_root, W); return; diff --git a/compiler/data/function-data.cpp b/compiler/data/function-data.cpp index f4ea8ed5ff..babc7a651c 100644 --- a/compiler/data/function-data.cpp +++ b/compiler/data/function-data.cpp @@ -295,9 +295,10 @@ VarPtr FunctionData::find_var_by_name(const std::string &var_name) { } bool FunctionData::does_need_codegen() const { - return type != FunctionData::func_class_holder && + return need_generated_stub || + (type != FunctionData::func_class_holder && type != FunctionData::func_extern && - (body_seq != body_value::empty || G->get_main_file()->main_function == get_self() || is_lambda()); + (body_seq != body_value::empty || G->get_main_file()->main_function == get_self() || is_lambda())); } bool operator<(FunctionPtr a, FunctionPtr b) { diff --git a/compiler/data/function-data.h b/compiler/data/function-data.h index 1a3662a74c..06e637d1d5 100644 --- a/compiler/data/function-data.h +++ b/compiler/data/function-data.h @@ -119,6 +119,7 @@ class FunctionData { bool is_resumable = false; bool is_interruptible = false; bool is_k2_fork = false; + bool need_generated_stub = false; bool can_be_implicitly_interrupted_by_other_resumable = false; bool is_virtual_method = false; bool is_overridden_method = false; diff --git a/compiler/pipes/calc-func-dep.cpp b/compiler/pipes/calc-func-dep.cpp index b5104b3ecb..a186104bdd 100644 --- a/compiler/pipes/calc-func-dep.cpp +++ b/compiler/pipes/calc-func-dep.cpp @@ -57,7 +57,8 @@ VertexPtr CalcFuncDepPass::on_enter_vertex(VertexPtr vertex) { // 2) build resumable call graph — to propagate resumable state and to calc resumable chains // 3) build interruptible call graph - calc interruptible chains // adding all built-in calls won't affect codegen, it will just overhead call graphs and execution time - if (!other_function->is_extern() || other_function->is_resumable || other_function->is_imported_from_static_lib() || other_function->is_interruptible) { + if (!other_function->is_extern() || other_function->is_resumable || other_function->is_imported_from_static_lib() + || other_function->is_interruptible || other_function->need_generated_stub) { data.dep.push_back(other_function); } calls.push_back(other_function); diff --git a/compiler/pipes/collect-main-edges.cpp b/compiler/pipes/collect-main-edges.cpp index e4d4597f14..96b6510aa8 100644 --- a/compiler/pipes/collect-main-edges.cpp +++ b/compiler/pipes/collect-main-edges.cpp @@ -328,7 +328,6 @@ void CollectMainEdgesPass::on_func_call(VertexAdaptor call) { VertexPtr arg = call->args()[i]; auto param = function_params[i].as(); bool is_cast_param = !stage::get_function()->file_id->is_strict_types && param->is_cast_param; - // call an extern function having a callback type description, like 'callable(^1[*]) : bool' if (function->is_extern() && param->type_hint && param->type_hint->try_as()) { // for FFI calls, php2c(null) is used to express a null function pointer diff --git a/compiler/pipes/parse-and-apply-phpdoc.cpp b/compiler/pipes/parse-and-apply-phpdoc.cpp index 561dc194f6..559bda7096 100644 --- a/compiler/pipes/parse-and-apply-phpdoc.cpp +++ b/compiler/pipes/parse-and-apply-phpdoc.cpp @@ -283,6 +283,8 @@ class ParseAndApplyPhpDocForFunction { f_->tl_common_h_dep = true; } else if (token == "interruptible") { f_->is_interruptible = true; + } else if (token == "generate-stub") { + f_->need_generated_stub = true; } else { kphp_error(0, fmt_format("Unknown @kphp-extern-func-info {}", token)); } diff --git a/runtime-light/component/component.h b/runtime-light/component/component.h index b58a81dca6..4560999d76 100644 --- a/runtime-light/component/component.h +++ b/runtime-light/component/component.h @@ -18,6 +18,7 @@ #include "runtime-light/scheduler/scheduler.h" #include "runtime-light/stdlib/fork/fork-context.h" #include "runtime-light/stdlib/output/output-buffer.h" +#include "runtime-light/stdlib/regex/regex-functions.h" #include "runtime-light/stdlib/rpc/rpc-context.h" constexpr uint64_t INVALID_PLATFORM_DESCRIPTOR = 0; @@ -78,6 +79,8 @@ struct ComponentState { KphpCoreContext kphp_core_context; RpcComponentContext rpc_component_context; + RegexComponentState regex_component_context; + private: task_t main_task; diff --git a/runtime-light/stdlib/array/array-functions.h b/runtime-light/stdlib/array/array-functions.h new file mode 100644 index 0000000000..bd5ac8cc1a --- /dev/null +++ b/runtime-light/stdlib/array/array-functions.h @@ -0,0 +1,430 @@ +// 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/runtime-core.h" + +constexpr int64_t SORT_REGULAR = 0; +constexpr int64_t SORT_NUMERIC = 1; +constexpr int64_t SORT_STRING = 2; + +template +string f$implode(const string &s, const array &a) { + php_critical_error("call to unsupported function"); +} + +template +array> f$array_chunk(const array &a, int64_t chunk_size, bool preserve_keys = false) { + php_critical_error("call to unsupported function"); +} + +template +array f$array_slice(const array &a, int64_t offset, const mixed &length_var = mixed(), bool preserve_keys = false) { + php_critical_error("call to unsupported function"); +} + +template +array f$array_splice(array &a, int64_t offset, int64_t length, const array &) { + php_critical_error("call to unsupported function"); +} + +template +array f$array_splice(array &a, int64_t offset, int64_t length = std::numeric_limits::max(), const array &replacement = array()) { + php_critical_error("call to unsupported function"); +} + +template +ReturnT f$array_pad(const array &a, int64_t size, const DefaultValueT &default_value) { + php_critical_error("call to unsupported function"); +} + +template +ReturnT f$array_pad(const array &a, int64_t size, const DefaultValueT &default_value) { + php_critical_error("call to unsupported function"); +} + +template +array f$array_filter(const array &a) noexcept { + php_critical_error("call to unsupported function"); +} + +template +array f$array_filter(const array &a, const T1 &callback) noexcept { + php_critical_error("call to unsupported function"); +} + +template +array f$array_filter_by_key(const array &a, const T1 &callback) noexcept { + php_critical_error("call to unsupported function"); +} + +template +T f$array_merge_spread(const T &a1) { + php_critical_error("call to unsupported function"); +} + +template +T f$array_merge_spread(const T &a1, const T &a2) { + php_critical_error("call to unsupported function"); +} + +template +T f$array_merge_spread(const T &a1, const T &a2, const T &a3, const T &a4 = T(), const T &a5 = T(), const T &a6 = T(), const T &a7 = T(), const T &a8 = T(), + const T &a9 = T(), const T &a10 = T(), const T &a11 = T(), const T &a12 = T()) { + php_critical_error("call to unsupported function"); +} + +template +T f$array_merge(const T &a1) { + php_critical_error("call to unsupported function"); +} + +template +T f$array_merge(const T &a1, const T &a2) { + php_critical_error("call to unsupported function"); +} + +template +T f$array_merge(const T &a1, const T &a2, const T &a3, const T &a4 = T(), const T &a5 = T(), const T &a6 = T(), const T &a7 = T(), const T &a8 = T(), + const T &a9 = T(), const T &a10 = T(), const T &a11 = T(), const T &a12 = T()) { + php_critical_error("call to unsupported function"); +} + +template +void f$array_merge_into(T &a, const T1 &another_array) { + php_critical_error("call to unsupported function"); +} + +template +T f$array_replace(const T &base_array, const T &replacements = T()) { + php_critical_error("call to unsupported function"); +} + +template +T f$array_replace(const T &base_array, const T &replacements_1, const T &replacements_2, const T &replacements_3 = T(), const T &replacements_4 = T(), + const T &replacements_5 = T(), const T &replacements_6 = T(), const T &replacements_7 = T(), const T &replacements_8 = T(), + const T &replacements_9 = T(), const T &replacements_10 = T(), const T &replacements_11 = T()) { + php_critical_error("call to unsupported function"); +} + +template +array f$array_intersect_key(const array &a1, const array &a2) { + php_critical_error("call to unsupported function"); +} + +template +array f$array_intersect(const array &a1, const array &a2) { + php_critical_error("call to unsupported function"); +} + +template +array f$array_intersect_assoc(const array &a1, const array &a2) { + php_critical_error("call to unsupported function"); +} + +template +array f$array_intersect_assoc(const array &a1, const array &a2, const array &a3) { + php_critical_error("call to unsupported function"); +} + +template +array f$array_diff_key(const array &a1, const array &a2) { + php_critical_error("call to unsupported function"); +} + +template +array f$array_diff(const array &a1, const array &a2) { + php_critical_error("call to unsupported function"); +} + +template +array f$array_diff(const array &a1, const array &a2, const array &a3) { + php_critical_error("call to unsupported function"); +} + +template +array f$array_diff_assoc(const array &a1, const array &a2) { + php_critical_error("call to unsupported function"); +} + +template +array f$array_diff_assoc(const array &a1, const array &a2, const array &a3) { + php_critical_error("call to unsupported function"); +} + +template +array f$array_reverse(const array &a, bool preserve_keys = false) { + php_critical_error("call to unsupported function"); +} + +template +T f$array_shift(array &a) { + php_critical_error("call to unsupported function"); +} + +template +int64_t f$array_unshift(array &a, const T1 &val) { + php_critical_error("call to unsupported function"); +} + +template +bool f$array_key_exists(int64_t int_key, const array &a) { + php_critical_error("call to unsupported function"); +} + +template +bool f$array_key_exists(const string &string_key, const array &a) { + php_critical_error("call to unsupported function"); +} + +template +bool f$array_key_exists(const mixed &v, const array &a) { + php_critical_error("call to unsupported function"); +} + +template +bool f$array_key_exists(const Optional &v, const array &a) { + php_critical_error("call to unsupported function"); +} + +template>> +bool f$array_key_exists(K, const array &) { + php_critical_error("call to unsupported function"); +} + +template +typename array::key_type f$array_search(const T1 &val, const array &a, bool strict = false) { + php_critical_error("call to unsupported function"); +} + +template +typename array::key_type f$array_rand(const array &a) { + php_critical_error("call to unsupported function"); +} + +template +mixed f$array_rand(const array &a, int64_t num) { + php_critical_error("call to unsupported function"); +} + +template +array::key_type> f$array_keys(const array &a) { + php_critical_error("call to unsupported function"); +} + +template +array f$array_keys_as_strings(const array &a) { + php_critical_error("call to unsupported function"); +} + +template +array f$array_keys_as_ints(const array &a) { + php_critical_error("call to unsupported function"); +} + +template +array f$array_values(const array &a) { + php_critical_error("call to unsupported function"); +} + +template +array f$array_unique(const array &a, int64_t flags = SORT_STRING) { + php_critical_error("call to unsupported function"); +} + +template +array f$array_count_values(const array &a) { + php_critical_error("call to unsupported function"); +} + +template +array::key_type> f$array_flip(const array &a) { + php_critical_error("call to unsupported function"); +} + +template +bool f$in_array(const T1 &value, const array &a, bool strict = false) { + php_critical_error("call to unsupported function"); +} + +template +array f$array_fill(int64_t start_index, int64_t num, const T &value) { + php_critical_error("call to unsupported function"); +} + +template +array f$array_fill_keys(const array &keys, const T &value) { + php_critical_error("call to unsupported function"); +} + +template +array f$array_combine(const array &keys, const array &values) { + php_critical_error("call to unsupported function"); +} + +template +int64_t f$array_push(array &a, const T2 &val) { + php_critical_error("call to unsupported function"); +} + +template +int64_t f$array_push(array &a, const T2 &val2, const T3 &val3) { + php_critical_error("call to unsupported function"); +} + +template +int64_t f$array_push(array &a, const T2 &val2, const T3 &val3, const T4 &val4) { + php_critical_error("call to unsupported function"); +} + +template +int64_t f$array_push(array &a, const T2 &val2, const T3 &val3, const T4 &val4, const T5 &val5) { + php_critical_error("call to unsupported function"); +} + +template +int64_t f$array_push(array &a, const T2 &val2, const T3 &val3, const T4 &val4, const T5 &val5, const T6 &val6) { + php_critical_error("call to unsupported function"); +} + +template +T f$array_pop(array &a) { + php_critical_error("call to unsupported function"); +} + +template +void f$array_reserve(array &a, int64_t int_size, int64_t string_size, bool make_vector_if_possible = true) { + php_critical_error("call to unsupported function"); +} + +template +void f$array_reserve_vector(array &a, int64_t size) { + php_critical_error("call to unsupported function"); +} + +template +void f$array_reserve_map_int_keys(array &a, int64_t size) { + php_critical_error("call to unsupported function"); +} + +template +void f$array_reserve_map_string_keys(array &a, int64_t size) { + php_critical_error("call to unsupported function"); +} + +template +void f$array_reserve_from(array &a, const array &base) { + php_critical_error("call to unsupported function"); +} + +template +bool f$array_is_vector(const array &a) { + php_critical_error("call to unsupported function"); +} + +template +bool f$array_is_list(const array &a) { + php_critical_error("call to unsupported function"); +} + +template +void f$shuffle(array &a) { + php_critical_error("call to unsupported function"); +} + +template +void f$sort(array &a, int64_t flag = SORT_REGULAR) { + php_critical_error("call to unsupported function"); +} + +template +void f$rsort(array &a, int64_t flag = SORT_REGULAR) { + php_critical_error("call to unsupported function"); +} + +template +void f$usort(array &a, const T1 &compare) { + php_critical_error("call to unsupported function"); +} + +template +void f$asort(array &a, int64_t flag = SORT_REGULAR) { + php_critical_error("call to unsupported function"); +} + +template +void f$arsort(array &a, int64_t flag = SORT_REGULAR) { + php_critical_error("call to unsupported function"); +} + +template +void f$uasort(array &a, const T1 &compare) { + php_critical_error("call to unsupported function"); +} + +template +void f$ksort(array &a, int64_t flag = SORT_REGULAR) { + php_critical_error("call to unsupported function"); +} + +template +void f$krsort(array &a, int64_t flag = SORT_REGULAR) { + php_critical_error("call to unsupported function"); +} + +template +void f$uksort(array &a, const T1 &compare) { + php_critical_error("call to unsupported function"); +} + +template +void f$natsort(array &a) { + php_critical_error("call to unsupported function"); +} + +template{}, int64_t, double>> +ReturnT f$array_sum(const array &a) { + php_critical_error("call to unsupported function"); +} + +template +mixed f$getKeyByPos(const array &a, int64_t pos) { + php_critical_error("call to unsupported function"); +} + +template +T f$getValueByPos(const array &a, int64_t pos) { + php_critical_error("call to unsupported function"); +} + +template +array f$create_vector(int64_t n, const T &default_value) { + php_critical_error("call to unsupported function"); +} + +template +mixed f$array_first_key(const array &a) { + php_critical_error("call to unsupported function"); +} + +template +T f$array_first_value(const array &a) { + php_critical_error("call to unsupported function"); +} + +template +mixed f$array_last_key(const array &a) { + php_critical_error("call to unsupported function"); +} + +template +T f$array_last_value(const array &a) { + php_critical_error("call to unsupported function"); +} + +template +void f$array_swap_int_keys(array &a, int64_t idx1, int64_t idx2) noexcept { + php_critical_error("call to unsupported function"); +} diff --git a/runtime-light/stdlib/instance-cache/instance-cache.h b/runtime-light/stdlib/instance-cache/instance-cache.h new file mode 100644 index 0000000000..f7599ee04a --- /dev/null +++ b/runtime-light/stdlib/instance-cache/instance-cache.h @@ -0,0 +1,17 @@ +// 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/runtime-core.h" + +template +bool f$instance_cache_store(const string &key, const ClassInstanceType &instance, int64_t ttl = 0) { + php_critical_error("call to unsupported function"); +} + +template +ClassInstanceType f$instance_cache_fetch(const string &class_name, const string &key, bool even_if_expired = false) { + php_critical_error("call to unsupported function"); +} diff --git a/runtime-light/stdlib/math/math.h b/runtime-light/stdlib/math/math.h new file mode 100644 index 0000000000..7184131376 --- /dev/null +++ b/runtime-light/stdlib/math/math.h @@ -0,0 +1,37 @@ +// 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/runtime-core.h" + +template +T f$min(const array &a) { + php_critical_error("call to unsupported function"); +} + +template +T f$max(const array &a) { + php_critical_error("call to unsupported function"); +} + +template +T f$min(const T &arg1) { + php_critical_error("call to unsupported function"); +} + +template +T f$min(const T &arg1, const T &arg2, Args&&... args) { + php_critical_error("call to unsupported function"); +} + +template +T f$max(const T &arg1) { + php_critical_error("call to unsupported function"); +} + +template +T f$max(const T &arg1, const T &arg2, Args&&... args) { + php_critical_error("call to unsupported function"); +} diff --git a/runtime-light/stdlib/regex/regex-functions.cpp b/runtime-light/stdlib/regex/regex-functions.cpp new file mode 100644 index 0000000000..561d3493c5 --- /dev/null +++ b/runtime-light/stdlib/regex/regex-functions.cpp @@ -0,0 +1,11 @@ +// 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/regex/regex-functions.h" + +#include "runtime-light/component/component.h" + +RegexComponentState &RegexComponentState::get() noexcept { + return get_component_context()->regex_component_context; +} diff --git a/runtime-light/stdlib/regex/regex-functions.h b/runtime-light/stdlib/regex/regex-functions.h new file mode 100644 index 0000000000..bb9cb313aa --- /dev/null +++ b/runtime-light/stdlib/regex/regex-functions.h @@ -0,0 +1,50 @@ +// 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/runtime-core.h" + +struct RegexComponentState { + int64_t preg_replace_count_dummy{}; + + static RegexComponentState& get() noexcept; +}; + + +template> +auto f$preg_replace(const T1 &, const T2 &, const T3 &, int64_t = -1, int64_t & = RegexComponentState::get().preg_replace_count_dummy) { + php_critical_error("call to unsupported function"); +} + +template> +auto f$preg_replace_callback(const T1 &, const T2 &, const T3 &, int64_t = -1, int64_t & = RegexComponentState::get().preg_replace_count_dummy) { + php_critical_error("call to unsupported function"); +} + +template +Optional f$preg_replace_callback(const Regexp &, const T &, const string &, int64_t = -1, int64_t & = RegexComponentState::get().preg_replace_count_dummy) { + php_critical_error("call to unsupported function"); +} + +template +mixed f$preg_replace_callback(const Regexp &, const T &, const mixed &, int64_t = -1, int64_t & = RegexComponentState::get().preg_replace_count_dummy) { + php_critical_error("call to unsupported function"); +} + +template +auto f$preg_replace_callback(const string &, const T &, const T2 &, int64_t = -1, int64_t & = RegexComponentState::get().preg_replace_count_dummy) { + php_critical_error("call to unsupported function"); +} + +template +Optional f$preg_replace_callback(const mixed &, const T &, const string &, int64_t = -1, int64_t & = RegexComponentState::get().preg_replace_count_dummy) { + php_critical_error("call to unsupported function"); +} + +template +mixed f$preg_replace_callback(const mixed &, const T &, const mixed &, int64_t = -1, int64_t & = RegexComponentState::get().preg_replace_count_dummy) { + php_critical_error("call to unsupported function"); +} diff --git a/runtime-light/stdlib/rpc/rpc-api.h b/runtime-light/stdlib/rpc/rpc-api.h index 50522a3e92..22e2f3b3aa 100644 --- a/runtime-light/stdlib/rpc/rpc-api.h +++ b/runtime-light/stdlib/rpc/rpc-api.h @@ -99,6 +99,11 @@ f$typed_rpc_tl_query_result(array query_ids) noexcept { co_return res; } +template +array> f$rpc_tl_query_result_synchronously(const array &) { + php_critical_error("call to unsupported function"); +} + // === Rpc Misc =================================================================================== void f$rpc_clean() noexcept; @@ -110,3 +115,8 @@ 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; + +template +bool f$rpc_parse(T) { + php_critical_error("call to unsupported function"); +} diff --git a/runtime-light/stdlib/stdlib.cmake b/runtime-light/stdlib/stdlib.cmake index 065909959c..d43fc48256 100644 --- a/runtime-light/stdlib/stdlib.cmake +++ b/runtime-light/stdlib/stdlib.cmake @@ -1,16 +1,17 @@ prepend( - RUNTIME_STDLIB_SRC - stdlib/ - component/component-api.cpp - exit/exit-functions.cpp - fork/fork-context.cpp - output/output-buffer.cpp - output/print-functions.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 - string/concat.cpp) + RUNTIME_STDLIB_SRC + stdlib/ + component/component-api.cpp + exit/exit-functions.cpp + fork/fork-context.cpp + output/output-buffer.cpp + output/print-functions.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 + string/concat.cpp + regex/regex-functions.cpp) diff --git a/runtime-light/stdlib/string/concat.h b/runtime-light/stdlib/string/concat.h index 6d0a2fbd41..7f00179b56 100644 --- a/runtime-light/stdlib/string/concat.h +++ b/runtime-light/stdlib/string/concat.h @@ -6,6 +6,16 @@ #include "runtime-core/runtime-core.h" +template +string f$strtr(const string &, const array &) { + php_critical_error("call to unsupported function"); +} + +template +Optional f$strpos(const string &, const Optional &, int64_t = 0) { + php_critical_error("call to unsupported function"); +} + // str_concat_arg generalizes both tmp_string and string arguments; // it can be constructed from both of them, so concat functions can operate // on both tmp_string and string types From 3e2ef14c74f10dcaac4476ce05186438fb7f487b Mon Sep 17 00:00:00 2001 From: Mikhail Kornaukhov <66915223+mkornaukhov03@users.noreply.github.com> Date: Thu, 29 Aug 2024 14:51:00 +0300 Subject: [PATCH 33/45] Fix runtime core flags (#1083) Use -fPIC only with light runtime --- runtime-core/runtime-core.cmake | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/runtime-core/runtime-core.cmake b/runtime-core/runtime-core.cmake index ed48cf6bf5..13bee235f3 100644 --- a/runtime-core/runtime-core.cmake +++ b/runtime-core/runtime-core.cmake @@ -29,4 +29,7 @@ endif() prepend(KPHP_CORE_SRC ${RUNTIME_CORE_DIR}/ "${KPHP_CORE_SRC}") vk_add_library(runtime-core OBJECT ${KPHP_CORE_SRC}) -target_compile_options(runtime-core PUBLIC -fPIC) + +if (COMPILE_RUNTIME_LIGHT) + target_compile_options(runtime-core PUBLIC -fPIC) +endif() From 811b430d0d6e20c57e3fb12fd457323d9d2249f1 Mon Sep 17 00:00:00 2001 From: Vadim Sadokhov <65451602+astrophysik@users.noreply.github.com> Date: Fri, 30 Aug 2024 11:33:49 +0300 Subject: [PATCH 34/45] sync k2 header (#1084) --- .../kphp-light/unsupported/crypto.txt | 4 -- runtime-light/header.h | 70 +++++++++++-------- 2 files changed, 41 insertions(+), 33 deletions(-) diff --git a/builtin-functions/kphp-light/unsupported/crypto.txt b/builtin-functions/kphp-light/unsupported/crypto.txt index 54341483c7..6c8c0becc1 100644 --- a/builtin-functions/kphp-light/unsupported/crypto.txt +++ b/builtin-functions/kphp-light/unsupported/crypto.txt @@ -39,8 +39,4 @@ function openssl_sign ($data ::: string, &$signature ::: string, $priv_key_id :: /** @kphp-extern-func-info generate-stub */ function openssl_verify ($data ::: string, $signature ::: string, $pub_key_id ::: string, $signature_alg ::: int = 1) ::: int; /** @kphp-extern-func-info generate-stub */ -function openssl_random_pseudo_bytes ($length ::: int) ::: string | false; -/** @kphp-extern-func-info generate-stub */ -function openssl_x509_parse ($x509cert ::: string, $shortnames ::: bool = true) ::: mixed[] | false; -/** @kphp-extern-func-info generate-stub */ function openssl_x509_verify ($x509cert ::: string, $public_key ::: string) ::: int; diff --git a/runtime-light/header.h b/runtime-light/header.h index 5bae9daf93..1d498ad26f 100644 --- a/runtime-light/header.h +++ b/runtime-light/header.h @@ -17,7 +17,7 @@ #include #endif -#define K2_PLATFORM_HEADER_H_VERSION 6 +#define K2_PLATFORM_HEADER_H_VERSION 7 // Always check that enum value is a valid value! @@ -64,16 +64,20 @@ enum TimerStatus { enum OpenStreamResult { OpenStreamOk = 0, - // TODO: really need error? MB it's better to open and immediately close - // channel with corresponding error + /* + * TODO: really need error? MB it's better to open and immediately close + * channel with corresponding error + */ OpenStreamErrorInvalidName = 1, OpenStreamErrorUnknownComponent = 3, OpenStreamErrorComponentUnavailable = 4, OpenStreamErrorLimitExceeded = 5, }; -// This time point is valid only within the component. -// Similar to c++ `std::chrono::steady_clock::time_point` +/* + * This time point is valid only within the component. + * Similar to c++ `std::chrono::steady_clock::time_point` + */ struct TimePoint { uint64_t time_point_ns; }; @@ -95,7 +99,13 @@ struct PlatformCtx { /* * Immediately abort component execution. * Function is `[[noreturn]]` + * Note: `exit_code` used just as indicator for now. + * `exit_code` == 0 => FinishedOk, + * `exit_code` != 0 => FinishedError, */ + void (*exit)(int32_t exit_code); + + // Deprecated; Synonym for `exit(255)`; void (*abort)(); struct Allocator allocator; @@ -107,8 +117,7 @@ struct PlatformCtx { * `stream_d` will be assigned `0`. * however `stream_d=0` itself is not an error marker */ - enum OpenStreamResult (*open)(size_t name_len, const char *name, - uint64_t *stream_d); + enum OpenStreamResult (*open)(size_t name_len, const char *name, uint64_t *stream_d); /* * If the write or read status is `Blocked` - then the platform ensures that * the component receives this `stream_d` via `take_update` when the status is @@ -118,8 +127,7 @@ struct PlatformCtx { * `new_status` will be assigned as * `{.read_status = 0, .write_status = 0, .please_shutdown = 0}`. */ - enum GetStatusResult (*get_stream_status)(uint64_t stream_d, - struct StreamStatus *new_status); + enum GetStatusResult (*get_stream_status)(uint64_t stream_d, struct StreamStatus *new_status); /* * Return processed bytes (written or read). * Guaranteed to return `0` if the stream is `Closed`, `Blocked` or @@ -133,6 +141,7 @@ struct PlatformCtx { */ size_t (*write)(uint64_t stream_d, size_t data_len, const void *data); size_t (*read)(uint64_t stream_d, size_t data_len, void *data); + /* * Sets `StreamStatus.please_whutdown_write=true` for the component on the * opposite side (does not affect `StreamStatus` on your side). @@ -141,6 +150,7 @@ struct PlatformCtx { * as long as `read_status != IOClosed`. */ void (*please_shutdown_write)(uint64_t stream_d); + /* * Disables the ability to write to a stream. * Data written to the stream buffer is still available for reading on the @@ -150,6 +160,7 @@ struct PlatformCtx { * TODO: design information errors. */ void (*shutdown_write)(uint64_t stream_d); + /* * "Free" associated descriptor. * All future uses of this `descriptor` will be invalid. @@ -175,6 +186,7 @@ struct PlatformCtx { // Coordinated with timers. Monotonical, for timeouts, measurements, etc.. void (*get_time)(struct TimePoint *time_point); + /* * In case of `result == Ok` timer_d will be NonZero * In case of `result != Ok` timer_d will be `0` @@ -183,6 +195,7 @@ struct PlatformCtx { * Use `free_descriptor` to cancel */ enum SetTimerResult (*set_timer)(uint64_t *timer_d, uint64_t duration_ns); + /* * It is guaranteed that if `TimerStatusElapsed` is returned * then `deadline <= get_time()` @@ -190,8 +203,7 @@ struct PlatformCtx { * * `deadline` will be assigned `0` if `timer_d` invalid */ - enum TimerStatus (*get_timer_status)(uint64_t timer_d, - struct TimePoint *deadline); + enum TimerStatus (*get_timer_status)(uint64_t timer_d, struct TimePoint *deadline); /* * Return: `bool`. * If `True`: the update was successfully received. @@ -211,6 +223,7 @@ struct PlatformCtx { * platform is guaranteed to reschedule it. */ uint8_t (*take_update)(uint64_t *update_d); + /* * Only utf-8 string supported. * Possible `level` values: @@ -220,19 +233,23 @@ struct PlatformCtx { * 4 => Debug * 5 => Trace * Any other value will cause the log to be skipped - */ - void (*log)(size_t level, size_t len, const char *str); - /* * if `level` > `log_level_enabled()` log will be skipped */ + void (*log)(size_t level, size_t len, const char *str); + + // Use for optimization, see `log` size_t (*log_level_enabled)(); + + // Note: prefer to use only as seed generator for pseudo-random + void (*os_rnd)(size_t len, void *bytes); }; struct ComponentState; + /* - * Image state created once on library load - * shared between all component [instances]. - * designed to prevent heavy `_init` section of dlib + * Image state created once on library load. + * Shared between all component [instances]. + * Designed to prevent heavy `_init` section of dlib */ struct ImageState; @@ -262,18 +279,13 @@ struct ImageInfo { }; // Every image should provide these symbols -enum PollStatus vk_k2_poll(const struct ImageState *image_state, - const struct PlatformCtx *pt_ctx, - struct ComponentState *component_ctx); - -// platform_ctx without IO stuff (nullptr instead io-functions) -// for now, returning nullptr will indicate error -struct ComponentState * -vk_k2_create_component_state(const struct ImageState *image_state, - const struct PlatformCtx *pt_ctx); - -// platform_ctx without IO stuff (nullptr instead io-functions) -// for now, returning nullptr will indicate error +enum PollStatus vk_k2_poll(const struct ImageState *image_state, const struct PlatformCtx *pt_ctx, struct ComponentState *component_ctx); + +/* + * platform_ctx without IO stuff (nullptr instead io-functions) + * for now, returning nullptr will indicate error + */ +struct ComponentState *vk_k2_create_component_state(const struct ImageState *image_state, const struct PlatformCtx *pt_ctx); struct ImageState *vk_k2_create_image_state(const struct PlatformCtx *pt_ctx); const struct ImageInfo *vk_k2_describe(); From 7f605cc399201a2a12fdc5f014c8600ad6d1a7d3 Mon Sep 17 00:00:00 2001 From: Vladislav Senin Date: Fri, 30 Aug 2024 19:31:52 +0300 Subject: [PATCH 35/45] fixed clang --asm-operand-widths warnings for aarch (2) --- common/crypto/aes256-aarch64.cpp | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/common/crypto/aes256-aarch64.cpp b/common/crypto/aes256-aarch64.cpp index 4614aded21..1350b6df74 100644 --- a/common/crypto/aes256-aarch64.cpp +++ b/common/crypto/aes256-aarch64.cpp @@ -436,10 +436,10 @@ static inline void crypto_aarch64_aes256_encrypt_single_block(vk_aes_ctx_t *vk_c static inline void crypto_aarch64_aes256_encrypt_n_blocks(vk_aes_ctx_t *vk_ctx, const uint8_t *in, uint8_t *out, uint8_t iv[16], int n) { asm volatile("mov x9, %[out] ;" // move out address in x9 - "mov x10, %x[in] ;" // move plaintext address in x10 + "mov x10, %[in] ;" // move plaintext address in x10 "mov x11, %[key] ;" // move key address in x11 "mov x12, %[iv] ;" // move IV address in x12 - "mov x13, %[n] ;" // move n value in x11 + "mov x13, %x[n] ;" // move n value in x11 "ld1 {v26.16b}, [x12] ;" // load IV to v0.16b "eor v25.16b, v25.16b, v25.16b ;" "mov w15, #1 ;" @@ -504,10 +504,19 @@ static inline void crypto_aarch64_aes256_encrypt_n_blocks(vk_aes_ctx_t *vk_ctx, ; } -void crypto_aarch64_aes256_ctr_encrypt(vk_aes_ctx_t *vk_ctx, const uint8_t *in, uint8_t *out, int size, uint8_t iv[16], uint64_t offset) { #if defined(__GNUC__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +# if defined(__clang__) && defined(__has_warning) +# if __has_warning("-Wmaybe-uninitialized") +# define MAYBE_UNINITIALIZED +# endif +# elif !defined(__clang__) +# define MAYBE_UNINITIALIZED +# endif +#endif +void crypto_aarch64_aes256_ctr_encrypt(vk_aes_ctx_t *vk_ctx, const uint8_t *in, uint8_t *out, int size, uint8_t iv[16], uint64_t offset) { +#if defined(MAYBE_UNINITIALIZED) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wmaybe-uninitialized" #endif unsigned char iv_copy[16], u[16]; memcpy(iv_copy, iv, 16); @@ -540,7 +549,7 @@ void crypto_aarch64_aes256_ctr_encrypt(vk_aes_ctx_t *vk_ctx, const uint8_t *in, *out++ = (*in++) ^ u[i++]; } while (i < l); } -#if defined(__GNUC__) -#pragma GCC diagnostic pop +#if defined(MAYBE_UNINITIALIZED) +# pragma GCC diagnostic pop #endif } From 3cb212061a260a247f85dd5f536702a2cc193f34 Mon Sep 17 00:00:00 2001 From: Vladislav Senin Date: Sat, 31 Aug 2024 01:10:23 +0300 Subject: [PATCH 36/45] disabled diagnostics of variable-length arrays for clang version 18 and higher --- cmake/init-compilation-flags.cmake | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cmake/init-compilation-flags.cmake b/cmake/init-compilation-flags.cmake index d76ead6413..b78f922259 100644 --- a/cmake/init-compilation-flags.cmake +++ b/cmake/init-compilation-flags.cmake @@ -105,6 +105,11 @@ endif() add_compile_options(-Werror -Wall -Wextra -Wunused-function -Wfloat-conversion -Wno-sign-compare -Wuninitialized -Wno-redundant-move -Wno-missing-field-initializers) + +if(CMAKE_CXX_COMPILER_ID MATCHES Clang AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL "18") + add_compile_options(-Wno-vla-cxx-extension) +endif() + if(COMPILE_RUNTIME_LIGHT) add_compile_options(-Wno-vla-extension) endif() From e511730511280c7ebe6dee7b3fb23881aa7c4953 Mon Sep 17 00:00:00 2001 From: Mikhail Kornaukhov <66915223+mkornaukhov03@users.noreply.github.com> Date: Tue, 3 Sep 2024 21:06:42 +0300 Subject: [PATCH 37/45] Get rid of zero length array extension (#1076) --- runtime-core/core-types/decl/array_decl.inl | 16 +- runtime-core/core-types/decl/array_iterator.h | 10 +- runtime-core/core-types/definition/array.inl | 156 ++++++++++-------- 3 files changed, 96 insertions(+), 86 deletions(-) diff --git a/runtime-core/core-types/decl/array_decl.inl b/runtime-core/core-types/decl/array_decl.inl index 007b536113..e85963cf64 100644 --- a/runtime-core/core-types/decl/array_decl.inl +++ b/runtime-core/core-types/decl/array_decl.inl @@ -26,13 +26,6 @@ struct array_size { inline array_size &min(const array_size &other) noexcept; }; -#if defined(__clang__) || (defined(__GNUC__) && __GNUC__ < 10) -// clang complains about 'flexible array member x of type T[] with non-trivial destruction' -#define KPHP_ARRAY_TAIL_SIZE 0 -#else -// gcc10 complains about out-of-bounds access to an array of zero size -#define KPHP_ARRAY_TAIL_SIZE -#endif namespace dl { template @@ -95,15 +88,17 @@ private: uint32_t string_size{0}; }; - struct array_inner : array_inner_control { + struct alignas(array_bucket) array_inner : array_inner_control { static constexpr uint32_t MAX_HASHTABLE_SIZE = (1 << 26); //empty hash_entry identified by (next == EMPTY_POINTER) static constexpr entry_pointer_type EMPTY_POINTER = 0; - - array_bucket entries[KPHP_ARRAY_TAIL_SIZE]; + static constexpr size_t ENTRIES_OFFSET = sizeof(array_inner); inline bool is_vector() const noexcept __attribute__ ((always_inline)); + inline array_bucket* entries() noexcept __attribute__ ((always_inline)); + inline const array_bucket* entries() const noexcept __attribute__ ((always_inline)); + inline list_hash_entry *get_entry(entry_pointer_type pointer) const __attribute__ ((always_inline)); inline entry_pointer_type get_pointer(list_hash_entry *entry) const __attribute__ ((always_inline)); @@ -111,7 +106,6 @@ private: inline const array_bucket *next(const array_bucket *ptr) const __attribute__ ((always_inline)) ubsan_supp("alignment"); inline const array_bucket *prev(const array_bucket *ptr) const __attribute__ ((always_inline)) ubsan_supp("alignment"); inline const array_bucket *end() const __attribute__ ((always_inline)) ubsan_supp("alignment"); - inline array_bucket *begin() __attribute__ ((always_inline)) ubsan_supp("alignment"); inline array_bucket *next(array_bucket *ptr) __attribute__ ((always_inline)) ubsan_supp("alignment"); inline array_bucket *prev(array_bucket *ptr) __attribute__ ((always_inline)) ubsan_supp("alignment"); diff --git a/runtime-core/core-types/decl/array_iterator.h b/runtime-core/core-types/decl/array_iterator.h index b03b59af9c..f4c6050a9f 100644 --- a/runtime-core/core-types/decl/array_iterator.h +++ b/runtime-core/core-types/decl/array_iterator.h @@ -49,7 +49,7 @@ class array_iterator { inline key_type get_key() const noexcept __attribute__ ((always_inline)) { if (self_->is_vector()) { - return key_type{static_cast(reinterpret_cast(entry_) - reinterpret_cast(self_->entries))}; + return key_type{static_cast(reinterpret_cast(entry_) - reinterpret_cast(self_->entries()))}; } if (is_string_key()) { @@ -112,7 +112,7 @@ class array_iterator { static inline array_iterator make_begin(std::add_const_t &arr) noexcept __attribute__ ((always_inline)) { static_assert(std::is_const{}, "expected to be const"); return arr.is_vector() - ? array_iterator{arr.p, arr.p->entries} + ? array_iterator{arr.p, arr.p->entries()} : array_iterator{arr.p, arr.p->begin()}; } @@ -120,7 +120,7 @@ class array_iterator { static_assert(!std::is_const{}, "expected to be mutable"); if (arr.is_vector()) { arr.mutate_if_vector_shared(); - return array_iterator{arr.p, arr.p->entries}; + return array_iterator{arr.p, arr.p->entries()}; } arr.mutate_if_map_shared(); @@ -129,7 +129,7 @@ class array_iterator { static inline array_iterator make_end(array_type &arr) noexcept __attribute__ ((always_inline)) { return arr.is_vector() - ? array_iterator{arr.p, reinterpret_cast(reinterpret_cast(arr.p->entries) + arr.p->size)} + ? array_iterator{arr.p, reinterpret_cast(reinterpret_cast(arr.p->entries()) + arr.p->size)} : array_iterator{arr.p, arr.p->end()}; } @@ -147,7 +147,7 @@ class array_iterator { return make_end(arr); } - return array_iterator{arr.p, reinterpret_cast(reinterpret_cast(arr.p->entries) + n)}; + return array_iterator{arr.p, reinterpret_cast(reinterpret_cast(arr.p->entries()) + n)}; } if (n < -l / 2) { diff --git a/runtime-core/core-types/definition/array.inl b/runtime-core/core-types/definition/array.inl index 9c8e08f744..55d871568a 100644 --- a/runtime-core/core-types/definition/array.inl +++ b/runtime-core/core-types/definition/array.inl @@ -105,12 +105,16 @@ bool array::is_int_key(const typename array::key_type &key) { template<> inline typename array::array_inner *array::array_inner::empty_array() { - static array_inner_control empty_array{ - true, ExtraRefCnt::for_global_const, -1, + // need this hack because gcc10 and newer complains about + // "array subscript is outside array bounds of array::array_inner" + static array_inner_control empty_array[1]{{ + true, ExtraRefCnt::for_global_const, + -1, {0, 0}, - 0, 2, - }; - return static_cast::array_inner *>(&empty_array); + 0, + 2, + }}; + return static_cast::array_inner *>(&empty_array[0]); } template @@ -133,6 +137,15 @@ bool array::array_inner::is_vector() const noexcept { return is_vector_internal; } +template +typename array::array_bucket *array::array_inner::entries() noexcept { + return reinterpret_cast(reinterpret_cast(this) + ENTRIES_OFFSET); +} + +template +const typename array::array_bucket *array::array_inner::entries() const noexcept { + return reinterpret_cast(reinterpret_cast(this) + ENTRIES_OFFSET); +} template typename array::list_hash_entry *array::array_inner::get_entry(entry_pointer_type pointer) const { @@ -204,12 +217,15 @@ const typename array::array_inner_fields_for_map &array::array_inner::fiel template size_t array::array_inner::sizeof_vector(uint32_t int_size) noexcept { - return sizeof(array_inner) + int_size * sizeof(T); + return sizeof(array_inner) + + int_size * sizeof(T); } template size_t array::array_inner::sizeof_map(uint32_t int_size) noexcept { - return sizeof(array_inner_fields_for_map) + sizeof(array_inner) + int_size * sizeof(array_bucket); + return sizeof(array_inner_fields_for_map) + + sizeof(array_inner) + + int_size * sizeof(array_bucket); } template @@ -272,7 +288,7 @@ void array::array_inner::dispose() { if (ref_cnt <= -1) { if (is_vector()) { for (uint32_t i = 0; i < size; i++) { - ((T *)entries)[i].~T(); + ((T *)entries())[i].~T(); } RuntimeAllocator::current().free_script_memory((void *)this, sizeof_vector(buf_size)); @@ -287,7 +303,7 @@ void array::array_inner::dispose() { } php_assert(this != empty_array()); - auto shifted_this = reinterpret_cast(this) - sizeof(array_inner_fields_for_map); + auto shifted_this = std::launder(reinterpret_cast(this)) - sizeof(array_inner_fields_for_map); RuntimeAllocator::current().free_script_memory(shifted_this, sizeof_map(buf_size)); } } @@ -307,10 +323,10 @@ template inline T &array::array_inner::emplace_back_vector_value(Args &&... args) noexcept { static_assert(std::is_constructible{}, "should be constructible"); php_assert (size < buf_size); - new(&((T *)entries)[size]) T(std::forward(args)...); + new(&((T *)entries())[size]) T(std::forward(args)...); max_key++; size++; - return reinterpret_cast(entries)[max_key]; + return reinterpret_cast(entries())[max_key]; } template @@ -320,19 +336,19 @@ T &array::array_inner::push_back_vector_value(const T &v) { template T &array::array_inner::get_vector_value(int64_t int_key) { - return reinterpret_cast(entries)[int_key]; + return reinterpret_cast(entries())[int_key]; } template const T &array::array_inner::get_vector_value(int64_t int_key) const { - return reinterpret_cast(entries)[int_key]; + return reinterpret_cast(entries())[int_key]; } template template T &array::array_inner::emplace_vector_value(int64_t int_key, Args &&... args) noexcept { static_assert(std::is_constructible{}, "should be constructible"); - reinterpret_cast(entries)[int_key] = T(std::forward(args)...); + reinterpret_cast(entries())[int_key] = T(std::forward(args)...); return get_vector_value(int_key); } @@ -346,24 +362,24 @@ template T &array::array_inner::emplace_int_key_map_value(overwrite_element policy, int64_t int_key, Args &&... args) noexcept { static_assert(std::is_constructible{}, "should be constructible"); uint32_t bucket = choose_bucket(int_key); - while (entries[bucket].next != EMPTY_POINTER && - (entries[bucket].int_key != int_key || !entries[bucket].string_key.is_dummy_string())) { + while (entries()[bucket].next != EMPTY_POINTER && + (entries()[bucket].int_key != int_key || !entries()[bucket].string_key.is_dummy_string())) { if (unlikely(++bucket == buf_size)) { bucket = 0; } } - if (entries[bucket].next == EMPTY_POINTER) { - entries[bucket].int_key = int_key; - new(&entries[bucket].string_key) string{ArrayBucketDummyStrTag{}}; + if (entries()[bucket].next == EMPTY_POINTER) { + entries()[bucket].int_key = int_key; + new(&entries()[bucket].string_key) string{ArrayBucketDummyStrTag{}}; - entries[bucket].prev = end()->prev; - get_entry(end()->prev)->next = get_pointer(&entries[bucket]); + entries()[bucket].prev = end()->prev; + get_entry(end()->prev)->next = get_pointer(&entries()[bucket]); - entries[bucket].next = get_pointer(end()); - end()->prev = get_pointer(&entries[bucket]); + entries()[bucket].next = get_pointer(end()); + end()->prev = get_pointer(&entries()[bucket]); - new(&entries[bucket].value) T(std::forward(args)...); + new(&entries()[bucket].value) T(std::forward(args)...); size++; @@ -371,10 +387,10 @@ T &array::array_inner::emplace_int_key_map_value(overwrite_element policy, in max_key = int_key; } } else if (policy == overwrite_element::YES) { - entries[bucket].value = T(std::forward(args)...); + entries()[bucket].value = T(std::forward(args)...); } - return entries[bucket].value; + return entries()[bucket].value; } template @@ -385,30 +401,30 @@ T &array::array_inner::set_map_value(overwrite_element policy, int64_t int_ke template T array::array_inner::unset_vector_value() { --size; - T res = std::move(reinterpret_cast(entries)[max_key--]); + T res = std::move(reinterpret_cast(entries())[max_key--]); return res; } template T array::array_inner::unset_map_value(int64_t int_key) { uint32_t bucket = choose_bucket(int_key); - while (entries[bucket].next != EMPTY_POINTER && - (entries[bucket].int_key != int_key || !entries[bucket].string_key.is_dummy_string())) { + while (entries()[bucket].next != EMPTY_POINTER && + (entries()[bucket].int_key != int_key || !entries()[bucket].string_key.is_dummy_string())) { if (unlikely (++bucket == buf_size)) { bucket = 0; } } - if (entries[bucket].next != EMPTY_POINTER) { - entries[bucket].int_key = 0; + if (entries()[bucket].next != EMPTY_POINTER) { + entries()[bucket].int_key = 0; - get_entry(entries[bucket].prev)->next = entries[bucket].next; - get_entry(entries[bucket].next)->prev = entries[bucket].prev; + get_entry(entries()[bucket].prev)->next = entries()[bucket].next; + get_entry(entries()[bucket].next)->prev = entries()[bucket].prev; - entries[bucket].next = EMPTY_POINTER; - entries[bucket].prev = EMPTY_POINTER; + entries()[bucket].next = EMPTY_POINTER; + entries()[bucket].prev = EMPTY_POINTER; - T res = std::move(entries[bucket].value); + T res = std::move(entries()[bucket].value); size--; @@ -417,15 +433,15 @@ T array::array_inner::unset_map_value(int64_t int_key) { uint32_t j, rj, ri = bucket; for (j = bucket + 1; 1; j++) { rj = FIXD(j); - if (entries[rj].next == EMPTY_POINTER) { + if (entries()[rj].next == EMPTY_POINTER) { break; } - uint32_t bucket_j = choose_bucket(entries[rj].int_key); + uint32_t bucket_j = choose_bucket(entries()[rj].int_key); uint32_t wnt = FIXU(bucket_j, bucket); if (wnt > j || wnt <= bucket) { - list_hash_entry *ei = entries + ri, *ej = entries + rj; + list_hash_entry *ei = entries() + ri, *ej = entries() + rj; memcpy(ei, ej, sizeof(array_bucket)); ej->next = EMPTY_POINTER; @@ -447,14 +463,14 @@ template template auto &array::array_inner::find_map_entry(S &self, int64_t int_key) noexcept { uint32_t bucket = self.choose_bucket(int_key); - while (self.entries[bucket].next != EMPTY_POINTER && - (self.entries[bucket].int_key != int_key || !self.entries[bucket].string_key.is_dummy_string())) { + while (self.entries()[bucket].next != EMPTY_POINTER && + (self.entries()[bucket].int_key != int_key || !self.entries()[bucket].string_key.is_dummy_string())) { if (unlikely (++bucket == self.buf_size)) { bucket = 0; } } - return self.entries[bucket]; + return self.entries()[bucket]; } template @@ -469,7 +485,7 @@ auto &array::array_inner::find_map_entry(S &self, const char *key, string::si static const auto str_not_eq = [](const string &lhs, const char *rhs, string::size_type rhs_size) { return lhs.size() != rhs_size || string::compare(lhs, rhs, rhs_size) != 0; }; - auto *string_entries = self.entries; + auto *string_entries = self.entries(); uint32_t bucket = self.choose_bucket(precomputed_hash); while (string_entries[bucket].next != EMPTY_POINTER && (string_entries[bucket].int_key != precomputed_hash || string_entries[bucket].string_key.is_dummy_string() || str_not_eq(string_entries[bucket].string_key, key, key_size))) { @@ -503,7 +519,7 @@ template std::pair array::array_inner::emplace_string_key_map_value(overwrite_element policy, int64_t int_key, STRING &&string_key, Args &&... args) noexcept { static_assert(std::is_same, string>::value, "string_key should be string"); - array_bucket *string_entries = entries; + array_bucket *string_entries = entries(); auto &fields = fields_for_map(); uint32_t bucket = choose_bucket(fields, int_key); while (string_entries[bucket].next != EMPTY_POINTER && @@ -544,7 +560,7 @@ T &array::array_inner::set_map_value(overwrite_element policy, int64_t int_ke template T array::array_inner::unset_map_value(const string &string_key, int64_t precomputed_hash) { - array_bucket *string_entries = entries; + array_bucket *string_entries = entries(); auto &fields = fields_for_map(); uint32_t bucket = choose_bucket(fields, precomputed_hash); while (string_entries[bucket].next != EMPTY_POINTER && @@ -663,7 +679,7 @@ bool array::mutate_to_size_if_vector_shared(int64_t int_size) { array_inner *new_array = array_inner::create(int_size, true); const auto size = static_cast(p->size); - T *it = (T *)p->entries; + T *it = (T *)p->entries(); for (uint32_t i = 0; i < size; i++) { new_array->push_back_vector_value(it[i]); @@ -766,7 +782,7 @@ void array::reserve(int64_t int_size, bool make_vector_if_possible) { if (is_vector()) { for (uint32_t it = 0; it != p->size; it++) { - new_array->set_map_value(overwrite_element::YES, it, ((T *)p->entries)[it]); + new_array->set_map_value(overwrite_element::YES, it, ((T *)p->entries())[it]); } php_assert (new_array->max_key == p->max_key); } else { @@ -840,7 +856,7 @@ template void array::convert_to_map() { array_inner *new_array = array_inner::create(p->size + 4, false); - T *elements = reinterpret_cast(p->entries); + T *elements = reinterpret_cast(p->entries()); const bool move_values = p->ref_cnt == 0; if (move_values) { for (uint32_t it = 0; it != p->size; it++) { @@ -870,7 +886,7 @@ void array::copy_from(const array &other) { if (new_array->is_vector()) { uint32_t size = other.p->size; - T1 *it = reinterpret_cast(other.p->entries); + T1 *it = reinterpret_cast(other.p->entries()); for (uint32_t i = 0; i < size; i++) { new_array->push_back_vector_value(convert_to::convert(it[i])); } @@ -907,7 +923,7 @@ void array::move_from(array &&other) noexcept { if (new_array->is_vector()) { uint32_t size = other.p->size; - T1 *it = reinterpret_cast(other.p->entries); + T1 *it = reinterpret_cast(other.p->entries()); for (uint32_t i = 0; i < size; i++) { new_array->emplace_back_vector_value(convert_to::convert(std::move(it[i]))); } @@ -1114,7 +1130,7 @@ T &array::operator[](double double_key) { template T &array::operator[](const const_iterator &it) noexcept { if (it.self_->is_vector()) { - const auto key = static_cast(reinterpret_cast(it.entry_) - reinterpret_cast(it.self_->entries)); + const auto key = static_cast(reinterpret_cast(it.entry_) - reinterpret_cast(it.self_->entries())); return operator[](key); } auto *entry = reinterpret_cast(it.entry_); @@ -1281,7 +1297,7 @@ void array::set_value(const Optional &key, const T &value) noexcep template void array::set_value(const const_iterator &it) noexcept { if (it.self_->is_vector()) { - const auto key = static_cast(reinterpret_cast(it.entry_) - reinterpret_cast(it.self_->entries)); + const auto key = static_cast(reinterpret_cast(it.entry_) - reinterpret_cast(it.self_->entries())); emplace_value(key, *reinterpret_cast(it.entry_)); return; } @@ -1358,7 +1374,7 @@ const T *array::find_value(double double_key) const noexcept { template const T *array::find_value(const const_iterator &it) const noexcept { if (it.self_->is_vector()) { - const auto key = static_cast(reinterpret_cast(it.entry_) - reinterpret_cast(it.self_->entries)); + const auto key = static_cast(reinterpret_cast(it.entry_) - reinterpret_cast(it.self_->entries())); return find_value(key); } else { auto *entry = reinterpret_cast(it.entry_); @@ -1594,7 +1610,7 @@ const array array::operator+(const array &other) const { if (is_vector()) { uint32_t size = p->size; - T *it = (T *)p->entries; + T *it = (T *)p->entries(); if (result.is_vector()) { for (uint32_t i = 0; i < size; i++) { @@ -1617,7 +1633,7 @@ const array array::operator+(const array &other) const { if (other.is_vector()) { uint32_t size = other.p->size; - T *it = (T *)other.p->entries; + T *it = (T *)other.p->entries(); if (result.is_vector()) { for (uint32_t i = p->size; i < size; i++) { @@ -1649,11 +1665,11 @@ array &array::operator+=(const array &other) { if (is_vector()) { if (other.is_vector()) { uint32_t size = other.p->size; - T *it = (T *)other.p->entries; + T *it = (T *)other.p->entries(); if (p->ref_cnt > 0) { uint32_t my_size = p->size; - T *my_it = (T *)p->entries; + T *my_it = (T *)p->entries(); array_inner *new_array = array_inner::create(max(size, my_size), true); @@ -1680,7 +1696,7 @@ array &array::operator+=(const array &other) { return *this; } else { array_inner *new_array = array_inner::create(p->size + other.p->size + 4, false); - T *it = (T *)p->entries; + T *it = (T *)p->entries(); for (uint32_t i = 0; i != p->size; i++) { new_array->set_map_value(overwrite_element::YES, i, it[i]); @@ -1714,7 +1730,7 @@ array &array::operator+=(const array &other) { if (other.is_vector()) { uint32_t size = other.p->size; - T *it = (T *)other.p->entries; + T *it = (T *)other.p->entries(); for (uint32_t i = 0; i < size; i++) { p->set_map_value(overwrite_element::NO, i, it[i]); @@ -1826,7 +1842,7 @@ void array::swap_int_keys(int64_t idx1, int64_t idx2) noexcept { // this function is supposed to be used for vector optimization, else branch is just to be on the safe side if (is_vector() && idx1 >= 0 && idx2 >= 0 && idx1 < p->size && idx2 < p->size) { mutate_if_vector_shared(); - std::swap(reinterpret_cast(p->entries)[idx1], reinterpret_cast(p->entries)[idx2]); + std::swap(reinterpret_cast(p->entries())[idx1], reinterpret_cast(p->entries())[idx2]); } else { if (auto *v1 = find_value(idx1)) { if (auto *v2 = find_value(idx2)) { @@ -1842,7 +1858,7 @@ template void array::fill_vector(int64_t num, const T &value) { php_assert(is_vector() && p->size == 0 && num <= p->buf_size); - std::uninitialized_fill((T *)p->entries, (T *)p->entries + num, value); + std::uninitialized_fill((T *)p->entries(), (T *)p->entries() + num, value); p->max_key = num - 1; p->size = static_cast(num); } @@ -1853,7 +1869,7 @@ void array::memcpy_vector(int64_t num __attribute__((unused)), const void *sr php_assert(is_vector() && p->size == 0 && num <= p->buf_size); mutate_if_vector_shared(); - memcpy(reinterpret_cast(p->entries), src_buf, num * sizeof(T)); + memcpy(reinterpret_cast(p->entries()), src_buf, num * sizeof(T)); p->max_key = num - 1; p->size = static_cast(num); } else { @@ -1894,7 +1910,7 @@ void array::sort(const T1 &compare, bool renumber) { [&compare](const T &lhs, const T &rhs) { return compare(lhs, rhs) > 0; }; - T *begin = reinterpret_cast(p->entries); + T *begin = reinterpret_cast(p->entries()); dl::sort(begin, begin + n, elements_cmp); return; } @@ -1954,7 +1970,7 @@ void array::ksort(const T1 &compare) { keys.p->push_back_vector_value(it->get_key()); } - key_type *keysp = (key_type *)keys.p->entries; + key_type *keysp = (key_type *)keys.p->entries(); dl::sort(keysp, keysp + n, compare); list_hash_entry *prev = (list_hash_entry *)p->end(); @@ -1963,16 +1979,16 @@ void array::ksort(const T1 &compare) { if (is_int_key(keysp[j])) { int64_t int_key = keysp[j].to_int(); uint32_t bucket = p->choose_bucket(int_key); - while (p->entries[bucket].int_key != int_key || !p->entries[bucket].string_key.is_dummy_string()) { + while (p->entries()[bucket].int_key != int_key || !p->entries()[bucket].string_key.is_dummy_string()) { if (unlikely (++bucket == p->buf_size)) { bucket = 0; } } - cur = (list_hash_entry * ) & p->entries[bucket]; + cur = (list_hash_entry * ) &p->entries()[bucket]; } else { string string_key = keysp[j].to_string(); int64_t int_key = string_key.hash(); - array_bucket *string_entries = p->entries; + array_bucket *string_entries = p->entries(); uint32_t bucket = p->choose_bucket(int_key); while ((string_entries[bucket].int_key != int_key || string_entries[bucket].string_key.is_dummy_string() || string_entries[bucket].string_key != string_key)) { if (unlikely (++bucket == p->buf_size)) { @@ -2033,7 +2049,7 @@ T array::shift() { if (is_vector()) { mutate_if_vector_shared(); - T *it = (T *)p->entries; + T *it = (T *)p->entries(); T res = *it; it->~T(); @@ -2076,7 +2092,7 @@ int64_t array::unshift(const T &val) { if (is_vector()) { mutate_if_vector_needs_space(); - T *it = (T *)p->entries; + T *it = (T *)p->entries(); memmove((void *)(it + 1), it, p->size++ * sizeof(T)); p->max_key++; new(it) T(val); @@ -2162,7 +2178,7 @@ void array::force_destroy(ExtraRefCnt expected_ref_cnt) noexcept { template typename array::iterator array::begin_no_mutate() { if (is_vector()) { - return typename array::iterator(p, p->entries); + return typename array::iterator(p, p->entries()); } return typename array::iterator(p, p->begin()); } From 24fcf353297128e259d495fe6162379c9db05819 Mon Sep 17 00:00:00 2001 From: Denis Vaksman Date: Wed, 4 Sep 2024 18:36:36 +0300 Subject: [PATCH 38/45] added vkext build for php8.3 (#1094) --- vkext/vkext.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vkext/vkext.cmake b/vkext/vkext.cmake index 541813c014..1dc28e9098 100644 --- a/vkext/vkext.cmake +++ b/vkext/vkext.cmake @@ -56,7 +56,7 @@ prepend(VKEXT_SOURCES ${VKEXT_DIR}/ vkext-stats.cpp vkext-sp.cpp) -foreach(PHP_VERSION IN ITEMS "" "7.4" "8.0" "8.1" "8.2") +foreach(PHP_VERSION IN ITEMS "" "7.4" "8.0" "8.1" "8.2" "8.3") find_program(PHP_CONFIG_EXEC${PHP_VERSION} php-config${PHP_VERSION}) set(PHP_CONFIG_EXEC ${PHP_CONFIG_EXEC${PHP_VERSION}}) if(PHP_CONFIG_EXEC) From 6091ab7fde4eb9cf1adb4d2fb6911f1d592a7c94 Mon Sep 17 00:00:00 2001 From: Alexander Polyakov Date: Thu, 5 Sep 2024 17:24:13 +0300 Subject: [PATCH 39/45] Update K2 component modes (#1087) Previously we had two component modes: oneshot, multshot. This commit introduces following modes: * k2-cli: most simple mode. Semantically equivalent to regular PHP script * k2-server: semantically equivalent to current KPHP-server * k2-oneshot: K2 oneshot component * k2-multishot: K2 multishot component --- compiler/code-gen/files/init-scripts.cpp | 53 +++++----- .../code-gen/files/tl2cpp/tl-combinator.cpp | 2 +- compiler/compiler-core.cpp | 17 +++- compiler/compiler-core.h | 31 ++++-- compiler/compiler-settings.cpp | 16 ++-- compiler/compiler-settings.h | 3 +- compiler/kphp2cpp.cpp | 6 +- compiler/make/make.cpp | 4 +- compiler/pipes/calc-bad-vars.cpp | 2 +- compiler/pipes/code-gen.cpp | 6 +- runtime-light/component/component.cpp | 96 +++++++++++++++++-- runtime-light/component/component.h | 20 +++- .../core/globals/php-script-globals.h | 2 +- runtime-light/stdlib/exit/exit-functions.cpp | 38 +------- runtime-light/streams/streams.cpp | 20 ---- tests/kphp_tester.py | 3 +- 16 files changed, 189 insertions(+), 130 deletions(-) diff --git a/compiler/code-gen/files/init-scripts.cpp b/compiler/code-gen/files/init-scripts.cpp index 3ec4d17c72..67c3b7d103 100644 --- a/compiler/code-gen/files/init-scripts.cpp +++ b/compiler/code-gen/files/init-scripts.cpp @@ -40,12 +40,12 @@ void StaticInit::compile(CodeGenerator &W) const { W << "extern array gen$tl_storers_ht;" << NL; FunctionSignatureGenerator(W) << "void fill_tl_storers_ht()" << SemicolonAndNL() << NL; } - if (!G->is_output_mode_k2_component()) { + if (!G->is_output_mode_k2()) { FunctionSignatureGenerator(W) << ("const char *get_php_scripts_version()") << BEGIN << "return " << RawString(G->settings().php_code_version.get()) << ";" << NL << END << NL << NL; } - if (!G->is_output_mode_k2_component()) { + if (!G->is_output_mode_k2()) { FunctionSignatureGenerator(W) << ("char **get_runtime_options([[maybe_unused]] int *count)") << BEGIN; const auto &runtime_opts = G->get_kphp_runtime_opts(); @@ -113,19 +113,16 @@ struct RunInterruptedFunction { void compile(CodeGenerator &W) const { std::string await_prefix = function->is_interruptible ? "co_await " : ""; - /** - * Oneshot components work the same way as php scripts: - * 1) Start when the request came in - * 2) Collecting output buffer after script finished - **/ - std::string script_start = G->settings().k2_component_is_oneshot.get() ? "co_await accept_initial_stream();" : ""; - std::string script_finish = G->settings().k2_component_is_oneshot.get() ? "co_await shutdown_script();" : ""; - FunctionSignatureGenerator(W) << "task_t " << FunctionName(function) << "$run() " << BEGIN - << script_start << NL - << await_prefix << FunctionName(function) << "();" << NL - << script_finish << NL - << "co_return;" - << END; + std::string component_kind = G->is_output_mode_k2_cli() ? "ComponentKind::CLI" + : G->is_output_mode_k2_server() ? "ComponentKind::Server" + : G->is_output_mode_k2_oneshot() ? "ComponentKind::Oneshot" + : G->is_output_mode_k2_multishot() ? "ComponentKind::Multishot" + : "ComponentKind::Invalid"; + + std::string script_start = "co_await get_component_context()->run_component_prologue<" + component_kind + ">();"; + std::string script_finish = "co_await get_component_context()->run_component_epilogue();"; + FunctionSignatureGenerator(W) << "task_t " << FunctionName(function) << "$run() " << BEGIN << script_start << NL << await_prefix + << FunctionName(function) << "();" << NL << script_finish << NL << "co_return;" << END; W << NL; } }; @@ -207,7 +204,7 @@ void InitScriptsCpp::compile(CodeGenerator &W) const { W << OpenFile("init_php_scripts.cpp", "", false); W << ExternInclude(G->settings().runtime_headers.get()); - if (!G->is_output_mode_k2_component()) { + if (!G->is_output_mode_k2()) { W << ExternInclude("server/php-init-scripts.h"); } @@ -227,14 +224,14 @@ void InitScriptsCpp::compile(CodeGenerator &W) const { return; } - if (G->is_output_mode_k2_component()) { + if (G->is_output_mode_k2()) { W << RunInterruptedFunction(main_file_id->main_function) << NL; } else { W << RunFunction(main_file_id->main_function) << NL; } W << GlobalsResetFunction(main_file_id->main_function) << NL; - if (G->is_output_mode_k2_component()) { + if (G->is_output_mode_k2()) { FunctionSignatureGenerator(W) << "void init_php_scripts_in_each_worker(" << PhpMutableGlobalsRefArgument() << ", task_t &run" ")" << BEGIN; } else { FunctionSignatureGenerator(W) << "void init_php_scripts_in_each_worker(" << PhpMutableGlobalsRefArgument() << ")" << BEGIN; @@ -249,7 +246,7 @@ 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()) { + if (G->is_output_mode_k2()) { W << "run = " << FunctionName(main_file_id->main_function) << "$run();" << NL; } else { W << "set_script (" @@ -283,20 +280,16 @@ void CppMainFile::compile(CodeGenerator &W) const { } void ComponentInfoFile::compile(CodeGenerator &W) const { - kphp_assert(G->is_output_mode_k2_component()); + kphp_assert(G->is_output_mode_k2()); G->settings().get_version(); auto now = std::chrono::system_clock::now(); W << OpenFile("image_info.cpp"); W << ExternInclude(G->settings().runtime_headers.get()); - W << "const ImageInfo *vk_k2_describe() " << BEGIN - << "static ImageInfo imageInfo {\"" << G->settings().k2_component_name.get() << "\"" << "," - << std::chrono::duration_cast(now.time_since_epoch()).count() << "," - << "K2_PLATFORM_HEADER_H_VERSION, " - << "{}," //todo:k2 add commit hash - << "{}," //todo:k2 add compiler hash? - << (G->settings().k2_component_is_oneshot.get() ? "1" : "0") - << "};" << NL - << "return &imageInfo;" << NL - << END; + W << "const ImageInfo *vk_k2_describe() " << BEGIN << "static ImageInfo imageInfo {\"" << G->settings().k2_component_name.get() << "\"" << "," + << std::chrono::duration_cast(now.time_since_epoch()).count() << "," + << "K2_PLATFORM_HEADER_H_VERSION, " + << "{}," // todo:k2 add commit hash + << "{}," // todo:k2 add compiler hash? + << (G->is_output_mode_k2_multishot() ? "0" : "1") << "};" << NL << "return &imageInfo;" << NL << END; W << CloseFile(); } diff --git a/compiler/code-gen/files/tl2cpp/tl-combinator.cpp b/compiler/code-gen/files/tl2cpp/tl-combinator.cpp index e003a91dbb..53bc3861cc 100644 --- a/compiler/code-gen/files/tl2cpp/tl-combinator.cpp +++ b/compiler/code-gen/files/tl2cpp/tl-combinator.cpp @@ -100,7 +100,7 @@ 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()." : ""; + const auto *k2_tl_storers_prefix = G->is_output_mode_k2() ? "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; diff --git a/compiler/compiler-core.cpp b/compiler/compiler-core.cpp index 521ea99d41..bebcc37411 100644 --- a/compiler/compiler-core.cpp +++ b/compiler/compiler-core.cpp @@ -120,15 +120,22 @@ void CompilerCore::finish() { } void CompilerCore::register_settings(CompilerSettings *settings) { - kphp_assert (settings_ == nullptr); + kphp_assert(settings_ == nullptr); settings_ = settings; + const auto mode = settings->mode.get(); - if (settings->mode.get() == "cli") { + if (mode == "cli") { output_mode = OutputMode::cli; - } else if (settings->mode.get() == "lib") { + } else if (mode == "lib") { output_mode = OutputMode::lib; - } else if (settings->mode.get() == "k2-component") { - output_mode = OutputMode::k2_component; + } else if (mode == "k2-cli") { + output_mode = OutputMode::k2_cli; + } else if (mode == "k2-server") { + output_mode = OutputMode::k2_server; + } else if (mode == "k2-oneshot") { + output_mode = OutputMode::k2_oneshot; + } else if (mode == "k2-multishot") { + output_mode = OutputMode::k2_multishot; } else { output_mode = OutputMode::server; } diff --git a/compiler/compiler-core.h b/compiler/compiler-core.h index 4dbf3e7d4a..7d02d4e180 100644 --- a/compiler/compiler-core.h +++ b/compiler/compiler-core.h @@ -25,10 +25,13 @@ #include "compiler/tl-classes.h" enum class OutputMode { - server, // -M server - cli, // -M cli - lib, // -M lib - k2_component // -M k2-component + server, // -M server + cli, // -M cli + lib, // -M lib + k2_cli, // -M k2-cli + k2_server, // -M k2-server + k2_oneshot, // -M k2-oneshot + k2_multishot, // -M k2-multishot }; class CompilerCore { @@ -189,8 +192,24 @@ class CompilerCore { return output_mode == OutputMode::lib; } - bool is_output_mode_k2_component() const { - return output_mode == OutputMode::k2_component; + bool is_output_mode_k2_cli() const { + return output_mode == OutputMode::k2_cli; + } + + bool is_output_mode_k2_server() const { + return output_mode == OutputMode::k2_server; + } + + bool is_output_mode_k2_oneshot() const { + return output_mode == OutputMode::k2_oneshot; + } + + bool is_output_mode_k2_multishot() const { + return output_mode == OutputMode::k2_multishot; + } + + bool is_output_mode_k2() const { + return is_output_mode_k2_cli() || is_output_mode_k2_server() || is_output_mode_k2_oneshot() || is_output_mode_k2_multishot(); } Stats stats; diff --git a/compiler/compiler-settings.cpp b/compiler/compiler-settings.cpp index 6d17d6d0b0..cca200a018 100644 --- a/compiler/compiler-settings.cpp +++ b/compiler/compiler-settings.cpp @@ -216,8 +216,10 @@ void CompilerSettings::init() { option_as_dir(kphp_src_path); functions_file.value_ = get_full_path(functions_file.get()); runtime_sha256_file.value_ = get_full_path(runtime_sha256_file.get()); + + bool is_k2_mode = mode.get().substr(0, 3) == "k2-"; if (link_file.value_.empty()) { - if (mode.get() == "k2-component") { + if (is_k2_mode) { link_file.value_ = kphp_src_path.get() + "/objs/libkphp-light-runtime.a"; } else { link_file.value_ = kphp_src_path.get() + "/objs/libkphp-full-runtime.a"; @@ -225,7 +227,7 @@ void CompilerSettings::init() { } link_file.value_ = get_full_path(link_file.get()); if (functions_file.value_.empty()) { - if (mode.get() == "k2-component") { + if (is_k2_mode) { functions_file.value_ = kphp_src_path.get() + "/builtin-functions/kphp-light/functions.txt"; } else { functions_file.value_ = kphp_src_path.get() + "/builtin-functions/kphp-full/_functions.txt"; @@ -233,8 +235,8 @@ void CompilerSettings::init() { } functions_file.value_ = get_full_path(functions_file.get()); - if (k2_component_name.get() != "KPHP" || k2_component_is_oneshot.get()) { - kphp_error(mode.get() == "k2-component", "Options \"k2-component-name\" and \"oneshot\" available only fore k2-component mode"); + if (k2_component_name.get() != "KPHP") { + kphp_error(is_k2_mode, "Option \"k2-component-name\" is only available for k2 component modes"); } if (mode.get() == "lib") { @@ -302,7 +304,7 @@ void CompilerSettings::init() { if (!no_pch.get()) { ss << " -Winvalid-pch -fpch-preprocess"; } - if (mode.get() == "k2-component" || dynamic_incremental_linkage.get()) { + if (is_k2_mode || dynamic_incremental_linkage.get()) { ss << " -fPIC"; } if (vk::contains(cxx.get(), "clang")) { @@ -316,7 +318,7 @@ void CompilerSettings::init() { #error unsupported __cplusplus value #endif - if (mode.get() == "k2-component") { + if (is_k2_mode) { // for now k2-component must be compiled with clang and statically linked libc++ ss << " -stdlib=libc++"; } else { @@ -413,7 +415,7 @@ void CompilerSettings::init() { option_as_dir(dest_dir); dest_cpp_dir.value_ = dest_dir.get() + "kphp/"; dest_objs_dir.value_ = dest_dir.get() + "objs/"; - if (mode.get() == "k2-component") { + if (is_k2_mode) { binary_path.value_ = dest_dir.get() + k2_component_name.get() + ".so"; } else { binary_path.value_ = dest_dir.get() + mode.get(); diff --git a/compiler/compiler-settings.h b/compiler/compiler-settings.h index bd0222d278..cb20ce99ef 100644 --- a/compiler/compiler-settings.h +++ b/compiler/compiler-settings.h @@ -149,9 +149,8 @@ class CompilerSettings : vk::not_copyable { KphpOption compilation_metrics_file; KphpOption override_kphp_version; KphpOption php_code_version; - KphpOption k2_component_name; - KphpOption k2_component_is_oneshot; + KphpOption k2_component_name; KphpOption cxx; KphpOption cxx_toolchain_dir; diff --git a/compiler/kphp2cpp.cpp b/compiler/kphp2cpp.cpp index 42b42272ae..0ac07c0def 100644 --- a/compiler/kphp2cpp.cpp +++ b/compiler/kphp2cpp.cpp @@ -215,8 +215,8 @@ int main(int argc, char *argv[]) { 'f', "functions-file", "KPHP_FUNCTIONS"); parser.add("File with kphp runtime sha256 hash", settings->runtime_sha256_file, "runtime-sha256", "KPHP_RUNTIME_SHA256", "${KPHP_PATH}/objs/php_lib_version.sha256"); - parser.add("The output binary type: server, k2-component, cli or lib", settings->mode, - 'M', "mode", "KPHP_MODE", "server", {"server", "k2-component", "cli", "lib"}); + parser.add("The output binary type: server, cli, lib, k2-cli, k2-server, k2-oneshot, k2-multishot", settings->mode, 'M', "mode", "KPHP_MODE", "server", + {"server", "cli", "lib", "k2-cli", "k2-server", "k2-oneshot", "k2-multishot"}); parser.add("A runtime library for building the output binary", settings->link_file, 'l', "link-with", "KPHP_LINK_FILE"); parser.add("Build runtime from sources", settings->force_link_runtime, @@ -298,8 +298,6 @@ int main(int argc, char *argv[]) { "require-class-typing", "KPHP_REQUIRE_CLASS_TYPING"); parser.add("Define k2 component name. Default is \"KPHP\"", settings->k2_component_name, "k2-component-name", "KPHP_K2_COMPONENT_NAME", "KPHP"); - parser.add("Enable oneshot mode to k2 component", settings->k2_component_is_oneshot, - "oneshot", "KPHP_K2_COMPONENT_IS_ONESHOT"); parser.add_implicit_option("Linker flags", settings->ld_flags); parser.add_implicit_option("Incremental linker flags", settings->incremental_linker_flags); diff --git a/compiler/make/make.cpp b/compiler/make/make.cpp index 87da4cde6a..52a334070a 100644 --- a/compiler/make/make.cpp +++ b/compiler/make/make.cpp @@ -548,10 +548,10 @@ void run_make() { auto objs = run_pre_make(output_mode, settings, make_stats_file, make, obj_index, bin_file, obj_rt_index); stage::die_if_global_errors(); - if (output_mode == OutputMode::lib) { + if (G->is_output_mode_lib()) { // todo:k2 think about kphp libraries make.create_objs2static_lib_target(objs, &bin_file); - } else if (output_mode == OutputMode::k2_component) { + } else if (G->is_output_mode_k2()) { make.create_objs2k2_component_target(objs, &bin_file); } else { const std::string build_stage{"Compiling"}; diff --git a/compiler/pipes/calc-bad-vars.cpp b/compiler/pipes/calc-bad-vars.cpp index cac43595d2..f3f6a0cc57 100644 --- a/compiler/pipes/calc-bad-vars.cpp +++ b/compiler/pipes/calc-bad-vars.cpp @@ -696,7 +696,7 @@ class CalcBadVars { { FuncCallGraph call_graph(std::move(functions), dep_datas); - if (G->is_output_mode_k2_component()) { + if (G->is_output_mode_k2()) { calc_k2_fork(call_graph, dep_datas); calc_interruptible(call_graph); } else { diff --git a/compiler/pipes/code-gen.cpp b/compiler/pipes/code-gen.cpp index e4db43bf9f..32674d4462 100644 --- a/compiler/pipes/code-gen.cpp +++ b/compiler/pipes/code-gen.cpp @@ -117,7 +117,7 @@ void CodeGenF::on_finish(DataStream> &os) { // TODO: should be done in lib mode also, but in some other way if (!G->is_output_mode_lib()) { - if (!G->is_output_mode_k2_component()) { + if (!G->is_output_mode_k2()) { code_gen_start_root_task(os, std::make_unique(vk::singleton::get().flush_forkable_types(), vk::singleton::get().flush_waitable_types())); } @@ -132,10 +132,10 @@ void CodeGenF::on_finish(DataStream> &os) { code_gen_start_root_task(os, std::make_unique()); code_gen_start_root_task(os, std::make_unique()); - if (!G->is_output_mode_lib() && !G->is_output_mode_k2_component()) { + if (!G->is_output_mode_lib() && !G->is_output_mode_k2()) { code_gen_start_root_task(os, std::make_unique()); } - if (G->is_output_mode_k2_component()) { + if (G->is_output_mode_k2()) { code_gen_start_root_task(os, std::make_unique()); } } diff --git a/runtime-light/component/component.cpp b/runtime-light/component/component.cpp index 4d7f4b678c..161c8146ec 100644 --- a/runtime-light/component/component.cpp +++ b/runtime-light/component/component.cpp @@ -6,18 +6,104 @@ #include #include +#include #include #include "runtime-core/utils/kphp-assert-core.h" #include "runtime-light/core/globals/php-init-scripts.h" +#include "runtime-light/coroutine/awaitable.h" +#include "runtime-light/coroutine/task.h" #include "runtime-light/header.h" #include "runtime-light/scheduler/scheduler.h" +#include "runtime-light/streams/streams.h" #include "runtime-light/utils/context.h" +#include "runtime-light/utils/json-functions.h" + +namespace { + +constexpr uint32_t K2_INVOKE_HTTP_MAGIC = 0xd909efe8; +constexpr uint32_t K2_INVOKE_JOB_WORKER_MAGIC = 0x437d7312; + +void init_http_superglobals(const string &http_query) noexcept { + auto &component_ctx{*get_component_context()}; + component_ctx.php_script_mutable_globals_singleton.get_superglobals().v$_SERVER.set_value(string{"QUERY_TYPE"}, string{"http"}); + component_ctx.php_script_mutable_globals_singleton.get_superglobals().v$_POST = f$json_decode(http_query, true); +} + +task_t init_kphp_cli_component() noexcept { + co_return co_await wait_for_incoming_stream_t{}; +} + +task_t init_kphp_server_component() noexcept { + uint32_t magic{}; + const auto stream_d{co_await wait_for_incoming_stream_t{}}; + const auto read{co_await read_exact_from_stream(stream_d, reinterpret_cast(std::addressof(magic)), sizeof(uint32_t))}; + php_assert(read == sizeof(uint32_t)); + if (magic == K2_INVOKE_HTTP_MAGIC) { + const auto [buffer, size]{co_await read_all_from_stream(stream_d)}; + init_http_superglobals(string{buffer, static_cast(size)}); + get_platform_context()->allocator.free(buffer); + } else if (magic == K2_INVOKE_JOB_WORKER_MAGIC) { + php_error("not implemented"); + } else { + php_error("server got unexpected type of request: 0x%x", magic); + } + + co_return stream_d; +} + +int32_t merge_output_buffers(ComponentState &component_ctx) noexcept { + Response &response{component_ctx.response}; + php_assert(response.current_buffer >= 0); + + int32_t ob_first_not_empty{}; + while (ob_first_not_empty < response.current_buffer && response.output_buffers[ob_first_not_empty].size() == 0) { + ++ob_first_not_empty; // TODO: optimize by precomputing final buffer's size to reserve enough space + } + for (auto i = ob_first_not_empty + 1; i <= response.current_buffer; i++) { + response.output_buffers[ob_first_not_empty].append(response.output_buffers[i].c_str(), response.output_buffers[i].size()); + } + return ob_first_not_empty; +} + +} // namespace void ComponentState::init_script_execution() noexcept { kphp_core_context.init(); - init_php_scripts_in_each_worker(php_script_mutable_globals_singleton, main_task); - scheduler.suspend(std::make_pair(main_task.get_handle(), WaitEvent::Rechedule{})); + init_php_scripts_in_each_worker(php_script_mutable_globals_singleton, main_task_); + scheduler.suspend(std::make_pair(main_task_.get_handle(), WaitEvent::Rechedule{})); +} + +template +task_t ComponentState::run_component_prologue() noexcept { + static_assert(kind != ComponentKind::Invalid); + + component_kind_ = kind; + if constexpr (kind == ComponentKind::CLI) { + standard_stream_ = co_await init_kphp_cli_component(); + } else if constexpr (kind == ComponentKind::Server) { + standard_stream_ = co_await init_kphp_server_component(); + } +} + +template task_t ComponentState::run_component_prologue(); +template task_t ComponentState::run_component_prologue(); +template task_t ComponentState::run_component_prologue(); +template task_t ComponentState::run_component_prologue(); + +task_t ComponentState::run_component_epilogue() noexcept { + if (component_kind_ == ComponentKind::Oneshot || component_kind_ == ComponentKind::Multishot) { + co_return; + } + if (standard_stream() == INVALID_PLATFORM_DESCRIPTOR) { + poll_status = PollStatus::PollFinishedError; + co_return; + } + + const auto &buffer{response.output_buffers[merge_output_buffers(*this)]}; + if ((co_await write_all_to_stream(standard_stream(), buffer.buffer(), buffer.size())) != buffer.size()) { + php_warning("can't write component result to stream %" PRIu64, standard_stream()); + } } void ComponentState::process_platform_updates() noexcept { @@ -51,12 +137,6 @@ void ComponentState::process_platform_updates() noexcept { } } else { // update on incoming stream php_debug("got new incoming stream %" PRIu64, stream_d); - if (standard_stream_ != INVALID_PLATFORM_DESCRIPTOR) { - php_warning("skip new incoming stream since previous one is not closed"); - release_stream(stream_d); - continue; - } // TODO: multiple incoming streams (except for http queries) - standard_stream_ = stream_d; incoming_streams_.push_back(stream_d); opened_streams_.insert(stream_d); if (const auto schedule_status{scheduler.schedule(ScheduleEvent::IncomingStream{.stream_d = stream_d})}; schedule_status == ScheduleStatus::Error) { diff --git a/runtime-light/component/component.h b/runtime-light/component/component.h index 4560999d76..81531b2788 100644 --- a/runtime-light/component/component.h +++ b/runtime-light/component/component.h @@ -27,6 +27,15 @@ constexpr uint64_t INVALID_PLATFORM_DESCRIPTOR = 0; using CoroutineScheduler = SimpleCoroutineScheduler; static_assert(CoroutineSchedulerConcept); +/** + * Supported kinds of KPHP components: + * 1. CLI — works the same way as regular PHP script does + * 2. Server — automatically accepts a stream and expects it to contain either http or job worker request + * 3. Oneshot — can only accept one incoming stream + * 4. Multishot — can accept any number of incoming streams + */ +enum class ComponentKind : uint8_t { Invalid, CLI, Server, Oneshot, Multishot }; + struct ComponentState { template using unordered_set = memory_resource::stl::unordered_set; @@ -47,6 +56,12 @@ struct ComponentState { ~ComponentState() = default; void init_script_execution() noexcept; + + template + task_t run_component_prologue() noexcept; + + task_t run_component_epilogue() noexcept; + void process_platform_updates() noexcept; bool stream_updated(uint64_t stream_d) const noexcept { @@ -71,7 +86,7 @@ struct ComponentState { CoroutineScheduler scheduler; ForkComponentContext fork_component_context; - PollStatus poll_status = PollStatus::PollReschedule; + PollStatus poll_status{PollStatus::PollReschedule}; Response response; PhpScriptMutableGlobals php_script_mutable_globals_singleton; @@ -82,8 +97,9 @@ struct ComponentState { RegexComponentState regex_component_context; private: - task_t main_task; + task_t main_task_; + ComponentKind component_kind_{ComponentKind::Invalid}; uint64_t standard_stream_{INVALID_PLATFORM_DESCRIPTOR}; deque incoming_streams_; unordered_set opened_streams_; diff --git a/runtime-light/core/globals/php-script-globals.h b/runtime-light/core/globals/php-script-globals.h index 5b843dae17..a1a45b08d4 100644 --- a/runtime-light/core/globals/php-script-globals.h +++ b/runtime-light/core/globals/php-script-globals.h @@ -33,7 +33,7 @@ class PhpScriptMutableGlobals { PhpScriptBuiltInSuperGlobals superglobals; public: - PhpScriptMutableGlobals(memory_resource::unsynchronized_pool_resource &resource) + explicit PhpScriptMutableGlobals(memory_resource::unsynchronized_pool_resource &resource) : libs_linear_mem(unordered_map::allocator_type{resource}) {} static PhpScriptMutableGlobals ¤t() noexcept; diff --git a/runtime-light/stdlib/exit/exit-functions.cpp b/runtime-light/stdlib/exit/exit-functions.cpp index c097812977..8db7a4ad3b 100644 --- a/runtime-light/stdlib/exit/exit-functions.cpp +++ b/runtime-light/stdlib/exit/exit-functions.cpp @@ -6,44 +6,10 @@ #include -#include "runtime-core/utils/kphp-assert-core.h" #include "runtime-light/component/component.h" #include "runtime-light/header.h" -#include "runtime-light/streams/streams.h" #include "runtime-light/utils/context.h" -namespace { - -int32_t ob_merge_buffers() noexcept { - Response &response{get_component_context()->response}; - php_assert(response.current_buffer >= 0); - - int32_t ob_first_not_empty{}; - while (ob_first_not_empty < response.current_buffer && response.output_buffers[ob_first_not_empty].size() == 0) { - ++ob_first_not_empty; - } - for (auto i = ob_first_not_empty + 1; i <= response.current_buffer; i++) { - response.output_buffers[ob_first_not_empty].append(response.output_buffers[i].c_str(), response.output_buffers[i].size()); - } - return ob_first_not_empty; -} - -} // namespace - -task_t shutdown_script() noexcept { - auto &component_ctx{*get_component_context()}; - const auto standard_stream{component_ctx.standard_stream()}; - if (standard_stream == INVALID_PLATFORM_DESCRIPTOR) { - component_ctx.poll_status = PollStatus::PollFinishedError; - co_return; - } - - const auto &buffer{component_ctx.response.output_buffers[ob_merge_buffers()]}; - if ((co_await write_all_to_stream(standard_stream, buffer.buffer(), buffer.size())) != buffer.size()) { - php_warning("can't write component result to stream %" PRIu64, standard_stream); - } -} - task_t f$exit(const mixed &v) noexcept { // TODO: make it synchronous int64_t exit_code{}; if (v.is_string()) { @@ -56,10 +22,10 @@ task_t f$exit(const mixed &v) noexcept { // TODO: make it synchronous } else { exit_code = 1; } - co_await shutdown_script(); auto &component_ctx{*get_component_context()}; + co_await component_ctx.run_component_epilogue(); component_ctx.poll_status = component_ctx.poll_status != PollStatus::PollFinishedError && exit_code == 0 ? PollStatus::PollFinishedOk : PollStatus::PollFinishedError; component_ctx.release_all_streams(); - get_platform_context()->abort(); + get_platform_context()->exit(static_cast(exit_code)); } diff --git a/runtime-light/streams/streams.cpp b/runtime-light/streams/streams.cpp index 2d19628090..41147037ce 100644 --- a/runtime-light/streams/streams.cpp +++ b/runtime-light/streams/streams.cpp @@ -9,31 +9,11 @@ #include #include -#include "runtime-core/runtime-core.h" #include "runtime-core/utils/kphp-assert-core.h" #include "runtime-light/coroutine/awaitable.h" #include "runtime-light/coroutine/task.h" #include "runtime-light/header.h" #include "runtime-light/utils/context.h" -#include "runtime-light/utils/json-functions.h" - -namespace { - -void init_http_superglobals(const string &http_query) { - auto &component_ctx{*get_component_context()}; - component_ctx.php_script_mutable_globals_singleton.get_superglobals().v$_SERVER.set_value(string{"QUERY_TYPE"}, string{"http"}); - component_ctx.php_script_mutable_globals_singleton.get_superglobals().v$_POST = f$json_decode(http_query, true); -} - -} // namespace - -task_t accept_initial_stream() noexcept { - const auto incoming_stream_d{co_await wait_for_incoming_stream_t{}}; - const auto [buffer, size]{co_await read_all_from_stream(incoming_stream_d)}; - init_http_superglobals(string{buffer, static_cast(size)}); - get_platform_context()->allocator.free(buffer); - co_return incoming_stream_d; -} task_t> read_all_from_stream(uint64_t stream_d) noexcept { const auto &platform_ctx = *get_platform_context(); diff --git a/tests/kphp_tester.py b/tests/kphp_tester.py index ce2cb96f2c..77a79f6fe0 100755 --- a/tests/kphp_tester.py +++ b/tests/kphp_tester.py @@ -64,11 +64,10 @@ def make_kphp_once_runner(self, use_nocc, cxx_name, k2_bin): ) def set_up_env_for_k2(self, cxx_name="clang++"): - self.env_vars["KPHP_MODE"] = "k2-component" + self.env_vars["KPHP_MODE"] = "k2-cli" self.env_vars["KPHP_USER_BINARY_PATH"] = "component.so" self.env_vars["KPHP_ENABLE_GLOBAL_VARS_MEMORY_STATS"] = "0" self.env_vars["KPHP_PROFILER"] = "0" - self.env_vars["KPHP_K2_COMPONENT_IS_ONESHOT"] = "1" self.env_vars["KPHP_FORCE_LINK_RUNTIME"] = "1" From b30b495fcfaa268f913a045aab574fcbcf6cb5c6 Mon Sep 17 00:00:00 2001 From: Vadim Sadokhov <65451602+astrophysik@users.noreply.github.com> Date: Fri, 6 Sep 2024 14:57:22 +0300 Subject: [PATCH 40/45] add stubs for internal classes (#1093) Add tag `/** @kphp-generate-stub-class */` for generating internal classes stubs in k2 mode. Second part of (#1078) --- builtin-functions/kphp-light/functions.txt | 20 -- .../kphp-light/unsupported-functions.txt | 2 + .../kphp-light/unsupported/arrays.txt | 6 +- .../kphp-light/unsupported/curl.txt | 241 +++++++++++++++++- .../kphp-light/unsupported/error.txt | 20 ++ .../kphp-light/unsupported/file.txt | 4 + .../kphp-light/unsupported/hash.txt | 1 + .../kphp-light/unsupported/job-worker.txt | 10 +- .../kphp-light/unsupported/kphp-tracing.txt | 25 ++ .../kphp-light/unsupported/kphp_internal.txt | 14 +- .../kphp-light/unsupported/math.txt | 18 ++ .../kphp-light/unsupported/misc.txt | 15 +- .../kphp-light/unsupported/regex.txt | 8 +- .../kphp-light/unsupported/serialize.txt | 26 ++ .../kphp-light/unsupported/server.txt | 2 +- .../kphp-light/unsupported/string.txt | 23 +- .../kphp-light/unsupported/time.txt | 56 +++- .../kphp-light/unsupported/uberh3.txt | 77 ++++++ compiler/code-gen/declarations.cpp | 8 + compiler/code-gen/includes.cpp | 2 +- compiler/code-gen/vertex-compiler.cpp | 2 +- compiler/data/class-data.cpp | 3 +- compiler/data/class-data.h | 1 + compiler/gentree.cpp | 1 + compiler/phpdoc.cpp | 3 +- compiler/phpdoc.h | 1 + compiler/pipes/check-classes.cpp | 2 +- .../pipes/deduce-implicit-types-and-casts.cpp | 2 +- compiler/pipes/sort-and-inherit-classes.cpp | 2 +- runtime-core/runtime-core-context.h | 1 + runtime-core/runtime-core.h | 4 +- runtime-light/component/component.h | 2 + runtime-light/stdlib/array/array-functions.h | 93 +++++++ runtime-light/stdlib/curl/curl.cpp | 12 + runtime-light/stdlib/curl/curl.h | 23 ++ .../stdlib/exception/exception-functions.h | 13 + runtime-light/stdlib/output/print-functions.h | 20 ++ runtime-light/stdlib/regex/regex-functions.h | 155 ++++++++++- runtime-light/stdlib/stdlib.cmake | 1 + .../stdlib/string/string-functions.h | 41 +++ .../stdlib/system/system-functions.h | 32 +++ runtime-light/utils/json-functions.h | 11 + runtime-light/utils/to-array-processor.h | 132 ---------- 43 files changed, 932 insertions(+), 203 deletions(-) create mode 100644 builtin-functions/kphp-light/unsupported/uberh3.txt create mode 100644 runtime-light/stdlib/curl/curl.cpp create mode 100644 runtime-light/stdlib/curl/curl.h create mode 100644 runtime-light/stdlib/exception/exception-functions.h create mode 100644 runtime-light/stdlib/system/system-functions.h diff --git a/builtin-functions/kphp-light/functions.txt b/builtin-functions/kphp-light/functions.txt index 043afe81dd..82a04abe61 100644 --- a/builtin-functions/kphp-light/functions.txt +++ b/builtin-functions/kphp-light/functions.txt @@ -162,26 +162,6 @@ function component_server_send_response($query ::: ComponentQuery, $message ::: // === 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; diff --git a/builtin-functions/kphp-light/unsupported-functions.txt b/builtin-functions/kphp-light/unsupported-functions.txt index 5a224e6b23..021f080ebc 100644 --- a/builtin-functions/kphp-light/unsupported-functions.txt +++ b/builtin-functions/kphp-light/unsupported-functions.txt @@ -13,10 +13,12 @@ require_once __DIR__ . '/unsupported/kphp-toggles.txt'; require_once __DIR__ . '/unsupported/kphp-tracing.txt'; require_once __DIR__ . '/unsupported/kphp_internal.txt'; require_once __DIR__ . '/unsupported/math.txt'; +require_once __DIR__ . '/unsupported/misc.txt'; require_once __DIR__ . '/unsupported/regex.txt'; require_once __DIR__ . '/unsupported/rpc.txt'; require_once __DIR__ . '/unsupported/serialize.txt'; require_once __DIR__ . '/unsupported/server.txt'; require_once __DIR__ . '/unsupported/string.txt'; require_once __DIR__ . '/unsupported/time.txt'; +require_once __DIR__ . '/unsupported/uberh3.txt'; require_once __DIR__ . '/unsupported/vkext.txt'; diff --git a/builtin-functions/kphp-light/unsupported/arrays.txt b/builtin-functions/kphp-light/unsupported/arrays.txt index 4c89941a20..1f012e1492 100644 --- a/builtin-functions/kphp-light/unsupported/arrays.txt +++ b/builtin-functions/kphp-light/unsupported/arrays.txt @@ -65,7 +65,8 @@ function in_array ($value ::: any, $a ::: array, $strict ::: bool = false) ::: b function array_fill ($start_index ::: int, $num ::: int, $value ::: any) ::: ^3[]; function array_fill_keys ($a ::: array, $value ::: any) ::: ^2[]; function array_combine ($keys ::: array, $values ::: array) ::: ^2; -function range ($from, $to, $step ::: int = 1) ::: mixed[];//TODO +/** @kphp-extern-func-info generate-stub */ +function range ($from ::: mixed, $to ::: mixed, $step ::: int = 1) ::: mixed[]; function array_push (&$a ::: array, $val2 ::: any, $val3 ::: any = TODO, $val4 ::: any = TODO, $val5 ::: any = TODO, $val6 ::: any = TODO) ::: int; function array_pop (&$a ::: array) ::: ^1[*]; function array_sum ($a ::: array) ::: float;//TODO @@ -110,8 +111,9 @@ function vk_dot_product ($a ::: array, $b ::: array) ::: ^1[*] | ^2[*]; function to_array_debug(any $instance, bool $with_class_names = false) ::: mixed[]; +function instance_to_array(object $instance, $with_class_names ::: bool = false) ::: mixed[]; function implode ($s ::: string, $v ::: array) ::: string; /** @kphp-extern-func-info generate-stub */ -function explode ($delimiter ::: string, $str ::: string, $limit ::: int = PHP_INT_MAX) ::: string[]; \ No newline at end of file +function explode ($delimiter ::: string, $str ::: string, $limit ::: int = PHP_INT_MAX) ::: string[]; diff --git a/builtin-functions/kphp-light/unsupported/curl.txt b/builtin-functions/kphp-light/unsupported/curl.txt index ef4ef9bb0a..535976e58d 100644 --- a/builtin-functions/kphp-light/unsupported/curl.txt +++ b/builtin-functions/kphp-light/unsupported/curl.txt @@ -1,12 +1,249 @@ ; +define('JSON_UNESCAPED_UNICODE', 1); +define('JSON_FORCE_OBJECT', 16); +define('JSON_PRETTY_PRINT', 128); // TODO: add actual support +define('JSON_PARTIAL_OUTPUT_ON_ERROR', 512); +define('JSON_PRESERVE_ZERO_FRACTION', 1024); +/** @kphp-generate-stub-class */ +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>; + /** @kphp-extern-func-info generate-stub */ + 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>; +} diff --git a/builtin-functions/kphp-light/unsupported/server.txt b/builtin-functions/kphp-light/unsupported/server.txt index 2c9cefd99d..ea7127b595 100644 --- a/builtin-functions/kphp-light/unsupported/server.txt +++ b/builtin-functions/kphp-light/unsupported/server.txt @@ -22,6 +22,7 @@ function ini_set ($s ::: string, $v ::: string) ::: bool; +/** @kphp-extern-func-info cpp_template_call */ function instance_cache_fetch(string $type, string $key, bool $even_if_expired = false) ::: instance<^1>; function instance_cache_store(string $key, object $value, int $ttl = 0) ::: bool; @@ -87,7 +88,6 @@ function setlocale ($category ::: int, $locale ::: string) ::: string | false; /** @kphp-extern-func-info generate-stub */ function debug_backtrace() ::: string[][]; -/** @kphp-extern-func-info generate-stub */ function estimate_memory_usage($value ::: any) ::: int; /** @kphp-extern-func-info generate-stub */ diff --git a/builtin-functions/kphp-light/unsupported/string.txt b/builtin-functions/kphp-light/unsupported/string.txt index fd07eac1ea..e013b803b8 100644 --- a/builtin-functions/kphp-light/unsupported/string.txt +++ b/builtin-functions/kphp-light/unsupported/string.txt @@ -1,5 +1,14 @@ is_output_mode_k2()) { + // The current version of runtime-light does not support visitors + return; + } bool need_generic_accept = klass->need_to_array_debug_visitor || klass->need_instance_cache_visitors || @@ -1057,6 +1061,10 @@ void ClassDeclaration::compile_job_worker_shared_memory_piece_methods(CodeGenera } void ClassMembersDefinition::compile(CodeGenerator &W) const { + if (G->is_output_mode_k2()) { + // The current version of runtime-light does not support visitors + return; + } bool need_generic_accept = klass->need_to_array_debug_visitor || klass->need_instance_cache_visitors || diff --git a/compiler/code-gen/includes.cpp b/compiler/code-gen/includes.cpp index 7a61fb850e..7cbf272c2a 100644 --- a/compiler/code-gen/includes.cpp +++ b/compiler/code-gen/includes.cpp @@ -167,7 +167,7 @@ void IncludesCollector::compile(CodeGenerator &W) const { if (klass->ffi_class_mixin) { // FFI CData classes (structs really) are defined at their scope class header class_to_include = G->get_class(FFIRoot::scope_class_name(klass->ffi_class_mixin->scope_name)); - } else if (!klass->is_builtin()) { + } else if (!klass->is_builtin() || klass->need_generated_stub) { // add include for generated internal class class_to_include = klass; } if (class_to_include && !prev_classes_.count(class_to_include)) { diff --git a/compiler/code-gen/vertex-compiler.cpp b/compiler/code-gen/vertex-compiler.cpp index 892a62d11e..19be159ce5 100644 --- a/compiler/code-gen/vertex-compiler.cpp +++ b/compiler/code-gen/vertex-compiler.cpp @@ -1496,7 +1496,7 @@ void compile_function(VertexAdaptor func_root, CodeGenerator &W) { W << PhpMutableGlobalsAssignCurrent() << NL; } - if (func->kphp_tracing) { + if (func->kphp_tracing && !G->is_output_mode_k2()) { TracingAutogen::codegen_runtime_func_guard_declaration(W, func); TracingAutogen::codegen_runtime_func_guard_start(W, func); } diff --git a/compiler/data/class-data.cpp b/compiler/data/class-data.cpp index 25f289fbc5..08866980d3 100644 --- a/compiler/data/class-data.cpp +++ b/compiler/data/class-data.cpp @@ -354,8 +354,7 @@ bool ClassData::has_polymorphic_member_dfs(std::unordered_set &checked } bool ClassData::does_need_codegen() const { - return !is_builtin() && !is_trait() && - (really_used || is_tl_class); + return need_generated_stub || (!is_builtin() && !is_trait() && (really_used || is_tl_class)); } bool operator<(const ClassPtr &lhs, const ClassPtr &rhs) { diff --git a/compiler/data/class-data.h b/compiler/data/class-data.h index 063ec6d2b0..71c0d99ee7 100644 --- a/compiler/data/class-data.h +++ b/compiler/data/class-data.h @@ -65,6 +65,7 @@ class ClassData : public Lockable { bool can_be_php_autoloaded{false}; bool is_immutable{false}; + bool need_generated_stub{false}; std::atomic is_subtree_immutable{SubtreeImmutableType::not_visited}; std::atomic process_fields_ic_compatibility{false}; bool really_used{false}; diff --git a/compiler/gentree.cpp b/compiler/gentree.cpp index 8dc0fa7afe..ff7167c4f1 100644 --- a/compiler/gentree.cpp +++ b/compiler/gentree.cpp @@ -1721,6 +1721,7 @@ VertexPtr GenTree::get_class(const PhpDocComment *phpdoc, ClassType class_type) cur_class->set_name_and_src_name(full_class_name); // with full namespaces and slashes cur_class->phpdoc = phpdoc; cur_class->is_immutable = phpdoc && phpdoc->has_tag(PhpDocType::kphp_immutable_class); + cur_class->need_generated_stub = phpdoc && phpdoc->has_tag(PhpDocType::kphp_generated_stub_class); cur_class->location_line_num = line_num; bool registered = G->register_class(cur_class); diff --git a/compiler/phpdoc.cpp b/compiler/phpdoc.cpp index 7fb34263bc..269b46eba2 100644 --- a/compiler/phpdoc.cpp +++ b/compiler/phpdoc.cpp @@ -42,7 +42,7 @@ struct KnownPhpDocTag { }; class AllDocTags { - static constexpr int N_TAGS = 41; + static constexpr int N_TAGS = 42; static const KnownPhpDocTag ALL_TAGS[N_TAGS]; public: @@ -92,6 +92,7 @@ const KnownPhpDocTag AllDocTags::ALL_TAGS[] = { KnownPhpDocTag("@kphp-memcache-class", PhpDocType::kphp_memcache_class), KnownPhpDocTag("@kphp-immutable-class", PhpDocType::kphp_immutable_class), KnownPhpDocTag("@kphp-tl-class", PhpDocType::kphp_tl_class), + KnownPhpDocTag("@kphp-generate-stub-class", PhpDocType::kphp_generated_stub_class), KnownPhpDocTag("@kphp-const", PhpDocType::kphp_const), KnownPhpDocTag("@kphp-no-return", PhpDocType::kphp_noreturn), KnownPhpDocTag("@kphp-warn-unused-result", PhpDocType::kphp_warn_unused_result), diff --git a/compiler/phpdoc.h b/compiler/phpdoc.h index 0053db9f17..07a5151a67 100644 --- a/compiler/phpdoc.h +++ b/compiler/phpdoc.h @@ -35,6 +35,7 @@ enum class PhpDocType { kphp_memcache_class, kphp_immutable_class, kphp_tl_class, + kphp_generated_stub_class, kphp_const, kphp_noreturn, kphp_warn_unused_result, diff --git a/compiler/pipes/check-classes.cpp b/compiler/pipes/check-classes.cpp index 75e5765129..329f33fcdd 100644 --- a/compiler/pipes/check-classes.cpp +++ b/compiler/pipes/check-classes.cpp @@ -43,7 +43,7 @@ inline void CheckClassesPass::analyze_class(ClassPtr klass) { if (klass->does_need_codegen()) { check_instance_fields_inited(klass); } - if (klass->can_be_php_autoloaded && !klass->is_builtin()) { + if (klass->can_be_php_autoloaded && !klass->is_builtin() && !klass->need_generated_stub) { kphp_error(klass->file_id->main_function->body_seq == FunctionData::body_value::empty, fmt_format("class {} can be autoloaded, but its file contains some logic (maybe, require_once files with global vars?)\n", klass->as_human_readable())); diff --git a/compiler/pipes/deduce-implicit-types-and-casts.cpp b/compiler/pipes/deduce-implicit-types-and-casts.cpp index 5396b8d9e3..61e4c3426d 100644 --- a/compiler/pipes/deduce-implicit-types-and-casts.cpp +++ b/compiler/pipes/deduce-implicit-types-and-casts.cpp @@ -807,7 +807,7 @@ void DeduceImplicitTypesAndCastsPass::on_clone(VertexAdaptor v_clone) kphp_error_return(v_clone->class_id, "`clone` keyword can be used only with instances"); kphp_error(!v_clone->class_id->is_lambda_class(), "It's forbidden to `clone` lambdas"); kphp_error(!v_clone->class_id->is_typed_callable_interface(), "It's forbidden to `clone` lambdas"); - kphp_error(!v_clone->class_id->is_builtin() || v_clone->class_id->is_ffi_cdata(), "It's forbidden to `clone` built-in classes"); + kphp_error(!v_clone->class_id->is_builtin() || v_clone->class_id->need_generated_stub || v_clone->class_id->is_ffi_cdata(), "It's forbidden to `clone` built-in classes"); } // handle `throw $ex`, calc assumptions to be used later diff --git a/compiler/pipes/sort-and-inherit-classes.cpp b/compiler/pipes/sort-and-inherit-classes.cpp index d349e226c0..9338ed8d38 100644 --- a/compiler/pipes/sort-and-inherit-classes.cpp +++ b/compiler/pipes/sort-and-inherit-classes.cpp @@ -384,7 +384,7 @@ void SortAndInheritClassesF::check_on_finish(DataStream &os) { ); } - if (!c->is_builtin() && c->is_polymorphic_class()) { + if ((!c->is_builtin() || c->need_generated_stub) && c->is_polymorphic_class()) { if (vk::any_of(c->get_all_derived_classes(), [](ClassPtr c) { return c->is_class(); })) { auto virt_clone = c->add_virt_clone(); G->register_and_require_function(virt_clone, generated_self_methods, true); diff --git a/runtime-core/runtime-core-context.h b/runtime-core/runtime-core-context.h index 426ef70c07..e9a659f538 100644 --- a/runtime-core/runtime-core-context.h +++ b/runtime-core/runtime-core-context.h @@ -25,6 +25,7 @@ struct KphpCoreContext { void free(); int show_migration_php8_warning = 0; + int php_disable_warnings = 0; uint32_t empty_obj_count = 0; string_buffer_lib_context sb_lib_context; }; diff --git a/runtime-core/runtime-core.h b/runtime-core/runtime-core.h index 549203f99c..dccb788f19 100644 --- a/runtime-core/runtime-core.h +++ b/runtime-core/runtime-core.h @@ -54,8 +54,8 @@ #define SAFE_SET_VALUE(a, b, b_type, c, c_type) ({b_type b_tmp___ = b; c_type c_tmp___ = c; (a).set_value (b_tmp___, c_tmp___);}) #define SAFE_PUSH_BACK(a, b, b_type) ({b_type b_tmp___ = b; a.push_back (b_tmp___);}) #define SAFE_PUSH_BACK_RETURN(a, b, b_type) ({b_type b_tmp___ = b; a.push_back_return (b_tmp___);}) -#define NOERR(a, a_type) ({php_disable_warnings++; a_type a_tmp___ = a; php_disable_warnings--; a_tmp___;}) -#define NOERR_VOID(a) ({php_disable_warnings++; a; php_disable_warnings--;}) +#define NOERR(a, a_type) ({KphpCoreContext::current().php_disable_warnings++; a_type a_tmp___ = a; KphpCoreContext::current().php_disable_warnings--; a_tmp___;}) +#define NOERR_VOID(a) ({KphpCoreContext::current().php_disable_warnings++; a; KphpCoreContext::current().php_disable_warnings--;}) #define f$likely likely #define f$unlikely unlikely diff --git a/runtime-light/component/component.h b/runtime-light/component/component.h index 81531b2788..b3c48246dc 100644 --- a/runtime-light/component/component.h +++ b/runtime-light/component/component.h @@ -19,6 +19,7 @@ #include "runtime-light/stdlib/fork/fork-context.h" #include "runtime-light/stdlib/output/output-buffer.h" #include "runtime-light/stdlib/regex/regex-functions.h" +#include "runtime-light/stdlib/curl/curl.h" #include "runtime-light/stdlib/rpc/rpc-context.h" constexpr uint64_t INVALID_PLATFORM_DESCRIPTOR = 0; @@ -95,6 +96,7 @@ struct ComponentState { RpcComponentContext rpc_component_context; RegexComponentState regex_component_context; + CurlComponentState curl_component_state; private: task_t main_task_; diff --git a/runtime-light/stdlib/array/array-functions.h b/runtime-light/stdlib/array/array-functions.h index bd5ac8cc1a..77e4d4a29d 100644 --- a/runtime-light/stdlib/array/array-functions.h +++ b/runtime-light/stdlib/array/array-functions.h @@ -60,6 +60,18 @@ array f$array_filter_by_key(const array &a, const T1 &callback) noexcept { php_critical_error("call to unsupported function"); } + +template, T>> +array f$array_map(const CallbackT &callback, const array &a) { + php_critical_error("call to unsupported function"); +} + + +template +R f$array_reduce(const array &a, const CallbackT &callback, InitialT initial) { + php_critical_error("call to unsupported function"); +} + template T f$array_merge_spread(const T &a1) { php_critical_error("call to unsupported function"); @@ -92,6 +104,11 @@ T f$array_merge(const T &a1, const T &a2, const T &a3, const T &a4 = T(), const php_critical_error("call to unsupported function"); } +template +ReturnT f$array_merge_recursive(const Args &...args) { + php_critical_error("call to unsupported function"); +} + template void f$array_merge_into(T &a, const T1 &another_array) { php_critical_error("call to unsupported function"); @@ -428,3 +445,79 @@ template void f$array_swap_int_keys(array &a, int64_t idx1, int64_t idx2) noexcept { php_critical_error("call to unsupported function"); } + + +template +array f$to_array_debug(const class_instance &klass, bool with_class_names = false) { + php_critical_error("call to unsupported function"); +} + +template +array f$to_array_debug(const std::tuple &tuple, bool with_class_names = false) { + php_critical_error("call to unsupported function"); +} + +template +array f$to_array_debug(const shape, T...> &shape, bool with_class_names = false) { + php_critical_error("call to unsupported function"); +} + +template +array f$instance_to_array(const class_instance &klass, bool with_class_names = false) { + php_critical_error("call to unsupported function"); +} + +template +array f$instance_to_array(const std::tuple &tuple, bool with_class_names = false) { + php_critical_error("call to unsupported function"); +} + +template +array f$instance_to_array(const shape, T...> &shape, bool with_class_names = false) { + php_critical_error("call to unsupported function"); +} + +template +T f$array_unset(array &arr, int64_t key) { + php_critical_error("call to unsupported function"); +} + +template +T f$array_unset(array &arr, const string &key) { + php_critical_error("call to unsupported function"); +} + +template +T f$array_unset(array &arr, const mixed &key) { + php_critical_error("call to unsupported function"); +} + +template +Optional>> f$array_column(const array>> &a, const mixed &column_key) { + php_critical_error("call to unsupported function"); +} + +template +Optional>> f$array_column(const array>>> &a, const mixed &column_key) { + php_critical_error("call to unsupported function"); +} + +template +Optional> f$array_column(const array> &a, const mixed &column_key, const mixed &index_key = {}) { + php_critical_error("call to unsupported function"); +} + +template +Optional> f$array_column(const array>> &a, const mixed &column_key, const mixed &index_key = {}) { + php_critical_error("call to unsupported function"); +} + +inline Optional> f$array_column(const array &a, const mixed &column_key, const mixed &index_key = {}) { + php_critical_error("call to unsupported function"); +} + +template +auto f$array_column(const Optional &a, const mixed &column_key, const mixed &index_key = {}) -> decltype(f$array_column(std::declval(), column_key, index_key)) { + php_critical_error("call to unsupported function"); +} + diff --git a/runtime-light/stdlib/curl/curl.cpp b/runtime-light/stdlib/curl/curl.cpp new file mode 100644 index 0000000000..d774c103e6 --- /dev/null +++ b/runtime-light/stdlib/curl/curl.cpp @@ -0,0 +1,12 @@ +// 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/curl/curl.h" + +#include "runtime-light/component/component.h" +#include "runtime-light/utils/context.h" + +CurlComponentState &CurlComponentState::get() noexcept { + return get_component_context()->curl_component_state; +} diff --git a/runtime-light/stdlib/curl/curl.h b/runtime-light/stdlib/curl/curl.h new file mode 100644 index 0000000000..6a4f91bb76 --- /dev/null +++ b/runtime-light/stdlib/curl/curl.h @@ -0,0 +1,23 @@ +// 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" + +struct CurlComponentState { + int64_t curl_multi_info_read_msgs_in_queue_stub{}; + + static CurlComponentState &get() noexcept; +}; + +inline Optional> f$curl_multi_info_read(int64_t, int64_t & = CurlComponentState::get().curl_multi_info_read_msgs_in_queue_stub) { + php_critical_error("call to unsupported function : curl_multi_info_read"); +} + +inline bool f$curl_setopt_array(int64_t, const array &) noexcept { + php_critical_error("call to unsupported function : curl_multi_info_read"); +} diff --git a/runtime-light/stdlib/exception/exception-functions.h b/runtime-light/stdlib/exception/exception-functions.h new file mode 100644 index 0000000000..a45eb14837 --- /dev/null +++ b/runtime-light/stdlib/exception/exception-functions.h @@ -0,0 +1,13 @@ +// 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/runtime-core.h" + +template +T f$_exception_set_location(const T &e, const string &file, int64_t line) { + php_critical_error("call to unsupported function : _exception_set_location"); +} diff --git a/runtime-light/stdlib/output/print-functions.h b/runtime-light/stdlib/output/print-functions.h index 6b26308f66..2275eeedd0 100644 --- a/runtime-light/stdlib/output/print-functions.h +++ b/runtime-light/stdlib/output/print-functions.h @@ -69,3 +69,23 @@ void f$var_dump(const class_instance &v) noexcept { inline void f$echo(const string &s) noexcept { print(s); } + +inline Optional f$fprintf(const mixed &, const string &, const array &) { + php_critical_error("call to unsupported function"); +} + +inline Optional f$fputcsv(const mixed &, const array &, string = string(",", 1), string = string("\"", 1), string = string("\\", 1)) { + php_critical_error("call to unsupported function"); +} + +inline int64_t f$printf(const string &, const array &) { + php_critical_error("call to unsupported function"); +} + +inline string f$sprintf(const string &, const array &) { + php_critical_error("call to unsupported function"); +} + +inline string f$vsprintf(const string &, const array &) { + php_critical_error("call to unsupported function"); +} diff --git a/runtime-light/stdlib/regex/regex-functions.h b/runtime-light/stdlib/regex/regex-functions.h index bb9cb313aa..5c0c2e6369 100644 --- a/runtime-light/stdlib/regex/regex-functions.h +++ b/runtime-light/stdlib/regex/regex-functions.h @@ -2,7 +2,6 @@ // Copyright (c) 2024 LLC «V Kontakte» // Distributed under the GPL v3 License, see LICENSE.notice.txt - #pragma once #include "runtime-core/runtime-core.h" @@ -10,41 +9,171 @@ struct RegexComponentState { int64_t preg_replace_count_dummy{}; - static RegexComponentState& get() noexcept; + static RegexComponentState &get() noexcept; }; +class regexp final : private vk::not_copyable { +public: + regexp() = default; + + explicit regexp(const string &) {} + regexp(const char *, int64_t) {} + + void init(const string &, const char * = nullptr, const char * = nullptr) {} + void init(const char *, int64_t, const char * = nullptr, const char * = nullptr) {} +}; template> -auto f$preg_replace(const T1 &, const T2 &, const T3 &, int64_t = -1, int64_t & = RegexComponentState::get().preg_replace_count_dummy) { +auto f$preg_replace(const T1 ®ex, const T2 &replace_val, const T3 &subject, int64_t limit = -1, + int64_t &replace_count = RegexComponentState::get().preg_replace_count_dummy) { + return f$preg_replace(regex, replace_val, subject.val(), limit, replace_count); +} + +inline Optional f$preg_replace(const regexp &, const string &, const string &, int64_t = -1, + int64_t & = RegexComponentState::get().preg_replace_count_dummy) { php_critical_error("call to unsupported function"); } -template> -auto f$preg_replace_callback(const T1 &, const T2 &, const T3 &, int64_t = -1, int64_t & = RegexComponentState::get().preg_replace_count_dummy) { +inline Optional f$preg_replace(const regexp &, const mixed &, const string &, int64_t = -1, + int64_t & = RegexComponentState::get().preg_replace_count_dummy) { php_critical_error("call to unsupported function"); } -template -Optional f$preg_replace_callback(const Regexp &, const T &, const string &, int64_t = -1, int64_t & = RegexComponentState::get().preg_replace_count_dummy) { +inline mixed f$preg_replace(const regexp &, const string &, const mixed &, int64_t = -1, int64_t & = RegexComponentState::get().preg_replace_count_dummy) { php_critical_error("call to unsupported function"); } -template -mixed f$preg_replace_callback(const Regexp &, const T &, const mixed &, int64_t = -1, int64_t & = RegexComponentState::get().preg_replace_count_dummy) { +inline mixed f$preg_replace(const regexp &, const mixed &, const mixed &, int64_t = -1, int64_t & = RegexComponentState::get().preg_replace_count_dummy) { php_critical_error("call to unsupported function"); } -template -auto f$preg_replace_callback(const string &, const T &, const T2 &, int64_t = -1, int64_t & = RegexComponentState::get().preg_replace_count_dummy) { +template +auto f$preg_replace(const string ®ex, const T1 &replace_val, const T2 &subject, int64_t limit, + int64_t &replace_count = RegexComponentState::get().preg_replace_count_dummy) { + return f$preg_replace(regexp(regex), replace_val, subject, limit, replace_count); +} + +inline Optional f$preg_replace(const mixed &, const string &, const string &, int64_t = -1, + int64_t & = RegexComponentState::get().preg_replace_count_dummy) { + php_critical_error("call to unsupported function"); +} + +inline mixed f$preg_replace(const mixed &, const string &, const mixed &, int64_t = -1, int64_t & = RegexComponentState::get().preg_replace_count_dummy) { + php_critical_error("call to unsupported function"); +} + +inline Optional f$preg_replace(const mixed &, const mixed &, const string &, int64_t = -1, + int64_t & = RegexComponentState::get().preg_replace_count_dummy) { + php_critical_error("call to unsupported function"); +} + +inline mixed f$preg_replace(const mixed &, const mixed &, const mixed &, int64_t = -1, int64_t & = RegexComponentState::get().preg_replace_count_dummy) { + php_critical_error("call to unsupported function"); +} + +template> +auto f$preg_replace_callback(const T1 ®ex, const T2 &replace_val, const T3 &subject, int64_t limit = -1, + int64_t &replace_count = RegexComponentState::get().preg_replace_count_dummy) { + return f$preg_replace_callback(regex, replace_val, subject.val(), limit, replace_count); +} + +template +Optional f$preg_replace_callback(const regexp &, const T &, const string &, int64_t = -1, + int64_t & = RegexComponentState::get().preg_replace_count_dummy) { php_critical_error("call to unsupported function"); } template -Optional f$preg_replace_callback(const mixed &, const T &, const string &, int64_t = -1, int64_t & = RegexComponentState::get().preg_replace_count_dummy) { +mixed f$preg_replace_callback(const regexp &, const T &, const mixed &, int64_t = -1, int64_t & = RegexComponentState::get().preg_replace_count_dummy) { + php_critical_error("call to unsupported function"); +} + +template +auto f$preg_replace_callback(const string ®ex, const T &replace_val, const T2 &subject, int64_t limit = -1, + int64_t &replace_count = RegexComponentState::get().preg_replace_count_dummy) { + return f$preg_replace_callback(regexp(regex), replace_val, subject, limit, replace_count); +} + +template +Optional f$preg_replace_callback(const mixed &, const T &, const string &, int64_t = -1, + int64_t & = RegexComponentState::get().preg_replace_count_dummy) { php_critical_error("call to unsupported function"); } template -mixed f$preg_replace_callback(const mixed &, const T &, const mixed &, int64_t = -1, int64_t & = RegexComponentState::get().preg_replace_count_dummy) { +mixed f$preg_replace_callback(const mixed &, const T &, const mixed &, int64_t = -1, int64_t & = RegexComponentState::get().preg_replace_count_dummy) { + php_critical_error("call to unsupported function"); +} + +inline Optional f$preg_match(const regexp &, const string &) { + php_critical_error("call to unsupported function"); +} + +inline Optional f$preg_match_all(const regexp &, const string &) { + php_critical_error("call to unsupported function"); +} + +inline Optional f$preg_match(const regexp &, const string &, mixed &) { + php_critical_error("call to unsupported function"); +} + +inline Optional f$preg_match_all(const regexp &, const string &, mixed &) { + php_critical_error("call to unsupported function"); +} + +inline Optional f$preg_match(const regexp &, const string &, mixed &, int64_t, int64_t = 0) { + php_critical_error("call to unsupported function"); +} + +inline Optional f$preg_match_all(const regexp &, const string &, mixed &, int64_t, int64_t = 0) { + php_critical_error("call to unsupported function"); +} + +inline Optional f$preg_match(const string &, const string &) { + php_critical_error("call to unsupported function"); +} + +inline Optional f$preg_match_all(const string &, const string &) { + php_critical_error("call to unsupported function"); +} + +inline Optional f$preg_match(const string &, const string &, mixed &) { + php_critical_error("call to unsupported function"); +} + +inline Optional f$preg_match_all(const string &, const string &, mixed &) { + php_critical_error("call to unsupported function"); +} + +inline Optional f$preg_match(const string &, const string &, mixed &, int64_t, int64_t = 0) { php_critical_error("call to unsupported function"); } + +inline Optional f$preg_match_all(const string &, const string &, mixed &, int64_t, int64_t = 0) { + php_critical_error("call to unsupported function"); +} + +inline Optional f$preg_match(const mixed &, const string &) { + php_critical_error("call to unsupported function"); +} + +inline Optional f$preg_match_all(const mixed &, const string &) { + php_critical_error("call to unsupported function"); +} + +inline Optional f$preg_match(const mixed &, const string &, mixed &) { + php_critical_error("call to unsupported function"); +} + +inline Optional f$preg_match_all(const mixed &, const string &, mixed &) { + php_critical_error("call to unsupported function"); +} + +inline Optional f$preg_match(const mixed &, const string &, mixed &, int64_t, int64_t = 0) { + php_critical_error("call to unsupported function"); +} + +inline Optional f$preg_match_all(const mixed &, const string &, mixed &, int64_t, int64_t = 0) { + php_critical_error("call to unsupported function"); +} + diff --git a/runtime-light/stdlib/stdlib.cmake b/runtime-light/stdlib/stdlib.cmake index d43fc48256..32caac39d2 100644 --- a/runtime-light/stdlib/stdlib.cmake +++ b/runtime-light/stdlib/stdlib.cmake @@ -2,6 +2,7 @@ prepend( RUNTIME_STDLIB_SRC stdlib/ component/component-api.cpp + curl/curl.cpp exit/exit-functions.cpp fork/fork-context.cpp output/output-buffer.cpp diff --git a/runtime-light/stdlib/string/string-functions.h b/runtime-light/stdlib/string/string-functions.h index ca1b237c6a..7063a939e9 100644 --- a/runtime-light/stdlib/string/string-functions.h +++ b/runtime-light/stdlib/string/string-functions.h @@ -9,3 +9,44 @@ inline int64_t f$strlen(const string &s) noexcept { return s.size(); } + +inline tmp_string f$_tmp_substr(const string &str, int64_t start, int64_t length = std::numeric_limits::max()) { + php_critical_error("call to unsupported function"); +} + +inline tmp_string f$_tmp_substr(tmp_string str, int64_t start, int64_t length = std::numeric_limits::max()) { + php_critical_error("call to unsupported function"); +} + +inline tmp_string f$_tmp_trim(tmp_string s, const string &what = string()) { + php_critical_error("call to unsupported function"); +} + +inline tmp_string f$_tmp_trim(const string &s, const string &what = string()) { + php_critical_error("call to unsupported function"); +} + +inline string f$trim(tmp_string s, const string &what = string()) { + php_critical_error("call to unsupported function"); +} + +inline string f$trim(const string &s, const string &what = string()) { + php_critical_error("call to unsupported function"); +} + +inline Optional f$substr(const string &str, int64_t start, int64_t length = std::numeric_limits::max()) { + php_critical_error("call to unsupported function"); +} + +inline Optional f$substr(tmp_string, int64_t start, int64_t length = std::numeric_limits::max()) { + php_critical_error("call to unsupported function"); +} + + +inline string f$pack(const string &pattern, const array &a) { + php_critical_error("call to unsupported function"); +} + +inline Optional> f$unpack(const string &pattern, const string &data) { + php_critical_error("call to unsupported function"); +} diff --git a/runtime-light/stdlib/system/system-functions.h b/runtime-light/stdlib/system/system-functions.h new file mode 100644 index 0000000000..0e5eb1637e --- /dev/null +++ b/runtime-light/stdlib/system/system-functions.h @@ -0,0 +1,32 @@ +// 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/utils/kphp-assert-core.h" + +template +int64_t f$estimate_memory_usage(const T &) { + php_critical_error("call to unsupported function"); +} + +template +void f$register_shutdown_function(F &&f) { + php_critical_error("call to unsupported function"); +} + +template +void f$register_kphp_on_warning_callback(F &&callback) { + php_critical_error("call to unsupported function"); +} + +template +bool f$register_kphp_on_oom_callback(F &&callback) { + php_critical_error("call to unsupported function"); +} + +template +void f$kphp_extended_instance_cache_metrics_init(F &&callback) { + php_critical_error("call to unsupported function"); +} diff --git a/runtime-light/utils/json-functions.h b/runtime-light/utils/json-functions.h index 15626a7e23..9f3dc26782 100644 --- a/runtime-light/utils/json-functions.h +++ b/runtime-light/utils/json-functions.h @@ -179,3 +179,14 @@ inline Optional f$vk_json_encode(const T &v) noexcept { std::pair json_decode(const string &v, const char *json_obj_magic_key = nullptr) noexcept; mixed f$json_decode(const string &v, bool assoc = false) noexcept; + +template +string f$JsonEncoder$$to_json_impl(Tag /*tag*/, const class_instance &, int64_t = 0, const array & = {}) noexcept { + php_critical_error("call to unsupported function"); +} + +template +ClassName f$JsonEncoder$$from_json_impl(Tag /*tag*/, const string &, const string & /*class_mame*/) noexcept { + php_critical_error("call to unsupported function"); +} + diff --git a/runtime-light/utils/to-array-processor.h b/runtime-light/utils/to-array-processor.h index 19ac809395..1824313c0b 100644 --- a/runtime-light/utils/to-array-processor.h +++ b/runtime-light/utils/to-array-processor.h @@ -32,135 +32,3 @@ class ShapeKeyDemangle : vk::not_copyable { bool inited_{false}; std::unordered_map shape_keys_storage_; }; - -class ToArrayVisitor { -public: - explicit ToArrayVisitor(bool with_class_names) - : with_class_names_(with_class_names) {} - - array flush_result() && noexcept { - return std::move(result_); - } - - template - void operator()(const char *field_name, const T &value) { - process_impl(field_name, value); - } - - template - static void process_tuple(const std::tuple &tuple, ToArrayVisitor &visitor, std::index_sequence /*indexes*/) { - (visitor.process_impl("", std::get(tuple)), ...); - } - - template - static void process_shape(const shape, T...> &shape, ToArrayVisitor &visitor) { - auto &demangler = vk::singleton::get(); - (visitor.process_impl(demangler.get_key_by(Is).data(), shape.template get()), ...); - } - -private: - template - void process_impl(const char *field_name, const T &value) { - add_value(field_name, value); - } - - template - void process_impl(const char *field_name, const Optional &value) { - auto process_impl_lambda = [this, field_name](const auto &v) { return this->process_impl(field_name, v); }; - call_fun_on_optional_value(process_impl_lambda, value); - } - - template - void process_impl(const char *field_name, const array &value) { - array converted_value(value.size()); - for (auto it = value.begin(); it != value.end(); ++it) { - process_impl("", it.get_value()); - converted_value.set_value(it.get_key(), result_.pop()); - } - - add_value(field_name, converted_value); - } - - template - void process_impl(const char *field_name, const class_instance &instance) { - add_value(field_name, instance.is_null() ? mixed{} : f$to_array_debug(instance, with_class_names_)); - } - - template - void process_impl(const char *field_name, const std::tuple &value) { - ToArrayVisitor tuple_processor{with_class_names_}; - tuple_processor.result_.reserve(sizeof...(Args), true); - - process_tuple(value, tuple_processor, std::index_sequence_for{}); - add_value(field_name, std::move(tuple_processor).flush_result()); - } - - template - void process_impl(const char *field_name, const shape, T...> &value) { - ToArrayVisitor shape_processor{with_class_names_}; - shape_processor.result_.reserve(sizeof...(Is), true); - - process_shape(value, shape_processor); - add_value(field_name, std::move(shape_processor).flush_result()); - } - - template - void add_value(const char *field_name, T &&value) { - if (field_name[0] != '\0') { - result_.set_value(string{field_name}, std::forward(value)); - } else { - result_.push_back(std::forward(value)); - } - } - - array result_; - bool with_class_names_{false}; -}; - -template -array f$to_array_debug(const class_instance &klass, bool with_class_names = false) { - array result; - if (klass.is_null()) { - return result; - } - - if constexpr (!std::is_empty_v) { - ToArrayVisitor visitor{with_class_names}; - klass.get()->accept(visitor); - result = std::move(visitor).flush_result(); - } - - if (with_class_names) { - result.set_value(string("__class_name"), string(klass.get_class())); - } - return result; -} - -template -array f$to_array_debug(const std::tuple &tuple, bool with_class_names = false) { - ToArrayVisitor visitor{with_class_names}; - ToArrayVisitor::process_tuple(tuple, visitor, std::index_sequence_for{}); - return std::move(visitor).flush_result(); -} - -template -array f$to_array_debug(const shape, T...> &shape, bool with_class_names = false) { - ToArrayVisitor visitor{with_class_names}; - ToArrayVisitor::process_shape(shape, visitor); - return std::move(visitor).flush_result(); -} - -template -array f$instance_to_array(const class_instance &klass, bool with_class_names = false) { - return f$to_array_debug(klass, with_class_names); -} - -template -array f$instance_to_array(const std::tuple &tuple, bool with_class_names = false) { - return f$to_array_debug(tuple, with_class_names); -} - -template -array f$instance_to_array(const shape, T...> &shape, bool with_class_names = false) { - return f$to_array_debug(shape, with_class_names); -} From 6198e0de8970374ab12495750ca6742a8a6cd0b3 Mon Sep 17 00:00:00 2001 From: Petr Shumilov Date: Fri, 6 Sep 2024 19:12:03 +0300 Subject: [PATCH 41/45] Add str_getcsv builtin support (#1096) Signed-off-by: Petr Shumilov --- builtin-functions/kphp-full/_functions.txt | 2 + runtime/streams.cpp | 94 ++++++++++--------- runtime/streams.h | 3 + runtime/string_functions.cpp | 40 ++++++++ runtime/string_functions.h | 3 + .../phpt/string_functions/011_str_getcsv.php | 27 ++++++ tests/zend-test-list | 3 + 7 files changed, 127 insertions(+), 45 deletions(-) create mode 100644 tests/phpt/string_functions/011_str_getcsv.php diff --git a/builtin-functions/kphp-full/_functions.txt b/builtin-functions/kphp-full/_functions.txt index 7846b8c4b0..48b2d84f79 100644 --- a/builtin-functions/kphp-full/_functions.txt +++ b/builtin-functions/kphp-full/_functions.txt @@ -739,6 +739,8 @@ function rtrim ($s ::: string, $what ::: string = " \n\r\t\v\0") ::: string; function xor_strings ($s ::: string, $t ::: string) ::: string; function similar_text ($first ::: string, $second ::: string, float &$percent = TODO) ::: int; +function str_getcsv($str ::: string, string $delimiter ::: string = ",", string $enclosure ::: string = "\"", string $escape ::: string = "\\") ::: mixed[] | false; + function extension_loaded(string $extension): bool; function ctype_alnum(mixed $text): bool; diff --git a/runtime/streams.cpp b/runtime/streams.cpp index f6a5a2e7e5..3a9c47ad0d 100644 --- a/runtime/streams.cpp +++ b/runtime/streams.cpp @@ -12,8 +12,6 @@ #include "runtime/allocator.h" #include "runtime/critical_section.h" -constexpr int PHP_CSV_NO_ESCAPE = EOF; - static string::size_type max_wrapper_name_size = 0; static array wrappers; @@ -505,43 +503,15 @@ static const char *fgetcsv_lookup_trailing_spaces(const char *ptr, size_t len) { return ptr; } - -Optional> f$fgetcsv(const Stream &stream, int64_t length, string delimiter, string enclosure, string escape) { - if (delimiter.empty()) { - php_warning("delimiter must be a character"); - return false; - } else if (delimiter.size() > 1) { - php_warning("delimiter must be a single character"); - } - if (enclosure.empty()) { - php_warning("enclosure must be a character"); - return false; - } else if (enclosure.size() > 1) { - php_warning("enclosure must be a single character"); - } - int escape_char = PHP_CSV_NO_ESCAPE; - if (!escape.empty()) { - escape_char = static_cast(escape[0]); - } else if (escape.size() > 1) { - php_warning("escape_char must be a single character"); - } - char delimiter_char = delimiter[0]; - char enclosure_char = enclosure[0]; - if (length < 0) { - php_warning("Length parameter may not be negative"); - return false; - } else if (length == 0) { - length = -1; - } - Optional buf_optional = length < 0 ? f$fgets(stream) : f$fgets(stream, length + 1); - if (!buf_optional.has_value()) { - return false; - } - string buffer = buf_optional.val(); +// Common csv-parsing functionality for +// * fgetcsv +// * str_getcsv +// The function is similar to `php_fgetcsv` function from https://github.com/php/php-src/blob/master/ext/standard/file.c +Optional> getcsv(const Stream &stream, string buffer, char delimiter, char enclosure, char escape) { array answer; int current_id = 0; string_buffer tmp_buffer; - // this part is imported from https://github.com/php/php-src/blob/master/ext/standard/file.c, function php_fgetcsv + // Following part is imported from `php_fgetcsv` char const *buf = buffer.c_str(); char const *bptr = buf; size_t buf_len = buffer.size(); @@ -557,10 +527,10 @@ Optional> f$fgetcsv(const Stream &stream, int64_t length, string de inc_len = (bptr < limit ? (*bptr == '\0' ? 1 : mblen(bptr, limit - bptr)) : 0); if (inc_len == 1) { char const *tmp = bptr; - while ((*tmp != delimiter_char) && isspace((int)*(unsigned char *)tmp)) { + while ((*tmp != delimiter) && isspace((int)*(unsigned char *)tmp)) { tmp++; } - if (*tmp == enclosure_char) { + if (*tmp == enclosure) { bptr = tmp; } } @@ -571,7 +541,7 @@ Optional> f$fgetcsv(const Stream &stream, int64_t length, string de } first_field = false; /* 2. Read field, leaving bptr pointing at start of next field */ - if (inc_len != 0 && *bptr == enclosure_char) { + if (inc_len != 0 && *bptr == enclosure) { int state = 0; bptr++; /* move on to first character in field */ @@ -641,7 +611,7 @@ Optional> f$fgetcsv(const Stream &stream, int64_t length, string de state = 0; break; case 2: /* embedded enclosure ? let's check it */ - if (*bptr != enclosure_char) { + if (*bptr != enclosure) { /* real enclosure */ tmp_buffer.append(hunk_begin, static_cast(bptr - hunk_begin - 1)); hunk_begin = bptr; @@ -653,9 +623,9 @@ Optional> f$fgetcsv(const Stream &stream, int64_t length, string de state = 0; break; default: - if (*bptr == enclosure_char) { + if (*bptr == enclosure) { state = 2; - } else if (escape_char != PHP_CSV_NO_ESCAPE && *bptr == escape_char) { + } else if (escape != PHP_CSV_NO_ESCAPE && *bptr == escape) { state = 1; } bptr++; @@ -697,7 +667,7 @@ Optional> f$fgetcsv(const Stream &stream, int64_t length, string de inc_len = 1; /* fallthrough */ case 1: - if (*bptr == delimiter_char) { + if (*bptr == delimiter) { goto quit_loop_3; } break; @@ -725,7 +695,7 @@ Optional> f$fgetcsv(const Stream &stream, int64_t length, string de inc_len = 1; /* fallthrough */ case 1: - if (*bptr == delimiter_char) { + if (*bptr == delimiter) { goto quit_loop_4; } break; @@ -740,7 +710,7 @@ Optional> f$fgetcsv(const Stream &stream, int64_t length, string de char const *comp_end = (char *)fgetcsv_lookup_trailing_spaces(tmp_buffer.c_str(), tmp_buffer.size()); tmp_buffer.set_pos(comp_end - tmp_buffer.c_str()); - if (*bptr == delimiter_char) { + if (*bptr == delimiter) { bptr++; } } @@ -753,6 +723,40 @@ Optional> f$fgetcsv(const Stream &stream, int64_t length, string de return answer; } +Optional> f$fgetcsv(const Stream &stream, int64_t length, string delimiter, string enclosure, string escape) { + if (delimiter.empty()) { + php_warning("delimiter must be a character"); + return false; + } else if (delimiter.size() > 1) { + php_warning("delimiter must be a single character"); + } + if (enclosure.empty()) { + php_warning("enclosure must be a character"); + return false; + } else if (enclosure.size() > 1) { + php_warning("enclosure must be a single character"); + } + int escape_char = PHP_CSV_NO_ESCAPE; + if (!escape.empty()) { + escape_char = static_cast(escape[0]); + } else if (escape.size() > 1) { + php_warning("escape_char must be a single character"); + } + char delimiter_char = delimiter[0]; + char enclosure_char = enclosure[0]; + if (length < 0) { + php_warning("Length parameter may not be negative"); + return false; + } else if (length == 0) { + length = -1; + } + Optional buf_optional = length < 0 ? f$fgets(stream) : f$fgets(stream, length + 1); + if (!buf_optional.has_value()) { + return false; + } + return getcsv(stream, buf_optional.val(), delimiter_char, enclosure_char, escape_char); +} + Optional f$file_get_contents(const string &stream) { STREAM_FUNCTION_BODY(file_get_contents, false)(url); } diff --git a/runtime/streams.h b/runtime/streams.h index 1f1ccc6b6f..1ae579423a 100644 --- a/runtime/streams.h +++ b/runtime/streams.h @@ -15,6 +15,7 @@ constexpr int64_t STREAM_SET_READ_BUFFER_OPTION = 2; constexpr int64_t FILE_APPEND = 1; +constexpr int PHP_CSV_NO_ESCAPE = EOF; struct stream_functions { string name; @@ -89,6 +90,8 @@ Optional f$vfprintf(const Stream &stream, const string &format, const a Optional f$fputcsv(const Stream &stream, const array &fields, string delimiter = string(",", 1), string enclosure = string("\"", 1), string escape_char = string("\\", 1)); +Optional> getcsv(const Stream &stream, string buffer, char delimiter, char enclosure, char escape); + Optional> f$fgetcsv(const Stream &stream, int64_t length = 0, string delimiter = string(",", 1), string enclosure = string("\"", 1), string escape_char = string("\\", 1)); diff --git a/runtime/string_functions.cpp b/runtime/string_functions.cpp index d61931bec0..18756a4512 100644 --- a/runtime/string_functions.cpp +++ b/runtime/string_functions.cpp @@ -14,6 +14,9 @@ #include "runtime/context/runtime-context.h" #include "runtime/interface.h" +// For "f$str_getcsv" support +#include "runtime/streams.h" + const string COLON(",", 1); const string CP1251("cp1251"); const string DOT(".", 1); @@ -2950,3 +2953,40 @@ string str_concat(str_concat_arg s1, str_concat_arg s2, str_concat_arg s3, str_c auto new_size = s1.size + s2.size + s3.size + s4.size + s5.size; return string(new_size, true).append_unsafe(s1.as_tmp_string()).append_unsafe(s2.as_tmp_string()).append_unsafe(s3.as_tmp_string()).append_unsafe(s4.as_tmp_string()).append_unsafe(s5.as_tmp_string()).finish_append(); } + +// Based on `getcsv` from `streams` +Optional> f$str_getcsv(const string &str, const string &delimiter, const string &enclosure, const string &escape) { + char delimiter_char = ','; + char enclosure_char = '"'; + char escape_char = PHP_CSV_NO_ESCAPE; + /* + * By PHP Manual: delimiter, enclosure, escape -- one single-byte character only + * We make it a warning + * Since PHP 8.3.11 it should return false + */ + const auto del_size = delimiter.size(); + if (del_size > 1) { + php_warning("Delimiter must be a single character"); + } + if (del_size != 0) { + delimiter_char = delimiter[0]; + } + + const auto enc_size = enclosure.size(); + if (enc_size > 1) { + php_warning("Enclosure must be a single character"); + } + if (enc_size != 0) { + enclosure_char = enclosure[0]; + } + + const auto esc_size = escape.size(); + if (esc_size > 1) { + php_warning("Escape must be a single character"); + } + if (esc_size != 0) { + escape_char = escape[0]; + } + + return getcsv(mixed() /* null */, str, delimiter_char, enclosure_char, escape_char); +} diff --git a/runtime/string_functions.h b/runtime/string_functions.h index a0f13df801..b7e3cc2995 100644 --- a/runtime/string_functions.h +++ b/runtime/string_functions.h @@ -263,6 +263,9 @@ string f$vsprintf(const string &format, const array &args); string f$wordwrap(const string &str, int64_t width = 75, const string &brk = NEW_LINE, bool cut = false); +Optional> f$str_getcsv(const string &s, const string &delimiter = string(1, ','), + const string &enclosure = string(1, '\"'), const string &escape = string(1, '\\')); + /* * * IMPLEMENTATION diff --git a/tests/phpt/string_functions/011_str_getcsv.php b/tests/phpt/string_functions/011_str_getcsv.php new file mode 100644 index 0000000000..4dfc2ebd93 --- /dev/null +++ b/tests/phpt/string_functions/011_str_getcsv.php @@ -0,0 +1,27 @@ +@ok + 2 +// not 1 <=> 3 +var_dump(str_getcsv($s2, ",", "*")); // 1 +var_dump(str_getcsv($s2, ",", "*", "\\")); // 2 +var_dump(str_getcsv($s2, ",", "*", "")); // 3 + + diff --git a/tests/zend-test-list b/tests/zend-test-list index bb6c9b4bf1..8b5c776c2a 100644 --- a/tests/zend-test-list +++ b/tests/zend-test-list @@ -659,6 +659,9 @@ ext/standard/tests/strings/vsprintf_basic8.phpt ext/standard/tests/strings/vsprintf_basic9.phpt ext/standard/tests/strings/wordwrap_basic.phpt ext/standard/tests/strings/wordwrap_variation5.phpt +ext/standard/tests/strings/str_getcsv_001.phpt +ext/standard/tests/strings/str_getcsv_002.phpt +ext/standard/tests/strings/bug55674.phpt ext/standard/tests/url/base64_decode_basic_001.phpt ext/standard/tests/url/base64_decode_basic_002.phpt ext/standard/tests/url/base64_encode_basic_001.phpt From 107ed2af237c94f44a303405cb83e16b1baec862 Mon Sep 17 00:00:00 2001 From: Denis Vaksman Date: Tue, 10 Sep 2024 11:32:40 +0300 Subject: [PATCH 42/45] [flex] update flex-config.php (#1100) --- flex/lib/configs/flex-config.php | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/flex/lib/configs/flex-config.php b/flex/lib/configs/flex-config.php index bce51746f7..b04bd74569 100644 --- a/flex/lib/configs/flex-config.php +++ b/flex/lib/configs/flex-config.php @@ -78,7 +78,7 @@ function setupFlexNew($lang_id, $language_config) { 'female' => ['Gen' => 'ии', 'Dat' => 'ии', 'Acc' => 'ию', 'Ins' => 'ией', 'Abl' => 'ии'], ], [ - 'patterns' => ['Али(я)', 'Нажи(я)', 'Гали(я)', 'Альфи(я)', 'Балхи(я)', 'Нури(я)', 'Зульфи(я)', 'Ади(я)', 'Кадри(я)', 'Дани(я)', 'Гвазба(я)', 'Зали(я)', 'Гульфи(я)', 'Руми(я)', 'Раушани(я)', 'Сани(я)', 'Рани(я)', 'Аси(я)', 'Нази(я)', 'Рамзи(я)'], + 'patterns' => ['Али(я)', 'Нажи(я)', 'Гали(я)', 'Альфи(я)', 'Балхи(я)', 'Нури(я)', 'Зульфи(я)', 'Ади(я)', 'Кадри(я)', 'Дани(я)', 'Гвазба(я)', 'Зали(я)', 'Гульфи(я)', 'Руми(я)', 'Раушани(я)', 'Сани(я)', 'Рани(я)', 'Аси(я)', 'Нази(я)', 'Рамзи(я)', 'Марзи(я)'], 'male' => ['Gen' => 'и', 'Dat' => 'е', 'Acc' => 'ю', 'Ins' => 'ей', 'Abl' => 'е'], 'female' => ['Gen' => 'и', 'Dat' => 'е', 'Acc' => 'ю', 'Ins' => 'ей', 'Abl' => 'е'], ], @@ -301,7 +301,7 @@ function setupFlexNew($lang_id, $language_config) { 'female' => 'fixed', ], [ - 'patterns' => ['*й', 'Берего(й)', 'Водосто(й)', 'Корро(й)', 'Коро(й)', 'Геро(й)', 'Стро(й)', 'Лыхопо(й)', 'Лихопо(й)', 'Воскобо(й)', 'Алло(й)', 'Градобо(й)', 'Драпо(й)', 'Тро(й)', 'Трибо(й)', 'Килиго(й)', 'Устро(й)', 'Рокджо(й)', 'Бо(й)', 'Во(й)', 'Го(й)', 'До(й)', 'Ко(й)', 'Но(й)', '*Ро(й)', 'Со(й)', 'Джо(й)', 'Гранобо(й)', 'То(й)', 'Ко(й)', 'Ло(й)', 'Уо(й)', 'Фо(й)', 'Хо(й)', 'Цо(й)', 'Чо(й)', 'Шо(й)', 'Забо(й)', 'Фро(й)', 'Свинобо(й)', 'Козодо(й)', 'Волобо(й)', 'Нагисо(й)', 'Сысо(й)', 'Бро(й)', 'Попушо(й)', 'Тоницо(й)', 'Малфо(й)', 'Чимпо(й)', 'Нецо(й)', 'Буки(й)', 'Карако(й)', 'Дупли(й)', 'Того(й)', 'Тяги(й)', 'Незбуди(й)', 'Анани(й)', 'Анани(й)', 'Сантони(й)', 'Прибо(й)', 'Барбо(й)', 'Барбо(й)', 'Карагеорги(й)', 'Дзо(й)', 'Ручи(й)', 'Папушо(й)', 'Якобо(й)', 'Рутени(й)', 'Гранабо(й)', 'Моки(й)', 'Аксено(й)', 'Хво(й)'], + 'patterns' => ['*й', 'Берего(й)', 'Водосто(й)', 'Корро(й)', 'Коро(й)', 'Геро(й)', 'Стро(й)', 'Лыхопо(й)', 'Лихопо(й)', 'Воскобо(й)', 'Алло(й)', 'Градобо(й)', 'Драпо(й)', 'Тро(й)', 'Трибо(й)', 'Килиго(й)', 'Устро(й)', 'Рокджо(й)', 'Бо(й)', 'Во(й)', 'Го(й)', 'До(й)', 'Ко(й)', 'Но(й)', '*Ро(й)', 'Со(й)', 'Джо(й)', 'Гранобо(й)', 'То(й)', 'Ко(й)', 'Ло(й)', 'Уо(й)', 'Фо(й)', 'Хо(й)', 'Цо(й)', 'Чо(й)', 'Шо(й)', 'Забо(й)', 'Фро(й)', 'Свинобо(й)', 'Козодо(й)', 'Волобо(й)', 'Нагисо(й)', 'Сысо(й)', 'Бро(й)', 'Попушо(й)', 'Тоницо(й)', 'Малфо(й)', 'Чимпо(й)', 'Нецо(й)', 'Буки(й)', 'Карако(й)', 'Дупли(й)', 'Того(й)', 'Тяги(й)', 'Незбуди(й)', 'Анани(й)', 'Анани(й)', 'Сантони(й)', 'Прибо(й)', 'Барбо(й)', 'Барбо(й)', 'Карагеорги(й)', 'Дзо(й)', 'Ручи(й)', 'Папушо(й)', 'Якобо(й)', 'Рутени(й)', 'Гранабо(й)', 'Моки(й)', 'Аксено(й)', 'Хво(й)', 'Стры(й)'], 'male' => ['Gen' => 'я', 'Dat' => 'ю', 'Acc' => 'я', 'Ins' => 'ем', 'Abl' => 'е'], 'female' => 'fixed', ], @@ -371,12 +371,12 @@ function setupFlexNew($lang_id, $language_config) { 'female' => 'fixed', ], [ - 'patterns' => ['Говоре(к)', 'Шпе(к)', '*ок', '*ек', '*ч(ёк)', 'Коновалён(ак)', 'Клё(к)', '*що(к)', '*що(к)', 'Конте(к)', 'Берневе(к)', '', 'Лёре(к)', 'Иванцо(к)', 'Блэйло(к)', 'Петруче(к)', 'Коршаче(к)', 'Опаре(к)', 'Сухобо(к)', 'Голоско(к)', 'Рее(к)', 'Куте(к)', 'Отро(к)', 'Выруче(к)', 'Джирайо(к)', 'Галаме(к)', 'Строче(к)', 'Зубаче(к)', 'Суще(к)', 'Дыче(к)', 'Вороче(к)', 'Пауше(к)', 'Дже(к)', 'Смуше(к)', 'Дьяче(к)', 'Казаче(к)', 'Маче(к)', 'Куо(к)', 'Смарщё(к)', 'Скваре(к)', 'Микитее(к)', 'Ткаце(к)', 'Коте(к)', 'Якуше(к)', 'Сое(к)', 'Мике(к)', 'Дуно(к)', 'Семене(к)', 'Заме(к)'], + 'patterns' => ['Говоре(к)', 'Шпе(к)', '*ок', '*ек', '*ч(ёк)', 'Коновалён(ак)', 'Клё(к)', '*що(к)', '*що(к)', 'Конте(к)', 'Берневе(к)', '', 'Лёре(к)', 'Иванцо(к)', 'Блэйло(к)', 'Петруче(к)', 'Коршаче(к)', 'Опаре(к)', 'Сухобо(к)', 'Голоско(к)', 'Рее(к)', 'Куте(к)', 'Отро(к)', 'Выруче(к)', 'Джирайо(к)', 'Галаме(к)', 'Строче(к)', 'Зубаче(к)', 'Суще(к)', 'Дыче(к)', 'Вороче(к)', 'Пауше(к)', 'Дже(к)', 'Смуше(к)', 'Дьяче(к)', 'Казаче(к)', 'Маче(к)', 'Куо(к)', 'Смарщё(к)', 'Скваре(к)', 'Микитее(к)', 'Ткаце(к)', 'Коте(к)', 'Якуше(к)', 'Сое(к)', 'Мике(к)', 'Дуно(к)', 'Семене(к)', 'Заме(к)', 'Вечоре(к)', 'Явье(к)'], 'male' => ['Gen' => 'ка', 'Dat' => 'ку', 'Acc' => 'ка', 'Ins' => 'ком', 'Abl' => 'ке'], 'female' => 'fixed', ], [ - 'patterns' => ['*бе(к)', 'Войтише(к)', 'Цве(к)', 'Дуде(к)', 'Буче(к)', 'Ляше(к)', 'Ше(к)', 'Клаче(к)', 'Баче(к)', 'Кривобо(к)', 'Джамбе(к)', 'Мроже(к)', 'Керпе(к)', 'Любе(к)', 'Стре(к)', 'Маршале(к)', 'Жуче(к)', 'Корчмаре(к)', 'Дяче(к)', 'Цумбе(к)', 'Собе-Пане(к)', 'Штре(к)', 'Сире(к)', 'Псуе(к)', 'Матие(к)', 'Пане(к)', 'Маше(к)', 'Тимоше(к)', 'Домбе(к)', 'Пасе(к)', 'Поле(к)', 'Саде(к)', 'Стро(к)', 'Ско(к)', 'Смо(к)', 'Воло(к)', 'Набо(к)', 'Бро(к)', 'Шейнро(к)', 'Скаче(к)', 'Тере(к)', 'Якубе(к)', 'Жунусбе(к)', 'Суше(к)', 'Юкшто(к)', 'Гае(к)', 'Бо(к)', 'Бло(к)', 'Кониче(к)', 'Козодро(к)', 'Гло(к)', 'Сте(к)', 'Проро(к)', 'Чапе(к)', 'Калымбе(к)', 'Булатбе(к)', 'Чуле(к)', 'Ное(к)', 'Коо(к)', 'Домаше(к)', 'Ходаче(к)', 'Гре(к)', 'Гуче(к)', 'Волче(к)', 'Дро(к)', 'Клембе(к)', 'Гайдее(к)', 'Кулаче(к)', 'Филобо(к)', 'Ре(к)', 'Туребе(к)', 'Климе(к)', 'Кириче(к)', 'Ваце(к)', 'Херте(к)', 'Бе(к)', 'Конюше(к)', 'Ге(к)', 'Даутбе(к)', 'Саче(к)', 'Кло(к)', 'Долейше(к)', 'Айдарбе(к)', 'Мозо(к)', 'Краче(к)', 'Ро(к)', 'Кре(к)', 'Ерме(к)', 'Дане(к)', 'Быче(к)', 'Краснощё(к)', 'Здро(к)', 'Недосе(к)', 'Синео(к)', 'Фо(к)', 'Шо(к)', 'Птаче(к)', 'Хабе(к)', 'Подрабине(к)', 'Боче(к)', 'Саме(к)', 'Фле(к)', 'Эрюре(к)', 'Кряче(к)', 'Вольче(к)', 'Заране(к)', 'Лоше(к)', 'Гле(к)', 'Пельте(к)', 'Шрео(к)', 'Го(к)', 'Павшо(к)', 'Куле(к)', 'Рабе(к)', 'Заране(к)', 'Зброже(к)', 'Доброско(к)', 'Хлопче(к)', 'Бре(к)', 'Чиле(к)', 'Вале(к)', 'Шне(к)', 'Алибе(к)', 'Паце(к)', 'До(к)', 'Лодбро(к)', 'Але(к)', 'Теле(к)', 'Але(к)', 'Лещео(к)', 'Куре(к)', 'Смерче(к)', 'Здро(к)', 'Саросе(к)', 'Крюче(к)', 'Толче(к)', 'Кро(к)', 'Стое(к)', 'Обмо(к)', 'Жерносе(к)', 'Толо(к)', 'Лое(к)', 'Боже(к)', 'Бабе(к)', 'Тышле(к)', 'Крсе(к)', 'Пате(к)', 'Геро(к)', 'Чесно(к)', 'Малоо(к)', 'Вальцене(к)', 'Казыбе(к)', 'Мадибе(к)', 'Бельте(к)', 'То(к)', 'Раде(к)', 'Иманбе(к)', 'Боре(к)', 'Садубе(к)', 'Унуче(к)', 'Раче(к)', 'Иорде(к)', 'Понче(к)', 'Че(к)', 'Янаше(к)', 'Гузе(к)', 'Кымкабе(к)', 'Чве(к)', 'Се(к)', 'Витенбе(к)', 'Каирбе(к)', 'Чле(к)', 'Глуше(к)', 'Бирсе(к)', 'Бле(к)', 'Татло(к)', 'Рысбе(к)', 'Соло(к)', 'Сухане(к)', 'Хае(к)', 'Павлючё(к)', 'Пуче(к)', 'Максудбе(к)', 'Боче(к)', 'Гасе(к)', 'Про(к)', 'Ккро(к)', 'Манде(к)', 'Проце(к)', 'Кривобо(к)', 'Старосе(к)', 'Карасе(к)', 'Красноще(к)', 'Быче(к)', 'Кудре(к)', 'Барыше(к)', 'Овчаре(к)', 'Боло(к)', 'Коза(к)', 'Со(к)', 'Ноце(к)', 'Клоче(к)', 'Сре(к)', 'Жернасе(к)', 'Цейпе(к)', 'Дио(к)', 'Круче(к)', 'Дарзне(к)', 'Кло(к)', 'Черноо(к)', 'Мице(к)', 'Гейде(к)', 'Полубо(к)', 'Дерешо(к)', 'Гавине(к)', 'Ко(к)', 'Краснощо(к)', 'Четырбо(к)', 'Мо(к)', 'Ро(к)', 'Дзинтарне(к)', 'Рыче(к)', 'Горбаче(к)', 'Биле(к)', 'Раззо(к)', 'Лерё(к)', 'Шаче(к)', 'Ратче(к)', 'Колто(к)', 'Челе(к)', 'Броже(к)', 'Кабаче(к)', 'Глё(к)', 'Кле(к)', 'Гураче(к)', 'Скворо(к)', 'Русно(к)', 'Синящо(к)', 'Челе(к)', 'Пе(к)', 'Сапе(к)', 'Райбе(к)', 'Ко(к)', 'Кумо(к)', 'Тото(к)'], + 'patterns' => ['*бе(к)', 'Войтише(к)', 'Цве(к)', 'Дуде(к)', 'Буче(к)', 'Ляше(к)', 'Ше(к)', 'Клаче(к)', 'Баче(к)', 'Кривобо(к)', 'Джамбе(к)', 'Мроже(к)', 'Керпе(к)', 'Любе(к)', 'Стре(к)', 'Маршале(к)', 'Жуче(к)', 'Корчмаре(к)', 'Дяче(к)', 'Цумбе(к)', 'Собе-Пане(к)', 'Штре(к)', 'Сире(к)', 'Псуе(к)', 'Матие(к)', 'Пане(к)', 'Маше(к)', 'Тимоше(к)', 'Домбе(к)', 'Пасе(к)', 'Поле(к)', 'Саде(к)', 'Стро(к)', 'Ско(к)', 'Смо(к)', 'Воло(к)', 'Набо(к)', 'Бро(к)', 'Шейнро(к)', 'Скаче(к)', 'Тере(к)', 'Якубе(к)', 'Жунусбе(к)', 'Суше(к)', 'Юкшто(к)', 'Гае(к)', 'Бо(к)', 'Бло(к)', 'Кониче(к)', 'Козодро(к)', 'Гло(к)', 'Сте(к)', 'Проро(к)', 'Чапе(к)', 'Калымбе(к)', 'Булатбе(к)', 'Чуле(к)', 'Ное(к)', 'Коо(к)', 'Домаше(к)', 'Ходаче(к)', 'Гре(к)', 'Гуче(к)', 'Волче(к)', 'Дро(к)', 'Клембе(к)', 'Гайдее(к)', 'Кулаче(к)', 'Филобо(к)', 'Ре(к)', 'Туребе(к)', 'Климе(к)', 'Кириче(к)', 'Ваце(к)', 'Херте(к)', 'Бе(к)', 'Конюше(к)', 'Ге(к)', 'Даутбе(к)', 'Саче(к)', 'Кло(к)', 'Долейше(к)', 'Айдарбе(к)', 'Мозо(к)', 'Краче(к)', 'Ро(к)', 'Кре(к)', 'Ерме(к)', 'Дане(к)', 'Быче(к)', 'Краснощё(к)', 'Здро(к)', 'Недосе(к)', 'Синео(к)', 'Фо(к)', 'Шо(к)', 'Птаче(к)', 'Хабе(к)', 'Подрабине(к)', 'Боче(к)', 'Саме(к)', 'Фле(к)', 'Эрюре(к)', 'Кряче(к)', 'Вольче(к)', 'Заране(к)', 'Лоше(к)', 'Гле(к)', 'Пельте(к)', 'Шрео(к)', 'Го(к)', 'Павшо(к)', 'Куле(к)', 'Рабе(к)', 'Заране(к)', 'Зброже(к)', 'Доброско(к)', 'Хлопче(к)', 'Бре(к)', 'Чиле(к)', 'Вале(к)', 'Шне(к)', 'Алибе(к)', 'Паце(к)', 'До(к)', 'Лодбро(к)', 'Але(к)', 'Теле(к)', 'Але(к)', 'Лещео(к)', 'Куре(к)', 'Смерче(к)', 'Здро(к)', 'Саросе(к)', 'Крюче(к)', 'Толче(к)', 'Кро(к)', 'Стое(к)', 'Обмо(к)', 'Жерносе(к)', 'Толо(к)', 'Лое(к)', 'Боже(к)', 'Бабе(к)', 'Тышле(к)', 'Крсе(к)', 'Пате(к)', 'Геро(к)', 'Чесно(к)', 'Малоо(к)', 'Вальцене(к)', 'Казыбе(к)', 'Мадибе(к)', 'Бельте(к)', 'То(к)', 'Раде(к)', 'Иманбе(к)', 'Боре(к)', 'Садубе(к)', 'Унуче(к)', 'Раче(к)', 'Иорде(к)', 'Понче(к)', 'Че(к)', 'Янаше(к)', 'Гузе(к)', 'Кымкабе(к)', 'Чве(к)', 'Се(к)', 'Витенбе(к)', 'Каирбе(к)', 'Чле(к)', 'Глуше(к)', 'Бирсе(к)', 'Бле(к)', 'Татло(к)', 'Рысбе(к)', 'Соло(к)', 'Сухане(к)', 'Хае(к)', 'Павлючё(к)', 'Пуче(к)', 'Максудбе(к)', 'Боче(к)', 'Гасе(к)', 'Про(к)', 'Ккро(к)', 'Манде(к)', 'Проце(к)', 'Кривобо(к)', 'Старосе(к)', 'Карасе(к)', 'Красноще(к)', 'Быче(к)', 'Кудре(к)', 'Барыше(к)', 'Овчаре(к)', 'Боло(к)', 'Коза(к)', 'Со(к)', 'Ноце(к)', 'Клоче(к)', 'Сре(к)', 'Жернасе(к)', 'Цейпе(к)', 'Дио(к)', 'Круче(к)', 'Дарзне(к)', 'Кло(к)', 'Черноо(к)', 'Мице(к)', 'Гейде(к)', 'Полубо(к)', 'Дерешо(к)', 'Гавине(к)', 'Ко(к)', 'Краснощо(к)', 'Четырбо(к)', 'Мо(к)', 'Ро(к)', 'Дзинтарне(к)', 'Рыче(к)', 'Горбаче(к)', 'Биле(к)', 'Раззо(к)', 'Лерё(к)', 'Шаче(к)', 'Ратче(к)', 'Колто(к)', 'Челе(к)', 'Броже(к)', 'Кабаче(к)', 'Глё(к)', 'Кле(к)', 'Гураче(к)', 'Скворо(к)', 'Русно(к)', 'Синящо(к)', 'Челе(к)', 'Пе(к)', 'Сапе(к)', 'Райбе(к)', 'Ко(к)', 'Кумо(к)', 'Тото(к)', 'Велико(к)'], 'male' => ['Gen' => 'ка', 'Dat' => 'ку', 'Acc' => 'ка', 'Ins' => 'ком', 'Abl' => 'ке'], 'female' => 'fixed', ], @@ -391,12 +391,12 @@ function setupFlexNew($lang_id, $language_config) { 'female' => 'fixed', ], [ - 'patterns' => ['Черношве(ц)', '*ец', 'Коре(ц)', 'Ое(ц)', 'Шке(ц)', 'Яре(ц)', 'Шле(ц)', 'Шпе(ц)', 'Марин(ец)', 'Фе(ц)', 'Куриле(ц)'], + 'patterns' => ['Черношве(ц)', '*ец', 'Коре(ц)', 'Ое(ц)', 'Шке(ц)', 'Яре(ц)', 'Шле(ц)', 'Шпе(ц)', 'Марин(ец)', 'Фе(ц)', 'Куриле(ц)', 'Бе(ц)'], 'male' => ['Gen' => 'ца', 'Dat' => 'цу', 'Acc' => 'ца', 'Ins' => 'цем', 'Abl' => 'це'], 'female' => 'fixed', ], [ - 'patterns' => ['Жуклин(ец)', 'Гапан(ец)', 'Кремен(ец)', 'Ворон(ец)', 'Сасков(ец)', 'Трохим(ец)', 'Шите(ц)', 'Вороб(ец)', 'Пригне(ц)', 'Лисов(ец)', 'Рухов(ец)', 'От(ец)', 'Чигрин(ец)', 'Тестел(ец)', 'Короб(ец)', 'Лубен(ец)', 'Краве(ц)', 'Шве(ц)', 'Жн(ец)', 'Кузне(ц)', 'Добрин(ец)', 'Дубин(ец)', 'Бо(ец)', 'Титов(ец)', 'Скреб(ец)', 'Канив(ец)', 'Митьков(ец)', 'Зимов(ец)', 'Мул(ец)', 'Дон(ец)', 'Сидор(ец)', 'Туров(ец)', 'Герге(ц)', 'Орде(ц)', 'Де(ц)', 'Солон(ец)', 'Насков(ец)', 'Ливин(ец)', 'Чуднов(ец)', 'Мыслив(ец)', 'Малахов(ец)', 'Козуб(ец)', 'Казан(ец)', 'Якуб(ец)', 'Козин(ец)', 'Москал(ец)', 'Обод(ец)', 'Супрун(ец)', 'Шабан(ец)', 'Корни(ец)', 'Степан(ец)', 'Брагин(ец)', 'Левин(ец)', 'Руб(ец)', 'Кацав(ец)', 'Остап(ец)', 'Гороб(ец)', 'Кербе(ц)', 'Волын(ец)', 'Адын(ец)', 'Сив(ец)', 'Мелехов(ец)', 'Кор(ец)', 'Кре(ц)', 'Хлеб(ец)', 'Сидоров(ец)', 'Дан(ец)', 'Пасов(ец)', 'Сте(ц)', 'Березин(ец)', 'Москов(ец)', 'Зем(ец)', 'Редков(ец)', 'Черне(ц)', 'Крржев(ец)', 'Баков(ец)', 'Демьян(ец)', 'Богре(ц)', 'Голын(ец)', 'Домов(ец)', 'Писар(ец)', 'Мисов(ец)', 'Куп(ец)', 'Пилип(ец)', 'Крив(ец)', 'Скомор(ец)', 'Гарин(ец)', 'Гришков(ец)', 'Мошен(ец)', 'Гаврилов(ец)', 'Дун(ец)', 'Марков(ец)', 'Лесков(ец)', 'Сан(ец)', 'Слив(ец)', 'Мышков(ец)', 'Лашне(ц)', 'Марине(ц)', 'Ум(ец)', 'Караку(ц)', 'Вежнов(ец)', 'Михнов(ец)', 'Ребков(ец)', 'Белев(ец)', 'Стругов(ец)', 'Лукьян(ец)', 'Кожен(ец)', 'Юшков(ец)', 'Ферен(ец)', 'Медлов(ец)', 'Жуков(ец)', 'Полтав(ец)', 'Ляхов(ец)', 'Максим(ец)', 'Чернов(ец)', 'Товпен(ец)', 'Долголев(ец)', 'Конон(ец)', 'Яким(ец)', 'Обушве(ц)', 'Мале(ц)', 'Салтов(ец)', 'Стрил(ец)', 'Раков(ец)', 'Харма(ц)', 'Ман(ец)', 'Божен(ец)', 'Бабин(ец)', 'Абле(ц)', 'Кудре(ц)', 'Ше(ц)', 'Антон(ец)', 'Близне(ц)', 'Липов(ец)', 'Пе(ц)', 'Федор(ец)', 'Скобле(ц)', 'Ве(ц)', 'Гале(ц)', 'Фастов(ец)', 'Стовп(ец)', 'Краве(ц)', 'Не(ц)', 'Лисив(ец)', 'Бегун(ец)', 'Кобе(ц)', 'Хин(ец)', 'Семен(ец)', 'Карпе(ц)', 'Роман(ец)', 'Иван(ец)', 'Скором(ец)', 'Шаков(ец)', 'Рослов(ец)', 'Куров(ец)', 'Грицов(ец)', 'Штербе(ц)', 'Замир(ец)', 'Гусин(ец)', 'Бров(ец)', 'Мазе(ц)', 'Конов(ец)', 'Демидов(ец)', 'Дубов(ец)', 'Лысков(ец)', 'Дедов(ец)', 'Левков(ец)', 'Рыбян(ец)', 'Волове(ц)', 'Борозен(ец)', 'Сосков(ец)', 'Стром(ец)', 'Корин(ец)', 'Зин(ец)', 'Янкун(ец)', 'Гребен(ец)', 'Крижив(ец)', 'Литвин(ец)', 'Кролив(ец)', 'Цве(ц)', 'Могилев(ец)', 'Яков(ец)', 'Корон(ец)', 'Юхим(ец)', 'Тишков(ец)', 'Захар(ец)', 'Чабан(ец)', 'Скрип(ец)', 'Стовпе(ц)', 'Глаубе(ц)', 'Корене(ц)', 'Дашив(ец)', 'Котелев(ец)', 'Котков(ец)', 'Локе(ц)', 'Быков(ец)', 'Кишин(ец)', 'Косин(ец)', 'Самохов(ец)', 'Кремне(ц)', 'Горов(ец)', 'Пше(ц)', 'Багнов(ец)', 'Ряб(ец)', 'Гарне(ц)', 'Мушав(ец)', 'Раб(ец)', 'Перегин(ец)', 'Компан(ец)', 'Брашев(ец)', 'Ле(ц)', 'Шене(ц)', 'Таран(ец)', 'Лубин(ец)', '', 'Мирон(ец)', 'Ольхов(ец)', 'Кохов(ец)', 'Власов(ец)', 'Ильков(ец)', 'Ипе(ц)', 'Шапоре(ц)', 'Шапоре(ц)', 'Здоров(ец)', 'Здоров(ец)', 'Чирвон(ец)', 'Здоров(ец)', 'Чирвон(ец)', 'Зинков(ец)', 'Кабан(ец)', 'Анисков(ец)', 'Машин(ец)', 'Адам(ец)', 'Маков(ец)', 'Никипор(ец)', 'Гоб(ец)', 'Деревен(ец)', 'Павлюков(ец)', 'Михайле(ц)', 'Митков(ец)', 'Пелеп(ец)', 'Самар(ец)', 'Браган(ец)', 'Крампе(ц)', 'Милюн(ец)', 'Мишков(ец)', 'Саван(ец)', 'Остапов(ец)', 'Карачин(ец)', 'Бухов(ец)', 'Немер(ец)', 'Леонов(ец)', 'Петров(ец)', 'Гузов(ец)', 'Вашков(ец)'], + 'patterns' => ['Жуклин(ец)', 'Гапан(ец)', 'Кремен(ец)', 'Ворон(ец)', 'Сасков(ец)', 'Трохим(ец)', 'Шите(ц)', 'Вороб(ец)', 'Пригне(ц)', 'Лисов(ец)', 'Рухов(ец)', 'От(ец)', 'Чигрин(ец)', 'Тестел(ец)', 'Короб(ец)', 'Лубен(ец)', 'Краве(ц)', 'Шве(ц)', 'Жн(ец)', 'Кузне(ц)', 'Добрин(ец)', 'Дубин(ец)', 'Бо(ец)', 'Титов(ец)', 'Скреб(ец)', 'Канив(ец)', 'Митьков(ец)', 'Зимов(ец)', 'Мул(ец)', 'Дон(ец)', 'Сидор(ец)', 'Туров(ец)', 'Герге(ц)', 'Орде(ц)', 'Де(ц)', 'Солон(ец)', 'Насков(ец)', 'Ливин(ец)', 'Чуднов(ец)', 'Мыслив(ец)', 'Малахов(ец)', 'Козуб(ец)', 'Казан(ец)', 'Якуб(ец)', 'Козин(ец)', 'Москал(ец)', 'Обод(ец)', 'Супрун(ец)', 'Шабан(ец)', 'Корни(ец)', 'Степан(ец)', 'Брагин(ец)', 'Левин(ец)', 'Руб(ец)', 'Кацав(ец)', 'Остап(ец)', 'Гороб(ец)', 'Кербе(ц)', 'Волын(ец)', 'Адын(ец)', 'Сив(ец)', 'Мелехов(ец)', 'Кор(ец)', 'Кре(ц)', 'Хлеб(ец)', 'Сидоров(ец)', 'Дан(ец)', 'Пасов(ец)', 'Сте(ц)', 'Березин(ец)', 'Москов(ец)', 'Зем(ец)', 'Редков(ец)', 'Черне(ц)', 'Крржев(ец)', 'Баков(ец)', 'Демьян(ец)', 'Богре(ц)', 'Голын(ец)', 'Домов(ец)', 'Писар(ец)', 'Мисов(ец)', 'Куп(ец)', 'Пилип(ец)', 'Крив(ец)', 'Скомор(ец)', 'Гарин(ец)', 'Гришков(ец)', 'Мошен(ец)', 'Гаврилов(ец)', 'Дун(ец)', 'Марков(ец)', 'Лесков(ец)', 'Сан(ец)', 'Слив(ец)', 'Мышков(ец)', 'Лашне(ц)', 'Марине(ц)', 'Ум(ец)', 'Караку(ц)', 'Вежнов(ец)', 'Михнов(ец)', 'Ребков(ец)', 'Белев(ец)', 'Стругов(ец)', 'Лукьян(ец)', 'Кожен(ец)', 'Юшков(ец)', 'Ферен(ец)', 'Медлов(ец)', 'Жуков(ец)', 'Полтав(ец)', 'Ляхов(ец)', 'Максим(ец)', 'Чернов(ец)', 'Товпен(ец)', 'Долголев(ец)', 'Конон(ец)', 'Яким(ец)', 'Обушве(ц)', 'Мале(ц)', 'Салтов(ец)', 'Стрил(ец)', 'Раков(ец)', 'Харма(ц)', 'Ман(ец)', 'Божен(ец)', 'Бабин(ец)', 'Абле(ц)', 'Кудре(ц)', 'Ше(ц)', 'Антон(ец)', 'Близне(ц)', 'Липов(ец)', 'Пе(ц)', 'Федор(ец)', 'Скобле(ц)', 'Ве(ц)', 'Гале(ц)', 'Фастов(ец)', 'Стовп(ец)', 'Краве(ц)', 'Не(ц)', 'Лисив(ец)', 'Бегун(ец)', 'Кобе(ц)', 'Хин(ец)', 'Семен(ец)', 'Карпе(ц)', 'Роман(ец)', 'Иван(ец)', 'Скором(ец)', 'Шаков(ец)', 'Рослов(ец)', 'Куров(ец)', 'Грицов(ец)', 'Штербе(ц)', 'Замир(ец)', 'Гусин(ец)', 'Бров(ец)', 'Мазе(ц)', 'Конов(ец)', 'Демидов(ец)', 'Дубов(ец)', 'Лысков(ец)', 'Дедов(ец)', 'Левков(ец)', 'Рыбян(ец)', 'Волове(ц)', 'Борозен(ец)', 'Сосков(ец)', 'Стром(ец)', 'Корин(ец)', 'Зин(ец)', 'Янкун(ец)', 'Гребен(ец)', 'Крижив(ец)', 'Литвин(ец)', 'Кролив(ец)', 'Цве(ц)', 'Могилев(ец)', 'Яков(ец)', 'Корон(ец)', 'Юхим(ец)', 'Тишков(ец)', 'Захар(ец)', 'Чабан(ец)', 'Скрип(ец)', 'Стовпе(ц)', 'Глаубе(ц)', 'Дашив(ец)', 'Котелев(ец)', 'Котков(ец)', 'Локе(ц)', 'Быков(ец)', 'Кишин(ец)', 'Косин(ец)', 'Самохов(ец)', 'Кремне(ц)', 'Горов(ец)', 'Пше(ц)', 'Багнов(ец)', 'Ряб(ец)', 'Гарне(ц)', 'Мушав(ец)', 'Раб(ец)', 'Перегин(ец)', 'Компан(ец)', 'Брашев(ец)', 'Ле(ц)', 'Шене(ц)', 'Таран(ец)', 'Лубин(ец)', 'Мирон(ец)', 'Ольхов(ец)', 'Кохов(ец)', 'Власов(ец)', 'Ильков(ец)', 'Ипе(ц)', 'Шапоре(ц)', 'Шапоре(ц)', 'Здоров(ец)', 'Здоров(ец)', 'Чирвон(ец)', 'Здоров(ец)', 'Чирвон(ец)', 'Зинков(ец)', 'Кабан(ец)', 'Анисков(ец)', 'Машин(ец)', 'Адам(ец)', 'Маков(ец)', 'Никипор(ец)', 'Гоб(ец)', 'Деревен(ец)', 'Павлюков(ец)', 'Михайле(ц)', 'Митков(ец)', 'Пелеп(ец)', 'Самар(ец)', 'Браган(ец)', 'Крампе(ц)', 'Милюн(ец)', 'Мишков(ец)', 'Саван(ец)', 'Остапов(ец)', 'Карачин(ец)', 'Бухов(ец)', 'Немер(ец)', 'Леонов(ец)', 'Петров(ец)', 'Гузов(ец)', 'Вашков(ец)', 'Наум(ец)', 'Кошов(ец)', 'Корен(ец)'], 'male' => ['Gen' => 'ца', 'Dat' => 'цу', 'Acc' => 'ца', 'Ins' => 'цом', 'Abl' => 'це'], 'female' => 'fixed', ], @@ -421,7 +421,7 @@ function setupFlexNew($lang_id, $language_config) { 'female' => 'fixed', ], [ - 'patterns' => ['Шв(ец)', 'Жн(ец)', 'Игр(ец)', 'За(ец)', 'Б(ец)', 'Бразн(ец)', 'Гр(ец)', 'Ойн(ец)', 'Хейф(ец)', 'Х(ец)', 'Корни(ец)', 'Кн(ец)'], + 'patterns' => ['Шв(ец)', 'Жн(ец)', 'Игр(ец)', 'За(ец)', 'Бразн(ец)', 'Гр(ец)', 'Ойн(ец)', 'Хейф(ец)', 'Х(ец)', 'Корни(ец)', 'Кн(ец)'], 'male' => ['Gen' => 'еца', 'Dat' => 'ецу', 'Acc' => 'еца', 'Ins' => 'ецом', 'Abl' => 'еце'], 'female' => 'fixed', ], @@ -461,7 +461,7 @@ function setupFlexNew($lang_id, $language_config) { 'female' => 'fixed', ], [ - 'patterns' => ['*', 'Хвин()', 'Иов()', 'Манчын()', 'Целестин()', 'Син()', 'Цын()'], + 'patterns' => ['*', 'Хвин()', 'Иов()', 'Манчын()', 'Целестин()', 'Син()', 'Цын()', 'Брин()'], 'male' => ['Gen' => 'а', 'Dat' => 'у', 'Acc' => 'а', 'Ins' => 'ом', 'Abl' => 'е'], 'female' => 'fixed', ], @@ -491,7 +491,7 @@ function setupFlexNew($lang_id, $language_config) { 'female' => 'fixed', ], [ - 'patterns' => ['Тер-', 'Нор-', 'Сулима-', 'Бей-', 'Джан-', 'Гаген-', 'Крым-', 'И-', 'ген-', 'Сун-', 'Сун-дин-', 'дин-', 'Ин-', 'Ин-Дин-', 'Дин-', 'Бит-', 'Догуй-', 'Кызыл-', 'Аль-', 'Фон-', 'Шангыр-', '-оол()', 'Бадма-', 'дер-', 'Ван-', 'Ван-дер-', 'Ага-', 'ча-', 'Юн-', 'Эль-', '-Паш(а)', 'Ал-', 'Рза-', 'Бут-', 'Гук-', 'Алдын-', 'Чин-', 'Лин-', 'Дель-', 'Гуан-', 'Тин-', ' ', 'Ир-', 'Покинь-', 'Тилла', 'Әл-', 'Па-', 'Ве-', 'Гехт-', 'Рефа', 'Сак-', 'Тун-', 'Серин-', 'Цин-', 'Дэ-', 'Я-', 'О-', 'Аль-', 'Сармини-', 'Жан-', 'Ми-', 'Ай-', 'Чен-', 'На-', 'Сы-', 'Ли-', 'Шан-', 'Тя-', 'Шен-', 'Пак-', 'Си', 'Ай-', 'Секерж-', 'Мелик-', 'Буд-', 'Ен-', 'Джек-', 'Куй-', 'Пей-', 'Шин-', 'У', 'Чан-', 'Бен-', 'На-', 'Сы-', 'Тя-', 'Дын-', 'Зан-', 'На-', 'Бей-', 'Ак-', 'Хан-'], + 'patterns' => ['Тер-', 'Нор-', 'Сулима-', 'Бей-', 'Джан-', 'Гаген-', 'Крым-', 'И-', 'ген-', 'Сун-', 'Сун-дин-', 'дин-', 'Ин-', 'Ин-Дин-', 'Дин-', 'Бит-', 'Догуй-', 'Кызыл-', 'Аль-', 'Фон-', 'Шангыр-', '-оол()', 'Бадма-', 'дер-', 'Ван-', 'Ван-дер-', 'Ага-', 'ча-', 'Юн-', 'Эль-', '-Паш(а)', 'Ал-', 'Рза-', 'Бут-', 'Гук-', 'Алдын-', 'Чин-', 'Лин-', 'Дель-', 'Гуан-', 'Тин-', ' ', 'Ир-', 'Покинь-', 'Тилла', 'Әл-', 'Па-', 'Ве-', 'Гехт-', 'Рефа', 'Сак-', 'Тун-', 'Серин-', 'Цин-', 'Дэ-', 'Я-', 'О-', 'Аль-', 'Сармини-', 'Жан-', 'Ми-', 'Ай-', 'Чен-', 'На-', 'Сы-', 'Ли-', 'Шан-', 'Тя-', 'Шен-', 'Пак-', 'Си', 'Ай-', 'Секерж-', 'Мелик-', 'Буд-', 'Ен-', 'Джек-', 'Куй-', 'Пей-', 'Шин-', 'У', 'Чан-', 'Бен-', 'На-', 'Сы-', 'Тя-', 'Дын-', 'Зан-', 'На-', 'Бей-', 'Ак-', 'Хан-', 'Ка-'], 'male' => 'fixed', 'female' => 'fixed', ], @@ -1920,4 +1920,4 @@ function setupFlexNew($lang_id, $language_config) { } return $language_config; -} \ No newline at end of file +} From 15dd7c4e6c42eafb4c1d967cc12d80bb1ba3e080 Mon Sep 17 00:00:00 2001 From: Mikhail Kornaukhov <66915223+mkornaukhov03@users.noreply.github.com> Date: Tue, 10 Sep 2024 19:12:34 +0300 Subject: [PATCH 43/45] Make `tl/constants/common.h` better (#1082) * Get rid of defines and use inline constexpr * Reformat tl/constans/common.h * Fix copyright --- common/tl/constants/common.h | 132 +++++++++++++++++------------------ 1 file changed, 66 insertions(+), 66 deletions(-) diff --git a/common/tl/constants/common.h b/common/tl/constants/common.h index 70e41edf1f..fd435d5f16 100644 --- a/common/tl/constants/common.h +++ b/common/tl/constants/common.h @@ -1,83 +1,83 @@ // Compiler for PHP (aka KPHP) -// Copyright (c) 2020 LLC «V Kontakte» +// Copyright (c) 2024 LLC «V Kontakte» // Distributed under the GPL v3 License, see LICENSE.notice.txt -// TODO get rid of defines and rewrite on enum or constexpr inline +#include + /* Autogenerated from common.tl and left only used constants */ #pragma once -#define TL__ 0x840e0eccU -#define TL_BOOL_FALSE 0xbc799737U -#define TL_BOOL_STAT 0x92cbcbfaU -#define TL_BOOL_TRUE 0x997275b5U -#define TL_DICTIONARY 0x1f4c618fU -#define TL_DICTIONARY_FIELD 0x239c1b62U -#define TL_DOUBLE 0x2210c154U -#define TL_FLOAT 0x824dab22U -#define TL_INT 0xa8509bdaU -#define TL_INT_KEY_DICTIONARY 0x07bafc42U -#define TL_INT_KEY_DICTIONARY_FIELD 0x721ea8b9U -#define TL_LEFT 0x0a29cd5dU -#define TL_LONG 0x22076cbaU -#define TL_LONG_KEY_DICTIONARY 0xb424d8f1U -#define TL_REQ_RESULT_HEADER 0x8cc84ce1U -#define TL_MAYBE_FALSE 0x27930a7bU -#define TL_MAYBE_TRUE 0x3f9c8ef8U -#define TL_RPC_DEST_ACTOR 0x7568aabdU -#define TL_RPC_DEST_ACTOR_FLAGS 0xf0a5acf7U -#define TL_RPC_DEST_FLAGS 0xe352035eU -#define TL_RPC_INVOKE_REQ 0x2374df3dU -#define TL_RPC_INVOKE_REQ_EXTRA 0xf3ef81a9U -#define TL_RPC_PING 0x5730a2dfU -#define TL_RPC_PONG 0x8430eaa7U -#define TL_RPC_REQ_ERROR 0x7ae432f5U -#define TL_RPC_REQ_RESULT 0x63aeda4eU -#define TL_RPC_REQ_RESULT_EXTRA 0xc5011709U -#define TL_STAT 0x9d56e6b2U -#define TL_STRING 0xb5286e24U -#define TL_TRUE 0x3fedd339U -#define TL_TUPLE 0x9770768aU -#define TL_VECTOR 0x1cb5c415U -#define TL_VECTOR_TOTAL 0x10133f47U -#define TL_ZERO 0x00000000U +inline constexpr uint32_t TL__ = 0x840e0eccU; +inline constexpr uint32_t TL_BOOL_FALSE = 0xbc799737U; +inline constexpr uint32_t TL_BOOL_STAT = 0x92cbcbfaU; +inline constexpr uint32_t TL_BOOL_TRUE = 0x997275b5U; +inline constexpr uint32_t TL_DICTIONARY = 0x1f4c618fU; +inline constexpr uint32_t TL_DICTIONARY_FIELD = 0x239c1b62U; +inline constexpr uint32_t TL_DOUBLE = 0x2210c154U; +inline constexpr uint32_t TL_FLOAT = 0x824dab22U; +inline constexpr uint32_t TL_INT = 0xa8509bdaU; +inline constexpr uint32_t TL_INT_KEY_DICTIONARY = 0x07bafc42U; +inline constexpr uint32_t TL_INT_KEY_DICTIONARY_FIELD = 0x721ea8b9U; +inline constexpr uint32_t TL_LEFT = 0x0a29cd5dU; +inline constexpr uint32_t TL_LONG = 0x22076cbaU; +inline constexpr uint32_t TL_LONG_KEY_DICTIONARY = 0xb424d8f1U; +inline constexpr uint32_t TL_REQ_RESULT_HEADER = 0x8cc84ce1U; +inline constexpr uint32_t TL_MAYBE_FALSE = 0x27930a7bU; +inline constexpr uint32_t TL_MAYBE_TRUE = 0x3f9c8ef8U; +inline constexpr uint32_t TL_RPC_DEST_ACTOR = 0x7568aabdU; +inline constexpr uint32_t TL_RPC_DEST_ACTOR_FLAGS = 0xf0a5acf7U; +inline constexpr uint32_t TL_RPC_DEST_FLAGS = 0xe352035eU; +inline constexpr uint32_t TL_RPC_INVOKE_REQ = 0x2374df3dU; +inline constexpr uint32_t TL_RPC_INVOKE_REQ_EXTRA = 0xf3ef81a9U; +inline constexpr uint32_t TL_RPC_PING = 0x5730a2dfU; +inline constexpr uint32_t TL_RPC_PONG = 0x8430eaa7U; +inline constexpr uint32_t TL_RPC_REQ_ERROR = 0x7ae432f5U; +inline constexpr uint32_t TL_RPC_REQ_RESULT = 0x63aeda4eU; +inline constexpr uint32_t TL_RPC_REQ_RESULT_EXTRA = 0xc5011709U; +inline constexpr uint32_t TL_STAT = 0x9d56e6b2U; +inline constexpr uint32_t TL_STRING = 0xb5286e24U; +inline constexpr uint32_t TL_TRUE = 0x3fedd339U; +inline constexpr uint32_t TL_TUPLE = 0x9770768aU; +inline constexpr uint32_t TL_VECTOR = 0x1cb5c415U; +inline constexpr uint32_t TL_VECTOR_TOTAL = 0x10133f47U; +inline constexpr uint32_t TL_ZERO = 0x00000000U; -#include namespace vk { namespace tl { namespace common { namespace rpc_invoke_req_extra_flags { -constexpr static uint32_t return_binlog_pos = 1U << 0U; -constexpr static uint32_t return_binlog_time = 1U << 1U; -constexpr static uint32_t return_pid = 1U << 2U; -constexpr static uint32_t return_request_sizes = 1U << 3U; -constexpr static uint32_t return_failed_subqueries = 1U << 4U; -constexpr static uint32_t return_query_stats = 1U << 6U; -constexpr static uint32_t no_result = 1U << 7U; -constexpr static uint32_t wait_binlog_pos = 1U << 16U; -constexpr static uint32_t string_forward_keys = 1U << 18U; -constexpr static uint32_t int_forward_keys = 1U << 19U; -constexpr static uint32_t string_forward = 1U << 20U; -constexpr static uint32_t int_forward = 1U << 21U; -constexpr static uint32_t custom_timeout_ms = 1U << 23U; -constexpr static uint32_t supported_compression_version = 1U << 25U; -constexpr static uint32_t random_delay = 1U << 26U; -constexpr static uint32_t return_view_number = 1U << 27U; -constexpr static uint32_t persistent_query = 1U << 28U; -constexpr static uint32_t ALL = 0x1ebd00df; +inline constexpr uint32_t return_binlog_pos = 1U << 0U; +inline constexpr uint32_t return_binlog_time = 1U << 1U; +inline constexpr uint32_t return_pid = 1U << 2U; +inline constexpr uint32_t return_request_sizes = 1U << 3U; +inline constexpr uint32_t return_failed_subqueries = 1U << 4U; +inline constexpr uint32_t return_query_stats = 1U << 6U; +inline constexpr uint32_t no_result = 1U << 7U; +inline constexpr uint32_t wait_binlog_pos = 1U << 16U; +inline constexpr uint32_t string_forward_keys = 1U << 18U; +inline constexpr uint32_t int_forward_keys = 1U << 19U; +inline constexpr uint32_t string_forward = 1U << 20U; +inline constexpr uint32_t int_forward = 1U << 21U; +inline constexpr uint32_t custom_timeout_ms = 1U << 23U; +inline constexpr uint32_t supported_compression_version = 1U << 25U; +inline constexpr uint32_t random_delay = 1U << 26U; +inline constexpr uint32_t return_view_number = 1U << 27U; +inline constexpr uint32_t persistent_query = 1U << 28U; +inline constexpr uint32_t ALL = 0x1ebd00df; } // namespace rpc_invoke_req_extra_flags namespace rpc_req_result_extra_flags { -constexpr static uint32_t binlog_pos = 1U << 0U; -constexpr static uint32_t binlog_time = 1U << 1U; -constexpr static uint32_t engine_pid = 1U << 2U; -constexpr static uint32_t request_size = 1U << 3U; -constexpr static uint32_t response_size = 1U << 3U; -constexpr static uint32_t failed_subqueries = 1U << 4U; -constexpr static uint32_t compression_version = 1U << 5U; -constexpr static uint32_t stats = 1U << 6U; -constexpr static uint32_t epoch_number = 1U << 27U; -constexpr static uint32_t view_number = 1U << 27U; -constexpr static uint32_t ALL = 0x0800007f; +inline constexpr uint32_t binlog_pos = 1U << 0U; +inline constexpr uint32_t binlog_time = 1U << 1U; +inline constexpr uint32_t engine_pid = 1U << 2U; +inline constexpr uint32_t request_size = 1U << 3U; +inline constexpr uint32_t response_size = 1U << 3U; +inline constexpr uint32_t failed_subqueries = 1U << 4U; +inline constexpr uint32_t compression_version = 1U << 5U; +inline constexpr uint32_t stats = 1U << 6U; +inline constexpr uint32_t epoch_number = 1U << 27U; +inline constexpr uint32_t view_number = 1U << 27U; +inline constexpr uint32_t ALL = 0x0800007f; } // namespace rpc_req_result_extra_flags } // namespace common From e8da89fb528f7c6d22aeb1834b423dc0e4f8566b Mon Sep 17 00:00:00 2001 From: Alexander Polyakov Date: Thu, 12 Sep 2024 16:11:23 +0300 Subject: [PATCH 44/45] Add job workers support to K2 runtime (#1097) --- builtin-functions/kphp-light/functions.txt | 3 +- builtin-functions/kphp-light/job-workers.txt | 49 ++++++ .../kphp-light/unsupported-functions.txt | 1 - .../kphp-light/unsupported/job-worker.txt | 52 ------ .../class-instance/refcountable-php-classes.h | 9 +- runtime-light/component/component.cmake | 2 +- runtime-light/component/component.cpp | 42 +---- runtime-light/component/component.h | 7 + runtime-light/component/image.h | 1 - runtime-light/component/init-functions.cpp | 69 ++++++++ runtime-light/component/init-functions.h | 16 ++ runtime-light/stdlib/array/array-functions.h | 15 +- runtime-light/stdlib/fork/fork-context.h | 2 +- runtime-light/stdlib/fork/fork-functions.h | 8 +- .../stdlib/job-worker/job-worker-api.cpp | 150 ++++++++++++++++++ .../stdlib/job-worker/job-worker-api.h | 36 +++++ .../stdlib/job-worker/job-worker-context.cpp | 16 ++ .../stdlib/job-worker/job-worker-context.h | 29 ++++ runtime-light/stdlib/job-worker/job-worker.h | 87 ++++++++++ runtime-light/stdlib/rpc/rpc-api.h | 6 +- runtime-light/stdlib/stdlib.cmake | 37 +++-- runtime-light/tl/tl-core.h | 21 ++- runtime-light/tl/tl-functions.cpp | 48 ++++++ runtime-light/tl/tl-functions.h | 29 ++++ runtime-light/tl/tl-types.cpp | 41 +++++ runtime-light/tl/tl-types.h | 25 +++ runtime-light/tl/tl.cmake | 6 +- tests/k2-components/test_job_worker.php | 12 ++ tests/k2-components/test_job_worker_multi.php | 18 +++ .../k2-components/test_job_worker_noreply.php | 11 ++ .../3_mutable_shared_memory_piece.php | 2 +- ..._not_single_shared_memory_piece_member.php | 2 +- 32 files changed, 707 insertions(+), 145 deletions(-) create mode 100644 builtin-functions/kphp-light/job-workers.txt delete mode 100644 builtin-functions/kphp-light/unsupported/job-worker.txt create mode 100644 runtime-light/component/init-functions.cpp create mode 100644 runtime-light/component/init-functions.h create mode 100644 runtime-light/stdlib/job-worker/job-worker-api.cpp create mode 100644 runtime-light/stdlib/job-worker/job-worker-api.h create mode 100644 runtime-light/stdlib/job-worker/job-worker-context.cpp create mode 100644 runtime-light/stdlib/job-worker/job-worker-context.h create mode 100644 runtime-light/stdlib/job-worker/job-worker.h create mode 100644 runtime-light/tl/tl-functions.cpp create mode 100644 runtime-light/tl/tl-functions.h create mode 100644 runtime-light/tl/tl-types.cpp create mode 100644 runtime-light/tl/tl-types.h create mode 100644 tests/k2-components/test_job_worker.php create mode 100644 tests/k2-components/test_job_worker_multi.php create mode 100644 tests/k2-components/test_job_worker_noreply.php diff --git a/builtin-functions/kphp-light/functions.txt b/builtin-functions/kphp-light/functions.txt index 82a04abe61..7b7e75f2e8 100644 --- a/builtin-functions/kphp-light/functions.txt +++ b/builtin-functions/kphp-light/functions.txt @@ -1,5 +1,7 @@ | false; + +/** @kphp-extern-func-info interruptible */ +function kphp_job_worker_start_no_reply(string $request, float $timeout): bool; + +/** @kphp-extern-func-info interruptible */ +function kphp_job_worker_start_multi(string[] $request, float $timeout): (future | false)[]; + +/** @kphp-extern-func-info interruptible */ +function kphp_job_worker_fetch_request(): string; + +/** @kphp-extern-func-info interruptible */ +function kphp_job_worker_store_response(string $response): int; + +function is_kphp_job_workers_enabled(): bool; + +function get_job_workers_number(): int; + diff --git a/builtin-functions/kphp-light/unsupported-functions.txt b/builtin-functions/kphp-light/unsupported-functions.txt index 021f080ebc..bc71685454 100644 --- a/builtin-functions/kphp-light/unsupported-functions.txt +++ b/builtin-functions/kphp-light/unsupported-functions.txt @@ -7,7 +7,6 @@ require_once __DIR__ . '/unsupported/error.txt'; require_once __DIR__ . '/unsupported/file.txt'; require_once __DIR__ . '/unsupported/fork.txt'; require_once __DIR__ . '/unsupported/hash.txt'; -require_once __DIR__ . '/unsupported/job-worker.txt'; require_once __DIR__ . '/unsupported/kml.txt'; require_once __DIR__ . '/unsupported/kphp-toggles.txt'; require_once __DIR__ . '/unsupported/kphp-tracing.txt'; diff --git a/builtin-functions/kphp-light/unsupported/job-worker.txt b/builtin-functions/kphp-light/unsupported/job-worker.txt deleted file mode 100644 index 3b370da193..0000000000 --- a/builtin-functions/kphp-light/unsupported/job-worker.txt +++ /dev/null @@ -1,52 +0,0 @@ - | false; -/** @kphp-extern-func-info generate-stub */ -function kphp_job_worker_start_no_reply(KphpJobWorkerRequest $request, float $timeout) ::: bool; -/** @kphp-extern-func-info generate-stub */ -function kphp_job_worker_start_multi(KphpJobWorkerRequest[] $request, float $timeout) ::: (future | false)[]; -/** @kphp-extern-func-info generate-stub */ -function kphp_job_worker_store_response(KphpJobWorkerResponse $response) ::: int; - diff --git a/runtime-core/class-instance/refcountable-php-classes.h b/runtime-core/class-instance/refcountable-php-classes.h index 92db4d16a7..04c54f164c 100644 --- a/runtime-core/class-instance/refcountable-php-classes.h +++ b/runtime-core/class-instance/refcountable-php-classes.h @@ -21,7 +21,7 @@ class abstract_refcountable_php_interface : public ScriptAllocatorManaged { virtual void *get_instance_data_raw_ptr() noexcept = 0; }; -template +template class refcountable_polymorphic_php_classes : public Bases... { public: void add_ref() noexcept final { @@ -55,7 +55,7 @@ class refcountable_polymorphic_php_classes : public Bases... { uint32_t refcnt{0}; }; -template +template class refcountable_polymorphic_php_classes_virt : public virtual abstract_refcountable_php_interface, public Interfaces... { public: refcountable_polymorphic_php_classes_virt() __attribute__((always_inline)) = default; @@ -98,7 +98,7 @@ class refcountable_polymorphic_php_classes_virt<> : public virtual abstract_refc }; template -class refcountable_php_classes : public ScriptAllocatorManaged { +class refcountable_php_classes : public ScriptAllocatorManaged { public: void add_ref() noexcept { if (refcnt < ExtraRefCnt::for_global_const) { @@ -133,6 +133,7 @@ class refcountable_php_classes : public ScriptAllocatorManaged { void *get_instance_data_raw_ptr() noexcept { return this; } + private: uint32_t refcnt{0}; }; @@ -144,6 +145,6 @@ class refcountable_empty_php_classes { }; struct may_be_mixed_base : public virtual abstract_refcountable_php_interface { - virtual ~may_be_mixed_base() = default; + ~may_be_mixed_base() override = default; virtual const char *get_class() const noexcept = 0; }; diff --git a/runtime-light/component/component.cmake b/runtime-light/component/component.cmake index e67a81992e..01c9c5ede4 100644 --- a/runtime-light/component/component.cmake +++ b/runtime-light/component/component.cmake @@ -1 +1 @@ -prepend(RUNTIME_COMPONENT_SRC component/ component.cpp) +prepend(RUNTIME_COMPONENT_SRC component/ component.cpp init-functions.cpp) diff --git a/runtime-light/component/component.cpp b/runtime-light/component/component.cpp index 161c8146ec..6a58fcbe4d 100644 --- a/runtime-light/component/component.cpp +++ b/runtime-light/component/component.cpp @@ -10,49 +10,19 @@ #include #include "runtime-core/utils/kphp-assert-core.h" +#include "runtime-light/component/init-functions.h" #include "runtime-light/core/globals/php-init-scripts.h" -#include "runtime-light/coroutine/awaitable.h" #include "runtime-light/coroutine/task.h" #include "runtime-light/header.h" #include "runtime-light/scheduler/scheduler.h" +#include "runtime-light/stdlib/job-worker/job-worker-context.h" #include "runtime-light/streams/streams.h" #include "runtime-light/utils/context.h" -#include "runtime-light/utils/json-functions.h" namespace { -constexpr uint32_t K2_INVOKE_HTTP_MAGIC = 0xd909efe8; -constexpr uint32_t K2_INVOKE_JOB_WORKER_MAGIC = 0x437d7312; - -void init_http_superglobals(const string &http_query) noexcept { +int32_t merge_output_buffers() noexcept { auto &component_ctx{*get_component_context()}; - component_ctx.php_script_mutable_globals_singleton.get_superglobals().v$_SERVER.set_value(string{"QUERY_TYPE"}, string{"http"}); - component_ctx.php_script_mutable_globals_singleton.get_superglobals().v$_POST = f$json_decode(http_query, true); -} - -task_t init_kphp_cli_component() noexcept { - co_return co_await wait_for_incoming_stream_t{}; -} - -task_t init_kphp_server_component() noexcept { - uint32_t magic{}; - const auto stream_d{co_await wait_for_incoming_stream_t{}}; - const auto read{co_await read_exact_from_stream(stream_d, reinterpret_cast(std::addressof(magic)), sizeof(uint32_t))}; - php_assert(read == sizeof(uint32_t)); - if (magic == K2_INVOKE_HTTP_MAGIC) { - const auto [buffer, size]{co_await read_all_from_stream(stream_d)}; - init_http_superglobals(string{buffer, static_cast(size)}); - get_platform_context()->allocator.free(buffer); - } else if (magic == K2_INVOKE_JOB_WORKER_MAGIC) { - php_error("not implemented"); - } else { - php_error("server got unexpected type of request: 0x%x", magic); - } - - co_return stream_d; -} - -int32_t merge_output_buffers(ComponentState &component_ctx) noexcept { Response &response{component_ctx.response}; php_assert(response.current_buffer >= 0); @@ -95,12 +65,16 @@ task_t ComponentState::run_component_epilogue() noexcept { if (component_kind_ == ComponentKind::Oneshot || component_kind_ == ComponentKind::Multishot) { co_return; } + // do not flush output buffers if we are in job worker + if (JobWorkerServerComponentContext::get().kind != JobWorkerServerComponentContext::Kind::Invalid) { + co_return; + } if (standard_stream() == INVALID_PLATFORM_DESCRIPTOR) { poll_status = PollStatus::PollFinishedError; co_return; } - const auto &buffer{response.output_buffers[merge_output_buffers(*this)]}; + const auto &buffer{response.output_buffers[merge_output_buffers()]}; if ((co_await write_all_to_stream(standard_stream(), buffer.buffer(), buffer.size())) != buffer.size()) { php_warning("can't write component result to stream %" PRIu64, standard_stream()); } diff --git a/runtime-light/component/component.h b/runtime-light/component/component.h index b3c48246dc..27cfb1cff3 100644 --- a/runtime-light/component/component.h +++ b/runtime-light/component/component.h @@ -17,6 +17,7 @@ #include "runtime-light/header.h" #include "runtime-light/scheduler/scheduler.h" #include "runtime-light/stdlib/fork/fork-context.h" +#include "runtime-light/stdlib/job-worker/job-worker-context.h" #include "runtime-light/stdlib/output/output-buffer.h" #include "runtime-light/stdlib/regex/regex-functions.h" #include "runtime-light/stdlib/curl/curl.h" @@ -63,6 +64,10 @@ struct ComponentState { task_t run_component_epilogue() noexcept; + ComponentKind component_kind() const noexcept { + return component_kind_; + } + void process_platform_updates() noexcept; bool stream_updated(uint64_t stream_d) const noexcept { @@ -94,6 +99,8 @@ struct ComponentState { KphpCoreContext kphp_core_context; RpcComponentContext rpc_component_context; + JobWorkerClientComponentContext job_worker_client_component_context{}; + JobWorkerServerComponentContext job_worker_server_component_context{}; RegexComponentState regex_component_context; CurlComponentState curl_component_state; diff --git a/runtime-light/component/image.h b/runtime-light/component/image.h index 40025b9c40..0dc98eb2b5 100644 --- a/runtime-light/component/image.h +++ b/runtime-light/component/image.h @@ -4,7 +4,6 @@ #pragma once -#include "runtime-light/header.h" #include "runtime-light/stdlib/rpc/rpc-context.h" struct ImageState { diff --git a/runtime-light/component/init-functions.cpp b/runtime-light/component/init-functions.cpp new file mode 100644 index 0000000000..e85b9982c6 --- /dev/null +++ b/runtime-light/component/init-functions.cpp @@ -0,0 +1,69 @@ +// Compiler for PHP (aka KPHP) +// Copyright (c) 2024 LLC «V Kontakte» +// Distributed under the GPL v3 License, see LICENSE.notice.txt + +#include "runtime-light/component/init-functions.h" + +#include + +#include "runtime-core/runtime-core.h" +#include "runtime-core/utils/kphp-assert-core.h" +#include "runtime-light/component/component.h" +#include "runtime-light/coroutine/awaitable.h" +#include "runtime-light/coroutine/task.h" +#include "runtime-light/header.h" +#include "runtime-light/stdlib/job-worker/job-worker-context.h" +#include "runtime-light/streams/streams.h" +#include "runtime-light/tl/tl-core.h" +#include "runtime-light/tl/tl-functions.h" +#include "runtime-light/utils/context.h" + +namespace { + +void process_k2_invoke_job_worker(tl::TLBuffer &tlb) noexcept { + tl::K2InvokeJobWorker invoke_jw{}; + if (!invoke_jw.fetch(tlb)) { + php_error("erroneous job worker request"); + } + php_assert(invoke_jw.image_id == vk_k2_describe()->build_timestamp); // ensure that we got the request from ourselves + + auto &jw_server_ctx{JobWorkerServerComponentContext::get()}; + jw_server_ctx.kind = invoke_jw.ignore_answer ? JobWorkerServerComponentContext::Kind::NoReply : JobWorkerServerComponentContext::Kind::Regular; + jw_server_ctx.state = JobWorkerServerComponentContext::State::Working; + jw_server_ctx.job_id = invoke_jw.job_id; + jw_server_ctx.body = std::move(invoke_jw.body); + get_component_context()->php_script_mutable_globals_singleton.get_superglobals().v$_SERVER.set_value(string{"JOB_ID"}, invoke_jw.job_id); +} + +void process_k2_invoke_http([[maybe_unused]] tl::TLBuffer &tlb) noexcept {} + +} // namespace + +task_t init_kphp_server_component() noexcept { + auto stream_d{co_await wait_for_incoming_stream_t{}}; + const auto [buffer, size]{co_await read_all_from_stream(stream_d)}; + php_assert(size >= sizeof(uint32_t)); // check that we can fetch at least magic + tl::TLBuffer tlb{}; + tlb.store_bytes(buffer, static_cast(size)); + get_platform_context()->allocator.free(buffer); + + switch (const auto magic{*reinterpret_cast(tlb.data())}) { // lookup magic + case tl::K2_INVOKE_HTTP_MAGIC: { + process_k2_invoke_http(tlb); + break; + } + case tl::K2_INVOKE_JOB_WORKER_MAGIC: { + process_k2_invoke_job_worker(tlb); + // release standard stream in case of a no reply job worker since we don't need that stream anymore + if (JobWorkerServerComponentContext::get().kind == JobWorkerServerComponentContext::Kind::NoReply) { + get_component_context()->release_stream(stream_d); + stream_d = INVALID_PLATFORM_DESCRIPTOR; + } + break; + } + default: { + php_error("unexpected magic: 0x%x", magic); + } + } + co_return stream_d; +} diff --git a/runtime-light/component/init-functions.h b/runtime-light/component/init-functions.h new file mode 100644 index 0000000000..f320fd906b --- /dev/null +++ b/runtime-light/component/init-functions.h @@ -0,0 +1,16 @@ +// 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-light/coroutine/awaitable.h" +#include "runtime-light/coroutine/task.h" + +// Returns a stream descriptor that is supposed to be a stream to stdout +inline task_t init_kphp_cli_component() noexcept { + co_return co_await wait_for_incoming_stream_t{}; +} + +// Performs some initialization and returns a stream descriptor we need to write server response into +task_t init_kphp_server_component() noexcept; diff --git a/runtime-light/stdlib/array/array-functions.h b/runtime-light/stdlib/array/array-functions.h index 77e4d4a29d..65cd664eeb 100644 --- a/runtime-light/stdlib/array/array-functions.h +++ b/runtime-light/stdlib/array/array-functions.h @@ -6,9 +6,9 @@ #include "runtime-core/runtime-core.h" -constexpr int64_t SORT_REGULAR = 0; -constexpr int64_t SORT_NUMERIC = 1; -constexpr int64_t SORT_STRING = 2; +inline constexpr int64_t SORT_REGULAR = 0; +inline constexpr int64_t SORT_NUMERIC = 1; +inline constexpr int64_t SORT_STRING = 2; template string f$implode(const string &s, const array &a) { @@ -60,13 +60,11 @@ array f$array_filter_by_key(const array &a, const T1 &callback) noexcept { php_critical_error("call to unsupported function"); } - template, T>> array f$array_map(const CallbackT &callback, const array &a) { php_critical_error("call to unsupported function"); } - template R f$array_reduce(const array &a, const CallbackT &callback, InitialT initial) { php_critical_error("call to unsupported function"); @@ -104,7 +102,7 @@ T f$array_merge(const T &a1, const T &a2, const T &a3, const T &a4 = T(), const php_critical_error("call to unsupported function"); } -template +template ReturnT f$array_merge_recursive(const Args &...args) { php_critical_error("call to unsupported function"); } @@ -446,7 +444,6 @@ void f$array_swap_int_keys(array &a, int64_t idx1, int64_t idx2) noexcept { php_critical_error("call to unsupported function"); } - template array f$to_array_debug(const class_instance &klass, bool with_class_names = false) { php_critical_error("call to unsupported function"); @@ -517,7 +514,7 @@ inline Optional> f$array_column(const array &a, const mixed } template -auto f$array_column(const Optional &a, const mixed &column_key, const mixed &index_key = {}) -> decltype(f$array_column(std::declval(), column_key, index_key)) { +auto f$array_column(const Optional &a, const mixed &column_key, + const mixed &index_key = {}) -> decltype(f$array_column(std::declval(), column_key, index_key)) { php_critical_error("call to unsupported function"); } - diff --git a/runtime-light/stdlib/fork/fork-context.h b/runtime-light/stdlib/fork/fork-context.h index 153e34fdd0..abe33ae4d3 100644 --- a/runtime-light/stdlib/fork/fork-context.h +++ b/runtime-light/stdlib/fork/fork-context.h @@ -12,7 +12,7 @@ #include "runtime-light/coroutine/task.h" #include "runtime-light/utils/concepts.h" -constexpr int64_t INVALID_FORK_ID = -1; +inline constexpr int64_t INVALID_FORK_ID = -1; class ForkComponentContext { template diff --git a/runtime-light/stdlib/fork/fork-functions.h b/runtime-light/stdlib/fork/fork-functions.h index 6ee65fa110..5e98a6d6cb 100644 --- a/runtime-light/stdlib/fork/fork-functions.h +++ b/runtime-light/stdlib/fork/fork-functions.h @@ -17,10 +17,10 @@ namespace fork_api_impl_ { -constexpr double MAX_TIMEOUT_S = 86400.0; -constexpr double DEFAULT_TIMEOUT_S = MAX_TIMEOUT_S; -constexpr auto MAX_TIMEOUT_NS = std::chrono::duration_cast(std::chrono::duration{MAX_TIMEOUT_S}); -constexpr auto DEFAULT_TIMEOUT_NS = std::chrono::duration_cast(std::chrono::duration{DEFAULT_TIMEOUT_S}); +inline constexpr double MAX_TIMEOUT_S = 86400.0; +inline constexpr double DEFAULT_TIMEOUT_S = MAX_TIMEOUT_S; +inline constexpr auto MAX_TIMEOUT_NS = std::chrono::duration_cast(std::chrono::duration{MAX_TIMEOUT_S}); +inline constexpr auto DEFAULT_TIMEOUT_NS = std::chrono::duration_cast(std::chrono::duration{DEFAULT_TIMEOUT_S}); } // namespace fork_api_impl_ diff --git a/runtime-light/stdlib/job-worker/job-worker-api.cpp b/runtime-light/stdlib/job-worker/job-worker-api.cpp new file mode 100644 index 0000000000..6fee43a6dc --- /dev/null +++ b/runtime-light/stdlib/job-worker/job-worker-api.cpp @@ -0,0 +1,150 @@ +// 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/job-worker/job-worker-api.h" + +#include +#include +#include +#include +#include + +#include "runtime-core/runtime-core.h" +#include "runtime-core/utils/kphp-assert-core.h" +#include "runtime-light/component/component.h" +#include "runtime-light/coroutine/awaitable.h" +#include "runtime-light/coroutine/task.h" +#include "runtime-light/header.h" +#include "runtime-light/stdlib/component/component-api.h" +#include "runtime-light/stdlib/fork/fork-context.h" +#include "runtime-light/stdlib/job-worker/job-worker-context.h" +#include "runtime-light/stdlib/job-worker/job-worker.h" +#include "runtime-light/streams/streams.h" +#include "runtime-light/tl/tl-core.h" +#include "runtime-light/tl/tl-functions.h" +#include "runtime-light/tl/tl-types.h" +#include "runtime-light/utils/context.h" + +namespace { + +constexpr const char *JOB_WORKER_COMPONENT_NAME = "_self"; + +constexpr double MIN_TIMEOUT_S = 0.05; +constexpr double MAX_TIMEOUT_S = 86400.0; + +task_t kphp_job_worker_start_impl(string request, double timeout, bool ignore_answer) noexcept { + if (!f$is_kphp_job_workers_enabled()) { + php_warning("can't start job worker: job workers are disabled"); + co_return INVALID_FORK_ID; + } + if (request.empty()) { + php_warning("job worker request is empty"); + co_return INVALID_FORK_ID; + } + + auto &jw_client_ctx{JobWorkerClientComponentContext::get()}; + // normalize timeout + const auto timeout_ns{std::chrono::duration_cast(std::chrono::duration(std::clamp(timeout, MIN_TIMEOUT_S, MAX_TIMEOUT_S)))}; + // prepare JW component request + tl::TLBuffer tlb{}; + const tl::K2InvokeJobWorker invoke_jw{.image_id = vk_k2_describe()->build_timestamp, + .job_id = jw_client_ctx.current_job_id++, + .ignore_answer = ignore_answer, + .timeout_ns = static_cast(timeout_ns.count()), + .body = std::move(request)}; + invoke_jw.store(tlb); + + // send JW request + auto comp_query{co_await f$component_client_send_request(string{JOB_WORKER_COMPONENT_NAME}, string{tlb.data(), static_cast(tlb.size())})}; + if (comp_query.is_null()) { + php_warning("couldn't start job worker"); + co_return INVALID_FORK_ID; + } + // create fork to wait for job worker response. we need to do it even if 'ignore_answer' is 'true' to make sure + // that the stream will not be closed too early. otherwise, platform may even not send job worker request + auto waiter_task{[](auto comp_query, std::chrono::nanoseconds timeout) noexcept -> task_t { + auto fetch_task{f$component_client_fetch_response(std::move(comp_query))}; + const string response{(co_await wait_with_timeout_t{task_t::awaiter_t{std::addressof(fetch_task)}, timeout}).value_or(string{})}; + + tl::TLBuffer tlb{}; + tlb.store_bytes(response.c_str(), static_cast(response.size())); + tl::K2JobWorkerResponse jw_response{}; + if (!jw_response.fetch(tlb)) { + co_return string{}; + } + co_return std::move(jw_response.body); + }(std::move(comp_query), timeout_ns)}; + // start waiter fork and return its ID + co_return(co_await start_fork_t{static_cast>(std::move(waiter_task)), start_fork_t::execution::self}); +} + +} // namespace + +// ================================================================================================ + +task_t> f$kphp_job_worker_start(string request, double timeout) noexcept { + const auto fork_id{co_await kphp_job_worker_start_impl(std::move(request), timeout, false)}; + co_return fork_id != INVALID_FORK_ID ? fork_id : false; +} + +task_t f$kphp_job_worker_start_no_reply(string request, double timeout) noexcept { + const auto fork_id{co_await kphp_job_worker_start_impl(std::move(request), timeout, true)}; + co_return fork_id != INVALID_FORK_ID; +} + +task_t>> f$kphp_job_worker_start_multi(array requests, double timeout) noexcept { + array> fork_ids{requests.size()}; + for (const auto &it : requests) { + const auto fork_id{co_await kphp_job_worker_start_impl(it.get_value(), timeout, false)}; + fork_ids.set_value(it.get_key(), fork_id != INVALID_FORK_ID ? fork_id : false); + } + co_return fork_ids; +} + +// ================================================================================================ + +task_t f$kphp_job_worker_fetch_request() noexcept { + if (!f$is_kphp_job_workers_enabled()) { + php_warning("couldn't fetch job worker request: job workers are disabled"); + co_return string{}; + } + + auto &jw_server_ctx{JobWorkerServerComponentContext::get()}; + if (jw_server_ctx.job_id == JOB_WORKER_INVALID_JOB_ID || jw_server_ctx.body.empty()) { + php_warning("couldn't fetch job worker request"); + co_return string{}; + } + co_return std::exchange(jw_server_ctx.body, string{}); +} + +task_t f$kphp_job_worker_store_response(string response) noexcept { + auto &component_ctx{*get_component_context()}; + auto &jw_server_ctx{JobWorkerServerComponentContext::get()}; + if (!f$is_kphp_job_workers_enabled()) { // workers are enabled + php_warning("couldn't store job worker response: job workers are disabled"); + co_return static_cast(JobWorkerError::store_response_incorrect_call_error); + } else if (jw_server_ctx.kind != JobWorkerServerComponentContext::Kind::Regular) { // we're in regular worker + php_warning("couldn't store job worker response: we are either in no reply job worker or not in a job worker at all"); + co_return static_cast(JobWorkerError::store_response_incorrect_call_error); + } else if (jw_server_ctx.state == JobWorkerServerComponentContext::State::Replied) { // it's the first attempt to reply + php_warning("couldn't store job worker response: multiple stores are forbidden"); + co_return static_cast(JobWorkerError::store_response_incorrect_call_error); + } else if (component_ctx.standard_stream() == INVALID_PLATFORM_DESCRIPTOR) { // we have a stream to write into + php_warning("couldn't store job worker response: no standard stream"); + co_return static_cast(JobWorkerError::store_response_incorrect_call_error); + } else if (response.empty()) { // we have a response to reply + php_warning("couldn't store job worker response: it shouldn't be empty"); + co_return static_cast(JobWorkerError::store_response_incorrect_call_error); + } + + tl::TLBuffer tlb{}; + tl::K2JobWorkerResponse jw_response{.job_id = jw_server_ctx.job_id, .body = std::move(response)}; + jw_response.store(tlb); + if ((co_await write_all_to_stream(component_ctx.standard_stream(), tlb.data(), tlb.size())) != tlb.size()) { + php_warning("couldn't store job worker response"); + co_return static_cast(JobWorkerError::store_response_cant_send_error); + } + jw_server_ctx.state = JobWorkerServerComponentContext::State::Replied; + co_return 0; +} diff --git a/runtime-light/stdlib/job-worker/job-worker-api.h b/runtime-light/stdlib/job-worker/job-worker-api.h new file mode 100644 index 0000000000..47b21973c6 --- /dev/null +++ b/runtime-light/stdlib/job-worker/job-worker-api.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 "runtime-core/runtime-core.h" +#include "runtime-light/component/component.h" +#include "runtime-light/coroutine/task.h" +#include "runtime-light/utils/context.h" + +// === Client ===================================================================================== + +task_t> f$kphp_job_worker_start(string request, double timeout) noexcept; + +task_t f$kphp_job_worker_start_no_reply(string request, double timeout) noexcept; + +task_t>> f$kphp_job_worker_start_multi(array requests, double timeout) noexcept; + +// === Server ===================================================================================== + +task_t f$kphp_job_worker_fetch_request() noexcept; + +task_t f$kphp_job_worker_store_response(string response) noexcept; + +// === Misc ======================================================================================= + +inline bool f$is_kphp_job_workers_enabled() noexcept { + return get_component_context()->component_kind() == ComponentKind::Server; +} + +inline int64_t f$get_job_workers_number() noexcept { + return 50; // TODO +} diff --git a/runtime-light/stdlib/job-worker/job-worker-context.cpp b/runtime-light/stdlib/job-worker/job-worker-context.cpp new file mode 100644 index 0000000000..35b3db42d6 --- /dev/null +++ b/runtime-light/stdlib/job-worker/job-worker-context.cpp @@ -0,0 +1,16 @@ +// 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/job-worker/job-worker-context.h" + +#include "runtime-light/component/component.h" +#include "runtime-light/utils/context.h" + +JobWorkerServerComponentContext &JobWorkerServerComponentContext::get() noexcept { + return get_component_context()->job_worker_server_component_context; +} + +JobWorkerClientComponentContext &JobWorkerClientComponentContext::get() noexcept { + return get_component_context()->job_worker_client_component_context; +} diff --git a/runtime-light/stdlib/job-worker/job-worker-context.h b/runtime-light/stdlib/job-worker/job-worker-context.h new file mode 100644 index 0000000000..f0f27aa79c --- /dev/null +++ b/runtime-light/stdlib/job-worker/job-worker-context.h @@ -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 + +#pragma once + +#include + +#include "common/mixin/not_copyable.h" +#include "runtime-core/runtime-core.h" +#include "runtime-light/stdlib/job-worker/job-worker.h" + +struct JobWorkerServerComponentContext final : private vk::not_copyable { + enum class Kind : uint8_t { Invalid, Regular, NoReply }; + enum class State : uint8_t { Invalid, Working, Replied }; + + Kind kind{Kind::Invalid}; + State state{State::Invalid}; + int64_t job_id{JOB_WORKER_INVALID_JOB_ID}; + string body; + + static JobWorkerServerComponentContext &get() noexcept; +}; + +struct JobWorkerClientComponentContext final : private vk::not_copyable { + int64_t current_job_id{JOB_WORKER_VALID_JOB_ID_RANGE_START}; + + static JobWorkerClientComponentContext &get() noexcept; +}; diff --git a/runtime-light/stdlib/job-worker/job-worker.h b/runtime-light/stdlib/job-worker/job-worker.h new file mode 100644 index 0000000000..e356d71132 --- /dev/null +++ b/runtime-light/stdlib/job-worker/job-worker.h @@ -0,0 +1,87 @@ +// 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 + +#include "runtime-core/class-instance/refcountable-php-classes.h" +#include "runtime-core/runtime-core.h" + +inline constexpr int64_t JOB_WORKER_VALID_JOB_ID_RANGE_START = 0; +inline constexpr int64_t JOB_WORKER_INVALID_JOB_ID = -1; + +namespace job_worker_impl_ { + +struct SendableBase : virtual abstract_refcountable_php_interface { + virtual const char *get_class() const noexcept = 0; + virtual int32_t get_hash() const noexcept = 0; + virtual size_t virtual_builtin_sizeof() const noexcept = 0; + virtual SendableBase *virtual_builtin_clone() const noexcept = 0; + + ~SendableBase() override = default; +}; + +} // namespace job_worker_impl_ + +enum class JobWorkerError : int16_t { + store_response_incorrect_call_error = -3000, + store_response_cant_send_error = -3003, +}; + +// === KphpJobWorkerSharedMemoryPiece ============================================================= + +struct C$KphpJobWorkerSharedMemoryPiece : public job_worker_impl_::SendableBase { + C$KphpJobWorkerSharedMemoryPiece *virtual_builtin_clone() const noexcept override = 0; +}; + +// === KphpJobWorkerRequest ======================================================================= + +struct C$KphpJobWorkerRequest : public job_worker_impl_::SendableBase { + C$KphpJobWorkerRequest *virtual_builtin_clone() const noexcept override = 0; +}; + +// === KphpJobWorkerResponse ====================================================================== + +struct C$KphpJobWorkerResponse : public job_worker_impl_::SendableBase { + C$KphpJobWorkerResponse *virtual_builtin_clone() const noexcept override = 0; +}; + +// === KphpJobWorkerResponseError ================================================================= + +struct C$KphpJobWorkerResponseError : public refcountable_polymorphic_php_classes { + string error; + int64_t error_code; + + const char *get_class() const noexcept override { + return "KphpJobWorkerResponseError"; + } + + int32_t get_hash() const noexcept override { + return static_cast(std::hash{}(get_class())); + } + + size_t virtual_builtin_sizeof() const noexcept override { + return sizeof(*this); + } + + C$KphpJobWorkerResponseError *virtual_builtin_clone() const noexcept override { + return new C$KphpJobWorkerResponseError{*this}; + } +}; + +inline class_instance f$KphpJobWorkerResponseError$$__construct(class_instance v$this) noexcept { + return v$this; +} + +inline string f$KphpJobWorkerResponseError$$getError(class_instance v$this) noexcept { + return v$this.get()->error; +} + +inline int64_t f$KphpJobWorkerResponseError$$getErrorCode(class_instance v$this) noexcept { + return v$this.get()->error_code; +} diff --git a/runtime-light/stdlib/rpc/rpc-api.h b/runtime-light/stdlib/rpc/rpc-api.h index 22e2f3b3aa..1fedd4208a 100644 --- a/runtime-light/stdlib/rpc/rpc-api.h +++ b/runtime-light/stdlib/rpc/rpc-api.h @@ -14,9 +14,9 @@ #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; +inline constexpr int64_t RPC_VALID_QUERY_ID_RANGE_START = 0; +inline constexpr int64_t RPC_INVALID_QUERY_ID = -1; +inline constexpr int64_t RPC_IGNORED_ANSWER_QUERY_ID = -2; namespace rpc_impl_ { diff --git a/runtime-light/stdlib/stdlib.cmake b/runtime-light/stdlib/stdlib.cmake index 32caac39d2..abefe33df5 100644 --- a/runtime-light/stdlib/stdlib.cmake +++ b/runtime-light/stdlib/stdlib.cmake @@ -1,18 +1,21 @@ prepend( - RUNTIME_STDLIB_SRC - stdlib/ - component/component-api.cpp - curl/curl.cpp - exit/exit-functions.cpp - fork/fork-context.cpp - output/output-buffer.cpp - output/print-functions.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 - string/concat.cpp - regex/regex-functions.cpp) + RUNTIME_STDLIB_SRC + stdlib/ + component/component-api.cpp + crypto/crypto-functions.cpp + curl/curl.cpp + exit/exit-functions.cpp + fork/fork-context.cpp + job-worker/job-worker-api.cpp + job-worker/job-worker-context.cpp + output/output-buffer.cpp + output/print-functions.cpp + regex/regex-functions.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 + string/concat.cpp) diff --git a/runtime-light/tl/tl-core.h b/runtime-light/tl/tl-core.h index ba179db59c..68467eff10 100644 --- a/runtime-light/tl/tl-core.h +++ b/runtime-light/tl/tl-core.h @@ -7,7 +7,6 @@ #include #include #include -#include #include "common/mixin/not_copyable.h" #include "runtime-core/runtime-core.h" @@ -15,16 +14,17 @@ #include "runtime-light/utils/concepts.h" namespace tl { -constexpr auto SMALL_STRING_SIZE_LEN = 1; -constexpr auto MEDIUM_STRING_SIZE_LEN = 3; -constexpr auto LARGE_STRING_SIZE_LEN = 7; -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; +inline constexpr auto SMALL_STRING_SIZE_LEN = 1; +inline constexpr auto MEDIUM_STRING_SIZE_LEN = 3; +inline constexpr auto LARGE_STRING_SIZE_LEN = 7; -constexpr uint8_t LARGE_STRING_MAGIC = 0xff; -constexpr uint8_t MEDIUM_STRING_MAGIC = 0xfe; +inline constexpr uint64_t SMALL_STRING_MAX_LEN = 253; +inline constexpr uint64_t MEDIUM_STRING_MAX_LEN = (static_cast(1) << 24) - 1; +[[maybe_unused]] inline constexpr uint64_t LARGE_STRING_MAX_LEN = (static_cast(1) << 56) - 1; + +inline constexpr uint8_t LARGE_STRING_MAGIC = 0xff; +inline constexpr uint8_t MEDIUM_STRING_MAGIC = 0xfe; class TLBuffer : private vk::not_copyable { string_buffer m_buffer; @@ -74,8 +74,7 @@ class TLBuffer : private vk::not_copyable { } template - requires std::convertible_to - void store_trivial(const U &t) noexcept { + requires std::convertible_to void store_trivial(const U &t) noexcept { // Here we rely on that endianness of architecture is Little Endian store_bytes(reinterpret_cast(std::addressof(t)), sizeof(T)); } diff --git a/runtime-light/tl/tl-functions.cpp b/runtime-light/tl/tl-functions.cpp new file mode 100644 index 0000000000..f283afcda1 --- /dev/null +++ b/runtime-light/tl/tl-functions.cpp @@ -0,0 +1,48 @@ +// 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-functions.h" + +#include +#include + +#include "runtime-light/tl/tl-core.h" + +namespace { + +// magic + flags + image_id + job_id + minimum string size length +constexpr auto K2_INVOKE_JW_MIN_SIZE = sizeof(uint32_t) + sizeof(uint32_t) + sizeof(uint64_t) + sizeof(int64_t) + sizeof(uint64_t) + tl::SMALL_STRING_SIZE_LEN; + +constexpr auto K2_JOB_WORKER_IGNORE_ANSWER_FLAG = static_cast(1U << 0U); + +} // namespace + +namespace tl { + +bool K2InvokeJobWorker::fetch(TLBuffer &tlb) noexcept { + if (tlb.size() < K2_INVOKE_JW_MIN_SIZE || *tlb.fetch_trivial() != K2_INVOKE_JOB_WORKER_MAGIC) { + return false; + } + + const auto flags{*tlb.fetch_trivial()}; + ignore_answer = static_cast(flags & K2_JOB_WORKER_IGNORE_ANSWER_FLAG); + image_id = *tlb.fetch_trivial(); + job_id = *tlb.fetch_trivial(); + timeout_ns = *tlb.fetch_trivial(); + const std::string_view body_view{tlb.fetch_string()}; + body = string{body_view.data(), static_cast(body_view.size())}; + return true; +} + +void K2InvokeJobWorker::store(TLBuffer &tlb) const noexcept { + const uint32_t flags{ignore_answer ? K2_JOB_WORKER_IGNORE_ANSWER_FLAG : 0x0}; + tlb.store_trivial(K2_INVOKE_JOB_WORKER_MAGIC); + tlb.store_trivial(flags); + tlb.store_trivial(image_id); + tlb.store_trivial(job_id); + tlb.store_trivial(timeout_ns); + tlb.store_string({body.c_str(), body.size()}); +} + +} // namespace tl diff --git a/runtime-light/tl/tl-functions.h b/runtime-light/tl/tl-functions.h new file mode 100644 index 0000000000..8510875603 --- /dev/null +++ b/runtime-light/tl/tl-functions.h @@ -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 + +#pragma once + +#include + +#include "runtime-core/runtime-core.h" +#include "runtime-light/tl/tl-core.h" + +namespace tl { + +inline constexpr uint32_t K2_INVOKE_HTTP_MAGIC = 0xd909efe8; +inline constexpr uint32_t K2_INVOKE_JOB_WORKER_MAGIC = 0x437d7312; + +struct K2InvokeJobWorker final { + uint64_t image_id{}; + int64_t job_id{}; + bool ignore_answer{}; + uint64_t timeout_ns{}; + string body; + + bool fetch(TLBuffer &tlb) noexcept; + + void store(TLBuffer &tlb) const noexcept; +}; + +} // namespace tl diff --git a/runtime-light/tl/tl-types.cpp b/runtime-light/tl/tl-types.cpp new file mode 100644 index 0000000000..b742cf816a --- /dev/null +++ b/runtime-light/tl/tl-types.cpp @@ -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 + +#include "runtime-light/tl/tl-types.h" + +#include +#include +#include + +#include "runtime-light/tl/tl-core.h" + +namespace { + +// magic + flags + job_id + minimum string size length +constexpr auto K2_JOB_WORKER_RESPONSE_MIN_SIZE = sizeof(uint32_t) + sizeof(uint32_t) + sizeof(int64_t) + tl::SMALL_STRING_SIZE_LEN; + +} // namespace + +namespace tl { + +bool K2JobWorkerResponse::fetch(TLBuffer &tlb) noexcept { + if (tlb.size() < K2_JOB_WORKER_RESPONSE_MIN_SIZE || *tlb.fetch_trivial() != K2_JOB_WORKER_RESPONSE_MAGIC) { + return false; + } + + std::ignore = tlb.fetch_trivial(); // ignore flags + job_id = *tlb.fetch_trivial(); + const std::string_view body_view{tlb.fetch_string()}; + body = string{body_view.data(), static_cast(body_view.size())}; + return true; +} + +void K2JobWorkerResponse::store(TLBuffer &tlb) const noexcept { + tlb.store_trivial(K2_JOB_WORKER_RESPONSE_MAGIC); + tlb.store_trivial(0x0); // flags + tlb.store_trivial(job_id); + tlb.store_string({body.c_str(), body.size()}); +} + +} // namespace tl diff --git a/runtime-light/tl/tl-types.h b/runtime-light/tl/tl-types.h new file mode 100644 index 0000000000..8f08359de7 --- /dev/null +++ b/runtime-light/tl/tl-types.h @@ -0,0 +1,25 @@ +// 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/tl/tl-core.h" + +namespace tl { + +inline constexpr uint32_t K2_JOB_WORKER_RESPONSE_MAGIC = 0x3afb3a08; + +struct K2JobWorkerResponse final { + int64_t job_id{}; + string body; + + bool fetch(TLBuffer &tlb) noexcept; + + void store(TLBuffer &tlb) const noexcept; +}; + +} // namespace tl diff --git a/runtime-light/tl/tl.cmake b/runtime-light/tl/tl.cmake index 59a7ae7772..248ed8a18a 100644 --- a/runtime-light/tl/tl.cmake +++ b/runtime-light/tl/tl.cmake @@ -1,4 +1,2 @@ -prepend(RUNTIME_TL_SRC tl/ - tl-builtins.cpp - tl-core.cpp -) +prepend(RUNTIME_TL_SRC tl/ tl-builtins.cpp tl-core.cpp tl-functions.cpp + tl-types.cpp) diff --git a/tests/k2-components/test_job_worker.php b/tests/k2-components/test_job_worker.php new file mode 100644 index 0000000000..2aab3314e9 --- /dev/null +++ b/tests/k2-components/test_job_worker.php @@ -0,0 +1,12 @@ + Date: Thu, 12 Sep 2024 18:00:44 +0300 Subject: [PATCH 45/45] Serialize inheritance (#1067) Allow serialize of inherited classes. --- compiler/code-gen/declarations.cpp | 50 ++++++++++++++----- compiler/pipes/check-classes.cpp | 5 -- compiler/pipes/final-check.cpp | 39 +++++++++++++++ compiler/pipes/final-check.h | 1 + .../howto-by-kphp/serialization-msgpack.md | 6 +++ .../026_serialize_with_base.php | 30 +++++++++++ .../027_serialize_intermediate.php | 34 +++++++++++++ .../028_deserialize_inherited.php | 50 +++++++++++++++++++ .../103_serialize_with_base.php | 2 +- .../113_duplicated_inherited_tags.php | 27 ++++++++++ 10 files changed, 226 insertions(+), 18 deletions(-) create mode 100644 tests/phpt/msgpack_serialize/026_serialize_with_base.php create mode 100644 tests/phpt/msgpack_serialize/027_serialize_intermediate.php create mode 100644 tests/phpt/msgpack_serialize/028_deserialize_inherited.php create mode 100644 tests/phpt/msgpack_serialize/113_duplicated_inherited_tags.php diff --git a/compiler/code-gen/declarations.cpp b/compiler/code-gen/declarations.cpp index b5e6a6f1d5..f1fe0c5318 100644 --- a/compiler/code-gen/declarations.cpp +++ b/compiler/code-gen/declarations.cpp @@ -1147,16 +1147,29 @@ void ClassMembersDefinition::compile_msgpack_serialize(CodeGenerator &W, ClassPt // packer.pack(field_1); // ... //} + std::vector body; uint16_t cnt_fields = 0; - klass->members.for_each([&](ClassMemberInstanceField &field) { - if (field.serialization_tag != -1) { - auto func_name = fmt_format("vk::msgpack::packer_float32_decorator::pack_value{}", field.serialize_as_float32 ? "_float32" : ""); - body.emplace_back(fmt_format("packer.pack({}); {}(packer, ${});", field.serialization_tag, func_name, field.var->name)); - cnt_fields += 2; - } - }); + std::vector klasses; + ClassPtr the_klass = klass; + + while (the_klass) { + klasses.push_back(the_klass); + the_klass = the_klass->parent_class; + } + + std::reverse(klasses.begin(), klasses.end()); + + for (auto &k : klasses) { + k->members.for_each([&](ClassMemberInstanceField &field) { + if (field.serialization_tag != -1) { + auto func_name = fmt_format("vk::msgpack::packer_float32_decorator::pack_value{}", field.serialize_as_float32 ? "_float32" : ""); + body.emplace_back(fmt_format("packer.pack({}); {}(packer, ${});", field.serialization_tag, func_name, field.var->name)); + cnt_fields += 2; + } + }); + } FunctionSignatureGenerator(W).set_const_this() << "void " << klass->src_name << "::msgpack_pack(vk::msgpack::packer &packer)" << BEGIN @@ -1184,12 +1197,25 @@ void ClassMembersDefinition::compile_msgpack_deserialize(CodeGenerator &W, Class // std::vector cases; - klass->members.for_each([&](ClassMemberInstanceField &field) { - if (field.serialization_tag != -1) { - cases.emplace_back(fmt_format("case {}: elem.convert(${}); break;", field.serialization_tag, field.var->name)); - } - }); + std::vector klasses; + + ClassPtr the_klass = klass; + + // Put class' fields along with all parent's fields. + while (the_klass) { + klasses.push_back(the_klass); + the_klass = the_klass->parent_class; + } + std::reverse(klasses.begin(), klasses.end()); + + for (auto &k : klasses) { + k->members.for_each([&](ClassMemberInstanceField &field) { + if (field.serialization_tag != -1) { + cases.emplace_back(fmt_format("case {}: elem.convert(${}); break;", field.serialization_tag, field.var->name)); + } + }); + } cases.emplace_back("default: break;"); W << "void " << klass->src_name << "::msgpack_unpack(const vk::msgpack::object &msgpack_o) " << BEGIN diff --git a/compiler/pipes/check-classes.cpp b/compiler/pipes/check-classes.cpp index 329f33fcdd..947c3440c8 100644 --- a/compiler/pipes/check-classes.cpp +++ b/compiler/pipes/check-classes.cpp @@ -48,10 +48,6 @@ inline void CheckClassesPass::analyze_class(ClassPtr klass) { fmt_format("class {} can be autoloaded, but its file contains some logic (maybe, require_once files with global vars?)\n", klass->as_human_readable())); } - if (klass->is_serializable) { - kphp_error(!klass->parent_class || !klass->parent_class->members.has_any_instance_var(), - "You may not serialize classes which has a parent with fields"); - } } /* @@ -90,7 +86,6 @@ void CheckClassesPass::check_serialized_fields(ClassPtr klass) { return; } - kphp_error_return(klass->is_serializable, fmt_format("you may not use @kphp-serialized-field inside non-serializable klass: {}", klass->name)); if (kphp_serialized_field_tag->value.starts_with("none")) { return; } diff --git a/compiler/pipes/final-check.cpp b/compiler/pipes/final-check.cpp index 3b0e1a2d14..2b7b7dcaa6 100644 --- a/compiler/pipes/final-check.cpp +++ b/compiler/pipes/final-check.cpp @@ -16,6 +16,7 @@ #include "compiler/data/var-data.h" #include "compiler/vertex-util.h" #include "compiler/type-hint.h" +#include "compiler/phpdoc.h" namespace { void check_class_immutableness(ClassPtr klass) { @@ -570,6 +571,7 @@ void FinalCheckPass::on_start() { if (current_function->type == FunctionData::func_class_holder) { check_class_immutableness(current_function->class_id); + check_serialized_fields_hierarchy(current_function->class_id); process_job_worker_class(current_function->class_id); } @@ -1063,3 +1065,40 @@ void FinalCheckPass::check_magic_clone_method(FunctionPtr fun) { kphp_error(!fun->is_resumable, fmt_format("{} method has to be not resumable", fun->as_human_readable())); kphp_error(!fun->can_throw(), fmt_format("{} method should not throw exception", fun->as_human_readable())); } + + +void FinalCheckPass::check_serialized_fields_hierarchy(ClassPtr klass) { + auto the_klass = klass; + // This loop finishes unconditionally since there is NULL klass->parent_class if there is no base class. + while (the_klass) { + + // Inheritance with serialization is allowed if + // * parent class has ho instance field + // * if there are instance fields, class should be marked with kphp-serializable + if (klass->is_serializable) { + kphp_error_return( + (the_klass->members.has_any_instance_var() && the_klass->is_serializable) || !the_klass->members.has_any_instance_var(), + fmt_format("Class {} and all its ancestors must be @kphp-serializable if there are instance fields. Class {} is not.", klass->name, the_klass->name)); + + } + the_klass = the_klass->parent_class; + } + + klass->members.for_each([&](ClassMemberInstanceField &f) { + // Check if there is a field with the same number available above in hierarchy + auto f_tag = f.serialization_tag; + the_klass = klass->parent_class; + + while (the_klass) { + auto same_numbered_field = the_klass->members.find_member([&f_tag](const ClassMemberInstanceField &f) { + return (f.serialization_tag == f_tag) && (f_tag != -1); + }); + + if (same_numbered_field) { + kphp_error_return(false, fmt_format("kphp-serialized-field: field with number {} found in both classes {} and {}", f_tag, the_klass->name, klass->name)); + } + + the_klass = the_klass->parent_class; + } + }); +} diff --git a/compiler/pipes/final-check.h b/compiler/pipes/final-check.h index d792f3920c..04c7b583c5 100644 --- a/compiler/pipes/final-check.h +++ b/compiler/pipes/final-check.h @@ -44,4 +44,5 @@ class FinalCheckPass final : public FunctionPassBase { static void check_instanceof(VertexAdaptor instanceof_vertex); static void check_indexing(VertexPtr array, VertexPtr key) noexcept; static void check_array_literal(VertexAdaptor vertex) noexcept; + static void check_serialized_fields_hierarchy(ClassPtr klass); }; diff --git a/docs/kphp-language/howto-by-kphp/serialization-msgpack.md b/docs/kphp-language/howto-by-kphp/serialization-msgpack.md index 512cdfe01f..aedb46ed9d 100644 --- a/docs/kphp-language/howto-by-kphp/serialization-msgpack.md +++ b/docs/kphp-language/howto-by-kphp/serialization-msgpack.md @@ -105,7 +105,13 @@ After having deleted the field, you must specify its index in `@kphp-reserved-fi * Deleting is ok, but don't forget about *@kphp-reserved-fields* * **Types of fields can't be changed!** Old binary data would not map to the new structure ``` +## Inheritance +It is allowed to inherit classes with `@kphp-serializable` annotation, but there are restrictions: +```tip +* Class with instance field must be marked with `@kphp-serializable` +* Within annotation `@kphp-serialized-field {index}` index must be unique across whole inheritance hierarchy. +``` ## Serializing floats diff --git a/tests/phpt/msgpack_serialize/026_serialize_with_base.php b/tests/phpt/msgpack_serialize/026_serialize_with_base.php new file mode 100644 index 0000000000..6fb27909e1 --- /dev/null +++ b/tests/phpt/msgpack_serialize/026_serialize_with_base.php @@ -0,0 +1,30 @@ +@ok k2_skip +foo(); +$c_new = instance_deserialize($str, C::class); +$c_new->foo(); \ No newline at end of file diff --git a/tests/phpt/msgpack_serialize/028_deserialize_inherited.php b/tests/phpt/msgpack_serialize/028_deserialize_inherited.php new file mode 100644 index 0000000000..37e8d336c2 --- /dev/null +++ b/tests/phpt/msgpack_serialize/028_deserialize_inherited.php @@ -0,0 +1,50 @@ +@ok k2_skip +foo(); + +$c = instance_deserialize($str, C::class); +$b = instance_deserialize($str, B::class); +$a = instance_deserialize($str, A::class); + +var_dump(instance_to_array($a)); +var_dump(instance_to_array($b)); +var_dump(instance_to_array($c)); + +$a->foo(); +$b->foo(); +$c->foo(); diff --git a/tests/phpt/msgpack_serialize/103_serialize_with_base.php b/tests/phpt/msgpack_serialize/103_serialize_with_base.php index 0b0eb3fd8c..9c918ae4fe 100644 --- a/tests/phpt/msgpack_serialize/103_serialize_with_base.php +++ b/tests/phpt/msgpack_serialize/103_serialize_with_base.php @@ -1,5 +1,5 @@ @kphp_should_fail k2_skip -/You may not serialize classes which has a parent with fields/ +/Class A and all its ancestors must be @kphp-serializable if there are instance fields. Class Base is not./