Skip to content
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
36 changes: 31 additions & 5 deletions compiler/code-gen/vertex-compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -797,7 +797,7 @@ void compile_switch_str(VertexAdaptor<op_switch> root, CodeGenerator &W) {
std::transform(cases_vertices.begin(), cases_vertices.end(), cases.begin(), [](auto v) { return CaseInfo(v); });

auto default_case_it = std::find_if(cases.begin(), cases.end(), vk::make_field_getter(&CaseInfo::is_default));
auto default_case = default_case_it != cases.end() ? &(*default_case_it) : nullptr;
auto *default_case = default_case_it != cases.end() ? &(*default_case_it) : nullptr;

int n = static_cast<int>(cases.size());
std::map<unsigned int, int> hash_to_last_id;
Expand All @@ -817,7 +817,7 @@ void compile_switch_str(VertexAdaptor<op_switch> root, CodeGenerator &W) {
cases[i].next = &cases[next_i];
}

auto next = cases[i].next;
auto *next = cases[i].next;
if (next && next->goto_name.empty()) {
next->goto_name = gen_unique_name("switch_goto");
}
Expand Down Expand Up @@ -885,7 +885,7 @@ void compile_switch_int(VertexAdaptor<op_switch> root, CodeGenerator &W) {
if (val->type() == op_int_const) {
const string &str = val.as<op_int_const>()->str_val;
W << str;
kphp_error(used.insert(str).second, fmt_format("Switch: repeated cases found [{}]", str));
kphp_error(used.insert(str).second, fmt_format("Repeated case [{}] in switch/match", str));
} else {
kphp_assert(is_const_int(val));
W << val;
Expand All @@ -903,6 +903,9 @@ void compile_switch_int(VertexAdaptor<op_switch> root, CodeGenerator &W) {


void compile_switch_var(VertexAdaptor<op_switch> root, CodeGenerator &W) {
const auto strict_comparison = root->is_match;
const auto *string_comparison_func = strict_comparison ? "equals" : "eq2";

string goto_name_if_default_in_the_middle;

auto temp_var_condition_on_switch = root->condition_on_switch();
Expand All @@ -916,7 +919,7 @@ void compile_switch_var(VertexAdaptor<op_switch> root, CodeGenerator &W) {
VertexAdaptor<op_seq> cmd;
if (auto cs = one_case.try_as<op_case>()) {
cmd = cs->cmd();
W << "if (" << temp_var_matched_with_a_case << " || eq2(" << temp_var_condition_on_switch << ", " << cs->expr() << "))" << BEGIN;
W << "if (" << temp_var_matched_with_a_case << " || " << string_comparison_func << "(" << temp_var_condition_on_switch << ", " << cs->expr() << "))" << BEGIN;
W << temp_var_matched_with_a_case << " = true;" << NL;
} else {
cmd = one_case.as<op_default>()->cmd();
Expand Down Expand Up @@ -952,7 +955,7 @@ void compile_switch(VertexAdaptor<op_switch> root, CodeGenerator &W) {

for (auto one_case : root->cases()) {
if (one_case->type() == op_default) {
kphp_error_return(!has_default, "Switch: several `default` cases found");
kphp_error_return(!has_default, "Switch statements may only contain one `default` clause");
has_default = true;
continue;
}
Expand All @@ -962,6 +965,29 @@ void compile_switch(VertexAdaptor<op_switch> root, CodeGenerator &W) {
all_cases_are_strings &= (val->type() == op_string);
}

if (root->is_match) {
// Since match uses strict comparisons, we need to additionally
// check that the type of the expression in the condition matches
// string or int in order to generate an optimized version.
const auto *condition_type = tinf::get_type(root->condition());
if (!condition_type) {
compile_switch_var(root, W);
return;
}

const auto condition_is_string = condition_type->get_real_ptype() == tp_string;
const auto condition_is_int = condition_type->get_real_ptype() == tp_int;

if (all_cases_are_strings && condition_is_string) {
compile_switch_str(root, W);
} else if (all_cases_are_ints && condition_is_int) {
compile_switch_int(root, W);
} else {
compile_switch_var(root, W);
}
return;
}

if (all_cases_are_strings) {
compile_switch_str(root, W);
} else if (all_cases_are_ints) {
Expand Down
1 change: 1 addition & 0 deletions compiler/debug.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ std::string debugTokenName(TokenType t) {
{tok_as, "tok_as"},
{tok_case, "tok_case"},
{tok_switch, "tok_switch"},
{tok_match, "tok_match"},
{tok_class, "tok_class"},
{tok_interface, "tok_interface"},
{tok_trait, "tok_trait"},
Expand Down
111 changes: 111 additions & 0 deletions compiler/gentree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -660,6 +660,9 @@ VertexPtr GenTree::get_expr_top(bool was_arrow) {
case tok_shape:
res = get_shape();
break;
case tok_match:
res = get_match();
break;
case tok_opbrk:
res = get_short_array();
return_flag = false; // array is valid postfix expression operand
Expand Down Expand Up @@ -1315,6 +1318,114 @@ VertexAdaptor<op_switch> GenTree::get_switch() {
return create_switch_vertex(cur_function, switch_condition, std::move(cases)).set_location(location);
}

VertexAdaptor<op_match> GenTree::create_match_vertex(FunctionPtr cur_function, VertexPtr match_condition, std::vector<VertexPtr> &&cases) {
auto temp_var_condition_on_switch = create_superlocal_var("condition_on_match", cur_function);
auto temp_var_matched_with_one_case = create_superlocal_var("matched_with_one_case", cur_function);
return VertexAdaptor<op_match>::create(match_condition, temp_var_condition_on_switch, temp_var_matched_with_one_case, std::move(cases));
}

VertexAdaptor<op_match_default> GenTree::get_match_default() {
auto location = auto_location();
next_cur();
// 'default, => $return'
// | comma allowed
if (cur->type() == tok_comma) {
next_cur();
}
CE (expect(tok_double_arrow, "'=>'"));

const auto return_expr = get_expression();
if (cur->type() == tok_comma) {
next_cur();
}

return VertexAdaptor<op_match_default>::create(return_expr).set_location(location);
}

VertexPtr GenTree::get_match_case() {
auto location = auto_location();

if (cur->type() == tok_default) {
return get_match_default();
}

std::vector<VertexPtr> conditions;

while (cur->type() != tok_double_arrow) {
const auto cur_type = cur->type();
// '$expr, => $return'
// | comma allowed
if (cur_type == tok_comma) {
next_cur();
continue;
}

const auto condition = get_expression();
if (!condition) {
kphp_error(0, "Could not parse expression");
}

if (const auto &double_arrow = condition.try_as<op_double_arrow>()) {
// if simple $expr => $return_expr
if (conditions.empty()) {
const auto return_expr = double_arrow->value();
const auto conditions_vertex = VertexAdaptor<op_seq_comma>::create(std::vector<VertexPtr>{double_arrow->key()});

return VertexAdaptor<op_match_case>::create(conditions_vertex, return_expr).set_location(location);
}

// if several conditions
// add last condition
conditions.push_back(double_arrow->key());

const auto return_expr = double_arrow->value();
const auto conditions_vertex = VertexAdaptor<op_seq_comma>::create(conditions);

return VertexAdaptor<op_match_case>::create(conditions_vertex, return_expr).set_location(location);
}

conditions.push_back(condition);
}
CE (expect(tok_double_arrow, "'=>'"));
// case " => 100"
if (conditions.empty()) {
kphp_error(0, "Expected expression before token '=>'");
}

const auto return_expr = get_expression();
const auto conditions_vertex = VertexAdaptor<op_seq_comma>::create(conditions);

return VertexAdaptor<op_match_case>::create(conditions_vertex, return_expr).set_location(location);
}

VertexAdaptor<op_match> GenTree::get_match() {
auto location = auto_location();

next_cur();
CE (expect(tok_oppar, "'('"));
skip_phpdoc_tokens();
auto match_condition = get_expression();
CE (!kphp_error(match_condition, "Failed to parse 'match' expression"));
CE (expect(tok_clpar, "')'"));

CE (expect(tok_opbrc, "'{'"));

vector<VertexPtr> cases;
while (cur->type() != tok_clbrc) {
skip_phpdoc_tokens();
auto cur_type = cur->type();
if (cur_type == tok_comma) {
next_cur();
continue;
}

cases.emplace_back(get_match_case());
}
CE (expect(tok_clbrc, "'}'"));

return create_match_vertex(cur_function, match_condition, std::move(cases)).set_location(location);
}

VertexAdaptor<op_shape> GenTree::get_shape() {
auto location = auto_location();

Expand Down
12 changes: 12 additions & 0 deletions compiler/gentree.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class GenTree {
VertexAdaptor<op_var> create_superlocal_var(const std::string& name_prefix);
static VertexAdaptor<op_var> create_superlocal_var(const std::string& name_prefix, FunctionPtr cur_function);
static VertexAdaptor<op_switch> create_switch_vertex(FunctionPtr cur_function, VertexPtr switch_condition, std::vector<VertexPtr> &&cases);
static VertexAdaptor<op_match> create_match_vertex(FunctionPtr cur_function, VertexPtr match_condition, std::vector<VertexPtr> &&cases);

static bool is_superglobal(const string &s);
static bool is_magic_method_name_allowed(const std::string &name);
Expand Down Expand Up @@ -74,6 +75,12 @@ class GenTree {
return int_v;
}

static auto create_string_const(const std::string &str) {
auto string_vertex = VertexAdaptor<op_string>::create();
string_vertex->str_val = str;
return string_vertex;
}

VertexAdaptor<op_func_param> get_func_param();
VertexAdaptor<op_var> get_var_name();
VertexAdaptor<op_var> get_var_name_ref();
Expand Down Expand Up @@ -120,7 +127,12 @@ class GenTree {
VertexAdaptor<op_if> get_if();
VertexAdaptor<op_for> get_for();
VertexAdaptor<op_do> get_do();

VertexAdaptor<op_switch> get_switch();
VertexAdaptor<op_match_default> get_match_default();
VertexPtr get_match_case();
VertexAdaptor<op_match> get_match();

VertexAdaptor<op_shape> get_shape();
VertexPtr get_phpdoc_inside_function();
bool parse_function_uses(std::vector<VertexAdaptor<op_func_param>> *uses_of_lambda);
Expand Down
1 change: 1 addition & 0 deletions compiler/keywords.gperf
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Array, tok_array
as, tok_as
case, tok_case
switch, tok_switch
match, tok_match
class, tok_class
interface, tok_interface
trait, tok_trait
Expand Down
102 changes: 102 additions & 0 deletions compiler/pipes/gen-tree-postprocess.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "compiler/data/lib-data.h"
#include "compiler/data/src-file.h"
#include "compiler/gentree.h"
#include "compiler/name-gen.h"

namespace {
template <typename F>
Expand Down Expand Up @@ -256,9 +257,110 @@ VertexPtr GenTreePostprocessPass::on_exit_vertex(VertexPtr root) {
return convert_array_with_spread_operators(array);
}

if (auto match = root.try_as<op_match>()) {
return convert_match_to_switch(match);
}

return root;
}

VertexAdaptor<op_switch> create_switch_vertex_from_match(VertexAdaptor<op_match> match, VertexAdaptor<op_var> local_var) {
const auto switch_condition = match->condition();
const auto temp_var_condition_on_switch = match->condition_on_match();
const auto temp_var_matched_with_one_case = match->matched_with_one_case();
const auto cases = match->cases();

std::vector<VertexPtr> switch_cases;
switch_cases.reserve(cases.size());

VertexAdaptor<op_match_default> default_case;

for (const auto &case_vertex : cases) {
if (const auto &default_case_vertex = case_vertex.try_as<op_match_default>()) {
if (default_case) {
kphp_error(0, "Match expressions may only contain one default arm");
}
default_case = default_case_vertex;
continue;
}
if (const auto &match_case = case_vertex.try_as<op_match_case>()) {
const auto set_vertex = VertexAdaptor<op_set>::create(local_var, match_case->return_expr());
const auto break_vertex = VertexAdaptor<op_break>::create(GenTree::create_int_const(1));
const auto stmts = std::vector<VertexPtr>{set_vertex, break_vertex};
const auto seq = VertexAdaptor<op_seq>::create(stmts).set_location(match_case);
const auto empty_seq = VertexAdaptor<op_seq>::create().set_location(match_case);

const auto conditions = match_case->conditions()->args();
const auto condition_count = conditions.size();

// We create a case body only for the last case for several conditions, so as not to duplicate the code.
// For example:
// $a = match($b) { 10, 20 => 1 };
// Converts to something like this switch:
// switch ($b) {
// case 10: {} // fallthrough
// case 20: { $a = 1; break; }
// }
auto current_condition = 0;
for (const auto &condition : match_case->conditions()->args()) {
if (current_condition == condition_count - 1) {
switch_cases.emplace_back(VertexAdaptor<op_case>::create(condition, seq));
continue;
}

switch_cases.emplace_back(VertexAdaptor<op_case>::create(condition, empty_seq));
++current_condition;
}
}
}

if (default_case) {
const auto set_vertex = VertexAdaptor<op_set>::create(local_var, default_case->return_expr());
const auto break_vertex = VertexAdaptor<op_break>::create(GenTree::create_int_const(1));
const auto stmts = std::vector<VertexPtr>{set_vertex, break_vertex};
const auto seq = VertexAdaptor<op_seq>::create(stmts).set_location(default_case);

switch_cases.emplace_back(VertexAdaptor<op_default>::create(seq));
} else {
// If there is no default case, then create a default one,
// which will give warning to any unhandled value.
//
// PHP throws an UnhandledMatchError if the value is unhandled,
// but we only give warning.
auto print_r = VertexAdaptor<op_func_call>::create(std::vector<VertexPtr>{
match->condition().clone(),
VertexAdaptor<op_true>::create()
});
print_r->set_string("print_r");
const auto message = VertexAdaptor<op_string_build>::create(std::vector<VertexPtr>{
GenTree::create_string_const("Unhandled match value '"),
print_r,
GenTree::create_string_const("'")
});
const auto args = std::vector<VertexPtr>{message};
auto call_function = VertexAdaptor<op_func_call>::create(args);
call_function->set_string("warning");

const auto seq = VertexAdaptor<op_seq>::create(std::vector<VertexPtr>{call_function});

switch_cases.emplace_back(VertexAdaptor<op_default>::create(seq));
}

auto switch_vertex = VertexAdaptor<op_switch>::create(switch_condition, temp_var_condition_on_switch, temp_var_matched_with_one_case, std::move(switch_cases));
switch_vertex->is_match = true;

return switch_vertex;
}

VertexPtr GenTreePostprocessPass::convert_match_to_switch(VertexAdaptor<op_match> &match) {
auto tmp_var = VertexAdaptor<op_var>::create();
tmp_var->set_string(gen_unique_name("tmp_var"));
tmp_var->extra_type = op_ex_var_superlocal;

const auto switch_vertex = create_switch_vertex_from_match(match, tmp_var);
return VertexAdaptor<op_seq_rval>::create(switch_vertex, tmp_var);
}

VertexAdaptor<op_array> array_vertex_from_slice(const VertexRange &args, size_t start, size_t end) {
return VertexAdaptor<op_array>::create(
std::vector<VertexPtr>{args.begin() + start, args.begin() + end}
Expand Down
Loading