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

[compiler] add basic support for type-checked mixed union types #734

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 6 additions & 1 deletion compiler/inferring/node-recalc.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,12 @@ void NodeRecalc::set_lca_at(const MultiKey *key, const RValue &rvalue) {
key = &MultiKey::any_key(0);
}

new_type_->set_lca_at(*key, type, !rvalue.drop_or_false, !rvalue.drop_or_null, rvalue.ffi_flags);
TypeData::LCAFlags lca_flags;
lca_flags.save_or_false = !rvalue.drop_or_false;
lca_flags.save_or_null = !rvalue.drop_or_null;
lca_flags.ffi_drop_ref = rvalue.ffi_drop_ref;
lca_flags.ffi_take_addr = rvalue.ffi_take_addr;
new_type_->set_lca_at(*key, type, lca_flags);

if (unlikely(new_type_->error_flag())) {
on_new_type_became_tpError(type, rvalue);
Expand Down
7 changes: 4 additions & 3 deletions compiler/inferring/rvalue.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,21 @@ struct RValue {
const MultiKey *key{nullptr};
bool drop_or_false{false};
bool drop_or_null{false};
TypeData::FFIRvalueFlags ffi_flags;
bool ffi_drop_ref{false};
bool ffi_take_addr{false};
};

// take &T cdata type and return it as T;
// for non-cdata types it has no effect
inline RValue ffi_rvalue_drop_ref(RValue rvalue) {
rvalue.ffi_flags.drop_ref = true;
rvalue.ffi_drop_ref = true;
return rvalue;
}

// take T cdata type and return it as *T;
// for non-cdata types it has no effect
inline RValue ffi_rvalue_take_addr(RValue rvalue) {
rvalue.ffi_flags.take_addr = true;
rvalue.ffi_take_addr = true;
return rvalue;
}

Expand Down
140 changes: 127 additions & 13 deletions compiler/inferring/type-data.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

static std::vector<const TypeData *> primitive_types;
static std::vector<const TypeData *> array_types;
static TypeData *foreach_key_type;

void TypeData::init_static() {
if (!primitive_types.empty()) {
Expand All @@ -39,6 +40,17 @@ void TypeData::init_static() {
for (int tp = 0; tp < ptype_size; tp++) {
array_types[tp] = create_array_of(primitive_types[tp]);
}

// create this TypeData object once and use it for all foreach keys;
// foreach key is encoded as string|int mixed type
foreach_key_type = new TypeData(tp_mixed);
foreach_key_type->flags_ = restricted_mixed_flag_e |
restricted_mixed_string_flag_e |
restricted_mixed_int_flag_e;
}

const TypeData *TypeData::get_foreach_key_type() {
return foreach_key_type;
}

const TypeData *TypeData::get_type(PrimitiveType type) {
Expand Down Expand Up @@ -142,16 +154,41 @@ std::string TypeData::as_human_readable(bool colored) const {
}
break;
}
case tp_mixed:
if (is_restricted_mixed()) {
const PrimitiveType possible_elements[] = {
tp_int,
tp_float,
tp_string,
tp_array,
tp_bool,
};
for (auto ptype : possible_elements) {
if (!restricted_mixed_contains(ptype)) {
continue;
}
if (res.empty()) {
res = ptype_name(ptype);
} else {
res += "|" + std::string{ptype_name(ptype)};
}
}
} else {
res = ptype_name(ptype_);
}
break;
default:
res = ptype_name(ptype_);
}

if (ptype_ != tp_any) {
if (use_or_null() && !use_or_false()) {
bool print_null_type = use_or_null() || (is_restricted_mixed() && or_null_flag());
bool print_false_type = use_or_false() || (is_restricted_mixed() && or_false_flag());
if (print_null_type && !print_false_type) {
res = "?" + res;
} else if (use_or_false() && !use_or_null()) {
} else if (print_false_type && !print_null_type) {
res += "|false";
} else if (use_or_false() && use_or_null()) {
} else if (print_false_type && print_null_type) {
res += "|false|null";
}
}
Expand Down Expand Up @@ -188,6 +225,55 @@ PrimitiveType TypeData::get_real_ptype() const {
return p;
}

bool TypeData::restricted_mixed_contains(const TypeData *rhs) const {
if (rhs->ptype() == tp_mixed && !rhs->is_restricted_mixed()) {
return true; // allow any unrestricted mixed assignment for now
}
if (rhs->or_null_flag() && !or_null_flag()) {
return false;
}
if (rhs->or_false_flag() && !(or_false_flag() || restricted_mixed_contains(tp_bool))) {
return false;
}
if (rhs->ptype() == tp_int && restricted_mixed_contains(tp_float)) {
return true;
}
if (ptype_mixed_flag(rhs->ptype()) != 0 && !restricted_mixed_contains(rhs->ptype())) {
return false;
}
return true;
}

uint16_t TypeData::restricted_mixed_types_mask() const {
return flags_ & restricted_mixed_flags_mask();
}

uint16_t TypeData::restricted_mixed_flags_mask() {
return restricted_mixed_flag_e |
restricted_mixed_int_flag_e |
restricted_mixed_float_flag_e |
restricted_mixed_string_flag_e |
restricted_mixed_array_flag_e |
restricted_mixed_bool_flag_e;
}

uint16_t TypeData::ptype_mixed_flag(PrimitiveType ptype) {
switch (ptype) {
case tp_int:
return restricted_mixed_int_flag_e;
case tp_float:
return restricted_mixed_float_flag_e;
case tp_string:
return restricted_mixed_string_flag_e;
case tp_array:
return restricted_mixed_array_flag_e;
case tp_bool:
return restricted_mixed_bool_flag_e;
default:
return 0;
}
}

bool TypeData::is_ffi_ref() const {
auto klass = class_type();
if (klass && klass->ffi_class_mixin) {
Expand Down Expand Up @@ -320,7 +406,7 @@ bool TypeData::is_primitive_type() const {
return vk::any_of_equal(get_real_ptype(), tp_int, tp_bool, tp_float, tp_future, tp_future_queue);
}

void TypeData::set_flags(uint8_t new_flags) {
void TypeData::set_flags(uint16_t new_flags) {
kphp_assert_msg((flags_ & new_flags) == flags_, "It is forbidden to remove flag");
flags_ = new_flags;
}
Expand Down Expand Up @@ -411,14 +497,31 @@ const TypeData *TypeData::get_deepest_type_of_array() const {
return this;
}

void TypeData::set_lca(const TypeData *rhs, bool save_or_false, bool save_or_null, FFIRvalueFlags ffi_flags) {
void TypeData::set_lca(const TypeData *rhs, LCAFlags flags) {
if (rhs == nullptr) {
return;
}
TypeData *lhs = this;

auto new_flags = rhs->flags_;
PrimitiveType new_ptype = type_lca(lhs->ptype(), rhs->ptype());
if (lhs->is_restricted_mixed()) {
if (flags.phpdoc) {
// still constructing a type from a phpdoc context
new_flags |= ptype_mixed_flag(rhs->ptype());
} else {
// checking a type compatibility
if (!lhs->restricted_mixed_contains(rhs)) {
new_ptype = tp_Error;
}
}
}
if (new_ptype == tp_mixed) {
if (flags.phpdoc && lhs->ptype() != tp_mixed && rhs->ptype() != tp_mixed) {
new_flags |= restricted_mixed_flag_e;
new_flags |= ptype_mixed_flag(lhs->ptype());
new_flags |= ptype_mixed_flag(rhs->ptype());
}
if (lhs->ptype() == tp_array && lhs->lookup_at_any_key()) {
lhs->set_lca_at(MultiKey::any_key(1), TypeData::get_type(tp_mixed));
if (lhs->ptype() == tp_Error) {
Expand All @@ -435,24 +538,29 @@ void TypeData::set_lca(const TypeData *rhs, bool save_or_false, bool save_or_nul
}

lhs->set_ptype(new_ptype);
uint8_t new_flags = rhs->flags_;
if (!save_or_false) {
if (!flags.save_or_false) {
new_flags &= ~(or_false_flag_e);
}
if (!save_or_null) {
if (!flags.save_or_null) {
new_flags &= ~(or_null_flag_e);
}
if (!flags.phpdoc) {
// only phpdoc types can have restricted mixed types;
// when assigning to a local variable, etc. we wash all these flags away
// note that we may allow these types globally at some point
// (it would require the changes to cfg pass so the casts work correctly)
new_flags &= ~restricted_mixed_flags_mask();
}
new_flags |= lhs->flags_;

lhs->set_flags(new_flags);

if (ffi_flags.drop_ref && rhs->is_ffi_ref()) {
if (flags.ffi_drop_ref && rhs->is_ffi_ref()) {
auto *new_rhs = rhs->clone();
new_rhs->class_type_ = {rhs->class_type()->ffi_class_mixin->non_ref};
rhs = new_rhs;
}
int rhs_indirection = rhs->get_indirection();
if (ffi_flags.take_addr) {
if (flags.ffi_take_addr) {
rhs_indirection++;
}

Expand Down Expand Up @@ -509,7 +617,7 @@ void TypeData::set_lca(const TypeData *rhs, bool save_or_false, bool save_or_nul
}
}

void TypeData::set_lca_at(const MultiKey &multi_key, const TypeData *rhs, bool save_or_false, bool save_or_null, FFIRvalueFlags ffi_flags) {
void TypeData::set_lca_at(const MultiKey &multi_key, const TypeData *rhs, LCAFlags flags) {
TypeData *cur = this;

for (const Key &key : multi_key) {
Expand All @@ -530,7 +638,7 @@ 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);
cur->set_lca(rhs, flags);
if (cur->error_flag()) { // proxy tp_Error from keys to the type itself
this->set_ptype(tp_Error);
}
Expand Down Expand Up @@ -902,6 +1010,12 @@ bool is_less_or_equal_type(const TypeData *given, const TypeData *expected, cons
}
break;
case tp_mixed:
if (expected->is_restricted_mixed()) {
if (given->is_restricted_mixed()) {
return expected->restricted_mixed_types_mask() == given->restricted_mixed_types_mask();
}
return expected->restricted_mixed_contains(given);
}
if (vk::any_of_equal(tp, tp_bool, tp_int, tp_float, tp_string, tp_mixed)) {
return true;
}
Expand Down
75 changes: 53 additions & 22 deletions compiler/inferring/type-data.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,39 +17,62 @@
#include "compiler/stage.h"
#include "compiler/threading/tls.h"


class TypeData {
DEBUG_STRING_METHOD { return as_human_readable(false); }

private:
enum flag_id_t : uint8_t {
write_flag_e = 0b00000001,
or_null_flag_e = 0b00000010,
or_false_flag_e = 0b00000100,
shape_has_varg_flag_e = 0b00001000,
ffi_const_flag_e = 0b00010000,
tuple_as_array_flag_e = 0b00100000,
enum flag_id_t : uint16_t {
write_flag_e = 1 << 0,
or_null_flag_e = 1 << 1,
or_false_flag_e = 1 << 2,
shape_has_varg_flag_e = 1 << 3,
ffi_const_flag_e = 1 << 4,
tuple_as_array_flag_e = 1 << 5,

// the flags in the following group encode a restricted union type variants;
// int|string would have restricted_mixed_int_flag_e and restricted_mixed_string_flag_e
// flags set to 1 as well as restricted_mixed_flag_e
// too complex types like (?int)|string may still decay to mixed, but we can
// express ?(int|string) instead by using the two variant flags along with or_null_flag_e
restricted_mixed_flag_e = 1 << 6, // whether tp_mixed type should be considered to be a ptype union (default is false)
restricted_mixed_int_flag_e = 1 << 7, // if restricted_mixed_flag_e is 1, this flag tells if mixed ptype union contains an int type
restricted_mixed_float_flag_e = 1 << 8, // if restricted_mixed_flag_e is 1, this flag tells if mixed ptype union contains a float type
restricted_mixed_string_flag_e = 1 << 9, // if restricted_mixed_flag_e is 1, this flag tells if mixed ptype union contains a string type
restricted_mixed_array_flag_e = 1 << 10, // if restricted_mixed_flag_e is 1, this flag tells if mixed ptype union contains an array type
restricted_mixed_bool_flag_e = 1 << 11, // if restricted_mixed_flag_e is 1, this flag tells if mixed ptype union contains an bool type

// 4 bits are unused
};

public:
using SubkeyItem = std::pair<Key, TypeData *>;
using lookup_iterator = std::forward_list<SubkeyItem>::const_iterator;

// TODO: move all flags (drop_false, drop_null) here and rename this struct?
// passing several booleans as set_lca args is clumsy
struct FFIRvalueFlags {
bool drop_ref: 1;
bool take_addr: 1;

FFIRvalueFlags()
: drop_ref{false}
, take_addr{false} {}
struct LCAFlags {
bool save_or_false: 1;
bool save_or_null: 1;
bool ffi_drop_ref: 1;
bool ffi_take_addr: 1;
bool phpdoc: 1;

LCAFlags()
: save_or_false{true}
, save_or_null{true}
, ffi_drop_ref{false}
, ffi_take_addr{false}
, phpdoc {false} {}

static LCAFlags for_phpdoc() {
LCAFlags flags;
flags.phpdoc = true;
return flags;
}
};

private:

PrimitiveType ptype_ : 8; // current type (int/array/etc); tp_any for uninited, tp_Error if error
uint8_t flags_{0}; // a binary mask of flag_id_t
uint16_t flags_{0}; // a binary mask of flag_id_t
uint8_t indirection_{0}; // ptr levels for FFI pointers

// current class for tp_Class (but during inferring it could contain many classes due to multiple implements)
Expand Down Expand Up @@ -97,14 +120,21 @@ class TypeData {
ClassPtr get_first_class_type_inside() const;
bool is_primitive_type() const;

uint8_t flags() const { return flags_; }
void set_flags(uint8_t new_flags);
uint16_t flags() const { return flags_; }
void set_flags(uint16_t new_flags);

bool is_ffi_ref() const;

bool ffi_const_flag() const { return get_flag<ffi_const_flag_e>(); }
void set_ffi_const_flag() { set_flag<ffi_const_flag_e>(); }

bool is_restricted_mixed() const { return get_flag<restricted_mixed_flag_e>(); }
bool restricted_mixed_contains(PrimitiveType ptype) const { return (flags_ & ptype_mixed_flag(ptype)) != 0; }
bool restricted_mixed_contains(const TypeData *other) const;
uint16_t restricted_mixed_types_mask() const;
static uint16_t restricted_mixed_flags_mask();
static uint16_t ptype_mixed_flag(PrimitiveType ptype);

bool or_false_flag() const { return get_flag<or_false_flag_e>(); }
void set_or_false_flag() { set_flag<or_false_flag_e>(); }
bool use_or_false() const { return or_false_flag() && !::can_store_false(ptype_); }
Expand Down Expand Up @@ -151,8 +181,8 @@ class TypeData {
}

const TypeData *const_read_at(const MultiKey &multi_key) const;
void set_lca(const TypeData *rhs, bool save_or_false = true, bool save_or_null = true, FFIRvalueFlags ffi_flags = {});
void set_lca_at(const MultiKey &multi_key, const TypeData *rhs, bool save_or_false = true, bool save_or_null = true, FFIRvalueFlags ffi_flags = {});
void set_lca(const TypeData *rhs, LCAFlags flags = {});
void set_lca_at(const MultiKey &multi_key, const TypeData *rhs, LCAFlags flags = {});
void set_lca(PrimitiveType ptype);
void fix_inf_array();

Expand All @@ -164,6 +194,7 @@ class TypeData {
static void init_static();
static const TypeData *get_type(PrimitiveType type);
static const TypeData *get_type(PrimitiveType array, PrimitiveType type);
static const TypeData *get_foreach_key_type();
static const TypeData *create_for_class(ClassPtr klass);
static const TypeData *create_array_of(const TypeData *element_type);
};
Expand Down
Loading