From: Dmitry Stogov Date: Tue, 8 Sep 2020 23:41:22 +0000 (+0300) Subject: JIT for FETCH_DIM_W/RW insructions X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=73c7fa272d095b000c664f472a5f1e56b4c4d167;p=php JIT for FETCH_DIM_W/RW insructions --- diff --git a/ext/opcache/jit/zend_jit.c b/ext/opcache/jit/zend_jit.c index 718117a56a..5243907365 100644 --- a/ext/opcache/jit/zend_jit.c +++ b/ext/opcache/jit/zend_jit.c @@ -2853,6 +2853,22 @@ static int zend_jit(const zend_op_array *op_array, zend_ssa *ssa, const zend_op goto jit_failure; } goto done; + case ZEND_FETCH_DIM_W: + case ZEND_FETCH_DIM_RW: +// case ZEND_FETCH_DIM_UNSET: + case ZEND_FETCH_LIST_W: + if (PROFITABILITY_CHECKS && (!ssa->ops || !ssa->var_info)) { + break; + } + if (opline->op1_type != IS_CV) { + break; + } + if (!zend_jit_fetch_dim(&dasm_state, opline, + OP1_INFO(), OP1_REG_ADDR(), OP2_INFO(), RES_REG_ADDR(), + zend_may_throw_ex(opline, ssa_op, op_array, ssa, op1_info, op2_info))) { + goto jit_failure; + } + goto done; case ZEND_ISSET_ISEMPTY_DIM_OBJ: if ((opline->extended_value & ZEND_ISEMPTY)) { // TODO: support for empty() ??? diff --git a/ext/opcache/jit/zend_jit_disasm_x86.c b/ext/opcache/jit/zend_jit_disasm_x86.c index bd737fa669..b044d63235 100644 --- a/ext/opcache/jit/zend_jit_disasm_x86.c +++ b/ext/opcache/jit/zend_jit_disasm_x86.c @@ -420,6 +420,9 @@ static int zend_jit_disasm_init(void) REGISTER_HELPER(zend_jit_fetch_dim_obj_is_helper); REGISTER_HELPER(zend_jit_fetch_dim_rw_helper); REGISTER_HELPER(zend_jit_fetch_dim_w_helper); + REGISTER_HELPER(zend_jit_fetch_dim_obj_rw_helper); + REGISTER_HELPER(zend_jit_fetch_dim_obj_w_helper); +// REGISTER_HELPER(zend_jit_fetch_dim_obj_unset_helper); REGISTER_HELPER(zend_jit_assign_dim_helper); REGISTER_HELPER(zend_jit_assign_dim_op_helper); REGISTER_HELPER(zend_jit_fast_assign_concat_helper); diff --git a/ext/opcache/jit/zend_jit_helpers.c b/ext/opcache/jit/zend_jit_helpers.c index 5b6b0e4b7f..42d98e383e 100644 --- a/ext/opcache/jit/zend_jit_helpers.c +++ b/ext/opcache/jit/zend_jit_helpers.c @@ -690,7 +690,8 @@ try_again: /* For BC reasons we allow errors so that we can warn on leading numeric string */ if (IS_LONG == is_numeric_string_ex(Z_STRVAL_P(dim), Z_STRLEN_P(dim), &offset, NULL, /* allow errors */ true, NULL, &trailing_data)) { - if (UNEXPECTED(trailing_data) /*&& type != BP_VAR_UNSET*/) { + if (UNEXPECTED(trailing_data) + && EG(current_execute_data)->opline->opcode != ZEND_FETCH_DIM_UNSET) { zend_error(E_WARNING, "Illegal string offset \"%s\"", Z_STRVAL_P(dim)); } return offset; @@ -850,6 +851,7 @@ static zend_never_inline ZEND_COLD void zend_wrong_string_offset(void) case ZEND_FETCH_DIM_RW: case ZEND_FETCH_DIM_FUNC_ARG: case ZEND_FETCH_DIM_UNSET: + case ZEND_FETCH_LIST_W: /* TODO: Encode the "reason" into opline->extended_value??? */ var = opline->result.var; opline++; @@ -858,9 +860,21 @@ static zend_never_inline ZEND_COLD void zend_wrong_string_offset(void) while (opline < end) { if (opline->op1_type == IS_VAR && opline->op1.var == var) { switch (opline->opcode) { + case ZEND_FETCH_OBJ_W: + case ZEND_FETCH_OBJ_RW: + case ZEND_FETCH_OBJ_FUNC_ARG: + case ZEND_FETCH_OBJ_UNSET: + case ZEND_ASSIGN_OBJ: case ZEND_ASSIGN_OBJ_OP: + case ZEND_ASSIGN_OBJ_REF: msg = "Cannot use string offset as an object"; break; + case ZEND_FETCH_DIM_W: + case ZEND_FETCH_DIM_RW: + case ZEND_FETCH_DIM_FUNC_ARG: + case ZEND_FETCH_DIM_UNSET: + case ZEND_FETCH_LIST_W: + case ZEND_ASSIGN_DIM: case ZEND_ASSIGN_DIM_OP: msg = "Cannot use string offset as an array"; break; @@ -878,20 +892,6 @@ static zend_never_inline ZEND_COLD void zend_wrong_string_offset(void) case ZEND_POST_DEC: msg = "Cannot increment/decrement string offsets"; break; - case ZEND_FETCH_DIM_W: - case ZEND_FETCH_DIM_RW: - case ZEND_FETCH_DIM_FUNC_ARG: - case ZEND_FETCH_DIM_UNSET: - case ZEND_ASSIGN_DIM: - msg = "Cannot use string offset as an array"; - break; - case ZEND_FETCH_OBJ_W: - case ZEND_FETCH_OBJ_RW: - case ZEND_FETCH_OBJ_FUNC_ARG: - case ZEND_FETCH_OBJ_UNSET: - case ZEND_ASSIGN_OBJ: - msg = "Cannot use string offset as an object"; - break; case ZEND_ASSIGN_REF: case ZEND_ADD_ARRAY_ELEMENT: case ZEND_INIT_ARRAY: @@ -914,6 +914,9 @@ static zend_never_inline ZEND_COLD void zend_wrong_string_offset(void) case ZEND_SEND_FUNC_ARG: msg = "Only variables can be passed by reference"; break; + case ZEND_FE_RESET_RW: + msg = "Cannot iterate on string offsets by reference"; + break; EMPTY_SWITCH_DEFAULT_CASE(); } break; @@ -1014,6 +1017,75 @@ static zend_never_inline void zend_assign_to_string_offset(zval *str, zval *dim, } } +static zend_always_inline void ZEND_FASTCALL zend_jit_fetch_dim_obj_helper(zval *object_ptr, zval *dim, zval *result, int type) +{ + zval *retval; + + if (EXPECTED(Z_TYPE_P(object_ptr) == IS_OBJECT)) { + retval = Z_OBJ_HT_P(object_ptr)->read_dimension(Z_OBJ_P(object_ptr), dim, type, result); + if (UNEXPECTED(retval == &EG(uninitialized_zval))) { + zend_class_entry *ce = Z_OBJCE_P(object_ptr); + + ZVAL_NULL(result); + zend_error(E_NOTICE, "Indirect modification of overloaded element of %s has no effect", ZSTR_VAL(ce->name)); + } else if (EXPECTED(retval && Z_TYPE_P(retval) != IS_UNDEF)) { + if (!Z_ISREF_P(retval)) { + if (result != retval) { + ZVAL_COPY(result, retval); + retval = result; + } + if (Z_TYPE_P(retval) != IS_OBJECT) { + zend_class_entry *ce = Z_OBJCE_P(object_ptr); + zend_error(E_NOTICE, "Indirect modification of overloaded element of %s has no effect", ZSTR_VAL(ce->name)); + } + } else if (UNEXPECTED(Z_REFCOUNT_P(retval) == 1)) { + ZVAL_UNREF(retval); + } + if (result != retval) { + ZVAL_INDIRECT(result, retval); + } + } else { + ZEND_ASSERT(EG(exception) && "read_dimension() returned NULL without exception"); + ZVAL_UNDEF(result); + } + } else if (EXPECTED(Z_TYPE_P(object_ptr) == IS_STRING)) { + if (!dim) { + zend_throw_error(NULL, "[] operator not supported for strings"); + } else { + if (UNEXPECTED(Z_TYPE_P(dim) != IS_LONG)) { + zend_check_string_offset(dim/*, BP_VAR_RW*/); + } + if (!EG(exception)) { + zend_wrong_string_offset(); + } + } + ZVAL_UNDEF(result); + } else { + if (type == BP_VAR_UNSET) { + zend_throw_error(NULL, "Cannot unset offset in a non-array variable"); + ZVAL_UNDEF(result); + } else { + zend_throw_error(NULL, "Cannot use a scalar value as an array"); + ZVAL_UNDEF(result); + } + } +} + +static void ZEND_FASTCALL zend_jit_fetch_dim_obj_w_helper(zval *object_ptr, zval *dim, zval *result) +{ + zend_jit_fetch_dim_obj_helper(object_ptr, dim, result, BP_VAR_W); +} + +static void ZEND_FASTCALL zend_jit_fetch_dim_obj_rw_helper(zval *object_ptr, zval *dim, zval *result) +{ + zend_jit_fetch_dim_obj_helper(object_ptr, dim, result, BP_VAR_RW); +} + +//static void ZEND_FASTCALL zend_jit_fetch_dim_obj_unset_helper(zval *object_ptr, zval *dim, zval *result) +//{ +// zend_jit_fetch_dim_obj_helper(object_ptr, dim, result, BP_VAR_UNSET); +//} + static void ZEND_FASTCALL zend_jit_assign_dim_helper(zval *object_ptr, zval *dim, zval *value, zval *result) { if (EXPECTED(Z_TYPE_P(object_ptr) == IS_OBJECT)) { diff --git a/ext/opcache/jit/zend_jit_trace.c b/ext/opcache/jit/zend_jit_trace.c index 8da6e899df..9eee964126 100644 --- a/ext/opcache/jit/zend_jit_trace.c +++ b/ext/opcache/jit/zend_jit_trace.c @@ -1558,6 +1558,16 @@ static zend_ssa *zend_jit_trace_build_tssa(zend_jit_trace_rec *trace_buffer, uin } } break; + case ZEND_FETCH_DIM_W: + case ZEND_FETCH_DIM_RW: +// case ZEND_FETCH_DIM_UNSET: + case ZEND_FETCH_LIST_W: + if (opline->op1_type != IS_CV) { + break; + } + ADD_OP1_TRACE_GUARD(); + ADD_OP2_TRACE_GUARD(); + break; case ZEND_SEND_VAL_EX: case ZEND_SEND_VAR_EX: case ZEND_SEND_VAR_NO_REF_EX: @@ -4311,6 +4321,37 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par goto jit_failure; } goto done; + case ZEND_FETCH_DIM_W: + case ZEND_FETCH_DIM_RW: +// case ZEND_FETCH_DIM_UNSET: + case ZEND_FETCH_LIST_W: + if (opline->op1_type != IS_CV) { + break; + } + op1_info = OP1_INFO(); + op1_addr = OP1_REG_ADDR(); + if (orig_op1_type != IS_UNKNOWN + && (orig_op1_type & IS_TRACE_REFERENCE)) { + if (!zend_jit_fetch_reference(&dasm_state, opline, orig_op1_type, &op1_info, &op1_addr, + !ssa->var_info[ssa_op->op1_use].guarded_reference, 1)) { + goto jit_failure; + } + if (opline->op1_type == IS_CV + && zend_jit_var_may_alias(op_array, op_array_ssa, EX_VAR_TO_NUM(opline->op1.var)) == NO_ALIAS) { + ssa->var_info[ssa_op->op1_use].guarded_reference = 1; + } + } else { + CHECK_OP1_TRACE_TYPE(); + } + op2_info = OP2_INFO(); + CHECK_OP2_TRACE_TYPE(); + op1_def_info = OP1_DEF_INFO(); + if (!zend_jit_fetch_dim(&dasm_state, opline, + op1_info, op1_addr, op2_info, RES_REG_ADDR(), + zend_may_throw_ex(opline, ssa_op, op_array, ssa, op1_info, op2_info))) { + goto jit_failure; + } + goto done; case ZEND_ISSET_ISEMPTY_DIM_OBJ: if ((opline->extended_value & ZEND_ISEMPTY)) { // TODO: support for empty() ??? diff --git a/ext/opcache/jit/zend_jit_x86.dasc b/ext/opcache/jit/zend_jit_x86.dasc index f44a472b95..1962b23a47 100644 --- a/ext/opcache/jit/zend_jit_x86.dasc +++ b/ext/opcache/jit/zend_jit_x86.dasc @@ -10950,6 +10950,199 @@ static int zend_jit_fetch_dim_read(dasm_State **Dst, return 1; } +static int zend_jit_fetch_dim(dasm_State **Dst, + const zend_op *opline, + uint32_t op1_info, + zend_jit_addr op1_addr, + uint32_t op2_info, + zend_jit_addr res_addr, + int may_throw) +{ + zend_jit_addr op2_addr; + + ZEND_ASSERT(opline->op1_type == IS_CV); + + op2_addr = (opline->op2_type != IS_UNUSED) ? OP2_ADDR() : 0; + + if (op1_info & MAY_BE_REF) { + | LOAD_ZVAL_ADDR FCARG1a, op1_addr + | IF_NOT_Z_TYPE FCARG1a, IS_REFERENCE, >1 + | GET_Z_PTR FCARG2a, FCARG1a + | IF_NOT_TYPE byte [FCARG2a + offsetof(zend_reference, val) + offsetof(zval, u1.v.type)], IS_ARRAY, >2 + | lea FCARG1a, [FCARG2a + offsetof(zend_reference, val)] + | jmp >3 + |.cold_code + |2: + | SET_EX_OPLINE opline, r0 + | EXT_CALL zend_jit_prepare_assign_dim_ref, r0 + | test r0, r0 + | mov FCARG1a, r0 + | jne >1 + | jmp ->exception_handler_undef + |.code + |1: + op1_addr = ZEND_ADDR_MEM_ZVAL(ZREG_FCARG1a, 0); + } + + if (op1_info & MAY_BE_ARRAY) { + if (op1_info & ((MAY_BE_ANY|MAY_BE_UNDEF) - MAY_BE_ARRAY)) { + | IF_NOT_ZVAL_TYPE op1_addr, IS_ARRAY, >7 + } + |3: + | SEPARATE_ARRAY op1_addr, op1_info, 1 + } + if (op1_info & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE)) { + if (op1_info & MAY_BE_ARRAY) { + |.cold_code + |7: + } + if (op1_info & (MAY_BE_ANY-(MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_ARRAY))) { + | CMP_ZVAL_TYPE op1_addr, IS_FALSE + | jg >7 + } + if ((op1_info & MAY_BE_UNDEF) + && opline->opcode == ZEND_FETCH_DIM_RW) { + if (op1_info & (MAY_BE_NULL|MAY_BE_FALSE)) { + | IF_NOT_ZVAL_TYPE op1_addr, IS_UNDEF, >1 + } + | SET_EX_OPLINE opline, r0 + | mov FCARG1a, opline->op1.var + | EXT_CALL zend_jit_undefined_op_helper, r0 + |1: + } + | // ZVAL_ARR(container, zend_new_array(8)); + if (Z_REG(op1_addr) != ZREG_FP) { + | mov T1, Ra(Z_REG(op1_addr)) // save + } + | EXT_CALL _zend_new_array_0, r0 + if (Z_REG(op1_addr) != ZREG_FP) { + | mov Ra(Z_REG(op1_addr)), T1 // restore + } + | SET_ZVAL_LVAL op1_addr, r0 + | SET_ZVAL_TYPE_INFO op1_addr, IS_ARRAY_EX + | mov FCARG1a, r0 + if (op1_info & MAY_BE_ARRAY) { + | jmp >1 + |.code + |1: + } + } + + if (op1_info & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_ARRAY)) { + |6: + if (opline->op2_type == IS_UNUSED) { + | // var_ptr = zend_hash_next_index_insert(Z_ARRVAL_P(container), &EG(uninitialized_zval)); + | LOAD_ADDR_ZTS FCARG2a, executor_globals, uninitialized_zval + | EXT_CALL zend_hash_next_index_insert, r0 + | // if (UNEXPECTED(!var_ptr)) { + | test r0, r0 + | jz >1 + |.cold_code + |1: + | // zend_throw_error(NULL, "Cannot add element to the array as the next element is already occupied"); + | CANNOT_ADD_ELEMENT opline + | SET_ZVAL_TYPE_INFO res_addr, IS_UNDEF + | //ZEND_VM_C_GOTO(assign_dim_op_ret_null); + | jmp >8 + |.code + | SET_ZVAL_PTR res_addr, r0 + | SET_ZVAL_TYPE_INFO res_addr, IS_INDIRECT + } else { + uint32_t type; + + switch (opline->opcode) { + case ZEND_FETCH_DIM_W: + case ZEND_FETCH_LIST_W: + type = BP_VAR_W; + break; + case ZEND_FETCH_DIM_RW: + type = BP_VAR_RW; + break; + case ZEND_FETCH_DIM_UNSET: + type = BP_VAR_UNSET; + break; + default: + ZEND_UNREACHABLE(); + } + + if (!zend_jit_fetch_dimension_address_inner(Dst, opline, type, op1_info, op2_info, NULL, NULL, NULL)) { + return 0; + } + + |8: + | SET_ZVAL_PTR res_addr, r0 + | SET_ZVAL_TYPE_INFO res_addr, IS_INDIRECT + + if (type == BP_VAR_RW || (op2_info & ((MAY_BE_ANY|MAY_BE_UNDEF) - (MAY_BE_LONG|MAY_BE_STRING)))) { + |.cold_code + |9: + | SET_ZVAL_TYPE_INFO res_addr, IS_NULL + | jmp >8 + |.code + } + } + } + + if (op1_info & (MAY_BE_ANY-(MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_ARRAY))) { + if (op1_info & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_ARRAY)) { + |.cold_code + |7: + } + + | SET_EX_OPLINE opline, r0 + if (Z_REG(op1_addr) != ZREG_FCARG1a || Z_OFFSET(op1_addr) != 0) { + | LOAD_ZVAL_ADDR FCARG1a, op1_addr + } + if (opline->op2_type == IS_UNUSED) { + | xor FCARG2a, FCARG2a + } else if (opline->op2_type == IS_CONST && Z_EXTRA_P(RT_CONSTANT(opline, opline->op2)) == ZEND_EXTRA_VALUE) { + ZEND_ASSERT(Z_MODE(op2_addr) == IS_CONST_ZVAL); + | LOAD_ADDR FCARG2a, (Z_ZV(op2_addr) + 1) + } else { + | LOAD_ZVAL_ADDR FCARG2a, op2_addr + } + |.if X64 + | LOAD_ZVAL_ADDR CARG3, res_addr + |.else + | sub r4, 12 + | PUSH_ZVAL_ADDR res_addr, r0 + |.endif + switch (opline->opcode) { + case ZEND_FETCH_DIM_W: + case ZEND_FETCH_LIST_W: + | EXT_CALL zend_jit_fetch_dim_obj_w_helper, r0 + break; + case ZEND_FETCH_DIM_RW: + | EXT_CALL zend_jit_fetch_dim_obj_rw_helper, r0 + break; +// case ZEND_FETCH_DIM_UNSET: +// | EXT_CALL zend_jit_fetch_dim_obj_unset_helper, r0 +// break; + default: + ZEND_UNREACHABLE(); + } + |.if not(X64) + | add r4, 12 + |.endif + + if (op1_info & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_ARRAY)) { + | jmp >8 // END + |.code + } + } + + |8: + | FREE_OP opline->op2_type, opline->op2, op2_info, 0, opline + + if (may_throw) { + if (!zend_jit_check_exception(Dst)) { + return 0; + } + } + + return 1; +} + static int zend_jit_isset_isempty_dim(dasm_State **Dst, const zend_op *opline, uint32_t op1_info,