Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Low overhead fcall (only PHP 5.6) #958

Merged
merged 3 commits into from May 18, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
212 changes: 211 additions & 1 deletion ext/kernel/extended/fcall.c
Expand Up @@ -34,7 +34,6 @@

#if PHP_VERSION_ID >= 50600


#if ZEND_MODULE_API_NO >= 20141001
void zephir_clean_and_cache_symbol_table(zend_array *symbol_table)
{
Expand Down Expand Up @@ -93,6 +92,217 @@ static void zephir_throw_exception_internal(zval *exception TSRMLS_DC)
EG(current_execute_data)->opline = EG(exception_op);
}

int zephir_call_func_aparams_fast(zval **retval_ptr_ptr, zephir_fcall_cache_entry **cache_entry, zend_uint param_count, zval *pparams[] TSRMLS_DC)
{
zend_uint i;
zval **original_return_value;
HashTable *calling_symbol_table;
zend_op_array *original_op_array;
zend_op **original_opline_ptr;
zend_class_entry *current_scope;
zend_class_entry *current_called_scope;
zend_class_entry *calling_scope = NULL;
zend_class_entry *called_scope = NULL;
zend_execute_data execute_data;
zval ***params, ***params_ptr, ***params_array = NULL;
zval **static_params_array[10];
zend_class_entry *old_scope = EG(scope);
zend_function_state *function_state = &EX(function_state);
zend_function *func;

if (retval_ptr_ptr && *retval_ptr_ptr) {
zval_ptr_dtor(retval_ptr_ptr);
*retval_ptr_ptr = NULL;
}

if (param_count) {

if (UNEXPECTED(param_count > 10)) {
params_array = (zval***) emalloc(param_count * sizeof(zval**));
params = params_array;
for (i = 0; i < param_count; ++i) {
params_array[i] = &pparams[i];
}
} else {
params = static_params_array;
for (i = 0; i < param_count; ++i) {
static_params_array[i] = &pparams[i];
}
}
} else {
params_ptr = NULL;
}

if (!EG(active)) {
return FAILURE; /* executor is already inactive */
}

if (EG(exception)) {
return FAILURE; /* we would result in an instable executor otherwise */
}

/* Initialize execute_data */
if (EG(current_execute_data)) {
execute_data = *EG(current_execute_data);
EX(op_array) = NULL;
EX(opline) = NULL;
EX(object) = NULL;
} else {
/* This only happens when we're called outside any execute()'s
* It shouldn't be strictly necessary to NULL execute_data out,
* but it may make bugs easier to spot
*/
memset(&execute_data, 0, sizeof(zend_execute_data));
}

#ifndef ZEPHIR_RELEASE
function_state->function = (*cache_entry)->f;
++(*cache_entry)->times;
#else
function_state->function = *cache_entry;
#endif
func = function_state->function;

calling_scope = NULL;
called_scope = NULL;
EX(object) = NULL;

ZEND_VM_STACK_GROW_IF_NEEDED(param_count + 1);

for (i = 0; i < param_count; i++) {
zval *param;

if (ARG_SHOULD_BE_SENT_BY_REF(func, i + 1)) {
if (!PZVAL_IS_REF(*params[i]) && Z_REFCOUNT_PP(params[i]) > 1) {
zval *new_zval;

if (!ARG_MAY_BE_SENT_BY_REF(func, i + 1)) {
if (i || UNEXPECTED(ZEND_VM_STACK_ELEMETS(EG(argument_stack)) == (EG(argument_stack)->top))) {
/* hack to clean up the stack */
zend_vm_stack_push((void *) (zend_uintptr_t)i TSRMLS_CC);
zend_vm_stack_clear_multiple(0 TSRMLS_CC);
}

zend_error(E_WARNING, "Parameter %d to %s%s%s() expected to be a reference, value given",
i+1,
func->common.scope ? func->common.scope->name : "",
func->common.scope ? "::" : "",
func->common.function_name);
return FAILURE;
}

ALLOC_ZVAL(new_zval);
*new_zval = **params[i];
zval_copy_ctor(new_zval);
Z_SET_REFCOUNT_P(new_zval, 1);
Z_DELREF_PP(params[i]);
*params[i] = new_zval;
}
Z_ADDREF_PP(params[i]);
Z_SET_ISREF_PP(params[i]);
param = *params[i];
} else if (PZVAL_IS_REF(*params[i]) && (func->common.fn_flags & ZEND_ACC_CALL_VIA_HANDLER) == 0 ) {
ALLOC_ZVAL(param);
*param = **(params[i]);
INIT_PZVAL(param);
zval_copy_ctor(param);
} else if (*params[i] != &EG(uninitialized_zval)) {
Z_ADDREF_PP(params[i]);
param = *params[i];
} else {
ALLOC_ZVAL(param);
*param = **(params[i]);
INIT_PZVAL(param);
}
zend_vm_stack_push(param TSRMLS_CC);
}

function_state->arguments = zend_vm_stack_top(TSRMLS_C);
zend_vm_stack_push((void*)(zend_uintptr_t)param_count TSRMLS_CC);

current_scope = EG(scope);
EG(scope) = calling_scope;

current_called_scope = EG(called_scope);
if (called_scope) {
EG(called_scope) = called_scope;
} else if (func->type != ZEND_INTERNAL_FUNCTION) {
EG(called_scope) = NULL;
}

EX(prev_execute_data) = EG(current_execute_data);
EG(current_execute_data) = &execute_data;

if (func->type == ZEND_USER_FUNCTION) {

calling_symbol_table = EG(active_symbol_table);
EG(scope) = func->common.scope;
EG(active_symbol_table) = NULL;

original_return_value = EG(return_value_ptr_ptr);
original_op_array = EG(active_op_array);
EG(return_value_ptr_ptr) = retval_ptr_ptr;
EG(active_op_array) = (zend_op_array *) function_state->function;
original_opline_ptr = EG(opline_ptr);

zend_execute(EG(active_op_array) TSRMLS_CC);

if (EG(active_symbol_table)) {
zephir_clean_and_cache_symbol_table(EG(active_symbol_table) TSRMLS_CC);
}
EG(active_symbol_table) = calling_symbol_table;
EG(active_op_array) = original_op_array;
EG(return_value_ptr_ptr)=original_return_value;
EG(opline_ptr) = original_opline_ptr;
} else if (func->type == ZEND_INTERNAL_FUNCTION) {

ALLOC_INIT_ZVAL(*retval_ptr_ptr);
if (func->common.scope) {
EG(scope) = func->common.scope;
}

func->internal_function.handler(param_count, *retval_ptr_ptr, retval_ptr_ptr, NULL, 1 TSRMLS_CC);

if (EG(exception) && retval_ptr_ptr) {
zval_ptr_dtor(retval_ptr_ptr);
*retval_ptr_ptr = NULL;
}

} else { /* ZEND_OVERLOADED_FUNCTION */
ALLOC_INIT_ZVAL(*retval_ptr_ptr);

/* Not sure what should be done here if it's a static method */
zend_error_noreturn(E_ERROR, "Cannot call overloaded function for non-object");

if (func->type == ZEND_OVERLOADED_FUNCTION_TEMPORARY) {
efree((char*)func->common.function_name);
}
efree(function_state->function);

if (EG(exception) && retval_ptr_ptr) {
zval_ptr_dtor(retval_ptr_ptr);
*retval_ptr_ptr = NULL;
}
}
zend_vm_stack_clear_multiple(0 TSRMLS_CC);

EG(called_scope) = current_called_scope;
EG(scope) = current_scope;
EG(current_execute_data) = EX(prev_execute_data);

if (EG(exception)) {
zephir_throw_exception_internal(NULL TSRMLS_CC);
}

EG(scope) = old_scope;

if (UNEXPECTED(params_array != NULL)) {
efree(params_array);
}

return SUCCESS;
}

static int zephir_is_callable_check_class(const char *name, int name_len, zend_fcall_info_cache *fcc, int *strict_class, char **error TSRMLS_DC) /* {{{ */
{
int ret = 0;
Expand Down
1 change: 1 addition & 0 deletions ext/kernel/extended/fcall.h
Expand Up @@ -38,5 +38,6 @@ typedef struct _zephir_fcall_info {
} zephir_fcall_info;

int zephir_call_function_opt(zend_fcall_info *fci, zend_fcall_info_cache *fci_cache, zephir_fcall_info *info TSRMLS_DC);
int zephir_call_func_aparams_fast(zval **return_value_ptr, zephir_fcall_cache_entry **cache_entry, uint param_count, zval **params TSRMLS_DC);

#endif
15 changes: 9 additions & 6 deletions ext/kernel/fcall.c
Expand Up @@ -514,6 +514,9 @@ int zephir_call_user_function(zval **object_pp, zend_class_entry *obj_ce, zephir
if (zephir_globals_ptr->scache[cache_slot]) {
reload_cache = 0;
temp_cache_entry = &zephir_globals_ptr->scache[cache_slot];
if (cache_entry) {
*cache_entry = *temp_cache_entry;
}
}
}

Expand Down Expand Up @@ -609,19 +612,19 @@ int zephir_call_user_function(zval **object_pp, zend_class_entry *obj_ce, zephir
if (!cache_entry || !*cache_entry) {
if (EXPECTED(status != FAILURE) && fcall_key && !temp_cache_entry && fcic.initialized) {
#ifndef ZEPHIR_RELEASE
zephir_fcall_cache_entry *temp_cache_entry = malloc(sizeof(zephir_fcall_cache_entry));
temp_cache_entry->f = fcic.function_handler;
temp_cache_entry->times = 0;
zephir_fcall_cache_entry *cache_entry_temp = malloc(sizeof(zephir_fcall_cache_entry));
cache_entry_temp->f = fcic.function_handler;
cache_entry_temp->times = 0;
#else
zephir_fcall_cache_entry *temp_cache_entry = fcic.function_handler;
zephir_fcall_cache_entry *cache_entry_temp = fcic.function_handler;
#endif
if (FAILURE == zend_hash_quick_add(zephir_globals_ptr->fcache, fcall_key, fcall_key_len, fcall_key_hash, &temp_cache_entry, sizeof(zephir_fcall_cache_entry*), NULL)) {
if (FAILURE == zend_hash_quick_add(zephir_globals_ptr->fcache, fcall_key, fcall_key_len, fcall_key_hash, &cache_entry_temp, sizeof(zephir_fcall_cache_entry*), NULL)) {
#ifndef ZEPHIR_RELEASE
free(temp_cache_entry);
#endif
} else {
if (cache_entry) {
*cache_entry = temp_cache_entry;
*cache_entry = cache_entry_temp;
if (cache_slot > 0) {
zephir_globals_ptr->scache[cache_slot] = *cache_entry;
}
Expand Down
45 changes: 38 additions & 7 deletions ext/kernel/fcall.h
Expand Up @@ -25,6 +25,7 @@
#include "kernel/main.h"
#include "kernel/memory.h"
#include "kernel/extended/fcall.h"

#include <Zend/zend_hash.h>
#include <Zend/zend.h>

Expand Down Expand Up @@ -59,13 +60,16 @@ typedef enum _zephir_call_type {
* @note If the call fails or an exception occurs, the memory frame is @em not restored.
* In this case if @c return_value_ptr is not @c NULL, <tt>*return_value_ptr</tt> is set to @c NULL
*/
#define ZEPHIR_CALL_FUNCTIONW(return_value_ptr, func_name, ...) \
#define ZEPHIR_CALL_FUNCTIONW(return_value_ptr, func_name, cache, cache_slot, ...) \
do { \
zval *params_[] = {ZEPHIR_FETCH_VA_ARGS __VA_ARGS__}; \
if (__builtin_constant_p(func_name)) { \
ZEPHIR_LAST_CALL_STATUS = zephir_call_func_aparams(return_value_ptr, func_name, sizeof(func_name)-1, ZEPHIR_CALL_NUM_PARAMS(params_), ZEPHIR_PASS_CALL_PARAMS(params_) TSRMLS_CC); \
} \
else { \
if (cache && *cache) { \
ZEPHIR_LAST_CALL_STATUS = zephir_call_func_aparams_fast(return_value_ptr, cache, ZEPHIR_CALL_NUM_PARAMS(params_), ZEPHIR_PASS_CALL_PARAMS(params_) TSRMLS_CC); \
} else { \
ZEPHIR_LAST_CALL_STATUS = zephir_call_func_aparams(return_value_ptr, func_name, sizeof(func_name)-1, cache, cache_slot, ZEPHIR_CALL_NUM_PARAMS(params_), ZEPHIR_PASS_CALL_PARAMS(params_) TSRMLS_CC); \
} \
} else { \
ZEPHIR_LAST_CALL_STATUS = zephir_call_func_aparams(return_value_ptr, func_name, strlen(func_name), ZEPHIR_CALL_NUM_PARAMS(params_), ZEPHIR_PASS_CALL_PARAMS(params_) TSRMLS_CC); \
} \
} while (0)
Expand All @@ -78,18 +82,39 @@ typedef enum _zephir_call_type {
* @note If the call fails or an exception occurs, the memory frame is restored.
* In this case if @c return_value_ptr is not @c NULL, <tt>*return_value_ptr</tt> is set to @c NULL
*/
#if PHP_VERSION_ID >= 50600

#define ZEPHIR_CALL_FUNCTION(return_value_ptr, func_name, cache, cache_slot, ...) \
do { \
zephir_fcall_cache_entry **cache_entry_ = cache; \
zval *params_[] = {ZEPHIR_FETCH_VA_ARGS __VA_ARGS__}; \
ZEPHIR_OBSERVE_OR_NULLIFY_PPZV(return_value_ptr); \
if (__builtin_constant_p(func_name)) { \
ZEPHIR_LAST_CALL_STATUS = zephir_call_func_aparams(return_value_ptr, func_name, sizeof(func_name)-1, cache, cache_slot, ZEPHIR_CALL_NUM_PARAMS(params_), ZEPHIR_PASS_CALL_PARAMS(params_) TSRMLS_CC); \
if (cache_entry_ && *cache_entry_) { \
ZEPHIR_LAST_CALL_STATUS = zephir_call_func_aparams_fast(return_value_ptr, cache, ZEPHIR_CALL_NUM_PARAMS(params_), ZEPHIR_PASS_CALL_PARAMS(params_) TSRMLS_CC); \
} else { \
ZEPHIR_LAST_CALL_STATUS = zephir_call_func_aparams(return_value_ptr, func_name, sizeof(func_name)-1, cache, cache_slot, ZEPHIR_CALL_NUM_PARAMS(params_), ZEPHIR_PASS_CALL_PARAMS(params_) TSRMLS_CC); \
} \
} else { \
ZEPHIR_LAST_CALL_STATUS = zephir_call_func_aparams(return_value_ptr, func_name, strlen(func_name), cache, cache_slot, ZEPHIR_CALL_NUM_PARAMS(params_), ZEPHIR_PASS_CALL_PARAMS(params_) TSRMLS_CC); \
} \
else { \
} while (0)

#else

#define ZEPHIR_CALL_FUNCTION(return_value_ptr, func_name, cache, cache_slot, ...) \
do { \
zval *params_[] = {ZEPHIR_FETCH_VA_ARGS __VA_ARGS__}; \
ZEPHIR_OBSERVE_OR_NULLIFY_PPZV(return_value_ptr); \
if (__builtin_constant_p(func_name)) { \
ZEPHIR_LAST_CALL_STATUS = zephir_call_func_aparams(return_value_ptr, func_name, sizeof(func_name)-1, cache, cache_slot, ZEPHIR_CALL_NUM_PARAMS(params_), ZEPHIR_PASS_CALL_PARAMS(params_) TSRMLS_CC); \
} else { \
ZEPHIR_LAST_CALL_STATUS = zephir_call_func_aparams(return_value_ptr, func_name, strlen(func_name), cache, cache_slot, ZEPHIR_CALL_NUM_PARAMS(params_), ZEPHIR_PASS_CALL_PARAMS(params_) TSRMLS_CC); \
} \
} while (0)

#endif

/**
* @brief Invokes a function @a func_name passing @c return_value and @c return_value_ptr
* as return value address; returns if the function fails due to an error or exception.
Expand Down Expand Up @@ -143,7 +168,11 @@ typedef enum _zephir_call_type {
zval *params_[] = {ZEPHIR_FETCH_VA_ARGS __VA_ARGS__}; \
ZEPHIR_OBSERVE_OR_NULLIFY_PPZV(return_value_ptr); \
if (__builtin_constant_p(func_name)) { \
ZEPHIR_LAST_CALL_STATUS = zephir_call_zval_func_aparams(return_value_ptr, func_name, cache, cache_slot, ZEPHIR_CALL_NUM_PARAMS(params_), ZEPHIR_PASS_CALL_PARAMS(params_) TSRMLS_CC); \
if (!cache || !*cache) { \
ZEPHIR_LAST_CALL_STATUS = zephir_call_zval_func_aparams(return_value_ptr, func_name, cache, cache_slot, ZEPHIR_CALL_NUM_PARAMS(params_), ZEPHIR_PASS_CALL_PARAMS(params_) TSRMLS_CC); \
} else { \
ZEPHIR_LAST_CALL_STATUS = zephir_call_func_aparams_fast(return_value_ptr, cache_entry, param_count, *params TSRMLS_CC); \
} \
} \
else { \
ZEPHIR_LAST_CALL_STATUS = zephir_call_zval_func_aparams(return_value_ptr, func_name, cache, cache_slot, ZEPHIR_CALL_NUM_PARAMS(params_), ZEPHIR_PASS_CALL_PARAMS(params_) TSRMLS_CC); \
Expand Down Expand Up @@ -525,6 +554,8 @@ int zephir_call_func_aparams(zval **return_value_ptr, const char *func_name, uin
zephir_fcall_cache_entry **cache_entry, int cache_slot,
uint param_count, zval **params TSRMLS_DC);

int zephir_call_func_aparams_fast(zval **return_value_ptr, zephir_fcall_cache_entry **cache_entry, uint param_count, zval **params TSRMLS_DC);

int zephir_call_zval_func_aparams(zval **return_value_ptr, zval *func_name,
zephir_fcall_cache_entry **cache_entry, int cache_slot,
uint param_count, zval **params TSRMLS_DC) ZEPHIR_ATTR_WARN_UNUSED_RESULT;
Expand Down
2 changes: 1 addition & 1 deletion parser/parser.lemon
Expand Up @@ -536,7 +536,7 @@ static json_object *xx_ret_type(int type)
default:
fprintf(stderr, "unknown type?\n");
}

return json_object_new_string("unknown");
}

static json_object *xx_ret_list(json_object *list_left, json_object *right_list)
Expand Down