From 88fb71fb096e4f0fa6eeef19fddee582a2bc6e2b Mon Sep 17 00:00:00 2001 From: Alexander Polyakov Date: Thu, 30 Apr 2026 19:46:18 +0300 Subject: [PATCH 1/4] support op_add in CollectConstVarsPass --- compiler/pipes/collect-const-vars.cpp | 86 +++++++++++++++++++++------ compiler/pipes/collect-const-vars.h | 1 - 2 files changed, 68 insertions(+), 19 deletions(-) diff --git a/compiler/pipes/collect-const-vars.cpp b/compiler/pipes/collect-const-vars.cpp index e0d774d625..07cea1104e 100644 --- a/compiler/pipes/collect-const-vars.cpp +++ b/compiler/pipes/collect-const-vars.cpp @@ -4,7 +4,11 @@ #include "compiler/pipes/collect-const-vars.h" -#include "compiler/data/src-file.h" +#include + +#include "auto/compiler/vertex/vertex-types.h" +#include "common/algorithms/hashes.h" +#include "compiler/data/vertex-adaptor.h" #include "compiler/vertex-util.h" #include "compiler/data/var-data.h" #include "compiler/const-manipulations.h" @@ -17,22 +21,26 @@ template struct VertexVisitor { static ResultType visit(VertexPtr v) { switch (v->type()) { - case op_array: - return Derived::on_array(v.as()); - case op_string: - return Derived::on_string(v.as()); - case op_concat: - return Derived::on_concat(v.as()); - case op_conv_regexp: - return Derived::on_conv_regexp(v.as()); - case op_func_call: - return Derived::on_func_call(v.as()); - case op_string_build: - return Derived::on_string_build(v.as()); - case op_define_val: - return Derived::on_define_val(v.as()); - default: - return Derived::fallback(v); + case op_array: + return Derived::on_array(v.as()); + case op_string: + return Derived::on_string(v.as()); + case op_concat: + return Derived::on_concat(v.as()); + case op_add: + return Derived::on_add(v.as()); + case op_conv_regexp: + return Derived::on_conv_regexp(v.as()); + case op_func_call: + return Derived::on_func_call(v.as()); + case op_string_build: + return Derived::on_string_build(v.as()); + case op_define_val: + return Derived::on_define_val(v.as()); + case op_var: + return Derived::on_var(v.as()); + default: + return Derived::fallback(v); } } @@ -48,6 +56,10 @@ struct VertexVisitor { return Derived::fallback(v); } + static ResultType on_add(VertexAdaptor v) { + return Derived::fallback(v); + } + static ResultType on_conv_regexp(VertexAdaptor v) { return Derived::fallback(v); } @@ -64,6 +76,10 @@ struct VertexVisitor { return Derived::fallback(v); } + static ResultType on_var(VertexAdaptor v) { + return Derived::fallback(v); + } + static ResultType fallback(VertexPtr v [[maybe_unused]]) { kphp_assert_msg(false, "Internal error: invalid visitor in CollectConstVars pass!"); } @@ -78,6 +94,14 @@ struct IsComposite : public VertexVisitor { return true; } + static bool on_add(VertexAdaptor v) { + return v && (visit(v->lhs()) || visit(v->rhs())); + } + + static bool on_var(VertexAdaptor v) { + return v && v->var_id && v->var_id->init_val && visit(v->var_id->init_val); + } + static bool fallback(VertexPtr v [[maybe_unused]]) { return false; } @@ -91,7 +115,7 @@ struct ShouldStoreOnTopDown : public VertexVisitor { static bool on_func_call(VertexAdaptor v) { // const constructors are handled in on_define_val - auto res = v->func_id && v->func_id->is_pure; + auto res = v->func_id && v->func_id->is_pure; return res; } @@ -107,6 +131,19 @@ struct ShouldStoreOnBottomUp : public VertexVisitor return res; } + static bool on_add(VertexAdaptor v) { + // This optimization moves constant expression evaluation from request execution time to kPHP initialization + // stage (which runs before the first request is processed). During initialization, we use the kPHP runtime + // to evaluate the '+' operation. This is safe even for edge cases like adding an integer to a string constant + // (e.g., 'c_str$ + 1', where 'c_str$' is an auto-generated constant variable name), because the runtime + // handles type coercion just as it would during normal request execution. + return v && visit(v->lhs()) && visit(v->rhs()); + } + + static bool on_var(VertexAdaptor v) { + return v && v->var_id && v->var_id->init_val && visit(v->var_id->init_val); + } + static bool fallback(VertexPtr v) { return vk::any_of_equal(v->type(), op_string, op_array); } @@ -137,6 +174,15 @@ struct NameGenerator : public VertexVisitor { return fallback(v); } + static std::string on_add(VertexAdaptor v) { + return fmt_format("c_add${:x}", vk::std_hash(visit(v->lhs()) + visit(v->rhs()))); + } + + static std::string on_var(VertexAdaptor v) { + kphp_assert_msg(v && v->var_id && v->var_id->init_val, "Internal error: expected op_var to be fully defined"); + return fmt_format("c_var${:x}", vk::std_hash(visit(v->var_id->init_val))); + } + static std::string on_conv_regexp(VertexAdaptor v) { if (v->expr()->type() == op_string) { return gen_const_regexp_name(v->expr().as()->str_val); @@ -194,6 +240,10 @@ struct ProcessBeforeReplace : public VertexVisitor add) { + return remove_op_define_val(add); + } + private: static VertexPtr remove_op_define_val(VertexPtr v) { for (VertexPtr& sub_vertex: *v) { diff --git a/compiler/pipes/collect-const-vars.h b/compiler/pipes/collect-const-vars.h index 3a927c0d8b..4d67597216 100644 --- a/compiler/pipes/collect-const-vars.h +++ b/compiler/pipes/collect-const-vars.h @@ -4,7 +4,6 @@ #pragma once -#include "compiler/data/class-data.h" #include "compiler/function-pass.h" /*** Replace constant expressions with const variables ***/ From 43f0490c1c3b4fc8be92a5b84ffb95e5a2ec3481 Mon Sep 17 00:00:00 2001 From: Alexander Polyakov Date: Sat, 2 May 2026 20:21:24 +0300 Subject: [PATCH 2/4] relax restrictions on op_add in CollectConstVarsPass --- compiler/pipes/collect-const-vars.cpp | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/compiler/pipes/collect-const-vars.cpp b/compiler/pipes/collect-const-vars.cpp index 07cea1104e..45d77bf86d 100644 --- a/compiler/pipes/collect-const-vars.cpp +++ b/compiler/pipes/collect-const-vars.cpp @@ -131,21 +131,13 @@ struct ShouldStoreOnBottomUp : public VertexVisitor return res; } - static bool on_add(VertexAdaptor v) { - // This optimization moves constant expression evaluation from request execution time to kPHP initialization + static bool fallback(VertexPtr v) { + // `op_add`: this optimization moves constant expression evaluation from request execution time to kPHP initialization // stage (which runs before the first request is processed). During initialization, we use the kPHP runtime // to evaluate the '+' operation. This is safe even for edge cases like adding an integer to a string constant // (e.g., 'c_str$ + 1', where 'c_str$' is an auto-generated constant variable name), because the runtime // handles type coercion just as it would during normal request execution. - return v && visit(v->lhs()) && visit(v->rhs()); - } - - static bool on_var(VertexAdaptor v) { - return v && v->var_id && v->var_id->init_val && visit(v->var_id->init_val); - } - - static bool fallback(VertexPtr v) { - return vk::any_of_equal(v->type(), op_string, op_array); + return vk::any_of_equal(v->type(), op_string, op_array, op_add); } }; From 67bb87f023fe5b4667b41d639340aa0af0dffc8a Mon Sep 17 00:00:00 2001 From: Alexander Polyakov Date: Sat, 2 May 2026 20:49:39 +0300 Subject: [PATCH 3/4] remove redundant extern declarations --- compiler/pipes/collect-const-vars.cpp | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/compiler/pipes/collect-const-vars.cpp b/compiler/pipes/collect-const-vars.cpp index 45d77bf86d..201b3bed8b 100644 --- a/compiler/pipes/collect-const-vars.cpp +++ b/compiler/pipes/collect-const-vars.cpp @@ -37,8 +37,6 @@ struct VertexVisitor { return Derived::on_string_build(v.as()); case op_define_val: return Derived::on_define_val(v.as()); - case op_var: - return Derived::on_var(v.as()); default: return Derived::fallback(v); } @@ -76,10 +74,6 @@ struct VertexVisitor { return Derived::fallback(v); } - static ResultType on_var(VertexAdaptor v) { - return Derived::fallback(v); - } - static ResultType fallback(VertexPtr v [[maybe_unused]]) { kphp_assert_msg(false, "Internal error: invalid visitor in CollectConstVars pass!"); } @@ -94,14 +88,6 @@ struct IsComposite : public VertexVisitor { return true; } - static bool on_add(VertexAdaptor v) { - return v && (visit(v->lhs()) || visit(v->rhs())); - } - - static bool on_var(VertexAdaptor v) { - return v && v->var_id && v->var_id->init_val && visit(v->var_id->init_val); - } - static bool fallback(VertexPtr v [[maybe_unused]]) { return false; } @@ -170,11 +156,6 @@ struct NameGenerator : public VertexVisitor { return fmt_format("c_add${:x}", vk::std_hash(visit(v->lhs()) + visit(v->rhs()))); } - static std::string on_var(VertexAdaptor v) { - kphp_assert_msg(v && v->var_id && v->var_id->init_val, "Internal error: expected op_var to be fully defined"); - return fmt_format("c_var${:x}", vk::std_hash(visit(v->var_id->init_val))); - } - static std::string on_conv_regexp(VertexAdaptor v) { if (v->expr()->type() == op_string) { return gen_const_regexp_name(v->expr().as()->str_val); @@ -267,10 +248,8 @@ void set_var_dep_level(VarPtr var_id) { } } - } // namespace - VertexPtr CollectConstVarsPass::on_exit_vertex(VertexPtr root) { if (root->const_type == cnst_const_val) { composite_const_depth_ -= static_cast(IsComposite::visit(root)); From f35ed924e467f64f7bef27ab6cc6f06a31365ed3 Mon Sep 17 00:00:00 2001 From: Alexander Polyakov Date: Sun, 3 May 2026 17:09:27 +0300 Subject: [PATCH 4/4] limit optimization to arr + arr case only --- compiler/pipes/collect-const-vars.cpp | 39 ++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/compiler/pipes/collect-const-vars.cpp b/compiler/pipes/collect-const-vars.cpp index 201b3bed8b..7767bad996 100644 --- a/compiler/pipes/collect-const-vars.cpp +++ b/compiler/pipes/collect-const-vars.cpp @@ -9,6 +9,7 @@ #include "auto/compiler/vertex/vertex-types.h" #include "common/algorithms/hashes.h" #include "compiler/data/vertex-adaptor.h" +#include "compiler/kphp_assert.h" #include "compiler/vertex-util.h" #include "compiler/data/var-data.h" #include "compiler/const-manipulations.h" @@ -117,13 +118,39 @@ struct ShouldStoreOnBottomUp : public VertexVisitor return res; } + // This optimization moves constant expression evaluation from request execution time to kPHP initialization + // stage (which runs before the first request is processed). During initialization, we use the kPHP runtime + // to evaluate the '+' operation. This is safe even for edge cases like adding an integer to a string constant + // (e.g., 'c_str$ + 1', where 'c_str$' is an auto-generated constant variable name), because the runtime + // handles type coercion just as it would during normal request execution. However, due to current limitations in + // K2 runtime's impementation of string addition (it requires RuntimeContext to be initialized), we limit this + // optimization to `arr + arr` case for now. + static bool on_add(VertexAdaptor v) { + static constexpr auto is_suitable_op = [](const auto& self, VertexPtr v, Operation target_op) { + if (!v) { + return false; + } + + switch (v->type()) { + case op_var: + if (const auto var = v.as(); var && var->var_id && var->var_id->init_val) { + return self(self, var->var_id->init_val, target_op); + } + return false; + case op_add: { + const auto add = v.as(); + return self(self, add->lhs(), target_op) && self(self, add->rhs(), target_op); + } + default: + return v->type() == target_op; + } + }; + + return v && (is_suitable_op(is_suitable_op, v->lhs(), op_array) && is_suitable_op(is_suitable_op, v->rhs(), op_array)); + } + static bool fallback(VertexPtr v) { - // `op_add`: this optimization moves constant expression evaluation from request execution time to kPHP initialization - // stage (which runs before the first request is processed). During initialization, we use the kPHP runtime - // to evaluate the '+' operation. This is safe even for edge cases like adding an integer to a string constant - // (e.g., 'c_str$ + 1', where 'c_str$' is an auto-generated constant variable name), because the runtime - // handles type coercion just as it would during normal request execution. - return vk::any_of_equal(v->type(), op_string, op_array, op_add); + return vk::any_of_equal(v->type(), op_string, op_array); } };