Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
a5d5a51
added instance cache + fix tests
denisichh Aug 20, 2026
deed634
fix
denisichh Aug 20, 2026
e01271b
added kphp::visitors to C and C*
denisichh Aug 20, 2026
bf451d4
fix
denisichh Aug 20, 2026
830c8b4
xif
denisichh Aug 20, 2026
661a80d
fix?
denisichh Aug 20, 2026
831c506
tmp fix
denisichh Aug 20, 2026
2754ed3
added kphp::visitors to DummyVisitorMethods
denisichh Aug 20, 2026
55367ae
removed useless warnings
denisichh Aug 20, 2026
c892496
minor fix
denisichh Aug 20, 2026
a8e1501
fix
denisichh Aug 23, 2026
75894c6
code_style++?
denisichh Aug 24, 2026
f87c14a
fmt + minor fixes and optimisations
denisichh Aug 25, 2026
55e951d
added alignment for ic_store
denisichh Aug 26, 2026
540acad
sigsegv fix
denisichh Aug 26, 2026
d1951b1
fmt
denisichh Aug 27, 2026
fdc8bb3
fix
denisichh Aug 27, 2026
923f0c6
fix
denisichh Aug 27, 2026
4faef3c
added new test
denisichh Aug 27, 2026
57b4641
-
denisichh Aug 28, 2026
3d9988e
removed unused include
denisichh Aug 28, 2026
be85ca1
k2api updated
denisichh Aug 31, 2026
7cdaf87
added: k2_free_shared_memory + removed: fetch optimisation, part of t…
denisichh Sep 2, 2026
2ef75f8
fix
denisichh Sep 8, 2026
dd93926
update k2api
denisichh Sep 8, 2026
710b601
fix instance_cache_store
denisichh Sep 8, 2026
064bfc5
update k2api
denisichh Sep 9, 2026
25a4529
fix
denisichh Sep 9, 2026
f4a9849
optional-based clone_in/alloc_in + get_class_name_hash()
denisichh Sep 10, 2026
73134a1
update allocators
apolyakov Sep 10, 2026
c87d381
add missing define to compiler
apolyakov Sep 10, 2026
a82bd97
switch instance_deep_copy/estimate_visitor to allocator substitution
denisichh Sep 11, 2026
2a52817
add missing include
apolyakov Sep 10, 2026
475e2a6
remove smth
denisichh Sep 11, 2026
9faa830
remove lambda from array<T>::copy_from
denisichh Sep 11, 2026
74fe10b
move VISITED_INSTANCE_MASK
denisichh Sep 11, 2026
7958a63
renames and minor fixes
denisichh Sep 11, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions builtin-functions/kphp-light/stdlib/instance-cache.txt
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
<?php

/** @kphp-extern-func-info cpp_template_call interruptible */
/** @kphp-extern-func-info cpp_template_call */
function instance_cache_fetch(string $type, string $key, bool $even_if_expired = false) ::: instance<^1>;

/** @kphp-extern-func-info interruptible */
function instance_cache_store(string $key, object $value, int $ttl = 0) ::: bool;

/** @kphp-extern-func-info interruptible */
function instance_cache_update_ttl(string $key, int $ttl = 0) ::: bool;

/** @kphp-extern-func-info interruptible */
function instance_cache_delete(string $key) ::: bool;
29 changes: 25 additions & 4 deletions compiler/code-gen/declarations.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

#include "common/algorithms/compare.h"
#include "common/algorithms/find.h"
#include "common/algorithms/hashes.h"
#include "common/tlo-parsing/tl-objects.h"
#include "common/wrappers/fmt_format.h"
#include "common/wrappers/iterator_range.h"
Expand Down Expand Up @@ -588,6 +589,7 @@ void ClassDeclaration::compile_inner_methods(CodeGenerator& W, ClassPtr klass) {
compile_has_wakeup_flag(W, klass);
compile_get_class(W, klass);
compile_get_hash(W, klass);
compile_class_name_hash(W, klass);
compile_accept_visitor_methods(W, klass);
compile_msgpack_declarations(W, klass);
compile_virtual_builtin_functions(W, klass);
Expand Down Expand Up @@ -759,6 +761,13 @@ void ClassDeclaration::compile_get_hash(CodeGenerator& W, ClassPtr klass) {
compile_class_method(FunctionSignatureGenerator(W).set_const_this(), klass, "int get_hash()", klass->get_hash());
}

void ClassDeclaration::compile_class_name_hash(CodeGenerator& W, ClassPtr klass) {
// hash of the class name, computed once at compile time -- same for every instance,
Comment thread
denisichh marked this conversation as resolved.
// unlike the virtual get_hash() it can be read without an instance at hand.
FunctionSignatureGenerator(W) << "static uint64_t get_class_name_hash()" << BEGIN;
W << "return " << vk::murmur_hash<uint64_t>(klass->name.data(), klass->name.size()) << "ULL;" << NL << END << NL << NL;
}

void ClassDeclaration::compile_accept_visitor(CodeGenerator& W, ClassPtr klass, const char* visitor_type) {
compile_class_method(FunctionSignatureGenerator(W), klass, fmt_format("void accept({} &visitor)", visitor_type), "generic_accept(visitor)");
}
Expand Down Expand Up @@ -914,8 +923,7 @@ void ClassDeclaration::compile_accept_json_visitor(CodeGenerator& W, ClassPtr kl
}

void ClassDeclaration::compile_accept_visitor_methods(CodeGenerator& W, ClassPtr klass) {
bool need_generic_accept =
klass->need_to_array_debug_visitor || (klass->need_instance_cache_visitors && !G->is_output_mode_k2()) || (klass->need_instance_memory_estimate_visitor);
bool need_generic_accept = klass->need_to_array_debug_visitor || klass->need_instance_cache_visitors || klass->need_instance_memory_estimate_visitor;

if (!need_generic_accept && klass->json_encoders.empty()) {
return;
Expand Down Expand Up @@ -947,6 +955,13 @@ void ClassDeclaration::compile_accept_visitor_methods(CodeGenerator& W, ClassPtr
compile_accept_visitor(W, klass, "InstanceDeepDestroyVisitor");
}

if (klass->need_instance_cache_visitors && G->is_output_mode_k2()) {
W << NL;
compile_accept_visitor(W, klass, "kphp::visitors::instance_deep_copy_visitor");
W << NL;
compile_accept_visitor(W, klass, "kphp::visitors::instance_deep_estimate_size_visitor");
}

compile_accept_json_visitor(W, klass);
}

Expand Down Expand Up @@ -1063,8 +1078,7 @@ void ClassDeclaration::compile_job_worker_shared_memory_piece_methods(CodeGenera
}

void ClassMembersDefinition::compile(CodeGenerator& W) const {
bool need_generic_accept =
klass->need_to_array_debug_visitor || (klass->need_instance_cache_visitors && !G->is_output_mode_k2()) || (klass->need_instance_memory_estimate_visitor);
bool need_generic_accept = klass->need_to_array_debug_visitor || klass->need_instance_cache_visitors || klass->need_instance_memory_estimate_visitor;

if (!need_generic_accept && !klass->is_serializable && klass->json_encoders.empty()) {
return;
Expand Down Expand Up @@ -1105,6 +1119,13 @@ void ClassMembersDefinition::compile(CodeGenerator& W) const {
compile_generic_accept_instantiations(W, klass, "InstanceDeepDestroyVisitor");
}

if (klass->need_instance_cache_visitors && G->is_output_mode_k2()) {
W << NL;
compile_generic_accept_instantiations(W, klass, "kphp::visitors::instance_deep_copy_visitor");
W << NL;
compile_generic_accept_instantiations(W, klass, "kphp::visitors::instance_deep_estimate_size_visitor");
}

W << NL;
compile_accept_json_visitor(W, klass);

Expand Down
1 change: 1 addition & 0 deletions compiler/code-gen/declarations.h
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ struct ClassDeclaration : CodeGenRootCmd {
static void compile_has_wakeup_flag(CodeGenerator& W, ClassPtr klass);
static void compile_get_class(CodeGenerator& W, ClassPtr klass);
static void compile_get_hash(CodeGenerator& W, ClassPtr klass);
static void compile_class_name_hash(CodeGenerator& W, ClassPtr klass);
static void compile_accept_visitor_methods(CodeGenerator& W, ClassPtr klass);
static void compile_msgpack_declarations(CodeGenerator& W, ClassPtr klass);
static void compile_virtual_builtin_functions(CodeGenerator& W, ClassPtr klass);
Expand Down
2 changes: 2 additions & 0 deletions compiler/compiler-settings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,8 @@ void CompilerSettings::init() {

ss << " -I" << kphp_src_path.get() + "objs/include ";
if (is_k2_mode) {
// Generated code and its precompiled header must use the light runtime declarations.
ss << " -DRUNTIME_LIGHT";
// for now k2-component must be compiled with clang and statically linked libc++
ss << " -stdlib=libc++";
if (!dynamic_incremental_linkage.get()) {
Expand Down
15 changes: 2 additions & 13 deletions compiler/pipes/final-check.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ void check_class_immutableness(ClassPtr klass) {
std::vector<ClassPtr> find_not_ic_compatibility_derivatives(ClassPtr klass);

void check_fields_ic_compatibility(ClassPtr klass) {
// In case of K2 mode, all checks about serializability have already done
bool flag = false;
if (!klass->process_fields_ic_compatibility.compare_exchange_strong(flag, true, std::memory_order_acq_rel)) {
return;
Expand All @@ -97,7 +96,6 @@ void check_fields_ic_compatibility(ClassPtr klass) {
}

void check_derivatives_ic_compatibility(ClassPtr klass) {
// In case of K2 mode, all checks about serializability have already done
std::vector<ClassPtr> descendants = find_not_ic_compatibility_derivatives(klass);
for (const auto& element : descendants) {
kphp_error(false, fmt_format("Can not store polymorphic type {} with mutable derived class {}", klass->name, element->name));
Expand Down Expand Up @@ -164,12 +162,9 @@ void check_instance_cache_fetch_call(VertexAdaptor<op_func_call> call) {

kphp_error(klass->is_immutable || klass->is_interface(),
fmt_format("Can not fetch instance of mutable class {} with instance_cache_fetch call", klass->name));
kphp_error(klass->is_serializable, fmt_format("Can not fetch instance of non-serializable class {} with instance_cache_fetch call", klass->name));

if (G->is_output_mode_k2()) {
// To be able to store instances in request cache
klass->deeply_require_may_be_mixed_base();
} else {
if (!G->is_output_mode_k2()) {
// in K2 mode fetch just reinterprets the shared memory block, so no visitor codegen is needed
klass->deeply_require_instance_cache_visitor();
}
}
Expand All @@ -182,12 +177,6 @@ void check_instance_cache_store_call(VertexAdaptor<op_func_call> call) {

kphp_error_return(klass->is_immutable || klass->is_interface(),
fmt_format("Can not store instance of mutable class {} with instance_cache_store call", klass->name));
kphp_error_return(klass->is_serializable, fmt_format("Can not store instance of non-serializable class {} with instance_cache_store call", klass->name));

if (G->is_output_mode_k2()) {
// To be able to store instances in request cache
klass->deeply_require_may_be_mixed_base();
}

check_fields_ic_compatibility(klass);
check_derivatives_ic_compatibility(klass);
Expand Down
12 changes: 10 additions & 2 deletions runtime-common/core/allocator/pool-allocator.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,25 @@
namespace kphp::memory {

struct pool_allocator : private vk::not_copyable {
struct external_memory {};

private:
enum class memory_mode {
owned_growable,
external_fixed, // Never grows or releases the externally owned backing buffer.
};

memory_resource::unsynchronized_pool_resource memory_resource;
memory_mode m_memory_mode{memory_mode::owned_growable};
size_t m_min_extra_mem_size{0};

auto request_extra_memory(size_t requested_size) noexcept -> void;

public:
pool_allocator() = default;
pool_allocator(size_t script_mem_size, size_t min_extra_mem_size, size_t oom_handling_mem_size) noexcept;
// Borrows the buffer without growing it or releasing it in free().
pool_allocator(external_memory, void* buffer, size_t script_mem_size, size_t oom_handling_mem_size) noexcept;

auto init(void* buffer, size_t script_mem_size, size_t oom_handling_mem_size) noexcept -> void;
auto free() noexcept -> void;

auto alloc_script_memory(size_t size) noexcept -> void*;
Expand Down
38 changes: 32 additions & 6 deletions runtime-common/core/allocator/runtime-allocator.h
Original file line number Diff line number Diff line change
@@ -1,32 +1,58 @@
// Compiler for PHP (aka KPHP)
// Copyright (c) 2024 LLC «V Kontakte»
// Distributed under the GPL v3 License, see LICENSE.notice.txt
// Compiler for PHP (aka KPHP)
// Copyright (c) 2024 LLC «V Kontakte»
// Distributed under the GPL v3 License, see LICENSE.notice.txt

#pragma once

#include <cstddef>
#ifdef RUNTIME_LIGHT
#include <cstdint>
#include <functional>
#include <type_traits>
#include <utility>

#include "common/containers/final_action.h"
#include "runtime-common/core/allocator/pool-allocator.h"
#endif

struct RuntimeAllocator final {
#ifdef RUNTIME_LIGHT
private:
kphp::memory::pool_allocator m_allocator;
std::reference_wrapper<kphp::memory::pool_allocator> m_allocator_ref{m_allocator};
#endif

public:
static auto get() noexcept -> RuntimeAllocator&;

RuntimeAllocator() = default;
#ifdef RUNTIME_LIGHT
RuntimeAllocator(size_t script_mem_size, size_t min_extra_mem_size, size_t oom_handling_mem_size) noexcept;

#else
RuntimeAllocator() = default;
auto init(void* buffer, size_t script_mem_size, size_t oom_handling_mem_size) noexcept -> void;
#endif

auto free() noexcept -> void;

auto alloc_script_memory(size_t size) noexcept -> void*;
auto calloc_script_memory(size_t size) noexcept -> void*;
auto realloc_script_memory(void* mem, size_t new_size, size_t old_size) noexcept -> void*;
auto free_script_memory(void* mem, size_t size) noexcept -> void;

#ifdef RUNTIME_LIGHT
auto get_memory_resource() noexcept -> memory_resource::unsynchronized_pool_resource& {
return m_allocator.get_memory_resource();
return m_allocator_ref.get().get_memory_resource();
}

// The callback must run synchronously without yielding. Objects allocated by it
// may outlive the scope, but later operations that allocate or deallocate their
// memory must install the same allocator. The replacement must outlive the callback.
template<typename callback_type,
std::enable_if_t<std::is_nothrow_invocable_v<callback_type> && std::is_same_v<std::invoke_result_t<callback_type>, void>, int32_t> = 0>
auto with_allocator(kphp::memory::pool_allocator& replacement, callback_type&& callback) noexcept -> void {
const auto previous_allocator{std::exchange(m_allocator_ref, std::ref(replacement))};
const auto restore_allocator{vk::finally([this, previous_allocator]() noexcept { m_allocator_ref = previous_allocator; })};
std::invoke(std::forward<callback_type>(callback));
}
#endif
};
8 changes: 8 additions & 0 deletions runtime-common/stdlib/visitors/dummy-visitor-methods.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ class InstanceDeepCopyVisitor;
class InstanceDeepDestroyVisitor;
class InstanceReferencesCountingVisitor;

namespace kphp::visitors {
class instance_deep_copy_visitor;
class instance_deep_estimate_size_visitor;
} // namespace kphp::visitors

struct DummyVisitorMethods {
// for f$estimate_memory_usage()
// set at compiler at deeply_require_instance_memory_estimate_visitor()
Expand All @@ -22,4 +27,7 @@ struct DummyVisitorMethods {
void accept(InstanceReferencesCountingVisitor& /*unused*/) noexcept {}
void accept(InstanceDeepCopyVisitor& /*unused*/) noexcept {}
void accept(InstanceDeepDestroyVisitor& /*unused*/) noexcept {}
// K2 counterparts of the instance cache visitors
void accept(kphp::visitors::instance_deep_copy_visitor& /*unused*/) noexcept {}
void accept(kphp::visitors::instance_deep_estimate_size_visitor& /*unused*/) noexcept {}
};
116 changes: 116 additions & 0 deletions runtime-common/stdlib/visitors/instance-deep-basic-visitor.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// Compiler for PHP (aka KPHP)
// Copyright (c) 2026 LLC «V Kontakte»
// Distributed under the GPL v3 License, see LICENSE.notice.txt

#pragma once

#include <algorithm>
#include <cstdint>
#include <tuple>
#include <type_traits>
#include <utility>

#include "common/mixin/not_copyable.h"
#include "common/type_traits/list_of_types.h"
#include "runtime-common/core/runtime-core.h"

namespace kphp::visitors {

// CRTP base for visitors that traverse an instance graph via compiler-generated accept() methods.
// Every field is dispatched to Child::process, and a false result from any field is accumulated into is_ok().
template<typename Child>
class instance_deep_basic_visitor : vk::not_copyable {
public:
template<typename T>
void operator()(const char* /*unused*/, T&& value) noexcept {
const bool is_ok{child_.process(std::forward<T>(value))};
is_ok_ = is_ok_ && is_ok;
}

template<typename T>
bool process(T& /*unused*/) noexcept {
return true;
}

template<typename T>
bool process(Optional<T>& value) noexcept {
return !value.has_value() || child_.process(value.val());
}

template<typename I>
bool process(class_instance<I>& instance) noexcept {
if (!instance.is_null()) {
instance.get()->accept(child_);
return child_.is_ok();
}
return true;
}

template<typename... Args>
bool process(std::tuple<Args...>& value) noexcept {
return process_tuple(value);
}

template<size_t... Is, typename... T>
bool process(shape<std::index_sequence<Is...>, T...>& value) noexcept {
const bool child_res[]{child_.process(value.template get<Is>())...};
return std::all_of(std::begin(child_res), std::end(child_res), [](bool r) noexcept { return r; });
}

bool process(mixed& value) noexcept {
if (value.is_string()) {
return child_.process(value.as_string());
} else if (value.is_array()) {
return child_.process(value.as_array());
}
return true;
}

bool is_ok() const noexcept {
return is_ok_;
}

ExtraRefCnt get_memory_ref_cnt() const noexcept {
return memory_ref_cnt_;
}

protected:
template<class T>
static constexpr bool is_primitive{vk::is_type_in_list<T, int64_t, double, bool, Optional<int64_t>, Optional<double>, Optional<bool>>::value};

explicit instance_deep_basic_visitor(Child& child, ExtraRefCnt memory_ref_cnt = ExtraRefCnt::extra_ref_cnt_value(0)) noexcept
: memory_ref_cnt_{memory_ref_cnt},
child_{child} {}

template<typename Iterator>
bool process_range(Iterator first, Iterator last) noexcept {
bool res{true};
for (; first != last; ++first) {
if (!child_.process(first.get_value())) {
res = false;
}
if (first.is_string_key() && !child_.process(first.get_string_key())) {
res = false;
}
}
return res;
}

private:
template<size_t Index = 0, typename... Args>
std::enable_if_t<Index != sizeof...(Args), bool> process_tuple(std::tuple<Args...>& value) noexcept {
bool res = child_.process(std::get<Index>(value));
return process_tuple<Index + 1>(value) && res;
}

template<size_t Index = 0, typename... Args>
std::enable_if_t<Index == sizeof...(Args), bool> process_tuple(std::tuple<Args...>& /*unused*/) noexcept {
return true;
}

bool is_ok_{true};
const ExtraRefCnt memory_ref_cnt_{ExtraRefCnt::extra_ref_cnt_value(0)};
Child& child_;
};

} // namespace kphp::visitors
Loading
Loading