From ddbdd59d8b0441d6d97a70ae9e54ee1e32efec6a Mon Sep 17 00:00:00 2001 From: nielsdos <7771979+nielsdos@users.noreply.github.com> Date: Fri, 28 Apr 2023 15:57:29 +0200 Subject: [PATCH 1/4] Implement implicit move optimisation ## Introduction This implements a more generic version of the in-place modification I first tried in GH-11060. We decided to limit that PR to RC1 optimisations of some array functions only, and not do the in-place $variable optimisation in that way because of issues. This patch overcomes those issues and builds on the previous one. With this patch, any internal function that supports RC1 optimisations automatically gets the optimisation for in-place variable modifications. Contrary to the previous approach, this is compatible with exceptions. Furthermore, this approach also allows userland functions to benefit from this optimisation. e.g. the following code will not take a copy of the array with this patch: ``` function foo($array) { $array[1] = 1; } function bar() { $array = ...; $array = foo($array); } ``` Right now the impact on the benchmark suite isn't that high. The reason is that only a handful of functions within PHP optimise for RC1 cases, and the array sizes for those cases are fairly small. When more support for these cases are added, the benefit from this patch will increase. I've added a micro benchmark for array operations that shows the effect of this optimisation. ## Implementation The optimiser already tracks which SSA variables have a value that doesn't matter with the no_val field. By changing ZEND_SEND_VAR to redefine op1, we automatically know if the variable will ever be used again without being overwritten by looking at the no_val field. If the no_val field is set, the variable may hold a string/array and the refcount may be 1, we set a flag on the ZEND_SEND_VAR(_EX) opline to indicate that it may avoid a copy. The flag is stored in extended_value. There are two new VM type spec handlers for this that check for the flag: one for ZEND_SEND_VAR and one for ZEND_SEND_VAR_EX. ## Limitations * The optimisation isn't performed on arguments. This is because the optimisation would be externally visible, which is undesirable. Unfortunately, this is also the case where a lot of optimisation opportunity lies. Nonetheless, even with this limitation it seems like it can help a lot. * The optimisation does not apply to functions using indirect variable access (e.g. variable-variables, compact()) and vararg functions. --- Zend/Optimizer/dfa_pass.c | 57 ++++++++++- Zend/Optimizer/sccp.c | 30 +++++- Zend/Optimizer/zend_dfg.c | 4 + Zend/Optimizer/zend_inference.c | 8 ++ Zend/Optimizer/zend_ssa.c | 14 +++ Zend/Optimizer/zend_ssa.h | 1 + Zend/zend_vm_def.h | 37 ++++++- Zend/zend_vm_execute.h | 169 ++++++++++++++++++++------------ Zend/zend_vm_handlers.h | 16 +-- 9 files changed, 262 insertions(+), 74 deletions(-) diff --git a/Zend/Optimizer/dfa_pass.c b/Zend/Optimizer/dfa_pass.c index ac99455c1f808..5a7eaeac293db 100644 --- a/Zend/Optimizer/dfa_pass.c +++ b/Zend/Optimizer/dfa_pass.c @@ -466,7 +466,10 @@ int zend_dfa_optimize_calls(zend_op_array *op_array, zend_ssa *ssa) int var_num = ssa_op->op1_use; zend_ssa_var *var = ssa->vars + var_num; - ZEND_ASSERT(ssa_op->op1_def < 0); + if (ssa_op->op1_def >= 0) { + zend_ssa_replace_op1_def_op1_use(ssa, ssa_op); + } + zend_ssa_unlink_use_chain(ssa, op_num, ssa_op->op1_use); ssa_op->op1_use = -1; ssa_op->op1_use_chain = -1; @@ -1066,6 +1069,50 @@ static bool zend_dfa_try_to_replace_result(zend_op_array *op_array, zend_ssa *ss return 0; } +/* Sets a flag on SEND ops when a copy can be a avoided. */ +static void zend_dfa_optimize_send_copies(zend_op_array *op_array, zend_ssa *ssa) +{ + /* func_get_args(), indirect accesses and exceptions could make the optimization observable. + * The latter two cases are already tested before applying the DFA pass. */ + if (ssa->cfg.flags & ZEND_FUNC_VARARG) { + return; + } + + for (uint32_t i = 0; i < op_array->last; i++) { + zend_op *opline = op_array->opcodes + i; + if ((opline->opcode != ZEND_SEND_VAR && opline->opcode != ZEND_SEND_VAR_EX) || opline->op2_type != IS_UNUSED || opline->op1_type != IS_CV) { + continue; + } + + zend_ssa_op *ssa_op = ssa->ops + i; + int op1_def = ssa_op->op1_def; + if (op1_def == -1) { + continue; + } + + int ssa_cv = ssa_op->op1_use; + + /* Argument move must not be observable in backtraces */ + if (ssa->vars[ssa_cv].var < op_array->num_args) { + continue; + } + + /* Unsetting a CV is always fine if it gets overwritten afterwards. + * Since type inference often infers very wide types, we are very loose in matching types. */ + uint32_t type = ssa->var_info[ssa_cv].type; + if ((type & (MAY_BE_REF|MAY_BE_UNDEF)) || !(type & MAY_BE_RC1) || !(type & (MAY_BE_STRING|MAY_BE_ARRAY))) { + continue; + } + + zend_ssa_var *ssa_var = ssa->vars + op1_def; + + if (ssa_var->no_val && !ssa_var->alias) { + /* Flag will be used by VM type spec handler */ + opline->extended_value = 1; + } + } +} + void zend_dfa_optimize_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx, zend_ssa *ssa, zend_call_info **call_map) { if (ctx->debug_level & ZEND_DUMP_BEFORE_DFA_PASS) { @@ -1124,6 +1171,14 @@ void zend_dfa_optimize_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx #endif } + /* Optimization should not be done on main because of globals. */ + if (op_array->function_name) { + zend_dfa_optimize_send_copies(op_array, ssa); +#if ZEND_DEBUG_DFA + ssa_verify_integrity(op_array, ssa, "after optimize send copies"); +#endif + } + for (v = op_array->last_var; v < ssa->vars_count; v++) { op_1 = ssa->vars[v].definition; diff --git a/Zend/Optimizer/sccp.c b/Zend/Optimizer/sccp.c index f6144f87b4fac..0c59340694ee1 100644 --- a/Zend/Optimizer/sccp.c +++ b/Zend/Optimizer/sccp.c @@ -98,7 +98,7 @@ typedef struct _sccp_ctx { #define MAKE_TOP(zv) (Z_TYPE_INFO_P(zv) = TOP) #define MAKE_BOT(zv) (Z_TYPE_INFO_P(zv) = BOT) -static void scp_dump_value(zval *zv) { +static void scp_dump_value(const zval *zv) { if (IS_TOP(zv)) { fprintf(stderr, " top"); } else if (IS_BOT(zv)) { @@ -1050,6 +1050,12 @@ static void sccp_visit_instr(scdf_ctx *scdf, zend_op *opline, zend_ssa_op *ssa_o case ZEND_SEND_VAL: case ZEND_SEND_VAR: { + SKIP_IF_TOP(op1); + + if (opline->opcode == ZEND_SEND_VAR) { + SET_RESULT(op1, op1); + } + /* If the value of a SEND for an ICALL changes, we need to reconsider the * ICALL result value. Otherwise we can ignore the opcode. */ zend_call_info *call; @@ -1058,7 +1064,7 @@ static void sccp_visit_instr(scdf_ctx *scdf, zend_op *opline, zend_ssa_op *ssa_o } call = ctx->call_map[opline - ctx->scdf.op_array->opcodes]; - if (IS_TOP(op1) || !call || !call->caller_call_opline + if (!call || !call->caller_call_opline || call->caller_call_opline->opcode != ZEND_DO_ICALL) { return; } @@ -2034,8 +2040,14 @@ static int remove_call(sccp_ctx *ctx, zend_op *opline, zend_ssa_op *ssa_op) &ssa->ops[call->caller_init_opline - op_array->opcodes]); for (i = 0; i < call->num_args; i++) { - zend_ssa_remove_instr(ssa, call->arg_info[i].opline, - &ssa->ops[call->arg_info[i].opline - op_array->opcodes]); + zend_op *op = call->arg_info[i].opline; + zend_ssa_op *this_ssa_op = &ssa->ops[op - op_array->opcodes]; + + if (op->opcode == ZEND_SEND_VAR && this_ssa_op->op1_def >= 0) { + zend_ssa_replace_op1_def_op1_use(ssa, this_ssa_op); + } + + zend_ssa_remove_instr(ssa, op, this_ssa_op); } // TODO: remove call_info completely??? @@ -2188,6 +2200,10 @@ static int try_remove_definition(sccp_ctx *ctx, int var_num, zend_ssa_var *var, return 0; } + if (opline->opcode == ZEND_SEND_VAR) { + return 0; + } + /* Compound assign or incdec -> convert to direct ASSIGN */ if (!value) { @@ -2330,6 +2346,12 @@ static int replace_constant_operands(sccp_ctx *ctx) { FOREACH_USE(var, use) { zend_op *opline = &op_array->opcodes[use]; zend_ssa_op *ssa_op = &ssa->ops[use]; + /* Removing the def in try_remove_definition() may reduce optimisation opportunities. + * We want to keep the no_val definition until we actually replace it with a constant. */ + if (opline->opcode == ZEND_SEND_VAR && ssa_op->op1_use == i && ssa_op->op1_def >= 0) { + zend_ssa_replace_op1_def_op1_use(ssa, ssa_op); + opline->extended_value = 0; + } if (try_replace_op1(ctx, opline, ssa_op, i, value)) { if (opline->opcode == ZEND_NOP) { removed_ops++; diff --git a/Zend/Optimizer/zend_dfg.c b/Zend/Optimizer/zend_dfg.c index 2207b594b85a5..73cc205cd5374 100644 --- a/Zend/Optimizer/zend_dfg.c +++ b/Zend/Optimizer/zend_dfg.c @@ -174,6 +174,10 @@ static zend_always_inline void _zend_dfg_add_use_def_op(const zend_op_array *op_ } break; case ZEND_SEND_VAR: + if (opline->op1_type == IS_CV && ((build_flags & ZEND_SSA_RC_INFERENCE) || opline->op2_type == IS_UNUSED)) { + goto add_op1_def; + } + break; case ZEND_CAST: case ZEND_QM_ASSIGN: case ZEND_JMP_SET: diff --git a/Zend/Optimizer/zend_inference.c b/Zend/Optimizer/zend_inference.c index 944283af71abc..2cb491efa5f3d 100644 --- a/Zend/Optimizer/zend_inference.c +++ b/Zend/Optimizer/zend_inference.c @@ -2950,6 +2950,10 @@ static zend_always_inline zend_result _zend_update_type_info( if (t1 & (MAY_BE_RC1|MAY_BE_REF)) { tmp |= MAY_BE_RCN; } + if ((t1 & (MAY_BE_ARRAY|MAY_BE_STRING)) && (t1 & MAY_BE_RC1) && !(t1 & (MAY_BE_UNDEF|MAY_BE_REF)) && ssa_vars[ssa_op->op1_def].no_val && !ssa_vars[ssa_op->op1_def].alias) { + /* implicit move may make value undef */ + tmp |= MAY_BE_UNDEF; + } UPDATE_SSA_TYPE(tmp, ssa_op->op1_def); COPY_SSA_OBJ_TYPE(ssa_op->op1_use, ssa_op->op1_def); } @@ -2991,6 +2995,10 @@ static zend_always_inline zend_result _zend_update_type_info( case ZEND_SEND_FUNC_ARG: if (ssa_op->op1_def >= 0) { tmp = (t1 & MAY_BE_UNDEF)|MAY_BE_REF|MAY_BE_RC1|MAY_BE_RCN|MAY_BE_ANY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF; + if (opline->opcode == ZEND_SEND_VAR_EX && (t1 & (MAY_BE_ARRAY|MAY_BE_STRING)) && (t1 & MAY_BE_RC1) && !(t1 & (MAY_BE_UNDEF|MAY_BE_REF)) && ssa_vars[ssa_op->op1_def].no_val && !ssa_vars[ssa_op->op1_def].alias) { + /* implicit move may make value undef */ + tmp |= MAY_BE_UNDEF; + } UPDATE_SSA_TYPE(tmp, ssa_op->op1_def); } break; diff --git a/Zend/Optimizer/zend_ssa.c b/Zend/Optimizer/zend_ssa.c index 67165a9b26d7a..e3a1b4d21f8db 100644 --- a/Zend/Optimizer/zend_ssa.c +++ b/Zend/Optimizer/zend_ssa.c @@ -703,6 +703,10 @@ static zend_always_inline int _zend_ssa_rename_op(const zend_op_array *op_array, } break; case ZEND_SEND_VAR: + if (opline->op1_type == IS_CV && ((build_flags & ZEND_SSA_RC_INFERENCE) || opline->op2_type == IS_UNUSED)) { + goto add_op1_def; + } + break; case ZEND_CAST: case ZEND_QM_ASSIGN: case ZEND_JMP_SET: @@ -1680,3 +1684,13 @@ void zend_ssa_rename_var_uses(zend_ssa *ssa, int old, int new, bool update_types old_var->phi_use_chain = NULL; } /* }}} */ + +void zend_ssa_replace_op1_def_op1_use(zend_ssa *ssa, zend_ssa_op *ssa_op) +{ + int op1_new = ssa_op->op1_use; + ZEND_ASSERT(op1_new >= 0); + ZEND_ASSERT(ssa_op->op1_def >= 0); + /* zend_ssa_rename_var_uses() clear use_chain & phi_use_chain for us */ + zend_ssa_rename_var_uses(ssa, ssa_op->op1_def, op1_new, true); + zend_ssa_remove_op1_def(ssa, ssa_op); +} diff --git a/Zend/Optimizer/zend_ssa.h b/Zend/Optimizer/zend_ssa.h index 5a6fce38d2f81..4541d465b2951 100644 --- a/Zend/Optimizer/zend_ssa.h +++ b/Zend/Optimizer/zend_ssa.h @@ -159,6 +159,7 @@ void zend_ssa_remove_uses_of_var(zend_ssa *ssa, int var_num); void zend_ssa_remove_block(zend_op_array *op_array, zend_ssa *ssa, int b); void zend_ssa_rename_var_uses(zend_ssa *ssa, int old_var, int new_var, bool update_types); void zend_ssa_remove_block_from_cfg(zend_ssa *ssa, int b); +void zend_ssa_replace_op1_def_op1_use(zend_ssa *ssa, zend_ssa_op *ssa_op); static zend_always_inline void _zend_ssa_remove_def(zend_ssa_var *var) { diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 0b6604217fa35..5a16f7a930f40 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -9900,7 +9900,7 @@ ZEND_VM_C_LABEL(fetch_dim_r_index_undef): ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION(); } -ZEND_VM_HOT_TYPE_SPEC_HANDLER(ZEND_SEND_VAR, op->op2_type == IS_UNUSED && (op1_info & (MAY_BE_UNDEF|MAY_BE_REF)) == 0, ZEND_SEND_VAR_SIMPLE, CV|VAR, NUM) +ZEND_VM_HOT_TYPE_SPEC_HANDLER(ZEND_SEND_VAR, op->op2_type == IS_UNUSED && !op->extended_value && (op1_info & (MAY_BE_UNDEF|MAY_BE_REF)) == 0, ZEND_SEND_VAR_SIMPLE, CV|VAR, NUM) { USE_OPLINE zval *varptr, *arg; @@ -9917,7 +9917,21 @@ ZEND_VM_HOT_TYPE_SPEC_HANDLER(ZEND_SEND_VAR, op->op2_type == IS_UNUSED && (op1_i ZEND_VM_NEXT_OPCODE(); } -ZEND_VM_HOT_TYPE_SPEC_HANDLER(ZEND_SEND_VAR_EX, op->op2_type == IS_UNUSED && op->op2.num <= MAX_ARG_FLAG_NUM && (op1_info & (MAY_BE_UNDEF|MAY_BE_REF)) == 0, ZEND_SEND_VAR_EX_SIMPLE, CV|VAR, UNUSED|NUM) +ZEND_VM_HOT_TYPE_SPEC_HANDLER(ZEND_SEND_VAR, op->extended_value /* extended_value implies here OP2 UNUSED and OP1 not UNDEF or REF */, ZEND_SEND_VAR_SIMPLE_EXT, CV, NUM) +{ + USE_OPLINE + zval *varptr, *arg; + + varptr = GET_OP1_ZVAL_PTR_UNDEF(BP_VAR_R); + arg = ZEND_CALL_VAR(EX(call), opline->result.var); + + ZVAL_COPY_VALUE(arg, varptr); + ZVAL_UNDEF(varptr); + + ZEND_VM_NEXT_OPCODE(); +} + +ZEND_VM_HOT_TYPE_SPEC_HANDLER(ZEND_SEND_VAR_EX, !op->extended_value && op->op2_type == IS_UNUSED && op->op2.num <= MAX_ARG_FLAG_NUM && (op1_info & (MAY_BE_UNDEF|MAY_BE_REF)) == 0, ZEND_SEND_VAR_EX_SIMPLE, CV|VAR, UNUSED|NUM) { USE_OPLINE zval *varptr, *arg; @@ -9939,6 +9953,25 @@ ZEND_VM_HOT_TYPE_SPEC_HANDLER(ZEND_SEND_VAR_EX, op->op2_type == IS_UNUSED && op- ZEND_VM_NEXT_OPCODE(); } +ZEND_VM_HOT_TYPE_SPEC_HANDLER(ZEND_SEND_VAR_EX, op->extended_value && op->op2.num <= MAX_ARG_FLAG_NUM /* extended_value implies here OP2 UNUSED and OP1 not UNDEF or REF */, ZEND_SEND_VAR_EX_SIMPLE_EXT, CV, UNUSED|NUM) +{ + USE_OPLINE + zval *varptr, *arg; + uint32_t arg_num = opline->op2.num; + + if (QUICK_ARG_SHOULD_BE_SENT_BY_REF(EX(call)->func, arg_num)) { + ZEND_VM_DISPATCH_TO_HANDLER(ZEND_SEND_REF); + } + + varptr = GET_OP1_ZVAL_PTR_UNDEF(BP_VAR_R); + arg = ZEND_CALL_VAR(EX(call), opline->result.var); + + ZVAL_COPY_VALUE(arg, varptr); + ZVAL_UNDEF(varptr); + + ZEND_VM_NEXT_OPCODE(); +} + ZEND_VM_HOT_TYPE_SPEC_HANDLER(ZEND_SEND_VAL, op->op1_type == IS_CONST && op->op2_type == IS_UNUSED && !Z_REFCOUNTED_P(RT_CONSTANT(op, op->op1)), ZEND_SEND_VAL_SIMPLE, CONST, NUM) { USE_OPLINE diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 21b927c02b895..4286880cd2f91 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -40531,6 +40531,20 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_SEND_VAR_SIMPLE_SP ZEND_VM_NEXT_OPCODE(); } +static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_SEND_VAR_SIMPLE_EXT_SPEC_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS) +{ + USE_OPLINE + zval *varptr, *arg; + + varptr = EX_VAR(opline->op1.var); + arg = ZEND_CALL_VAR(EX(call), opline->result.var); + + ZVAL_COPY_VALUE(arg, varptr); + ZVAL_UNDEF(varptr); + + ZEND_VM_NEXT_OPCODE(); +} + static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DIV_SPEC_CV_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE @@ -49788,6 +49802,25 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_SEND_VAR_EX_SIMPLE ZEND_VM_NEXT_OPCODE(); } +static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_SEND_VAR_EX_SIMPLE_EXT_SPEC_CV_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS) +{ + USE_OPLINE + zval *varptr, *arg; + uint32_t arg_num = opline->op2.num; + + if (QUICK_ARG_SHOULD_BE_SENT_BY_REF(EX(call)->func, arg_num)) { + ZEND_VM_TAIL_CALL(ZEND_SEND_REF_SPEC_CV_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU)); + } + + varptr = EX_VAR(opline->op1.var); + arg = ZEND_CALL_VAR(EX(call), opline->result.var); + + ZVAL_COPY_VALUE(arg, varptr); + ZVAL_UNDEF(varptr); + + ZEND_VM_NEXT_OPCODE(); +} + static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DIV_SPEC_CV_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { USE_OPLINE @@ -56760,11 +56793,13 @@ ZEND_API void execute_ex(zend_execute_data *ex) (void*)&&ZEND_SEND_VAR_SIMPLE_SPEC_VAR_LABEL, (void*)&&ZEND_NULL_LABEL, (void*)&&ZEND_SEND_VAR_SIMPLE_SPEC_CV_LABEL, + (void*)&&ZEND_SEND_VAR_SIMPLE_EXT_SPEC_CV_LABEL, (void*)&&ZEND_NULL_LABEL, (void*)&&ZEND_NULL_LABEL, (void*)&&ZEND_SEND_VAR_EX_SIMPLE_SPEC_VAR_UNUSED_LABEL, (void*)&&ZEND_NULL_LABEL, (void*)&&ZEND_SEND_VAR_EX_SIMPLE_SPEC_CV_UNUSED_LABEL, + (void*)&&ZEND_SEND_VAR_EX_SIMPLE_EXT_SPEC_CV_UNUSED_LABEL, (void*)&&ZEND_SEND_VAL_SIMPLE_SPEC_CONST_LABEL, (void*)&&ZEND_SEND_VAL_EX_SIMPLE_SPEC_CONST_LABEL, (void*)&&ZEND_FE_FETCH_R_SIMPLE_SPEC_VAR_CV_RETVAL_UNUSED_LABEL, @@ -60487,6 +60522,10 @@ ZEND_API void execute_ex(zend_execute_data *ex) VM_TRACE(ZEND_SEND_VAR_SIMPLE_SPEC_CV) ZEND_SEND_VAR_SIMPLE_SPEC_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); HYBRID_BREAK(); + HYBRID_CASE(ZEND_SEND_VAR_SIMPLE_EXT_SPEC_CV): + VM_TRACE(ZEND_SEND_VAR_SIMPLE_EXT_SPEC_CV) + ZEND_SEND_VAR_SIMPLE_EXT_SPEC_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); + HYBRID_BREAK(); HYBRID_CASE(ZEND_DIV_SPEC_CV_CONST): VM_TRACE(ZEND_DIV_SPEC_CV_CONST) ZEND_DIV_SPEC_CV_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); @@ -61107,6 +61146,10 @@ ZEND_API void execute_ex(zend_execute_data *ex) VM_TRACE(ZEND_SEND_VAR_EX_SIMPLE_SPEC_CV_UNUSED) ZEND_SEND_VAR_EX_SIMPLE_SPEC_CV_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); HYBRID_BREAK(); + HYBRID_CASE(ZEND_SEND_VAR_EX_SIMPLE_EXT_SPEC_CV_UNUSED): + VM_TRACE(ZEND_SEND_VAR_EX_SIMPLE_EXT_SPEC_CV_UNUSED) + ZEND_SEND_VAR_EX_SIMPLE_EXT_SPEC_CV_UNUSED_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); + HYBRID_BREAK(); HYBRID_CASE(ZEND_DIV_SPEC_CV_CV): VM_TRACE(ZEND_DIV_SPEC_CV_CV) ZEND_DIV_SPEC_CV_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU); @@ -64864,11 +64907,13 @@ void zend_vm_init(void) ZEND_SEND_VAR_SIMPLE_SPEC_VAR_HANDLER, ZEND_NULL_HANDLER, ZEND_SEND_VAR_SIMPLE_SPEC_CV_HANDLER, + ZEND_SEND_VAR_SIMPLE_EXT_SPEC_CV_HANDLER, ZEND_NULL_HANDLER, ZEND_NULL_HANDLER, ZEND_SEND_VAR_EX_SIMPLE_SPEC_VAR_UNUSED_HANDLER, ZEND_NULL_HANDLER, ZEND_SEND_VAR_EX_SIMPLE_SPEC_CV_UNUSED_HANDLER, + ZEND_SEND_VAR_EX_SIMPLE_EXT_SPEC_CV_UNUSED_HANDLER, ZEND_SEND_VAL_SIMPLE_SPEC_CONST_HANDLER, ZEND_SEND_VAL_EX_SIMPLE_SPEC_CONST_HANDLER, ZEND_FE_FETCH_R_SIMPLE_SPEC_VAR_CV_RETVAL_UNUSED_HANDLER, @@ -64921,7 +64966,7 @@ void zend_vm_init(void) 1255, 1256 | SPEC_RULE_OP1, 1261 | SPEC_RULE_OP1, - 3470, + 3472, 1266 | SPEC_RULE_OP1, 1271 | SPEC_RULE_OP1, 1276 | SPEC_RULE_OP2, @@ -65079,59 +65124,59 @@ void zend_vm_init(void) 2564, 2565, 2566, - 3470, - 3470, - 3470, - 3470, - 3470, - 3470, - 3470, - 3470, - 3470, - 3470, - 3470, - 3470, - 3470, - 3470, - 3470, - 3470, - 3470, - 3470, - 3470, - 3470, - 3470, - 3470, - 3470, - 3470, - 3470, - 3470, - 3470, - 3470, - 3470, - 3470, - 3470, - 3470, - 3470, - 3470, - 3470, - 3470, - 3470, - 3470, - 3470, - 3470, - 3470, - 3470, - 3470, - 3470, - 3470, - 3470, - 3470, - 3470, - 3470, - 3470, - 3470, - 3470, - 3470, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, + 3472, }; #if (ZEND_VM_KIND == ZEND_VM_KIND_HYBRID) zend_opcode_handler_funcs = labels; @@ -65508,17 +65553,19 @@ ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t break; case ZEND_SEND_VAL: if (op->op1_type == IS_CONST && op->op2_type == IS_UNUSED && !Z_REFCOUNTED_P(RT_CONSTANT(op, op->op1))) { - spec = 3466; + spec = 3468; } break; case ZEND_SEND_VAR_EX: - if (op->op2_type == IS_UNUSED && op->op2.num <= MAX_ARG_FLAG_NUM && (op1_info & (MAY_BE_UNDEF|MAY_BE_REF)) == 0) { - spec = 3461 | SPEC_RULE_OP1; + if (!op->extended_value && op->op2_type == IS_UNUSED && op->op2.num <= MAX_ARG_FLAG_NUM && (op1_info & (MAY_BE_UNDEF|MAY_BE_REF)) == 0) { + spec = 3462 | SPEC_RULE_OP1; + } else if (op->extended_value && op->op2.num <= MAX_ARG_FLAG_NUM /* extended_value implies here OP2 UNUSED and OP1 not UNDEF or REF */) { + spec = 3467; } break; case ZEND_FE_FETCH_R: if (op->op2_type == IS_CV && (op1_info & (MAY_BE_ANY|MAY_BE_REF)) == MAY_BE_ARRAY) { - spec = 3468 | SPEC_RULE_RETVAL; + spec = 3470 | SPEC_RULE_RETVAL; } break; case ZEND_FETCH_DIM_R: @@ -65531,12 +65578,14 @@ ZEND_API void ZEND_FASTCALL zend_vm_set_opcode_handler_ex(zend_op* op, uint32_t break; case ZEND_SEND_VAL_EX: if (op->op2_type == IS_UNUSED && op->op2.num <= MAX_ARG_FLAG_NUM && op->op1_type == IS_CONST && !Z_REFCOUNTED_P(RT_CONSTANT(op, op->op1))) { - spec = 3467; + spec = 3469; } break; case ZEND_SEND_VAR: - if (op->op2_type == IS_UNUSED && (op1_info & (MAY_BE_UNDEF|MAY_BE_REF)) == 0) { + if (op->op2_type == IS_UNUSED && !op->extended_value && (op1_info & (MAY_BE_UNDEF|MAY_BE_REF)) == 0) { spec = 3456 | SPEC_RULE_OP1; + } else if (op->extended_value /* extended_value implies here OP2 UNUSED and OP1 not UNDEF or REF */) { + spec = 3461; } break; case ZEND_BW_OR: diff --git a/Zend/zend_vm_handlers.h b/Zend/zend_vm_handlers.h index fae2138ef912e..38c1dc3eae3b3 100644 --- a/Zend/zend_vm_handlers.h +++ b/Zend/zend_vm_handlers.h @@ -1850,10 +1850,12 @@ _(3455, ZEND_FETCH_DIM_R_INDEX_SPEC_CV_TMPVARCV) \ _(3458, ZEND_SEND_VAR_SIMPLE_SPEC_VAR) \ _(3460, ZEND_SEND_VAR_SIMPLE_SPEC_CV) \ - _(3463, ZEND_SEND_VAR_EX_SIMPLE_SPEC_VAR_UNUSED) \ - _(3465, ZEND_SEND_VAR_EX_SIMPLE_SPEC_CV_UNUSED) \ - _(3466, ZEND_SEND_VAL_SIMPLE_SPEC_CONST) \ - _(3467, ZEND_SEND_VAL_EX_SIMPLE_SPEC_CONST) \ - _(3468, ZEND_FE_FETCH_R_SIMPLE_SPEC_VAR_CV_RETVAL_UNUSED) \ - _(3469, ZEND_FE_FETCH_R_SIMPLE_SPEC_VAR_CV_RETVAL_USED) \ - _(3469+1, ZEND_NULL) + _(3461, ZEND_SEND_VAR_SIMPLE_EXT_SPEC_CV) \ + _(3464, ZEND_SEND_VAR_EX_SIMPLE_SPEC_VAR_UNUSED) \ + _(3466, ZEND_SEND_VAR_EX_SIMPLE_SPEC_CV_UNUSED) \ + _(3467, ZEND_SEND_VAR_EX_SIMPLE_EXT_SPEC_CV_UNUSED) \ + _(3468, ZEND_SEND_VAL_SIMPLE_SPEC_CONST) \ + _(3469, ZEND_SEND_VAL_EX_SIMPLE_SPEC_CONST) \ + _(3470, ZEND_FE_FETCH_R_SIMPLE_SPEC_VAR_CV_RETVAL_UNUSED) \ + _(3471, ZEND_FE_FETCH_R_SIMPLE_SPEC_VAR_CV_RETVAL_USED) \ + _(3471+1, ZEND_NULL) From 4749e5b1606c0b7b236f13f1e15bc0562507e659 Mon Sep 17 00:00:00 2001 From: nielsdos <7771979+nielsdos@users.noreply.github.com> Date: Fri, 28 Apr 2023 17:16:13 +0200 Subject: [PATCH 2/4] Add array_micro_bench.php --- Zend/array_micro_bench.php | 66 ++++++++++++++++++++++++++++++++++++++ Zend/bench.php | 36 +-------------------- Zend/bench_common.php | 48 +++++++++++++++++++++++++++ Zend/micro_bench.php | 47 +-------------------------- 4 files changed, 116 insertions(+), 81 deletions(-) create mode 100644 Zend/array_micro_bench.php create mode 100644 Zend/bench_common.php diff --git a/Zend/array_micro_bench.php b/Zend/array_micro_bench.php new file mode 100644 index 0000000000000..ca9ec218bbefc --- /dev/null +++ b/Zend/array_micro_bench.php @@ -0,0 +1,66 @@ + Date: Sat, 29 Apr 2023 00:06:43 +0200 Subject: [PATCH 3/4] RC1 optimisation for ucfirst and lcfirst --- Zend/zend_types.h | 5 +++++ ext/standard/string.c | 18 ++++++++++++++++-- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/Zend/zend_types.h b/Zend/zend_types.h index c341ffa0b4d8c..fc7ae3f361d6e 100644 --- a/Zend/zend_types.h +++ b/Zend/zend_types.h @@ -1512,4 +1512,9 @@ static zend_always_inline bool zend_may_modify_arg_in_place(const zval *arg) return Z_REFCOUNTED_P(arg) && !(GC_FLAGS(Z_COUNTED_P(arg)) & (GC_IMMUTABLE | GC_PERSISTENT)) && Z_REFCOUNT_P(arg) == 1; } +static zend_always_inline bool zend_may_modify_string_in_place(const zend_string *arg) +{ + return !(GC_FLAGS(arg) & (GC_IMMUTABLE | GC_PERSISTENT)) && GC_REFCOUNT(arg) == 1; +} + #endif /* ZEND_TYPES_H */ diff --git a/ext/standard/string.c b/ext/standard/string.c index 335e6fd897128..17840d4803420 100644 --- a/ext/standard/string.c +++ b/ext/standard/string.c @@ -2341,7 +2341,14 @@ static zend_string* php_ucfirst(zend_string *str) if (r == ch) { return zend_string_copy(str); } else { - zend_string *s = zend_string_init(ZSTR_VAL(str), ZSTR_LEN(str), 0); + zend_string *s; + if (zend_may_modify_string_in_place(str)) { + s = str; + zend_string_forget_hash_val(s); + GC_ADDREF(s); + } else { + s = zend_string_init(ZSTR_VAL(str), ZSTR_LEN(str), false); + } ZSTR_VAL(s)[0] = r; return s; } @@ -2373,7 +2380,14 @@ static zend_string* php_lcfirst(zend_string *str) if (r == ZSTR_VAL(str)[0]) { return zend_string_copy(str); } else { - zend_string *s = zend_string_init(ZSTR_VAL(str), ZSTR_LEN(str), 0); + zend_string *s; + if (zend_may_modify_string_in_place(str)) { + s = str; + zend_string_forget_hash_val(s); + GC_ADDREF(s); + } else { + s = zend_string_init(ZSTR_VAL(str), ZSTR_LEN(str), false); + } ZSTR_VAL(s)[0] = r; return s; } From 945c0ba3076dbfaa997338d75e056d263d970e3e Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sat, 29 Apr 2023 00:20:37 +0200 Subject: [PATCH 4/4] RC1 optimisation for strto{lower,upper} We can't unconditionally allow inplace modification in zend_string_toupper_ex and zend_string_tolower_ex because their API contract expects a copy. --- Zend/zend_operators.c | 40 ++++++++++++++++++++++++++++++---------- Zend/zend_operators.h | 6 ++++-- ext/standard/string.c | 4 ++-- 3 files changed, 36 insertions(+), 14 deletions(-) diff --git a/Zend/zend_operators.c b/Zend/zend_operators.c index 00286cac388c7..6c423297ecbb6 100644 --- a/Zend/zend_operators.c +++ b/Zend/zend_operators.c @@ -2877,7 +2877,21 @@ ZEND_API char* ZEND_FASTCALL zend_str_toupper_dup_ex(const char *source, size_t } /* }}} */ -ZEND_API zend_string* ZEND_FASTCALL zend_string_tolower_ex(zend_string *str, bool persistent) /* {{{ */ +static zend_string* ZEND_FASTCALL zend_string_alloc_or_partial_dup(zend_string *str, bool persistent, bool inplace, const unsigned char *p) +{ + if (inplace) { + ZEND_ASSERT(!persistent); + zend_string_forget_hash_val(str); + GC_ADDREF(str); + return str; + } else { + zend_string *res = zend_string_alloc(ZSTR_LEN(str), persistent); + memcpy(ZSTR_VAL(res), ZSTR_VAL(str), p - (unsigned char *) ZSTR_VAL(str)); + return res; + } +} + +ZEND_API zend_string* ZEND_FASTCALL zend_string_tolower_ex_maybe_inplace(zend_string *str, bool persistent, bool inplace) /* {{{ */ { size_t length = ZSTR_LEN(str); unsigned char *p = (unsigned char *) ZSTR_VAL(str); @@ -2888,8 +2902,7 @@ ZEND_API zend_string* ZEND_FASTCALL zend_string_tolower_ex(zend_string *str, boo while (p + BLOCKCONV_STRIDE <= end) { BLOCKCONV_LOAD(p); if (BLOCKCONV_FOUND()) { - zend_string *res = zend_string_alloc(length, persistent); - memcpy(ZSTR_VAL(res), ZSTR_VAL(str), p - (unsigned char *) ZSTR_VAL(str)); + zend_string *res = zend_string_alloc_or_partial_dup(str, persistent, inplace, p); unsigned char *q = (unsigned char*) ZSTR_VAL(res) + (p - (unsigned char*) ZSTR_VAL(str)); /* Lowercase the chunk we already compared. */ @@ -2909,8 +2922,7 @@ ZEND_API zend_string* ZEND_FASTCALL zend_string_tolower_ex(zend_string *str, boo while (p < end) { if (*p != zend_tolower_ascii(*p)) { - zend_string *res = zend_string_alloc(length, persistent); - memcpy(ZSTR_VAL(res), ZSTR_VAL(str), p - (unsigned char*) ZSTR_VAL(str)); + zend_string *res = zend_string_alloc_or_partial_dup(str, persistent, inplace, p); unsigned char *q = (unsigned char*) ZSTR_VAL(res) + (p - (unsigned char*) ZSTR_VAL(str)); while (p < end) { @@ -2926,7 +2938,12 @@ ZEND_API zend_string* ZEND_FASTCALL zend_string_tolower_ex(zend_string *str, boo } /* }}} */ -ZEND_API zend_string* ZEND_FASTCALL zend_string_toupper_ex(zend_string *str, bool persistent) /* {{{ */ +ZEND_API zend_string* ZEND_FASTCALL zend_string_tolower_ex(zend_string *str, bool persistent) +{ + return zend_string_tolower_ex_maybe_inplace(str, persistent, false); +} + +ZEND_API zend_string* ZEND_FASTCALL zend_string_toupper_ex_maybe_inplace(zend_string *str, bool persistent, bool inplace) /* {{{ */ { size_t length = ZSTR_LEN(str); unsigned char *p = (unsigned char *) ZSTR_VAL(str); @@ -2937,8 +2954,7 @@ ZEND_API zend_string* ZEND_FASTCALL zend_string_toupper_ex(zend_string *str, boo while (p + BLOCKCONV_STRIDE <= end) { BLOCKCONV_LOAD(p); if (BLOCKCONV_FOUND()) { - zend_string *res = zend_string_alloc(length, persistent); - memcpy(ZSTR_VAL(res), ZSTR_VAL(str), p - (unsigned char *) ZSTR_VAL(str)); + zend_string *res = zend_string_alloc_or_partial_dup(str, persistent, inplace, p); unsigned char *q = (unsigned char *) ZSTR_VAL(res) + (p - (unsigned char *) ZSTR_VAL(str)); /* Uppercase the chunk we already compared. */ @@ -2958,8 +2974,7 @@ ZEND_API zend_string* ZEND_FASTCALL zend_string_toupper_ex(zend_string *str, boo while (p < end) { if (*p != zend_toupper_ascii(*p)) { - zend_string *res = zend_string_alloc(length, persistent); - memcpy(ZSTR_VAL(res), ZSTR_VAL(str), p - (unsigned char*) ZSTR_VAL(str)); + zend_string *res = zend_string_alloc_or_partial_dup(str, persistent, inplace, p); unsigned char *q = (unsigned char *) ZSTR_VAL(res) + (p - (unsigned char *) ZSTR_VAL(str)); while (p < end) { @@ -2975,6 +2990,11 @@ ZEND_API zend_string* ZEND_FASTCALL zend_string_toupper_ex(zend_string *str, boo } /* }}} */ +ZEND_API zend_string* ZEND_FASTCALL zend_string_toupper_ex(zend_string *str, bool persistent) +{ + return zend_string_toupper_ex_maybe_inplace(str, persistent, false); +} + ZEND_API int ZEND_FASTCALL zend_binary_strcmp(const char *s1, size_t len1, const char *s2, size_t len2) /* {{{ */ { int retval; diff --git a/Zend/zend_operators.h b/Zend/zend_operators.h index 40974da03249f..380be2cc3dc4f 100644 --- a/Zend/zend_operators.h +++ b/Zend/zend_operators.h @@ -438,13 +438,15 @@ ZEND_API char* ZEND_FASTCALL zend_str_toupper_dup(const char *source, siz ZEND_API char* ZEND_FASTCALL zend_str_tolower_dup_ex(const char *source, size_t length); ZEND_API char* ZEND_FASTCALL zend_str_toupper_dup_ex(const char *source, size_t length); ZEND_API zend_string* ZEND_FASTCALL zend_string_tolower_ex(zend_string *str, bool persistent); +ZEND_API zend_string* ZEND_FASTCALL zend_string_tolower_ex_maybe_inplace(zend_string *str, bool persistent, bool inplace); ZEND_API zend_string* ZEND_FASTCALL zend_string_toupper_ex(zend_string *str, bool persistent); +ZEND_API zend_string* ZEND_FASTCALL zend_string_toupper_ex_maybe_inplace(zend_string *str, bool persistent, bool inplace); static zend_always_inline zend_string* zend_string_tolower(zend_string *str) { - return zend_string_tolower_ex(str, false); + return zend_string_tolower_ex_maybe_inplace(str, false, false); } static zend_always_inline zend_string* zend_string_toupper(zend_string *str) { - return zend_string_toupper_ex(str, false); + return zend_string_toupper_ex_maybe_inplace(str, false, false); } ZEND_API int ZEND_FASTCALL zend_binary_zval_strcmp(zval *s1, zval *s2); diff --git a/ext/standard/string.c b/ext/standard/string.c index 17840d4803420..7bb544c0afa7c 100644 --- a/ext/standard/string.c +++ b/ext/standard/string.c @@ -1160,7 +1160,7 @@ PHP_FUNCTION(strtoupper) Z_PARAM_STR(arg) ZEND_PARSE_PARAMETERS_END(); - RETURN_STR(zend_string_toupper(arg)); + RETURN_STR(zend_string_toupper_ex_maybe_inplace(arg, false, zend_may_modify_string_in_place(arg))); } /* }}} */ @@ -1188,7 +1188,7 @@ PHP_FUNCTION(strtolower) Z_PARAM_STR(str) ZEND_PARSE_PARAMETERS_END(); - RETURN_STR(zend_string_tolower(str)); + RETURN_STR(zend_string_tolower_ex_maybe_inplace(str, false, zend_may_modify_string_in_place(str))); } /* }}} */