From fc8b6f3e0b7f05a4523d6dc642f9fb5f351b41ae Mon Sep 17 00:00:00 2001 From: Mikhail Kornaukhov Date: Fri, 11 Oct 2024 15:31:29 +0300 Subject: [PATCH 01/44] Initial experiments --- compiler/class-assumptions.cpp | 16 +++++++- compiler/code-gen/vertex-compiler.cpp | 41 ++++++++++++++++++++ compiler/inferring/type-data.cpp | 21 ++++++++++ compiler/inferring/type-inferer.cpp | 9 ++++- compiler/pipes/filter-only-actually-used.cpp | 25 +++++++++++- compiler/pipes/final-check.cpp | 10 ++++- compiler/pipes/gen-tree-postprocess.cpp | 8 ++++ compiler/pipes/optimization.cpp | 26 +++++++++++++ compiler/vertex-desc.json | 18 ++++++++- 9 files changed, 168 insertions(+), 6 deletions(-) diff --git a/compiler/class-assumptions.cpp b/compiler/class-assumptions.cpp index 855887eb37..71cc62299b 100644 --- a/compiler/class-assumptions.cpp +++ b/compiler/class-assumptions.cpp @@ -30,6 +30,7 @@ */ #include "compiler/class-assumptions.h" +#include "compiler/inferring/primitive-type.h" #include #include "compiler/data/class-data.h" @@ -81,9 +82,11 @@ Assumption Assumption::get_subkey_by_index(VertexPtr index_key) const { const TypeHint *type_hint = assumption_unwrap_optional(assum_hint); if (type_hint == nullptr) { + printf("asuumption by index nullptr\n"); return {}; } if (const auto *as_array = type_hint->try_as()) { + printf("assumption array\n"); return Assumption(as_array->inner); } if (const auto *as_tuple = type_hint->try_as()) { @@ -102,6 +105,12 @@ Assumption Assumption::get_subkey_by_index(VertexPtr index_key) const { } } } + if (const auto *as_instance = type_hint->try_as()) { + printf("$$$$$$$$$ A S I N S T A N C E $$$$$$$$$$"); + // TODO when execution goes here???? + // TODO resolve here and somehow check (here or later) that class implements ArrayAccess + return Assumption(TypeHintPrimitive::create(PrimitiveType::tp_mixed)); + } return {}; } @@ -324,10 +333,11 @@ class CalcAssumptionForVarPass final : public FunctionPassBase { , var_name(var_name) {} void on_start() override { -// printf("start assumptions for %s() $%s\n", current_function->as_human_readable().c_str(), std::string(var_name).c_str()); + printf("start assumptions for %s() $%s\n", current_function->as_human_readable().c_str(), std::string(var_name).c_str()); } VertexPtr on_enter_vertex(VertexPtr root) override { + puts("HERE88888888888!"); if (stopped) { return root; } @@ -751,6 +761,7 @@ Assumption assume_class_of_expr(FunctionPtr f, VertexPtr root, VertexPtr stop_at case op_ffi_array_get: return Assumption(root.as()->c_elem_type); case op_index: { + puts("OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO"); auto index = root.as(); if (index->has_key()) { return assume_class_of_expr(f, index->array(), stop_at).get_subkey_by_index(index->key()); @@ -779,6 +790,9 @@ Assumption assume_class_of_expr(FunctionPtr f, VertexPtr root, VertexPtr stop_at case op_move: return assume_class_of_expr(f, root.as()->expr(), stop_at); case op_set: + printf("On op_set assume:\n"); + // root.debugPrint(); + // puts("---------"); return assume_class_of_expr(f, root.as()->rhs(), stop_at); case op_define_val: return assume_class_of_expr(f, root.as()->value(), stop_at); diff --git a/compiler/code-gen/vertex-compiler.cpp b/compiler/code-gen/vertex-compiler.cpp index 19b3089398..82b99f2ef1 100644 --- a/compiler/code-gen/vertex-compiler.cpp +++ b/compiler/code-gen/vertex-compiler.cpp @@ -4,6 +4,12 @@ #include "compiler/code-gen/vertex-compiler.h" +#include "auto/compiler/vertex/vertex-types.h" +#include "compiler/code-gen/code-generator.h" +#include "compiler/data/vertex-adaptor.h" +#include "compiler/inferring/primitive-type.h" +#include "compiler/kphp_assert.h" +#include #include #include @@ -1808,6 +1814,34 @@ void compile_index_of_string(VertexAdaptor root, CodeGenerator &W) { W << root->array() << ".get_value(" << root->key() << ")"; } +void compile_index_of_object(VertexAdaptor root, CodeGenerator &W) { + // For now, it never calls because in optimization pass op_index -> op_func_call "f$className$$offsetGet" + // It should be not only the var, but function call, too + auto as_var = root->array().try_as(); + auto var_id = as_var->var_id; + kphp_assert_msg(var_id, "Var is not var sadly"); + + printf("var name = %s\n", var_id->name.c_str()); + + const auto *tpe = as_var->tinf_node.get_type(); + kphp_assert_msg(tpe, "cannot get tinf node"); + + printf("type = %s\n", tpe->as_human_readable().c_str()); + + auto klass = tpe->class_type(); + kphp_assert_msg(klass, "Klass badly does not exist"); + + // klass->debugPrint(); + + const auto *method = klass->get_instance_method("offsetGet"); + + kphp_assert_msg(method, "no method \"offsetGet\""); + + std::string method_name = "f$" + method->global_name(); + W << method_name << "(" << as_var << ","; + W << root->key() << ")"; // check has_key and pass null or smth like that +} + void compile_instance_prop(VertexAdaptor root, CodeGenerator &W) { switch (root->access_type) { case InstancePropAccessType::Default: @@ -1848,6 +1882,10 @@ void compile_instance_prop(VertexAdaptor root, CodeGenerator & void compile_index(VertexAdaptor root, CodeGenerator &W) { PrimitiveType array_ptype = root->array()->tinf_node.get_type()->ptype(); + printf("COMPILING INDEX!!!!!!\n"); + printf("root = "); + // root.debugPrint(); + printf("type = %d\n", (int)array_ptype); switch (array_ptype) { case tp_string: @@ -1859,6 +1897,9 @@ void compile_index(VertexAdaptor root, CodeGenerator &W) { case tp_shape: W << ShapeGetIndex(root->array(), root->key()); break; + case tp_Class: + compile_index_of_object(root, W); + break; default: compile_index_of_array(root, W); } diff --git a/compiler/inferring/type-data.cpp b/compiler/inferring/type-data.cpp index 41efacd742..e55b9b0569 100644 --- a/compiler/inferring/type-data.cpp +++ b/compiler/inferring/type-data.cpp @@ -4,6 +4,7 @@ #include "compiler/inferring/type-data.h" +#include "compiler/inferring/primitive-type.h" #include #include @@ -351,6 +352,26 @@ const TypeData *TypeData::const_read_at(const Key &key) const { if (ptype() == tp_string) { return get_type(tp_string); } + if (ptype() == tp_Class) { + // TODO any race conditions? + puts("Wanna inference type for const_read_at"); + if (!class_type_.empty()) { + puts("class types are not empty!"); + auto klass = class_type(); // Here is the place to think about inheritance stuff + // TODO better check here + // What is first: checking interface methods compatibility or type inference? Looks like there is no happens-before relation + const bool impl_aa = + std::find_if(klass->implements.begin(), klass->implements.end(), [](ClassPtr x) { return x->name == "ArrayAccess"; }) != klass->implements.end(); + + printf("implementing ArrayAccess: %s\n", (impl_aa ? "yes" : "no")); + if (impl_aa) { + return get_type(tp_mixed); + } + + } else { + puts("class types is empty! =("); + } + } if (!structured()) { return get_type(tp_any); } diff --git a/compiler/inferring/type-inferer.cpp b/compiler/inferring/type-inferer.cpp index ae08fa6744..503760ce8b 100644 --- a/compiler/inferring/type-inferer.cpp +++ b/compiler/inferring/type-inferer.cpp @@ -25,16 +25,21 @@ void TypeInferer::recalc_node(Node *node) { } void TypeInferer::add_node(Node *node) { - //fprintf (stderr, "tinf::add_node %d %p %s\n", get_thread_id(), node, node->get_description().c_str()); + if (!node->was_recalc_started_at_least_once()) { recalc_node(node); } + if (node) { + // fprintf (stderr, "tinf::add_node %d %p %s\n", get_thread_id(), node, node->get_description().c_str()); + } } void TypeInferer::add_edge(const Edge *edge) { - //fprintf (stderr, "add_edge %d [%p %s] -> [%p %s]\n", get_thread_id(), edge->from, edge->from->get_description().c_str(), edge->to, edge->to->get_description().c_str()); edge->from->register_edge_from_this(edge); edge->to->register_edge_to_this(edge); + if (edge) { + // fprintf (stderr, "add_edge %d [%p] -> [%p]\n", get_thread_id(), edge->from, edge->to); + } } void TypeInferer::add_restriction(RestrictionBase *restriction) { diff --git a/compiler/pipes/filter-only-actually-used.cpp b/compiler/pipes/filter-only-actually-used.cpp index 8cfbdf905d..000b64a06c 100644 --- a/compiler/pipes/filter-only-actually-used.cpp +++ b/compiler/pipes/filter-only-actually-used.cpp @@ -4,6 +4,7 @@ #include "compiler/pipes/filter-only-actually-used.h" +#include "common/algorithms/find.h" #include "compiler/data/class-data.h" #include "compiler/data/function-data.h" #include "compiler/data/src-file.h" @@ -239,13 +240,27 @@ IdMap calc_actually_used_having_call_edges(std::vectormodifiers.is_instance()) { + return false; + } + ClassPtr klass = fun->class_id; + const bool impl_aa = + std::find_if(klass->implements.begin(), klass->implements.end(), [](ClassPtr x) { return x->name == "ArrayAccess"; }) != klass->implements.end(); + + return impl_aa && vk::any_of_equal(fun->local_name(), "offsetGet", "offsetSet", "offsetExists", "offsetUnset"); + }(); + + // TODO think about more accurate check const bool should_be_used_apriori = fun->is_main_function() || fun->type == FunctionData::func_class_holder || // classes should be carried along the pipeline (fun->is_extern() && vk::any_of_equal(fun->name, "wait", "make_clone")) || fun->kphp_lib_export || (fun->modifiers.is_instance() && fun->local_name() == ClassData::NAME_OF_TO_STRING) || - (fun->modifiers.is_instance() && fun->local_name() == ClassData::NAME_OF_WAKEUP); + (fun->modifiers.is_instance() && fun->local_name() == ClassData::NAME_OF_WAKEUP) || + is_array_access_fun; if (should_be_used_apriori && !used_functions[fun]) { calc_actually_used_dfs(fun, used_functions, call_graph); } @@ -302,6 +317,10 @@ void FilterOnlyActuallyUsedFunctionsF::on_finish(DataStream &os) { for (int id = 0; id < all.size(); ++id) { kphp_assert(get_index(all[id].first) == -1); set_index(all[id].first, id); + auto &func_data = all[id].first; + if (func_data->name.find("offsetGet") != std::string::npos) { + printf("In all func: %s (local name = %s)\n", func_data->name.c_str(), func_data->local_name().data()); + } } // uncomment this to debug "invalid index of IdMap: -1" @@ -347,6 +366,10 @@ void FilterOnlyActuallyUsedFunctionsF::on_finish(DataStream &os) { // this should be the last step for (const auto &f : used_functions) { if (f) { + const auto &func_data = f; + if (func_data->name.find("offsetGet") != std::string::npos) { + printf("In used func: %s\n", func_data->name.c_str()); + } os << f; } } diff --git a/compiler/pipes/final-check.cpp b/compiler/pipes/final-check.cpp index 466c02f426..2bd998bfc4 100644 --- a/compiler/pipes/final-check.cpp +++ b/compiler/pipes/final-check.cpp @@ -604,6 +604,13 @@ void FinalCheckPass::on_start() { } VertexPtr FinalCheckPass::on_enter_vertex(VertexPtr vertex) { + if (current_function->name == "test") { + if (auto as_op_func = vertex.try_as()) { + // op_index + printf("\n====== test in FinalCheckPass ======\n"); + as_op_func.debugPrint(); + } + } if (vertex->type() == op_func_name) { kphp_error (0, fmt_format("Unexpected {} (maybe, it should be a define?)", vertex->get_string())); } @@ -1027,7 +1034,8 @@ void FinalCheckPass::raise_error_using_Unknown_type(VertexPtr v) { } else if (tinf::get_type(var)->get_real_ptype() == tp_shape) { kphp_error(0, fmt_format("Accessing unexisting element of shape ${}", var->name)); } else { - kphp_error(0, fmt_format("${} is {}, can not get element", var->name, tinf::get_type(var)->as_human_readable())); + // TODO fix smth here + // kphp_error(0, fmt_format("${} is {}, can not get element", var->name, tinf::get_type(var)->as_human_readable())); } } else { // multidimentional array[*]...[*] access diff --git a/compiler/pipes/gen-tree-postprocess.cpp b/compiler/pipes/gen-tree-postprocess.cpp index fd1f1c81b4..10ffbbca58 100644 --- a/compiler/pipes/gen-tree-postprocess.cpp +++ b/compiler/pipes/gen-tree-postprocess.cpp @@ -4,6 +4,7 @@ #include "compiler/pipes/gen-tree-postprocess.h" +#include "auto/compiler/vertex/vertex-types.h" #include "compiler/compiler-core.h" #include "compiler/data/class-data.h" #include "compiler/data/lib-data.h" @@ -155,6 +156,13 @@ GenTreePostprocessPass::builtin_fun GenTreePostprocessPass::get_builtin_function } VertexPtr GenTreePostprocessPass::on_enter_vertex(VertexPtr root) { + if (current_function->name == "test") { + if (auto as_op_func = root.try_as()) { + // op_index + printf("\n====== test in GenTreePostprocessPass ======\n"); + as_op_func.debugPrint(); + } + } stage::set_line(root->location.line); if (auto set_op = root.try_as()) { // list(...) = ... or short syntax (PHP 7) [...] = ... diff --git a/compiler/pipes/optimization.cpp b/compiler/pipes/optimization.cpp index 9bc407896e..9d100f7544 100644 --- a/compiler/pipes/optimization.cpp +++ b/compiler/pipes/optimization.cpp @@ -4,6 +4,9 @@ #include "compiler/pipes/optimization.h" +#include "compiler/inferring/primitive-type.h" +#include "compiler/kphp_assert.h" +#include "compiler/operation.h" #include #include "common/algorithms/hashes.h" @@ -93,6 +96,7 @@ void explicit_cast_array_type(VertexPtr &type_acceptor, const TypeData *required } // namespace VertexPtr OptimizationPass::optimize_set_push_back(VertexAdaptor set_op) { + // TODO embed here if (set_op->lhs()->type() != op_index) { return set_op; } @@ -177,6 +181,28 @@ VertexPtr OptimizationPass::optimize_index(VertexAdaptor index) { kphp_error (0, "Cannot use [] for reading"); } } + + auto &lhs = index->array(); + const auto *tpe = lhs->tinf_node.get_type(); + if (tpe->get_real_ptype() == tp_Class) { + puts("OUR CASE!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); + // index.debugPrint(); + + auto klass = tpe->class_type(); + kphp_assert_msg(klass, "bad klass"); + + const auto *method = klass->get_instance_method("offsetGet"); + + // TODO assume here that key is present + auto new_call = VertexAdaptor::create(lhs, index->key()).set_location(lhs); + new_call->str_val = method->global_name(); + new_call->func_id = method->function; + new_call->extra_type = op_ex_func_call_arrow; // Is that right? + new_call->auto_inserted = true; + + return new_call; + } + return index; } VertexPtr OptimizationPass::remove_extra_conversions(VertexPtr root) { diff --git a/compiler/vertex-desc.json b/compiler/vertex-desc.json index 493011e3c0..b88a20d3dd 100644 --- a/compiler/vertex-desc.json +++ b/compiler/vertex-desc.json @@ -1727,7 +1727,7 @@ } }, { - "comment": "array()[key()]; or array()[]", + "comment": "array()[key()]; or array()[] or Obj()[key()]; or Obj()[]", "sons": { "array": 0, "key": { @@ -1776,6 +1776,22 @@ "str": "set_value" } }, + { + "comment": "lowered form of Obj()[key()] = value()", + "sons": { + "obj": 0, + "key": 1, + "value": 2 + }, + "name": "op_offset_set", + "base_name": "meta_op_base", + "props": { + "rl": "rl_error", + "cnst": "cnst_error", + "type": "common_op", + "str": "offset_set" + } + }, { "comment": "false literal", "name": "op_false", From 4bf010e82001c1b4decce3a6dcf3856dfcad811e Mon Sep 17 00:00:00 2001 From: Mikhail Kornaukhov Date: Fri, 11 Oct 2024 20:09:19 +0300 Subject: [PATCH 02/44] logs for [.] = ... type inference --- compiler/inferring/type-data.cpp | 9 +++++++++ compiler/inferring/type-inferer.cpp | 10 ++++++---- compiler/inferring/var-node.cpp | 1 + 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/compiler/inferring/type-data.cpp b/compiler/inferring/type-data.cpp index e55b9b0569..f6dd0530b1 100644 --- a/compiler/inferring/type-data.cpp +++ b/compiler/inferring/type-data.cpp @@ -439,6 +439,13 @@ void TypeData::set_lca(const TypeData *rhs, bool save_or_false, bool save_or_nul TypeData *lhs = this; PrimitiveType new_ptype = type_lca(lhs->ptype(), rhs->ptype()); + if (lhs->ptype_ == tp_array && rhs->ptype_ == tp_Class) { + puts("xxxxxxxxxxxxxxxxxxxxxxxxxxx HIT IT xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); + printf("new type = %s\n", ptype_name(new_ptype)); + } + if (rhs->ptype_ == tp_array && lhs->ptype_ == tp_Class) { + puts("yyyyyyyyyyyyyyyyyyyyyyyyyyy HIT IT yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"); + } if (new_ptype == tp_mixed) { if (lhs->ptype() == tp_array && lhs->lookup_at_any_key()) { lhs->set_lca_at(MultiKey::any_key(1), TypeData::get_type(tp_mixed)); @@ -553,6 +560,8 @@ void TypeData::set_lca_at(const MultiKey &multi_key, const TypeData *rhs, bool s cur->set_lca(rhs, save_or_false, save_or_null, ffi_flags); if (cur->error_flag()) { // proxy tp_Error from keys to the type itself + puts("------------ GONNA ERROR ---------------"); + printf("%s -- %s\n", rhs->_debug_string().c_str(), cur->_debug_string().c_str()); this->set_ptype(tp_Error); } } diff --git a/compiler/inferring/type-inferer.cpp b/compiler/inferring/type-inferer.cpp index 503760ce8b..cc24670221 100644 --- a/compiler/inferring/type-inferer.cpp +++ b/compiler/inferring/type-inferer.cpp @@ -30,16 +30,18 @@ void TypeInferer::add_node(Node *node) { recalc_node(node); } if (node) { - // fprintf (stderr, "tinf::add_node %d %p %s\n", get_thread_id(), node, node->get_description().c_str()); + fprintf (stderr, "tinf::add_node %d %p %s\n", get_thread_id(), node, node->get_description().c_str()); } } void TypeInferer::add_edge(const Edge *edge) { - edge->from->register_edge_from_this(edge); - edge->to->register_edge_to_this(edge); if (edge) { - // fprintf (stderr, "add_edge %d [%p] -> [%p]\n", get_thread_id(), edge->from, edge->to); + auto lhs_type = edge->from->get_type()->as_human_readable(); + auto rhs_type = edge->to->get_type()->as_human_readable(); + fprintf(stderr, "add_edge %d [%p %s] -> [%p %s]\n", get_thread_id(), edge->from, lhs_type.c_str(), edge->to, rhs_type.c_str()); } + edge->from->register_edge_from_this(edge); + edge->to->register_edge_to_this(edge); } void TypeInferer::add_restriction(RestrictionBase *restriction) { diff --git a/compiler/inferring/var-node.cpp b/compiler/inferring/var-node.cpp index 7f7e6a045a..68ac9a7890 100644 --- a/compiler/inferring/var-node.cpp +++ b/compiler/inferring/var-node.cpp @@ -35,6 +35,7 @@ void VarNodeRecalc::do_recalc() { } // at first, we just calculate new_type_ without any checks for (const tinf::Edge *e : node->get_edges_from_this()) { + printf("via edge %p -> %p\n", e->from, e->to); set_lca_at(e->from_at, as_rvalue(e->to)); inferer_->add_node(e->to); } From 7c5fee9fac39e343e36f08c002508c82ffe7283a Mon Sep 17 00:00:00 2001 From: Mikhail Kornaukhov Date: Wed, 16 Oct 2024 13:52:39 +0300 Subject: [PATCH 03/44] works with write, but read not found function --- compiler/inferring/node-recalc.cpp | 2 +- compiler/inferring/primitive-type.h | 1 + compiler/inferring/type-data.cpp | 19 ++++++++++----- compiler/inferring/type-data.h | 1 + compiler/inferring/type-inferer.cpp | 5 ++-- compiler/inferring/var-node.cpp | 9 +++++-- compiler/pipes/optimization.cpp | 37 ++++++++++++++++++++++++++++- 7 files changed, 62 insertions(+), 12 deletions(-) diff --git a/compiler/inferring/node-recalc.cpp b/compiler/inferring/node-recalc.cpp index 4acfb9f82a..37e45b2c99 100644 --- a/compiler/inferring/node-recalc.cpp +++ b/compiler/inferring/node-recalc.cpp @@ -33,7 +33,7 @@ void NodeRecalc::on_new_type_became_tpError(const TypeData *because_of_type, con } else if ((mix_class || mix_class2) && !vk::any_of_equal(ptype_before_error, tp_tuple, tp_shape)) { const auto class_name = TermStringFormat::paint_green(mix_class ? mix_class->name : mix_class2->name); - kphp_error(0, fmt_format("Type Error: mix class {} with non-class: {} and {}\n", class_name, desc1, desc2)); + kphp_error(0, fmt_format("Type Error: mix class {} with non-class: {} [{}] and {} [{}]\n", class_name, desc1, (void*)node_, desc2, (void*)because_of_rvalue.node)); } else if (ptype_before_error == tp_tuple && because_of_type->ptype() == tp_tuple) { kphp_error(0, fmt_format("Type Error: inconsistent tuples {} and {}\n", desc1, desc2)); diff --git a/compiler/inferring/primitive-type.h b/compiler/inferring/primitive-type.h index f6fec41c0c..d8898465f2 100644 --- a/compiler/inferring/primitive-type.h +++ b/compiler/inferring/primitive-type.h @@ -6,6 +6,7 @@ #include +// The order is important! enum PrimitiveType { tp_any, tp_Null, diff --git a/compiler/inferring/type-data.cpp b/compiler/inferring/type-data.cpp index f6dd0530b1..0a6ec21480 100644 --- a/compiler/inferring/type-data.cpp +++ b/compiler/inferring/type-data.cpp @@ -408,6 +408,7 @@ const TypeData *TypeData::const_read_at(const MultiKey &multi_key) const { } void TypeData::make_structured() { + // TODO fix here for writing into objects that implements ArrayAccess // 'lvalue $s[idx]' makes $s array-typed: strings and tuples keep their types only for read-only operations if (ptype() < tp_array) { PrimitiveType new_ptype = type_lca(ptype(), tp_array); @@ -440,11 +441,13 @@ void TypeData::set_lca(const TypeData *rhs, bool save_or_false, bool save_or_nul PrimitiveType new_ptype = type_lca(lhs->ptype(), rhs->ptype()); if (lhs->ptype_ == tp_array && rhs->ptype_ == tp_Class) { - puts("xxxxxxxxxxxxxxxxxxxxxxxxxxx HIT IT xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); - printf("new type = %s\n", ptype_name(new_ptype)); - } - if (rhs->ptype_ == tp_array && lhs->ptype_ == tp_Class) { - puts("yyyyyyyyyyyyyyyyyyyyyyyyyyy HIT IT yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"); + puts("HITIT FIRST"); + if (lhs->get_write_flag()) { + // It means that lhs(==this) is something like that "$a[.] = " + new_ptype = tp_Class; // for array access + puts("HITIT SECOND"); + printf("new type = %s\n", ptype_name(new_ptype)); + } } if (new_ptype == tp_mixed) { if (lhs->ptype() == tp_array && lhs->lookup_at_any_key()) { @@ -542,7 +545,7 @@ void TypeData::set_lca_at(const MultiKey &multi_key, const TypeData *rhs, bool s for (const Key &key : multi_key) { auto *prev = cur; - cur = cur->write_at(key); + cur = cur->write_at(key); // HERE // handle writing to a subkey of mixed (when cur is not structured) if (cur == nullptr) { if (prev->ptype() == tp_mixed) { @@ -558,6 +561,10 @@ void TypeData::set_lca_at(const MultiKey &multi_key, const TypeData *rhs, bool s } } + if (cur->get_write_flag()) { + this->set_write_flag(); + } + cur->set_lca(rhs, save_or_false, save_or_null, ffi_flags); if (cur->error_flag()) { // proxy tp_Error from keys to the type itself puts("------------ GONNA ERROR ---------------"); diff --git a/compiler/inferring/type-data.h b/compiler/inferring/type-data.h index 740de71894..db0ef8bcc7 100644 --- a/compiler/inferring/type-data.h +++ b/compiler/inferring/type-data.h @@ -118,6 +118,7 @@ class TypeData { bool use_optional() const { return use_or_false() || use_or_null(); } void set_write_flag() { set_flag(); } + bool get_write_flag() { return get_flag(); } bool error_flag() const { return ptype_ == tp_Error; } diff --git a/compiler/inferring/type-inferer.cpp b/compiler/inferring/type-inferer.cpp index cc24670221..f055203510 100644 --- a/compiler/inferring/type-inferer.cpp +++ b/compiler/inferring/type-inferer.cpp @@ -30,7 +30,7 @@ void TypeInferer::add_node(Node *node) { recalc_node(node); } if (node) { - fprintf (stderr, "tinf::add_node %d %p %s\n", get_thread_id(), node, node->get_description().c_str()); + // fprintf (stderr, "tinf::add_node %d %p %s\n", get_thread_id(), node, node->get_description().c_str()); } } @@ -38,7 +38,8 @@ void TypeInferer::add_edge(const Edge *edge) { if (edge) { auto lhs_type = edge->from->get_type()->as_human_readable(); auto rhs_type = edge->to->get_type()->as_human_readable(); - fprintf(stderr, "add_edge %d [%p %s] -> [%p %s]\n", get_thread_id(), edge->from, lhs_type.c_str(), edge->to, rhs_type.c_str()); + // std::string via = edge->from_at ? edge->from_at->to_string().c_str() : ""; + // fprintf(stderr, "add_edge %d [%p %s] -> [%p %s] from at %s\n", get_thread_id(), edge->from, lhs_type.c_str(), edge->to, rhs_type.c_str(), via.c_str()); } edge->from->register_edge_from_this(edge); edge->to->register_edge_to_this(edge); diff --git a/compiler/inferring/var-node.cpp b/compiler/inferring/var-node.cpp index 68ac9a7890..2d917ceffe 100644 --- a/compiler/inferring/var-node.cpp +++ b/compiler/inferring/var-node.cpp @@ -8,6 +8,7 @@ #include "compiler/data/var-data.h" #include "compiler/inferring/edge.h" #include "compiler/inferring/node-recalc.h" +#include "compiler/inferring/primitive-type.h" #include "compiler/inferring/restriction-match-phpdoc.h" #include "compiler/vertex.h" @@ -35,8 +36,12 @@ void VarNodeRecalc::do_recalc() { } // at first, we just calculate new_type_ without any checks for (const tinf::Edge *e : node->get_edges_from_this()) { - printf("via edge %p -> %p\n", e->from, e->to); - set_lca_at(e->from_at, as_rvalue(e->to)); + std::string via = e->from_at ? e->from_at->to_string().c_str() : ""; + // printf("via edge %p -> %p (at %s)\n", e->from, e->to, via.c_str()); + if (node->var_ && node->var_->name == "abobus") { + puts("HERE!"); + } + set_lca_at(e->from_at, as_rvalue(e->to)); // go deep dive here inferer_->add_node(e->to); } diff --git a/compiler/pipes/optimization.cpp b/compiler/pipes/optimization.cpp index 9d100f7544..ed6ebf1333 100644 --- a/compiler/pipes/optimization.cpp +++ b/compiler/pipes/optimization.cpp @@ -4,6 +4,7 @@ #include "compiler/pipes/optimization.h" +#include "auto/compiler/vertex/vertex-types.h" #include "compiler/inferring/primitive-type.h" #include "compiler/kphp_assert.h" #include "compiler/operation.h" @@ -127,7 +128,40 @@ VertexPtr OptimizationPass::optimize_set_push_back(VertexAdaptor set_op) result = VertexAdaptor::create(a, c); } } else { - result = VertexAdaptor::create(a, b, c); + PrimitiveType a_ptype = tinf::get_type(a)->get_real_ptype(); + puts("HERE1"); + if (a_ptype == tp_Class) { + puts("HERE2"); + + auto klass = tinf::get_type(a)->class_type(); + kphp_assert_msg(klass, "bad klass"); + + puts("HERE3"); + + const auto *method = klass->get_instance_method("offsetSet"); + + kphp_assert_msg(klass, "bad method"); + + puts("HERE4"); + + + // TODO assume here that key is present + auto new_call = VertexAdaptor::create(a, b, c).set_location(set_op->get_location()); + puts("HERE5"); + + new_call->str_val = method->global_name(); + new_call->func_id = method->function; + new_call->extra_type = op_ex_func_call_arrow; // Is that right? + new_call->auto_inserted = true; + new_call->rl_type = set_op->rl_type; + + result = new_call; + + return result; + } else { + result = VertexAdaptor::create(a, b, c); + } + } result->location = set_op->get_location(); result->extra_type = op_ex_internal_func; @@ -199,6 +233,7 @@ VertexPtr OptimizationPass::optimize_index(VertexAdaptor index) { new_call->func_id = method->function; new_call->extra_type = op_ex_func_call_arrow; // Is that right? new_call->auto_inserted = true; + new_call->rl_type = index->rl_type; return new_call; } From de6a34a4110c2483e6cb8791c9100978e5cdf364 Mon Sep 17 00:00:00 2001 From: Mikhail Kornaukhov Date: Wed, 16 Oct 2024 20:32:12 +0300 Subject: [PATCH 04/44] tmp --- compiler/class-assumptions.cpp | 1 - compiler/code-gen/files/function-header.cpp | 6 ++++++ compiler/code-gen/files/function-source.cpp | 20 +++++++++++++++++++- compiler/code-gen/includes.cpp | 15 +++++++++++++++ compiler/code-gen/vertex-compiler.cpp | 18 +++++++++++++++--- compiler/inferring/var-node.cpp | 2 +- compiler/pipes/optimization.cpp | 17 +++++++++-------- 7 files changed, 65 insertions(+), 14 deletions(-) diff --git a/compiler/class-assumptions.cpp b/compiler/class-assumptions.cpp index 71cc62299b..d04043a745 100644 --- a/compiler/class-assumptions.cpp +++ b/compiler/class-assumptions.cpp @@ -337,7 +337,6 @@ class CalcAssumptionForVarPass final : public FunctionPassBase { } VertexPtr on_enter_vertex(VertexPtr root) override { - puts("HERE88888888888!"); if (stopped) { return root; } diff --git a/compiler/code-gen/files/function-header.cpp b/compiler/code-gen/files/function-header.cpp index f75890d0e4..e17fee2c08 100644 --- a/compiler/code-gen/files/function-header.cpp +++ b/compiler/code-gen/files/function-header.cpp @@ -50,6 +50,12 @@ void FunctionH::compile(CodeGenerator &W) const { W << CloseNamespace(); includes.start_next_block(); + + bool correct = function->name.find("test") != std::string::npos; + if (correct) { + puts("YYYYYYYY"); + } + includes.add_function_body_depends(function); W << includes; W << OpenNamespace(); diff --git a/compiler/code-gen/files/function-source.cpp b/compiler/code-gen/files/function-source.cpp index d464e457fc..c165d05671 100644 --- a/compiler/code-gen/files/function-source.cpp +++ b/compiler/code-gen/files/function-source.cpp @@ -12,6 +12,7 @@ #include "compiler/code-gen/naming.h" #include "compiler/code-gen/vertex-compiler.h" #include "compiler/stage.h" +#include FunctionCpp::FunctionCpp(FunctionPtr function) : function(function) { @@ -36,8 +37,16 @@ void FunctionCpp::compile(CodeGenerator &W) const { stage::set_function(function); function->name_gen_map = {}; // make codegeneration of this function idempotent + std::string name = function->name; + bool good = name.find("test") != std::string::npos; + if (good) { + + puts("YYYYYYYY"); + } + IncludesCollector includes; - includes.add_function_body_depends(function); + includes.add_function_body_depends(function); // here + W << includes; W << OpenNamespace(); @@ -50,6 +59,15 @@ void FunctionCpp::compile(CodeGenerator &W) const { W << function->root << NL; W << LockComments(); + if (good) { + static int here = 0; + here++; + printf("Attempt #%d\n", here); + // TODO + // understand why each var dump argument don't do anything during type infer + // Or why type info is present during code gen, but isn\t during optimization pass + } + W << CloseNamespace(); W << CloseFile(); } diff --git a/compiler/code-gen/includes.cpp b/compiler/code-gen/includes.cpp index 7cbf272c2a..6e0e7605af 100644 --- a/compiler/code-gen/includes.cpp +++ b/compiler/code-gen/includes.cpp @@ -10,10 +10,14 @@ #include "compiler/data/class-data.h" #include "compiler/data/function-data.h" #include "compiler/data/var-data.h" +#include ExternInclude::ExternInclude(vk::string_view file_name) : file_name(file_name) { kphp_assert(!file_name.empty()); + if (file_name.find("offsetGet") != std::string::npos) { + puts("Gotit"); + } } void ExternInclude::compile(CodeGenerator &W) const { @@ -58,6 +62,10 @@ void IncludesCollector::add_function_body_depends(const FunctionPtr &function) { lib_headers_.emplace(to_include->header_full_name, std::move(relative_path)); } else if (!to_include->is_extern() || to_include->need_generated_stub) { kphp_assert(!to_include->header_full_name.empty()); + auto file_name = to_include->name; + if (file_name.find("offsetGet") != std::string::npos) { + puts("Gotit x2"); + } internal_headers_.emplace(to_include->header_full_name); } } @@ -130,6 +138,12 @@ void IncludesCollector::add_all_class_types(const TypeData &tinf_type) { } void IncludesCollector::add_raw_filename_include(const std::string &file_name) { + if (file_name.find("bar_baz") != std::string::npos) { + puts("YEAH!"); + } + if (file_name.find("offsetGet") != std::string::npos) { + puts("DONE!!!!"); + } internal_headers_.emplace(file_name); } @@ -141,6 +155,7 @@ void IncludesCollector::add_vertex_depends(VertexPtr v) { add_vertex_depends(child); } if (auto as_func_call = v.try_as()) { + printf("func name = %s\n", as_func_call->func_id ? as_func_call->func_id->name.c_str() : ""); if (as_func_call->func_id) { const auto &header_full_name = as_func_call->func_id->header_full_name; if (!header_full_name.empty()) { diff --git a/compiler/code-gen/vertex-compiler.cpp b/compiler/code-gen/vertex-compiler.cpp index 82b99f2ef1..6f64ee9b3a 100644 --- a/compiler/code-gen/vertex-compiler.cpp +++ b/compiler/code-gen/vertex-compiler.cpp @@ -1815,7 +1815,7 @@ void compile_index_of_string(VertexAdaptor root, CodeGenerator &W) { } void compile_index_of_object(VertexAdaptor root, CodeGenerator &W) { - // For now, it never calls because in optimization pass op_index -> op_func_call "f$className$$offsetGet" + // It calls when root node is argument of var_dump func // It should be not only the var, but function call, too auto as_var = root->array().try_as(); auto var_id = as_var->var_id; @@ -1826,17 +1826,22 @@ void compile_index_of_object(VertexAdaptor root, CodeGenerator &W) { const auto *tpe = as_var->tinf_node.get_type(); kphp_assert_msg(tpe, "cannot get tinf node"); - printf("type = %s\n", tpe->as_human_readable().c_str()); + // printf("type = %s\n", tpe->as_human_readable().c_str()); auto klass = tpe->class_type(); kphp_assert_msg(klass, "Klass badly does not exist"); - // klass->debugPrint(); + + puts("WTFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"); const auto *method = klass->get_instance_method("offsetGet"); + kphp_assert_msg(method, "no method \"offsetGet\""); + // TODO check how is it synchronized with inlude collector + W.get_context().parent_func->dep.push_back(method->function); + std::string method_name = "f$" + method->global_name(); W << method_name << "(" << as_var << ","; W << root->key() << ")"; // check has_key and pass null or smth like that @@ -2438,6 +2443,13 @@ void compile_vertex(VertexPtr root, CodeGenerator &W) { W << UpdateLocation(root->location); + if (auto as_func = root.try_as()) { + if (as_func->func_id && as_func->func_id->name.find("test") != std::string::npos) { + puts("-----------------------------------------------------------"); + as_func.debugPrint(); + } + } + bool close_par = root->val_ref_flag == val_r || root->val_ref_flag == val_l; if (root->val_ref_flag == val_r) { diff --git a/compiler/inferring/var-node.cpp b/compiler/inferring/var-node.cpp index 2d917ceffe..3428ae40bd 100644 --- a/compiler/inferring/var-node.cpp +++ b/compiler/inferring/var-node.cpp @@ -39,7 +39,7 @@ void VarNodeRecalc::do_recalc() { std::string via = e->from_at ? e->from_at->to_string().c_str() : ""; // printf("via edge %p -> %p (at %s)\n", e->from, e->to, via.c_str()); if (node->var_ && node->var_->name == "abobus") { - puts("HERE!"); + // puts("HERE!"); } set_lca_at(e->from_at, as_rvalue(e->to)); // go deep dive here inferer_->add_node(e->to); diff --git a/compiler/pipes/optimization.cpp b/compiler/pipes/optimization.cpp index ed6ebf1333..67ef738537 100644 --- a/compiler/pipes/optimization.cpp +++ b/compiler/pipes/optimization.cpp @@ -129,25 +129,17 @@ VertexPtr OptimizationPass::optimize_set_push_back(VertexAdaptor set_op) } } else { PrimitiveType a_ptype = tinf::get_type(a)->get_real_ptype(); - puts("HERE1"); if (a_ptype == tp_Class) { - puts("HERE2"); - auto klass = tinf::get_type(a)->class_type(); kphp_assert_msg(klass, "bad klass"); - puts("HERE3"); - const auto *method = klass->get_instance_method("offsetSet"); kphp_assert_msg(klass, "bad method"); - puts("HERE4"); - // TODO assume here that key is present auto new_call = VertexAdaptor::create(a, b, c).set_location(set_op->get_location()); - puts("HERE5"); new_call->str_val = method->global_name(); new_call->func_id = method->function; @@ -156,6 +148,8 @@ VertexPtr OptimizationPass::optimize_set_push_back(VertexAdaptor set_op) new_call->rl_type = set_op->rl_type; result = new_call; + // As I see it's reduntant, but I do not understand why + current_function->dep.emplace_back(method->function); return result; } else { @@ -208,6 +202,10 @@ VertexPtr OptimizationPass::optimize_postfix_dec(VertexPtr root) { return root; } VertexPtr OptimizationPass::optimize_index(VertexAdaptor index) { + if (current_function->name.find("test") != std::string::npos) { + puts("In optimize index in test func"); + index.debugPrint(); + } if (!index->has_key()) { if (index->rl_type == val_l) { kphp_error (0, "Unsupported []"); @@ -235,6 +233,9 @@ VertexPtr OptimizationPass::optimize_index(VertexAdaptor index) { new_call->auto_inserted = true; new_call->rl_type = index->rl_type; + printf("CURRRRRRRRRRRRRRRRR = %s\n", current_function->name.c_str()); + current_function->dep.emplace_back(method->function); + return new_call; } From 7885e13b0778f9c7b1e797576d936e8b4cbaf8b6 Mon Sep 17 00:00:00 2001 From: Mikhail Kornaukhov Date: Thu, 17 Oct 2024 12:29:42 +0300 Subject: [PATCH 05/44] tinf::get_type instead of node->get_type() --- compiler/code-gen/vertex-compiler.cpp | 13 ++++++++++--- compiler/pipes/optimization.cpp | 4 +++- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/compiler/code-gen/vertex-compiler.cpp b/compiler/code-gen/vertex-compiler.cpp index 6f64ee9b3a..ed19ca2ce0 100644 --- a/compiler/code-gen/vertex-compiler.cpp +++ b/compiler/code-gen/vertex-compiler.cpp @@ -1815,15 +1815,17 @@ void compile_index_of_string(VertexAdaptor root, CodeGenerator &W) { } void compile_index_of_object(VertexAdaptor root, CodeGenerator &W) { + kphp_fail_msg("It should never be here! op_index on object should transformated into op_func_call"); // It calls when root node is argument of var_dump func // It should be not only the var, but function call, too auto as_var = root->array().try_as(); auto var_id = as_var->var_id; kphp_assert_msg(var_id, "Var is not var sadly"); - printf("var name = %s\n", var_id->name.c_str()); + // printf("var name = %s\n", var_id->name.c_str()); const auto *tpe = as_var->tinf_node.get_type(); + printf("Node addr in compiling: %p (%s)\n", &as_var->tinf_node, as_var->tinf_node.get_description().c_str()); kphp_assert_msg(tpe, "cannot get tinf node"); // printf("type = %s\n", tpe->as_human_readable().c_str()); @@ -1833,13 +1835,18 @@ void compile_index_of_object(VertexAdaptor root, CodeGenerator &W) { - puts("WTFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"); + // puts("WTFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"); const auto *method = klass->get_instance_method("offsetGet"); kphp_assert_msg(method, "no method \"offsetGet\""); - // TODO check how is it synchronized with inlude collector + // TODO check how is it synchronized with inlude collector + + puts("in compiling index:"); + W.get_context().parent_func->root.debugPrint(); + + W.get_context().parent_func->dep.push_back(method->function); std::string method_name = "f$" + method->global_name(); diff --git a/compiler/pipes/optimization.cpp b/compiler/pipes/optimization.cpp index 67ef738537..3932ed2632 100644 --- a/compiler/pipes/optimization.cpp +++ b/compiler/pipes/optimization.cpp @@ -203,6 +203,8 @@ VertexPtr OptimizationPass::optimize_postfix_dec(VertexPtr root) { } VertexPtr OptimizationPass::optimize_index(VertexAdaptor index) { if (current_function->name.find("test") != std::string::npos) { + // printf("Node addr in optimization: %p (%s)\n", &index->array()->tinf_node, index->array()->tinf_node.get_description().c_str()); + puts("In optimize index in test func"); index.debugPrint(); } @@ -215,7 +217,7 @@ VertexPtr OptimizationPass::optimize_index(VertexAdaptor index) { } auto &lhs = index->array(); - const auto *tpe = lhs->tinf_node.get_type(); + const auto *tpe = tinf::get_type(index->array()); // funny if (tpe->get_real_ptype() == tp_Class) { puts("OUR CASE!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); // index.debugPrint(); From 4612a5cc68a29cc8fd1b49c455bf37f2ef2f485f Mon Sep 17 00:00:00 2001 From: Mikhail Kornaukhov Date: Thu, 17 Oct 2024 16:05:12 +0300 Subject: [PATCH 06/44] cleanup --- compiler/class-assumptions.cpp | 15 +---- compiler/code-gen/files/function-header.cpp | 6 -- compiler/code-gen/files/function-source.cpp | 20 +------ compiler/code-gen/includes.cpp | 15 ----- compiler/code-gen/vertex-compiler.cpp | 60 -------------------- compiler/inferring/node-recalc.cpp | 2 +- compiler/inferring/type-data.cpp | 13 +---- compiler/inferring/type-inferer.cpp | 12 +--- compiler/inferring/var-node.cpp | 8 +-- compiler/pipes/filter-only-actually-used.cpp | 8 --- compiler/pipes/final-check.cpp | 7 --- compiler/pipes/gen-tree-postprocess.cpp | 8 --- compiler/pipes/optimization.cpp | 11 +--- 13 files changed, 10 insertions(+), 175 deletions(-) diff --git a/compiler/class-assumptions.cpp b/compiler/class-assumptions.cpp index d04043a745..855887eb37 100644 --- a/compiler/class-assumptions.cpp +++ b/compiler/class-assumptions.cpp @@ -30,7 +30,6 @@ */ #include "compiler/class-assumptions.h" -#include "compiler/inferring/primitive-type.h" #include #include "compiler/data/class-data.h" @@ -82,11 +81,9 @@ Assumption Assumption::get_subkey_by_index(VertexPtr index_key) const { const TypeHint *type_hint = assumption_unwrap_optional(assum_hint); if (type_hint == nullptr) { - printf("asuumption by index nullptr\n"); return {}; } if (const auto *as_array = type_hint->try_as()) { - printf("assumption array\n"); return Assumption(as_array->inner); } if (const auto *as_tuple = type_hint->try_as()) { @@ -105,12 +102,6 @@ Assumption Assumption::get_subkey_by_index(VertexPtr index_key) const { } } } - if (const auto *as_instance = type_hint->try_as()) { - printf("$$$$$$$$$ A S I N S T A N C E $$$$$$$$$$"); - // TODO when execution goes here???? - // TODO resolve here and somehow check (here or later) that class implements ArrayAccess - return Assumption(TypeHintPrimitive::create(PrimitiveType::tp_mixed)); - } return {}; } @@ -333,7 +324,7 @@ class CalcAssumptionForVarPass final : public FunctionPassBase { , var_name(var_name) {} void on_start() override { - printf("start assumptions for %s() $%s\n", current_function->as_human_readable().c_str(), std::string(var_name).c_str()); +// printf("start assumptions for %s() $%s\n", current_function->as_human_readable().c_str(), std::string(var_name).c_str()); } VertexPtr on_enter_vertex(VertexPtr root) override { @@ -760,7 +751,6 @@ Assumption assume_class_of_expr(FunctionPtr f, VertexPtr root, VertexPtr stop_at case op_ffi_array_get: return Assumption(root.as()->c_elem_type); case op_index: { - puts("OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO"); auto index = root.as(); if (index->has_key()) { return assume_class_of_expr(f, index->array(), stop_at).get_subkey_by_index(index->key()); @@ -789,9 +779,6 @@ Assumption assume_class_of_expr(FunctionPtr f, VertexPtr root, VertexPtr stop_at case op_move: return assume_class_of_expr(f, root.as()->expr(), stop_at); case op_set: - printf("On op_set assume:\n"); - // root.debugPrint(); - // puts("---------"); return assume_class_of_expr(f, root.as()->rhs(), stop_at); case op_define_val: return assume_class_of_expr(f, root.as()->value(), stop_at); diff --git a/compiler/code-gen/files/function-header.cpp b/compiler/code-gen/files/function-header.cpp index e17fee2c08..f75890d0e4 100644 --- a/compiler/code-gen/files/function-header.cpp +++ b/compiler/code-gen/files/function-header.cpp @@ -50,12 +50,6 @@ void FunctionH::compile(CodeGenerator &W) const { W << CloseNamespace(); includes.start_next_block(); - - bool correct = function->name.find("test") != std::string::npos; - if (correct) { - puts("YYYYYYYY"); - } - includes.add_function_body_depends(function); W << includes; W << OpenNamespace(); diff --git a/compiler/code-gen/files/function-source.cpp b/compiler/code-gen/files/function-source.cpp index c165d05671..d464e457fc 100644 --- a/compiler/code-gen/files/function-source.cpp +++ b/compiler/code-gen/files/function-source.cpp @@ -12,7 +12,6 @@ #include "compiler/code-gen/naming.h" #include "compiler/code-gen/vertex-compiler.h" #include "compiler/stage.h" -#include FunctionCpp::FunctionCpp(FunctionPtr function) : function(function) { @@ -37,16 +36,8 @@ void FunctionCpp::compile(CodeGenerator &W) const { stage::set_function(function); function->name_gen_map = {}; // make codegeneration of this function idempotent - std::string name = function->name; - bool good = name.find("test") != std::string::npos; - if (good) { - - puts("YYYYYYYY"); - } - IncludesCollector includes; - includes.add_function_body_depends(function); // here - + includes.add_function_body_depends(function); W << includes; W << OpenNamespace(); @@ -59,15 +50,6 @@ void FunctionCpp::compile(CodeGenerator &W) const { W << function->root << NL; W << LockComments(); - if (good) { - static int here = 0; - here++; - printf("Attempt #%d\n", here); - // TODO - // understand why each var dump argument don't do anything during type infer - // Or why type info is present during code gen, but isn\t during optimization pass - } - W << CloseNamespace(); W << CloseFile(); } diff --git a/compiler/code-gen/includes.cpp b/compiler/code-gen/includes.cpp index 6e0e7605af..7cbf272c2a 100644 --- a/compiler/code-gen/includes.cpp +++ b/compiler/code-gen/includes.cpp @@ -10,14 +10,10 @@ #include "compiler/data/class-data.h" #include "compiler/data/function-data.h" #include "compiler/data/var-data.h" -#include ExternInclude::ExternInclude(vk::string_view file_name) : file_name(file_name) { kphp_assert(!file_name.empty()); - if (file_name.find("offsetGet") != std::string::npos) { - puts("Gotit"); - } } void ExternInclude::compile(CodeGenerator &W) const { @@ -62,10 +58,6 @@ void IncludesCollector::add_function_body_depends(const FunctionPtr &function) { lib_headers_.emplace(to_include->header_full_name, std::move(relative_path)); } else if (!to_include->is_extern() || to_include->need_generated_stub) { kphp_assert(!to_include->header_full_name.empty()); - auto file_name = to_include->name; - if (file_name.find("offsetGet") != std::string::npos) { - puts("Gotit x2"); - } internal_headers_.emplace(to_include->header_full_name); } } @@ -138,12 +130,6 @@ void IncludesCollector::add_all_class_types(const TypeData &tinf_type) { } void IncludesCollector::add_raw_filename_include(const std::string &file_name) { - if (file_name.find("bar_baz") != std::string::npos) { - puts("YEAH!"); - } - if (file_name.find("offsetGet") != std::string::npos) { - puts("DONE!!!!"); - } internal_headers_.emplace(file_name); } @@ -155,7 +141,6 @@ void IncludesCollector::add_vertex_depends(VertexPtr v) { add_vertex_depends(child); } if (auto as_func_call = v.try_as()) { - printf("func name = %s\n", as_func_call->func_id ? as_func_call->func_id->name.c_str() : ""); if (as_func_call->func_id) { const auto &header_full_name = as_func_call->func_id->header_full_name; if (!header_full_name.empty()) { diff --git a/compiler/code-gen/vertex-compiler.cpp b/compiler/code-gen/vertex-compiler.cpp index ed19ca2ce0..19b3089398 100644 --- a/compiler/code-gen/vertex-compiler.cpp +++ b/compiler/code-gen/vertex-compiler.cpp @@ -4,12 +4,6 @@ #include "compiler/code-gen/vertex-compiler.h" -#include "auto/compiler/vertex/vertex-types.h" -#include "compiler/code-gen/code-generator.h" -#include "compiler/data/vertex-adaptor.h" -#include "compiler/inferring/primitive-type.h" -#include "compiler/kphp_assert.h" -#include #include #include @@ -1814,46 +1808,6 @@ void compile_index_of_string(VertexAdaptor root, CodeGenerator &W) { W << root->array() << ".get_value(" << root->key() << ")"; } -void compile_index_of_object(VertexAdaptor root, CodeGenerator &W) { - kphp_fail_msg("It should never be here! op_index on object should transformated into op_func_call"); - // It calls when root node is argument of var_dump func - // It should be not only the var, but function call, too - auto as_var = root->array().try_as(); - auto var_id = as_var->var_id; - kphp_assert_msg(var_id, "Var is not var sadly"); - - // printf("var name = %s\n", var_id->name.c_str()); - - const auto *tpe = as_var->tinf_node.get_type(); - printf("Node addr in compiling: %p (%s)\n", &as_var->tinf_node, as_var->tinf_node.get_description().c_str()); - kphp_assert_msg(tpe, "cannot get tinf node"); - - // printf("type = %s\n", tpe->as_human_readable().c_str()); - - auto klass = tpe->class_type(); - kphp_assert_msg(klass, "Klass badly does not exist"); - - - - // puts("WTFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"); - const auto *method = klass->get_instance_method("offsetGet"); - - - kphp_assert_msg(method, "no method \"offsetGet\""); - - // TODO check how is it synchronized with inlude collector - - puts("in compiling index:"); - W.get_context().parent_func->root.debugPrint(); - - - W.get_context().parent_func->dep.push_back(method->function); - - std::string method_name = "f$" + method->global_name(); - W << method_name << "(" << as_var << ","; - W << root->key() << ")"; // check has_key and pass null or smth like that -} - void compile_instance_prop(VertexAdaptor root, CodeGenerator &W) { switch (root->access_type) { case InstancePropAccessType::Default: @@ -1894,10 +1848,6 @@ void compile_instance_prop(VertexAdaptor root, CodeGenerator & void compile_index(VertexAdaptor root, CodeGenerator &W) { PrimitiveType array_ptype = root->array()->tinf_node.get_type()->ptype(); - printf("COMPILING INDEX!!!!!!\n"); - printf("root = "); - // root.debugPrint(); - printf("type = %d\n", (int)array_ptype); switch (array_ptype) { case tp_string: @@ -1909,9 +1859,6 @@ void compile_index(VertexAdaptor root, CodeGenerator &W) { case tp_shape: W << ShapeGetIndex(root->array(), root->key()); break; - case tp_Class: - compile_index_of_object(root, W); - break; default: compile_index_of_array(root, W); } @@ -2450,13 +2397,6 @@ void compile_vertex(VertexPtr root, CodeGenerator &W) { W << UpdateLocation(root->location); - if (auto as_func = root.try_as()) { - if (as_func->func_id && as_func->func_id->name.find("test") != std::string::npos) { - puts("-----------------------------------------------------------"); - as_func.debugPrint(); - } - } - bool close_par = root->val_ref_flag == val_r || root->val_ref_flag == val_l; if (root->val_ref_flag == val_r) { diff --git a/compiler/inferring/node-recalc.cpp b/compiler/inferring/node-recalc.cpp index 37e45b2c99..4acfb9f82a 100644 --- a/compiler/inferring/node-recalc.cpp +++ b/compiler/inferring/node-recalc.cpp @@ -33,7 +33,7 @@ void NodeRecalc::on_new_type_became_tpError(const TypeData *because_of_type, con } else if ((mix_class || mix_class2) && !vk::any_of_equal(ptype_before_error, tp_tuple, tp_shape)) { const auto class_name = TermStringFormat::paint_green(mix_class ? mix_class->name : mix_class2->name); - kphp_error(0, fmt_format("Type Error: mix class {} with non-class: {} [{}] and {} [{}]\n", class_name, desc1, (void*)node_, desc2, (void*)because_of_rvalue.node)); + kphp_error(0, fmt_format("Type Error: mix class {} with non-class: {} and {}\n", class_name, desc1, desc2)); } else if (ptype_before_error == tp_tuple && because_of_type->ptype() == tp_tuple) { kphp_error(0, fmt_format("Type Error: inconsistent tuples {} and {}\n", desc1, desc2)); diff --git a/compiler/inferring/type-data.cpp b/compiler/inferring/type-data.cpp index 0a6ec21480..fff3f4f3be 100644 --- a/compiler/inferring/type-data.cpp +++ b/compiler/inferring/type-data.cpp @@ -5,6 +5,7 @@ #include "compiler/inferring/type-data.h" #include "compiler/inferring/primitive-type.h" +#include "compiler/kphp_assert.h" #include #include @@ -354,22 +355,19 @@ const TypeData *TypeData::const_read_at(const Key &key) const { } if (ptype() == tp_Class) { // TODO any race conditions? - puts("Wanna inference type for const_read_at"); if (!class_type_.empty()) { - puts("class types are not empty!"); auto klass = class_type(); // Here is the place to think about inheritance stuff // TODO better check here // What is first: checking interface methods compatibility or type inference? Looks like there is no happens-before relation const bool impl_aa = std::find_if(klass->implements.begin(), klass->implements.end(), [](ClassPtr x) { return x->name == "ArrayAccess"; }) != klass->implements.end(); - printf("implementing ArrayAccess: %s\n", (impl_aa ? "yes" : "no")); if (impl_aa) { return get_type(tp_mixed); } - + kphp_fail_msg("Read at class that does not implements ArrayAccess"); } else { - puts("class types is empty! =("); + kphp_fail_msg("class types is empty! =("); } } if (!structured()) { @@ -441,12 +439,9 @@ void TypeData::set_lca(const TypeData *rhs, bool save_or_false, bool save_or_nul PrimitiveType new_ptype = type_lca(lhs->ptype(), rhs->ptype()); if (lhs->ptype_ == tp_array && rhs->ptype_ == tp_Class) { - puts("HITIT FIRST"); if (lhs->get_write_flag()) { // It means that lhs(==this) is something like that "$a[.] = " new_ptype = tp_Class; // for array access - puts("HITIT SECOND"); - printf("new type = %s\n", ptype_name(new_ptype)); } } if (new_ptype == tp_mixed) { @@ -567,8 +562,6 @@ void TypeData::set_lca_at(const MultiKey &multi_key, const TypeData *rhs, bool s cur->set_lca(rhs, save_or_false, save_or_null, ffi_flags); if (cur->error_flag()) { // proxy tp_Error from keys to the type itself - puts("------------ GONNA ERROR ---------------"); - printf("%s -- %s\n", rhs->_debug_string().c_str(), cur->_debug_string().c_str()); this->set_ptype(tp_Error); } } diff --git a/compiler/inferring/type-inferer.cpp b/compiler/inferring/type-inferer.cpp index f055203510..ae08fa6744 100644 --- a/compiler/inferring/type-inferer.cpp +++ b/compiler/inferring/type-inferer.cpp @@ -25,22 +25,14 @@ void TypeInferer::recalc_node(Node *node) { } void TypeInferer::add_node(Node *node) { - + //fprintf (stderr, "tinf::add_node %d %p %s\n", get_thread_id(), node, node->get_description().c_str()); if (!node->was_recalc_started_at_least_once()) { recalc_node(node); } - if (node) { - // fprintf (stderr, "tinf::add_node %d %p %s\n", get_thread_id(), node, node->get_description().c_str()); - } } void TypeInferer::add_edge(const Edge *edge) { - if (edge) { - auto lhs_type = edge->from->get_type()->as_human_readable(); - auto rhs_type = edge->to->get_type()->as_human_readable(); - // std::string via = edge->from_at ? edge->from_at->to_string().c_str() : ""; - // fprintf(stderr, "add_edge %d [%p %s] -> [%p %s] from at %s\n", get_thread_id(), edge->from, lhs_type.c_str(), edge->to, rhs_type.c_str(), via.c_str()); - } + //fprintf (stderr, "add_edge %d [%p %s] -> [%p %s]\n", get_thread_id(), edge->from, edge->from->get_description().c_str(), edge->to, edge->to->get_description().c_str()); edge->from->register_edge_from_this(edge); edge->to->register_edge_to_this(edge); } diff --git a/compiler/inferring/var-node.cpp b/compiler/inferring/var-node.cpp index 3428ae40bd..7f7e6a045a 100644 --- a/compiler/inferring/var-node.cpp +++ b/compiler/inferring/var-node.cpp @@ -8,7 +8,6 @@ #include "compiler/data/var-data.h" #include "compiler/inferring/edge.h" #include "compiler/inferring/node-recalc.h" -#include "compiler/inferring/primitive-type.h" #include "compiler/inferring/restriction-match-phpdoc.h" #include "compiler/vertex.h" @@ -36,12 +35,7 @@ void VarNodeRecalc::do_recalc() { } // at first, we just calculate new_type_ without any checks for (const tinf::Edge *e : node->get_edges_from_this()) { - std::string via = e->from_at ? e->from_at->to_string().c_str() : ""; - // printf("via edge %p -> %p (at %s)\n", e->from, e->to, via.c_str()); - if (node->var_ && node->var_->name == "abobus") { - // puts("HERE!"); - } - set_lca_at(e->from_at, as_rvalue(e->to)); // go deep dive here + set_lca_at(e->from_at, as_rvalue(e->to)); inferer_->add_node(e->to); } diff --git a/compiler/pipes/filter-only-actually-used.cpp b/compiler/pipes/filter-only-actually-used.cpp index 000b64a06c..e7a47ffe6c 100644 --- a/compiler/pipes/filter-only-actually-used.cpp +++ b/compiler/pipes/filter-only-actually-used.cpp @@ -317,10 +317,6 @@ void FilterOnlyActuallyUsedFunctionsF::on_finish(DataStream &os) { for (int id = 0; id < all.size(); ++id) { kphp_assert(get_index(all[id].first) == -1); set_index(all[id].first, id); - auto &func_data = all[id].first; - if (func_data->name.find("offsetGet") != std::string::npos) { - printf("In all func: %s (local name = %s)\n", func_data->name.c_str(), func_data->local_name().data()); - } } // uncomment this to debug "invalid index of IdMap: -1" @@ -366,10 +362,6 @@ void FilterOnlyActuallyUsedFunctionsF::on_finish(DataStream &os) { // this should be the last step for (const auto &f : used_functions) { if (f) { - const auto &func_data = f; - if (func_data->name.find("offsetGet") != std::string::npos) { - printf("In used func: %s\n", func_data->name.c_str()); - } os << f; } } diff --git a/compiler/pipes/final-check.cpp b/compiler/pipes/final-check.cpp index 2bd998bfc4..5a21d6c8c3 100644 --- a/compiler/pipes/final-check.cpp +++ b/compiler/pipes/final-check.cpp @@ -604,13 +604,6 @@ void FinalCheckPass::on_start() { } VertexPtr FinalCheckPass::on_enter_vertex(VertexPtr vertex) { - if (current_function->name == "test") { - if (auto as_op_func = vertex.try_as()) { - // op_index - printf("\n====== test in FinalCheckPass ======\n"); - as_op_func.debugPrint(); - } - } if (vertex->type() == op_func_name) { kphp_error (0, fmt_format("Unexpected {} (maybe, it should be a define?)", vertex->get_string())); } diff --git a/compiler/pipes/gen-tree-postprocess.cpp b/compiler/pipes/gen-tree-postprocess.cpp index 10ffbbca58..fd1f1c81b4 100644 --- a/compiler/pipes/gen-tree-postprocess.cpp +++ b/compiler/pipes/gen-tree-postprocess.cpp @@ -4,7 +4,6 @@ #include "compiler/pipes/gen-tree-postprocess.h" -#include "auto/compiler/vertex/vertex-types.h" #include "compiler/compiler-core.h" #include "compiler/data/class-data.h" #include "compiler/data/lib-data.h" @@ -156,13 +155,6 @@ GenTreePostprocessPass::builtin_fun GenTreePostprocessPass::get_builtin_function } VertexPtr GenTreePostprocessPass::on_enter_vertex(VertexPtr root) { - if (current_function->name == "test") { - if (auto as_op_func = root.try_as()) { - // op_index - printf("\n====== test in GenTreePostprocessPass ======\n"); - as_op_func.debugPrint(); - } - } stage::set_line(root->location.line); if (auto set_op = root.try_as()) { // list(...) = ... or short syntax (PHP 7) [...] = ... diff --git a/compiler/pipes/optimization.cpp b/compiler/pipes/optimization.cpp index 3932ed2632..280c9f81c6 100644 --- a/compiler/pipes/optimization.cpp +++ b/compiler/pipes/optimization.cpp @@ -202,12 +202,6 @@ VertexPtr OptimizationPass::optimize_postfix_dec(VertexPtr root) { return root; } VertexPtr OptimizationPass::optimize_index(VertexAdaptor index) { - if (current_function->name.find("test") != std::string::npos) { - // printf("Node addr in optimization: %p (%s)\n", &index->array()->tinf_node, index->array()->tinf_node.get_description().c_str()); - - puts("In optimize index in test func"); - index.debugPrint(); - } if (!index->has_key()) { if (index->rl_type == val_l) { kphp_error (0, "Unsupported []"); @@ -219,13 +213,11 @@ VertexPtr OptimizationPass::optimize_index(VertexAdaptor index) { auto &lhs = index->array(); const auto *tpe = tinf::get_type(index->array()); // funny if (tpe->get_real_ptype() == tp_Class) { - puts("OUR CASE!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); - // index.debugPrint(); - auto klass = tpe->class_type(); kphp_assert_msg(klass, "bad klass"); const auto *method = klass->get_instance_method("offsetGet"); + kphp_assert_msg(klass, "bad method"); // TODO assume here that key is present auto new_call = VertexAdaptor::create(lhs, index->key()).set_location(lhs); @@ -235,7 +227,6 @@ VertexPtr OptimizationPass::optimize_index(VertexAdaptor index) { new_call->auto_inserted = true; new_call->rl_type = index->rl_type; - printf("CURRRRRRRRRRRRRRRRR = %s\n", current_function->name.c_str()); current_function->dep.emplace_back(method->function); return new_call; From 015ff3b0dcb66fe8a566756aa5b29a41f6f308d7 Mon Sep 17 00:00:00 2001 From: Mikhail Kornaukhov Date: Thu, 17 Oct 2024 18:24:17 +0300 Subject: [PATCH 07/44] add new tag for internal interfaces --- builtin-functions/kphp-full/_functions.txt | 8 ++++++++ compiler/data/class-data.cpp | 2 +- compiler/data/class-data.h | 1 + compiler/gentree.cpp | 1 + compiler/phpdoc.cpp | 3 ++- compiler/phpdoc.h | 1 + compiler/pipes/check-classes.cpp | 2 +- 7 files changed, 15 insertions(+), 3 deletions(-) diff --git a/builtin-functions/kphp-full/_functions.txt b/builtin-functions/kphp-full/_functions.txt index dbd22a5a41..7b05df6378 100644 --- a/builtin-functions/kphp-full/_functions.txt +++ b/builtin-functions/kphp-full/_functions.txt @@ -1684,3 +1684,11 @@ function getenv(string $varname = '', bool $local_only = false): mixed; // builtin that allows to store objects inside a mixed function to_mixed(object $instance) ::: mixed; + +/** @kphp-internal-interface */ +interface ArrayAccess { + public function offsetExists(mixed $offset): bool; + public function offsetGet(mixed $offset): mixed; + public function offsetSet(mixed $offset, mixed $value); + public function offsetUnset(mixed $offset); +} \ No newline at end of file diff --git a/compiler/data/class-data.cpp b/compiler/data/class-data.cpp index 08866980d3..c40240184d 100644 --- a/compiler/data/class-data.cpp +++ b/compiler/data/class-data.cpp @@ -326,7 +326,7 @@ std::vector ClassData::get_all_ancestors() const { } bool ClassData::is_builtin() const { - return is_ffi_cdata() || (file_id && file_id->is_builtin()); + return is_ffi_cdata() || (file_id && file_id->is_builtin() && !internal_interface); } bool ClassData::is_polymorphic_or_has_polymorphic_member() const { diff --git a/compiler/data/class-data.h b/compiler/data/class-data.h index 71c0d99ee7..c56c558c09 100644 --- a/compiler/data/class-data.h +++ b/compiler/data/class-data.h @@ -66,6 +66,7 @@ class ClassData : public Lockable { bool can_be_php_autoloaded{false}; bool is_immutable{false}; bool need_generated_stub{false}; + bool internal_interface{false}; std::atomic is_subtree_immutable{SubtreeImmutableType::not_visited}; std::atomic process_fields_ic_compatibility{false}; bool really_used{false}; diff --git a/compiler/gentree.cpp b/compiler/gentree.cpp index ff7167c4f1..d5f57e6cf5 100644 --- a/compiler/gentree.cpp +++ b/compiler/gentree.cpp @@ -1722,6 +1722,7 @@ VertexPtr GenTree::get_class(const PhpDocComment *phpdoc, ClassType class_type) cur_class->phpdoc = phpdoc; cur_class->is_immutable = phpdoc && phpdoc->has_tag(PhpDocType::kphp_immutable_class); cur_class->need_generated_stub = phpdoc && phpdoc->has_tag(PhpDocType::kphp_generated_stub_class); + cur_class->internal_interface = phpdoc && phpdoc->has_tag(PhpDocType::kphp_internal_interface); cur_class->location_line_num = line_num; bool registered = G->register_class(cur_class); diff --git a/compiler/phpdoc.cpp b/compiler/phpdoc.cpp index 269b46eba2..8ec098d125 100644 --- a/compiler/phpdoc.cpp +++ b/compiler/phpdoc.cpp @@ -42,7 +42,7 @@ struct KnownPhpDocTag { }; class AllDocTags { - static constexpr int N_TAGS = 42; + static constexpr int N_TAGS = 43; static const KnownPhpDocTag ALL_TAGS[N_TAGS]; public: @@ -91,6 +91,7 @@ const KnownPhpDocTag AllDocTags::ALL_TAGS[] = { KnownPhpDocTag("@kphp-return", PhpDocType::kphp_return), KnownPhpDocTag("@kphp-memcache-class", PhpDocType::kphp_memcache_class), KnownPhpDocTag("@kphp-immutable-class", PhpDocType::kphp_immutable_class), + KnownPhpDocTag("@kphp-internal-interface", PhpDocType::kphp_internal_interface), KnownPhpDocTag("@kphp-tl-class", PhpDocType::kphp_tl_class), KnownPhpDocTag("@kphp-generate-stub-class", PhpDocType::kphp_generated_stub_class), KnownPhpDocTag("@kphp-const", PhpDocType::kphp_const), diff --git a/compiler/phpdoc.h b/compiler/phpdoc.h index 07a5151a67..85ef4a892c 100644 --- a/compiler/phpdoc.h +++ b/compiler/phpdoc.h @@ -36,6 +36,7 @@ enum class PhpDocType { kphp_immutable_class, kphp_tl_class, kphp_generated_stub_class, + kphp_internal_interface, kphp_const, kphp_noreturn, kphp_warn_unused_result, diff --git a/compiler/pipes/check-classes.cpp b/compiler/pipes/check-classes.cpp index 947c3440c8..ab818fc5fa 100644 --- a/compiler/pipes/check-classes.cpp +++ b/compiler/pipes/check-classes.cpp @@ -43,7 +43,7 @@ inline void CheckClassesPass::analyze_class(ClassPtr klass) { if (klass->does_need_codegen()) { check_instance_fields_inited(klass); } - if (klass->can_be_php_autoloaded && !klass->is_builtin() && !klass->need_generated_stub) { + if (klass->can_be_php_autoloaded && !klass->is_builtin() && !klass->need_generated_stub && !klass->internal_interface) { // TODO think about it later kphp_error(klass->file_id->main_function->body_seq == FunctionData::body_value::empty, fmt_format("class {} can be autoloaded, but its file contains some logic (maybe, require_once files with global vars?)\n", klass->as_human_readable())); From d35cebfe82a0ee7dad402395ef9d79023fc8e274 Mon Sep 17 00:00:00 2001 From: Mikhail Kornaukhov Date: Tue, 22 Oct 2024 09:55:52 +0300 Subject: [PATCH 08/44] support type erasure to ArrayAccess interface --- builtin-functions/kphp-full/_functions.txt | 9 +++++++++ compiler/compiler.cpp | 6 +++--- compiler/inferring/type-inferer.cpp | 6 ++++-- compiler/inferring/var-node.cpp | 1 + compiler/pipes/filter-only-actually-used.cpp | 9 +++++++-- compiler/pipes/optimization.cpp | 15 +++++++++++++-- 6 files changed, 37 insertions(+), 9 deletions(-) diff --git a/builtin-functions/kphp-full/_functions.txt b/builtin-functions/kphp-full/_functions.txt index 7b05df6378..35a669733b 100644 --- a/builtin-functions/kphp-full/_functions.txt +++ b/builtin-functions/kphp-full/_functions.txt @@ -1691,4 +1691,13 @@ interface ArrayAccess { public function offsetGet(mixed $offset): mixed; public function offsetSet(mixed $offset, mixed $value); public function offsetUnset(mixed $offset); +} + +if (0) { + /** @var ArrayAccess */ + $x = null; + $x->offsetExists(0); + $x->offsetGet(0); + $x->offsetSet(0, 0); + $x->offsetUnset(0); } \ No newline at end of file diff --git a/compiler/compiler.cpp b/compiler/compiler.cpp index d4fdbd826b..9544361d3a 100644 --- a/compiler/compiler.cpp +++ b/compiler/compiler.cpp @@ -266,7 +266,7 @@ bool compiler_execute(CompilerSettings *settings) { >> PipeC{} >> PipeC{} >> PassC{} - >> SyncC{} + >> SyncC{} // removes unused func calls >> PassC{} >> PassC{} >> PassC{} @@ -280,9 +280,9 @@ bool compiler_execute(CompilerSettings *settings) { >> PipeC{} >> SyncC{} >> PassC{} - >> PassC{} + >> PassC{} // collects edges for type inference >> SyncC{} - >> SyncC{} + >> SyncC{} // type inferer finishes here >> PipeC{} >> PassC{} >> PassC{} diff --git a/compiler/inferring/type-inferer.cpp b/compiler/inferring/type-inferer.cpp index ae08fa6744..ff7cc9ee7a 100644 --- a/compiler/inferring/type-inferer.cpp +++ b/compiler/inferring/type-inferer.cpp @@ -25,10 +25,10 @@ void TypeInferer::recalc_node(Node *node) { } void TypeInferer::add_node(Node *node) { - //fprintf (stderr, "tinf::add_node %d %p %s\n", get_thread_id(), node, node->get_description().c_str()); if (!node->was_recalc_started_at_least_once()) { recalc_node(node); } + // fprintf (stderr, "tinf::add_node %d %p %s\n", get_thread_id(), node, node->get_description().c_str()); } void TypeInferer::add_edge(const Edge *edge) { @@ -95,10 +95,12 @@ std::vector TypeInferer::get_tasks() { return res; } +[[clang::optnone]] void TypeInferer::do_run_queue() { NodeQueue &q = Q.get(); - while (!q.empty()) { + // in bad case first node add new VarNode into the Q queue + while (!q.empty()) { // when add here 2 nodes (when bad case) Node *node = q.front(); node->start_recalc(); diff --git a/compiler/inferring/var-node.cpp b/compiler/inferring/var-node.cpp index 7f7e6a045a..1d09133f26 100644 --- a/compiler/inferring/var-node.cpp +++ b/compiler/inferring/var-node.cpp @@ -11,6 +11,7 @@ #include "compiler/inferring/restriction-match-phpdoc.h" #include "compiler/vertex.h" +// Check in add_node that adding f$ArrayAccess$offsetGet class VarNodeRecalc : public NodeRecalc { static void on_restricted_type_mismatch(const tinf::Edge *edge, const TypeData *type_restriction); diff --git a/compiler/pipes/filter-only-actually-used.cpp b/compiler/pipes/filter-only-actually-used.cpp index e7a47ffe6c..51b784f01d 100644 --- a/compiler/pipes/filter-only-actually-used.cpp +++ b/compiler/pipes/filter-only-actually-used.cpp @@ -294,8 +294,13 @@ void remove_unused_class_methods(const std::vector &all, const return get_index(m.function) == -1 || !used_functions[m.function]; }); fun->class_id->members.remove_if( - [&used_functions](const ClassMemberInstanceMethod &m) { - return get_index(m.function) == -1 || !used_functions[m.function]; + [&used_functions, class_id=fun->class_id](const ClassMemberInstanceMethod &m) { + bool cond = get_index(m.function) == -1 || !used_functions[m.function]; + const std::string& name = m.global_name(); + if (cond && name.find("offsetGet") != std::string::npos) { + printf("removing %s\n",name.c_str()); + } + return !class_id->internal_interface && cond; }); } } diff --git a/compiler/pipes/optimization.cpp b/compiler/pipes/optimization.cpp index 280c9f81c6..2e5a969e57 100644 --- a/compiler/pipes/optimization.cpp +++ b/compiler/pipes/optimization.cpp @@ -6,6 +6,7 @@ #include "auto/compiler/vertex/vertex-types.h" #include "compiler/inferring/primitive-type.h" +#include "compiler/inferring/type-data.h" #include "compiler/kphp_assert.h" #include "compiler/operation.h" #include @@ -201,6 +202,7 @@ VertexPtr OptimizationPass::optimize_postfix_dec(VertexPtr root) { } return root; } +[[clang::optnone]] VertexPtr OptimizationPass::optimize_index(VertexAdaptor index) { if (!index->has_key()) { if (index->rl_type == val_l) { @@ -217,8 +219,9 @@ VertexPtr OptimizationPass::optimize_index(VertexAdaptor index) { kphp_assert_msg(klass, "bad klass"); const auto *method = klass->get_instance_method("offsetGet"); - kphp_assert_msg(klass, "bad method"); - + if (!method) { + kphp_assert_msg(method, "bad method"); + } // TODO assume here that key is present auto new_call = VertexAdaptor::create(lhs, index->key()).set_location(lhs); new_call->str_val = method->global_name(); @@ -229,6 +232,13 @@ VertexPtr OptimizationPass::optimize_index(VertexAdaptor index) { current_function->dep.emplace_back(method->function); + // For interfaces, I should construct type node here? + auto &node = new_call->tinf_node; + auto * tdata = new TypeData(*method->function->tinf_node.get_type()); + auto xxx = method->function->tinf_node; + node.set_type(tdata); + tinf::get_type(new_call); // why OK for LikeArray, but bad for array access + return new_call; } @@ -376,6 +386,7 @@ VertexPtr OptimizationPass::on_enter_vertex(VertexPtr root) { } if (root->rl_type != val_none/* && root->rl_type != val_error*/) { + // wtf? tinf::get_type(root); } return root; From 1cc4c33c04f9e4c5986d69b9f851effefbaf8945 Mon Sep 17 00:00:00 2001 From: Mikhail Kornaukhov Date: Tue, 22 Oct 2024 10:37:50 +0300 Subject: [PATCH 09/44] add comments and improve diagnostics --- builtin-functions/kphp-full/_functions.txt | 1 + compiler/inferring/type-data.cpp | 7 ++++++- compiler/pipes/optimization.cpp | 3 ++- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/builtin-functions/kphp-full/_functions.txt b/builtin-functions/kphp-full/_functions.txt index 35a669733b..4233b3ab34 100644 --- a/builtin-functions/kphp-full/_functions.txt +++ b/builtin-functions/kphp-full/_functions.txt @@ -1693,6 +1693,7 @@ interface ArrayAccess { public function offsetUnset(mixed $offset); } +// TODO get rid of it if (0) { /** @var ArrayAccess */ $x = null; diff --git a/compiler/inferring/type-data.cpp b/compiler/inferring/type-data.cpp index fff3f4f3be..7e69503c1a 100644 --- a/compiler/inferring/type-data.cpp +++ b/compiler/inferring/type-data.cpp @@ -362,7 +362,12 @@ const TypeData *TypeData::const_read_at(const Key &key) const { const bool impl_aa = std::find_if(klass->implements.begin(), klass->implements.end(), [](ClassPtr x) { return x->name == "ArrayAccess"; }) != klass->implements.end(); - if (impl_aa) { + // Why offsetSet for ArrayAccess is going through here + // Can I just always return tp_mixed? + // And in some later pass (FinalCheckPass, for example) check that access via [.] is enabled only for arrays and classes that implements ArrayAccess (by chain) + if ( + true || + impl_aa || klass->name.find("ArrayAccess") != std::string::npos) { return get_type(tp_mixed); } kphp_fail_msg("Read at class that does not implements ArrayAccess"); diff --git a/compiler/pipes/optimization.cpp b/compiler/pipes/optimization.cpp index 2e5a969e57..e7ab8d8c5b 100644 --- a/compiler/pipes/optimization.cpp +++ b/compiler/pipes/optimization.cpp @@ -134,9 +134,10 @@ VertexPtr OptimizationPass::optimize_set_push_back(VertexAdaptor set_op) auto klass = tinf::get_type(a)->class_type(); kphp_assert_msg(klass, "bad klass"); + // TODO doesn't it have the problem with that some parent classes are not linked in chain yet? const auto *method = klass->get_instance_method("offsetSet"); - kphp_assert_msg(klass, "bad method"); + kphp_assert_msg(method, fmt::format("Class {} does not implement offsetSet", klass->name).c_str()); // TODO assume here that key is present From 82d0223c2c64fe4ae6617226c7bfcea242b1cf82 Mon Sep 17 00:00:00 2001 From: Mikhail Kornaukhov Date: Tue, 22 Oct 2024 10:51:52 +0300 Subject: [PATCH 10/44] small improvements --- compiler/inferring/type-inferer.cpp | 1 - compiler/pipes/final-check.cpp | 6 ++++-- compiler/pipes/optimization.cpp | 4 ---- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/compiler/inferring/type-inferer.cpp b/compiler/inferring/type-inferer.cpp index ff7cc9ee7a..5271dbdffc 100644 --- a/compiler/inferring/type-inferer.cpp +++ b/compiler/inferring/type-inferer.cpp @@ -95,7 +95,6 @@ std::vector TypeInferer::get_tasks() { return res; } -[[clang::optnone]] void TypeInferer::do_run_queue() { NodeQueue &q = Q.get(); diff --git a/compiler/pipes/final-check.cpp b/compiler/pipes/final-check.cpp index 5a21d6c8c3..c52ce78249 100644 --- a/compiler/pipes/final-check.cpp +++ b/compiler/pipes/final-check.cpp @@ -1006,6 +1006,9 @@ void FinalCheckPass::on_function() { } void FinalCheckPass::raise_error_using_Unknown_type(VertexPtr v) { + // TODO I think I should add some checks here + // In this pass, at least + std::string index_depth; while (auto v_index = v.try_as()) { v = v_index->array(); @@ -1027,8 +1030,7 @@ void FinalCheckPass::raise_error_using_Unknown_type(VertexPtr v) { } else if (tinf::get_type(var)->get_real_ptype() == tp_shape) { kphp_error(0, fmt_format("Accessing unexisting element of shape ${}", var->name)); } else { - // TODO fix smth here - // kphp_error(0, fmt_format("${} is {}, can not get element", var->name, tinf::get_type(var)->as_human_readable())); + kphp_error(0, fmt_format("${} is {}, can not get element", var->name, tinf::get_type(var)->as_human_readable())); } } else { // multidimentional array[*]...[*] access diff --git a/compiler/pipes/optimization.cpp b/compiler/pipes/optimization.cpp index e7ab8d8c5b..71af09b3af 100644 --- a/compiler/pipes/optimization.cpp +++ b/compiler/pipes/optimization.cpp @@ -150,9 +150,6 @@ VertexPtr OptimizationPass::optimize_set_push_back(VertexAdaptor set_op) new_call->rl_type = set_op->rl_type; result = new_call; - // As I see it's reduntant, but I do not understand why - current_function->dep.emplace_back(method->function); - return result; } else { result = VertexAdaptor::create(a, b, c); @@ -203,7 +200,6 @@ VertexPtr OptimizationPass::optimize_postfix_dec(VertexPtr root) { } return root; } -[[clang::optnone]] VertexPtr OptimizationPass::optimize_index(VertexAdaptor index) { if (!index->has_key()) { if (index->rl_type == val_l) { From fd64900dde7e1020639943d026e27f824cfb7489 Mon Sep 17 00:00:00 2001 From: Mikhail Kornaukhov Date: Tue, 22 Oct 2024 12:13:15 +0300 Subject: [PATCH 11/44] add first test --- .../001_sanity_square_brackets.php | 20 +++++++ tests/phpt/array_access/Classes/LikeArray.php | 54 +++++++++++++++++++ 2 files changed, 74 insertions(+) create mode 100644 tests/phpt/array_access/001_sanity_square_brackets.php create mode 100644 tests/phpt/array_access/Classes/LikeArray.php diff --git a/tests/phpt/array_access/001_sanity_square_brackets.php b/tests/phpt/array_access/001_sanity_square_brackets.php new file mode 100644 index 0000000000..83e5a8ac51 --- /dev/null +++ b/tests/phpt/array_access/001_sanity_square_brackets.php @@ -0,0 +1,20 @@ +@ok + "four", "str_key" => 42]; + $obj = new Classes\LikeArray(); + + foreach ($arr as $k => $v) { + $arr[$k] = $v; + } + + foreach ($arr as $k => $v) { + var_dump("Expect: $v\n"); + $v2 = $obj[$k]; + var_dump("Got: $v2\n"); + } +} + +test(); diff --git a/tests/phpt/array_access/Classes/LikeArray.php b/tests/phpt/array_access/Classes/LikeArray.php new file mode 100644 index 0000000000..55c17d937e --- /dev/null +++ b/tests/phpt/array_access/Classes/LikeArray.php @@ -0,0 +1,54 @@ +data[] = $value; + } else { + $this->data[$offset] = $value; + } + } + + /** + * @param $offset mixed + * @return bool + */ + public function offsetExists($offset) { + echo "offsetExists\n"; + + return isset($this->data[$offset]); + } + + /** + * @param $offset mixed The offset to unset + * @return void + */ + public function offsetUnset($offset) { + echo "offsetUnset\n"; + + if ($this->offsetExists($offset)) { + unset($this->data[$offset]); + } + } + + /** + * @param $offset mixed + * @return mixed + */ + public function offsetGet($offset) { + echo "offsetGet\n"; + + return $this->offsetExists($offset) ? $this->data[$offset] : null; + } +} \ No newline at end of file From b8abfb15c99e611b186f85664e7b18bf2027d843 Mon Sep 17 00:00:00 2001 From: Mikhail Kornaukhov Date: Tue, 22 Oct 2024 12:35:47 +0300 Subject: [PATCH 12/44] fixup tests --- ...ckets.php => 001_set_and_get_by_index.php} | 2 +- tests/phpt/array_access/002_set_append.php | 1 + .../phpt/array_access/003_as_array_access.php | 1 + .../phpt/array_access/004_with_instanceof.php | 1 + tests/phpt/array_access/005_inheritance.php | 1 + tests/phpt/array_access/006_as_mixed.php | 1 + .../array_access/007_partial_override.php | 1 + tests/phpt/array_access/100_not_implement.php | 0 tests/phpt/array_access/101_not_override.php | 1 + tests/phpt/array_access/Classes/LikeArray.php | 8 --- .../array_access/Classes/LoggingLikeArray.php | 54 +++++++++++++++++++ 11 files changed, 62 insertions(+), 9 deletions(-) rename tests/phpt/array_access/{001_sanity_square_brackets.php => 001_set_and_get_by_index.php} (88%) create mode 100644 tests/phpt/array_access/002_set_append.php create mode 100644 tests/phpt/array_access/003_as_array_access.php create mode 100644 tests/phpt/array_access/004_with_instanceof.php create mode 100644 tests/phpt/array_access/005_inheritance.php create mode 100644 tests/phpt/array_access/006_as_mixed.php create mode 100644 tests/phpt/array_access/007_partial_override.php create mode 100644 tests/phpt/array_access/100_not_implement.php create mode 100644 tests/phpt/array_access/101_not_override.php create mode 100644 tests/phpt/array_access/Classes/LoggingLikeArray.php diff --git a/tests/phpt/array_access/001_sanity_square_brackets.php b/tests/phpt/array_access/001_set_and_get_by_index.php similarity index 88% rename from tests/phpt/array_access/001_sanity_square_brackets.php rename to tests/phpt/array_access/001_set_and_get_by_index.php index 83e5a8ac51..7aefff92a0 100644 --- a/tests/phpt/array_access/001_sanity_square_brackets.php +++ b/tests/phpt/array_access/001_set_and_get_by_index.php @@ -4,7 +4,7 @@ function test() { $arr = ["zero", "one", "two", 4 => "four", "str_key" => 42]; - $obj = new Classes\LikeArray(); + $obj = new Classes\LoggingLikeArray(); foreach ($arr as $k => $v) { $arr[$k] = $v; diff --git a/tests/phpt/array_access/002_set_append.php b/tests/phpt/array_access/002_set_append.php new file mode 100644 index 0000000000..0ffdd02fcb --- /dev/null +++ b/tests/phpt/array_access/002_set_append.php @@ -0,0 +1 @@ +// TODO \ No newline at end of file diff --git a/tests/phpt/array_access/003_as_array_access.php b/tests/phpt/array_access/003_as_array_access.php new file mode 100644 index 0000000000..0ffdd02fcb --- /dev/null +++ b/tests/phpt/array_access/003_as_array_access.php @@ -0,0 +1 @@ +// TODO \ No newline at end of file diff --git a/tests/phpt/array_access/004_with_instanceof.php b/tests/phpt/array_access/004_with_instanceof.php new file mode 100644 index 0000000000..0ffdd02fcb --- /dev/null +++ b/tests/phpt/array_access/004_with_instanceof.php @@ -0,0 +1 @@ +// TODO \ No newline at end of file diff --git a/tests/phpt/array_access/005_inheritance.php b/tests/phpt/array_access/005_inheritance.php new file mode 100644 index 0000000000..0ffdd02fcb --- /dev/null +++ b/tests/phpt/array_access/005_inheritance.php @@ -0,0 +1 @@ +// TODO \ No newline at end of file diff --git a/tests/phpt/array_access/006_as_mixed.php b/tests/phpt/array_access/006_as_mixed.php new file mode 100644 index 0000000000..0ffdd02fcb --- /dev/null +++ b/tests/phpt/array_access/006_as_mixed.php @@ -0,0 +1 @@ +// TODO \ No newline at end of file diff --git a/tests/phpt/array_access/007_partial_override.php b/tests/phpt/array_access/007_partial_override.php new file mode 100644 index 0000000000..0ffdd02fcb --- /dev/null +++ b/tests/phpt/array_access/007_partial_override.php @@ -0,0 +1 @@ +// TODO \ No newline at end of file diff --git a/tests/phpt/array_access/100_not_implement.php b/tests/phpt/array_access/100_not_implement.php new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/phpt/array_access/101_not_override.php b/tests/phpt/array_access/101_not_override.php new file mode 100644 index 0000000000..0ffdd02fcb --- /dev/null +++ b/tests/phpt/array_access/101_not_override.php @@ -0,0 +1 @@ +// TODO \ No newline at end of file diff --git a/tests/phpt/array_access/Classes/LikeArray.php b/tests/phpt/array_access/Classes/LikeArray.php index 55c17d937e..59516a6b06 100644 --- a/tests/phpt/array_access/Classes/LikeArray.php +++ b/tests/phpt/array_access/Classes/LikeArray.php @@ -11,8 +11,6 @@ class LikeArray implements \ArrayAccess { * @param $value mixed The value to set */ public function offsetSet($offset, $value) { - echo "offsetSet\n"; - if (is_null($offset)) { $this->data[] = $value; } else { @@ -25,8 +23,6 @@ public function offsetSet($offset, $value) { * @return bool */ public function offsetExists($offset) { - echo "offsetExists\n"; - return isset($this->data[$offset]); } @@ -35,8 +31,6 @@ public function offsetExists($offset) { * @return void */ public function offsetUnset($offset) { - echo "offsetUnset\n"; - if ($this->offsetExists($offset)) { unset($this->data[$offset]); } @@ -47,8 +41,6 @@ public function offsetUnset($offset) { * @return mixed */ public function offsetGet($offset) { - echo "offsetGet\n"; - return $this->offsetExists($offset) ? $this->data[$offset] : null; } } \ No newline at end of file diff --git a/tests/phpt/array_access/Classes/LoggingLikeArray.php b/tests/phpt/array_access/Classes/LoggingLikeArray.php new file mode 100644 index 0000000000..b6bcba5b70 --- /dev/null +++ b/tests/phpt/array_access/Classes/LoggingLikeArray.php @@ -0,0 +1,54 @@ +data[] = $value; + } else { + $this->data[$offset] = $value; + } + } + + /** + * @param $offset mixed + * @return bool + */ + public function offsetExists($offset) { + echo "offsetExists\n"; + + return isset($this->data[$offset]); + } + + /** + * @param $offset mixed The offset to unset + * @return void + */ + public function offsetUnset($offset) { + echo "offsetUnset\n"; + + if ($this->offsetExists($offset)) { + unset($this->data[$offset]); + } + } + + /** + * @param $offset mixed + * @return mixed + */ + public function offsetGet($offset) { + echo "offsetGet\n"; + + return $this->offsetExists($offset) ? $this->data[$offset] : null; + } +} \ No newline at end of file From 4379faa517ef8ecffdc77021fb173f2b8e7d6b04 Mon Sep 17 00:00:00 2001 From: Mikhail Kornaukhov Date: Tue, 22 Oct 2024 12:41:29 +0300 Subject: [PATCH 13/44] support append --- compiler/pipes/optimization.cpp | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/compiler/pipes/optimization.cpp b/compiler/pipes/optimization.cpp index 71af09b3af..8966c96e7a 100644 --- a/compiler/pipes/optimization.cpp +++ b/compiler/pipes/optimization.cpp @@ -5,6 +5,7 @@ #include "compiler/pipes/optimization.h" #include "auto/compiler/vertex/vertex-types.h" +#include "compiler/data/vertex-adaptor.h" #include "compiler/inferring/primitive-type.h" #include "compiler/inferring/type-data.h" #include "compiler/kphp_assert.h" @@ -120,6 +121,31 @@ VertexPtr OptimizationPass::optimize_set_push_back(VertexAdaptor set_op) // '$s[] = ...' is forbidden for non-array types; // for arrays it's converted to push_back PrimitiveType a_ptype = tinf::get_type(a)->get_real_ptype(); + + if (a_ptype == tp_Class) { + auto klass = tinf::get_type(a)->class_type(); + kphp_assert_msg(klass, "bad klass"); + + // TODO doesn't it have the problem with that some parent classes are not linked in chain yet? + const auto *method = klass->get_instance_method("offsetSet"); + + kphp_assert_msg(method, fmt::format("Class {} does not implement offsetSet", klass->name).c_str()); + + + // TODO assume here that key is present + auto new_call = VertexAdaptor::create(a, VertexAdaptor::create(), c).set_location(set_op->get_location()); + + new_call->str_val = method->global_name(); + new_call->func_id = method->function; + new_call->extra_type = op_ex_func_call_arrow; // Is that right? + new_call->auto_inserted = true; + new_call->rl_type = set_op->rl_type; + + result = new_call; + return result; + } + + kphp_error (a_ptype == tp_array || a_ptype == tp_mixed, fmt_format("Can not use [] for {}", type_out(tinf::get_type(a)))); From 7633beb4a07f72fdbef431693c647beb77865ddd Mon Sep 17 00:00:00 2001 From: Mikhail Kornaukhov Date: Tue, 22 Oct 2024 15:27:24 +0300 Subject: [PATCH 14/44] add some tests --- .../array_access/001_set_and_get_by_index.php | 5 +- tests/phpt/array_access/002_set_append.php | 79 ++++++++++++++++++- .../phpt/array_access/003_as_array_access.php | 34 +++++++- .../phpt/array_access/004_with_instanceof.php | 33 +++++++- tests/phpt/array_access/006_as_mixed.php | 3 +- .../008_multiple_implementors.php | 1 + tests/phpt/array_access/Classes/LikeArray.php | 7 ++ .../array_access/Classes/LoggingLikeArray.php | 4 +- 8 files changed, 158 insertions(+), 8 deletions(-) create mode 100644 tests/phpt/array_access/008_multiple_implementors.php diff --git a/tests/phpt/array_access/001_set_and_get_by_index.php b/tests/phpt/array_access/001_set_and_get_by_index.php index 7aefff92a0..97f5d41635 100644 --- a/tests/phpt/array_access/001_set_and_get_by_index.php +++ b/tests/phpt/array_access/001_set_and_get_by_index.php @@ -7,13 +7,12 @@ function test() { $obj = new Classes\LoggingLikeArray(); foreach ($arr as $k => $v) { - $arr[$k] = $v; + $obj[$k] = $v; } foreach ($arr as $k => $v) { - var_dump("Expect: $v\n"); $v2 = $obj[$k]; - var_dump("Got: $v2\n"); + assert_true($v == $v2); } } diff --git a/tests/phpt/array_access/002_set_append.php b/tests/phpt/array_access/002_set_append.php index 0ffdd02fcb..9ecad6494d 100644 --- a/tests/phpt/array_access/002_set_append.php +++ b/tests/phpt/array_access/002_set_append.php @@ -1 +1,78 @@ -// TODO \ No newline at end of file +@ok + "four", "str_key" => 42]; + $obj = new Classes\LikeArray(); + + foreach ($arr as $k => $v) { + $obj[$k] = $v; + } + + $keys = $obj->keys(); + foreach ($keys as $key) { + print_and_change($obj, $key, "key_" . strval($key)); + } + + foreach ($keys as $key) { + var_dump($obj[$key]); + } +} + +test(); diff --git a/tests/phpt/array_access/004_with_instanceof.php b/tests/phpt/array_access/004_with_instanceof.php index 0ffdd02fcb..d6a6f743a4 100644 --- a/tests/phpt/array_access/004_with_instanceof.php +++ b/tests/phpt/array_access/004_with_instanceof.php @@ -1 +1,32 @@ -// TODO \ No newline at end of file +@ok + 42]; + $obj = new Classes\LoggingLikeArray(); + + foreach ($arr as $k => $v) { + $obj[$k] = $v; + } + + if ($obj instanceof Classes\LoggingLikeArray) { + var_dump($obj[0]); + var_dump($obj["str_key"]); + } else { + assert_true(false); + } + + if ($obj instanceof Classes\LikeArray) { + assert_true(false); + } + + if ($obj instanceof \ArrayAccess) { + var_dump($obj[1]); + var_dump($obj[3]); + } else { + assert_true(false); + } +} + +test(); diff --git a/tests/phpt/array_access/006_as_mixed.php b/tests/phpt/array_access/006_as_mixed.php index 0ffdd02fcb..fb6d7569b2 100644 --- a/tests/phpt/array_access/006_as_mixed.php +++ b/tests/phpt/array_access/006_as_mixed.php @@ -1 +1,2 @@ -// TODO \ No newline at end of file +// TODO +// Check [.][.] and longer chains, too \ No newline at end of file diff --git a/tests/phpt/array_access/008_multiple_implementors.php b/tests/phpt/array_access/008_multiple_implementors.php new file mode 100644 index 0000000000..0ffdd02fcb --- /dev/null +++ b/tests/phpt/array_access/008_multiple_implementors.php @@ -0,0 +1 @@ +// TODO \ No newline at end of file diff --git a/tests/phpt/array_access/Classes/LikeArray.php b/tests/phpt/array_access/Classes/LikeArray.php index 59516a6b06..f5275a2c10 100644 --- a/tests/phpt/array_access/Classes/LikeArray.php +++ b/tests/phpt/array_access/Classes/LikeArray.php @@ -43,4 +43,11 @@ public function offsetUnset($offset) { public function offsetGet($offset) { return $this->offsetExists($offset) ? $this->data[$offset] : null; } + + /** + * @return mixed[] + */ + public function keys() { + return array_keys($this->data); + } } \ No newline at end of file diff --git a/tests/phpt/array_access/Classes/LoggingLikeArray.php b/tests/phpt/array_access/Classes/LoggingLikeArray.php index b6bcba5b70..bb86c44b5e 100644 --- a/tests/phpt/array_access/Classes/LoggingLikeArray.php +++ b/tests/phpt/array_access/Classes/LoggingLikeArray.php @@ -11,11 +11,13 @@ class LoggingLikeArray implements \ArrayAccess { * @param $value mixed The value to set */ public function offsetSet($offset, $value) { - echo "offsetSet\n"; + echo "offsetSet "; if (is_null($offset)) { + echo "append\n"; $this->data[] = $value; } else { + echo "by index\n"; $this->data[$offset] = $value; } } From e1d7f00567a19ce2bb76a6fb17d750c0cf1ce865 Mon Sep 17 00:00:00 2001 From: Mikhail Kornaukhov Date: Tue, 22 Oct 2024 16:47:46 +0300 Subject: [PATCH 15/44] better tests --- tests/phpt/array_access/005_inheritance.php | 55 ++++++++++++++++++- tests/phpt/array_access/006_as_mixed.php | 2 +- .../array_access/007_partial_override.php | 2 +- .../008_multiple_implementors.php | 2 +- tests/phpt/array_access/100_not_implement.php | 1 + tests/phpt/array_access/101_not_override.php | 2 +- tests/phpt/array_access/Classes/LikeArray.php | 6 +- .../array_access/Classes/LoggingLikeArray.php | 13 ++++- 8 files changed, 72 insertions(+), 11 deletions(-) diff --git a/tests/phpt/array_access/005_inheritance.php b/tests/phpt/array_access/005_inheritance.php index 0ffdd02fcb..58b0abe8a8 100644 --- a/tests/phpt/array_access/005_inheritance.php +++ b/tests/phpt/array_access/005_inheritance.php @@ -1 +1,54 @@ -// TODO \ No newline at end of file +@ok +data[] = $value; + $this->data[] = $value; + } else { + $this->data[$offset] = $value; + } + } +}; + +function test() { + global $called; + + $obj = new DoubleAppend(); + $obj[] = 123; + $obj[] = "abcd"; + $obj[123] = ["string", 0.125, [1, 2, 3]]; + $obj[] = $obj[123]; + + foreach ($obj->keys() as $key) { + var_dump($obj[$key]); + } + + if ($obj instanceof Classes\LoggingLikeArray) { + // TODO operator[] does not work here + $obj->offsetSet(null, $obj[124]); + var_dump($obj[124]); + } else { + die_if_failure(false, "false negative instanceof Classes\LoggingLikeArray"); + } + + if ($obj instanceof \ArrayAccess) { + $obj->offsetSet(null, 0.5); + var_dump($obj[126]); + } else { + die_if_failure(false, "false negative instanceof \ArrayAccess"); + } +} + +test(); diff --git a/tests/phpt/array_access/006_as_mixed.php b/tests/phpt/array_access/006_as_mixed.php index fb6d7569b2..44dfa778a4 100644 --- a/tests/phpt/array_access/006_as_mixed.php +++ b/tests/phpt/array_access/006_as_mixed.php @@ -1,2 +1,2 @@ // TODO -// Check [.][.] and longer chains, too \ No newline at end of file +// Check [.][.] and longer chains, too diff --git a/tests/phpt/array_access/007_partial_override.php b/tests/phpt/array_access/007_partial_override.php index 0ffdd02fcb..70b786d12e 100644 --- a/tests/phpt/array_access/007_partial_override.php +++ b/tests/phpt/array_access/007_partial_override.php @@ -1 +1 @@ -// TODO \ No newline at end of file +// TODO diff --git a/tests/phpt/array_access/008_multiple_implementors.php b/tests/phpt/array_access/008_multiple_implementors.php index 0ffdd02fcb..70b786d12e 100644 --- a/tests/phpt/array_access/008_multiple_implementors.php +++ b/tests/phpt/array_access/008_multiple_implementors.php @@ -1 +1 @@ -// TODO \ No newline at end of file +// TODO diff --git a/tests/phpt/array_access/100_not_implement.php b/tests/phpt/array_access/100_not_implement.php index e69de29bb2..70b786d12e 100644 --- a/tests/phpt/array_access/100_not_implement.php +++ b/tests/phpt/array_access/100_not_implement.php @@ -0,0 +1 @@ +// TODO diff --git a/tests/phpt/array_access/101_not_override.php b/tests/phpt/array_access/101_not_override.php index 0ffdd02fcb..70b786d12e 100644 --- a/tests/phpt/array_access/101_not_override.php +++ b/tests/phpt/array_access/101_not_override.php @@ -1 +1 @@ -// TODO \ No newline at end of file +// TODO diff --git a/tests/phpt/array_access/Classes/LikeArray.php b/tests/phpt/array_access/Classes/LikeArray.php index f5275a2c10..6e22d33f21 100644 --- a/tests/phpt/array_access/Classes/LikeArray.php +++ b/tests/phpt/array_access/Classes/LikeArray.php @@ -7,8 +7,8 @@ class LikeArray implements \ArrayAccess { protected $data = []; /** - * @param $offset mixed The offset to assign the value to - * @param $value mixed The value to set + * @param $offset mixed + * @param $value mixed */ public function offsetSet($offset, $value) { if (is_null($offset)) { @@ -50,4 +50,4 @@ public function offsetGet($offset) { public function keys() { return array_keys($this->data); } -} \ No newline at end of file +} diff --git a/tests/phpt/array_access/Classes/LoggingLikeArray.php b/tests/phpt/array_access/Classes/LoggingLikeArray.php index bb86c44b5e..0fb46e3ff6 100644 --- a/tests/phpt/array_access/Classes/LoggingLikeArray.php +++ b/tests/phpt/array_access/Classes/LoggingLikeArray.php @@ -7,8 +7,8 @@ class LoggingLikeArray implements \ArrayAccess { protected $data = []; /** - * @param $offset mixed The offset to assign the value to - * @param $value mixed The value to set + * @param $offset mixed + * @param $value mixed */ public function offsetSet($offset, $value) { echo "offsetSet "; @@ -53,4 +53,11 @@ public function offsetGet($offset) { return $this->offsetExists($offset) ? $this->data[$offset] : null; } -} \ No newline at end of file + + /** + * @return mixed[] + */ + public function keys() { + return array_keys($this->data); + } +} From e592e60dea73c45356782dd9b9db4bcdda050aca Mon Sep 17 00:00:00 2001 From: Mikhail Kornaukhov Date: Tue, 22 Oct 2024 17:02:16 +0300 Subject: [PATCH 16/44] rename var in smelling code inside _functions.txt --- builtin-functions/kphp-full/_functions.txt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/builtin-functions/kphp-full/_functions.txt b/builtin-functions/kphp-full/_functions.txt index 4233b3ab34..bf7c8d88a0 100644 --- a/builtin-functions/kphp-full/_functions.txt +++ b/builtin-functions/kphp-full/_functions.txt @@ -1696,9 +1696,9 @@ interface ArrayAccess { // TODO get rid of it if (0) { /** @var ArrayAccess */ - $x = null; - $x->offsetExists(0); - $x->offsetGet(0); - $x->offsetSet(0, 0); - $x->offsetUnset(0); + $_should_be_unique_var_name_123 = null; + $_should_be_unique_var_name_123->offsetExists(0); + $_should_be_unique_var_name_123->offsetGet(0); + $_should_be_unique_var_name_123->offsetSet(0, 0); + $_should_be_unique_var_name_123->offsetUnset(0); } \ No newline at end of file From 959caaa8d83fde21a84718f135d44d20ab8994c9 Mon Sep 17 00:00:00 2001 From: Mikhail Kornaukhov Date: Wed, 23 Oct 2024 18:43:00 +0300 Subject: [PATCH 17/44] add important comment that explains why there is no ub with inheritance chain --- compiler/pipes/wait-for-all-classes.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/compiler/pipes/wait-for-all-classes.cpp b/compiler/pipes/wait-for-all-classes.cpp index 92c3b3ef33..16902484cc 100644 --- a/compiler/pipes/wait-for-all-classes.cpp +++ b/compiler/pipes/wait-for-all-classes.cpp @@ -11,6 +11,9 @@ #include "compiler/data/src-file.h" #include "compiler/data/modulite-data.h" + +// Very important!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + /* * This is a sync point. When `on_finish()` is called (once, in a single thread), then * all php files have been parsed, all classes have been loaded. From 10881d525897d054f493af4a7ac18e1cd3a3d1c5 Mon Sep 17 00:00:00 2001 From: Mikhail Kornaukhov Date: Thu, 24 Oct 2024 15:51:42 +0300 Subject: [PATCH 18/44] support ArrayAccess in mixed [.] --- builtin-functions/kphp-full/_functions.txt | 1 + .../check-modifications-of-const-vars.cpp | 1 + .../core/core-types/definition/mixed.cpp | 73 ++++++++++++++++++- 3 files changed, 73 insertions(+), 2 deletions(-) diff --git a/builtin-functions/kphp-full/_functions.txt b/builtin-functions/kphp-full/_functions.txt index bf7c8d88a0..aec3b81894 100644 --- a/builtin-functions/kphp-full/_functions.txt +++ b/builtin-functions/kphp-full/_functions.txt @@ -1697,6 +1697,7 @@ interface ArrayAccess { if (0) { /** @var ArrayAccess */ $_should_be_unique_var_name_123 = null; + $_should_be_unique_var_name_456 = to_mixed($_should_be_unique_var_name_123); $_should_be_unique_var_name_123->offsetExists(0); $_should_be_unique_var_name_123->offsetGet(0); $_should_be_unique_var_name_123->offsetSet(0, 0); diff --git a/compiler/pipes/check-modifications-of-const-vars.cpp b/compiler/pipes/check-modifications-of-const-vars.cpp index 49b78595a1..40d45a6250 100644 --- a/compiler/pipes/check-modifications-of-const-vars.cpp +++ b/compiler/pipes/check-modifications-of-const-vars.cpp @@ -119,6 +119,7 @@ void CheckModificationsOfConstVars::check_modifications(VertexPtr v, bool write_ TermStringFormat::paint(std::string(std::next(const_var->name.begin(), std::strlen(constant_prefix)), const_var->name.end()), TermStringFormat::red))); } else { + // TODO fix this error when using setting [.] with smart cast kphp_error(modification_allowed, fmt_format("Modification of const variable: {}", TermStringFormat::paint(const_var->name, TermStringFormat::red))); } } diff --git a/runtime-common/core/core-types/definition/mixed.cpp b/runtime-common/core/core-types/definition/mixed.cpp index aa8bfa57a3..1f92c4bd1c 100644 --- a/runtime-common/core/core-types/definition/mixed.cpp +++ b/runtime-common/core/core-types/definition/mixed.cpp @@ -5,6 +5,37 @@ #include "common/wrappers/likely.h" #include "runtime-common/core/runtime-core.h" +// TODO must check that in runtime nothing will get wrong +struct C$ArrayAccess : public may_be_mixed_base { + virtual int get_hash() const noexcept = 0; + + C$ArrayAccess() __attribute__((always_inline)) = default; + ~C$ArrayAccess() __attribute__((always_inline)) = default; +}; + +extern bool f$ArrayAccess$$offsetExists(class_instance const &v$this, mixed const &v$offset) noexcept; +__attribute__((weak)) bool f$ArrayAccess$$offsetExists(class_instance const & /*v$this*/, mixed const & /*v$offset*/) noexcept { + php_error("using stub of offsetExists"); + return {}; +} + +extern mixed f$ArrayAccess$$offsetGet(class_instance const &v$this, mixed const &v$offset) noexcept; +__attribute__((weak)) mixed f$ArrayAccess$$offsetGet(class_instance const & /*v$this*/, mixed const & /*v$offset*/) noexcept { + php_error("using stub of offsetGet"); + return {}; +} + +extern void f$ArrayAccess$$offsetSet(class_instance const &v$this, mixed const &v$offset, mixed const &v$value) noexcept; +__attribute__((weak)) void f$ArrayAccess$$offsetSet(class_instance const & /*v$this*/, mixed const & /*v$offset*/, + mixed const & /*v$value*/) noexcept { + php_error("using stub of offsetSet"); +} + +extern void f$ArrayAccess$$offsetUnset(class_instance const &v$this, mixed const &v$offset) noexcept; +__attribute__((weak)) void f$ArrayAccess$$offsetUnset(class_instance const & /*v$this*/, mixed const & /*v$offset*/) noexcept { + php_error("using stub of offsetUnset"); +} + void mixed::copy_from(const mixed &other) { switch (other.get_type()) { case type::STRING: @@ -1056,14 +1087,21 @@ int64_t mixed::compare(const mixed &rhs) const { mixed &mixed::operator[](int64_t int_key) { if (unlikely (get_type() != type::ARRAY)) { if (get_type() == type::STRING) { - php_warning("Writing to string by offset is't supported"); + php_warning("Writing to string by offset isn't supported"); return empty_value(); } if (get_type() == type::NUL || (get_type() == type::BOOLEAN && !as_bool())) { type_ = type::ARRAY; new(&as_array()) array(); - } else { + } + else if (get_type() == type::OBJECT) { + auto xxx = from_mixed>(*this, string()); + puts("CASTED OK!!!"); + return empty_value(); + } + else { + php_warning("type = %d\n", (int)get_type()); php_warning("Cannot use a value \"%s\" of type %s as an array, index = %" PRIi64, to_string_without_warning(*this).c_str(), get_type_or_class_name(), int_key); return empty_value(); } @@ -1159,6 +1197,15 @@ void mixed::set_value(int64_t int_key, const mixed &v) { return; } + // TODO don't forget to use f$is_a !!! + // It'll look like an instance cast + if (get_type() == type::OBJECT) { + auto xxx = from_mixed>(*this, string()); + puts("CASTED OK!!!"); + f$ArrayAccess$$offsetSet(xxx, int_key, v); + return; + } + if (get_type() == type::NUL || (get_type() == type::BOOLEAN && !as_bool())) { type_ = type::ARRAY; new(&as_array()) array(); @@ -1195,6 +1242,14 @@ void mixed::set_value(const string &string_key, const mixed &v) { return; } + // TODO don't forget to use f$is_a !!! + // It'll look like an instance cast + if (get_type() == type::OBJECT) { + auto xxx = from_mixed>(*this, string()); + puts("CASTED OK!!!"); + f$ArrayAccess$$offsetSet(xxx, string_key, v); + return; + } if (get_type() == type::NUL || (get_type() == type::BOOLEAN && !as_bool())) { type_ = type::ARRAY; new(&as_array()) array(); @@ -1261,6 +1316,13 @@ const mixed mixed::get_value(int64_t int_key) const { return string(1, as_string()[static_cast(int_key)]); } + // TODO check with f$is_a + if (get_type() == type::OBJECT) { + auto xxx = from_mixed>(*this, string()); + puts("CASTED OK!!!"); + return f$ArrayAccess$$offsetGet(xxx, int_key); + } + if (get_type() != type::NUL && (get_type() != type::BOOLEAN || as_bool())) { php_warning("Cannot use a value \"%s\" of type %s as an array, index = %" PRIi64, to_string_without_warning(*this).c_str(), get_type_or_class_name(), int_key); } @@ -1284,6 +1346,13 @@ const mixed mixed::get_value(const string &string_key) const { return string(1, as_string()[static_cast(int_val)]); } + // TODO check with f$is_a + if (get_type() == type::OBJECT) { + auto xxx = from_mixed>(*this, string()); + puts("CASTED OK!!!"); + return f$ArrayAccess$$offsetGet(xxx, string_key); + } + if (get_type() != type::NUL && (get_type() != type::BOOLEAN || as_bool())) { php_warning("Cannot use a value \"%s\" of type %s as an array, index = %s", to_string_without_warning(*this).c_str(), get_type_or_class_name(), string_key.c_str()); } From 8ff1e5521a6235bafc857e13ce984f29771a9f62 Mon Sep 17 00:00:00 2001 From: Mikhail Kornaukhov Date: Thu, 24 Oct 2024 18:59:06 +0300 Subject: [PATCH 19/44] add test and found a bug with not linking redifinitions --- .../core/core-types/definition/mixed.cpp | 17 ++-- tests/phpt/array_access/006_as_mixed.php | 20 +++++ tests/phpt/array_access/Classes/User.php | 89 +++++++++++++++++++ 3 files changed, 117 insertions(+), 9 deletions(-) create mode 100644 tests/phpt/array_access/Classes/User.php diff --git a/runtime-common/core/core-types/definition/mixed.cpp b/runtime-common/core/core-types/definition/mixed.cpp index 1f92c4bd1c..d4623f7ed2 100644 --- a/runtime-common/core/core-types/definition/mixed.cpp +++ b/runtime-common/core/core-types/definition/mixed.cpp @@ -4,6 +4,7 @@ #include "common/wrappers/likely.h" #include "runtime-common/core/runtime-core.h" +#include // TODO must check that in runtime nothing will get wrong struct C$ArrayAccess : public may_be_mixed_base { @@ -13,27 +14,29 @@ struct C$ArrayAccess : public may_be_mixed_base { ~C$ArrayAccess() __attribute__((always_inline)) = default; }; + +// TODO in tests uses stubs =( extern bool f$ArrayAccess$$offsetExists(class_instance const &v$this, mixed const &v$offset) noexcept; __attribute__((weak)) bool f$ArrayAccess$$offsetExists(class_instance const & /*v$this*/, mixed const & /*v$offset*/) noexcept { - php_error("using stub of offsetExists"); + assert(0 && "using stub of offsetExists"); return {}; } extern mixed f$ArrayAccess$$offsetGet(class_instance const &v$this, mixed const &v$offset) noexcept; __attribute__((weak)) mixed f$ArrayAccess$$offsetGet(class_instance const & /*v$this*/, mixed const & /*v$offset*/) noexcept { - php_error("using stub of offsetGet"); + assert(0 && "using stub of offsetGet"); return {}; } extern void f$ArrayAccess$$offsetSet(class_instance const &v$this, mixed const &v$offset, mixed const &v$value) noexcept; __attribute__((weak)) void f$ArrayAccess$$offsetSet(class_instance const & /*v$this*/, mixed const & /*v$offset*/, mixed const & /*v$value*/) noexcept { - php_error("using stub of offsetSet"); + assert(0 && "using stub of offsetSet"); } extern void f$ArrayAccess$$offsetUnset(class_instance const &v$this, mixed const &v$offset) noexcept; __attribute__((weak)) void f$ArrayAccess$$offsetUnset(class_instance const & /*v$this*/, mixed const & /*v$offset*/) noexcept { - php_error("using stub of offsetUnset"); + assert(0 && "using stub of offsetUnset"); } void mixed::copy_from(const mixed &other) { @@ -1097,7 +1100,6 @@ mixed &mixed::operator[](int64_t int_key) { } else if (get_type() == type::OBJECT) { auto xxx = from_mixed>(*this, string()); - puts("CASTED OK!!!"); return empty_value(); } else { @@ -1201,7 +1203,6 @@ void mixed::set_value(int64_t int_key, const mixed &v) { // It'll look like an instance cast if (get_type() == type::OBJECT) { auto xxx = from_mixed>(*this, string()); - puts("CASTED OK!!!"); f$ArrayAccess$$offsetSet(xxx, int_key, v); return; } @@ -1246,7 +1247,6 @@ void mixed::set_value(const string &string_key, const mixed &v) { // It'll look like an instance cast if (get_type() == type::OBJECT) { auto xxx = from_mixed>(*this, string()); - puts("CASTED OK!!!"); f$ArrayAccess$$offsetSet(xxx, string_key, v); return; } @@ -1319,7 +1319,6 @@ const mixed mixed::get_value(int64_t int_key) const { // TODO check with f$is_a if (get_type() == type::OBJECT) { auto xxx = from_mixed>(*this, string()); - puts("CASTED OK!!!"); return f$ArrayAccess$$offsetGet(xxx, int_key); } @@ -1348,8 +1347,8 @@ const mixed mixed::get_value(const string &string_key) const { // TODO check with f$is_a if (get_type() == type::OBJECT) { + printf("Get [\"%s\"]\n", string_key.c_str()); auto xxx = from_mixed>(*this, string()); - puts("CASTED OK!!!"); return f$ArrayAccess$$offsetGet(xxx, string_key); } diff --git a/tests/phpt/array_access/006_as_mixed.php b/tests/phpt/array_access/006_as_mixed.php index 44dfa778a4..b2e61ea8d6 100644 --- a/tests/phpt/array_access/006_as_mixed.php +++ b/tests/phpt/array_access/006_as_mixed.php @@ -1,2 +1,22 @@ +@ok + 1, "name" => "Paul", "friends" => [2, 4, 256]]; + dump_user_mixed($user_arr); + dump_user_mixed(to_mixed($user_obj)); +} + +test(); diff --git a/tests/phpt/array_access/Classes/User.php b/tests/phpt/array_access/Classes/User.php new file mode 100644 index 0000000000..5f877561a9 --- /dev/null +++ b/tests/phpt/array_access/Classes/User.php @@ -0,0 +1,89 @@ +id = $id_; + $this->name = $name_; + $this->friends = $friends_; + } + + /** + * @param $offset mixed + * @param $value mixed + */ + public function offsetSet($offset, $value) { + if ($offset === null) { + echo "Append is not supported"; + return; + } + $offset = strval($offset); + + var_dump("Offset in set " . $offset); + + + if ($offset === "id") { + $this->id = (int)$value; + return; + } + if ($offset === "name") { + $this->name = (string)$value; + return; + } + if ($offset === "friends") { + $this->friends = array_map('intval', $value); + return; + } + echo "Unsupported offset \"" . $offset . "\"\n"; + } + + /** + * @param $offset mixed + * @return bool + */ + public function offsetExists($offset) { + $offset = strval($offset); + return $offset === "id" || $offset === "name" || $offset === "friends"; + } + + /** + * @param $offset mixed + * @return void + */ + public function offsetUnset($offset) { + echo "Unset is not supported"; + } + + /** + * @param $offset mixed + * @return mixed + */ + public function offsetGet($offset) { + $offset = strval($offset); + var_dump("Offset in get " . $offset); + + if ($offset === "id") { + return $this->id; + } + if ($offset === "name") { + return $this->name; + } + if ($offset === "friends") { + return $this->friends; + } + echo "No match in get!!"; + + return null; + } +} From fc7ec2d63bb4319997ba4cf2828b1f9035f88448 Mon Sep 17 00:00:00 2001 From: Mikhail Kornaukhov Date: Fri, 25 Oct 2024 14:35:57 +0300 Subject: [PATCH 20/44] fix linking error --- compiler/make/make.cpp | 14 ++++++++++---- compiler/make/objs-to-obj-target.h | 9 ++++++++- compiler/pipes/code-gen.cpp | 4 ++++ .../core/core-types/definition/mixed.cpp | 12 +++++++----- 4 files changed, 29 insertions(+), 10 deletions(-) diff --git a/compiler/make/make.cpp b/compiler/make/make.cpp index d4624c4ec7..27caf3b241 100644 --- a/compiler/make/make.cpp +++ b/compiler/make/make.cpp @@ -96,8 +96,8 @@ class MakeSetup { return create_target(new H2PchTarget(), to_targets(header_h), pch); } - Target *create_objs2obj_target(std::vector objs, File *obj) { - return create_target(new Objs2ObjTarget(), to_targets(std::move(objs)), obj); + Target *create_objs2obj_target(std::vector objs, File *obj, bool force_obj = false) { + return create_target(new Objs2ObjTarget(force_obj), to_targets(std::move(objs)), obj); } Target *create_objs2bin_target(std::vector objs, File *bin) { @@ -395,10 +395,16 @@ static std::vector create_obj_files(MakeSetup *make, Index &obj_dir, con for (File *f : deps) { vk::hash_combine(hash, vk::std_hash(f->name)); } + + bool force_obj = name_and_files.first == "internal_interfaces"; + auto intermediate_file_name = fmt_format("{}_{:x}.{}", name_and_files.first, hash, - G->settings().dynamic_incremental_linkage.get() ? "so" : "o"); + G->settings().dynamic_incremental_linkage.get() && (!force_obj) ? "so" : "o"); File *obj_file = obj_dir.insert_file(std::move(intermediate_file_name)); - make->create_objs2obj_target(std::move(deps), obj_file); + + + + make->create_objs2obj_target(std::move(deps), obj_file, force_obj); objs.push_back(obj_file); } fmt_fprintf(stderr, "objs cnt = {}\n", objs.size()); diff --git a/compiler/make/objs-to-obj-target.h b/compiler/make/objs-to-obj-target.h index 11858b6b42..8940a20782 100644 --- a/compiler/make/objs-to-obj-target.h +++ b/compiler/make/objs-to-obj-target.h @@ -10,12 +10,19 @@ #include "compiler/make/target.h" class Objs2ObjTarget : public Target { +bool force_obj; + public: + explicit Objs2ObjTarget(bool force_obj_) : force_obj(force_obj_) {} + std::string get_cmd() final { std::stringstream ss; ss << settings->cxx.get() << " " << settings->cxx_toolchain_option.get() << - " " << settings->incremental_linker_flags.get() << + " " << + // settings->incremental_linker_flags.get() << + (force_obj ? "-r -nostdlib" : settings->incremental_linker_flags.get()) + << " -o " << target() << " " << dep_list(); return ss.str(); diff --git a/compiler/pipes/code-gen.cpp b/compiler/pipes/code-gen.cpp index 32674d4462..013563c18d 100644 --- a/compiler/pipes/code-gen.cpp +++ b/compiler/pipes/code-gen.cpp @@ -214,6 +214,10 @@ std::string CodeGenF::calc_subdir_for_function(FunctionPtr func) { return "o_l"; } + if (func->name.find("ArrayAccess") != std::string::npos) { + return "internal_interfaces"; + } + int bucket = vk::std_hash(func->file_id->short_file_name) % 100; return "o_" + std::to_string(bucket); } diff --git a/runtime-common/core/core-types/definition/mixed.cpp b/runtime-common/core/core-types/definition/mixed.cpp index d4623f7ed2..f75acafe60 100644 --- a/runtime-common/core/core-types/definition/mixed.cpp +++ b/runtime-common/core/core-types/definition/mixed.cpp @@ -16,25 +16,27 @@ struct C$ArrayAccess : public may_be_mixed_base { // TODO in tests uses stubs =( -extern bool f$ArrayAccess$$offsetExists(class_instance const &v$this, mixed const &v$offset) noexcept; +// bool f$ArrayAccess$$offsetExists(class_instance const &v$this, mixed const &v$offset) noexcept; +// mixed f$ArrayAccess$$offsetGet(class_instance const &v$this, mixed const &v$offset) noexcept; +// void f$ArrayAccess$$offsetSet(class_instance const &v$this, mixed const &v$offset, mixed const &v$value) noexcept; +// void f$ArrayAccess$$offsetUnset(class_instance const &v$this, mixed const &v$offset) noexcept; + + __attribute__((weak)) bool f$ArrayAccess$$offsetExists(class_instance const & /*v$this*/, mixed const & /*v$offset*/) noexcept { assert(0 && "using stub of offsetExists"); return {}; } -extern mixed f$ArrayAccess$$offsetGet(class_instance const &v$this, mixed const &v$offset) noexcept; __attribute__((weak)) mixed f$ArrayAccess$$offsetGet(class_instance const & /*v$this*/, mixed const & /*v$offset*/) noexcept { assert(0 && "using stub of offsetGet"); return {}; } -extern void f$ArrayAccess$$offsetSet(class_instance const &v$this, mixed const &v$offset, mixed const &v$value) noexcept; __attribute__((weak)) void f$ArrayAccess$$offsetSet(class_instance const & /*v$this*/, mixed const & /*v$offset*/, mixed const & /*v$value*/) noexcept { assert(0 && "using stub of offsetSet"); } -extern void f$ArrayAccess$$offsetUnset(class_instance const &v$this, mixed const &v$offset) noexcept; __attribute__((weak)) void f$ArrayAccess$$offsetUnset(class_instance const & /*v$this*/, mixed const & /*v$offset*/) noexcept { assert(0 && "using stub of offsetUnset"); } @@ -1347,7 +1349,7 @@ const mixed mixed::get_value(const string &string_key) const { // TODO check with f$is_a if (get_type() == type::OBJECT) { - printf("Get [\"%s\"]\n", string_key.c_str()); + // printf("Get [\"%s\"]\n", string_key.c_str()); auto xxx = from_mixed>(*this, string()); return f$ArrayAccess$$offsetGet(xxx, string_key); } From ca6096152216310e7ab6eb349cd280bf1054d1c2 Mon Sep 17 00:00:00 2001 From: Mikhail Kornaukhov Date: Fri, 25 Oct 2024 15:50:02 +0300 Subject: [PATCH 21/44] add negative tests and small fixes --- compiler/pipes/optimization.cpp | 8 ++++++-- tests/phpt/array_access/005_inheritance.php | 6 +++--- tests/phpt/array_access/006_as_mixed.php | 12 +++++++++--- .../phpt/array_access/007_partial_override.php | 1 - .../array_access/008_multiple_implementors.php | 1 - tests/phpt/array_access/100_not_implement.php | 14 +++++++++++++- tests/phpt/array_access/101_not_override.php | 17 ++++++++++++++++- 7 files changed, 47 insertions(+), 12 deletions(-) delete mode 100644 tests/phpt/array_access/007_partial_override.php delete mode 100644 tests/phpt/array_access/008_multiple_implementors.php diff --git a/compiler/pipes/optimization.cpp b/compiler/pipes/optimization.cpp index 8966c96e7a..f3af390878 100644 --- a/compiler/pipes/optimization.cpp +++ b/compiler/pipes/optimization.cpp @@ -163,7 +163,10 @@ VertexPtr OptimizationPass::optimize_set_push_back(VertexAdaptor set_op) // TODO doesn't it have the problem with that some parent classes are not linked in chain yet? const auto *method = klass->get_instance_method("offsetSet"); - kphp_assert_msg(method, fmt::format("Class {} does not implement offsetSet", klass->name).c_str()); + if (!method) { + kphp_error(method, fmt_format("Class {} does not implement offsetSet", klass->name).c_str()); + return a; + } // TODO assume here that key is present @@ -243,7 +246,8 @@ VertexPtr OptimizationPass::optimize_index(VertexAdaptor index) { const auto *method = klass->get_instance_method("offsetGet"); if (!method) { - kphp_assert_msg(method, "bad method"); + kphp_error(method, fmt_format("Class {} does not implement offsetSet", klass->name).c_str()); + return index; } // TODO assume here that key is present auto new_call = VertexAdaptor::create(lhs, index->key()).set_location(lhs); diff --git a/tests/phpt/array_access/005_inheritance.php b/tests/phpt/array_access/005_inheritance.php index 58b0abe8a8..e8f935797c 100644 --- a/tests/phpt/array_access/005_inheritance.php +++ b/tests/phpt/array_access/005_inheritance.php @@ -29,7 +29,6 @@ function test() { $obj[] = 123; $obj[] = "abcd"; $obj[123] = ["string", 0.125, [1, 2, 3]]; - $obj[] = $obj[123]; foreach ($obj->keys() as $key) { var_dump($obj[$key]); @@ -37,8 +36,9 @@ function test() { if ($obj instanceof Classes\LoggingLikeArray) { // TODO operator[] does not work here - $obj->offsetSet(null, $obj[124]); - var_dump($obj[124]); + // TODO it's ub warning for arrays + $obj->offsetSet(null, $obj[123]); + var_dump($obj[123]); } else { die_if_failure(false, "false negative instanceof Classes\LoggingLikeArray"); } diff --git a/tests/phpt/array_access/006_as_mixed.php b/tests/phpt/array_access/006_as_mixed.php index b2e61ea8d6..11158938ae 100644 --- a/tests/phpt/array_access/006_as_mixed.php +++ b/tests/phpt/array_access/006_as_mixed.php @@ -2,9 +2,6 @@ 1, "name" => "Paul", "friends" => [2, 4, 256]]; + dump_user_mixed($user_arr); dump_user_mixed(to_mixed($user_obj)); + + dump_first_friend(to_mixed($user_obj)); } test(); diff --git a/tests/phpt/array_access/007_partial_override.php b/tests/phpt/array_access/007_partial_override.php deleted file mode 100644 index 70b786d12e..0000000000 --- a/tests/phpt/array_access/007_partial_override.php +++ /dev/null @@ -1 +0,0 @@ -// TODO diff --git a/tests/phpt/array_access/008_multiple_implementors.php b/tests/phpt/array_access/008_multiple_implementors.php deleted file mode 100644 index 70b786d12e..0000000000 --- a/tests/phpt/array_access/008_multiple_implementors.php +++ /dev/null @@ -1 +0,0 @@ -// TODO diff --git a/tests/phpt/array_access/100_not_implement.php b/tests/phpt/array_access/100_not_implement.php index 70b786d12e..20664dec52 100644 --- a/tests/phpt/array_access/100_not_implement.php +++ b/tests/phpt/array_access/100_not_implement.php @@ -1 +1,13 @@ -// TODO +@kphp_should_fail +/Class Sample does not implement offsetSet/ + Date: Mon, 28 Oct 2024 12:51:58 +0300 Subject: [PATCH 22/44] add useful comments and get rid of useless comments --- compiler/code-gen/vertex-compiler.cpp | 1 + runtime-common/core/core-types/decl/mixed_decl.inl | 2 ++ runtime-common/core/core-types/definition/mixed.cpp | 8 -------- 3 files changed, 3 insertions(+), 8 deletions(-) diff --git a/compiler/code-gen/vertex-compiler.cpp b/compiler/code-gen/vertex-compiler.cpp index 19b3089398..97b33d76c8 100644 --- a/compiler/code-gen/vertex-compiler.cpp +++ b/compiler/code-gen/vertex-compiler.cpp @@ -1786,6 +1786,7 @@ bool try_compile_append_inplace(VertexAdaptor root, CodeGenerator &W return false; } +// This also compiles index of mixed void compile_index_of_array(VertexAdaptor root, CodeGenerator &W) { bool used_as_rval = root->rl_type != val_l; if (!used_as_rval) { diff --git a/runtime-common/core/core-types/decl/mixed_decl.inl b/runtime-common/core/core-types/decl/mixed_decl.inl index e407fd3a49..1a61f2eca6 100644 --- a/runtime-common/core/core-types/decl/mixed_decl.inl +++ b/runtime-common/core/core-types/decl/mixed_decl.inl @@ -90,6 +90,8 @@ public: mixed &append(const string &v); mixed &append(tmp_string v); + // `operator[]` is only used inside runtime (runtime/interface.cpp, for example) + // and isn't used in codegen,`set_value()` / `get_value()` fulfil this purpose mixed &operator[](int64_t int_key); mixed &operator[](int32_t key) { return (*this)[int64_t{key}]; } mixed &operator[](const string &string_key); diff --git a/runtime-common/core/core-types/definition/mixed.cpp b/runtime-common/core/core-types/definition/mixed.cpp index f75acafe60..cabcb31aff 100644 --- a/runtime-common/core/core-types/definition/mixed.cpp +++ b/runtime-common/core/core-types/definition/mixed.cpp @@ -14,14 +14,6 @@ struct C$ArrayAccess : public may_be_mixed_base { ~C$ArrayAccess() __attribute__((always_inline)) = default; }; - -// TODO in tests uses stubs =( -// bool f$ArrayAccess$$offsetExists(class_instance const &v$this, mixed const &v$offset) noexcept; -// mixed f$ArrayAccess$$offsetGet(class_instance const &v$this, mixed const &v$offset) noexcept; -// void f$ArrayAccess$$offsetSet(class_instance const &v$this, mixed const &v$offset, mixed const &v$value) noexcept; -// void f$ArrayAccess$$offsetUnset(class_instance const &v$this, mixed const &v$offset) noexcept; - - __attribute__((weak)) bool f$ArrayAccess$$offsetExists(class_instance const & /*v$this*/, mixed const & /*v$offset*/) noexcept { assert(0 && "using stub of offsetExists"); return {}; From 2775dfe896ba9cc21806c84a54a894a8b812b144 Mon Sep 17 00:00:00 2001 From: Mikhail Kornaukhov Date: Wed, 30 Oct 2024 12:25:11 +0300 Subject: [PATCH 23/44] support basic chaining assignment for ArrayAccess --- compiler/code-gen/vertex-compiler.cpp | 18 ++++ compiler/compiler.cmake | 1 + compiler/compiler.cpp | 2 + compiler/pipes/check-ub.cpp | 3 + ...x-chaining-assignment-for-array-access.cpp | 58 +++++++++++++ ...fix-chaining-assignment-for-array-access.h | 16 ++++ compiler/pipes/optimization.cpp | 17 ++-- compiler/vertex-desc.json | 22 +++++ .../core/core-types/decl/mixed_decl.inl | 5 +- runtime-common/core/runtime-core.h | 4 + .../array_access/007_assignment_chain.php | 82 +++++++++++++++++++ tests/phpt/array_access/Classes/LikeArray.php | 5 ++ .../array_access/Classes/LoggingLikeArray.php | 5 ++ 13 files changed, 229 insertions(+), 9 deletions(-) create mode 100644 compiler/pipes/fix-chaining-assignment-for-array-access.cpp create mode 100644 compiler/pipes/fix-chaining-assignment-for-array-access.h create mode 100644 tests/phpt/array_access/007_assignment_chain.php diff --git a/compiler/code-gen/vertex-compiler.cpp b/compiler/code-gen/vertex-compiler.cpp index 97b33d76c8..f43ce2cb50 100644 --- a/compiler/code-gen/vertex-compiler.cpp +++ b/compiler/code-gen/vertex-compiler.cpp @@ -2385,6 +2385,24 @@ void compile_common_op(VertexPtr root, CodeGenerator &W) { W << TypeName(tp) << alloc_function; break; } + case op_set_with_ret: { + auto xxx = root.try_as(); + /* + { + mixed idx = index; + mixed val = value; + func(obj, idx, val); + val; + } + */ + W << "SAFE_SET_OP_ARR_ACC("; + W << xxx->obj() << ", "; + W << xxx->offset() << ", "; + W << xxx->value() << ", "; + W << "f$" << xxx->set_method->name; + W << ")"; + break; + } default: kphp_fail(); break; diff --git a/compiler/compiler.cmake b/compiler/compiler.cmake index 8d6244313e..bcfa0a959e 100644 --- a/compiler/compiler.cmake +++ b/compiler/compiler.cmake @@ -151,6 +151,7 @@ prepend(KPHP_COMPILER_PIPES_SOURCES pipes/ file-to-tokens.cpp filter-only-actually-used.cpp final-check.cpp + fix-chaining-assignment-for-array-access.cpp fix-returns.cpp gen-tree-postprocess.cpp generate-virtual-methods.cpp diff --git a/compiler/compiler.cpp b/compiler/compiler.cpp index 9544361d3a..d1a54576fa 100644 --- a/compiler/compiler.cpp +++ b/compiler/compiler.cpp @@ -64,6 +64,7 @@ #include "compiler/pipes/file-to-tokens.h" #include "compiler/pipes/filter-only-actually-used.h" #include "compiler/pipes/final-check.h" +#include "compiler/pipes/fix-chaining-assignment-for-array-access.h" #include "compiler/pipes/fix-returns.h" #include "compiler/pipes/gen-tree-postprocess.h" #include "compiler/pipes/generate-virtual-methods.h" @@ -287,6 +288,7 @@ bool compiler_execute(CompilerSettings *settings) { >> PassC{} >> PassC{} >> PassC{} + >> PassC{} >> PassC{} >> PassC{} >> PassC{} diff --git a/compiler/pipes/check-ub.cpp b/compiler/pipes/check-ub.cpp index b9607f7957..d75af87f53 100644 --- a/compiler/pipes/check-ub.cpp +++ b/compiler/pipes/check-ub.cpp @@ -206,6 +206,9 @@ void fix_ub_dfs(VertexPtr v, UBMergeData *data, VertexPtr parent = VertexPtr()) stage::set_location(save_location); if (res > 0) { + // TODO + // Find out how fix ub pass works + // and get rid of useless warning bool supported = vk::any_of_equal(v->type(), op_set, op_set_value, op_push_back, op_push_back_return, op_array, op_index) || OpInfo::rl(v->type()) == rl_set; if (supported) { diff --git a/compiler/pipes/fix-chaining-assignment-for-array-access.cpp b/compiler/pipes/fix-chaining-assignment-for-array-access.cpp new file mode 100644 index 0000000000..36a2dc0cad --- /dev/null +++ b/compiler/pipes/fix-chaining-assignment-for-array-access.cpp @@ -0,0 +1,58 @@ +// Compiler for PHP (aka KPHP) +// Copyright (c) 2020 LLC «V Kontakte» +// Distributed under the GPL v3 License, see LICENSE.notice.txt + +#include "compiler/pipes/fix-chaining-assignment-for-array-access.h" + +#include "auto/compiler/vertex/vertex-types.h" +#include "compiler/data/class-data.h" +#include "compiler/data/vertex-adaptor.h" +#include "compiler/inferring/primitive-type.h" +#include "compiler/inferring/public.h" +#include + + +// TODO maybe on_enter? +VertexPtr FixChainingAssignmentForArrayAccessPass::on_exit_vertex(VertexPtr root) { + if (root->type() != op_set) { + return root; + } + + auto set = root.as(); + if (set->lhs()->type() != op_func_call) { + return root; + } + + auto func_call = set->lhs().try_as(); + if (func_call->auto_inserted) { + if (func_call->func_id->local_name() == "offsetGet") { + auto obj_arg = func_call->args()[0]; + const auto *tpe = tinf::get_type(obj_arg); // funny + + assert(tpe); + assert(tpe->get_real_ptype() == tp_Class); + + auto klass = tpe->class_type(); + + const auto *method = klass->get_instance_method("offsetSet"); + + if (!method) { + kphp_error(method, fmt_format("Class {} does not implement offsetSet", klass->name).c_str()); + return root; + } + + auto sub_val = set->rhs(); + + + auto key = func_call->args()[1]; + + auto zzz = VertexAdaptor::create(key, sub_val, obj_arg); + zzz->set_method = method->function; + + return zzz; + } + } + + + return root; +} diff --git a/compiler/pipes/fix-chaining-assignment-for-array-access.h b/compiler/pipes/fix-chaining-assignment-for-array-access.h new file mode 100644 index 0000000000..6fd64b6774 --- /dev/null +++ b/compiler/pipes/fix-chaining-assignment-for-array-access.h @@ -0,0 +1,16 @@ +// Compiler for PHP (aka KPHP) +// Copyright (c) 2024 LLC «V Kontakte» +// Distributed under the GPL v3 License, see LICENSE.notice.txt + +#pragma once + +#include "compiler/function-pass.h" + +class FixChainingAssignmentForArrayAccessPass final : public FunctionPassBase { +public: + std::string get_description() override { + return "Fix chaining assignment for ArrayAccess"; + } + + VertexPtr on_exit_vertex(VertexPtr root) final; +}; diff --git a/compiler/pipes/optimization.cpp b/compiler/pipes/optimization.cpp index f3af390878..ec6a5ed011 100644 --- a/compiler/pipes/optimization.cpp +++ b/compiler/pipes/optimization.cpp @@ -183,7 +183,6 @@ VertexPtr OptimizationPass::optimize_set_push_back(VertexAdaptor set_op) } else { result = VertexAdaptor::create(a, b, c); } - } result->location = set_op->get_location(); result->extra_type = op_ex_internal_func; @@ -256,15 +255,19 @@ VertexPtr OptimizationPass::optimize_index(VertexAdaptor index) { new_call->extra_type = op_ex_func_call_arrow; // Is that right? new_call->auto_inserted = true; new_call->rl_type = index->rl_type; + new_call.set_location(index); + + // TODO maybe I'll have to uncomment code below during + // getting rid of if(0) trash in _functions.txt - current_function->dep.emplace_back(method->function); + // current_function->dep.emplace_back(method->function); // For interfaces, I should construct type node here? - auto &node = new_call->tinf_node; - auto * tdata = new TypeData(*method->function->tinf_node.get_type()); - auto xxx = method->function->tinf_node; - node.set_type(tdata); - tinf::get_type(new_call); // why OK for LikeArray, but bad for array access + // auto &node = new_call->tinf_node; + // auto * tdata = new TypeData(*method->function->tinf_node.get_type()); + // auto xxx = method->function->tinf_node; + // node.set_type(tdata); + // tinf::get_type(new_call); // why OK for LikeArray, but bad for array access return new_call; } diff --git a/compiler/vertex-desc.json b/compiler/vertex-desc.json index b88a20d3dd..b1691f7ceb 100644 --- a/compiler/vertex-desc.json +++ b/compiler/vertex-desc.json @@ -569,6 +569,28 @@ "str": "seq_rval" } }, + { + "comment": "node for array access hack", + "name": "op_set_with_ret", + "base_name": "meta_op_base", + "props": { + "rl": "rl_func", + "cnst": "cnst_nonconst_func", + "type": "common_op", + "str": "seq_with_ret" + }, + "sons": { + "offset": 0, + "value": 1, + "obj": 2 + }, + "extra_fields": { + "set_method": { + "type": "FunctionPtr", + "default": "{}" + } + } + }, { "comment": "holds op_function params in params()", "name": "op_func_param_list", diff --git a/runtime-common/core/core-types/decl/mixed_decl.inl b/runtime-common/core/core-types/decl/mixed_decl.inl index 1a61f2eca6..89d34a9d13 100644 --- a/runtime-common/core/core-types/decl/mixed_decl.inl +++ b/runtime-common/core/core-types/decl/mixed_decl.inl @@ -90,8 +90,9 @@ public: mixed &append(const string &v); mixed &append(tmp_string v); - // `operator[]` is only used inside runtime (runtime/interface.cpp, for example) - // and isn't used in codegen,`set_value()` / `get_value()` fulfil this purpose + // `operator[]` is used in runtime directly and + // during indirect assignment with macros + // For example, $x[0] = $x[1] = f(); mixed &operator[](int64_t int_key); mixed &operator[](int32_t key) { return (*this)[int64_t{key}]; } mixed &operator[](const string &string_key); diff --git a/runtime-common/core/runtime-core.h b/runtime-common/core/runtime-core.h index c70d1a5845..f15c774341 100644 --- a/runtime-common/core/runtime-core.h +++ b/runtime-common/core/runtime-core.h @@ -48,6 +48,10 @@ #define FFI_CALL(call) ({ dl::CriticalSectionGuard critical_section___; (call); }) #define FFI_INVOKE_CALLBACK(call) ({ dl::NonCriticalSectionGuard non_critical_section___; (call); }) +// In case of object, it would be `{offsetSet(ptr, key, val); val;}` +#define SAFE_SET_OP_MIXED(m, idx, idx_type, val, val_type) ({idx_type idx_tmp___ = idx; val_type val_tmp___ = val; m.set_on_and_get(idx_tmp__, val_tmp___);}) +#define SAFE_SET_OP_ARR_ACC(obj, idx, val, method) ({mixed idx_tmp___ = idx; mixed val_tmp___ = val; method(obj, idx_tmp___, val_tmp___); val_tmp___;}) + #define SAFE_SET_OP(a, op, b, b_type) ({b_type b_tmp___ = b; a op std::move(b_tmp___);}) #define SAFE_SET_FUNC_OP(a, func, b, b_type) ({b_type b_tmp___ = b; func (a, b_tmp___);}) #define SAFE_INDEX(a, b, b_type) a[({b_type b_tmp___ = b; b_tmp___;})] diff --git a/tests/phpt/array_access/007_assignment_chain.php b/tests/phpt/array_access/007_assignment_chain.php new file mode 100644 index 0000000000..78018969ca --- /dev/null +++ b/tests/phpt/array_access/007_assignment_chain.php @@ -0,0 +1,82 @@ +@ok +$x = $y; + } +} + +function test_common() { + $arr = [1, 2, "asd"]; + + $obj = new Classes\LoggingLikeArray([1, 2, 3, "kek" => "lol"]); + + $obj[0] = $obj[1] = "str1"; + $obj[4] = $obj[42] = $obj[111] = ""; + $obj[3] = $obj[42] = $arr[4] = $arr["kek"] = $obj["lol"] = $obj["arbidol"] = ["", 0.1, 1, null]; + + $keys = $obj->keys(); + foreach ($keys as $key) { + var_dump($obj[$key]); + } + + // $obj[3] = $obj[42] = $arr[4] = $arr["kek"] = $obj["lol"] = $obj["arbidol"] = to_mixed(new K()); + + // if ($obj[3] instanceof K) { + // $obj[3] + // } +} + +function test_common_obj_as_mixed() { + $arr = [1, 2, "asd"]; + + $obj = to_mixed(new Classes\LoggingLikeArray([1, 2, 3, "kek" => "lol"])); + + $obj[0] = $obj[1] = "str1"; + $obj[4] = $obj[42] = $obj[111] = ""; + $obj[3] = $obj[42] = $arr[4] = $arr["kek"] = $obj["lol"] = $obj["arbidol"] = ["", 0.1, 1, null]; + + if ($obj instanceof Classes\LoggingLikeArray) { + $keys = $obj->keys(); + foreach ($keys as $key) { + var_dump($obj[$key]); + } + } else { + die_if_failure(false, "Incorrect cast"); + } + + + + + // $obj[3] = $obj[42] = $arr[4] = $arr["kek"] = $obj["lol"] = $obj["arbidol"] = to_mixed(new K()); + + // if ($obj[3] instanceof K) { + // $obj[3] + // } +} + +function test_set_obj_as_mixed() { + $obj = new Classes\LoggingLikeArray(); + $obj[3] = $obj[42] = $obj["lol"] = $obj["arbidol"] = to_mixed(new K(42)); + + $keys = $obj->keys(); + foreach ($keys as $key) { + $val = $obj[$key]; // smart casts work only for local variables + if ($val instanceof K) { + $val->$x += 1; + var_dump($val->$x); + } else { + die_if_failure(false, "Incorrect cast"); + } + } + +} + +test_common(); +test_set_obj_as_mixed(); + +// TODO +// test_common_obj_as_mixed(); diff --git a/tests/phpt/array_access/Classes/LikeArray.php b/tests/phpt/array_access/Classes/LikeArray.php index 6e22d33f21..60295a284f 100644 --- a/tests/phpt/array_access/Classes/LikeArray.php +++ b/tests/phpt/array_access/Classes/LikeArray.php @@ -6,6 +6,11 @@ class LikeArray implements \ArrayAccess { /** @var mixed[] */ protected $data = []; + /** @param $data_ mixed[] */ + public function __construct($data_=[]) { + $this->data = $data_; + } + /** * @param $offset mixed * @param $value mixed diff --git a/tests/phpt/array_access/Classes/LoggingLikeArray.php b/tests/phpt/array_access/Classes/LoggingLikeArray.php index 0fb46e3ff6..720aea91f9 100644 --- a/tests/phpt/array_access/Classes/LoggingLikeArray.php +++ b/tests/phpt/array_access/Classes/LoggingLikeArray.php @@ -6,6 +6,11 @@ class LoggingLikeArray implements \ArrayAccess { /** @var mixed[] */ protected $data = []; + /** @param $data_ mixed[] */ + public function __construct($data_=[]) { + $this->data = $data_; + } + /** * @param $offset mixed * @param $value mixed From ff6efc4c51d3183ab648a24e063058e2fd07344e Mon Sep 17 00:00:00 2001 From: Mikhail Kornaukhov Date: Wed, 30 Oct 2024 12:28:45 +0300 Subject: [PATCH 24/44] fixup test --- .../array_access/007_assignment_chain.php | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/tests/phpt/array_access/007_assignment_chain.php b/tests/phpt/array_access/007_assignment_chain.php index 78018969ca..3fc22a35eb 100644 --- a/tests/phpt/array_access/007_assignment_chain.php +++ b/tests/phpt/array_access/007_assignment_chain.php @@ -22,12 +22,6 @@ function test_common() { foreach ($keys as $key) { var_dump($obj[$key]); } - - // $obj[3] = $obj[42] = $arr[4] = $arr["kek"] = $obj["lol"] = $obj["arbidol"] = to_mixed(new K()); - - // if ($obj[3] instanceof K) { - // $obj[3] - // } } function test_common_obj_as_mixed() { @@ -47,15 +41,26 @@ function test_common_obj_as_mixed() { } else { die_if_failure(false, "Incorrect cast"); } - +} +function test_common_as_aa() { + $arr = [1, 2, "asd"]; + /** @var \ArrayAccess */ + $obj = new Classes\LoggingLikeArray([1, 2, 3, "kek" => "lol"]); - // $obj[3] = $obj[42] = $arr[4] = $arr["kek"] = $obj["lol"] = $obj["arbidol"] = to_mixed(new K()); + $obj[0] = $obj[1] = "str1"; + $obj[4] = $obj[42] = $obj[111] = ""; + $obj[3] = $obj[42] = $arr[4] = $arr["kek"] = $obj["lol"] = $obj["arbidol"] = ["", 0.1, 1, null]; - // if ($obj[3] instanceof K) { - // $obj[3] - // } + if ($obj instanceof Classes\LoggingLikeArray) { + $keys = $obj->keys(); + foreach ($keys as $key) { + var_dump($obj[$key]); + } + } else { + die_if_failure(false, "Incorrect cast"); + } } function test_set_obj_as_mixed() { @@ -76,6 +81,7 @@ function test_set_obj_as_mixed() { } test_common(); +test_common_as_aa(); test_set_obj_as_mixed(); // TODO From 01ace1438e2f9f83995ba3d1ac4089fa4c0936f4 Mon Sep 17 00:00:00 2001 From: Mikhail Kornaukhov Date: Wed, 30 Oct 2024 12:58:04 +0300 Subject: [PATCH 25/44] fixup for append case --- compiler/pipes/optimization.cpp | 29 ++++++++++--------- .../array_access/007_assignment_chain.php | 8 +++-- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/compiler/pipes/optimization.cpp b/compiler/pipes/optimization.cpp index ec6a5ed011..576ada54d6 100644 --- a/compiler/pipes/optimization.cpp +++ b/compiler/pipes/optimization.cpp @@ -126,26 +126,29 @@ VertexPtr OptimizationPass::optimize_set_push_back(VertexAdaptor set_op) auto klass = tinf::get_type(a)->class_type(); kphp_assert_msg(klass, "bad klass"); - // TODO doesn't it have the problem with that some parent classes are not linked in chain yet? const auto *method = klass->get_instance_method("offsetSet"); kphp_assert_msg(method, fmt::format("Class {} does not implement offsetSet", klass->name).c_str()); + if (set_op->rl_type == val_none) { + auto new_call = VertexAdaptor::create(a, VertexAdaptor::create(), c).set_location(set_op->get_location()); + + new_call->str_val = method->global_name(); + new_call->func_id = method->function; + new_call->extra_type = op_ex_func_call_arrow; // Is that right? + new_call->auto_inserted = true; + new_call->rl_type = set_op->rl_type; + + result = new_call; + } else { + auto z = VertexAdaptor::create(VertexAdaptor::create(), c, a); + z->set_method = method->function; + z.set_location(set_op); + result = z; + } - - // TODO assume here that key is present - auto new_call = VertexAdaptor::create(a, VertexAdaptor::create(), c).set_location(set_op->get_location()); - - new_call->str_val = method->global_name(); - new_call->func_id = method->function; - new_call->extra_type = op_ex_func_call_arrow; // Is that right? - new_call->auto_inserted = true; - new_call->rl_type = set_op->rl_type; - - result = new_call; return result; } - kphp_error (a_ptype == tp_array || a_ptype == tp_mixed, fmt_format("Can not use [] for {}", type_out(tinf::get_type(a)))); diff --git a/tests/phpt/array_access/007_assignment_chain.php b/tests/phpt/array_access/007_assignment_chain.php index 3fc22a35eb..57f33c6dfa 100644 --- a/tests/phpt/array_access/007_assignment_chain.php +++ b/tests/phpt/array_access/007_assignment_chain.php @@ -16,7 +16,7 @@ function test_common() { $obj[0] = $obj[1] = "str1"; $obj[4] = $obj[42] = $obj[111] = ""; - $obj[3] = $obj[42] = $arr[4] = $arr["kek"] = $obj["lol"] = $obj["arbidol"] = ["", 0.1, 1, null]; + $obj[3] = $obj[42] = $arr[4] = $arr["kek"] = $obj[] = $obj["arbidol"] = ["", 0.1, 1, null]; $keys = $obj->keys(); foreach ($keys as $key) { @@ -31,7 +31,7 @@ function test_common_obj_as_mixed() { $obj[0] = $obj[1] = "str1"; $obj[4] = $obj[42] = $obj[111] = ""; - $obj[3] = $obj[42] = $arr[4] = $arr["kek"] = $obj["lol"] = $obj["arbidol"] = ["", 0.1, 1, null]; + $obj[3] = $obj[42] = $arr[4] = $arr["kek"] = $obj[] = $obj["arbidol"] = ["", 0.1, 1, null]; if ($obj instanceof Classes\LoggingLikeArray) { $keys = $obj->keys(); @@ -51,7 +51,7 @@ function test_common_as_aa() { $obj[0] = $obj[1] = "str1"; $obj[4] = $obj[42] = $obj[111] = ""; - $obj[3] = $obj[42] = $arr[4] = $arr["kek"] = $obj["lol"] = $obj["arbidol"] = ["", 0.1, 1, null]; + $obj[3] = $obj[42] = $arr[4] = $arr["kek"] = $obj[] = $obj["arbidol"] = ["", 0.1, 1, null]; if ($obj instanceof Classes\LoggingLikeArray) { $keys = $obj->keys(); @@ -63,6 +63,8 @@ function test_common_as_aa() { } } +// TODO $x[.] = $y[.] = $x[] = $y[] = ... + function test_set_obj_as_mixed() { $obj = new Classes\LoggingLikeArray(); $obj[3] = $obj[42] = $obj["lol"] = $obj["arbidol"] = to_mixed(new K(42)); From 31df49af96160fe82d749a2bec7c68fe17b68f2f Mon Sep 17 00:00:00 2001 From: Mikhail Kornaukhov Date: Thu, 31 Oct 2024 11:58:35 +0300 Subject: [PATCH 26/44] support better chaining assignment --- compiler/code-gen/vertex-compiler.cpp | 39 ++++++++++++++- ...x-chaining-assignment-for-array-access.cpp | 22 ++++----- .../core/core-types/decl/mixed_decl.inl | 2 + .../core/core-types/definition/mixed.cpp | 27 +++++++++-- runtime-common/core/runtime-core.h | 6 +-- .../array_access/007_assignment_chain.php | 48 ++++++++++++++----- .../Classes/KeysableArrayAccess.php | 10 ++++ tests/phpt/array_access/Classes/LikeArray.php | 4 +- .../array_access/Classes/LoggingLikeArray.php | 4 +- 9 files changed, 129 insertions(+), 33 deletions(-) create mode 100644 tests/phpt/array_access/Classes/KeysableArrayAccess.php diff --git a/compiler/code-gen/vertex-compiler.cpp b/compiler/code-gen/vertex-compiler.cpp index f43ce2cb50..f46b59969c 100644 --- a/compiler/code-gen/vertex-compiler.cpp +++ b/compiler/code-gen/vertex-compiler.cpp @@ -9,6 +9,7 @@ #include "common/wrappers/field_getter.h" #include "common/wrappers/likely.h" +#include "compiler/code-gen/code-generator.h" #include "compiler/code-gen/common.h" #include "compiler/code-gen/const-globals-batched-mem.h" #include "compiler/code-gen/declarations.h" @@ -23,6 +24,7 @@ #include "compiler/data/function-data.h" #include "compiler/data/src-file.h" #include "compiler/data/var-data.h" +#include "compiler/inferring/primitive-type.h" #include "compiler/inferring/public.h" #include "compiler/name-gen.h" #include "compiler/type-hint.h" @@ -530,6 +532,7 @@ void compile_binary_func_op(VertexAdaptor root, CodeGenerator &W } bool try_compile_append_inplace(VertexAdaptor root, CodeGenerator &W); +bool try_compile_set_by_index_of_mixed(VertexPtr root, CodeGenerator &W); void compile_binary_op(VertexAdaptor root, CodeGenerator &W) { const auto &root_type_str = OpInfo::str(root->type()); @@ -594,6 +597,10 @@ void compile_binary_op(VertexAdaptor root, CodeGenerator &W) { } } + if (try_compile_set_by_index_of_mixed(root, W)) { + return; + } + W << Operand{lhs, root->type(), true} << " " << root_type_str << " " << Operand{rhs, root->type(), false}; @@ -2062,6 +2069,33 @@ void compile_defined(VertexPtr root __attribute__((unused)), CodeGenerator &W __ //TODO: it is not CodeGen part } +bool try_compile_set_by_index_of_mixed(VertexPtr root, CodeGenerator &W) { + if (auto set = root.try_as()) { + auto lhs = set->lhs(); + auto rhs = set->rhs(); + if (auto index = lhs.try_as()) { + if (tinf::get_type(index->array())->get_real_ptype() == tp_mixed) { + if (set->extra_type == op_ex_safe_version) { + W << "SAFE_SET_MIXED_BY_INDEX("; + W << index->array() << ", "; + W << index->key() << ", "; + + TmpExpr tmp_rhs(rhs); + W << tmp_rhs << ", "; + W << TypeName(tmp_rhs.get_type()) << ")"; + } else { + W << "SET_MIXED_BY_INDEX("; + W << index->array() << ", "; + W << index->key() << ", "; + W << rhs << ")"; + } + return true; + } + } + } + return false; +} + void compile_safe_version(VertexPtr root, CodeGenerator &W) { if (auto set_value = root.try_as()) { TmpExpr key{set_value->key()}; @@ -2074,6 +2108,9 @@ void compile_safe_version(VertexPtr root, CodeGenerator &W) { TypeName(value.get_type()) << MacroEnd{}; } else if (OpInfo::rl(root->type()) == rl_set) { + if (try_compile_set_by_index_of_mixed(root, W)) { + return; + } auto op = root.as(); if (OpInfo::type(root->type()) == binary_func_op) { W << "SAFE_SET_FUNC_OP " << MacroBegin{}; @@ -2395,7 +2432,7 @@ void compile_common_op(VertexPtr root, CodeGenerator &W) { val; } */ - W << "SAFE_SET_OP_ARR_ACC("; + W << "SET_ARR_ACC_BY_INDEX("; W << xxx->obj() << ", "; W << xxx->offset() << ", "; W << xxx->value() << ", "; diff --git a/compiler/pipes/fix-chaining-assignment-for-array-access.cpp b/compiler/pipes/fix-chaining-assignment-for-array-access.cpp index 36a2dc0cad..cdf4701889 100644 --- a/compiler/pipes/fix-chaining-assignment-for-array-access.cpp +++ b/compiler/pipes/fix-chaining-assignment-for-array-access.cpp @@ -11,16 +11,9 @@ #include "compiler/inferring/public.h" #include - -// TODO maybe on_enter? -VertexPtr FixChainingAssignmentForArrayAccessPass::on_exit_vertex(VertexPtr root) { - if (root->type() != op_set) { - return root; - } - - auto set = root.as(); +static VertexPtr on_set(VertexAdaptor set) { if (set->lhs()->type() != op_func_call) { - return root; + return set; } auto func_call = set->lhs().try_as(); @@ -38,12 +31,11 @@ VertexPtr FixChainingAssignmentForArrayAccessPass::on_exit_vertex(VertexPtr root if (!method) { kphp_error(method, fmt_format("Class {} does not implement offsetSet", klass->name).c_str()); - return root; + return set; } auto sub_val = set->rhs(); - auto key = func_call->args()[1]; auto zzz = VertexAdaptor::create(key, sub_val, obj_arg); @@ -52,7 +44,15 @@ VertexPtr FixChainingAssignmentForArrayAccessPass::on_exit_vertex(VertexPtr root return zzz; } } + return set; +} +// TODO maybe on_enter? Think about it +// on_exit now generates x2 warnings, but may be the only correct way (may be not xD) +VertexPtr FixChainingAssignmentForArrayAccessPass::on_exit_vertex(VertexPtr root) { + if (auto set = root.try_as()) { + return on_set(set); + } return root; } diff --git a/runtime-common/core/core-types/decl/mixed_decl.inl b/runtime-common/core/core-types/decl/mixed_decl.inl index 89d34a9d13..1531dea32d 100644 --- a/runtime-common/core/core-types/decl/mixed_decl.inl +++ b/runtime-common/core/core-types/decl/mixed_decl.inl @@ -112,6 +112,8 @@ public: void set_value(const array::const_iterator &it); void set_value(const array::iterator &it); + mixed set_by_index_return(const mixed &key, const mixed &val); + const mixed get_value(int64_t int_key) const; const mixed get_value(int32_t key) const { return get_value(int64_t{key}); } const mixed get_value(const string &string_key) const; diff --git a/runtime-common/core/core-types/definition/mixed.cpp b/runtime-common/core/core-types/definition/mixed.cpp index cabcb31aff..47d17e5256 100644 --- a/runtime-common/core/core-types/definition/mixed.cpp +++ b/runtime-common/core/core-types/definition/mixed.cpp @@ -1167,6 +1167,17 @@ mixed &mixed::operator[](const array::iterator &it) { } +mixed mixed::set_by_index_return(const mixed &key, const mixed &val) { + if (get_type() == type::OBJECT) { + // TODO check with f$is_a + // TODO may be more efficient way? + set_value(key, val); + return val; + } + + return (*this)[key] = val; +} + void mixed::set_value(int64_t int_key, const mixed &v) { if (unlikely (get_type() != type::ARRAY)) { if (get_type() == type::STRING) { @@ -1403,10 +1414,15 @@ const mixed mixed::get_value(const array::iterator &it) const { return as_array().get_value(it); } - +// TODO USE f$is_a before every `from_mixed()` !!! void mixed::push_back(const mixed &v) { if (unlikely (get_type() != type::ARRAY)) { - if (get_type() == type::NUL || (get_type() == type::BOOLEAN && !as_bool())) { + if (get_type() == type::OBJECT) { + auto xxx = from_mixed>(*this, string()); + f$ArrayAccess$$offsetSet(xxx, Optional{}, v); + return; + } + else if (get_type() == type::NUL || (get_type() == type::BOOLEAN && !as_bool())) { type_ = type::ARRAY; new(&as_array()) array(); } else { @@ -1420,7 +1436,12 @@ void mixed::push_back(const mixed &v) { const mixed mixed::push_back_return(const mixed &v) { if (unlikely (get_type() != type::ARRAY)) { - if (get_type() == type::NUL || (get_type() == type::BOOLEAN && !as_bool())) { + if (get_type() == type::OBJECT) { + auto xxx = from_mixed>(*this, string()); + f$ArrayAccess$$offsetSet(xxx, Optional{}, v); + return v; + } + else if (get_type() == type::NUL || (get_type() == type::BOOLEAN && !as_bool())) { type_ = type::ARRAY; new(&as_array()) array(); } else { diff --git a/runtime-common/core/runtime-core.h b/runtime-common/core/runtime-core.h index f15c774341..92110635eb 100644 --- a/runtime-common/core/runtime-core.h +++ b/runtime-common/core/runtime-core.h @@ -48,9 +48,9 @@ #define FFI_CALL(call) ({ dl::CriticalSectionGuard critical_section___; (call); }) #define FFI_INVOKE_CALLBACK(call) ({ dl::NonCriticalSectionGuard non_critical_section___; (call); }) -// In case of object, it would be `{offsetSet(ptr, key, val); val;}` -#define SAFE_SET_OP_MIXED(m, idx, idx_type, val, val_type) ({idx_type idx_tmp___ = idx; val_type val_tmp___ = val; m.set_on_and_get(idx_tmp__, val_tmp___);}) -#define SAFE_SET_OP_ARR_ACC(obj, idx, val, method) ({mixed idx_tmp___ = idx; mixed val_tmp___ = val; method(obj, idx_tmp___, val_tmp___); val_tmp___;}) +#define SET_MIXED_BY_INDEX(mix, idx, val) mix.set_by_index_return(idx, val) +#define SAFE_SET_MIXED_BY_INDEX(mix, idx, val, val_type) ({ val_type val_tmp___ = val; mix.set_by_index_return(idx, std::move(val_tmp___)); }) +#define SET_ARR_ACC_BY_INDEX(obj, idx, val, method) ({mixed val_tmp___ = val; method(obj, idx, val_tmp___); val_tmp___;}) // it's always safe #define SAFE_SET_OP(a, op, b, b_type) ({b_type b_tmp___ = b; a op std::move(b_tmp___);}) #define SAFE_SET_FUNC_OP(a, func, b, b_type) ({b_type b_tmp___ = b; func (a, b_tmp___);}) diff --git a/tests/phpt/array_access/007_assignment_chain.php b/tests/phpt/array_access/007_assignment_chain.php index 57f33c6dfa..44f98109df 100644 --- a/tests/phpt/array_access/007_assignment_chain.php +++ b/tests/phpt/array_access/007_assignment_chain.php @@ -24,10 +24,11 @@ function test_common() { } } -function test_common_obj_as_mixed() { +function test_common_as_aa() { $arr = [1, 2, "asd"]; - $obj = to_mixed(new Classes\LoggingLikeArray([1, 2, 3, "kek" => "lol"])); + /** @var \ArrayAccess */ + $obj = new Classes\LoggingLikeArray([1, 2, 3, "kek" => "lol"]); $obj[0] = $obj[1] = "str1"; $obj[4] = $obj[42] = $obj[111] = ""; @@ -43,15 +44,15 @@ function test_common_obj_as_mixed() { } } -function test_common_as_aa() { +function test_common_obj_as_mixed() { $arr = [1, 2, "asd"]; - /** @var \ArrayAccess */ - $obj = new Classes\LoggingLikeArray([1, 2, 3, "kek" => "lol"]); + $obj = to_mixed(new Classes\LoggingLikeArray([1, 2, 3, "kek" => "lol"])); $obj[0] = $obj[1] = "str1"; $obj[4] = $obj[42] = $obj[111] = ""; - $obj[3] = $obj[42] = $arr[4] = $arr["kek"] = $obj[] = $obj["arbidol"] = ["", 0.1, 1, null]; + $obj[3] = $obj[42] = $arr[4] = $arr["kek"] = $obj[123456] = $obj["arbidol"] = ["", 0.1, 1, null]; + $obj[3] = $obj[42] = $arr[] = $arr["kek"] = $obj[] = $obj["arbidol"] = ["", 0.1, 1, null]; if ($obj instanceof Classes\LoggingLikeArray) { $keys = $obj->keys(); @@ -63,8 +64,6 @@ function test_common_as_aa() { } } -// TODO $x[.] = $y[.] = $x[] = $y[] = ... - function test_set_obj_as_mixed() { $obj = new Classes\LoggingLikeArray(); $obj[3] = $obj[42] = $obj["lol"] = $obj["arbidol"] = to_mixed(new K(42)); @@ -79,12 +78,39 @@ function test_set_obj_as_mixed() { die_if_failure(false, "Incorrect cast"); } } +} + +/** @param $a mixed */ +function dump($a) { + if ($a instanceof \Classes\KeysableArrayAccess) { + $keys = $a->keys(); + foreach ($keys as $key) { + var_dump($a[$key]); + } + } else { + die_if_failure(false, "Incorrect smart cast"); + } +} +function test_multiple_objects() { + $obj1 = to_mixed(new Classes\LoggingLikeArray([1, 2, 3, "kek" => "lol"])); + $obj2 = to_mixed(new Classes\LoggingLikeArray([])); + $obj3 = to_mixed(new Classes\LikeArray([500, 501, 502, 503])); + $obj4 = new Classes\LoggingLikeArray(["str", 0.541, "key" => "val"]); + + $obj1[] = $obj2[] = $obj1[] = $obj3[] = $obj4[] = "first"; + $obj1[1] = $obj2[] = $obj1[1] = $obj3[23] = $obj4["str_key"] = "second"; + $obj3[100] = $obj2[100] = $obj3[] = $obj2[] = $obj4[0]; + $obj4[] = $obj4[] = $obj4[1] = $obj1[] = $obj1[] = $obj1[] = $obj4["key"]; + + dump($obj1); + dump($obj2); + dump($obj3); + dump(to_mixed($obj4)); } test_common(); test_common_as_aa(); +test_common_obj_as_mixed(); test_set_obj_as_mixed(); - -// TODO -// test_common_obj_as_mixed(); +test_multiple_objects(); diff --git a/tests/phpt/array_access/Classes/KeysableArrayAccess.php b/tests/phpt/array_access/Classes/KeysableArrayAccess.php new file mode 100644 index 0000000000..963868fe07 --- /dev/null +++ b/tests/phpt/array_access/Classes/KeysableArrayAccess.php @@ -0,0 +1,10 @@ +data = $data_; } diff --git a/tests/phpt/array_access/Classes/LoggingLikeArray.php b/tests/phpt/array_access/Classes/LoggingLikeArray.php index 720aea91f9..1e5532faba 100644 --- a/tests/phpt/array_access/Classes/LoggingLikeArray.php +++ b/tests/phpt/array_access/Classes/LoggingLikeArray.php @@ -2,12 +2,12 @@ namespace Classes; -class LoggingLikeArray implements \ArrayAccess { +class LoggingLikeArray implements KeysableArrayAccess { /** @var mixed[] */ protected $data = []; /** @param $data_ mixed[] */ - public function __construct($data_=[]) { + public function __construct($data_ = []) { $this->data = $data_; } From 6a587493e7b22c63fa572207bac3cf21794b42dd Mon Sep 17 00:00:00 2001 From: Mikhail Kornaukhov Date: Thu, 31 Oct 2024 15:24:09 +0300 Subject: [PATCH 27/44] get rid of shitty code in _functions.txt --- builtin-functions/kphp-full/_functions.txt | 11 ----------- compiler/gentree.cpp | 4 ++++ compiler/pipes/filter-only-actually-used.cpp | 8 +++++--- runtime-common/core/core-types/definition/mixed.cpp | 2 +- 4 files changed, 10 insertions(+), 15 deletions(-) diff --git a/builtin-functions/kphp-full/_functions.txt b/builtin-functions/kphp-full/_functions.txt index aec3b81894..0a0cfc81e7 100644 --- a/builtin-functions/kphp-full/_functions.txt +++ b/builtin-functions/kphp-full/_functions.txt @@ -1692,14 +1692,3 @@ interface ArrayAccess { public function offsetSet(mixed $offset, mixed $value); public function offsetUnset(mixed $offset); } - -// TODO get rid of it -if (0) { - /** @var ArrayAccess */ - $_should_be_unique_var_name_123 = null; - $_should_be_unique_var_name_456 = to_mixed($_should_be_unique_var_name_123); - $_should_be_unique_var_name_123->offsetExists(0); - $_should_be_unique_var_name_123->offsetGet(0); - $_should_be_unique_var_name_123->offsetSet(0, 0); - $_should_be_unique_var_name_123->offsetUnset(0); -} \ No newline at end of file diff --git a/compiler/gentree.cpp b/compiler/gentree.cpp index d5f57e6cf5..6760c6e989 100644 --- a/compiler/gentree.cpp +++ b/compiler/gentree.cpp @@ -1723,6 +1723,10 @@ VertexPtr GenTree::get_class(const PhpDocComment *phpdoc, ClassType class_type) cur_class->is_immutable = phpdoc && phpdoc->has_tag(PhpDocType::kphp_immutable_class); cur_class->need_generated_stub = phpdoc && phpdoc->has_tag(PhpDocType::kphp_generated_stub_class); cur_class->internal_interface = phpdoc && phpdoc->has_tag(PhpDocType::kphp_internal_interface); + if (cur_class->internal_interface && full_class_name == "ArrayAccess") { + // TODO get rid of it later + cur_class->may_be_mixed = true; + } cur_class->location_line_num = line_num; bool registered = G->register_class(cur_class); diff --git a/compiler/pipes/filter-only-actually-used.cpp b/compiler/pipes/filter-only-actually-used.cpp index 51b784f01d..e83224b59d 100644 --- a/compiler/pipes/filter-only-actually-used.cpp +++ b/compiler/pipes/filter-only-actually-used.cpp @@ -245,10 +245,12 @@ IdMap calc_actually_used_having_call_edges(std::vectormodifiers.is_instance()) { return false; } - ClassPtr klass = fun->class_id; - const bool impl_aa = - std::find_if(klass->implements.begin(), klass->implements.end(), [](ClassPtr x) { return x->name == "ArrayAccess"; }) != klass->implements.end(); + ClassPtr aa = G->get_class("ArrayAccess"); + assert(aa && "Cannot find ArrayAccess"); + + ClassPtr klass = fun->class_id; + bool impl_aa = aa->is_parent_of(klass); return impl_aa && vk::any_of_equal(fun->local_name(), "offsetGet", "offsetSet", "offsetExists", "offsetUnset"); }(); diff --git a/runtime-common/core/core-types/definition/mixed.cpp b/runtime-common/core/core-types/definition/mixed.cpp index 47d17e5256..771c8311c7 100644 --- a/runtime-common/core/core-types/definition/mixed.cpp +++ b/runtime-common/core/core-types/definition/mixed.cpp @@ -6,7 +6,7 @@ #include "runtime-common/core/runtime-core.h" #include -// TODO must check that in runtime nothing will get wrong +// TODO must check that in runtime nothing will get wrong or store it some runtime header file struct C$ArrayAccess : public may_be_mixed_base { virtual int get_hash() const noexcept = 0; From 7521e4a0fbc977822c18126ca4eca73fbbe83466 Mon Sep 17 00:00:00 2001 From: Mikhail Kornaukhov Date: Thu, 31 Oct 2024 17:01:10 +0300 Subject: [PATCH 28/44] get rid of weak symbols and hack with dynamic linkage --- compiler/data/class-data.cpp | 2 +- compiler/gentree.cpp | 4 --- compiler/make/make.cpp | 10 +++---- compiler/make/objs-to-obj-target.h | 8 +----- compiler/pipes/code-gen.cpp | 4 --- runtime-common/core/array_access.h | 21 +++++++++++++++ .../core/core-types/definition/mixed.cpp | 27 ------------------- runtime-common/core/runtime-core.h | 1 + tests/cpp/runtime/_runtime-tests-env.cpp | 20 ++++++++++++++ 9 files changed, 48 insertions(+), 49 deletions(-) create mode 100644 runtime-common/core/array_access.h diff --git a/compiler/data/class-data.cpp b/compiler/data/class-data.cpp index c40240184d..08866980d3 100644 --- a/compiler/data/class-data.cpp +++ b/compiler/data/class-data.cpp @@ -326,7 +326,7 @@ std::vector ClassData::get_all_ancestors() const { } bool ClassData::is_builtin() const { - return is_ffi_cdata() || (file_id && file_id->is_builtin() && !internal_interface); + return is_ffi_cdata() || (file_id && file_id->is_builtin()); } bool ClassData::is_polymorphic_or_has_polymorphic_member() const { diff --git a/compiler/gentree.cpp b/compiler/gentree.cpp index 6760c6e989..d5f57e6cf5 100644 --- a/compiler/gentree.cpp +++ b/compiler/gentree.cpp @@ -1723,10 +1723,6 @@ VertexPtr GenTree::get_class(const PhpDocComment *phpdoc, ClassType class_type) cur_class->is_immutable = phpdoc && phpdoc->has_tag(PhpDocType::kphp_immutable_class); cur_class->need_generated_stub = phpdoc && phpdoc->has_tag(PhpDocType::kphp_generated_stub_class); cur_class->internal_interface = phpdoc && phpdoc->has_tag(PhpDocType::kphp_internal_interface); - if (cur_class->internal_interface && full_class_name == "ArrayAccess") { - // TODO get rid of it later - cur_class->may_be_mixed = true; - } cur_class->location_line_num = line_num; bool registered = G->register_class(cur_class); diff --git a/compiler/make/make.cpp b/compiler/make/make.cpp index 27caf3b241..a5d092d3c3 100644 --- a/compiler/make/make.cpp +++ b/compiler/make/make.cpp @@ -96,8 +96,8 @@ class MakeSetup { return create_target(new H2PchTarget(), to_targets(header_h), pch); } - Target *create_objs2obj_target(std::vector objs, File *obj, bool force_obj = false) { - return create_target(new Objs2ObjTarget(force_obj), to_targets(std::move(objs)), obj); + Target *create_objs2obj_target(std::vector objs, File *obj) { + return create_target(new Objs2ObjTarget(), to_targets(std::move(objs)), obj); } Target *create_objs2bin_target(std::vector objs, File *bin) { @@ -396,15 +396,13 @@ static std::vector create_obj_files(MakeSetup *make, Index &obj_dir, con vk::hash_combine(hash, vk::std_hash(f->name)); } - bool force_obj = name_and_files.first == "internal_interfaces"; auto intermediate_file_name = fmt_format("{}_{:x}.{}", name_and_files.first, hash, - G->settings().dynamic_incremental_linkage.get() && (!force_obj) ? "so" : "o"); + G->settings().dynamic_incremental_linkage.get() ? "so" : "o"); File *obj_file = obj_dir.insert_file(std::move(intermediate_file_name)); - - make->create_objs2obj_target(std::move(deps), obj_file, force_obj); + make->create_objs2obj_target(std::move(deps), obj_file); objs.push_back(obj_file); } fmt_fprintf(stderr, "objs cnt = {}\n", objs.size()); diff --git a/compiler/make/objs-to-obj-target.h b/compiler/make/objs-to-obj-target.h index 8940a20782..66b1c29a3f 100644 --- a/compiler/make/objs-to-obj-target.h +++ b/compiler/make/objs-to-obj-target.h @@ -10,19 +10,13 @@ #include "compiler/make/target.h" class Objs2ObjTarget : public Target { -bool force_obj; - public: - explicit Objs2ObjTarget(bool force_obj_) : force_obj(force_obj_) {} - std::string get_cmd() final { std::stringstream ss; ss << settings->cxx.get() << " " << settings->cxx_toolchain_option.get() << " " << - // settings->incremental_linker_flags.get() << - (force_obj ? "-r -nostdlib" : settings->incremental_linker_flags.get()) - << + settings->incremental_linker_flags.get() << " -o " << target() << " " << dep_list(); return ss.str(); diff --git a/compiler/pipes/code-gen.cpp b/compiler/pipes/code-gen.cpp index 013563c18d..32674d4462 100644 --- a/compiler/pipes/code-gen.cpp +++ b/compiler/pipes/code-gen.cpp @@ -214,10 +214,6 @@ std::string CodeGenF::calc_subdir_for_function(FunctionPtr func) { return "o_l"; } - if (func->name.find("ArrayAccess") != std::string::npos) { - return "internal_interfaces"; - } - int bucket = vk::std_hash(func->file_id->short_file_name) % 100; return "o_" + std::to_string(bucket); } diff --git a/runtime-common/core/array_access.h b/runtime-common/core/array_access.h new file mode 100644 index 0000000000..4f5cca5842 --- /dev/null +++ b/runtime-common/core/array_access.h @@ -0,0 +1,21 @@ +// 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-common/core/class-instance/refcountable-php-classes.h" + +// TODO may be add guard to force include only from runtime-core.h? + +struct C$ArrayAccess : public may_be_mixed_base { + virtual int get_hash() const noexcept = 0; + + C$ArrayAccess() __attribute__((always_inline)) = default; + ~C$ArrayAccess() override __attribute__((always_inline)) = default; +}; + + +bool f$ArrayAccess$$offsetExists(class_instance const & /*v$this*/, mixed const & /*v$offset*/) noexcept; +mixed f$ArrayAccess$$offsetGet(class_instance const & /*v$this*/, mixed const & /*v$offset*/) noexcept; +void f$ArrayAccess$$offsetSet(class_instance const & /*v$this*/, mixed const & /*v$offset*/, mixed const & /*v$value*/) noexcept; +void f$ArrayAccess$$offsetUnset(class_instance const & /*v$this*/, mixed const & /*v$offset*/) noexcept; \ No newline at end of file diff --git a/runtime-common/core/core-types/definition/mixed.cpp b/runtime-common/core/core-types/definition/mixed.cpp index 771c8311c7..52c1a75792 100644 --- a/runtime-common/core/core-types/definition/mixed.cpp +++ b/runtime-common/core/core-types/definition/mixed.cpp @@ -6,33 +6,6 @@ #include "runtime-common/core/runtime-core.h" #include -// TODO must check that in runtime nothing will get wrong or store it some runtime header file -struct C$ArrayAccess : public may_be_mixed_base { - virtual int get_hash() const noexcept = 0; - - C$ArrayAccess() __attribute__((always_inline)) = default; - ~C$ArrayAccess() __attribute__((always_inline)) = default; -}; - -__attribute__((weak)) bool f$ArrayAccess$$offsetExists(class_instance const & /*v$this*/, mixed const & /*v$offset*/) noexcept { - assert(0 && "using stub of offsetExists"); - return {}; -} - -__attribute__((weak)) mixed f$ArrayAccess$$offsetGet(class_instance const & /*v$this*/, mixed const & /*v$offset*/) noexcept { - assert(0 && "using stub of offsetGet"); - return {}; -} - -__attribute__((weak)) void f$ArrayAccess$$offsetSet(class_instance const & /*v$this*/, mixed const & /*v$offset*/, - mixed const & /*v$value*/) noexcept { - assert(0 && "using stub of offsetSet"); -} - -__attribute__((weak)) void f$ArrayAccess$$offsetUnset(class_instance const & /*v$this*/, mixed const & /*v$offset*/) noexcept { - assert(0 && "using stub of offsetUnset"); -} - void mixed::copy_from(const mixed &other) { switch (other.get_type()) { case type::STRING: diff --git a/runtime-common/core/runtime-core.h b/runtime-common/core/runtime-core.h index 92110635eb..d390d89a4c 100644 --- a/runtime-common/core/runtime-core.h +++ b/runtime-common/core/runtime-core.h @@ -26,6 +26,7 @@ #define INCLUDED_FROM_KPHP_CORE +#include "runtime-common/core/array_access.h" #include "runtime-common/core/core-types/decl/string_decl.inl" #include "runtime-common/core/core-types/decl/array_decl.inl" #include "runtime-common/core/class-instance/class-instance-decl.inl" diff --git a/tests/cpp/runtime/_runtime-tests-env.cpp b/tests/cpp/runtime/_runtime-tests-env.cpp index 5ad513e9b9..e3b96ad89e 100644 --- a/tests/cpp/runtime/_runtime-tests-env.cpp +++ b/tests/cpp/runtime/_runtime-tests-env.cpp @@ -9,6 +9,7 @@ #include "runtime/pdo/pdo_statement.h" #include "runtime/php_assert.h" #include "runtime/tl/rpc_response.h" +#include "runtime-common/core/array_access.h" #include "server/php-engine-vars.h" #include "server/workers-control.h" @@ -82,3 +83,22 @@ char **get_runtime_options(int *) noexcept { assert(0 && "this code shouldn't be executed and only for linkage test"); return nullptr; } + + +bool f$ArrayAccess$$offsetExists(class_instance const & /*v$this*/, mixed const & /*v$offset*/) noexcept { + assert(0 && "this code shouldn't be executed and only for linkage test"); + return {}; +} + +mixed f$ArrayAccess$$offsetGet(class_instance const & /*v$this*/, mixed const & /*v$offset*/) noexcept { + assert(0 && "this code shouldn't be executed and only for linkage test"); + return {}; +} + +void f$ArrayAccess$$offsetSet(class_instance const & /*v$this*/, mixed const & /*v$offset*/, mixed const & /*v$value*/) noexcept { + assert(0 && "this code shouldn't be executed and only for linkage test"); +} + +void f$ArrayAccess$$offsetUnset(class_instance const & /*v$this*/, mixed const & /*v$offset*/) noexcept { + assert(0 && "this code shouldn't be executed and only for linkage test"); +} \ No newline at end of file From a741d83120778a68477b1e79db86c5c847aacc30 Mon Sep 17 00:00:00 2001 From: Mikhail Kornaukhov Date: Thu, 31 Oct 2024 18:10:52 +0300 Subject: [PATCH 29/44] internal interfaces methods are not inlined --- compiler/pipes/inline-simple-functions.cpp | 12 ++++++++++++ compiler/pipes/inline-simple-functions.h | 1 + 2 files changed, 13 insertions(+) diff --git a/compiler/pipes/inline-simple-functions.cpp b/compiler/pipes/inline-simple-functions.cpp index f6bbaefc8a..45fa97ca96 100644 --- a/compiler/pipes/inline-simple-functions.cpp +++ b/compiler/pipes/inline-simple-functions.cpp @@ -4,6 +4,7 @@ #include "compiler/pipes/inline-simple-functions.h" +#include "compiler/data/class-data.h" #include "compiler/data/src-file.h" #include "compiler/data/var-data.h" #include "compiler/inferring/public.h" @@ -107,6 +108,17 @@ bool InlineSimpleFunctions::check_function(FunctionPtr function) const { !function->kphp_lib_export; } + +void InlineSimpleFunctions::on_start() { + if (auto klass = current_function->class_id) { + if (klass->internal_interface) { + inline_is_possible_ = false; + } + } + return FunctionPassBase::on_start(); +} + + void InlineSimpleFunctions::on_finish() { if (inline_is_possible_) { current_function->is_inline = true; diff --git a/compiler/pipes/inline-simple-functions.h b/compiler/pipes/inline-simple-functions.h index da5aedc448..c5f88a9f55 100644 --- a/compiler/pipes/inline-simple-functions.h +++ b/compiler/pipes/inline-simple-functions.h @@ -21,5 +21,6 @@ class InlineSimpleFunctions final : public FunctionPassBase { VertexPtr on_exit_vertex(VertexPtr root) final; bool user_recursion(VertexPtr) final; bool check_function(FunctionPtr function) const final; + void on_start() final; void on_finish() final; }; From 7cfbf8641ab95010846e7ed3bd8f959fdde1faa9 Mon Sep 17 00:00:00 2001 From: Mikhail Kornaukhov Date: Fri, 1 Nov 2024 14:32:44 +0300 Subject: [PATCH 30/44] implementation of isset(), unset(), empty() for ArrayAccess objects only --- compiler/compiler.cmake | 2 +- compiler/compiler.cpp | 4 +- compiler/pipes/fix-array-access.cpp | 143 ++++++++++++++++++ ...-for-array-access.h => fix-array-access.h} | 4 +- ...x-chaining-assignment-for-array-access.cpp | 58 ------- 5 files changed, 148 insertions(+), 63 deletions(-) create mode 100644 compiler/pipes/fix-array-access.cpp rename compiler/pipes/{fix-chaining-assignment-for-array-access.h => fix-array-access.h} (68%) delete mode 100644 compiler/pipes/fix-chaining-assignment-for-array-access.cpp diff --git a/compiler/compiler.cmake b/compiler/compiler.cmake index bcfa0a959e..a670d13ab0 100644 --- a/compiler/compiler.cmake +++ b/compiler/compiler.cmake @@ -151,7 +151,7 @@ prepend(KPHP_COMPILER_PIPES_SOURCES pipes/ file-to-tokens.cpp filter-only-actually-used.cpp final-check.cpp - fix-chaining-assignment-for-array-access.cpp + fix-array-access.cpp fix-returns.cpp gen-tree-postprocess.cpp generate-virtual-methods.cpp diff --git a/compiler/compiler.cpp b/compiler/compiler.cpp index d1a54576fa..61b19dafd5 100644 --- a/compiler/compiler.cpp +++ b/compiler/compiler.cpp @@ -64,7 +64,7 @@ #include "compiler/pipes/file-to-tokens.h" #include "compiler/pipes/filter-only-actually-used.h" #include "compiler/pipes/final-check.h" -#include "compiler/pipes/fix-chaining-assignment-for-array-access.h" +#include "compiler/pipes/fix-array-access.h" #include "compiler/pipes/fix-returns.h" #include "compiler/pipes/gen-tree-postprocess.h" #include "compiler/pipes/generate-virtual-methods.h" @@ -288,7 +288,7 @@ bool compiler_execute(CompilerSettings *settings) { >> PassC{} >> PassC{} >> PassC{} - >> PassC{} + >> PassC{} >> PassC{} >> PassC{} >> PassC{} diff --git a/compiler/pipes/fix-array-access.cpp b/compiler/pipes/fix-array-access.cpp new file mode 100644 index 0000000000..5bf45accc5 --- /dev/null +++ b/compiler/pipes/fix-array-access.cpp @@ -0,0 +1,143 @@ +// Compiler for PHP (aka KPHP) +// Copyright (c) 2024 LLC «V Kontakte» +// Distributed under the GPL v3 License, see LICENSE.notice.txt + +#include "compiler/pipes/fix-array-access.h" + +#include "auto/compiler/vertex/vertex-types.h" +#include "common/algorithms/contains.h" +#include "compiler/data/class-data.h" +#include "compiler/data/vertex-adaptor.h" +#include "compiler/inferring/primitive-type.h" +#include "compiler/inferring/public.h" +#include + +static VertexPtr on_set(VertexAdaptor set) { + if (set->lhs()->type() != op_func_call) { + return set; + } + + auto func_call = set->lhs().try_as(); + if (func_call->auto_inserted) { + if (func_call->func_id->local_name() == "offsetGet") { + auto obj_arg = func_call->args()[0]; + const auto *tpe = tinf::get_type(obj_arg); // funny + + assert(tpe); + assert(tpe->get_real_ptype() == tp_Class); + + auto klass = tpe->class_type(); + + const auto *method = klass->get_instance_method("offsetSet"); + + if (!method) { + kphp_error(method, fmt_format("Class {} does not implement offsetSet", klass->name).c_str()); + return set; + } + + auto sub_val = set->rhs(); + + auto key = func_call->args()[1]; + + auto zzz = VertexAdaptor::create(key, sub_val, obj_arg); + zzz->set_method = method->function; + + return zzz; + } + } + return set; +} + +static VertexPtr on_unset(VertexAdaptor unset) { + if (auto func_call = unset->expr().try_as()) { + if (func_call->func_id->name.find("offsetGet") != std::string::npos) { + auto klass = func_call->func_id->class_id; + // TODO assume here that all is good + assert(klass && "bad klass for unset"); + + const auto *unset_method = klass->get_instance_method("offsetUnset"); + assert(unset_method && "bad method for unset"); + + func_call->str_val = unset_method->global_name(); + func_call->func_id = unset_method->function; + return func_call; + } + } + + return unset; +} + +static VertexPtr on_isset(VertexAdaptor isset) { + if (auto func_call = isset->expr().try_as()) { + if (func_call->func_id->name.find("offsetGet") != std::string::npos) { + auto klass = func_call->func_id->class_id; + // TODO assume here that all is good + assert(klass && "bad klass for isset"); + + const auto *isset_method = klass->get_instance_method("offsetExists"); + assert(isset_method && "bad method for isset"); + + func_call->str_val = isset_method->global_name(); + func_call->func_id = isset_method->function; + return func_call; + } + } + + return isset; +} + +static VertexPtr on_empty(VertexAdaptor empty_call) { + if (auto offset_get_call = empty_call->args()[0].try_as()) { + if (offset_get_call->func_id->name.find("offsetGet") != std::string::npos) { + auto klass = offset_get_call->func_id->class_id; + // TODO assume here that all is good + assert(klass && "bad klass for isset"); + + const auto *isset_method = klass->get_instance_method("offsetExists"); + assert(isset_method && "bad method for isset"); + + auto exists_call = offset_get_call.clone(); + + exists_call->str_val = isset_method->global_name(); + exists_call->func_id = isset_method->function; + /* + for empty smth like: + op_log_or + op_log_not + op_func_call f$ClassName$exists + ... + op_func_call empty + op_func_call f$ClassName$get + ... + */ + + // TODO fix locations + auto as_or = VertexAdaptor::create(VertexAdaptor::create(exists_call), empty_call); + + return as_or; + } + } + + return empty_call; +} + +// TODO maybe on_enter? Think about it +// on_exit now generates x2 warnings, but may be the only correct way (may be not xD) +VertexPtr FixArrayAccessPass::on_exit_vertex(VertexPtr root) { + if (auto set = root.try_as()) { + return on_set(set); + } + if (auto unset = root.try_as()) { + return on_unset(unset); + } + if (auto isset = root.try_as()) { + return on_isset(isset); + } + if (auto func_call = root.try_as()) { + if (func_call->func_id->is_extern() && func_call->func_id->name == "empty") { + return on_empty(func_call); + } + } + + return root; +} diff --git a/compiler/pipes/fix-chaining-assignment-for-array-access.h b/compiler/pipes/fix-array-access.h similarity index 68% rename from compiler/pipes/fix-chaining-assignment-for-array-access.h rename to compiler/pipes/fix-array-access.h index 6fd64b6774..f909d5adaa 100644 --- a/compiler/pipes/fix-chaining-assignment-for-array-access.h +++ b/compiler/pipes/fix-array-access.h @@ -6,10 +6,10 @@ #include "compiler/function-pass.h" -class FixChainingAssignmentForArrayAccessPass final : public FunctionPassBase { +class FixArrayAccessPass final : public FunctionPassBase { public: std::string get_description() override { - return "Fix chaining assignment for ArrayAccess"; + return "Fix functions related to ArrayAccess"; } VertexPtr on_exit_vertex(VertexPtr root) final; diff --git a/compiler/pipes/fix-chaining-assignment-for-array-access.cpp b/compiler/pipes/fix-chaining-assignment-for-array-access.cpp deleted file mode 100644 index cdf4701889..0000000000 --- a/compiler/pipes/fix-chaining-assignment-for-array-access.cpp +++ /dev/null @@ -1,58 +0,0 @@ -// Compiler for PHP (aka KPHP) -// Copyright (c) 2020 LLC «V Kontakte» -// Distributed under the GPL v3 License, see LICENSE.notice.txt - -#include "compiler/pipes/fix-chaining-assignment-for-array-access.h" - -#include "auto/compiler/vertex/vertex-types.h" -#include "compiler/data/class-data.h" -#include "compiler/data/vertex-adaptor.h" -#include "compiler/inferring/primitive-type.h" -#include "compiler/inferring/public.h" -#include - -static VertexPtr on_set(VertexAdaptor set) { - if (set->lhs()->type() != op_func_call) { - return set; - } - - auto func_call = set->lhs().try_as(); - if (func_call->auto_inserted) { - if (func_call->func_id->local_name() == "offsetGet") { - auto obj_arg = func_call->args()[0]; - const auto *tpe = tinf::get_type(obj_arg); // funny - - assert(tpe); - assert(tpe->get_real_ptype() == tp_Class); - - auto klass = tpe->class_type(); - - const auto *method = klass->get_instance_method("offsetSet"); - - if (!method) { - kphp_error(method, fmt_format("Class {} does not implement offsetSet", klass->name).c_str()); - return set; - } - - auto sub_val = set->rhs(); - - auto key = func_call->args()[1]; - - auto zzz = VertexAdaptor::create(key, sub_val, obj_arg); - zzz->set_method = method->function; - - return zzz; - } - } - return set; -} - -// TODO maybe on_enter? Think about it -// on_exit now generates x2 warnings, but may be the only correct way (may be not xD) -VertexPtr FixChainingAssignmentForArrayAccessPass::on_exit_vertex(VertexPtr root) { - if (auto set = root.try_as()) { - return on_set(set); - } - - return root; -} From e4cef17d2ef1da738db4327c40906d2c0724adf2 Mon Sep 17 00:00:00 2001 From: Mikhail Kornaukhov Date: Fri, 1 Nov 2024 16:54:21 +0300 Subject: [PATCH 31/44] implementation of isset(), unset() for objects inside mixed --- .../core/core-types/definition/mixed.cpp | 18 ++++++++++++++++++ .../core/core-types/definition/mixed.inl | 12 ++++++++++++ 2 files changed, 30 insertions(+) diff --git a/runtime-common/core/core-types/definition/mixed.cpp b/runtime-common/core/core-types/definition/mixed.cpp index 52c1a75792..e4d3af26b3 100644 --- a/runtime-common/core/core-types/definition/mixed.cpp +++ b/runtime-common/core/core-types/definition/mixed.cpp @@ -1428,6 +1428,11 @@ const mixed mixed::push_back_return(const mixed &v) { bool mixed::isset(int64_t int_key) const { if (unlikely (get_type() != type::ARRAY)) { + if (get_type() == type::OBJECT) { + // TODO think about numeric-like string + auto xxx = from_mixed>(*this, string()); + return f$ArrayAccess$$offsetExists(xxx, int_key); + } if (get_type() == type::STRING) { int_key = as_string().get_correct_index(int_key); return as_string().isset(int_key); @@ -1472,6 +1477,13 @@ bool mixed::isset(double double_key) const { void mixed::unset(int64_t int_key) { if (unlikely (get_type() != type::ARRAY)) { + // TODO f$is_a + if (get_type() == type::OBJECT) { + auto xxx = from_mixed>(*this, string()); + f$ArrayAccess$$offsetUnset(xxx, int_key); + return; + } + if (get_type() != type::NUL && (get_type() != type::BOOLEAN || as_bool())) { php_warning("Cannot use variable of type %s as array in unset", get_type_or_class_name()); } @@ -1484,6 +1496,12 @@ void mixed::unset(int64_t int_key) { void mixed::unset(const mixed &v) { if (unlikely (get_type() != type::ARRAY)) { + if (get_type() == type::OBJECT) { + auto xxx = from_mixed>(*this, string()); + f$ArrayAccess$$offsetUnset(xxx, v); + return; + } + if (get_type() != type::NUL && (get_type() != type::BOOLEAN || as_bool())) { php_warning("Cannot use variable of type %s as array in unset", get_type_or_class_name()); } diff --git a/runtime-common/core/core-types/definition/mixed.inl b/runtime-common/core/core-types/definition/mixed.inl index 98adcac744..642de8a3d6 100644 --- a/runtime-common/core/core-types/definition/mixed.inl +++ b/runtime-common/core/core-types/definition/mixed.inl @@ -93,6 +93,12 @@ int64_t spaceship(const T1 &lhs, const T2 &rhs); template bool mixed::isset(const string &string_key, MaybeHash ...maybe_hash) const { if (unlikely (get_type() != type::ARRAY)) { + if (get_type() == type::OBJECT) { + // TODO think about numeric-like string + auto xxx = from_mixed>(*this, string()); + return f$ArrayAccess$$offsetExists(xxx, string_key); + } + int64_t int_key{std::numeric_limits::max()}; if (get_type() == type::STRING) { if (!string_key.try_to_int(&int_key)) { @@ -110,6 +116,12 @@ bool mixed::isset(const string &string_key, MaybeHash ...maybe_hash) const { template void mixed::unset(const string &string_key, MaybeHash ...maybe_hash) { if (unlikely (get_type() != type::ARRAY)) { + if (get_type() == type::OBJECT) { + auto xxx = from_mixed>(*this, string()); + f$ArrayAccess$$offsetUnset(xxx, string_key); + return; + } + if (get_type() != type::NUL && (get_type() != type::BOOLEAN || as_bool())) { php_warning("Cannot use variable of type %s as array in unset", get_type_or_class_name()); } From a8723a8cd73d0a574362ac40da6899f5facc2c64 Mon Sep 17 00:00:00 2001 From: Mikhail Kornaukhov Date: Fri, 1 Nov 2024 17:50:49 +0300 Subject: [PATCH 32/44] tests for isset(), unset() --- tests/phpt/array_access/008_isset_unset.php | 46 +++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 tests/phpt/array_access/008_isset_unset.php diff --git a/tests/phpt/array_access/008_isset_unset.php b/tests/phpt/array_access/008_isset_unset.php new file mode 100644 index 0000000000..7a74f76d85 --- /dev/null +++ b/tests/phpt/array_access/008_isset_unset.php @@ -0,0 +1,46 @@ +@ok + 42]); + + var_dump(isset($obj[0])); + var_dump(isset($obj["str_key"])); + var_dump(isset($obj["non-existing-key"])); + + unset($obj["str_key"]); + unset($obj[0]); + + $keys = $obj->keys(); + foreach ($keys as $key) { + var_dump($obj[$key]); + } + + $obj[0] = "new"; + var_dump(isset($obj[0])); +} + +function test_mixed_in_obj() { + $obj = to_mixed(new Classes\LoggingLikeArray(["zero", "one", "two", "str_key" => 42])); + + var_dump(isset($obj[0])); + var_dump(isset($obj["str_key"])); + var_dump(isset($obj["non-existing-key"])); + + unset($obj["str_key"]); + unset($obj[0]); + + if ($obj instanceof Classes\KeysableArrayAccess) { + $keys = $obj->keys(); + foreach ($keys as $key) { + var_dump($obj[$key]); + } + } + + $obj[0] = "new"; + var_dump(isset($obj[0])); +} + +test_obj(); +test_mixed_in_obj(); From 3a5c36cadea6cf22004204b4d0662749e890fba2 Mon Sep 17 00:00:00 2001 From: Mikhail Kornaukhov Date: Tue, 5 Nov 2024 11:59:35 +0300 Subject: [PATCH 33/44] implement empty() for mixed array acccess and add test --- compiler/code-gen/vertex-compiler.cpp | 10 ++++ .../core/core-types/decl/mixed_decl.inl | 2 + .../core/core-types/definition/mixed.cpp | 9 ++++ tests/phpt/array_access/009_empty.php | 49 +++++++++++++++++++ 4 files changed, 70 insertions(+) create mode 100644 tests/phpt/array_access/009_empty.php diff --git a/compiler/code-gen/vertex-compiler.cpp b/compiler/code-gen/vertex-compiler.cpp index f46b59969c..b46f48e148 100644 --- a/compiler/code-gen/vertex-compiler.cpp +++ b/compiler/code-gen/vertex-compiler.cpp @@ -857,6 +857,16 @@ void compile_func_call(VertexAdaptor root, CodeGenerator &W, func_ } } + if (root->func_id->is_extern() && root->str_val == "empty") { + if (auto index = root->args()[0].try_as()) { + if (tinf::get_type(index->array())->get_real_ptype() == tp_mixed) { + W << "(" << index->array() << ")"; + W << ".empty_on(" << index->key() << ")"; + return; + } + } + } + if (FFIRoot::is_ffi_scope_call(root)) { compile_ffi_func_call(root, W); return; diff --git a/runtime-common/core/core-types/decl/mixed_decl.inl b/runtime-common/core/core-types/decl/mixed_decl.inl index 1531dea32d..5c9ae7beec 100644 --- a/runtime-common/core/core-types/decl/mixed_decl.inl +++ b/runtime-common/core/core-types/decl/mixed_decl.inl @@ -228,6 +228,8 @@ public: const char *get_type_or_class_name() const; bool empty() const; + // todo with non mixed key + bool empty_on(const mixed &key); int64_t count() const; int64_t compare(const mixed &rhs) const; diff --git a/runtime-common/core/core-types/definition/mixed.cpp b/runtime-common/core/core-types/definition/mixed.cpp index e4d3af26b3..17bd54bfb0 100644 --- a/runtime-common/core/core-types/definition/mixed.cpp +++ b/runtime-common/core/core-types/definition/mixed.cpp @@ -976,6 +976,15 @@ const string mixed::get_type_str() const { return string(get_type_c_str()); } +bool mixed::empty_on(const mixed &key) { + if (type_ == type::OBJECT) { + // todo f$is_a + auto xxx = from_mixed>(*this, string()); + return !f$ArrayAccess$$offsetExists(xxx, key) || f$ArrayAccess$$offsetGet(xxx, key).empty(); + }; + + return get_value(key).empty(); +} bool mixed::empty() const { return !to_bool(); diff --git a/tests/phpt/array_access/009_empty.php b/tests/phpt/array_access/009_empty.php new file mode 100644 index 0000000000..6647a5ddb5 --- /dev/null +++ b/tests/phpt/array_access/009_empty.php @@ -0,0 +1,49 @@ +@ok + 42, "nil" => null]); + + var_dump(empty($obj[0])); + var_dump(empty($obj["str_key"])); + var_dump(empty($obj["non-existing-key"])); + + unset($obj["str_key"]); + unset($obj[0]); + + $keys = $obj->keys(); + foreach ($keys as $key) { + var_dump($obj[$key]); + var_dump(empty($obj[$key])); + + } + + $obj[0] = "new"; + var_dump(empty($obj[0])); +} + +function test_mixed_in_obj() { + $obj = to_mixed(new Classes\LoggingLikeArray(["zero", "", false, "str_key" => 42, "nil" => null])); + + var_dump(empty($obj[0])); + var_dump(empty($obj["str_key"])); + var_dump(empty($obj["non-existing-key"])); + + unset($obj["str_key"]); + unset($obj[0]); + + if ($obj instanceof Classes\KeysableArrayAccess) { + $keys = $obj->keys(); + foreach ($keys as $key) { + var_dump($obj[$key]); + var_dump(empty($obj[$key])); + } + } + + $obj[0] = "new"; + var_dump(empty($obj[0])); +} + +test_obj(); +test_mixed_in_obj(); From f3bf348f2a5b6d0096c645615dab9ba98775d16e Mon Sep 17 00:00:00 2001 From: Mikhail Kornaukhov Date: Tue, 5 Nov 2024 14:27:22 +0300 Subject: [PATCH 34/44] fix constness for empty_on() --- runtime-common/core/core-types/decl/mixed_decl.inl | 2 +- runtime-common/core/core-types/definition/mixed.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/runtime-common/core/core-types/decl/mixed_decl.inl b/runtime-common/core/core-types/decl/mixed_decl.inl index 5c9ae7beec..dc22e2aea3 100644 --- a/runtime-common/core/core-types/decl/mixed_decl.inl +++ b/runtime-common/core/core-types/decl/mixed_decl.inl @@ -229,7 +229,7 @@ public: bool empty() const; // todo with non mixed key - bool empty_on(const mixed &key); + bool empty_on(const mixed &key) const; int64_t count() const; int64_t compare(const mixed &rhs) const; diff --git a/runtime-common/core/core-types/definition/mixed.cpp b/runtime-common/core/core-types/definition/mixed.cpp index 17bd54bfb0..0c1ce1c00e 100644 --- a/runtime-common/core/core-types/definition/mixed.cpp +++ b/runtime-common/core/core-types/definition/mixed.cpp @@ -976,7 +976,7 @@ const string mixed::get_type_str() const { return string(get_type_c_str()); } -bool mixed::empty_on(const mixed &key) { +bool mixed::empty_on(const mixed &key) const { if (type_ == type::OBJECT) { // todo f$is_a auto xxx = from_mixed>(*this, string()); From a52589a13f442689d7ded8718d2c64682130a511 Mon Sep 17 00:00:00 2001 From: Mikhail Kornaukhov Date: Wed, 6 Nov 2024 15:42:58 +0300 Subject: [PATCH 35/44] get rid of 'dangerous ub' warning --- compiler/pipes/check-ub.cpp | 2 +- compiler/pipes/fix-array-access.cpp | 2 ++ compiler/pipes/optimization.cpp | 5 +++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/compiler/pipes/check-ub.cpp b/compiler/pipes/check-ub.cpp index d75af87f53..4ee8160f38 100644 --- a/compiler/pipes/check-ub.cpp +++ b/compiler/pipes/check-ub.cpp @@ -190,7 +190,7 @@ void fix_ub_dfs(VertexPtr v, UBMergeData *data, VertexPtr parent = VertexPtr()) Location save_location = stage::get_location(); int res = 0; - bool do_not_check = vk::any_of_equal(v->type(), op_ternary, op_log_or, op_log_and, op_log_or_let, op_log_and_let, op_unset); + bool do_not_check = vk::any_of_equal(v->type(), op_ternary, op_log_or, op_log_and, op_log_or_let, op_log_and_let, op_unset, op_set_with_ret); for (auto i : *v) { UBMergeData node_data; diff --git a/compiler/pipes/fix-array-access.cpp b/compiler/pipes/fix-array-access.cpp index 5bf45accc5..9212dcead2 100644 --- a/compiler/pipes/fix-array-access.cpp +++ b/compiler/pipes/fix-array-access.cpp @@ -10,6 +10,7 @@ #include "compiler/data/vertex-adaptor.h" #include "compiler/inferring/primitive-type.h" #include "compiler/inferring/public.h" +#include "compiler/operation.h" #include static VertexPtr on_set(VertexAdaptor set) { @@ -39,6 +40,7 @@ static VertexPtr on_set(VertexAdaptor set) { auto key = func_call->args()[1]; + obj_arg.set_rl_type(val_r); auto zzz = VertexAdaptor::create(key, sub_val, obj_arg); zzz->set_method = method->function; diff --git a/compiler/pipes/optimization.cpp b/compiler/pipes/optimization.cpp index 576ada54d6..cacd201a03 100644 --- a/compiler/pipes/optimization.cpp +++ b/compiler/pipes/optimization.cpp @@ -130,6 +130,7 @@ VertexPtr OptimizationPass::optimize_set_push_back(VertexAdaptor set_op) kphp_assert_msg(method, fmt::format("Class {} does not implement offsetSet", klass->name).c_str()); if (set_op->rl_type == val_none) { + a->rl_type = val_r; auto new_call = VertexAdaptor::create(a, VertexAdaptor::create(), c).set_location(set_op->get_location()); new_call->str_val = method->global_name(); @@ -140,6 +141,7 @@ VertexPtr OptimizationPass::optimize_set_push_back(VertexAdaptor set_op) result = new_call; } else { + a->rl_type = val_r; auto z = VertexAdaptor::create(VertexAdaptor::create(), c, a); z->set_method = method->function; z.set_location(set_op); @@ -163,7 +165,6 @@ VertexPtr OptimizationPass::optimize_set_push_back(VertexAdaptor set_op) auto klass = tinf::get_type(a)->class_type(); kphp_assert_msg(klass, "bad klass"); - // TODO doesn't it have the problem with that some parent classes are not linked in chain yet? const auto *method = klass->get_instance_method("offsetSet"); if (!method) { @@ -172,7 +173,7 @@ VertexPtr OptimizationPass::optimize_set_push_back(VertexAdaptor set_op) } - // TODO assume here that key is present + a->rl_type = val_r; auto new_call = VertexAdaptor::create(a, b, c).set_location(set_op->get_location()); new_call->str_val = method->global_name(); From 2d129231187761f1e4875738343111bccb86bd6c Mon Sep 17 00:00:00 2001 From: Mikhail Kornaukhov Date: Wed, 6 Nov 2024 16:56:37 +0300 Subject: [PATCH 36/44] get rid of if(true) and enhance a test --- compiler/inferring/type-data.cpp | 19 ++++++++----------- compiler/pipes/filter-only-actually-used.cpp | 2 +- compiler/pipes/fix-array-access.cpp | 2 +- compiler/pipes/optimization.cpp | 6 +++--- compiler/vertex-desc.json | 16 ---------------- tests/phpt/array_access/100_not_implement.php | 2 +- 6 files changed, 14 insertions(+), 33 deletions(-) diff --git a/compiler/inferring/type-data.cpp b/compiler/inferring/type-data.cpp index 7e69503c1a..a965b8a2d0 100644 --- a/compiler/inferring/type-data.cpp +++ b/compiler/inferring/type-data.cpp @@ -356,21 +356,18 @@ const TypeData *TypeData::const_read_at(const Key &key) const { if (ptype() == tp_Class) { // TODO any race conditions? if (!class_type_.empty()) { - auto klass = class_type(); // Here is the place to think about inheritance stuff - // TODO better check here - // What is first: checking interface methods compatibility or type inference? Looks like there is no happens-before relation - const bool impl_aa = - std::find_if(klass->implements.begin(), klass->implements.end(), [](ClassPtr x) { return x->name == "ArrayAccess"; }) != klass->implements.end(); + auto klass = class_type(); + + ClassPtr aa = G->get_class("ArrayAccess"); + assert(aa && "Cannot find ArrayAccess"); // Why offsetSet for ArrayAccess is going through here - // Can I just always return tp_mixed? - // And in some later pass (FinalCheckPass, for example) check that access via [.] is enabled only for arrays and classes that implements ArrayAccess (by chain) - if ( - true || - impl_aa || klass->name.find("ArrayAccess") != std::string::npos) { + // And in some later pass (FinalCheckPass, for example) check that access via [.] is enabled only for arrays and classes that implements ArrayAccess (by + // chain) + if (aa->is_parent_of(klass)) { return get_type(tp_mixed); } - kphp_fail_msg("Read at class that does not implements ArrayAccess"); + kphp_error(false, fmt_format("Class {} that does not implement \\ArrayAccess", klass->name)); } else { kphp_fail_msg("class types is empty! =("); } diff --git a/compiler/pipes/filter-only-actually-used.cpp b/compiler/pipes/filter-only-actually-used.cpp index e83224b59d..49341f897a 100644 --- a/compiler/pipes/filter-only-actually-used.cpp +++ b/compiler/pipes/filter-only-actually-used.cpp @@ -250,7 +250,7 @@ IdMap calc_actually_used_having_call_edges(std::vectorclass_id; - bool impl_aa = aa->is_parent_of(klass); + const bool impl_aa = aa->is_parent_of(klass); return impl_aa && vk::any_of_equal(fun->local_name(), "offsetGet", "offsetSet", "offsetExists", "offsetUnset"); }(); diff --git a/compiler/pipes/fix-array-access.cpp b/compiler/pipes/fix-array-access.cpp index 9212dcead2..f63f9493b9 100644 --- a/compiler/pipes/fix-array-access.cpp +++ b/compiler/pipes/fix-array-access.cpp @@ -32,7 +32,7 @@ static VertexPtr on_set(VertexAdaptor set) { const auto *method = klass->get_instance_method("offsetSet"); if (!method) { - kphp_error(method, fmt_format("Class {} does not implement offsetSet", klass->name).c_str()); + kphp_error(method, fmt_format("Class {} does not implement \\ArrayAccesst", klass->name).c_str()); return set; } diff --git a/compiler/pipes/optimization.cpp b/compiler/pipes/optimization.cpp index cacd201a03..4792ad49d4 100644 --- a/compiler/pipes/optimization.cpp +++ b/compiler/pipes/optimization.cpp @@ -128,7 +128,7 @@ VertexPtr OptimizationPass::optimize_set_push_back(VertexAdaptor set_op) const auto *method = klass->get_instance_method("offsetSet"); - kphp_assert_msg(method, fmt::format("Class {} does not implement offsetSet", klass->name).c_str()); + kphp_assert_msg(method, fmt::format("Class {} does not implement \\ArrayAccess", klass->name).c_str()); if (set_op->rl_type == val_none) { a->rl_type = val_r; auto new_call = VertexAdaptor::create(a, VertexAdaptor::create(), c).set_location(set_op->get_location()); @@ -168,7 +168,7 @@ VertexPtr OptimizationPass::optimize_set_push_back(VertexAdaptor set_op) const auto *method = klass->get_instance_method("offsetSet"); if (!method) { - kphp_error(method, fmt_format("Class {} does not implement offsetSet", klass->name).c_str()); + kphp_error(method, fmt_format("Class {} does not implement \\ArrayAccess", klass->name).c_str()); return a; } @@ -249,7 +249,7 @@ VertexPtr OptimizationPass::optimize_index(VertexAdaptor index) { const auto *method = klass->get_instance_method("offsetGet"); if (!method) { - kphp_error(method, fmt_format("Class {} does not implement offsetSet", klass->name).c_str()); + kphp_error(method, fmt_format("Class {} does not implement \\ArrayAccess", klass->name).c_str()); return index; } // TODO assume here that key is present diff --git a/compiler/vertex-desc.json b/compiler/vertex-desc.json index b1691f7ceb..a5dfff4d42 100644 --- a/compiler/vertex-desc.json +++ b/compiler/vertex-desc.json @@ -1798,22 +1798,6 @@ "str": "set_value" } }, - { - "comment": "lowered form of Obj()[key()] = value()", - "sons": { - "obj": 0, - "key": 1, - "value": 2 - }, - "name": "op_offset_set", - "base_name": "meta_op_base", - "props": { - "rl": "rl_error", - "cnst": "cnst_error", - "type": "common_op", - "str": "offset_set" - } - }, { "comment": "false literal", "name": "op_false", diff --git a/tests/phpt/array_access/100_not_implement.php b/tests/phpt/array_access/100_not_implement.php index 20664dec52..3d455d6369 100644 --- a/tests/phpt/array_access/100_not_implement.php +++ b/tests/phpt/array_access/100_not_implement.php @@ -1,5 +1,5 @@ @kphp_should_fail -/Class Sample does not implement offsetSet/ +/Class Sample does not implement \\ArrayAccess/ Date: Wed, 6 Nov 2024 17:59:37 +0300 Subject: [PATCH 37/44] remove useless comments and improve useful comments and naming --- compiler/code-gen/vertex-compiler.cpp | 5 ++--- compiler/compiler.cpp | 6 +++--- compiler/inferring/primitive-type.h | 7 +++---- compiler/inferring/type-data.cpp | 6 +----- compiler/inferring/type-inferer.cpp | 5 ++--- compiler/inferring/var-node.cpp | 1 - compiler/make/make.cpp | 6 +----- compiler/make/objs-to-obj-target.h | 3 +-- compiler/pipes/check-classes.cpp | 2 +- compiler/pipes/check-ub.cpp | 3 --- runtime-common/core/array_access.h | 4 +++- tests/cpp/runtime/_runtime-tests-env.cpp | 2 +- 12 files changed, 18 insertions(+), 32 deletions(-) diff --git a/compiler/code-gen/vertex-compiler.cpp b/compiler/code-gen/vertex-compiler.cpp index b46f48e148..fd8f46533c 100644 --- a/compiler/code-gen/vertex-compiler.cpp +++ b/compiler/code-gen/vertex-compiler.cpp @@ -1803,8 +1803,7 @@ bool try_compile_append_inplace(VertexAdaptor root, CodeGenerator &W return false; } -// This also compiles index of mixed -void compile_index_of_array(VertexAdaptor root, CodeGenerator &W) { +void compile_index_of_array_or_mixed(VertexAdaptor root, CodeGenerator &W) { bool used_as_rval = root->rl_type != val_l; if (!used_as_rval) { kphp_assert(root->has_key()); @@ -1878,7 +1877,7 @@ void compile_index(VertexAdaptor root, CodeGenerator &W) { W << ShapeGetIndex(root->array(), root->key()); break; default: - compile_index_of_array(root, W); + compile_index_of_array_or_mixed(root, W); } } diff --git a/compiler/compiler.cpp b/compiler/compiler.cpp index 61b19dafd5..8ab354eb9e 100644 --- a/compiler/compiler.cpp +++ b/compiler/compiler.cpp @@ -267,7 +267,7 @@ bool compiler_execute(CompilerSettings *settings) { >> PipeC{} >> PipeC{} >> PassC{} - >> SyncC{} // removes unused func calls + >> SyncC{} >> PassC{} >> PassC{} >> PassC{} @@ -281,9 +281,9 @@ bool compiler_execute(CompilerSettings *settings) { >> PipeC{} >> SyncC{} >> PassC{} - >> PassC{} // collects edges for type inference + >> PassC{} >> SyncC{} - >> SyncC{} // type inferer finishes here + >> SyncC{} >> PipeC{} >> PassC{} >> PassC{} diff --git a/compiler/inferring/primitive-type.h b/compiler/inferring/primitive-type.h index d8898465f2..67bf393ad0 100644 --- a/compiler/inferring/primitive-type.h +++ b/compiler/inferring/primitive-type.h @@ -1,12 +1,11 @@ // Compiler for PHP (aka KPHP) -// Copyright (c) 2020 LLC «V Kontakte» +// Copyright (c) 2024 LLC «V Kontakte» // Distributed under the GPL v3 License, see LICENSE.notice.txt #pragma once -#include - -// The order is important! +// Order is important. These values are compared with operator< +// in some passes (in type inferring, for example) enum PrimitiveType { tp_any, tp_Null, diff --git a/compiler/inferring/type-data.cpp b/compiler/inferring/type-data.cpp index a965b8a2d0..85c8153ce1 100644 --- a/compiler/inferring/type-data.cpp +++ b/compiler/inferring/type-data.cpp @@ -361,9 +361,6 @@ const TypeData *TypeData::const_read_at(const Key &key) const { ClassPtr aa = G->get_class("ArrayAccess"); assert(aa && "Cannot find ArrayAccess"); - // Why offsetSet for ArrayAccess is going through here - // And in some later pass (FinalCheckPass, for example) check that access via [.] is enabled only for arrays and classes that implements ArrayAccess (by - // chain) if (aa->is_parent_of(klass)) { return get_type(tp_mixed); } @@ -408,7 +405,6 @@ const TypeData *TypeData::const_read_at(const MultiKey &multi_key) const { } void TypeData::make_structured() { - // TODO fix here for writing into objects that implements ArrayAccess // 'lvalue $s[idx]' makes $s array-typed: strings and tuples keep their types only for read-only operations if (ptype() < tp_array) { PrimitiveType new_ptype = type_lca(ptype(), tp_array); @@ -542,7 +538,7 @@ void TypeData::set_lca_at(const MultiKey &multi_key, const TypeData *rhs, bool s for (const Key &key : multi_key) { auto *prev = cur; - cur = cur->write_at(key); // HERE + cur = cur->write_at(key); // handle writing to a subkey of mixed (when cur is not structured) if (cur == nullptr) { if (prev->ptype() == tp_mixed) { diff --git a/compiler/inferring/type-inferer.cpp b/compiler/inferring/type-inferer.cpp index 5271dbdffc..eeb0b9c6d4 100644 --- a/compiler/inferring/type-inferer.cpp +++ b/compiler/inferring/type-inferer.cpp @@ -25,10 +25,10 @@ void TypeInferer::recalc_node(Node *node) { } void TypeInferer::add_node(Node *node) { + // fprintf (stderr, "tinf::add_node %d %p %s\n", get_thread_id(), node, node->get_description().c_str()); if (!node->was_recalc_started_at_least_once()) { recalc_node(node); } - // fprintf (stderr, "tinf::add_node %d %p %s\n", get_thread_id(), node, node->get_description().c_str()); } void TypeInferer::add_edge(const Edge *edge) { @@ -98,8 +98,7 @@ std::vector TypeInferer::get_tasks() { void TypeInferer::do_run_queue() { NodeQueue &q = Q.get(); - // in bad case first node add new VarNode into the Q queue - while (!q.empty()) { // when add here 2 nodes (when bad case) + while (!q.empty()) { Node *node = q.front(); node->start_recalc(); diff --git a/compiler/inferring/var-node.cpp b/compiler/inferring/var-node.cpp index 1d09133f26..7f7e6a045a 100644 --- a/compiler/inferring/var-node.cpp +++ b/compiler/inferring/var-node.cpp @@ -11,7 +11,6 @@ #include "compiler/inferring/restriction-match-phpdoc.h" #include "compiler/vertex.h" -// Check in add_node that adding f$ArrayAccess$offsetGet class VarNodeRecalc : public NodeRecalc { static void on_restricted_type_mismatch(const tinf::Edge *edge, const TypeData *type_restriction); diff --git a/compiler/make/make.cpp b/compiler/make/make.cpp index a5d092d3c3..eed3dcb65f 100644 --- a/compiler/make/make.cpp +++ b/compiler/make/make.cpp @@ -395,13 +395,9 @@ static std::vector create_obj_files(MakeSetup *make, Index &obj_dir, con for (File *f : deps) { vk::hash_combine(hash, vk::std_hash(f->name)); } - - auto intermediate_file_name = fmt_format("{}_{:x}.{}", name_and_files.first, hash, G->settings().dynamic_incremental_linkage.get() ? "so" : "o"); File *obj_file = obj_dir.insert_file(std::move(intermediate_file_name)); - - make->create_objs2obj_target(std::move(deps), obj_file); objs.push_back(obj_file); } @@ -448,7 +444,7 @@ static std::string get_light_runtime_compiler_options() { for (vk::string_view option : options) { for (vk::string_view prohibit_substr : black_list_substrings) { if (vk::contains(option, prohibit_substr)) continue; - s << option << " "; + s tion << " "; } } s << "-std=c++20 "; diff --git a/compiler/make/objs-to-obj-target.h b/compiler/make/objs-to-obj-target.h index 66b1c29a3f..11858b6b42 100644 --- a/compiler/make/objs-to-obj-target.h +++ b/compiler/make/objs-to-obj-target.h @@ -15,8 +15,7 @@ class Objs2ObjTarget : public Target { std::stringstream ss; ss << settings->cxx.get() << " " << settings->cxx_toolchain_option.get() << - " " << - settings->incremental_linker_flags.get() << + " " << settings->incremental_linker_flags.get() << " -o " << target() << " " << dep_list(); return ss.str(); diff --git a/compiler/pipes/check-classes.cpp b/compiler/pipes/check-classes.cpp index ab818fc5fa..947c3440c8 100644 --- a/compiler/pipes/check-classes.cpp +++ b/compiler/pipes/check-classes.cpp @@ -43,7 +43,7 @@ inline void CheckClassesPass::analyze_class(ClassPtr klass) { if (klass->does_need_codegen()) { check_instance_fields_inited(klass); } - if (klass->can_be_php_autoloaded && !klass->is_builtin() && !klass->need_generated_stub && !klass->internal_interface) { // TODO think about it later + if (klass->can_be_php_autoloaded && !klass->is_builtin() && !klass->need_generated_stub) { kphp_error(klass->file_id->main_function->body_seq == FunctionData::body_value::empty, fmt_format("class {} can be autoloaded, but its file contains some logic (maybe, require_once files with global vars?)\n", klass->as_human_readable())); diff --git a/compiler/pipes/check-ub.cpp b/compiler/pipes/check-ub.cpp index 4ee8160f38..2334b0c5df 100644 --- a/compiler/pipes/check-ub.cpp +++ b/compiler/pipes/check-ub.cpp @@ -206,9 +206,6 @@ void fix_ub_dfs(VertexPtr v, UBMergeData *data, VertexPtr parent = VertexPtr()) stage::set_location(save_location); if (res > 0) { - // TODO - // Find out how fix ub pass works - // and get rid of useless warning bool supported = vk::any_of_equal(v->type(), op_set, op_set_value, op_push_back, op_push_back_return, op_array, op_index) || OpInfo::rl(v->type()) == rl_set; if (supported) { diff --git a/runtime-common/core/array_access.h b/runtime-common/core/array_access.h index 4f5cca5842..b1d8b5e3ff 100644 --- a/runtime-common/core/array_access.h +++ b/runtime-common/core/array_access.h @@ -5,7 +5,9 @@ #pragma once #include "runtime-common/core/class-instance/refcountable-php-classes.h" -// TODO may be add guard to force include only from runtime-core.h? +#ifndef INCLUDED_FROM_KPHP_CORE + #error "this file must be included only from runtime-core.h" +#endif struct C$ArrayAccess : public may_be_mixed_base { virtual int get_hash() const noexcept = 0; diff --git a/tests/cpp/runtime/_runtime-tests-env.cpp b/tests/cpp/runtime/_runtime-tests-env.cpp index e3b96ad89e..68667347fb 100644 --- a/tests/cpp/runtime/_runtime-tests-env.cpp +++ b/tests/cpp/runtime/_runtime-tests-env.cpp @@ -101,4 +101,4 @@ void f$ArrayAccess$$offsetSet(class_instance const & /*v$this*/, void f$ArrayAccess$$offsetUnset(class_instance const & /*v$this*/, mixed const & /*v$offset*/) noexcept { assert(0 && "this code shouldn't be executed and only for linkage test"); -} \ No newline at end of file +} From 7d6a0d4bc2851c2d8dba5877d99f7c2688d77161 Mon Sep 17 00:00:00 2001 From: Mikhail Kornaukhov Date: Wed, 6 Nov 2024 18:31:13 +0300 Subject: [PATCH 38/44] cleanup mixed files --- runtime-common/core/core-types/decl/mixed_decl.inl | 3 --- runtime-common/core/core-types/definition/mixed.cpp | 6 ------ 2 files changed, 9 deletions(-) diff --git a/runtime-common/core/core-types/decl/mixed_decl.inl b/runtime-common/core/core-types/decl/mixed_decl.inl index dc22e2aea3..13968b2c9c 100644 --- a/runtime-common/core/core-types/decl/mixed_decl.inl +++ b/runtime-common/core/core-types/decl/mixed_decl.inl @@ -90,9 +90,6 @@ public: mixed &append(const string &v); mixed &append(tmp_string v); - // `operator[]` is used in runtime directly and - // during indirect assignment with macros - // For example, $x[0] = $x[1] = f(); mixed &operator[](int64_t int_key); mixed &operator[](int32_t key) { return (*this)[int64_t{key}]; } mixed &operator[](const string &string_key); diff --git a/runtime-common/core/core-types/definition/mixed.cpp b/runtime-common/core/core-types/definition/mixed.cpp index 0c1ce1c00e..b8af63e2a0 100644 --- a/runtime-common/core/core-types/definition/mixed.cpp +++ b/runtime-common/core/core-types/definition/mixed.cpp @@ -1074,12 +1074,7 @@ mixed &mixed::operator[](int64_t int_key) { type_ = type::ARRAY; new(&as_array()) array(); } - else if (get_type() == type::OBJECT) { - auto xxx = from_mixed>(*this, string()); - return empty_value(); - } else { - php_warning("type = %d\n", (int)get_type()); php_warning("Cannot use a value \"%s\" of type %s as an array, index = %" PRIi64, to_string_without_warning(*this).c_str(), get_type_or_class_name(), int_key); return empty_value(); } @@ -1334,7 +1329,6 @@ const mixed mixed::get_value(const string &string_key) const { // TODO check with f$is_a if (get_type() == type::OBJECT) { - // printf("Get [\"%s\"]\n", string_key.c_str()); auto xxx = from_mixed>(*this, string()); return f$ArrayAccess$$offsetGet(xxx, string_key); } From 8fcfd6a7270f63ca4bb59d6d27e1fb60776d304a Mon Sep 17 00:00:00 2001 From: Mikhail Kornaukhov Date: Thu, 7 Nov 2024 15:13:55 +0300 Subject: [PATCH 39/44] improve mixed files --- runtime-common/core/array_access.h | 2 +- .../core/core-types/decl/mixed_decl.inl | 19 +- .../core/core-types/definition/mixed.cpp | 169 ++++++++++-------- .../core/core-types/definition/mixed.inl | 23 ++- runtime-common/core/runtime-core.h | 4 +- 5 files changed, 134 insertions(+), 83 deletions(-) diff --git a/runtime-common/core/array_access.h b/runtime-common/core/array_access.h index b1d8b5e3ff..24bb45e635 100644 --- a/runtime-common/core/array_access.h +++ b/runtime-common/core/array_access.h @@ -20,4 +20,4 @@ struct C$ArrayAccess : public may_be_mixed_base { bool f$ArrayAccess$$offsetExists(class_instance const & /*v$this*/, mixed const & /*v$offset*/) noexcept; mixed f$ArrayAccess$$offsetGet(class_instance const & /*v$this*/, mixed const & /*v$offset*/) noexcept; void f$ArrayAccess$$offsetSet(class_instance const & /*v$this*/, mixed const & /*v$offset*/, mixed const & /*v$value*/) noexcept; -void f$ArrayAccess$$offsetUnset(class_instance const & /*v$this*/, mixed const & /*v$offset*/) noexcept; \ No newline at end of file +void f$ArrayAccess$$offsetUnset(class_instance const & /*v$this*/, mixed const & /*v$offset*/) noexcept; diff --git a/runtime-common/core/core-types/decl/mixed_decl.inl b/runtime-common/core/core-types/decl/mixed_decl.inl index 13968b2c9c..a52563fe39 100644 --- a/runtime-common/core/core-types/decl/mixed_decl.inl +++ b/runtime-common/core/core-types/decl/mixed_decl.inl @@ -99,6 +99,21 @@ public: mixed &operator[](const array::const_iterator &it); mixed &operator[](const array::iterator &it); + /* + * The `set_value_return()` method is used in assignment chains like `$mix[0] = $mix[1] = foo();`. + * Normally, this could be transpiled to `v$mix[0] = v$mix[1] = f$foo()`. However, when `$mix` is an object + * implementing ArrayAccess, this doesn't work because `offsetGet()` returns by value, not by reference. + * This is why `mixed &operator[]` cannot be expressed using `offsetGet()`. + * Since returning by reference is not supported, we call `offsetSet($offset, $value)` and return `$value`. + */ + + template + inline mixed set_value_return(T key, const mixed &val); + mixed set_value_return(const mixed &key, const mixed &val); + mixed set_value_return(const string &key, const mixed &val); + mixed set_value_return(const array::iterator &key, const mixed &val); + mixed set_value_return(const array::const_iterator &key, const mixed &val); + void set_value(int64_t int_key, const mixed &v); void set_value(int32_t key, const mixed &value) { set_value(int64_t{key}, value); } void set_value(const string &string_key, const mixed &v); @@ -109,7 +124,6 @@ public: void set_value(const array::const_iterator &it); void set_value(const array::iterator &it); - mixed set_by_index_return(const mixed &key, const mixed &val); const mixed get_value(int64_t int_key) const; const mixed get_value(int32_t key) const { return get_value(int64_t{key}); } @@ -306,4 +320,5 @@ inline bool less_string_number_as_php8(bool php7_result, const string &lhs, T rh template mixed f$to_mixed(const class_instance &instance) noexcept; template -ResultClass from_mixed(const mixed &m, const string &) noexcept; \ No newline at end of file +ResultClass from_mixed(const mixed &m, const string &) noexcept; +std::pair, bool> try_as_array_access(const mixed &) noexcept; diff --git a/runtime-common/core/core-types/definition/mixed.cpp b/runtime-common/core/core-types/definition/mixed.cpp index b8af63e2a0..1d98134b4c 100644 --- a/runtime-common/core/core-types/definition/mixed.cpp +++ b/runtime-common/core/core-types/definition/mixed.cpp @@ -4,7 +4,33 @@ #include "common/wrappers/likely.h" #include "runtime-common/core/runtime-core.h" -#include + +namespace { + +string to_string_without_warning(const mixed &m) { + switch (m.get_type()) { + case mixed::type::NUL: + return string(); + case mixed::type::BOOLEAN: + return (m.as_bool() ? string("1", 1) : string()); + case mixed::type::INTEGER: + return string(m.as_int()); + case mixed::type::FLOAT: + return string(m.as_double()); + case mixed::type::STRING: + return m.as_string(); + case mixed::type::ARRAY: + return string("Array", 5); + case mixed::type::OBJECT: { + const char *s = m.get_type_or_class_name(); + return string(s, strlen(s)); + } + default: + __builtin_unreachable(); + } +} + +} // namespace void mixed::copy_from(const mixed &other) { switch (other.get_type()) { @@ -537,28 +563,6 @@ double mixed::to_float() const { } } -static string to_string_without_warning(const mixed &m) { - switch (m.get_type()) { - case mixed::type::NUL: - return string(); - case mixed::type::BOOLEAN: - return (m.as_bool() ? string("1", 1) : string()); - case mixed::type::INTEGER: - return string(m.as_int()); - case mixed::type::FLOAT: - return string(m.as_double()); - case mixed::type::STRING: - return m.as_string(); - case mixed::type::ARRAY: - return string("Array", 5); - case mixed::type::OBJECT: { - const char *s = m.get_type_or_class_name(); - return string(s, strlen(s)); - } - default: - __builtin_unreachable(); - } -} const string mixed::to_string() const { switch (get_type()) { @@ -976,12 +980,29 @@ const string mixed::get_type_str() const { return string(get_type_c_str()); } +// TODO +// Should we warn more precisely: "Class XXX does not implement \\ArrayAccess" or just +// "Cannot use XXX as array, index = YYY" will be OK? +std::pair, bool> try_as_array_access(const mixed &m) noexcept { + using T = class_instance; + + // For now, it does dynamic cast twice + // We can get rid of one of them + if (likely(m.is_a())) { + return {from_mixed(m, string()), true}; + } + + return {T{}, false}; +} + bool mixed::empty_on(const mixed &key) const { + // 1) `if (type_ == type::OBJECT)` is semantically redundant + // 2) it may be ok becuase of fast check if (type_ == type::OBJECT) { - // todo f$is_a - auto xxx = from_mixed>(*this, string()); - return !f$ArrayAccess$$offsetExists(xxx, key) || f$ArrayAccess$$offsetGet(xxx, key).empty(); - }; + if (auto [as_aa, succ] = try_as_array_access(*this); succ) { + return !f$ArrayAccess$$offsetExists(as_aa, key) || f$ArrayAccess$$offsetGet(as_aa, key).empty(); + } + } return get_value(key).empty(); } @@ -1060,9 +1081,6 @@ int64_t mixed::compare(const mixed &rhs) const { return three_way_comparison(to_float(), rhs.to_float()); } - - - mixed &mixed::operator[](int64_t int_key) { if (unlikely (get_type() != type::ARRAY)) { if (get_type() == type::STRING) { @@ -1073,8 +1091,7 @@ mixed &mixed::operator[](int64_t int_key) { if (get_type() == type::NUL || (get_type() == type::BOOLEAN && !as_bool())) { type_ = type::ARRAY; new(&as_array()) array(); - } - else { + } else { php_warning("Cannot use a value \"%s\" of type %s as an array, index = %" PRIi64, to_string_without_warning(*this).c_str(), get_type_or_class_name(), int_key); return empty_value(); } @@ -1143,15 +1160,27 @@ mixed &mixed::operator[](const array::iterator &it) { return as_array()[it]; } +mixed mixed::set_value_return(const mixed &key, const mixed &val) { + if (get_type() == type::OBJECT) { + set_value(key, val); + return val; + } + return (*this)[key] = val; +} -mixed mixed::set_by_index_return(const mixed &key, const mixed &val) { +mixed mixed::set_value_return(const string &key, const mixed &val) { if (get_type() == type::OBJECT) { - // TODO check with f$is_a - // TODO may be more efficient way? set_value(key, val); return val; } + return (*this)[key] = val; +} + +mixed mixed::set_value_return(const array::iterator &key, const mixed &val) { + return (*this)[key] = val; +} +mixed mixed::set_value_return(const array::const_iterator &key, const mixed &val) { return (*this)[key] = val; } @@ -1181,12 +1210,11 @@ void mixed::set_value(int64_t int_key, const mixed &v) { return; } - // TODO don't forget to use f$is_a !!! - // It'll look like an instance cast if (get_type() == type::OBJECT) { - auto xxx = from_mixed>(*this, string()); - f$ArrayAccess$$offsetSet(xxx, int_key, v); - return; + if (auto [as_aa, succ] = try_as_array_access(*this); succ) { + f$ArrayAccess$$offsetSet(as_aa, int_key, v); + return; + } } if (get_type() == type::NUL || (get_type() == type::BOOLEAN && !as_bool())) { @@ -1225,12 +1253,11 @@ void mixed::set_value(const string &string_key, const mixed &v) { return; } - // TODO don't forget to use f$is_a !!! - // It'll look like an instance cast if (get_type() == type::OBJECT) { - auto xxx = from_mixed>(*this, string()); - f$ArrayAccess$$offsetSet(xxx, string_key, v); - return; + if (auto [as_aa, succ] = try_as_array_access(*this); succ) { + f$ArrayAccess$$offsetSet(as_aa, string_key, v); + return; + } } if (get_type() == type::NUL || (get_type() == type::BOOLEAN && !as_bool())) { type_ = type::ARRAY; @@ -1298,10 +1325,10 @@ const mixed mixed::get_value(int64_t int_key) const { return string(1, as_string()[static_cast(int_key)]); } - // TODO check with f$is_a if (get_type() == type::OBJECT) { - auto xxx = from_mixed>(*this, string()); - return f$ArrayAccess$$offsetGet(xxx, int_key); + if (auto [as_aa, succ] = try_as_array_access(*this); succ) { + return f$ArrayAccess$$offsetGet(as_aa, int_key); + } } if (get_type() != type::NUL && (get_type() != type::BOOLEAN || as_bool())) { @@ -1327,10 +1354,10 @@ const mixed mixed::get_value(const string &string_key) const { return string(1, as_string()[static_cast(int_val)]); } - // TODO check with f$is_a if (get_type() == type::OBJECT) { - auto xxx = from_mixed>(*this, string()); - return f$ArrayAccess$$offsetGet(xxx, string_key); + if (auto [as_aa, succ] = try_as_array_access(*this); succ) { + return f$ArrayAccess$$offsetGet(as_aa, string_key); + } } if (get_type() != type::NUL && (get_type() != type::BOOLEAN || as_bool())) { @@ -1390,15 +1417,14 @@ const mixed mixed::get_value(const array::iterator &it) const { return as_array().get_value(it); } -// TODO USE f$is_a before every `from_mixed()` !!! void mixed::push_back(const mixed &v) { if (unlikely (get_type() != type::ARRAY)) { if (get_type() == type::OBJECT) { - auto xxx = from_mixed>(*this, string()); - f$ArrayAccess$$offsetSet(xxx, Optional{}, v); - return; - } - else if (get_type() == type::NUL || (get_type() == type::BOOLEAN && !as_bool())) { + if (auto [as_aa, succ] = try_as_array_access(*this); succ) { + f$ArrayAccess$$offsetSet(as_aa, Optional{}, v); + return; + } + } else if (get_type() == type::NUL || (get_type() == type::BOOLEAN && !as_bool())) { type_ = type::ARRAY; new(&as_array()) array(); } else { @@ -1413,11 +1439,11 @@ void mixed::push_back(const mixed &v) { const mixed mixed::push_back_return(const mixed &v) { if (unlikely (get_type() != type::ARRAY)) { if (get_type() == type::OBJECT) { - auto xxx = from_mixed>(*this, string()); - f$ArrayAccess$$offsetSet(xxx, Optional{}, v); - return v; - } - else if (get_type() == type::NUL || (get_type() == type::BOOLEAN && !as_bool())) { + if (auto [as_aa, succ] = try_as_array_access(*this); succ) { + f$ArrayAccess$$offsetSet(as_aa, Optional{}, v); + return v; + } + } else if (get_type() == type::NUL || (get_type() == type::BOOLEAN && !as_bool())) { type_ = type::ARRAY; new(&as_array()) array(); } else { @@ -1432,9 +1458,9 @@ const mixed mixed::push_back_return(const mixed &v) { bool mixed::isset(int64_t int_key) const { if (unlikely (get_type() != type::ARRAY)) { if (get_type() == type::OBJECT) { - // TODO think about numeric-like string - auto xxx = from_mixed>(*this, string()); - return f$ArrayAccess$$offsetExists(xxx, int_key); + if (auto [as_aa, succ] = try_as_array_access(*this); succ) { + return f$ArrayAccess$$offsetExists(as_aa, int_key); + } } if (get_type() == type::STRING) { int_key = as_string().get_correct_index(int_key); @@ -1480,11 +1506,11 @@ bool mixed::isset(double double_key) const { void mixed::unset(int64_t int_key) { if (unlikely (get_type() != type::ARRAY)) { - // TODO f$is_a if (get_type() == type::OBJECT) { - auto xxx = from_mixed>(*this, string()); - f$ArrayAccess$$offsetUnset(xxx, int_key); - return; + if (auto [as_aa, succ] = try_as_array_access(*this); succ) { + f$ArrayAccess$$offsetUnset(as_aa, int_key); + return; + } } if (get_type() != type::NUL && (get_type() != type::BOOLEAN || as_bool())) { @@ -1500,8 +1526,9 @@ void mixed::unset(int64_t int_key) { void mixed::unset(const mixed &v) { if (unlikely (get_type() != type::ARRAY)) { if (get_type() == type::OBJECT) { - auto xxx = from_mixed>(*this, string()); - f$ArrayAccess$$offsetUnset(xxx, v); + if (auto [as_aa, succ] = try_as_array_access(*this); succ) { + f$ArrayAccess$$offsetUnset(as_aa, v); + } return; } diff --git a/runtime-common/core/core-types/definition/mixed.inl b/runtime-common/core/core-types/definition/mixed.inl index 642de8a3d6..bb3ad1d3a2 100644 --- a/runtime-common/core/core-types/definition/mixed.inl +++ b/runtime-common/core/core-types/definition/mixed.inl @@ -94,9 +94,9 @@ template bool mixed::isset(const string &string_key, MaybeHash ...maybe_hash) const { if (unlikely (get_type() != type::ARRAY)) { if (get_type() == type::OBJECT) { - // TODO think about numeric-like string - auto xxx = from_mixed>(*this, string()); - return f$ArrayAccess$$offsetExists(xxx, string_key); + if (auto [as_aa, succ] = try_as_array_access(*this); succ) { + return f$ArrayAccess$$offsetExists(as_aa, string_key); + } } int64_t int_key{std::numeric_limits::max()}; @@ -117,9 +117,10 @@ template void mixed::unset(const string &string_key, MaybeHash ...maybe_hash) { if (unlikely (get_type() != type::ARRAY)) { if (get_type() == type::OBJECT) { - auto xxx = from_mixed>(*this, string()); - f$ArrayAccess$$offsetUnset(xxx, string_key); - return; + if (auto [as_aa, succ] = try_as_array_access(*this); succ) { + f$ArrayAccess$$offsetUnset(as_aa, string_key); + return; + } } if (get_type() != type::NUL && (get_type() != type::BOOLEAN || as_bool())) { @@ -131,7 +132,6 @@ void mixed::unset(const string &string_key, MaybeHash ...maybe_hash) { as_array().unset(string_key, maybe_hash...); } - inline mixed::type mixed::get_type() const { return type_; } @@ -340,3 +340,12 @@ ResultClass from_mixed(const mixed &m, const string &) noexcept { return ResultClass::create_from_base_raw_ptr(dynamic_cast(m.as_object_ptr())); } } + +template +inline mixed mixed::set_value_return(T key, const mixed &val) { + if (get_type() == type::OBJECT) { + set_value(key, val); + return val; + } + return (*this)[key] = val; +} diff --git a/runtime-common/core/runtime-core.h b/runtime-common/core/runtime-core.h index d390d89a4c..0367461997 100644 --- a/runtime-common/core/runtime-core.h +++ b/runtime-common/core/runtime-core.h @@ -49,8 +49,8 @@ #define FFI_CALL(call) ({ dl::CriticalSectionGuard critical_section___; (call); }) #define FFI_INVOKE_CALLBACK(call) ({ dl::NonCriticalSectionGuard non_critical_section___; (call); }) -#define SET_MIXED_BY_INDEX(mix, idx, val) mix.set_by_index_return(idx, val) -#define SAFE_SET_MIXED_BY_INDEX(mix, idx, val, val_type) ({ val_type val_tmp___ = val; mix.set_by_index_return(idx, std::move(val_tmp___)); }) +#define SET_MIXED_BY_INDEX(mix, idx, val) mix.set_value_return(idx, val) +#define SAFE_SET_MIXED_BY_INDEX(mix, idx, val, val_type) ({ val_type val_tmp___ = val; mix.set_value_return(idx, std::move(val_tmp___)); }) #define SET_ARR_ACC_BY_INDEX(obj, idx, val, method) ({mixed val_tmp___ = val; method(obj, idx, val_tmp___); val_tmp___;}) // it's always safe #define SAFE_SET_OP(a, op, b, b_type) ({b_type b_tmp___ = b; a op std::move(b_tmp___);}) From ab0342d6a0378bf5fc779f47c23746ea029adb86 Mon Sep 17 00:00:00 2001 From: Mikhail Kornaukhov Date: Thu, 7 Nov 2024 16:30:44 +0300 Subject: [PATCH 40/44] fix mixed::empty_on() --- .../core/core-types/decl/mixed_decl.inl | 11 +++++-- .../core/core-types/definition/mixed.cpp | 32 +++++++++++++++++-- .../core/core-types/definition/mixed.inl | 10 ++++++ 3 files changed, 49 insertions(+), 4 deletions(-) diff --git a/runtime-common/core/core-types/decl/mixed_decl.inl b/runtime-common/core/core-types/decl/mixed_decl.inl index a52563fe39..7d024d1d03 100644 --- a/runtime-common/core/core-types/decl/mixed_decl.inl +++ b/runtime-common/core/core-types/decl/mixed_decl.inl @@ -238,9 +238,16 @@ public: const char *get_type_c_str() const; const char *get_type_or_class_name() const; - bool empty() const; - // todo with non mixed key + + template + inline bool empty_on(T key) const; bool empty_on(const mixed &key) const; + bool empty_on(const string &key) const; + bool empty_on(const string &key, int64_t precomputed_hash) const; + bool empty_on(const array::iterator &key) const; + bool empty_on(const array::const_iterator &key) const; + + bool empty() const; int64_t count() const; int64_t compare(const mixed &rhs) const; diff --git a/runtime-common/core/core-types/definition/mixed.cpp b/runtime-common/core/core-types/definition/mixed.cpp index 1d98134b4c..d9e9810ef9 100644 --- a/runtime-common/core/core-types/definition/mixed.cpp +++ b/runtime-common/core/core-types/definition/mixed.cpp @@ -983,6 +983,8 @@ const string mixed::get_type_str() const { // TODO // Should we warn more precisely: "Class XXX does not implement \\ArrayAccess" or just // "Cannot use XXX as array, index = YYY" will be OK? +// Note on usage: should check `if (type_ == type::OBJECT)` or not? +// On the one hand, it's redundant. On the other hand, it's fast check std::pair, bool> try_as_array_access(const mixed &m) noexcept { using T = class_instance; @@ -996,8 +998,6 @@ std::pair, bool> try_as_array_access(const mixed & } bool mixed::empty_on(const mixed &key) const { - // 1) `if (type_ == type::OBJECT)` is semantically redundant - // 2) it may be ok becuase of fast check if (type_ == type::OBJECT) { if (auto [as_aa, succ] = try_as_array_access(*this); succ) { return !f$ArrayAccess$$offsetExists(as_aa, key) || f$ArrayAccess$$offsetGet(as_aa, key).empty(); @@ -1007,6 +1007,34 @@ bool mixed::empty_on(const mixed &key) const { return get_value(key).empty(); } +bool mixed::empty_on(const string &key) const { + if (type_ == type::OBJECT) { + if (auto [as_aa, succ] = try_as_array_access(*this); succ) { + return !f$ArrayAccess$$offsetExists(as_aa, key) || f$ArrayAccess$$offsetGet(as_aa, key).empty(); + } + } + + return get_value(key).empty(); +} + +bool mixed::empty_on(const string &key, int64_t precomputed_hash) const { + if (type_ == type::OBJECT) { + if (auto [as_aa, succ] = try_as_array_access(*this); succ) { + return !f$ArrayAccess$$offsetExists(as_aa, key) || f$ArrayAccess$$offsetGet(as_aa, key).empty(); + } + } + + return get_value(key, precomputed_hash).empty(); +} + +bool mixed::empty_on(const array::iterator &key) const { + return get_value(key).empty(); +} + +bool mixed::empty_on(const array::const_iterator &key) const { + return get_value(key).empty(); +} + bool mixed::empty() const { return !to_bool(); } diff --git a/runtime-common/core/core-types/definition/mixed.inl b/runtime-common/core/core-types/definition/mixed.inl index bb3ad1d3a2..3335340671 100644 --- a/runtime-common/core/core-types/definition/mixed.inl +++ b/runtime-common/core/core-types/definition/mixed.inl @@ -349,3 +349,13 @@ inline mixed mixed::set_value_return(T key, const mixed &val) { } return (*this)[key] = val; } + +template +inline bool mixed::empty_on(T key) const { + if (type_ == type::OBJECT) { + if (auto [as_aa, succ] = try_as_array_access(*this); succ) { + return !f$ArrayAccess$$offsetExists(as_aa, key) || f$ArrayAccess$$offsetGet(as_aa, key).empty(); + } + } + return get_value(key).empty(); +} From edc386ebae21e1726afc95d4bbb7f57c68ee2af9 Mon Sep 17 00:00:00 2001 From: Mikhail Kornaukhov Date: Thu, 7 Nov 2024 17:00:01 +0300 Subject: [PATCH 41/44] fix inferring and add some comments --- compiler/code-gen/vertex-compiler.cpp | 2 +- compiler/inferring/type-data.cpp | 29 +++++++++++++-------------- compiler/inferring/type-inferer.cpp | 2 +- compiler/make/make.cpp | 2 +- 4 files changed, 17 insertions(+), 18 deletions(-) diff --git a/compiler/code-gen/vertex-compiler.cpp b/compiler/code-gen/vertex-compiler.cpp index fd8f46533c..e000164b8c 100644 --- a/compiler/code-gen/vertex-compiler.cpp +++ b/compiler/code-gen/vertex-compiler.cpp @@ -861,7 +861,7 @@ void compile_func_call(VertexAdaptor root, CodeGenerator &W, func_ if (auto index = root->args()[0].try_as()) { if (tinf::get_type(index->array())->get_real_ptype() == tp_mixed) { W << "(" << index->array() << ")"; - W << ".empty_on(" << index->key() << ")"; + W << ".empty_on(" << index->key() << ")"; // TODO implement optimization with precomputed hash (can_use_precomputed_hash_indexing_array) return; } } diff --git a/compiler/inferring/type-data.cpp b/compiler/inferring/type-data.cpp index 85c8153ce1..eadb1815d4 100644 --- a/compiler/inferring/type-data.cpp +++ b/compiler/inferring/type-data.cpp @@ -1,29 +1,29 @@ // Compiler for PHP (aka KPHP) -// Copyright (c) 2020 LLC «V Kontakte» +// Copyright (c) 2024 LLC «V Kontakte» // Distributed under the GPL v3 License, see LICENSE.notice.txt #include "compiler/inferring/type-data.h" -#include "compiler/inferring/primitive-type.h" -#include "compiler/kphp_assert.h" + #include #include #include "common/algorithms/compare.h" #include "common/algorithms/contains.h" +#include "common/php-functions.h" #include "common/termformat/termformat.h" -#include "common/php-functions.h" -#include "compiler/compiler-core.h" #include "compiler/code-gen/common.h" +#include "compiler/compiler-core.h" #include "compiler/data/class-data.h" #include "compiler/data/ffi-data.h" +#include "compiler/inferring/primitive-type.h" +#include "compiler/kphp_assert.h" #include "compiler/pipes/collect-main-edges.h" #include "compiler/stage.h" #include "compiler/threading/hash-table.h" #include "compiler/utils/string-utils.h" - static std::vector primitive_types; static std::vector array_types; @@ -354,19 +354,14 @@ const TypeData *TypeData::const_read_at(const Key &key) const { return get_type(tp_string); } if (ptype() == tp_Class) { - // TODO any race conditions? - if (!class_type_.empty()) { - auto klass = class_type(); - + if (auto klass = class_type(); klass) { ClassPtr aa = G->get_class("ArrayAccess"); - assert(aa && "Cannot find ArrayAccess"); + kphp_assert_msg(aa, "Internal error: cannot find ArrayAccess interface"); if (aa->is_parent_of(klass)) { return get_type(tp_mixed); } kphp_error(false, fmt_format("Class {} that does not implement \\ArrayAccess", klass->name)); - } else { - kphp_fail_msg("class types is empty! =("); } } if (!structured()) { @@ -438,8 +433,9 @@ void TypeData::set_lca(const TypeData *rhs, bool save_or_false, bool save_or_nul PrimitiveType new_ptype = type_lca(lhs->ptype(), rhs->ptype()); if (lhs->ptype_ == tp_array && rhs->ptype_ == tp_Class) { if (lhs->get_write_flag()) { - // It means that lhs(==this) is something like that "$a[.] = " - new_ptype = tp_Class; // for array access + // `ArrayAccess::offsetSet()` case + // It means that lhs is something like that `$a[*] = foo()` + new_ptype = tp_Class; } } if (new_ptype == tp_mixed) { @@ -555,6 +551,9 @@ void TypeData::set_lca_at(const MultiKey &multi_key, const TypeData *rhs, bool s } if (cur->get_write_flag()) { + // Access using `multi_key` is storing, not loading + // So, we need to save this info in this node to correctly handle `ArrayAccess::offsetSet()` + // But `cur` is not always pointing to `this`, `write_at()` method in previous loop may reallocate node this->set_write_flag(); } diff --git a/compiler/inferring/type-inferer.cpp b/compiler/inferring/type-inferer.cpp index eeb0b9c6d4..ae08fa6744 100644 --- a/compiler/inferring/type-inferer.cpp +++ b/compiler/inferring/type-inferer.cpp @@ -25,7 +25,7 @@ void TypeInferer::recalc_node(Node *node) { } void TypeInferer::add_node(Node *node) { - // fprintf (stderr, "tinf::add_node %d %p %s\n", get_thread_id(), node, node->get_description().c_str()); + //fprintf (stderr, "tinf::add_node %d %p %s\n", get_thread_id(), node, node->get_description().c_str()); if (!node->was_recalc_started_at_least_once()) { recalc_node(node); } diff --git a/compiler/make/make.cpp b/compiler/make/make.cpp index eed3dcb65f..d4624c4ec7 100644 --- a/compiler/make/make.cpp +++ b/compiler/make/make.cpp @@ -444,7 +444,7 @@ static std::string get_light_runtime_compiler_options() { for (vk::string_view option : options) { for (vk::string_view prohibit_substr : black_list_substrings) { if (vk::contains(option, prohibit_substr)) continue; - s tion << " "; + s << option << " "; } } s << "-std=c++20 "; From ce12cdc612e77511e85cc207ab3aa413468a5ba5 Mon Sep 17 00:00:00 2001 From: Mikhail Kornaukhov Date: Thu, 7 Nov 2024 17:33:13 +0300 Subject: [PATCH 42/44] remove extra todo, smart cast with index access is not supported --- compiler/pipes/check-modifications-of-const-vars.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/compiler/pipes/check-modifications-of-const-vars.cpp b/compiler/pipes/check-modifications-of-const-vars.cpp index 40d45a6250..49b78595a1 100644 --- a/compiler/pipes/check-modifications-of-const-vars.cpp +++ b/compiler/pipes/check-modifications-of-const-vars.cpp @@ -119,7 +119,6 @@ void CheckModificationsOfConstVars::check_modifications(VertexPtr v, bool write_ TermStringFormat::paint(std::string(std::next(const_var->name.begin(), std::strlen(constant_prefix)), const_var->name.end()), TermStringFormat::red))); } else { - // TODO fix this error when using setting [.] with smart cast kphp_error(modification_allowed, fmt_format("Modification of const variable: {}", TermStringFormat::paint(const_var->name, TermStringFormat::red))); } } From 46115b2769283db53ca6743096d303ecb6b08287 Mon Sep 17 00:00:00 2001 From: Mikhail Kornaukhov Date: Thu, 7 Nov 2024 18:37:06 +0300 Subject: [PATCH 43/44] simplify filter-only-actually-used pass --- compiler/pipes/filter-only-actually-used.cpp | 24 ++------------------ 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/compiler/pipes/filter-only-actually-used.cpp b/compiler/pipes/filter-only-actually-used.cpp index 49341f897a..6c3f2f707c 100644 --- a/compiler/pipes/filter-only-actually-used.cpp +++ b/compiler/pipes/filter-only-actually-used.cpp @@ -240,21 +240,6 @@ IdMap calc_actually_used_having_call_edges(std::vectormodifiers.is_instance()) { - return false; - } - - ClassPtr aa = G->get_class("ArrayAccess"); - assert(aa && "Cannot find ArrayAccess"); - - ClassPtr klass = fun->class_id; - const bool impl_aa = aa->is_parent_of(klass); - return impl_aa && vk::any_of_equal(fun->local_name(), "offsetGet", "offsetSet", "offsetExists", "offsetUnset"); - }(); - - // TODO think about more accurate check const bool should_be_used_apriori = fun->is_main_function() || fun->type == FunctionData::func_class_holder || // classes should be carried along the pipeline @@ -262,7 +247,7 @@ IdMap calc_actually_used_having_call_edges(std::vectorkphp_lib_export || (fun->modifiers.is_instance() && fun->local_name() == ClassData::NAME_OF_TO_STRING) || (fun->modifiers.is_instance() && fun->local_name() == ClassData::NAME_OF_WAKEUP) || - is_array_access_fun; + (fun->modifiers.is_instance() && fun->class_id->internal_interface); if (should_be_used_apriori && !used_functions[fun]) { calc_actually_used_dfs(fun, used_functions, call_graph); } @@ -297,12 +282,7 @@ void remove_unused_class_methods(const std::vector &all, const }); fun->class_id->members.remove_if( [&used_functions, class_id=fun->class_id](const ClassMemberInstanceMethod &m) { - bool cond = get_index(m.function) == -1 || !used_functions[m.function]; - const std::string& name = m.global_name(); - if (cond && name.find("offsetGet") != std::string::npos) { - printf("removing %s\n",name.c_str()); - } - return !class_id->internal_interface && cond; + return get_index(m.function) == -1 || !used_functions[m.function]; }); } } From d0f4f5a7715da396b5edb3cfcc90e379d7bac389 Mon Sep 17 00:00:00 2001 From: Mikhail Kornaukhov Date: Thu, 7 Nov 2024 18:41:55 +0300 Subject: [PATCH 44/44] small cleanup --- compiler/pipes/final-check.cpp | 3 --- compiler/pipes/inline-simple-functions.cpp | 2 -- compiler/pipes/wait-for-all-classes.cpp | 3 --- 3 files changed, 8 deletions(-) diff --git a/compiler/pipes/final-check.cpp b/compiler/pipes/final-check.cpp index c52ce78249..466c02f426 100644 --- a/compiler/pipes/final-check.cpp +++ b/compiler/pipes/final-check.cpp @@ -1006,9 +1006,6 @@ void FinalCheckPass::on_function() { } void FinalCheckPass::raise_error_using_Unknown_type(VertexPtr v) { - // TODO I think I should add some checks here - // In this pass, at least - std::string index_depth; while (auto v_index = v.try_as()) { v = v_index->array(); diff --git a/compiler/pipes/inline-simple-functions.cpp b/compiler/pipes/inline-simple-functions.cpp index 45fa97ca96..8f4f747240 100644 --- a/compiler/pipes/inline-simple-functions.cpp +++ b/compiler/pipes/inline-simple-functions.cpp @@ -108,7 +108,6 @@ bool InlineSimpleFunctions::check_function(FunctionPtr function) const { !function->kphp_lib_export; } - void InlineSimpleFunctions::on_start() { if (auto klass = current_function->class_id) { if (klass->internal_interface) { @@ -118,7 +117,6 @@ void InlineSimpleFunctions::on_start() { return FunctionPassBase::on_start(); } - void InlineSimpleFunctions::on_finish() { if (inline_is_possible_) { current_function->is_inline = true; diff --git a/compiler/pipes/wait-for-all-classes.cpp b/compiler/pipes/wait-for-all-classes.cpp index 16902484cc..92c3b3ef33 100644 --- a/compiler/pipes/wait-for-all-classes.cpp +++ b/compiler/pipes/wait-for-all-classes.cpp @@ -11,9 +11,6 @@ #include "compiler/data/src-file.h" #include "compiler/data/modulite-data.h" - -// Very important!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - /* * This is a sync point. When `on_finish()` is called (once, in a single thread), then * all php files have been parsed, all classes have been loaded.