/* {{{ zend_implement_aggregate */ static int zend_implement_aggregate(zend_class_entry *interface, zend_class_entry *class_type) { uint32_t i; int t = -1; zend_class_iterator_funcs *funcs_ptr; if (class_type->get_iterator) { if (class_type->type == ZEND_INTERNAL_CLASS) { /* inheritance ensures the class has necessary userland methods */ return SUCCESS; } else if (class_type->get_iterator != zend_user_it_get_new_iterator) { /* c-level get_iterator cannot be changed (exception being only Traversable is implemented) */ if (class_type->num_interfaces) { ZEND_ASSERT(class_type->ce_flags & (ZEND_ACC_LINKED|ZEND_ACC_LINKING_IN_PROGRESS)); for (i = 0; i < class_type->num_interfaces; i++) { if (class_type->interfaces[i] == zend_ce_iterator) { zend_error_noreturn(E_ERROR, "Class %s cannot implement both %s and %s at the same time", ZSTR_VAL(class_type->name), ZSTR_VAL(interface->name), ZSTR_VAL(zend_ce_iterator->name)); return FAILURE; } if (class_type->interfaces[i] == zend_ce_traversable) { t = i; } } } if (t == -1) { return FAILURE; } } } if (class_type->parent && (class_type->parent->ce_flags & ZEND_ACC_REUSE_GET_ITERATOR)) { class_type->get_iterator = class_type->parent->get_iterator; class_type->ce_flags |= ZEND_ACC_REUSE_GET_ITERATOR; } else { class_type->get_iterator = zend_user_it_get_new_iterator; } funcs_ptr = class_type->iterator_funcs_ptr; if (class_type->type == ZEND_INTERNAL_CLASS) { if (!funcs_ptr) { funcs_ptr = calloc(1, sizeof(zend_class_iterator_funcs)); class_type->iterator_funcs_ptr = funcs_ptr; } funcs_ptr->zf_new_iterator = zend_hash_str_find_ptr(&class_type->function_table, "getiterator", sizeof("getiterator") - 1); } else { if (!funcs_ptr) { funcs_ptr = zend_arena_alloc(&CG(arena), sizeof(zend_class_iterator_funcs)); class_type->iterator_funcs_ptr = funcs_ptr; memset(funcs_ptr, 0, sizeof(zend_class_iterator_funcs)); } else { funcs_ptr->zf_new_iterator = NULL; } } return SUCCESS; }
void zend_optimizer_collect_constant(zend_optimizer_ctx *ctx, zval *name, zval* value) { zval val; if (!ctx->constants) { ctx->constants = zend_arena_alloc(&ctx->arena, sizeof(HashTable)); zend_hash_init(ctx->constants, 16, NULL, zend_optimizer_zval_dtor_wrapper, 0); } ZVAL_DUP(&val, value); zend_hash_add(ctx->constants, Z_STR_P(name), &val); }
/* {{{ zend_implement_iterator */ static int zend_implement_iterator(zend_class_entry *interface, zend_class_entry *class_type) { zend_class_iterator_funcs *funcs_ptr; if (class_type->get_iterator && class_type->get_iterator != zend_user_it_get_iterator) { if (class_type->type == ZEND_INTERNAL_CLASS) { /* inheritance ensures the class has the necessary userland methods */ return SUCCESS; } else { /* c-level get_iterator cannot be changed */ if (class_type->get_iterator == zend_user_it_get_new_iterator) { zend_error_noreturn(E_ERROR, "Class %s cannot implement both %s and %s at the same time", ZSTR_VAL(class_type->name), ZSTR_VAL(interface->name), ZSTR_VAL(zend_ce_aggregate->name)); } return FAILURE; } } if (class_type->parent && (class_type->parent->ce_flags & ZEND_ACC_REUSE_GET_ITERATOR)) { class_type->get_iterator = class_type->parent->get_iterator; class_type->ce_flags |= ZEND_ACC_REUSE_GET_ITERATOR; } else { class_type->get_iterator = zend_user_it_get_iterator; } funcs_ptr = class_type->iterator_funcs_ptr; if (class_type->type == ZEND_INTERNAL_CLASS) { if (!funcs_ptr) { funcs_ptr = calloc(1, sizeof(zend_class_iterator_funcs)); class_type->iterator_funcs_ptr = funcs_ptr; } else { funcs_ptr->zf_rewind = zend_hash_str_find_ptr(&class_type->function_table, "rewind", sizeof("rewind") - 1); funcs_ptr->zf_valid = zend_hash_str_find_ptr(&class_type->function_table, "valid", sizeof("valid") - 1); funcs_ptr->zf_key = zend_hash_str_find_ptr(&class_type->function_table, "key", sizeof("key") - 1); funcs_ptr->zf_current = zend_hash_str_find_ptr(&class_type->function_table, "current", sizeof("current") - 1); funcs_ptr->zf_next = zend_hash_str_find_ptr(&class_type->function_table, "next", sizeof("next") - 1); } } else { if (!funcs_ptr) { funcs_ptr = zend_arena_alloc(&CG(arena), sizeof(zend_class_iterator_funcs)); class_type->iterator_funcs_ptr = funcs_ptr; memset(funcs_ptr, 0, sizeof(zend_class_iterator_funcs)); } else { funcs_ptr->zf_valid = NULL; funcs_ptr->zf_current = NULL; funcs_ptr->zf_key = NULL; funcs_ptr->zf_next = NULL; funcs_ptr->zf_rewind = NULL; } } return SUCCESS; }
zend_function *duplicate_function(zend_function *func) { zend_function *new_function; if (func->type != ZEND_INTERNAL_FUNCTION) { new_function = zend_arena_alloc(&CG(arena), sizeof(zend_op_array)); memcpy(new_function, func, sizeof(zend_op_array)); function_add_ref(new_function); return new_function; } new_function = zend_arena_alloc(&CG(arena), sizeof(zend_internal_function)); memcpy(new_function, func, sizeof(zend_internal_function)); new_function->common.fn_flags |= ZEND_ACC_ARENA_ALLOCATED; function_add_ref(new_function); return new_function; }
void phpdbg_print_opline_ex(zend_execute_data *execute_data, zend_bool ignore_flags) /* {{{ */ { /* force out a line while stepping so the user knows what is happening */ if (ignore_flags || (!(PHPDBG_G(flags) & PHPDBG_IS_QUIET) || (PHPDBG_G(flags) & PHPDBG_IS_STEPPING) || (PHPDBG_G(oplog)))) { zend_op *opline = (zend_op *) execute_data->opline; char *decode = phpdbg_decode_opline(&execute_data->func->op_array, opline); if (ignore_flags || (!(PHPDBG_G(flags) & PHPDBG_IS_QUIET) || (PHPDBG_G(flags) & PHPDBG_IS_STEPPING))) { /* output line info */ phpdbg_notice("opline", "line=\"%u\" opline=\"%p\" op=\"%s\" file=\"%s\"", "L%-5u %16p %s %s", opline->lineno, opline, decode, execute_data->func->op_array.filename ? ZSTR_VAL(execute_data->func->op_array.filename) : "unknown"); } if (!ignore_flags && PHPDBG_G(oplog)) { phpdbg_log_ex(fileno(PHPDBG_G(oplog)), "L%-5u %16p %s %s\n", opline->lineno, opline, decode, execute_data->func->op_array.filename ? ZSTR_VAL(execute_data->func->op_array.filename) : "unknown"); } efree(decode); } if (PHPDBG_G(oplog_list)) { phpdbg_oplog_entry *cur = zend_arena_alloc(&PHPDBG_G(oplog_arena), sizeof(phpdbg_oplog_entry)); zend_op_array *op_array = &execute_data->func->op_array; cur->op = (zend_op *) execute_data->opline; cur->opcodes = op_array->opcodes; cur->filename = op_array->filename; cur->scope = op_array->scope; cur->function_name = op_array->function_name; cur->next = NULL; PHPDBG_G(oplog_cur)->next = cur; PHPDBG_G(oplog_cur) = cur; } } /* }}} */
static inline void *zend_ast_alloc(size_t size) { return zend_arena_alloc(&CG(ast_arena), size); }
zend_persistent_script *zend_file_cache_script_load(zend_file_handle *file_handle) { zend_string *full_path = file_handle->opened_path; int fd; char *filename; zend_persistent_script *script; zend_file_cache_metainfo info; zend_accel_hash_entry *bucket; void *mem, *checkpoint, *buf; int cache_it = 1; if (!full_path) { return NULL; } filename = zend_file_cache_get_bin_file_path(full_path); fd = open(filename, O_RDONLY | O_BINARY); if (fd < 0) { efree(filename); return NULL; } if (zend_file_cache_flock(fd, LOCK_SH) != 0) { close(fd); efree(filename); return NULL; } if (read(fd, &info, sizeof(info)) != sizeof(info)) { zend_accel_error(ACCEL_LOG_WARNING, "opcache cannot read from file '%s'\n", filename); zend_file_cache_flock(fd, LOCK_UN); close(fd); unlink(filename); efree(filename); return NULL; } /* verify header */ if (memcmp(info.magic, "OPCACHE", 8) != 0) { zend_accel_error(ACCEL_LOG_WARNING, "opcache cannot read from file '%s' (wrong header)\n", filename); zend_file_cache_flock(fd, LOCK_UN); close(fd); unlink(filename); efree(filename); return NULL; } if (memcmp(info.system_id, ZCG(system_id), 32) != 0) { zend_accel_error(ACCEL_LOG_WARNING, "opcache cannot read from file '%s' (wrong \"system_id\")\n", filename); zend_file_cache_flock(fd, LOCK_UN); close(fd); unlink(filename); efree(filename); return NULL; } /* verify timestamp */ if (ZCG(accel_directives).validate_timestamps && zend_get_file_handle_timestamp(file_handle, NULL) != info.timestamp) { if (zend_file_cache_flock(fd, LOCK_UN) != 0) { zend_accel_error(ACCEL_LOG_WARNING, "opcache cannot unlock file '%s'\n", filename); } close(fd); unlink(filename); efree(filename); return NULL; } checkpoint = zend_arena_checkpoint(CG(arena)); #ifdef __SSE2__ /* Align to 64-byte boundary */ mem = zend_arena_alloc(&CG(arena), info.mem_size + info.str_size + 64); mem = (void*)(((zend_uintptr_t)mem + 63L) & ~63L); #else mem = zend_arena_alloc(&CG(arena), info.mem_size + info.str_size); #endif if (read(fd, mem, info.mem_size + info.str_size) != (ssize_t)(info.mem_size + info.str_size)) { zend_accel_error(ACCEL_LOG_WARNING, "opcache cannot read from file '%s'\n", filename); zend_file_cache_flock(fd, LOCK_UN); close(fd); unlink(filename); zend_arena_release(&CG(arena), checkpoint); efree(filename); return NULL; } if (zend_file_cache_flock(fd, LOCK_UN) != 0) { zend_accel_error(ACCEL_LOG_WARNING, "opcache cannot unlock file '%s'\n", filename); } close(fd); /* verify checksum */ if (ZCG(accel_directives).file_cache_consistency_checks && zend_adler32(ADLER32_INIT, mem, info.mem_size + info.str_size) != info.checksum) { zend_accel_error(ACCEL_LOG_WARNING, "corrupted file '%s'\n", filename); unlink(filename); zend_arena_release(&CG(arena), checkpoint); efree(filename); return NULL; } if (!ZCG(accel_directives).file_cache_only && !ZCSG(restart_in_progress) && accelerator_shm_read_lock() == SUCCESS) { /* exclusive lock */ zend_shared_alloc_lock(); /* Check if we still need to put the file into the cache (may be it was * already stored by another process. This final check is done under * exclusive lock) */ bucket = zend_accel_hash_find_entry(&ZCSG(hash), full_path); if (bucket) { script = (zend_persistent_script *)bucket->data; if (!script->corrupted) { zend_shared_alloc_unlock(); zend_arena_release(&CG(arena), checkpoint); efree(filename); return script; } } if (zend_accel_hash_is_full(&ZCSG(hash))) { zend_accel_error(ACCEL_LOG_DEBUG, "No more entries in hash table!"); ZSMMG(memory_exhausted) = 1; zend_accel_schedule_restart_if_necessary(ACCEL_RESTART_HASH); zend_shared_alloc_unlock(); goto use_process_mem; } #ifdef __SSE2__ /* Align to 64-byte boundary */ buf = zend_shared_alloc(info.mem_size + 64); buf = (void*)(((zend_uintptr_t)buf + 63L) & ~63L); #else buf = zend_shared_alloc(info.mem_size); #endif if (!buf) { zend_accel_schedule_restart_if_necessary(ACCEL_RESTART_OOM); zend_shared_alloc_unlock(); goto use_process_mem; } memcpy(buf, mem, info.mem_size); } else { use_process_mem: buf = mem; cache_it = 0; } ZCG(mem) = ((char*)mem + info.mem_size); script = (zend_persistent_script*)((char*)buf + info.script_offset); script->corrupted = !cache_it; /* used to check if script restored to SHM or process memory */ zend_file_cache_unserialize(script, buf); script->corrupted = 0; if (cache_it) { script->dynamic_members.checksum = zend_accel_script_checksum(script); script->dynamic_members.last_used = ZCG(request_time); zend_accel_hash_update(&ZCSG(hash), ZSTR_VAL(script->script.filename), ZSTR_LEN(script->script.filename), 0, script); zend_shared_alloc_unlock(); zend_arena_release(&CG(arena), checkpoint); } efree(filename); return script; }
ZEND_API void zend_create_closure(zval *res, zend_function *func, zend_class_entry *scope, zend_class_entry *called_scope, zval *this_ptr) /* {{{ */ { zend_closure *closure; object_init_ex(res, zend_ce_closure); closure = (zend_closure *)Z_OBJ_P(res); if ((scope == NULL) && this_ptr && (Z_TYPE_P(this_ptr) != IS_UNDEF)) { /* use dummy scope if we're binding an object without specifying a scope */ /* maybe it would be better to create one for this purpose */ scope = zend_ce_closure; } if (func->type == ZEND_USER_FUNCTION) { memcpy(&closure->func, func, sizeof(zend_op_array)); closure->func.common.fn_flags |= ZEND_ACC_CLOSURE; if (closure->func.op_array.static_variables) { closure->func.op_array.static_variables = zend_array_dup(closure->func.op_array.static_variables); } /* Runtime cache is scope-dependent, so we cannot reuse it if the scope changed */ if (!closure->func.op_array.run_time_cache || func->common.scope != scope || (func->common.fn_flags & ZEND_ACC_NO_RT_ARENA) ) { if (!func->op_array.run_time_cache && (func->common.fn_flags & ZEND_ACC_CLOSURE)) { /* If a real closure is used for the first time, we create a shared runtime cache * and remember which scope it is for. */ func->common.scope = scope; func->op_array.run_time_cache = zend_arena_alloc(&CG(arena), func->op_array.cache_size); closure->func.op_array.run_time_cache = func->op_array.run_time_cache; } else { /* Otherwise, we use a non-shared runtime cache */ closure->func.op_array.run_time_cache = emalloc(func->op_array.cache_size); closure->func.op_array.fn_flags |= ZEND_ACC_NO_RT_ARENA; } memset(closure->func.op_array.run_time_cache, 0, func->op_array.cache_size); } if (closure->func.op_array.refcount) { (*closure->func.op_array.refcount)++; } } else { memcpy(&closure->func, func, sizeof(zend_internal_function)); closure->func.common.fn_flags |= ZEND_ACC_CLOSURE; /* wrap internal function handler to avoid memory leak */ if (UNEXPECTED(closure->func.internal_function.handler == zend_closure_internal_handler)) { /* avoid infinity recursion, by taking handler from nested closure */ zend_closure *nested = (zend_closure*)((char*)func - XtOffsetOf(zend_closure, func)); ZEND_ASSERT(nested->std.ce == zend_ce_closure); closure->orig_internal_handler = nested->orig_internal_handler; } else { closure->orig_internal_handler = closure->func.internal_function.handler; } closure->func.internal_function.handler = zend_closure_internal_handler; if (!func->common.scope) { /* if it's a free function, we won't set scope & this since they're meaningless */ this_ptr = NULL; scope = NULL; } } ZVAL_UNDEF(&closure->this_ptr); /* Invariant: * If the closure is unscoped or static, it has no bound object. */ closure->func.common.scope = scope; closure->called_scope = called_scope; if (scope) { closure->func.common.fn_flags |= ZEND_ACC_PUBLIC; if (this_ptr && Z_TYPE_P(this_ptr) == IS_OBJECT && (closure->func.common.fn_flags & ZEND_ACC_STATIC) == 0) { ZVAL_COPY(&closure->this_ptr, this_ptr); } } }
void optimize_temporary_variables(zend_op_array *op_array, zend_optimizer_ctx *ctx) { int T = op_array->T; int offset = op_array->last_var; char *taken_T; /* T index in use */ zend_op **start_of_T; /* opline where T is first used */ char *valid_T; /* Is the map_T valid */ int *map_T; /* Map's the T to its new index */ zend_op *opline, *end; int currT; int i; int max = -1; int var_to_free = -1; void *checkpoint = zend_arena_checkpoint(ctx->arena); taken_T = (char *) zend_arena_alloc(&ctx->arena, T); start_of_T = (zend_op **) zend_arena_alloc(&ctx->arena, T * sizeof(zend_op *)); valid_T = (char *) zend_arena_alloc(&ctx->arena, T); map_T = (int *) zend_arena_alloc(&ctx->arena, T * sizeof(int)); end = op_array->opcodes; opline = &op_array->opcodes[op_array->last - 1]; /* Find T definition points */ while (opline >= end) { if (ZEND_RESULT_TYPE(opline) & (IS_VAR | IS_TMP_VAR)) { start_of_T[VAR_NUM(ZEND_RESULT(opline).var) - offset] = opline; } /* special puprose variable to keep HashPointer on VM stack */ if (opline->opcode == ZEND_OP_DATA && (opline-1)->opcode == ZEND_FE_FETCH && opline->op1_type == IS_TMP_VAR) { start_of_T[VAR_NUM(ZEND_OP1(opline).var) - offset] = opline; if (sizeof(HashPointer) > sizeof(zval)) { /* Make shure 1 zval is enough for HashPointer (2 must be enough) */ start_of_T[VAR_NUM(ZEND_OP1(opline).var) + 1 - offset] = opline; } } opline--; } memset(valid_T, 0, T); memset(taken_T, 0, T); end = op_array->opcodes; opline = &op_array->opcodes[op_array->last - 1]; while (opline >= end) { if ((ZEND_OP1_TYPE(opline) & (IS_VAR | IS_TMP_VAR))) { /* special puprose variable to keep HashPointer on VM stack */ if (opline->opcode == ZEND_OP_DATA && (opline-1)->opcode == ZEND_FE_FETCH && opline->op1_type == IS_TMP_VAR) { max++; ZEND_OP1(opline).var = NUM_VAR(max + offset); if (sizeof(HashPointer) > sizeof(zval)) { /* Make shure 1 zval is enough for HashPointer (2 must be enough) */ max++; } } else { currT = VAR_NUM(ZEND_OP1(opline).var) - offset; if (!valid_T[currT]) { GET_AVAILABLE_T(); map_T[currT] = i; valid_T[currT] = 1; } ZEND_OP1(opline).var = NUM_VAR(map_T[currT] + offset); } } /* Skip OP_DATA */ if (opline->opcode == ZEND_OP_DATA && (opline-1)->opcode == ZEND_ASSIGN_DIM) { opline--; continue; } if ((ZEND_OP2_TYPE(opline) & (IS_VAR | IS_TMP_VAR))) { currT = VAR_NUM(ZEND_OP2(opline).var) - offset; if (!valid_T[currT]) { GET_AVAILABLE_T(); map_T[currT] = i; valid_T[currT] = 1; } ZEND_OP2(opline).var = NUM_VAR(map_T[currT] + offset); } if (opline->opcode == ZEND_DECLARE_INHERITED_CLASS || opline->opcode == ZEND_DECLARE_INHERITED_CLASS_DELAYED) { currT = VAR_NUM(opline->extended_value) - offset; if (!valid_T[currT]) { GET_AVAILABLE_T(); map_T[currT] = i; valid_T[currT] = 1; } opline->extended_value = NUM_VAR(map_T[currT] + offset); } /* Allocate OP_DATA->op2 after "operands", but before "result" */ if (opline->opcode == ZEND_ASSIGN_DIM && (opline + 1)->opcode == ZEND_OP_DATA && ZEND_OP2_TYPE(opline + 1) & (IS_VAR | IS_TMP_VAR)) { currT = VAR_NUM(ZEND_OP2(opline + 1).var) - offset; GET_AVAILABLE_T(); map_T[currT] = i; valid_T[currT] = 1; taken_T[i] = 0; ZEND_OP2(opline + 1).var = NUM_VAR(i + offset); var_to_free = i; } if (ZEND_RESULT_TYPE(opline) & (IS_VAR | IS_TMP_VAR)) { currT = VAR_NUM(ZEND_RESULT(opline).var) - offset; if (valid_T[currT]) { if (start_of_T[currT] == opline) { taken_T[map_T[currT]] = 0; } ZEND_RESULT(opline).var = NUM_VAR(map_T[currT] + offset); } else { /* Au still needs to be assigned a T which is a bit dumb. Should consider changing Zend */ GET_AVAILABLE_T(); if (RESULT_UNUSED(opline)) { taken_T[i] = 0; } else { /* Code which gets here is using a wrongly built opcode such as RECV() */ map_T[currT] = i; valid_T[currT] = 1; } ZEND_RESULT(opline).var = NUM_VAR(i + offset); } } if (var_to_free >= 0) { taken_T[var_to_free] = 0; var_to_free = -1; } opline--; } zend_arena_release(&ctx->arena, checkpoint); op_array->T = max + 1; }
ZEND_API void zend_create_closure(zval *res, zend_function *func, zend_class_entry *scope, zend_class_entry *called_scope, zval *this_ptr) /* {{{ */ { zend_closure *closure; object_init_ex(res, zend_ce_closure); closure = (zend_closure *)Z_OBJ_P(res); if ((scope == NULL) && this_ptr && (Z_TYPE_P(this_ptr) != IS_UNDEF)) { /* use dummy scope if we're binding an object without specifying a scope */ /* maybe it would be better to create one for this purpose */ scope = zend_ce_closure; } if (func->type == ZEND_USER_FUNCTION) { memcpy(&closure->func, func, sizeof(zend_op_array)); closure->func.common.prototype = (zend_function*)closure; closure->func.common.fn_flags |= ZEND_ACC_CLOSURE; if (closure->func.op_array.static_variables) { HashTable *static_variables = closure->func.op_array.static_variables; ALLOC_HASHTABLE(closure->func.op_array.static_variables); zend_hash_init(closure->func.op_array.static_variables, zend_hash_num_elements(static_variables), NULL, ZVAL_PTR_DTOR, 0); zend_hash_apply_with_arguments(static_variables, zval_copy_static_var, 1, closure->func.op_array.static_variables); } if (UNEXPECTED(!closure->func.op_array.run_time_cache)) { closure->func.op_array.run_time_cache = func->op_array.run_time_cache = zend_arena_alloc(&CG(arena), func->op_array.cache_size); memset(func->op_array.run_time_cache, 0, func->op_array.cache_size); } if (closure->func.op_array.refcount) { (*closure->func.op_array.refcount)++; } } else { memcpy(&closure->func, func, sizeof(zend_internal_function)); closure->func.common.prototype = (zend_function*)closure; closure->func.common.fn_flags |= ZEND_ACC_CLOSURE; /* wrap internal function handler to avoid memory leak */ if (UNEXPECTED(closure->func.internal_function.handler == zend_closure_internal_handler)) { /* avoid infinity recursion, by taking handler from nested closure */ zend_closure *nested = (zend_closure*)((char*)func - XtOffsetOf(zend_closure, func)); ZEND_ASSERT(nested->std.ce == zend_ce_closure); closure->orig_internal_handler = nested->orig_internal_handler; } else { closure->orig_internal_handler = closure->func.internal_function.handler; } closure->func.internal_function.handler = zend_closure_internal_handler; if (!func->common.scope) { /* if it's a free function, we won't set scope & this since they're meaningless */ this_ptr = NULL; scope = NULL; } } ZVAL_UNDEF(&closure->this_ptr); /* Invariant: * If the closure is unscoped or static, it has no bound object. */ closure->func.common.scope = scope; closure->called_scope = called_scope; if (scope) { closure->func.common.fn_flags |= ZEND_ACC_PUBLIC; if (this_ptr && Z_TYPE_P(this_ptr) == IS_OBJECT && (closure->func.common.fn_flags & ZEND_ACC_STATIC) == 0) { ZVAL_COPY(&closure->this_ptr, this_ptr); } } }
void optimize_temporary_variables(zend_op_array *op_array, zend_optimizer_ctx *ctx) { int T = op_array->T; int offset = op_array->last_var; char *taken_T; /* T index in use */ zend_op **start_of_T; /* opline where T is first used */ char *valid_T; /* Is the map_T valid */ int *map_T; /* Map's the T to its new index */ zend_op *opline, *end; int currT; int i; int max = -1; int var_to_free = -1; void *checkpoint = zend_arena_checkpoint(ctx->arena); taken_T = (char *) zend_arena_alloc(&ctx->arena, T); start_of_T = (zend_op **) zend_arena_alloc(&ctx->arena, T * sizeof(zend_op *)); valid_T = (char *) zend_arena_alloc(&ctx->arena, T); map_T = (int *) zend_arena_alloc(&ctx->arena, T * sizeof(int)); end = op_array->opcodes; opline = &op_array->opcodes[op_array->last - 1]; /* Find T definition points */ while (opline >= end) { if (ZEND_RESULT_TYPE(opline) & (IS_VAR | IS_TMP_VAR)) { start_of_T[VAR_NUM(ZEND_RESULT(opline).var) - offset] = opline; } opline--; } memset(valid_T, 0, T); memset(taken_T, 0, T); end = op_array->opcodes; opline = &op_array->opcodes[op_array->last - 1]; while (opline >= end) { if ((ZEND_OP1_TYPE(opline) & (IS_VAR | IS_TMP_VAR))) { currT = VAR_NUM(ZEND_OP1(opline).var) - offset; if (opline->opcode == ZEND_ROPE_END) { int num = (((opline->extended_value + 1) * sizeof(zend_string*)) + (sizeof(zval) - 1)) / sizeof(zval); int var; var = max; while (var >= 0 && !taken_T[var]) { var--; } max = MAX(max, var + num); var = var + 1; map_T[currT] = var; valid_T[currT] = 1; taken_T[var] = 1; ZEND_OP1(opline).var = NUM_VAR(var + offset); while (num > 1) { num--; taken_T[var + num] = 1; } } else { if (!valid_T[currT]) { GET_AVAILABLE_T(); map_T[currT] = i; valid_T[currT] = 1; } ZEND_OP1(opline).var = NUM_VAR(map_T[currT] + offset); } } /* Skip OP_DATA */ if (opline->opcode == ZEND_OP_DATA && (opline-1)->opcode == ZEND_ASSIGN_DIM) { opline--; continue; } if ((ZEND_OP2_TYPE(opline) & (IS_VAR | IS_TMP_VAR))) { currT = VAR_NUM(ZEND_OP2(opline).var) - offset; if (!valid_T[currT]) { GET_AVAILABLE_T(); map_T[currT] = i; valid_T[currT] = 1; } ZEND_OP2(opline).var = NUM_VAR(map_T[currT] + offset); } if (opline->opcode == ZEND_DECLARE_INHERITED_CLASS || opline->opcode == ZEND_DECLARE_ANON_INHERITED_CLASS || opline->opcode == ZEND_DECLARE_INHERITED_CLASS_DELAYED) { currT = VAR_NUM(opline->extended_value) - offset; if (!valid_T[currT]) { GET_AVAILABLE_T(); map_T[currT] = i; valid_T[currT] = 1; } opline->extended_value = NUM_VAR(map_T[currT] + offset); } /* Allocate OP_DATA->op2 after "operands", but before "result" */ if (opline->opcode == ZEND_ASSIGN_DIM && (opline + 1)->opcode == ZEND_OP_DATA && ZEND_OP2_TYPE(opline + 1) & (IS_VAR | IS_TMP_VAR)) { currT = VAR_NUM(ZEND_OP2(opline + 1).var) - offset; GET_AVAILABLE_T(); map_T[currT] = i; valid_T[currT] = 1; taken_T[i] = 0; ZEND_OP2(opline + 1).var = NUM_VAR(i + offset); var_to_free = i; } if (ZEND_RESULT_TYPE(opline) & (IS_VAR | IS_TMP_VAR)) { currT = VAR_NUM(ZEND_RESULT(opline).var) - offset; if (valid_T[currT]) { if (start_of_T[currT] == opline) { taken_T[map_T[currT]] = 0; } ZEND_RESULT(opline).var = NUM_VAR(map_T[currT] + offset); if (opline->opcode == ZEND_ROPE_INIT) { if (start_of_T[currT] == opline) { uint32_t num = ((opline->extended_value * sizeof(zend_string*)) + (sizeof(zval) - 1)) / sizeof(zval); while (num > 1) { num--; taken_T[map_T[currT]+num] = 0; } } } } else { /* Au still needs to be assigned a T which is a bit dumb. Should consider changing Zend */ GET_AVAILABLE_T(); if (RESULT_UNUSED(opline)) { taken_T[i] = 0; } else { /* Code which gets here is using a wrongly built opcode such as RECV() */ map_T[currT] = i; valid_T[currT] = 1; } ZEND_RESULT(opline).var = NUM_VAR(i + offset); } } if (var_to_free >= 0) { taken_T[var_to_free] = 0; var_to_free = -1; } opline--; } zend_arena_release(&ctx->arena, checkpoint); op_array->T = max + 1; }
void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx *ctx) { zend_op *opline, *end; int i, j, n, *map, cache_size; zval zv, *pos; literal_info *info; int l_null = -1; int l_false = -1; int l_true = -1; int l_empty_arr = -1; HashTable hash; zend_string *key = NULL; void *checkpoint = zend_arena_checkpoint(ctx->arena); int *const_slot, *class_slot, *func_slot, *bind_var_slot, *property_slot, *method_slot; if (op_array->last_literal) { info = (literal_info*)zend_arena_calloc(&ctx->arena, op_array->last_literal, sizeof(literal_info)); /* Mark literals of specific types */ opline = op_array->opcodes; end = opline + op_array->last; while (opline < end) { switch (opline->opcode) { case ZEND_INIT_FCALL: LITERAL_INFO(opline->op2.constant, LITERAL_FUNC, 1); break; case ZEND_INIT_FCALL_BY_NAME: LITERAL_INFO(opline->op2.constant, LITERAL_FUNC, 2); break; case ZEND_INIT_NS_FCALL_BY_NAME: LITERAL_INFO(opline->op2.constant, LITERAL_FUNC, 3); break; case ZEND_INIT_METHOD_CALL: if (opline->op1_type == IS_CONST) { LITERAL_INFO(opline->op1.constant, LITERAL_VALUE, 1); } if (opline->op2_type == IS_CONST) { LITERAL_INFO(opline->op2.constant, LITERAL_METHOD, 2); } break; case ZEND_INIT_STATIC_METHOD_CALL: if (opline->op1_type == IS_CONST) { LITERAL_INFO(opline->op1.constant, LITERAL_CLASS, 2); } if (opline->op2_type == IS_CONST) { LITERAL_INFO(opline->op2.constant, LITERAL_STATIC_METHOD, 2); } break; case ZEND_CATCH: LITERAL_INFO(opline->op1.constant, LITERAL_CLASS, 2); break; case ZEND_DEFINED: LITERAL_INFO(opline->op1.constant, LITERAL_CONST, 2); break; case ZEND_FETCH_CONSTANT: if ((opline->op1.num & (IS_CONSTANT_IN_NAMESPACE|IS_CONSTANT_UNQUALIFIED)) == (IS_CONSTANT_IN_NAMESPACE|IS_CONSTANT_UNQUALIFIED)) { LITERAL_INFO(opline->op2.constant, LITERAL_CONST, 5); } else { LITERAL_INFO(opline->op2.constant, LITERAL_CONST, 3); } break; case ZEND_FETCH_CLASS_CONSTANT: if (opline->op1_type == IS_CONST) { LITERAL_INFO(opline->op1.constant, LITERAL_CLASS, 2); } LITERAL_INFO(opline->op2.constant, LITERAL_CLASS_CONST, 1); break; case ZEND_FETCH_STATIC_PROP_R: case ZEND_FETCH_STATIC_PROP_W: case ZEND_FETCH_STATIC_PROP_RW: case ZEND_FETCH_STATIC_PROP_IS: case ZEND_FETCH_STATIC_PROP_UNSET: case ZEND_FETCH_STATIC_PROP_FUNC_ARG: case ZEND_UNSET_STATIC_PROP: case ZEND_ISSET_ISEMPTY_STATIC_PROP: if (opline->op2_type == IS_CONST) { LITERAL_INFO(opline->op2.constant, LITERAL_CLASS, 2); } if (opline->op1_type == IS_CONST) { LITERAL_INFO(opline->op1.constant, LITERAL_STATIC_PROPERTY, 1); } break; case ZEND_FETCH_CLASS: case ZEND_INSTANCEOF: if (opline->op2_type == IS_CONST) { LITERAL_INFO(opline->op2.constant, LITERAL_CLASS, 2); } break; case ZEND_NEW: if (opline->op1_type == IS_CONST) { LITERAL_INFO(opline->op1.constant, LITERAL_CLASS, 2); } break; case ZEND_ASSIGN_OBJ: case ZEND_FETCH_OBJ_R: case ZEND_FETCH_OBJ_W: case ZEND_FETCH_OBJ_RW: case ZEND_FETCH_OBJ_IS: case ZEND_FETCH_OBJ_UNSET: case ZEND_FETCH_OBJ_FUNC_ARG: case ZEND_UNSET_OBJ: case ZEND_PRE_INC_OBJ: case ZEND_PRE_DEC_OBJ: case ZEND_POST_INC_OBJ: case ZEND_POST_DEC_OBJ: case ZEND_ISSET_ISEMPTY_PROP_OBJ: if (opline->op2_type == IS_CONST) { LITERAL_INFO(opline->op2.constant, LITERAL_PROPERTY, 1); } break; case ZEND_ASSIGN_ADD: case ZEND_ASSIGN_SUB: case ZEND_ASSIGN_MUL: case ZEND_ASSIGN_DIV: case ZEND_ASSIGN_POW: case ZEND_ASSIGN_MOD: case ZEND_ASSIGN_SL: case ZEND_ASSIGN_SR: case ZEND_ASSIGN_CONCAT: case ZEND_ASSIGN_BW_OR: case ZEND_ASSIGN_BW_AND: case ZEND_ASSIGN_BW_XOR: if (opline->op2_type == IS_CONST) { if (opline->extended_value == ZEND_ASSIGN_OBJ) { LITERAL_INFO(opline->op2.constant, LITERAL_PROPERTY, 1); } else if (opline->extended_value == ZEND_ASSIGN_DIM) { if (Z_EXTRA(op_array->literals[opline->op2.constant]) == ZEND_EXTRA_VALUE) { LITERAL_INFO(opline->op2.constant, LITERAL_VALUE, 2); } else { LITERAL_INFO(opline->op2.constant, LITERAL_VALUE, 1); } } else { LITERAL_INFO(opline->op2.constant, LITERAL_VALUE, 1); } } break; case ZEND_BIND_GLOBAL: LITERAL_INFO(opline->op2.constant, LITERAL_GLOBAL, 1); break; case ZEND_RECV_INIT: LITERAL_INFO(opline->op2.constant, LITERAL_VALUE, 1); break; case ZEND_DECLARE_FUNCTION: case ZEND_DECLARE_CLASS: LITERAL_INFO(opline->op1.constant, LITERAL_VALUE, 2); break; case ZEND_DECLARE_INHERITED_CLASS: case ZEND_DECLARE_INHERITED_CLASS_DELAYED: LITERAL_INFO(opline->op1.constant, LITERAL_VALUE, 2); LITERAL_INFO(opline->op2.constant, LITERAL_VALUE, 2); break; case ZEND_DECLARE_ANON_INHERITED_CLASS: LITERAL_INFO(opline->op1.constant, LITERAL_VALUE, 1); LITERAL_INFO(opline->op2.constant, LITERAL_VALUE, 2); break; case ZEND_ISSET_ISEMPTY_DIM_OBJ: case ZEND_ASSIGN_DIM: case ZEND_UNSET_DIM: case ZEND_FETCH_DIM_R: case ZEND_FETCH_DIM_W: case ZEND_FETCH_DIM_RW: case ZEND_FETCH_DIM_IS: case ZEND_FETCH_DIM_FUNC_ARG: case ZEND_FETCH_DIM_UNSET: case ZEND_FETCH_LIST_R: case ZEND_FETCH_LIST_W: if (opline->op1_type == IS_CONST) { LITERAL_INFO(opline->op1.constant, LITERAL_VALUE, 1); } if (opline->op2_type == IS_CONST) { if (Z_EXTRA(op_array->literals[opline->op2.constant]) == ZEND_EXTRA_VALUE) { LITERAL_INFO(opline->op2.constant, LITERAL_VALUE, 2); } else { LITERAL_INFO(opline->op2.constant, LITERAL_VALUE, 1); } } break; default: if (opline->op1_type == IS_CONST) { LITERAL_INFO(opline->op1.constant, LITERAL_VALUE, 1); } if (opline->op2_type == IS_CONST) { LITERAL_INFO(opline->op2.constant, LITERAL_VALUE, 1); } break; } opline++; } #if DEBUG_COMPACT_LITERALS { int i, use_copy; fprintf(stderr, "File %s func %s\n", op_array->filename->val, op_array->function_name ? op_array->function_name->val : "main"); fprintf(stderr, "Literlas table size %d\n", op_array->last_literal); for (i = 0; i < op_array->last_literal; i++) { zval zv; ZVAL_COPY_VALUE(&zv, op_array->literals + i); use_copy = zend_make_printable_zval(op_array->literals + i, &zv); fprintf(stderr, "Literal %d, val (%d):%s\n", i, Z_STRLEN(zv), Z_STRVAL(zv)); if (use_copy) { zval_ptr_dtor_nogc(&zv); } } fflush(stderr); } #endif /* Merge equal constants */ j = 0; zend_hash_init(&hash, op_array->last_literal, NULL, NULL, 0); map = (int*)zend_arena_alloc(&ctx->arena, op_array->last_literal * sizeof(int)); memset(map, 0, op_array->last_literal * sizeof(int)); for (i = 0; i < op_array->last_literal; i++) { if (!info[i].flags) { /* unset literal */ zval_ptr_dtor_nogc(&op_array->literals[i]); continue; } switch (Z_TYPE(op_array->literals[i])) { case IS_NULL: if (l_null < 0) { l_null = j; if (i != j) { op_array->literals[j] = op_array->literals[i]; info[j] = info[i]; } j++; } map[i] = l_null; break; case IS_FALSE: if (l_false < 0) { l_false = j; if (i != j) { op_array->literals[j] = op_array->literals[i]; info[j] = info[i]; } j++; } map[i] = l_false; break; case IS_TRUE: if (l_true < 0) { l_true = j; if (i != j) { op_array->literals[j] = op_array->literals[i]; info[j] = info[i]; } j++; } map[i] = l_true; break; case IS_LONG: if (LITERAL_NUM_RELATED(info[i].flags) == 1) { if ((pos = zend_hash_index_find(&hash, Z_LVAL(op_array->literals[i]))) != NULL) { map[i] = Z_LVAL_P(pos); } else { map[i] = j; ZVAL_LONG(&zv, j); zend_hash_index_add_new(&hash, Z_LVAL(op_array->literals[i]), &zv); if (i != j) { op_array->literals[j] = op_array->literals[i]; info[j] = info[i]; } j++; } } else { ZEND_ASSERT(LITERAL_NUM_RELATED(info[i].flags) == 2); key = zend_string_init(Z_STRVAL(op_array->literals[i+1]), Z_STRLEN(op_array->literals[i+1]), 0); ZSTR_H(key) = ZSTR_HASH(Z_STR(op_array->literals[i+1])) + 100 + LITERAL_NUM_RELATED(info[i].flags) - 1; if ((pos = zend_hash_find(&hash, key)) != NULL && LITERAL_NUM_RELATED(info[Z_LVAL_P(pos)].flags) == 2) { map[i] = Z_LVAL_P(pos); zval_ptr_dtor_nogc(&op_array->literals[i+1]); } else { map[i] = j; ZVAL_LONG(&zv, j); zend_hash_add_new(&hash, key, &zv); if (i != j) { op_array->literals[j] = op_array->literals[i]; info[j] = info[i]; op_array->literals[j+1] = op_array->literals[i+1]; info[j+1] = info[i+1]; } j += 2; } zend_string_release_ex(key, 0); i++; } break; case IS_DOUBLE: if ((pos = zend_hash_str_find(&hash, (char*)&Z_DVAL(op_array->literals[i]), sizeof(double))) != NULL) { map[i] = Z_LVAL_P(pos); } else { map[i] = j; ZVAL_LONG(&zv, j); zend_hash_str_add(&hash, (char*)&Z_DVAL(op_array->literals[i]), sizeof(double), &zv); if (i != j) { op_array->literals[j] = op_array->literals[i]; info[j] = info[i]; } j++; } break; case IS_STRING: if (LITERAL_NUM_RELATED(info[i].flags) == 1) { key = zend_string_copy(Z_STR(op_array->literals[i])); } else { key = zend_string_init(Z_STRVAL(op_array->literals[i]), Z_STRLEN(op_array->literals[i]), 0); ZSTR_H(key) = ZSTR_HASH(Z_STR(op_array->literals[i])) + LITERAL_NUM_RELATED(info[i].flags) - 1; } pos = zend_hash_find(&hash, key); if (pos != NULL && Z_TYPE(op_array->literals[Z_LVAL_P(pos)]) == IS_STRING && LITERAL_NUM_RELATED(info[i].flags) == LITERAL_NUM_RELATED(info[Z_LVAL_P(pos)].flags) && (LITERAL_NUM_RELATED(info[i].flags) != 2 || ((info[i].flags & LITERAL_KIND_MASK) != LITERAL_VALUE && (info[Z_LVAL_P(pos)].flags & LITERAL_KIND_MASK) != LITERAL_VALUE))) { zend_string_release_ex(key, 0); map[i] = Z_LVAL_P(pos); zval_ptr_dtor_nogc(&op_array->literals[i]); n = LITERAL_NUM_RELATED(info[i].flags); while (n > 1) { i++; zval_ptr_dtor_nogc(&op_array->literals[i]); n--; } } else { map[i] = j; ZVAL_LONG(&zv, j); zend_hash_add_new(&hash, key, &zv); zend_string_release_ex(key, 0); if (i != j) { op_array->literals[j] = op_array->literals[i]; info[j] = info[i]; } j++; n = LITERAL_NUM_RELATED(info[i].flags); while (n > 1) { i++; if (i != j) op_array->literals[j] = op_array->literals[i]; j++; n--; } } break; case IS_ARRAY: if (zend_hash_num_elements(Z_ARRVAL(op_array->literals[i])) == 0) { if (l_empty_arr < 0) { l_empty_arr = j; if (i != j) { op_array->literals[j] = op_array->literals[i]; info[j] = info[i]; } j++; } else { zval_ptr_dtor_nogc(&op_array->literals[i]); } map[i] = l_empty_arr; break; } /* break missing intentionally */ default: /* don't merge other types */ map[i] = j; if (i != j) { op_array->literals[j] = op_array->literals[i]; info[j] = info[i]; } j++; break; } } zend_hash_clean(&hash); op_array->last_literal = j; const_slot = zend_arena_alloc(&ctx->arena, j * 6 * sizeof(int)); memset(const_slot, -1, j * 6 * sizeof(int)); class_slot = const_slot + j; func_slot = class_slot + j; bind_var_slot = func_slot + j; property_slot = bind_var_slot + j; method_slot = property_slot + j; /* Update opcodes to use new literals table */ cache_size = 0; opline = op_array->opcodes; end = opline + op_array->last; while (opline < end) { if (opline->op1_type == IS_CONST) { opline->op1.constant = map[opline->op1.constant]; } if (opline->op2_type == IS_CONST) { opline->op2.constant = map[opline->op2.constant]; } switch (opline->opcode) { case ZEND_RECV_INIT: if (class_name_type_hint(op_array, opline->op1.num)) { opline->extended_value = cache_size; cache_size += sizeof(void *); } break; case ZEND_RECV: case ZEND_RECV_VARIADIC: if (class_name_type_hint(op_array, opline->op1.num)) { opline->op2.num = cache_size; cache_size += sizeof(void *); } break; case ZEND_VERIFY_RETURN_TYPE: if (class_name_type_hint(op_array, 0)) { opline->op2.num = cache_size; cache_size += sizeof(void *); } break; case ZEND_ASSIGN_ADD: case ZEND_ASSIGN_SUB: case ZEND_ASSIGN_MUL: case ZEND_ASSIGN_DIV: case ZEND_ASSIGN_POW: case ZEND_ASSIGN_MOD: case ZEND_ASSIGN_SL: case ZEND_ASSIGN_SR: case ZEND_ASSIGN_CONCAT: case ZEND_ASSIGN_BW_OR: case ZEND_ASSIGN_BW_AND: case ZEND_ASSIGN_BW_XOR: if (opline->extended_value != ZEND_ASSIGN_OBJ) { break; } if (opline->op2_type == IS_CONST) { // op2 property if (opline->op1_type == IS_UNUSED && property_slot[opline->op2.constant] >= 0) { (opline+1)->extended_value = property_slot[opline->op2.constant]; } else { (opline+1)->extended_value = cache_size; cache_size += 2 * sizeof(void *); if (opline->op1_type == IS_UNUSED) { property_slot[opline->op2.constant] = (opline+1)->extended_value; } } } break; case ZEND_ASSIGN_OBJ: case ZEND_FETCH_OBJ_R: case ZEND_FETCH_OBJ_W: case ZEND_FETCH_OBJ_RW: case ZEND_FETCH_OBJ_IS: case ZEND_FETCH_OBJ_UNSET: case ZEND_FETCH_OBJ_FUNC_ARG: case ZEND_UNSET_OBJ: case ZEND_PRE_INC_OBJ: case ZEND_PRE_DEC_OBJ: case ZEND_POST_INC_OBJ: case ZEND_POST_DEC_OBJ: if (opline->op2_type == IS_CONST) { // op2 property if (opline->op1_type == IS_UNUSED && property_slot[opline->op2.constant] >= 0) { opline->extended_value = property_slot[opline->op2.constant]; } else { opline->extended_value = cache_size; cache_size += 2 * sizeof(void *); if (opline->op1_type == IS_UNUSED) { property_slot[opline->op2.constant] = opline->extended_value; } } } break; case ZEND_ISSET_ISEMPTY_PROP_OBJ: if (opline->op2_type == IS_CONST) { // op2 property if (opline->op1_type == IS_UNUSED && property_slot[opline->op2.constant] >= 0) { opline->extended_value = property_slot[opline->op2.constant] | (opline->extended_value & ZEND_ISEMPTY); } else { opline->extended_value = cache_size | (opline->extended_value & ZEND_ISEMPTY); cache_size += 2 * sizeof(void *); if (opline->op1_type == IS_UNUSED) { property_slot[opline->op2.constant] = opline->extended_value & ~ZEND_ISEMPTY; } } } break; case ZEND_INIT_FCALL: case ZEND_INIT_FCALL_BY_NAME: case ZEND_INIT_NS_FCALL_BY_NAME: // op2 func if (func_slot[opline->op2.constant] >= 0) { opline->result.num = func_slot[opline->op2.constant]; } else { opline->result.num = cache_size; cache_size += sizeof(void *); func_slot[opline->op2.constant] = opline->result.num; } break; case ZEND_INIT_METHOD_CALL: if (opline->op2_type == IS_CONST) { // op2 method if (opline->op1_type == IS_UNUSED && method_slot[opline->op2.constant] >= 0) { opline->result.num = method_slot[opline->op2.constant]; } else { opline->result.num = cache_size; cache_size += 2 * sizeof(void *); if (opline->op1_type == IS_UNUSED) { method_slot[opline->op2.constant] = opline->result.num; } } } break; case ZEND_INIT_STATIC_METHOD_CALL: if (opline->op2_type == IS_CONST) { // op2 static method if (opline->op1_type == IS_CONST) { opline->result.num = add_static_slot(&hash, op_array, opline->op1.constant, opline->op2.constant, LITERAL_STATIC_METHOD, &cache_size); } else { opline->result.num = cache_size; cache_size += 2 * sizeof(void *); } } else if (opline->op1_type == IS_CONST) { // op1 class if (class_slot[opline->op1.constant] >= 0) { opline->result.num = class_slot[opline->op1.constant]; } else { opline->result.num = cache_size; cache_size += sizeof(void *); class_slot[opline->op1.constant] = opline->result.num; } } break; case ZEND_DEFINED: // op1 const if (const_slot[opline->op1.constant] >= 0) { opline->extended_value = const_slot[opline->op1.constant]; } else { opline->extended_value = cache_size; cache_size += sizeof(void *); const_slot[opline->op1.constant] = opline->extended_value; } break; case ZEND_FETCH_CONSTANT: // op2 const if (const_slot[opline->op2.constant] >= 0) { opline->extended_value = const_slot[opline->op2.constant]; } else { opline->extended_value = cache_size; cache_size += sizeof(void *); const_slot[opline->op2.constant] = opline->extended_value; } break; case ZEND_FETCH_CLASS_CONSTANT: if (opline->op1_type == IS_CONST) { // op1/op2 class_const opline->extended_value = add_static_slot(&hash, op_array, opline->op1.constant, opline->op2.constant, LITERAL_CLASS_CONST, &cache_size); } else { opline->extended_value = cache_size; cache_size += 2 * sizeof(void *); } break; case ZEND_FETCH_STATIC_PROP_R: case ZEND_FETCH_STATIC_PROP_W: case ZEND_FETCH_STATIC_PROP_RW: case ZEND_FETCH_STATIC_PROP_IS: case ZEND_FETCH_STATIC_PROP_UNSET: case ZEND_FETCH_STATIC_PROP_FUNC_ARG: case ZEND_UNSET_STATIC_PROP: if (opline->op1_type == IS_CONST) { // op1 static property if (opline->op2_type == IS_CONST) { opline->extended_value = add_static_slot(&hash, op_array, opline->op2.constant, opline->op1.constant, LITERAL_STATIC_PROPERTY, &cache_size); } else { opline->extended_value = cache_size; cache_size += 2 * sizeof(void *); } } else if (opline->op2_type == IS_CONST) { // op2 class if (class_slot[opline->op2.constant] >= 0) { opline->extended_value = class_slot[opline->op2.constant]; } else { opline->extended_value = cache_size; cache_size += sizeof(void *); class_slot[opline->op2.constant] = opline->extended_value; } } break; case ZEND_ISSET_ISEMPTY_STATIC_PROP: if (opline->op1_type == IS_CONST) { // op1 static property if (opline->op2_type == IS_CONST) { opline->extended_value = add_static_slot(&hash, op_array, opline->op2.constant, opline->op1.constant, LITERAL_STATIC_PROPERTY, &cache_size) | (opline->extended_value & ZEND_ISEMPTY); } else { opline->extended_value = cache_size | (opline->extended_value & ZEND_ISEMPTY); cache_size += 2 * sizeof(void *); } } else if (opline->op2_type == IS_CONST) { // op2 class if (class_slot[opline->op2.constant] >= 0) { opline->extended_value = class_slot[opline->op2.constant] | (opline->extended_value & ZEND_ISEMPTY); } else { opline->extended_value = cache_size | (opline->extended_value & ZEND_ISEMPTY); cache_size += sizeof(void *); class_slot[opline->op2.constant] = opline->extended_value & ~ZEND_ISEMPTY; } } break; case ZEND_FETCH_CLASS: case ZEND_INSTANCEOF: if (opline->op2_type == IS_CONST) { // op2 class if (class_slot[opline->op2.constant] >= 0) { opline->extended_value = class_slot[opline->op2.constant]; } else { opline->extended_value = cache_size; cache_size += sizeof(void *); class_slot[opline->op2.constant] = opline->extended_value; } } break; case ZEND_NEW: if (opline->op1_type == IS_CONST) { // op1 class if (class_slot[opline->op1.constant] >= 0) { opline->op2.num = class_slot[opline->op1.constant]; } else { opline->op2.num = cache_size; cache_size += sizeof(void *); class_slot[opline->op1.constant] = opline->op2.num; } } break; case ZEND_CATCH: if (opline->op1_type == IS_CONST) { // op1 class if (class_slot[opline->op1.constant] >= 0) { opline->extended_value = class_slot[opline->op1.constant] | (opline->extended_value & ZEND_LAST_CATCH); } else { opline->extended_value = cache_size | (opline->extended_value & ZEND_LAST_CATCH); cache_size += sizeof(void *); class_slot[opline->op1.constant] = opline->extended_value & ~ZEND_LAST_CATCH; } } break; case ZEND_BIND_GLOBAL: // op2 bind var if (bind_var_slot[opline->op2.constant] >= 0) { opline->extended_value = bind_var_slot[opline->op2.constant]; } else { opline->extended_value = cache_size; cache_size += sizeof(void *); bind_var_slot[opline->op2.constant] = opline->extended_value; } break; } opline++; } op_array->cache_size = cache_size; zend_hash_destroy(&hash); zend_arena_release(&ctx->arena, checkpoint); if (1) { opline = op_array->opcodes; while (1) { if (opline->opcode == ZEND_RECV_INIT) { zval *val = &op_array->literals[opline->op2.constant]; if (Z_TYPE_P(val) == IS_CONSTANT_AST) { uint32_t slot = ZEND_MM_ALIGNED_SIZE_EX(op_array->cache_size, 8); Z_CACHE_SLOT_P(val) = slot; op_array->cache_size += sizeof(zval); } } else if (opline->opcode != ZEND_RECV) { break; } opline++; } } #if DEBUG_COMPACT_LITERALS { int i, use_copy; fprintf(stderr, "Optimized literlas table size %d\n", op_array->last_literal); for (i = 0; i < op_array->last_literal; i++) { zval zv; ZVAL_COPY_VALUE(&zv, op_array->literals + i); use_copy = zend_make_printable_zval(op_array->literals + i, &zv); fprintf(stderr, "Literal %d, val (%d):%s\n", i, Z_STRLEN(zv), Z_STRVAL(zv)); if (use_copy) { zval_ptr_dtor_nogc(&zv); } } fflush(stderr); } #endif } }
void zend_optimize_temporary_variables(zend_op_array *op_array, zend_optimizer_ctx *ctx) { int T = op_array->T; int offset = op_array->last_var; uint32_t bitset_len; zend_bitset taken_T; /* T index in use */ zend_op **start_of_T; /* opline where T is first used */ zend_bitset valid_T; /* Is the map_T valid */ int *map_T; /* Map's the T to its new index */ zend_op *opline, *end; int currT; int i; int max = -1; int var_to_free = -1; void *checkpoint = zend_arena_checkpoint(ctx->arena); bitset_len = zend_bitset_len(T); taken_T = (zend_bitset) zend_arena_alloc(&ctx->arena, bitset_len * ZEND_BITSET_ELM_SIZE); start_of_T = (zend_op **) zend_arena_alloc(&ctx->arena, T * sizeof(zend_op *)); valid_T = (zend_bitset) zend_arena_alloc(&ctx->arena, bitset_len * ZEND_BITSET_ELM_SIZE); map_T = (int *) zend_arena_alloc(&ctx->arena, T * sizeof(int)); end = op_array->opcodes; opline = &op_array->opcodes[op_array->last - 1]; /* Find T definition points */ while (opline >= end) { if (ZEND_RESULT_TYPE(opline) & (IS_VAR | IS_TMP_VAR)) { start_of_T[VAR_NUM(ZEND_RESULT(opline).var) - offset] = opline; } opline--; } zend_bitset_clear(valid_T, bitset_len); zend_bitset_clear(taken_T, bitset_len); end = op_array->opcodes; opline = &op_array->opcodes[op_array->last - 1]; while (opline >= end) { if ((ZEND_OP1_TYPE(opline) & (IS_VAR | IS_TMP_VAR))) { currT = VAR_NUM(ZEND_OP1(opline).var) - offset; if (opline->opcode == ZEND_ROPE_END) { int num = (((opline->extended_value + 1) * sizeof(zend_string*)) + (sizeof(zval) - 1)) / sizeof(zval); int var; var = max; while (var >= 0 && !zend_bitset_in(taken_T, var)) { var--; } max = MAX(max, var + num); var = var + 1; map_T[currT] = var; zend_bitset_incl(valid_T, currT); zend_bitset_incl(taken_T, var); ZEND_OP1(opline).var = NUM_VAR(var + offset); while (num > 1) { num--; zend_bitset_incl(taken_T, var + num); } } else { if (!zend_bitset_in(valid_T, currT)) { int use_new_var = 0; /* Code in "finally" blocks may modify temorary variables. * We allocate new temporaries for values that need to * relive FAST_CALLs. */ if ((op_array->fn_flags & ZEND_ACC_HAS_FINALLY_BLOCK) && (opline->opcode == ZEND_RETURN || opline->opcode == ZEND_GENERATOR_RETURN || opline->opcode == ZEND_RETURN_BY_REF || opline->opcode == ZEND_FREE || opline->opcode == ZEND_FE_FREE)) { zend_op *curr = opline; while (--curr >= end) { if (curr->opcode == ZEND_FAST_CALL) { use_new_var = 1; break; } else if (curr->opcode != ZEND_FREE && curr->opcode != ZEND_FE_FREE && curr->opcode != ZEND_VERIFY_RETURN_TYPE && curr->opcode != ZEND_DISCARD_EXCEPTION) { break; } } } if (use_new_var) { i = ++max; zend_bitset_incl(taken_T, i); } else { GET_AVAILABLE_T(); } map_T[currT] = i; zend_bitset_incl(valid_T, currT); } ZEND_OP1(opline).var = NUM_VAR(map_T[currT] + offset); } } if ((ZEND_OP2_TYPE(opline) & (IS_VAR | IS_TMP_VAR))) { currT = VAR_NUM(ZEND_OP2(opline).var) - offset; if (!zend_bitset_in(valid_T, currT)) { GET_AVAILABLE_T(); map_T[currT] = i; zend_bitset_incl(valid_T, currT); } ZEND_OP2(opline).var = NUM_VAR(map_T[currT] + offset); } if (ZEND_RESULT_TYPE(opline) & (IS_VAR | IS_TMP_VAR)) { currT = VAR_NUM(ZEND_RESULT(opline).var) - offset; if (zend_bitset_in(valid_T, currT)) { if (start_of_T[currT] == opline) { /* ZEND_FAST_CALL can not share temporary var with others * since the fast_var could also be set by ZEND_HANDLE_EXCEPTION * which could be ahead of it */ if (opline->opcode != ZEND_FAST_CALL) { zend_bitset_excl(taken_T, map_T[currT]); } } ZEND_RESULT(opline).var = NUM_VAR(map_T[currT] + offset); if (opline->opcode == ZEND_ROPE_INIT) { if (start_of_T[currT] == opline) { uint32_t num = ((opline->extended_value * sizeof(zend_string*)) + (sizeof(zval) - 1)) / sizeof(zval); while (num > 1) { num--; zend_bitset_excl(taken_T, map_T[currT]+num); } } } } else { /* Code which gets here is using a wrongly built opcode such as RECV() */ GET_AVAILABLE_T(); map_T[currT] = i; zend_bitset_incl(valid_T, currT); ZEND_RESULT(opline).var = NUM_VAR(i + offset); } } if (var_to_free >= 0) { zend_bitset_excl(taken_T, var_to_free); var_to_free = -1; } opline--; } if (op_array->live_range) { for (i = 0; i < op_array->last_live_range; i++) { op_array->live_range[i].var = NUM_VAR(map_T[VAR_NUM(op_array->live_range[i].var & ~ZEND_LIVE_MASK) - offset] + offset) | (op_array->live_range[i].var & ZEND_LIVE_MASK); } } zend_arena_release(&ctx->arena, checkpoint); op_array->T = max + 1; }
zend_op_array* zend_accel_load_script(zend_persistent_script *persistent_script, int from_shared_memory) { zend_op_array *op_array; op_array = (zend_op_array *) emalloc(sizeof(zend_op_array)); *op_array = persistent_script->main_op_array; if (EXPECTED(from_shared_memory)) { zend_hash_init(&ZCG(bind_hash), 10, NULL, NULL, 0); ZCG(current_persistent_script) = persistent_script; ZCG(arena_mem) = NULL; if (EXPECTED(persistent_script->arena_size)) { #ifdef __SSE2__ /* Target address must be aligned to 64-byte boundary */ ZCG(arena_mem) = zend_arena_alloc(&CG(arena), persistent_script->arena_size + 64); ZCG(arena_mem) = (void*)(((zend_uintptr_t)ZCG(arena_mem) + 63L) & ~63L); fast_memcpy(ZCG(arena_mem), persistent_script->arena_mem, persistent_script->arena_size); #else ZCG(arena_mem) = zend_arena_alloc(&CG(arena), persistent_script->arena_size); memcpy(ZCG(arena_mem), persistent_script->arena_mem, persistent_script->arena_size); #endif } /* Copy all the necessary stuff from shared memory to regular memory, and protect the shared script */ if (zend_hash_num_elements(&persistent_script->class_table) > 0) { zend_accel_class_hash_copy(CG(class_table), &persistent_script->class_table, (unique_copy_ctor_func_t) zend_class_copy_ctor); } /* we must first to copy all classes and then prepare functions, since functions may try to bind classes - which depend on pre-bind class entries existent in the class table */ if (zend_hash_num_elements(&persistent_script->function_table) > 0) { zend_accel_function_hash_copy_from_shm(CG(function_table), &persistent_script->function_table); } /* Register __COMPILER_HALT_OFFSET__ constant */ if (persistent_script->compiler_halt_offset != 0 && persistent_script->full_path) { zend_string *name; char haltoff[] = "__COMPILER_HALT_OFFSET__"; name = zend_mangle_property_name(haltoff, sizeof(haltoff) - 1, persistent_script->full_path->val, persistent_script->full_path->len, 0); if (!zend_hash_exists(EG(zend_constants), name)) { zend_register_long_constant(name->val, name->len, persistent_script->compiler_halt_offset, CONST_CS, 0); } zend_string_release(name); } zend_hash_destroy(&ZCG(bind_hash)); ZCG(current_persistent_script) = NULL; } else /* if (!from_shared_memory) */ { if (zend_hash_num_elements(&persistent_script->function_table) > 0) { zend_accel_function_hash_copy(CG(function_table), &persistent_script->function_table); } if (zend_hash_num_elements(&persistent_script->class_table) > 0) { zend_accel_class_hash_copy(CG(class_table), &persistent_script->class_table, NULL); } } if (op_array->early_binding != (uint32_t)-1) { zend_string *orig_compiled_filename = CG(compiled_filename); CG(compiled_filename) = persistent_script->full_path; zend_do_delayed_early_binding(op_array); CG(compiled_filename) = orig_compiled_filename; } if (UNEXPECTED(!from_shared_memory)) { free_persistent_script(persistent_script, 0); /* free only hashes */ } return op_array; }
void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx *ctx) { zend_op *opline, *end; int i, j, n, *map, cache_size; zval zv, *pos; literal_info *info; int l_null = -1; int l_false = -1; int l_true = -1; int l_empty_arr = -1; HashTable hash; zend_string *key = NULL; void *checkpoint = zend_arena_checkpoint(ctx->arena); if (op_array->last_literal) { cache_size = 0; info = (literal_info*)zend_arena_calloc(&ctx->arena, op_array->last_literal, sizeof(literal_info)); /* Mark literals of specific types */ opline = op_array->opcodes; end = opline + op_array->last; while (opline < end) { switch (opline->opcode) { case ZEND_INIT_FCALL: LITERAL_INFO(opline->op2.constant, LITERAL_FUNC, 1, 1, 1); break; case ZEND_INIT_FCALL_BY_NAME: LITERAL_INFO(opline->op2.constant, LITERAL_FUNC, 1, 1, 2); break; case ZEND_INIT_NS_FCALL_BY_NAME: LITERAL_INFO(opline->op2.constant, LITERAL_FUNC, 1, 1, 3); break; case ZEND_INIT_METHOD_CALL: if (ZEND_OP2_TYPE(opline) == IS_CONST) { optimizer_literal_obj_info( info, opline->op1_type, opline->op1, opline->op2.constant, LITERAL_METHOD, 2, 2, op_array); } break; case ZEND_INIT_STATIC_METHOD_CALL: if (ZEND_OP1_TYPE(opline) == IS_CONST) { LITERAL_INFO(opline->op1.constant, LITERAL_CLASS, 1, 1, 2); } if (ZEND_OP2_TYPE(opline) == IS_CONST) { optimizer_literal_class_info( info, opline->op1_type, opline->op1, opline->op2.constant, LITERAL_STATIC_METHOD, (ZEND_OP1_TYPE(opline) == IS_CONST) ? 1 : 2, 2, op_array); } break; case ZEND_CATCH: LITERAL_INFO(opline->op1.constant, LITERAL_CLASS, 1, 1, 2); break; case ZEND_DEFINED: LITERAL_INFO(opline->op1.constant, LITERAL_CONST, 1, 1, 2); break; case ZEND_FETCH_CONSTANT: if ((opline->extended_value & (IS_CONSTANT_IN_NAMESPACE|IS_CONSTANT_UNQUALIFIED)) == (IS_CONSTANT_IN_NAMESPACE|IS_CONSTANT_UNQUALIFIED)) { LITERAL_INFO(opline->op2.constant, LITERAL_CONST, 1, 1, 5); } else { LITERAL_INFO(opline->op2.constant, LITERAL_CONST, 1, 1, 3); } break; case ZEND_FETCH_CLASS_CONSTANT: if (ZEND_OP1_TYPE(opline) == IS_CONST) { LITERAL_INFO(opline->op1.constant, LITERAL_CLASS, 1, 1, 2); } optimizer_literal_class_info( info, opline->op1_type, opline->op1, opline->op2.constant, LITERAL_CLASS_CONST, (ZEND_OP1_TYPE(opline) == IS_CONST) ? 1 : 2, 1, op_array); break; case ZEND_FETCH_STATIC_PROP_R: case ZEND_FETCH_STATIC_PROP_W: case ZEND_FETCH_STATIC_PROP_RW: case ZEND_FETCH_STATIC_PROP_IS: case ZEND_FETCH_STATIC_PROP_UNSET: case ZEND_FETCH_STATIC_PROP_FUNC_ARG: case ZEND_UNSET_STATIC_PROP: case ZEND_ISSET_ISEMPTY_STATIC_PROP: if (ZEND_OP2_TYPE(opline) == IS_CONST) { LITERAL_INFO(opline->op2.constant, LITERAL_CLASS, 1, 1, 2); } if (ZEND_OP1_TYPE(opline) == IS_CONST) { optimizer_literal_class_info( info, opline->op2_type, opline->op2, opline->op1.constant, LITERAL_STATIC_PROPERTY, 2, 1, op_array); } break; case ZEND_FETCH_CLASS: case ZEND_ADD_INTERFACE: case ZEND_ADD_TRAIT: case ZEND_INSTANCEOF: if (ZEND_OP2_TYPE(opline) == IS_CONST) { LITERAL_INFO(opline->op2.constant, LITERAL_CLASS, 1, 1, 2); } break; case ZEND_NEW: if (ZEND_OP1_TYPE(opline) == IS_CONST) { LITERAL_INFO(opline->op1.constant, LITERAL_CLASS, 1, 1, 2); } break; case ZEND_ASSIGN_OBJ: case ZEND_FETCH_OBJ_R: case ZEND_FETCH_OBJ_W: case ZEND_FETCH_OBJ_RW: case ZEND_FETCH_OBJ_IS: case ZEND_FETCH_OBJ_UNSET: case ZEND_FETCH_OBJ_FUNC_ARG: case ZEND_UNSET_OBJ: case ZEND_PRE_INC_OBJ: case ZEND_PRE_DEC_OBJ: case ZEND_POST_INC_OBJ: case ZEND_POST_DEC_OBJ: case ZEND_ISSET_ISEMPTY_PROP_OBJ: if (ZEND_OP2_TYPE(opline) == IS_CONST) { optimizer_literal_obj_info( info, opline->op1_type, opline->op1, opline->op2.constant, LITERAL_PROPERTY, 2, 1, op_array); } break; case ZEND_ASSIGN_ADD: case ZEND_ASSIGN_SUB: case ZEND_ASSIGN_MUL: case ZEND_ASSIGN_DIV: case ZEND_ASSIGN_POW: case ZEND_ASSIGN_MOD: case ZEND_ASSIGN_SL: case ZEND_ASSIGN_SR: case ZEND_ASSIGN_CONCAT: case ZEND_ASSIGN_BW_OR: case ZEND_ASSIGN_BW_AND: case ZEND_ASSIGN_BW_XOR: if (ZEND_OP2_TYPE(opline) == IS_CONST) { if (opline->extended_value == ZEND_ASSIGN_OBJ) { optimizer_literal_obj_info( info, opline->op1_type, opline->op1, opline->op2.constant, LITERAL_PROPERTY, 2, 1, op_array); } else { LITERAL_INFO(opline->op2.constant, LITERAL_VALUE, 1, 0, 1); } } break; case ZEND_BIND_GLOBAL: LITERAL_INFO(opline->op2.constant, LITERAL_GLOBAL, 0, 1, 1); break; case ZEND_RECV_INIT: LITERAL_INFO(opline->op2.constant, LITERAL_VALUE, 0, 0, 1); if (Z_CACHE_SLOT(op_array->literals[opline->op2.constant]) != (uint32_t)-1) { Z_CACHE_SLOT(op_array->literals[opline->op2.constant]) = cache_size; cache_size += sizeof(void *); } break; case ZEND_DECLARE_FUNCTION: case ZEND_DECLARE_CLASS: case ZEND_DECLARE_INHERITED_CLASS: case ZEND_DECLARE_INHERITED_CLASS_DELAYED: LITERAL_INFO(opline->op1.constant, LITERAL_VALUE, 0, 0, 2); break; case ZEND_RECV: case ZEND_RECV_VARIADIC: case ZEND_VERIFY_RETURN_TYPE: if (opline->op2.num != (uint32_t)-1) { opline->op2.num = cache_size; cache_size += sizeof(void *); } default: if (ZEND_OP1_TYPE(opline) == IS_CONST) { LITERAL_INFO(opline->op1.constant, LITERAL_VALUE, 1, 0, 1); } if (ZEND_OP2_TYPE(opline) == IS_CONST) { LITERAL_INFO(opline->op2.constant, LITERAL_VALUE, 1, 0, 1); } break; } opline++; } #if DEBUG_COMPACT_LITERALS { int i, use_copy; fprintf(stderr, "File %s func %s\n", op_array->filename->val, op_array->function_name ? op_array->function_name->val : "main"); fprintf(stderr, "Literlas table size %d\n", op_array->last_literal); for (i = 0; i < op_array->last_literal; i++) { zval zv; ZVAL_COPY_VALUE(&zv, op_array->literals + i); use_copy = zend_make_printable_zval(op_array->literals + i, &zv); fprintf(stderr, "Literal %d, val (%d):%s\n", i, Z_STRLEN(zv), Z_STRVAL(zv)); if (use_copy) { zval_dtor(&zv); } } fflush(stderr); } #endif /* Merge equal constants */ j = 0; zend_hash_init(&hash, op_array->last_literal, NULL, NULL, 0); map = (int*)zend_arena_alloc(&ctx->arena, op_array->last_literal * sizeof(int)); memset(map, 0, op_array->last_literal * sizeof(int)); for (i = 0; i < op_array->last_literal; i++) { if (!info[i].flags) { /* unsed literal */ zval_dtor(&op_array->literals[i]); continue; } switch (Z_TYPE(op_array->literals[i])) { case IS_NULL: /* Only checking MAY_MERGE for IS_NULL here * is because only IS_NULL can be default value for class type hinting(RECV_INIT). */ if ((info[i].flags & LITERAL_MAY_MERGE)) { if (l_null < 0) { l_null = j; if (i != j) { op_array->literals[j] = op_array->literals[i]; info[j] = info[i]; } j++; } map[i] = l_null; } else { map[i] = j; if (i != j) { op_array->literals[j] = op_array->literals[i]; info[j] = info[i]; } j++; } break; case IS_FALSE: if (l_false < 0) { l_false = j; if (i != j) { op_array->literals[j] = op_array->literals[i]; info[j] = info[i]; } j++; } map[i] = l_false; break; case IS_TRUE: if (l_true < 0) { l_true = j; if (i != j) { op_array->literals[j] = op_array->literals[i]; info[j] = info[i]; } j++; } map[i] = l_true; break; case IS_LONG: if ((pos = zend_hash_index_find(&hash, Z_LVAL(op_array->literals[i]))) != NULL) { map[i] = Z_LVAL_P(pos); } else { map[i] = j; ZVAL_LONG(&zv, j); zend_hash_index_add_new(&hash, Z_LVAL(op_array->literals[i]), &zv); if (i != j) { op_array->literals[j] = op_array->literals[i]; info[j] = info[i]; } j++; } break; case IS_DOUBLE: if ((pos = zend_hash_str_find(&hash, (char*)&Z_DVAL(op_array->literals[i]), sizeof(double))) != NULL) { map[i] = Z_LVAL_P(pos); } else { map[i] = j; ZVAL_LONG(&zv, j); zend_hash_str_add(&hash, (char*)&Z_DVAL(op_array->literals[i]), sizeof(double), &zv); if (i != j) { op_array->literals[j] = op_array->literals[i]; info[j] = info[i]; } j++; } break; case IS_STRING: case IS_CONSTANT: if (info[i].flags & LITERAL_MAY_MERGE) { if (info[i].flags & LITERAL_EX_OBJ) { int key_len = sizeof("$this->") - 1 + Z_STRLEN(op_array->literals[i]); key = zend_string_alloc(key_len, 0); memcpy(ZSTR_VAL(key), "$this->", sizeof("$this->") - 1); memcpy(ZSTR_VAL(key) + sizeof("$this->") - 1, Z_STRVAL(op_array->literals[i]), Z_STRLEN(op_array->literals[i]) + 1); ZSTR_LEN(key) = key_len; } else if (info[i].flags & LITERAL_EX_CLASS) { int key_len; zval *class_name = &op_array->literals[(info[i].u.num < i) ? map[info[i].u.num] : info[i].u.num]; key_len = Z_STRLEN_P(class_name) + sizeof("::") - 1 + Z_STRLEN(op_array->literals[i]); key = zend_string_alloc(key_len, 0); memcpy(ZSTR_VAL(key), Z_STRVAL_P(class_name), Z_STRLEN_P(class_name)); memcpy(ZSTR_VAL(key) + Z_STRLEN_P(class_name), "::", sizeof("::") - 1); memcpy(ZSTR_VAL(key) + Z_STRLEN_P(class_name) + sizeof("::") - 1, Z_STRVAL(op_array->literals[i]), Z_STRLEN(op_array->literals[i]) + 1); } else { key = zend_string_init(Z_STRVAL(op_array->literals[i]), Z_STRLEN(op_array->literals[i]), 0); } ZSTR_H(key) = zend_hash_func(ZSTR_VAL(key), ZSTR_LEN(key)); ZSTR_H(key) += info[i].flags; } if ((info[i].flags & LITERAL_MAY_MERGE) && (pos = zend_hash_find(&hash, key)) != NULL && Z_TYPE(op_array->literals[i]) == Z_TYPE(op_array->literals[Z_LVAL_P(pos)]) && info[i].flags == info[Z_LVAL_P(pos)].flags) { zend_string_release(key); map[i] = Z_LVAL_P(pos); zval_dtor(&op_array->literals[i]); n = LITERAL_NUM_RELATED(info[i].flags); while (n > 1) { i++; zval_dtor(&op_array->literals[i]); n--; } } else { map[i] = j; if (info[i].flags & LITERAL_MAY_MERGE) { ZVAL_LONG(&zv, j); zend_hash_add_new(&hash, key, &zv); zend_string_release(key); } if (i != j) { op_array->literals[j] = op_array->literals[i]; info[j] = info[i]; } if (LITERAL_NUM_SLOTS(info[i].flags)) { Z_CACHE_SLOT(op_array->literals[j]) = cache_size; cache_size += LITERAL_NUM_SLOTS(info[i].flags) * sizeof(void*); } j++; n = LITERAL_NUM_RELATED(info[i].flags); while (n > 1) { i++; if (i != j) op_array->literals[j] = op_array->literals[i]; j++; n--; } } break; case IS_ARRAY: if (zend_hash_num_elements(Z_ARRVAL(op_array->literals[i])) == 0) { if (l_empty_arr < 0) { l_empty_arr = j; if (i != j) { op_array->literals[j] = op_array->literals[i]; info[j] = info[i]; } j++; } else { zval_dtor(&op_array->literals[i]); } map[i] = l_empty_arr; break; } /* break missing intentionally */ default: /* don't merge other types */ map[i] = j; if (i != j) { op_array->literals[j] = op_array->literals[i]; info[j] = info[i]; } j++; break; } } zend_hash_destroy(&hash); op_array->last_literal = j; op_array->cache_size = cache_size; /* Update opcodes to use new literals table */ opline = op_array->opcodes; end = opline + op_array->last; while (opline < end) { if (ZEND_OP1_TYPE(opline) == IS_CONST) { opline->op1.constant = map[opline->op1.constant]; } if (ZEND_OP2_TYPE(opline) == IS_CONST) { opline->op2.constant = map[opline->op2.constant]; } opline++; } zend_arena_release(&ctx->arena, checkpoint); #if DEBUG_COMPACT_LITERALS { int i, use_copy; fprintf(stderr, "Optimized literlas table size %d\n", op_array->last_literal); for (i = 0; i < op_array->last_literal; i++) { zval zv; ZVAL_COPY_VALUE(&zv, op_array->literals + i); use_copy = zend_make_printable_zval(op_array->literals + i, &zv); fprintf(stderr, "Literal %d, val (%d):%s\n", i, Z_STRLEN(zv), Z_STRVAL(zv)); if (use_copy) { zval_dtor(&zv); } } fflush(stderr); } #endif } }
ZEND_API void zend_create_closure(zval *res, zend_function *func, zend_class_entry *scope, zend_class_entry *called_scope, zval *this_ptr) /* {{{ */ { zend_closure *closure; object_init_ex(res, zend_ce_closure); closure = (zend_closure *)Z_OBJ_P(res); memcpy(&closure->func, func, func->type == ZEND_USER_FUNCTION ? sizeof(zend_op_array) : sizeof(zend_internal_function)); closure->func.common.prototype = (zend_function*)closure; closure->func.common.fn_flags |= ZEND_ACC_CLOSURE; if ((scope == NULL) && this_ptr && (Z_TYPE_P(this_ptr) != IS_UNDEF)) { /* use dummy scope if we're binding an object without specifying a scope */ /* maybe it would be better to create one for this purpose */ scope = zend_ce_closure; } if (closure->func.type == ZEND_USER_FUNCTION) { if (closure->func.op_array.static_variables) { HashTable *static_variables = closure->func.op_array.static_variables; ALLOC_HASHTABLE(closure->func.op_array.static_variables); zend_hash_init(closure->func.op_array.static_variables, zend_hash_num_elements(static_variables), NULL, ZVAL_PTR_DTOR, 0); zend_hash_apply_with_arguments(static_variables, zval_copy_static_var, 1, closure->func.op_array.static_variables); } if (UNEXPECTED(!closure->func.op_array.run_time_cache)) { closure->func.op_array.run_time_cache = func->op_array.run_time_cache = zend_arena_alloc(&CG(arena), func->op_array.cache_size); memset(func->op_array.run_time_cache, 0, func->op_array.cache_size); } if (closure->func.op_array.refcount) { (*closure->func.op_array.refcount)++; } } else { /* verify that we aren't binding internal function to a wrong scope */ if(func->common.scope != NULL) { if(scope && !instanceof_function(scope, func->common.scope)) { zend_error(E_WARNING, "Cannot bind function %s::%s to scope class %s", ZSTR_VAL(func->common.scope->name), ZSTR_VAL(func->common.function_name), ZSTR_VAL(scope->name)); scope = NULL; } if(scope && this_ptr && (func->common.fn_flags & ZEND_ACC_STATIC) == 0 && !instanceof_function(Z_OBJCE_P(this_ptr), closure->func.common.scope)) { zend_error(E_WARNING, "Cannot bind function %s::%s to object of class %s", ZSTR_VAL(func->common.scope->name), ZSTR_VAL(func->common.function_name), ZSTR_VAL(Z_OBJCE_P(this_ptr)->name)); scope = NULL; this_ptr = NULL; } } else { /* if it's a free function, we won't set scope & this since they're meaningless */ this_ptr = NULL; scope = NULL; } } ZVAL_UNDEF(&closure->this_ptr); /* Invariant: * If the closure is unscoped or static, it has no bound object. */ closure->func.common.scope = scope; closure->called_scope = called_scope; if (scope) { closure->func.common.fn_flags |= ZEND_ACC_PUBLIC; if (this_ptr && Z_TYPE_P(this_ptr) == IS_OBJECT && (closure->func.common.fn_flags & ZEND_ACC_STATIC) == 0) { ZVAL_COPY(&closure->this_ptr, this_ptr); } } }