Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support ArrayAccess #1116

Draft
wants to merge 44 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
fc8b6f3
Initial experiments
mkornaukhov03 Oct 11, 2024
4bf010e
logs for [.] = ... type inference
mkornaukhov03 Oct 11, 2024
7c5fee9
works with write, but read not found function
mkornaukhov03 Oct 16, 2024
de6a34a
tmp
mkornaukhov03 Oct 16, 2024
7885e13
tinf::get_type instead of node->get_type()
mkornaukhov03 Oct 17, 2024
4612a5c
cleanup
mkornaukhov03 Oct 17, 2024
015ff3b
add new tag for internal interfaces
mkornaukhov03 Oct 17, 2024
d35cebf
support type erasure to ArrayAccess interface
mkornaukhov03 Oct 22, 2024
1cc4c33
add comments and improve diagnostics
mkornaukhov03 Oct 22, 2024
82d0223
small improvements
mkornaukhov03 Oct 22, 2024
fd64900
add first test
mkornaukhov03 Oct 22, 2024
b8abfb1
fixup tests
mkornaukhov03 Oct 22, 2024
4379faa
support append
mkornaukhov03 Oct 22, 2024
7633beb
add some tests
mkornaukhov03 Oct 22, 2024
e1d7f00
better tests
mkornaukhov03 Oct 22, 2024
e592e60
rename var in smelling code inside _functions.txt
mkornaukhov03 Oct 22, 2024
959caaa
add important comment that explains why there is no ub with inheritan…
mkornaukhov03 Oct 23, 2024
10881d5
support ArrayAccess in mixed [.]
mkornaukhov03 Oct 24, 2024
8ff1e55
add test and found a bug with not linking redifinitions
mkornaukhov03 Oct 24, 2024
fc7ec2d
fix linking error
mkornaukhov03 Oct 25, 2024
ca60961
add negative tests and small fixes
mkornaukhov03 Oct 25, 2024
917e900
add useful comments and get rid of useless comments
mkornaukhov03 Oct 28, 2024
2775dfe
support basic chaining assignment for ArrayAccess
mkornaukhov03 Oct 30, 2024
ff6efc4
fixup test
mkornaukhov03 Oct 30, 2024
01ace14
fixup for append case
mkornaukhov03 Oct 30, 2024
31df49a
support better chaining assignment
mkornaukhov03 Oct 31, 2024
6a58749
get rid of shitty code in _functions.txt
mkornaukhov03 Oct 31, 2024
7521e4a
get rid of weak symbols and hack with dynamic linkage
mkornaukhov03 Oct 31, 2024
a741d83
internal interfaces methods are not inlined
mkornaukhov03 Oct 31, 2024
7cfbf86
implementation of isset(), unset(), empty() for ArrayAccess objects only
mkornaukhov03 Nov 1, 2024
e4cef17
implementation of isset(), unset() for objects inside mixed
mkornaukhov03 Nov 1, 2024
a8723a8
tests for isset(), unset()
mkornaukhov03 Nov 1, 2024
3a5c36c
implement empty() for mixed array acccess and add test
mkornaukhov03 Nov 5, 2024
f3bf348
fix constness for empty_on()
mkornaukhov03 Nov 5, 2024
a52589a
get rid of 'dangerous ub' warning
mkornaukhov03 Nov 6, 2024
2d12923
get rid of if(true) and enhance a test
mkornaukhov03 Nov 6, 2024
8124818
remove useless comments and improve useful comments and naming
mkornaukhov03 Nov 6, 2024
7d6a0d4
cleanup mixed files
mkornaukhov03 Nov 6, 2024
8fcfd6a
improve mixed files
mkornaukhov03 Nov 7, 2024
ab0342d
fix mixed::empty_on()
mkornaukhov03 Nov 7, 2024
edc386e
fix inferring and add some comments
mkornaukhov03 Nov 7, 2024
ce12cdc
remove extra todo, smart cast with index access is not supported
mkornaukhov03 Nov 7, 2024
46115b2
simplify filter-only-actually-used pass
mkornaukhov03 Nov 7, 2024
d0f4f5a
small cleanup
mkornaukhov03 Nov 7, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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-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);
}
69 changes: 67 additions & 2 deletions compiler/code-gen/vertex-compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand Down Expand Up @@ -530,6 +532,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 +597,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 @@ -850,6 +857,16 @@ void compile_func_call(VertexAdaptor<op_func_call> root, CodeGenerator &W, func_
}
}

if (root->func_id->is_extern() && root->str_val == "empty") {
if (auto index = root->args()[0].try_as<op_index>()) {
if (tinf::get_type(index->array())->get_real_ptype() == tp_mixed) {
W << "(" << index->array() << ")";
W << ".empty_on(" << index->key() << ")"; // TODO implement optimization with precomputed hash (can_use_precomputed_hash_indexing_array)
return;
}
}
}

if (FFIRoot::is_ffi_scope_call(root)) {
compile_ffi_func_call(root, W);
return;
Expand Down Expand Up @@ -1786,7 +1803,7 @@ 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());
Expand Down Expand Up @@ -1860,7 +1877,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 +2078,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<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 << 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<op_set_value>()) {
TmpExpr key{set_value->key()};
Expand All @@ -2073,6 +2117,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 @@ -2384,6 +2431,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<op_set_with_ret>();
/*
{
mixed idx = index;
mixed val = value;
func(obj, idx, val);
val;
}
*/
W << "SET_ARR_ACC_BY_INDEX(";
W << xxx->obj() << ", ";
W << xxx->offset() << ", ";
W << xxx->value() << ", ";
W << "f$" << xxx->set_method->name;
W << ")";
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
fix-array-access.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/fix-array-access.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<FixArrayAccessPass>{}
>> 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 internal_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->internal_interface = phpdoc && phpdoc->has_tag(PhpDocType::kphp_internal_interface);
cur_class->location_line_num = line_num;

bool registered = G->register_class(cur_class);
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 {} that 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
3 changes: 2 additions & 1 deletion compiler/phpdoc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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),
Expand Down
1 change: 1 addition & 0 deletions compiler/phpdoc.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion compiler/pipes/check-ub.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
6 changes: 4 additions & 2 deletions compiler/pipes/filter-only-actually-used.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -245,7 +246,8 @@ IdMap<FunctionPtr> calc_actually_used_having_call_edges(std::vector<FunctionAndE
(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) ||
(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);
}
Expand Down Expand Up @@ -279,7 +281,7 @@ void remove_unused_class_methods(const std::vector<FunctionAndEdges> &all, const
return get_index(m.function) == -1 || !used_functions[m.function];
});
fun->class_id->members.remove_if(
[&used_functions](const ClassMemberInstanceMethod &m) {
[&used_functions, class_id=fun->class_id](const ClassMemberInstanceMethod &m) {
return get_index(m.function) == -1 || !used_functions[m.function];
});
}
Expand Down
Loading
Loading