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 @@