Skip to content

Commit 0f4b004

Browse files
authored
Basic implementation of instance cache in k2 (#1290)
1 parent 6c04688 commit 0f4b004

File tree

21 files changed

+732
-48
lines changed

21 files changed

+732
-48
lines changed

builtin-functions/kphp-light/functions.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ require_once __DIR__ . '/stdlib/diagnostic.txt';
1414
require_once __DIR__ . '/stdlib/error.txt';
1515
require_once __DIR__ . '/stdlib/file-functions.txt';
1616
require_once __DIR__ . '/stdlib/fork-functions.txt';
17+
require_once __DIR__ . '/stdlib/instance-cache.txt';
1718
require_once __DIR__ . '/stdlib/job-workers.txt';
1819
require_once __DIR__ . '/stdlib/kml-functions.txt';
1920
require_once __DIR__ . '/stdlib/kphp-toggles.txt';
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
/** @kphp-extern-func-info cpp_template_call interruptible */
4+
function instance_cache_fetch(string $type, string $key, bool $even_if_expired = false) ::: instance<^1>;
5+
6+
/** @kphp-extern-func-info interruptible */
7+
function instance_cache_store(string $key, object $value, int $ttl = 0) ::: bool;
8+
9+
/** @kphp-extern-func-info interruptible */
10+
function instance_cache_update_ttl(string $key, int $ttl = 0) ::: bool;
11+
12+
/** @kphp-extern-func-info interruptible */
13+
function instance_cache_delete(string $key) ::: bool;

builtin-functions/kphp-light/stdlib/server-functions.txt

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -51,17 +51,6 @@ function ip2ulong ($ip ::: string) ::: string | false;
5151

5252
function long2ip ($ip ::: int) ::: string;
5353

54-
// Dummy implementation without caching
55-
/** @kphp-extern-func-info cpp_template_call */
56-
function instance_cache_fetch(string $type, string $key, bool $even_if_expired = false) ::: instance<^1>;
57-
58-
function instance_cache_store(string $key, object $value, int $ttl = 0) ::: bool;
59-
60-
function instance_cache_update_ttl(string $key, int $ttl = 0) ::: bool;
61-
62-
function instance_cache_delete(string $key) ::: bool;
63-
64-
6554
// === Memory =====================================================================================
6655

6756
function estimate_memory_usage($value ::: any) ::: int;

compiler/gentree.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
#include "compiler/gentree.h"
66

7+
#include <atomic>
8+
79
#include "common/algorithms/contains.h"
810
#include "common/algorithms/find.h"
911

@@ -1721,6 +1723,10 @@ VertexPtr GenTree::get_class(const PhpDocComment *phpdoc, ClassType class_type)
17211723
cur_class->set_name_and_src_name(full_class_name); // with full namespaces and slashes
17221724
cur_class->phpdoc = phpdoc;
17231725
cur_class->is_immutable = phpdoc && phpdoc->has_tag(PhpDocType::kphp_immutable_class);
1726+
if (G->is_output_mode_k2()) {
1727+
// To be able to store instances in request cache
1728+
cur_class->may_be_mixed.store(cur_class->is_immutable, std::memory_order_relaxed);
1729+
}
17241730
cur_class->need_generated_stub = phpdoc && phpdoc->has_tag(PhpDocType::kphp_generated_stub_class);
17251731
cur_class->is_required_interface = phpdoc && phpdoc->has_tag(PhpDocType::kphp_required) && class_type == ClassType::interface;
17261732
cur_class->location_line_num = line_num;

compiler/pipes/final-check.cpp

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ void check_class_immutableness(ClassPtr klass) {
4444
std::vector<ClassPtr> find_not_ic_compatibility_derivatives(ClassPtr klass);
4545

4646
void check_fields_ic_compatibility(ClassPtr klass) {
47+
// In case of K2 mode, all checks about serializability have already done
4748
bool flag = false;
4849
if (!klass->process_fields_ic_compatibility.compare_exchange_strong(flag, true, std::memory_order_acq_rel)) {
4950
return;
@@ -68,6 +69,7 @@ void check_fields_ic_compatibility(ClassPtr klass) {
6869
}
6970

7071
void check_derivatives_ic_compatibility(ClassPtr klass) {
72+
// In case of K2 mode, all checks about serializability have already done
7173
std::vector<ClassPtr> descendants = find_not_ic_compatibility_derivatives(klass);
7274
for (const auto &element : descendants) {
7375
kphp_error(false, fmt_format("Can not store polymorphic type {} with mutable derived class {}", klass->name, element->name));
@@ -129,18 +131,32 @@ void process_job_worker_class(ClassPtr klass) {
129131
void check_instance_cache_fetch_call(VertexAdaptor<op_func_call> call) {
130132
auto klass = tinf::get_type(call)->class_type();
131133
kphp_assert(klass);
132-
kphp_error(klass->is_immutable || klass->is_interface(),
133-
fmt_format("Can not fetch instance of mutable class {} with instance_cache_fetch call", klass->name));
134-
klass->deeply_require_instance_cache_visitor();
134+
if (G->is_output_mode_k2()) {
135+
kphp_error(klass->is_immutable,
136+
fmt_format("Can not fetch instance of mutable class {} with instance_cache_fetch call", klass->name));
137+
kphp_error(klass->is_serializable,
138+
fmt_format("Can not fetch instance of non-serializable class {} with instance_cache_fetch call", klass->name));
139+
} else {
140+
kphp_error(klass->is_immutable || klass->is_interface(),
141+
fmt_format("Can not fetch instance of mutable class {} with instance_cache_fetch call", klass->name));
142+
klass->deeply_require_instance_cache_visitor();
143+
}
135144
}
136145

137146
void check_instance_cache_store_call(VertexAdaptor<op_func_call> call) {
138147
const auto *type = tinf::get_type(call->args()[1]);
139148
kphp_error_return(type->ptype() == tp_Class, "Called instance_cache_store() with a non-instance argument");
140149
auto klass = type->class_type();
141150
kphp_error(!klass->is_empty_class(), fmt_format("Can not store instance of empty class {} with instance_cache_store call", klass->name));
142-
kphp_error_return(klass->is_immutable || klass->is_interface(),
143-
fmt_format("Can not store instance of mutable class {} with instance_cache_store call", klass->name));
151+
152+
if (G->is_output_mode_k2()) {
153+
kphp_error_return(klass->is_immutable, fmt_format("Can not store instance of mutable class {} with instance_cache_store call", klass->name));
154+
kphp_error_return(klass->is_serializable, fmt_format("Can not store instance of non-serializable class {} with instance_cache_store call", klass->name));
155+
} else {
156+
kphp_error_return(klass->is_immutable || klass->is_interface(),
157+
fmt_format("Can not store instance of mutable class {} with instance_cache_store call", klass->name));
158+
}
159+
144160
check_fields_ic_compatibility(klass);
145161
check_derivatives_ic_compatibility(klass);
146162
klass->deeply_require_instance_cache_visitor();

runtime-common/core/core-types/definition/mixed.inl

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -289,8 +289,11 @@ ResultClass from_mixed(const mixed& m, const string&) noexcept {
289289
php_error("Internal error. Class inside a mixed is not polymorphic");
290290
return {};
291291
} else {
292-
return m.is_object() ? ResultClass::create_from_base_raw_ptr(dynamic_cast<abstract_refcountable_php_interface*>(m.as_object_ptr<ResultClass>()))
293-
: ResultClass{};
292+
if (!m.is_object()) {
293+
return ResultClass{};
294+
}
295+
auto casted_ptr = dynamic_cast<abstract_refcountable_php_interface*>(m.as_object_ptr<ResultClass>());
296+
return casted_ptr ? ResultClass::create_from_base_raw_ptr(casted_ptr) : ResultClass{};
294297
}
295298
}
296299

runtime-light/state/instance-state.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "runtime-light/stdlib/curl/curl-state.h"
2626
#include "runtime-light/stdlib/file/file-system-state.h"
2727
#include "runtime-light/stdlib/fork/fork-state.h"
28+
#include "runtime-light/stdlib/instance-cache/instance-cache-state.h"
2829
#include "runtime-light/stdlib/job-worker/job-worker-client-state.h"
2930
#include "runtime-light/stdlib/math/math-state.h"
3031
#include "runtime-light/stdlib/math/random-state.h"
@@ -121,6 +122,7 @@ struct InstanceState final : vk::not_copyable {
121122
HttpServerInstanceState http_server_instance_state;
122123
JobWorkerClientInstanceState job_worker_client_instance_state;
123124
JobWorkerServerInstanceState job_worker_server_instance_state;
125+
InstanceCacheInstanceState instance_cache_instance_state;
124126

125127
MathInstanceState math_instance_state;
126128
RandomInstanceState random_instance_state;
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
// Compiler for PHP (aka KPHP)
2+
// Copyright (c) 2025 LLC «V Kontakte»
3+
// Distributed under the GPL v3 License, see LICENSE.notice.txt
4+
5+
#pragma once
6+
7+
#include <cstdint>
8+
#include <limits>
9+
#include <string_view>
10+
#include <utility>
11+
12+
#include "runtime-common/core/runtime-core.h"
13+
#include "runtime-common/stdlib/serialization/msgpack-functions.h"
14+
#include "runtime-light/coroutine/task.h"
15+
#include "runtime-light/stdlib/component/component-api.h"
16+
#include "runtime-light/stdlib/instance-cache/instance-cache-state.h"
17+
#include "runtime-light/stdlib/serialization/msgpack-functions.h"
18+
#include "runtime-light/tl/tl-core.h"
19+
#include "runtime-light/tl/tl-functions.h"
20+
#include "runtime-light/tl/tl-types.h"
21+
#include "runtime-light/utils/logs.h"
22+
23+
namespace kphp::instance_cache::details {
24+
25+
inline constexpr std::string_view COMPONENT_NAME{"instance_cache"};
26+
27+
} // namespace kphp::instance_cache::details
28+
29+
template<typename InstanceType>
30+
kphp::coro::task<bool> f$instance_cache_store(string key, InstanceType instance, int64_t ttl = 0) noexcept {
31+
if (ttl < 0) [[unlikely]] {
32+
kphp::log::warning("ttl can't be negative: ttl -> {}, key -> {}", ttl, key.c_str());
33+
co_return false;
34+
}
35+
if (ttl > std::numeric_limits<uint32_t>::max()) [[unlikely]] {
36+
kphp::log::warning("ttl exceeds maximum allowed value, key will be stored forever: ttl -> {}, max -> {}, key -> {}", ttl,
37+
std::numeric_limits<uint32_t>::max(), key.c_str());
38+
ttl = 0;
39+
}
40+
41+
auto serialized_instance{f$instance_serialize(instance)};
42+
if (!serialized_instance.has_value()) [[unlikely]] {
43+
kphp::log::warning("can't serialize instance: key -> {}", key.c_str());
44+
co_return false;
45+
}
46+
47+
tl::TLBuffer tlb;
48+
tl::CacheStore{.key = tl::string{.value = {key.c_str(), key.size()}},
49+
.value = tl::string{.value = {serialized_instance.val().c_str(), serialized_instance.val().size()}},
50+
.ttl = tl::u32{.value = static_cast<uint32_t>(ttl)}}
51+
.store(tlb);
52+
53+
auto query{co_await f$component_client_send_request(
54+
string{kphp::instance_cache::details::COMPONENT_NAME.data(), static_cast<string::size_type>(kphp::instance_cache::details::COMPONENT_NAME.size())},
55+
string{tlb.data(), static_cast<string::size_type>(tlb.size())})};
56+
if (query.is_null()) [[unlikely]] {
57+
co_return false;
58+
}
59+
60+
auto response{co_await f$component_client_fetch_response(std::move(query))};
61+
tlb.clean();
62+
tlb.store_bytes({response.c_str(), static_cast<size_t>(response.size())});
63+
64+
tl::Bool tl_bool{};
65+
kphp::log::assertion(tl_bool.fetch(tlb));
66+
InstanceCacheInstanceState::get().request_cache.emplace(std::move(key), std::move(instance));
67+
co_return tl_bool.value;
68+
}
69+
70+
template<typename InstanceType>
71+
kphp::coro::task<InstanceType> f$instance_cache_fetch(string /*class_name*/, string key, bool /*even_if_expired*/ = false) noexcept {
72+
auto& request_cache{InstanceCacheInstanceState::get().request_cache};
73+
if (auto it{request_cache.find(key)}; it != request_cache.end()) {
74+
auto cached_instance{from_mixed<InstanceType>(it->second, {})};
75+
co_return std::move(cached_instance);
76+
}
77+
78+
tl::TLBuffer tlb;
79+
tl::CacheFetch{.key = tl::string{.value = {key.c_str(), key.size()}}}.store(tlb);
80+
81+
auto query{co_await f$component_client_send_request(
82+
string{kphp::instance_cache::details::COMPONENT_NAME.data(), static_cast<string::size_type>(kphp::instance_cache::details::COMPONENT_NAME.size())},
83+
string{tlb.data(), static_cast<string::size_type>(tlb.size())})};
84+
if (query.is_null()) [[unlikely]] {
85+
co_return InstanceType{};
86+
}
87+
88+
auto response{co_await f$component_client_fetch_response(std::move(query))};
89+
tlb.clean();
90+
tlb.store_bytes({response.c_str(), static_cast<size_t>(response.size())});
91+
92+
tl::Maybe<tl::string> maybe_string{};
93+
kphp::log::assertion(maybe_string.fetch(tlb));
94+
if (!maybe_string.opt_value) [[unlikely]] {
95+
co_return InstanceType{};
96+
}
97+
98+
auto cached_instance{f$instance_deserialize<InstanceType>(
99+
string{(*maybe_string.opt_value).value.data(), static_cast<string::size_type>((*maybe_string.opt_value).value.size())}, {})};
100+
request_cache.emplace(std::move(key), cached_instance);
101+
co_return std::move(cached_instance);
102+
}
103+
104+
inline kphp::coro::task<bool> f$instance_cache_update_ttl(string key, int64_t ttl = 0) noexcept {
105+
if (ttl < 0) [[unlikely]] {
106+
kphp::log::warning("ttl can't be negative: ttl -> {}, key -> {}", ttl, key.c_str());
107+
co_return false;
108+
}
109+
if (ttl > std::numeric_limits<uint32_t>::max()) [[unlikely]] {
110+
kphp::log::warning("ttl exceeds maximum allowed value, key will be stored forever: ttl -> {}, max -> {}, key -> {}", ttl,
111+
std::numeric_limits<uint32_t>::max(), key.c_str());
112+
ttl = 0;
113+
}
114+
115+
tl::TLBuffer tlb;
116+
tl::CacheUpdateTtl{.key = tl::string{.value = {key.c_str(), key.size()}}, .ttl = tl::u32{.value = static_cast<uint32_t>(ttl)}}.store(tlb);
117+
118+
auto query{co_await f$component_client_send_request(
119+
string{kphp::instance_cache::details::COMPONENT_NAME.data(), static_cast<string::size_type>(kphp::instance_cache::details::COMPONENT_NAME.size())},
120+
string{tlb.data(), static_cast<string::size_type>(tlb.size())})};
121+
if (query.is_null()) [[unlikely]] {
122+
co_return false;
123+
}
124+
125+
auto response{co_await f$component_client_fetch_response(std::move(query))};
126+
tlb.clean();
127+
tlb.store_bytes({response.c_str(), static_cast<size_t>(response.size())});
128+
129+
tl::Bool tl_bool{};
130+
kphp::log::assertion(tl_bool.fetch(tlb));
131+
co_return tl_bool.value;
132+
}
133+
134+
inline kphp::coro::task<bool> f$instance_cache_delete(string key) noexcept {
135+
InstanceCacheInstanceState::get().request_cache.erase(key);
136+
137+
tl::TLBuffer tlb;
138+
tl::CacheDelete{.key = tl::string{.value = {key.c_str(), key.size()}}}.store(tlb);
139+
140+
auto query{co_await f$component_client_send_request(
141+
string{kphp::instance_cache::details::COMPONENT_NAME.data(), static_cast<string::size_type>(kphp::instance_cache::details::COMPONENT_NAME.size())},
142+
string{tlb.data(), static_cast<string::size_type>(tlb.size())})};
143+
if (query.is_null()) [[unlikely]] {
144+
co_return false;
145+
}
146+
147+
auto response{co_await f$component_client_fetch_response(std::move(query))};
148+
tlb.clean();
149+
tlb.store_bytes({response.c_str(), static_cast<size_t>(response.size())});
150+
151+
tl::Bool tl_bool{};
152+
kphp::log::assertion(tl_bool.fetch(tlb));
153+
co_return tl_bool.value;
154+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Compiler for PHP (aka KPHP)
2+
// Copyright (c) 2025 LLC «V Kontakte»
3+
// Distributed under the GPL v3 License, see LICENSE.notice.txt
4+
5+
#include "runtime-light/stdlib/instance-cache/instance-cache-state.h"
6+
7+
#include "runtime-light/state/instance-state.h"
8+
9+
InstanceCacheInstanceState& InstanceCacheInstanceState::get() noexcept {
10+
return InstanceState::get().instance_cache_instance_state;
11+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Compiler for PHP (aka KPHP)
2+
// Copyright (c) 2025 LLC «V Kontakte»
3+
// Distributed under the GPL v3 License, see LICENSE.notice.txt
4+
5+
#pragma once
6+
7+
#include <cstddef>
8+
9+
#include "common/mixin/not_copyable.h"
10+
#include "runtime-common/core/allocator/script-allocator.h"
11+
#include "runtime-common/core/runtime-core.h"
12+
#include "runtime-common/core/std/containers.h"
13+
14+
struct InstanceCacheInstanceState final : private vk::not_copyable {
15+
kphp::stl::unordered_map<string, mixed, kphp::memory::script_allocator, decltype([](const string& s) noexcept { return static_cast<size_t>(s.hash()); })>
16+
request_cache;
17+
18+
InstanceCacheInstanceState() noexcept = default;
19+
static InstanceCacheInstanceState& get() noexcept;
20+
};

0 commit comments

Comments
 (0)