diff --git a/Zend/Optimizer/dfa_pass.c b/Zend/Optimizer/dfa_pass.c index 3ed507c35b070..0f76566a171d5 100644 --- a/Zend/Optimizer/dfa_pass.c +++ b/Zend/Optimizer/dfa_pass.c @@ -38,7 +38,7 @@ # include "ssa_integrity.c" #endif -zend_result zend_dfa_analyze_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx, zend_ssa *ssa) +zend_result zend_dfa_analyze_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx, zend_ssa *ssa, uint32_t *arg_types, HashTable *op_array_to_arg_offset) { uint32_t build_flags; @@ -95,7 +95,7 @@ zend_result zend_dfa_analyze_op_array(zend_op_array *op_array, zend_optimizer_ct zend_ssa_find_sccs(op_array, ssa); - if (zend_ssa_inference(&ctx->arena, op_array, ctx->script, ssa, ctx->optimization_level) == FAILURE) { + if (zend_ssa_inference(&ctx->arena, op_array, ctx->script, ssa, ctx->optimization_level, arg_types, op_array_to_arg_offset) == FAILURE) { return FAILURE; } @@ -1708,7 +1708,7 @@ void zend_optimize_dfa(zend_op_array *op_array, zend_optimizer_ctx *ctx) void *checkpoint = zend_arena_checkpoint(ctx->arena); zend_ssa ssa; - if (zend_dfa_analyze_op_array(op_array, ctx, &ssa) == FAILURE) { + if (zend_dfa_analyze_op_array(op_array, ctx, &ssa, NULL, 0) == FAILURE) { zend_arena_release(&ctx->arena, checkpoint); return; } diff --git a/Zend/Optimizer/zend_inference.c b/Zend/Optimizer/zend_inference.c index afe1c2339ed3a..955716df82d65 100644 --- a/Zend/Optimizer/zend_inference.c +++ b/Zend/Optimizer/zend_inference.c @@ -4462,7 +4462,7 @@ static void zend_mark_cv_references(const zend_op_array *op_array, const zend_sc free_alloca(worklist, use_heap); } -ZEND_API zend_result zend_ssa_inference(zend_arena **arena, const zend_op_array *op_array, const zend_script *script, zend_ssa *ssa, zend_long optimization_level) /* {{{ */ +ZEND_API zend_result zend_ssa_inference(zend_arena **arena, const zend_op_array *op_array, const zend_script *script, zend_ssa *ssa, zend_long optimization_level, uint32_t *arg_types, HashTable *op_array_to_arg_offset) /* {{{ */ { zend_ssa_var_info *ssa_var_info; int i; @@ -4499,6 +4499,63 @@ ZEND_API zend_result zend_ssa_inference(zend_arena **arena, const zend_op_array return FAILURE; } + if (arg_types && op_array->scope) { + const zend_func_info *func_info = ZEND_FUNC_INFO(op_array); + if (func_info) { + for (zend_call_info *callee_info = func_info->callee_info; callee_info != NULL; callee_info = callee_info->next_callee) { + const zend_op_array *callee_op_array = &callee_info->callee_func->op_array; + + /* Only support argument type inference for user functions. */ + if (callee_op_array->type != ZEND_USER_FUNCTION) { + continue; + } + /* Private functions can only be called from the class itself, and cannot be overridden. + * Therefore, we know which argument types they can receive at compile time. */ + if (!(callee_op_array->fn_flags & ZEND_ACC_PRIVATE)) { + continue; + } + + const zval *arg_types_offset_zval = zend_hash_index_find(op_array_to_arg_offset, (zend_ulong) callee_op_array); + uint32_t arg_types_offset = Z_LVAL_P(arg_types_offset_zval); + + /* Too complicated to handle here for now. */ + if (callee_info->named_args || callee_info->num_args < callee_op_array->num_args || (callee_op_array->fn_flags & ZEND_ACC_VARIADIC)) { + /* If we already started inferring, clear those types. */ + memset(arg_types + arg_types_offset, -1, callee_op_array->num_args * sizeof(uint32_t)); + continue; + } + // fprintf(stderr, "callee info %p, %s\n", callee_info, callee_info->callee_func->common.function_name->val); + + bool all_by_val = true; + for (uint32_t arg = 0; arg < callee_op_array->num_args && all_by_val; arg++) { + const zend_op *opline = callee_info->arg_info[arg].opline; + if (opline->opcode != ZEND_SEND_VAR && opline->opcode != ZEND_SEND_VAL) { + all_by_val = false; + } + } + + if (all_by_val) { + for (uint32_t arg = 0; arg < callee_op_array->num_args; arg++) { + const zend_op *opline = callee_info->arg_info[arg].opline; + const zend_ssa_op *ssa_op = &ssa->ops[opline - op_array->opcodes]; + uint32_t type; + if (opline->op1_type == IS_CONST) { + type = 1 << Z_TYPE_P(CRT_CONSTANT(opline->op1)); + } else { + /* Note: if this has an op1_def, it's for refcounting purposes. */ + ZEND_ASSERT(ssa_op->op1_use > -1); + type = ssa->var_info[ssa_op->op1_use].type & _ZEND_TYPE_MAY_BE_MASK; + } + arg_types[arg_types_offset + arg] |= type; + } + } else { + /* If we already started inferring, clear those types. */ + memset(arg_types + arg_types_offset, -1, callee_op_array->num_args * sizeof(uint32_t)); + } + } + } + } + return SUCCESS; } /* }}} */ diff --git a/Zend/Optimizer/zend_inference.h b/Zend/Optimizer/zend_inference.h index 4e768c4f6d524..57095683d4e0b 100644 --- a/Zend/Optimizer/zend_inference.h +++ b/Zend/Optimizer/zend_inference.h @@ -220,7 +220,7 @@ BEGIN_EXTERN_C() ZEND_API void zend_ssa_find_false_dependencies(const zend_op_array *op_array, zend_ssa *ssa); ZEND_API void zend_ssa_find_sccs(const zend_op_array *op_array, zend_ssa *ssa); -ZEND_API zend_result zend_ssa_inference(zend_arena **raena, const zend_op_array *op_array, const zend_script *script, zend_ssa *ssa, zend_long optimization_level); +ZEND_API zend_result zend_ssa_inference(zend_arena **raena, const zend_op_array *op_array, const zend_script *script, zend_ssa *ssa, zend_long optimization_level, uint32_t *arg_types, HashTable *op_array_to_arg_offset); ZEND_API uint32_t zend_array_element_type(uint32_t t1, uint8_t op_type, int write, int insert); diff --git a/Zend/Optimizer/zend_optimizer.c b/Zend/Optimizer/zend_optimizer.c index 956a13d658399..d4c71edce4054 100644 --- a/Zend/Optimizer/zend_optimizer.c +++ b/Zend/Optimizer/zend_optimizer.c @@ -1466,6 +1466,18 @@ static void zend_optimizer_call_registered_passes(zend_script *script, void *ctx } } +static void zend_dfa_analysis_with_call_graph(zend_op_array *op_array, zend_optimizer_ctx *ctx, uint32_t *arg_types, HashTable *op_array_to_arg_offset) +{ + zend_func_info *func_info = ZEND_FUNC_INFO(op_array); + if (func_info) { + if (zend_dfa_analyze_op_array(op_array, ctx, &func_info->ssa, arg_types, op_array_to_arg_offset) == SUCCESS) { + func_info->flags = func_info->ssa.cfg.flags; + } else { + ZEND_SET_FUNC_INFO(op_array, NULL); + } + } +} + ZEND_API void zend_optimize_script(zend_script *script, zend_long optimization_level, zend_long debug_level) { zend_op_array *op_array; @@ -1505,17 +1517,54 @@ ZEND_API void zend_optimize_script(zend_script *script, zend_long optimization_l } } + /* Group all non-private methods first, they will be analyzed first, and their type inference info can be used + * for private method arguments. */ + int num_non_private_methods = 0; + uint32_t total_arg_count = 0; + zend_op_array **op_array_grouped = zend_arena_alloc(&ctx.arena, sizeof(zend_op_array *) * call_graph.op_arrays_count); + HashTable *op_array_to_arg_offset = zend_arena_alloc(&ctx.arena, sizeof(HashTable)); + zend_hash_init(op_array_to_arg_offset, call_graph.op_arrays_count, NULL, NULL, false); for (i = 0; i < call_graph.op_arrays_count; i++) { - func_info = ZEND_FUNC_INFO(call_graph.op_arrays[i]); - if (func_info) { - if (zend_dfa_analyze_op_array(call_graph.op_arrays[i], &ctx, &func_info->ssa) == SUCCESS) { - func_info->flags = func_info->ssa.cfg.flags; - } else { - ZEND_SET_FUNC_INFO(call_graph.op_arrays[i], NULL); + if (!(call_graph.op_arrays[i]->fn_flags & ZEND_ACC_PRIVATE)) { + num_non_private_methods++; + } + zval tmp; + ZVAL_LONG(&tmp, total_arg_count); + zend_hash_index_add_new(op_array_to_arg_offset, (zend_ulong) call_graph.op_arrays[i], &tmp); + total_arg_count += call_graph.op_arrays[i]->num_args; + } + uint32_t *arg_types = zend_arena_calloc(&ctx.arena, total_arg_count, sizeof(uint32_t)); + int start_private_method_idx = num_non_private_methods; + int current_private_method_idx = start_private_method_idx; + for (i = 0; i < call_graph.op_arrays_count; i++) { + if (call_graph.op_arrays[i]->fn_flags & ZEND_ACC_PRIVATE) { + op_array_grouped[current_private_method_idx++] = call_graph.op_arrays[i]; + } else { + op_array_grouped[--num_non_private_methods] = call_graph.op_arrays[i]; + } + } + + for (i = 0; i < start_private_method_idx; i++) { + zend_dfa_analysis_with_call_graph(op_array_grouped[i], &ctx, arg_types, op_array_to_arg_offset); + } + + for (i = start_private_method_idx; i < call_graph.op_arrays_count; i++) { + zend_op_array *op_array = op_array_grouped[i]; + const zval *arg_types_offset_zval = zend_hash_index_find(op_array_to_arg_offset, (zend_ulong) op_array); + uint32_t arg_types_offset = Z_LVAL_P(arg_types_offset_zval); + + for (uint32_t arg = 0; arg < op_array->num_args; arg++) { + uint32_t inferred_type = arg_types[arg_types_offset + arg]; + if (inferred_type != -1 && !ZEND_TYPE_IS_SET(op_array->arg_info[arg].type)) { + op_array->arg_info[arg].type.type_mask |= inferred_type; } } + + zend_dfa_analysis_with_call_graph(op_array, &ctx, arg_types, op_array_to_arg_offset); } + zend_hash_destroy(op_array_to_arg_offset); + //TODO: perform inner-script inference??? for (i = 0; i < call_graph.op_arrays_count; i++) { func_info = ZEND_FUNC_INFO(call_graph.op_arrays[i]); diff --git a/Zend/Optimizer/zend_optimizer_internal.h b/Zend/Optimizer/zend_optimizer_internal.h index 2f708471ab1d5..a6cb5eed6ea23 100644 --- a/Zend/Optimizer/zend_optimizer_internal.h +++ b/Zend/Optimizer/zend_optimizer_internal.h @@ -111,7 +111,7 @@ void zend_optimizer_pass3(zend_op_array *op_array, zend_optimizer_ctx *ctx); void zend_optimize_func_calls(zend_op_array *op_array, zend_optimizer_ctx *ctx); void zend_optimize_cfg(zend_op_array *op_array, zend_optimizer_ctx *ctx); void zend_optimize_dfa(zend_op_array *op_array, zend_optimizer_ctx *ctx); -zend_result zend_dfa_analyze_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx, zend_ssa *ssa); +zend_result zend_dfa_analyze_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx, zend_ssa *ssa, uint32_t *arg_types, HashTable *op_array_to_arg_offset); void zend_dfa_optimize_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx, zend_ssa *ssa, zend_call_info **call_map); void zend_optimize_temporary_variables(zend_op_array *op_array, zend_optimizer_ctx *ctx); void zend_optimizer_nop_removal(zend_op_array *op_array, zend_optimizer_ctx *ctx); diff --git a/ext/opcache/jit/zend_jit.c b/ext/opcache/jit/zend_jit.c index 874eff576b554..da158f6f40600 100644 --- a/ext/opcache/jit/zend_jit.c +++ b/ext/opcache/jit/zend_jit.c @@ -1363,7 +1363,7 @@ static int zend_jit_op_array_analyze2(const zend_op_array *op_array, zend_script && !(op_array->fn_flags & ZEND_ACC_GENERATOR) && !(ssa->cfg.flags & ZEND_FUNC_INDIRECT_VAR_ACCESS)) { if (zend_ssa_inference(&CG(arena), op_array, script, ssa, - optimization_level & ~ZEND_OPTIMIZER_NARROW_TO_DOUBLE) != SUCCESS) { + optimization_level & ~ZEND_OPTIMIZER_NARROW_TO_DOUBLE, NULL, 0) != SUCCESS) { return FAILURE; } }