Skip to content

Commit

Permalink
Implementation of ArrayAccess interface (#1116)
Browse files Browse the repository at this point in the history
  • Loading branch information
mkornaukhov03 authored Dec 6, 2024
1 parent d784424 commit f4ef4e8
Show file tree
Hide file tree
Showing 51 changed files with 1,999 additions and 106 deletions.
8 changes: 8 additions & 0 deletions builtin-functions/kphp-full/_functions.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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-required */
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);
}
8 changes: 8 additions & 0 deletions builtin-functions/kphp-light/functions.txt
Original file line number Diff line number Diff line change
Expand Up @@ -166,3 +166,11 @@ function to_mixed(object $instance) ::: mixed;
// === Unsupported =======================================================================================

require_once __DIR__ . '/unsupported-functions.txt';

/** @kphp-required */
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);
}
116 changes: 102 additions & 14 deletions compiler/code-gen/vertex-compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

#include "common/wrappers/field_getter.h"
#include "common/wrappers/likely.h"
#include "common/wrappers/string_view.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"
Expand All @@ -23,6 +25,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"
Expand Down Expand Up @@ -530,6 +533,7 @@ void compile_binary_func_op(VertexAdaptor<meta_op_binary> root, CodeGenerator &W
}

bool try_compile_append_inplace(VertexAdaptor<op_set_dot> root, CodeGenerator &W);
bool try_compile_set_by_index_of_mixed(VertexPtr root, CodeGenerator &W);

void compile_binary_op(VertexAdaptor<meta_op_binary> root, CodeGenerator &W) {
const auto &root_type_str = OpInfo::str(root->type());
Expand Down Expand Up @@ -594,6 +598,10 @@ void compile_binary_op(VertexAdaptor<meta_op_binary> 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};
Expand Down Expand Up @@ -848,6 +856,17 @@ void compile_func_call(VertexAdaptor<op_func_call> root, CodeGenerator &W, func_
W << "_tr_f.enter_branch(" << root->args()[0] << ")";
return;
}
if (root->str_val == "empty") {
if (auto index = root->args()[0].try_as<op_index>(); index && tinf::get_type(index->array())->get_real_ptype() == tp_mixed) {
W << "(" << index->array() << ")";
W << ".empty_at(" << index->key();
if (auto precomputed_hash = can_use_precomputed_hash_indexing_array(index->key())) {
W << ", " << precomputed_hash << "_i64";
}
W << ")";
return;
}
}
}

if (FFIRoot::is_ffi_scope_call(root)) {
Expand Down Expand Up @@ -1786,19 +1805,32 @@ bool try_compile_append_inplace(VertexAdaptor<op_set_dot> root, CodeGenerator &W
return false;
}

void compile_index_of_array(VertexAdaptor<op_index> root, CodeGenerator &W) {
void compile_index_of_array_or_mixed(VertexAdaptor<op_index> root, CodeGenerator &W) {
bool used_as_rval = root->rl_type != val_l;
if (!used_as_rval) {
kphp_assert(root->has_key());
W << root->array() << "[" << root->key() << "]";
vk::string_view pre{};
vk::string_view past{};
if (tinf::get_type(root->array())->ptype() == tp_mixed) {
pre = "static_cast<mixed&>(";
past = ")";
}
W << pre << root->array() << "[" << root->key() << "]" << past;
} else {
W << root->array() << ".get_value (" << root->key();
// if it's a const string key access like $a['somekey'],
// compute the 'somekey' string hash during the compile time and call array<T>::get_value(string, precomputed_hash)
if (auto precomputed_hash = can_use_precomputed_hash_indexing_array(root->key())) {
W << ", " << precomputed_hash << "_i64";
// TODO support precomputed hashes with macros below
if (tinf::get_type(root->array())->ptype() == tp_mixed && root->inside_isset) {
W << "MIXED_GET_IF_ISSET" << MacroBegin{} << root->array() << ", " << root->key() << MacroEnd{};
} else if (tinf::get_type(root->array())->ptype() == tp_mixed && root->inside_empty) {
W << "MIXED_GET_IF_NOT_EMPTY" << MacroBegin{} << root->array() << ", " << root->key() << MacroEnd{};
} else {
W << root->array() << ".get_value (" << root->key();
// if it's a const string key access like $a['somekey'],
// compute the 'somekey' string hash during the compile time and call array<T>::get_value(string, precomputed_hash)
if (auto precomputed_hash = can_use_precomputed_hash_indexing_array(root->key())) {
W << ", " << precomputed_hash << "_i64";
}
W << ")";
}
W << ")";
}
}

Expand Down Expand Up @@ -1860,7 +1892,7 @@ void compile_index(VertexAdaptor<op_index> 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);
}
}

Expand Down Expand Up @@ -2061,6 +2093,37 @@ 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<op_set>()) {
auto lhs = set->lhs();
auto rhs = set->rhs();
if (auto index = lhs.try_as<op_index>()) {
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 << MacroBegin{};
W << index->array() << ", ";
W << index->key() << ", ";

TmpExpr tmp_rhs(rhs);
W << tmp_rhs << ", ";
W << TypeName(tmp_rhs.get_type());
W << MacroEnd{};
} else {
W << "SET_MIXED_BY_INDEX";
W << MacroBegin{};
W << index->array() << ", ";
W << index->key() << ", ";
W << rhs;
W << MacroEnd{};
}
return true;
}
}
}
return false;
}

void compile_safe_version(VertexPtr root, CodeGenerator &W) {
if (auto set_value = root.try_as<op_set_value>()) {
TmpExpr key{set_value->key()};
Expand All @@ -2073,6 +2136,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<meta_op_binary>();
if (OpInfo::type(root->type()) == binary_func_op) {
W << "SAFE_SET_FUNC_OP " << MacroBegin{};
Expand Down Expand Up @@ -2107,11 +2173,13 @@ void compile_safe_version(VertexPtr root, CodeGenerator &W) {
} else if (auto index = root.try_as<op_index>()) {
kphp_assert (index->has_key());
TmpExpr key{index->key()};
W << "SAFE_INDEX " << MacroBegin{} <<
index->array() << ", " <<
key << ", " <<
TypeName(key.get_type()) <<
MacroEnd{};
vk::string_view pre{};
vk::string_view past{};
if (tinf::get_type(index->array())->ptype() == tp_mixed) {
pre = "static_cast<mixed&>(";
past = ")";
}
W << pre << "SAFE_INDEX " << MacroBegin{} << index->array() << ", " << key << ", " << TypeName(key.get_type()) << MacroEnd{} << past;
} else {
kphp_error (0, fmt_format("Safe version of [{}] is not supported", OpInfo::str(root->type())));
kphp_fail();
Expand Down Expand Up @@ -2384,6 +2452,26 @@ void compile_common_op(VertexPtr root, CodeGenerator &W) {
W << TypeName(tp) << alloc_function;
break;
}
case op_arr_acc_set_return: {
auto v = root.as<op_arr_acc_set_return>();
W << "ARR_ACC_SET_RETURN" << MacroBegin{} << v->obj() << ", " << v->offset() << ", " << v->value() << ", "
<< "f$" << v->set_method->name << MacroEnd{};
break;
}
case op_arr_acc_check_and_get: {
auto v = root.as<op_arr_acc_check_and_get>();

if (v->is_empty) {
W << "ARR_ACC_GET_IF_NOT_EMPTY";
} else {
W << "ARR_ACC_GET_IF_ISSET";
}

W << MacroBegin{} << v->obj() << ", " << v->offset() << ", "
<< "f$" << v->check_method->name << ", "
<< "f$" << v->get_method->name << MacroEnd{};
break;
}
default:
kphp_fail();
break;
Expand Down
1 change: 1 addition & 0 deletions compiler/compiler.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ prepend(KPHP_COMPILER_PIPES_SOURCES pipes/
file-to-tokens.cpp
filter-only-actually-used.cpp
final-check.cpp
array-access-transform.cpp
fix-returns.cpp
gen-tree-postprocess.cpp
generate-virtual-methods.cpp
Expand Down
2 changes: 2 additions & 0 deletions compiler/compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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/array-access-transform.h"
#include "compiler/pipes/fix-returns.h"
#include "compiler/pipes/gen-tree-postprocess.h"
#include "compiler/pipes/generate-virtual-methods.h"
Expand Down Expand Up @@ -287,6 +288,7 @@ bool compiler_execute(CompilerSettings *settings) {
>> PassC<CheckClassesPass>{}
>> PassC<CheckConversionsPass>{}
>> PassC<OptimizationPass>{}
>> PassC<ArrayAccessTransformPass>{}
>> PassC<FixReturnsPass>{}
>> PassC<CalcValRefPass>{}
>> PassC<CalcFuncDepPass>{}
Expand Down
1 change: 1 addition & 0 deletions compiler/data/class-data.h
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ class ClassData : public Lockable {
bool can_be_php_autoloaded{false};
bool is_immutable{false};
bool need_generated_stub{false};
bool is_required_interface{false};
std::atomic<SubtreeImmutableType> is_subtree_immutable{SubtreeImmutableType::not_visited};
std::atomic<bool> process_fields_ic_compatibility{false};
bool really_used{false};
Expand Down
1 change: 1 addition & 0 deletions compiler/gentree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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->is_required_interface = phpdoc && phpdoc->has_tag(PhpDocType::kphp_required) && class_type == ClassType::interface;
cur_class->location_line_num = line_num;

bool registered = G->register_class(cur_class);
Expand Down
5 changes: 5 additions & 0 deletions compiler/inferring/expr-node.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ class ExprNode : public Node {
expr_(expr) {
}

void copy_type_from(const TypeData *from) {
type_ = from;
recalc_state_ = recalc_st_waiting | recalc_bit_at_least_once;
}

void recalc(TypeInferer *inferer);

VertexPtr get_expr() const {
Expand Down
6 changes: 3 additions & 3 deletions compiler/inferring/primitive-type.h
Original file line number Diff line number Diff line change
@@ -1,11 +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 <string>

// Order is important. These values are compared with operator<
// in some passes (in type inferring, for example)
enum PrimitiveType {
tp_any,
tp_Null,
Expand Down
35 changes: 31 additions & 4 deletions compiler/inferring/type-data.cpp
Original file line number Diff line number Diff line change
@@ -1,27 +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 <string>
#include <vector>

#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<const TypeData *> primitive_types;
static std::vector<const TypeData *> array_types;

Expand Down Expand Up @@ -351,6 +353,17 @@ const TypeData *TypeData::const_read_at(const Key &key) const {
if (ptype() == tp_string) {
return get_type(tp_string);
}
if (ptype() == tp_Class) {
if (auto klass = class_type(); klass) {
ClassPtr aa = G->get_class("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 {} does not implement \\ArrayAccess", klass->name));
}
}
if (!structured()) {
return get_type(tp_any);
}
Expand Down Expand Up @@ -418,6 +431,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) {
if (lhs->get_write_flag()) {
// `ArrayAccess::offsetSet()` case
// It means that lhs is something like that `$a[*] = foo()`
new_ptype = tp_Class;
}
}
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));
Expand Down Expand Up @@ -530,6 +550,13 @@ 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();
}

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
this->set_ptype(tp_Error);
Expand Down
1 change: 1 addition & 0 deletions compiler/inferring/type-data.h
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ class TypeData {
bool use_optional() const { return use_or_false() || use_or_null(); }

void set_write_flag() { set_flag<write_flag_e>(); }
bool get_write_flag() { return get_flag<write_flag_e>(); }

bool error_flag() const { return ptype_ == tp_Error; }

Expand Down
Loading

0 comments on commit f4ef4e8

Please sign in to comment.