summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--NEWS3
-rw-r--r--Zend/zend.c10
-rw-r--r--Zend/zend_execute_API.c43
-rw-r--r--Zend/zend_opcode.c4
-rw-r--r--ext/opcache/ZendAccelerator.c959
-rw-r--r--ext/opcache/ZendAccelerator.h6
-rw-r--r--ext/opcache/tests/preload.inc39
-rw-r--r--ext/opcache/tests/preload_001.phpt24
-rw-r--r--ext/opcache/tests/preload_002.phpt19
-rw-r--r--ext/opcache/tests/preload_003.phpt19
-rw-r--r--ext/opcache/zend_accelerator_module.c44
-rwxr-xr-xrun-tests.php1
12 files changed, 1167 insertions, 4 deletions
diff --git a/NEWS b/NEWS
index 869d58d60b..0eafd0c37b 100644
--- a/NEWS
+++ b/NEWS
@@ -22,6 +22,9 @@ PHP NEWS
. Changed default of $variant parameter of idn_to_ascii() and idn_to_utf8().
(cmb)
+- Opcache:
+ . Implemented preloading RFC: https://wiki.php.net/rfc/preload. (Dmitry)
+
- PDO_OCI:
. Implemented FR #76908 (PDO_OCI getColumnMeta() not implemented).
(Valentin Collet, Chris Jones, Remi)
diff --git a/Zend/zend.c b/Zend/zend.c
index f8d5dc1317..b4f35130a2 100644
--- a/Zend/zend.c
+++ b/Zend/zend.c
@@ -566,8 +566,13 @@ static void auto_global_dtor(zval *zv) /* {{{ */
static void function_copy_ctor(zval *zv) /* {{{ */
{
zend_function *old_func = Z_FUNC_P(zv);
- zend_function *func = pemalloc(sizeof(zend_internal_function), 1);
+ zend_function *func;
+ if (old_func->type == ZEND_USER_FUNCTION) {
+ ZEND_ASSERT(old_func->op_array.fn_flags & ZEND_ACC_IMMUTABLE);
+ return;
+ }
+ func = pemalloc(sizeof(zend_internal_function), 1);
Z_FUNC_P(zv) = func;
memcpy(func, old_func, sizeof(zend_internal_function));
function_add_ref(func);
@@ -977,7 +982,9 @@ int zend_post_startup(void) /* {{{ */
zend_destroy_rsrc_list(&EG(persistent_list));
free(compiler_globals->function_table);
+ compiler_globals->function_table = NULL;
free(compiler_globals->class_table);
+ compiler_globals->class_table = NULL;
if ((script_encoding_list = (zend_encoding **)compiler_globals->script_encoding_list)) {
compiler_globals_ctor(compiler_globals);
compiler_globals->script_encoding_list = (const zend_encoding **)script_encoding_list;
@@ -985,6 +992,7 @@ int zend_post_startup(void) /* {{{ */
compiler_globals_ctor(compiler_globals);
}
free(EG(zend_constants));
+ EG(zend_constants) = NULL;
executor_globals_ctor(executor_globals);
global_persistent_list = &EG(persistent_list);
diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c
index bd2e24d954..646f3b927b 100644
--- a/Zend/zend_execute_API.c
+++ b/Zend/zend_execute_API.c
@@ -340,6 +340,23 @@ void shutdown_executor(void) /* {{{ */
destroy_op_array(&func->op_array);
zend_string_release_ex(key, 0);
} ZEND_HASH_FOREACH_END_DEL();
+
+ /* Cleanup preloaded immutable functions */
+ ZEND_HASH_REVERSE_FOREACH_VAL(EG(function_table), zv) {
+ zend_op_array *op_array = Z_PTR_P(zv);
+ if (op_array->type == ZEND_INTERNAL_FUNCTION) {
+ break;
+ }
+ ZEND_ASSERT(op_array->fn_flags & ZEND_ACC_IMMUTABLE);
+ if (op_array->static_variables) {
+ HashTable *ht = ZEND_MAP_PTR_GET(op_array->static_variables_ptr);
+ if (ht) {
+ ZEND_ASSERT(GC_REFCOUNT(ht) == 1);
+ zend_array_destroy(ht);
+ }
+ }
+ } ZEND_HASH_FOREACH_END();
+
ZEND_HASH_REVERSE_FOREACH_STR_KEY_VAL(EG(class_table), key, zv) {
if (_idx == EG(persistent_classes_count)) {
break;
@@ -347,6 +364,32 @@ void shutdown_executor(void) /* {{{ */
destroy_zend_class(zv);
zend_string_release_ex(key, 0);
} ZEND_HASH_FOREACH_END_DEL();
+
+ /* Cleanup preloaded immutable classes */
+ ZEND_HASH_REVERSE_FOREACH_VAL(EG(class_table), zv) {
+ zend_class_entry *ce = Z_PTR_P(zv);
+ if (ce->type == ZEND_INTERNAL_CLASS) {
+ break;
+ }
+ ZEND_ASSERT(ce->ce_flags & ZEND_ACC_IMMUTABLE);
+ if (ce->default_static_members_count) {
+ zend_cleanup_internal_class_data(ce);
+ }
+ if (ce->ce_flags & ZEND_HAS_STATIC_IN_METHODS) {
+ zend_op_array *op_array;
+ ZEND_HASH_FOREACH_PTR(&ce->function_table, op_array) {
+ if (op_array->type == ZEND_USER_FUNCTION) {
+ if (op_array->static_variables) {
+ HashTable *ht = ZEND_MAP_PTR_GET(op_array->static_variables_ptr);
+ if (ht) {
+ ZEND_ASSERT(GC_REFCOUNT(ht) == 1);
+ zend_array_destroy(ht);
+ }
+ }
+ }
+ } ZEND_HASH_FOREACH_END();
+ }
+ } ZEND_HASH_FOREACH_END();
}
zend_cleanup_internal_classes();
diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c
index 3bfeb1f79c..dbe2b5c20b 100644
--- a/Zend/zend_opcode.c
+++ b/Zend/zend_opcode.c
@@ -362,7 +362,9 @@ void zend_class_add_ref(zval *zv)
{
zend_class_entry *ce = Z_PTR_P(zv);
- ce->refcount++;
+ if (!(ce->ce_flags & ZEND_ACC_IMMUTABLE)) {
+ ce->refcount++;
+ }
}
ZEND_API void destroy_op_array(zend_op_array *op_array)
diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c
index 1a7e617d42..8b45140f5f 100644
--- a/ext/opcache/ZendAccelerator.c
+++ b/ext/opcache/ZendAccelerator.c
@@ -31,6 +31,8 @@
#include "zend_accelerator_blacklist.h"
#include "zend_list.h"
#include "zend_execute.h"
+#include "zend_inheritance.h"
+#include "main/php_main.h"
#include "main/SAPI.h"
#include "main/php_streams.h"
#include "main/php_open_temporary_file.h"
@@ -128,6 +130,11 @@ static int (*orig_post_startup_cb)(void);
static void accel_gen_system_id(void);
static int accel_post_startup(void);
+static int accel_finish_startup(void);
+
+static void preload_shutdown(void);
+static void preload_activate(void);
+static void preload_restart(void);
#ifdef ZEND_WIN32
# define INCREMENT(v) InterlockedIncrement64(&ZCSG(v))
@@ -1073,7 +1080,9 @@ static inline int do_validate_timestamps(zend_persistent_script *persistent_scri
int validate_timestamp_and_record(zend_persistent_script *persistent_script, zend_file_handle *file_handle)
{
- if (ZCG(accel_directives).revalidate_freq &&
+ if (persistent_script->timestamp == 0) {
+ return SUCCESS; /* Don't check timestamps of preloaded scripts */
+ } else if (ZCG(accel_directives).revalidate_freq &&
persistent_script->dynamic_members.revalidate >= ZCG(request_time)) {
return SUCCESS;
} else if (do_validate_timestamps(persistent_script, file_handle) == FAILURE) {
@@ -2373,6 +2382,9 @@ int accel_activate(INIT_FUNC_ARGS)
}
zend_shared_alloc_restore_state();
+ if (ZCSG(preload_script)) {
+ preload_restart();
+ }
ZCSG(accelerator_enabled) = ZCSG(cache_status_before_restart);
if (ZCSG(last_restart_time) < ZCG(request_time)) {
ZCSG(last_restart_time) = ZCG(request_time);
@@ -2402,6 +2414,10 @@ int accel_activate(INIT_FUNC_ARGS)
accel_reset_pcre_cache();
}
+ if (ZCSG(preload_script)) {
+ preload_activate();
+ }
+
return SUCCESS;
}
@@ -2899,7 +2915,7 @@ file_cache_fallback:
accel_use_shm_interned_strings();
}
- return SUCCESS;
+ return accel_finish_startup();
}
static void (*orig_post_shutdown_cb)(void);
@@ -2925,6 +2941,10 @@ void accel_shutdown(void)
return;
}
+ if (ZCSG(preload_script)) {
+ preload_shutdown();
+ }
+
#ifdef HAVE_OPCACHE_FILE_CACHE
_file_cache_only = file_cache_only;
#endif
@@ -3022,6 +3042,941 @@ void accelerator_shm_read_unlock(void)
}
}
+/* Preloading */
+static HashTable *preload_scripts = NULL;
+static zend_op_array *(*preload_orig_compile_file)(zend_file_handle *file_handle, int type);
+
+static void preload_shutdown(void)
+{
+ zval *zv;
+
+#if 0
+ if (EG(zend_constants)) {
+ ZEND_HASH_REVERSE_FOREACH_VAL(EG(zend_constants), zv) {
+ zend_constant *c = Z_PTR_P(zv);
+ if (ZEND_CONSTANT_FLAGS(c) & CONST_PERSISTENT) {
+ break;
+ }
+ } ZEND_HASH_FOREACH_END_DEL();
+ }
+#endif
+
+ if (EG(function_table)) {
+ ZEND_HASH_REVERSE_FOREACH_VAL(EG(function_table), zv) {
+ zend_function *func = Z_PTR_P(zv);
+ if (func->type == ZEND_INTERNAL_FUNCTION) {
+ break;
+ }
+ } ZEND_HASH_FOREACH_END_DEL();
+ }
+
+ if (EG(class_table)) {
+ ZEND_HASH_REVERSE_FOREACH_VAL(EG(class_table), zv) {
+ zend_class_entry *ce = Z_PTR_P(zv);
+ if (ce->type == ZEND_INTERNAL_CLASS) {
+ break;
+ }
+ } ZEND_HASH_FOREACH_END_DEL();
+ }
+}
+
+static void preload_activate(void)
+{
+ if (ZCSG(preload_script)->ping_auto_globals_mask) {
+ zend_accel_set_auto_globals(ZCSG(preload_script)->ping_auto_globals_mask);
+ }
+}
+
+static void preload_restart(void)
+{
+ zend_accel_hash_update(&ZCSG(hash), ZSTR_VAL(ZCSG(preload_script)->script.filename), ZSTR_LEN(ZCSG(preload_script)->script.filename), 0, ZCSG(preload_script));
+ if (ZCSG(saved_scripts)) {
+ zend_persistent_script **p = ZCSG(saved_scripts);
+ while (*p) {
+ zend_accel_hash_update(&ZCSG(hash), ZSTR_VAL((*p)->script.filename), ZSTR_LEN((*p)->script.filename), 0, *p);
+ p++;
+ }
+ }
+}
+
+static void preload_move_user_functions(HashTable *src, HashTable *dst)
+{
+ Bucket *p;
+ dtor_func_t orig_dtor = src->pDestructor;
+
+ src->pDestructor = NULL;
+ zend_hash_extend(dst, dst->nNumUsed + src->nNumUsed, 0);
+ ZEND_HASH_REVERSE_FOREACH_BUCKET(src, p) {
+ zend_function *function = Z_PTR(p->val);
+
+ if (EXPECTED(function->type == ZEND_USER_FUNCTION)) {
+ _zend_hash_append_ptr(dst, p->key, function);
+ zend_hash_del_bucket(src, p);
+ } else {
+ break;
+ }
+ } ZEND_HASH_FOREACH_END();
+ src->pDestructor = orig_dtor;
+}
+
+static void preload_move_user_classes(HashTable *src, HashTable *dst)
+{
+ Bucket *p;
+ dtor_func_t orig_dtor = src->pDestructor;
+
+ src->pDestructor = NULL;
+ zend_hash_extend(dst, dst->nNumUsed + src->nNumUsed, 0);
+ ZEND_HASH_REVERSE_FOREACH_BUCKET(src, p) {
+ zend_class_entry *ce = Z_PTR(p->val);
+
+ if (EXPECTED(ce->type == ZEND_USER_CLASS)) {
+ _zend_hash_append_ptr(dst, p->key, ce);
+ zend_hash_del_bucket(src, p);
+ } else {
+ break;
+ }
+ } ZEND_HASH_FOREACH_END();
+ src->pDestructor = orig_dtor;
+}
+
+static zend_op_array *preload_compile_file(zend_file_handle *file_handle, int type)
+{
+ zend_op_array *op_array = preload_orig_compile_file(file_handle, type);
+
+ if (op_array && op_array->refcount) {
+ zend_persistent_script *script;
+
+ script = create_persistent_script();
+ script->script.first_early_binding_opline = (uint32_t)-1;
+ script->script.filename = zend_string_copy(op_array->filename);
+ zend_string_hash_val(script->script.filename);
+ script->script.main_op_array = *op_array;
+
+//??? efree(op_array->refcount);
+ op_array->refcount = NULL;
+
+ if (op_array->static_variables &&
+ !(GC_FLAGS(op_array->static_variables) & IS_ARRAY_IMMUTABLE)) {
+ GC_ADDREF(op_array->static_variables);
+ }
+
+ zend_hash_add_ptr(preload_scripts, script->script.filename, script);
+ }
+
+ return op_array;
+}
+
+static void preload_sort_classes(void *base, size_t count, size_t siz, compare_func_t compare, swap_func_t swp) /* {{{ */
+{
+ Bucket *b1 = base;
+ Bucket *b2;
+ Bucket *end = b1 + count;
+ Bucket tmp;
+ zend_class_entry *ce, *p;
+
+ while (b1 < end) {
+try_again:
+ ce = (zend_class_entry*)Z_PTR(b1->val);
+ if (ce->parent && (ce->ce_flags & ZEND_ACC_LINKED)) {
+ p = ce->parent;
+ if (p->type == ZEND_USER_CLASS) {
+ b2 = b1 + 1;
+ while (b2 < end) {
+ if (p == Z_PTR(b2->val)) {
+ tmp = *b1;
+ *b1 = *b2;
+ *b2 = tmp;
+ goto try_again;
+ }
+ b2++;
+ }
+ }
+ }
+ if (ce->num_interfaces && (ce->ce_flags & ZEND_ACC_LINKED)) {
+ uint32_t i = 0;
+ for (i = 0; i < ce->num_interfaces; i++) {
+ p = ce->interfaces[i];
+ if (p->type == ZEND_USER_CLASS) {
+ b2 = b1 + 1;
+ while (b2 < end) {
+ if (p == Z_PTR(b2->val)) {
+ tmp = *b1;
+ *b1 = *b2;
+ *b2 = tmp;
+ goto try_again;
+ }
+ b2++;
+ }
+ }
+ }
+ }
+ b1++;
+ }
+}
+
+static void preload_link(void)
+{
+ zval *zv;
+ zend_persistent_script *script;
+ zend_class_entry *ce, *parent, *p;
+ zend_string *key;
+ zend_bool found, changed;
+ uint32_t i;
+ zend_op_array *op_array;
+ dtor_func_t orig_dtor;
+
+ /* Resolve class dependencies */
+ do {
+ changed = 0;
+
+ ZEND_HASH_REVERSE_FOREACH_VAL(EG(class_table), zv) {
+ ce = Z_PTR_P(zv);
+ if (ce->type == ZEND_INTERNAL_CLASS) {
+ break;
+ }
+ if ((ce->ce_flags & ZEND_ACC_TOP_LEVEL)
+ && !(ce->ce_flags & ZEND_ACC_LINKED)) {
+
+ parent = NULL;
+
+ if (ce->parent_name) {
+ key = zend_string_tolower(ce->parent_name);
+ parent = zend_hash_find_ptr(EG(class_table), key);
+ zend_string_release(key);
+ if (!parent) continue;
+#ifdef ZEND_WIN32
+ /* On Windows we can't link with internal class, because of ASLR */
+ if (parent->type == ZEND_INTERNAL_CLASS) continue;
+#endif
+ }
+
+ if (ce->num_interfaces) {
+ found = 1;
+ for (i = 0; i < ce->num_interfaces; i++) {
+ p = zend_hash_find_ptr(EG(class_table), ce->interface_names[i].lc_name);
+ if (!p) {
+ found = 0;
+ break;
+ }
+#ifdef ZEND_WIN32
+ /* On Windows we can't link with internal class, because of ASLR */
+ if (p->type == ZEND_INTERNAL_CLASS) {
+ found = 0;
+ break;
+ }
+#endif
+ }
+ if (!found) continue;
+ }
+
+ if (ce->num_traits) {
+ found = 1;
+ for (i = 0; i < ce->num_traits; i++) {
+ p = zend_hash_find_ptr(EG(class_table), ce->trait_names[i].lc_name);
+ if (!p) {
+ found = 0;
+ break;
+ }
+#ifdef ZEND_WIN32
+ /* On Windows we can't link with internal class, because of ASLR */
+ if (p->type == ZEND_INTERNAL_CLASS) {
+ found = 0;
+ break;
+ }
+#endif
+ }
+ if (!found) continue;
+ }
+
+ key = zend_string_tolower(ce->name);
+ zv = zend_hash_set_bucket_key(EG(class_table), (Bucket*)zv, key);
+ zend_string_release(key);
+ if (EXPECTED(zv)) {
+ zend_do_link_class(ce, parent);
+ changed = 1;
+ }
+ }
+ } ZEND_HASH_FOREACH_END();
+ } while (changed);
+
+ /* Resolve class constants */
+ EG(exception) = (void*)(uintptr_t)-1; /* prevent error reporting */
+ do {
+ changed = 0;
+ ZEND_HASH_REVERSE_FOREACH_VAL(EG(class_table), zv) {
+ ce = Z_PTR_P(zv);
+ if (ce->type == ZEND_INTERNAL_CLASS) {
+ break;
+ }
+ if ((ce->ce_flags & ZEND_ACC_LINKED)
+ && !(ce->ce_flags & ZEND_ACC_CONSTANTS_UPDATED)) {
+ zend_bool ok = 1;
+ zend_class_constant *c;
+ zval *val;
+
+ ZEND_HASH_FOREACH_PTR(&ce->constants_table, c) {
+ val = &c->value;
+ if (Z_TYPE_P(val) == IS_CONSTANT_AST) {
+ if (EXPECTED(zval_update_constant_ex(val, c->ce) == SUCCESS)) {
+ changed = 1;
+ } else {
+ ok = 0;
+ }
+ }
+ } ZEND_HASH_FOREACH_END();
+ if (ce->default_properties_count) {
+ zend_class_entry *pce = ce;
+
+ val = ce->default_properties_table + ce->default_properties_count - 1;
+ do {
+ uint32_t count = pce->parent ? pce->default_properties_count - pce->parent->default_properties_count : pce->default_properties_count;
+
+ while (count) {
+ if (Z_TYPE_P(val) == IS_CONSTANT_AST) {
+ if (UNEXPECTED(zval_update_constant_ex(val, pce) != SUCCESS)) {
+ ok = 0;
+ }
+ }
+ val--;
+ count--;
+ }
+ pce = pce->parent;
+ } while (pce && pce-> default_properties_count);
+ }
+ if (ce->default_static_members_count) {
+ uint32_t count = ce->parent ? ce->default_static_members_count - ce->parent->default_static_members_count : ce->default_static_members_count;
+
+ val = ce->default_static_members_table + ce->default_static_members_count - 1;
+ while (count) {
+ if (Z_TYPE_P(val) == IS_CONSTANT_AST) {
+ if (UNEXPECTED(zval_update_constant_ex(val, ce) != SUCCESS)) {
+ ok = 0;
+ }
+ }
+ val--;
+ count--;
+ }
+ }
+ if (ok) {
+ ce->ce_flags |= ZEND_ACC_CONSTANTS_UPDATED;
+ }
+ }
+ } ZEND_HASH_FOREACH_END();
+ } while (changed);
+ EG(exception) = NULL;
+
+ /* Move unlinked clases (and with unresilved constants) back to scripts */
+ orig_dtor = EG(class_table)->pDestructor;
+ EG(class_table)->pDestructor = NULL;
+ ZEND_HASH_REVERSE_FOREACH_STR_KEY_VAL(EG(class_table), key, zv) {
+ ce = Z_PTR_P(zv);
+ if (ce->type == ZEND_INTERNAL_CLASS) {
+ break;
+ }
+ if (!(ce->ce_flags & ZEND_ACC_LINKED)) {
+ zend_error(E_WARNING, "Can't preload unlinked class %s at %s:%d\n", ZSTR_VAL(ce->name), ZSTR_VAL(ce->info.user.filename), ce->info.user.line_start);
+ } else if (!(ce->ce_flags & ZEND_ACC_CONSTANTS_UPDATED)) {
+ zend_error(E_WARNING, "Can't preload class %s with unresolved constants at %s:%d\n", ZSTR_VAL(ce->name), ZSTR_VAL(ce->info.user.filename), ce->info.user.line_start);
+ } else {
+ continue;
+ }
+ script = zend_hash_find_ptr(preload_scripts, ce->info.user.filename);
+ ZEND_ASSERT(script);
+ zend_hash_add(&script->script.class_table, key, zv);
+ ZVAL_UNDEF(zv);
+ zend_string_release(key);
+ EG(class_table)->nNumOfElements--;
+ } ZEND_HASH_FOREACH_END();
+ EG(class_table)->pDestructor = orig_dtor;
+ zend_hash_rehash(EG(class_table));
+
+ /* Move run-time declared functions back to scripts */
+ orig_dtor = EG(function_table)->pDestructor;
+ EG(function_table)->pDestructor = NULL;
+ ZEND_HASH_REVERSE_FOREACH_STR_KEY_VAL(EG(function_table), key, zv) {
+ op_array = Z_PTR_P(zv);
+ if (op_array->type == ZEND_INTERNAL_FUNCTION) {
+ break;
+ }
+ if (op_array->fn_flags & (ZEND_ACC_TOP_LEVEL|ZEND_ACC_CLOSURE)) {
+ continue;
+ }
+ script = zend_hash_find_ptr(preload_scripts, op_array->filename);
+ ZEND_ASSERT(script);
+ zend_hash_add(&script->script.function_table, key, zv);
+ ZVAL_UNDEF(zv);
+ zend_string_release(key);
+ EG(function_table)->nNumOfElements--;
+ } ZEND_HASH_FOREACH_END();
+ EG(function_table)->pDestructor = orig_dtor;
+ zend_hash_rehash(EG(function_table));
+
+
+ /* Remove DECLARE opcodes */
+ ZEND_HASH_FOREACH_PTR(preload_scripts, script) {
+ zend_op_array *op_array = &script->script.main_op_array;
+ zend_op *opline = op_array->opcodes;
+ zend_op *end = opline + op_array->last;
+
+ while (opline != end) {
+ switch (opline->opcode) {
+ case ZEND_DECLARE_CLASS:
+ case ZEND_DECLARE_INHERITED_CLASS:
+ case ZEND_DECLARE_INHERITED_CLASS_DELAYED:
+ key = Z_STR_P(RT_CONSTANT(opline, opline->op1) + 1);
+ zv = zend_hash_find_ex(EG(class_table), key, 1);
+ if (EXPECTED(!zv)) {
+ MAKE_NOP(opline);
+ }
+ break;
+ }
+ opline++;
+ }
+
+ if (op_array->fn_flags & ZEND_ACC_EARLY_BINDING) {
+ script->script.first_early_binding_opline = zend_build_delayed_early_binding_list(op_array);
+ if (script->script.first_early_binding_opline == (uint32_t)-1) {
+ op_array->fn_flags &= ~ZEND_ACC_EARLY_BINDING;
+ }
+ }
+ } ZEND_HASH_FOREACH_END();
+}
+
+static zend_string *preload_resolve_path(zend_string *filename)
+{
+ return zend_resolve_path(ZSTR_VAL(filename), ZSTR_LEN(filename));
+}
+
+static void preload_remove_empty_includes(void)
+{
+ zend_persistent_script *script;
+ zend_bool changed;
+
+ /* mark all as empty */
+ ZEND_HASH_FOREACH_PTR(preload_scripts, script) {
+ script->empty = 1;
+ } ZEND_HASH_FOREACH_END();
+
+ /* find non empty scripts */
+ do {
+ changed = 0;
+ ZEND_HASH_FOREACH_PTR(preload_scripts, script) {
+ if (script->empty) {
+ int empty = 1;
+ zend_op *opline = script->script.main_op_array.opcodes;
+ zend_op *end = opline + script->script.main_op_array.last;
+
+ while (opline < end) {
+ if (opline->opcode == ZEND_INCLUDE_OR_EVAL &&
+ opline->extended_value != ZEND_EVAL &&
+ opline->op1_type == IS_CONST &&
+ Z_TYPE_P(RT_CONSTANT(opline, opline->op1)) == IS_STRING) {
+
+ zend_string *resolved_path = preload_resolve_path(Z_STR_P(RT_CONSTANT(opline, opline->op1)));
+
+ if (resolved_path) {
+ zend_persistent_script *incl = zend_hash_find_ptr(preload_scripts, resolved_path);
+ zend_string_release(resolved_path);
+ if (!incl->empty) {
+ empty = 0;
+ break;
+ }
+ } else {
+ empty = 0;
+ break;
+ }
+ } else if (opline->opcode != ZEND_NOP &&
+ opline->opcode != ZEND_RETURN &&
+ opline->opcode != ZEND_HANDLE_EXCEPTION) {
+ empty = 0;
+ break;
+ }
+ opline++;
+ }
+ if (!empty) {
+ script->empty = 0;
+ changed = 1;
+ }
+ }
+ } ZEND_HASH_FOREACH_END();
+ } while (changed);
+
+ /* remove empty includes */
+ ZEND_HASH_FOREACH_PTR(preload_scripts, script) {
+ zend_op *opline = script->script.main_op_array.opcodes;
+ zend_op *end = opline + script->script.main_op_array.last;
+
+ while (opline < end) {
+ if (opline->opcode == ZEND_INCLUDE_OR_EVAL &&
+ opline->extended_value != ZEND_EVAL &&
+ opline->op1_type == IS_CONST &&
+ Z_TYPE_P(RT_CONSTANT(opline, opline->op1)) == IS_STRING) {
+
+ zend_string *resolved_path = preload_resolve_path(Z_STR_P(RT_CONSTANT(opline, opline->op1)));
+
+ if (resolved_path) {
+ zend_persistent_script *incl = zend_hash_find_ptr(preload_scripts, resolved_path);
+ if (incl->empty) {
+ MAKE_NOP(opline);
+ } else {
+ if (!IS_ABSOLUTE_PATH(Z_STRVAL_P(RT_CONSTANT(opline, opline->op1)), Z_STRLEN_P(RT_CONSTANT(opline, opline->op1)))) {
+ /* replace relative patch with absolute one */
+ zend_string_release(Z_STR_P(RT_CONSTANT(opline, opline->op1)));
+ ZVAL_STR_COPY(RT_CONSTANT(opline, opline->op1), resolved_path);
+ }
+ }
+ zend_string_release(resolved_path);
+ }
+ }
+ opline++;
+ }
+ } ZEND_HASH_FOREACH_END();
+}
+
+static int preload_optimize(zend_persistent_script *script)
+{
+ zend_class_entry *ce;
+ zend_op_array *op_array;
+
+ zend_shared_alloc_init_xlat_table();
+
+ ZEND_HASH_FOREACH_PTR(&script->script.class_table, ce) {
+ if (ce->ce_flags & ZEND_ACC_TRAIT) {
+ ZEND_HASH_FOREACH_PTR(&ce->function_table, op_array) {
+ if (!(op_array->fn_flags & ZEND_ACC_TRAIT_CLONE)) {
+ zend_shared_alloc_register_xlat_entry(op_array->opcodes, op_array);
+ }
+ } ZEND_HASH_FOREACH_END();
+ }
+ } ZEND_HASH_FOREACH_END();
+
+ if (!zend_optimize_script(&script->script, ZCG(accel_directives).optimization_level, ZCG(accel_directives).opt_debug_level)) {
+ return FAILURE;
+ }
+
+ ZEND_HASH_FOREACH_PTR(&script->script.class_table, ce) {
+ if (ce->ce_flags & ZEND_ACC_IMPLEMENT_TRAITS) {
+ ZEND_HASH_FOREACH_PTR(&ce->function_table, op_array) {
+ if (op_array->fn_flags & ZEND_ACC_TRAIT_CLONE) {
+ zend_op_array *orig_op_array = zend_shared_alloc_get_xlat_entry(op_array->opcodes);
+ if (orig_op_array) {
+ zend_class_entry *scope = op_array->scope;
+ uint32_t fn_flags = op_array->fn_flags;
+ zend_function *prototype = op_array->prototype;
+ HashTable *ht = op_array->static_variables;
+ *op_array = *orig_op_array;
+ op_array->scope = scope;
+ op_array->fn_flags = fn_flags;
+ op_array->prototype = prototype;
+ op_array->static_variables = ht;
+ }
+ }
+ } ZEND_HASH_FOREACH_END();
+ }
+ } ZEND_HASH_FOREACH_END();
+
+ zend_shared_alloc_destroy_xlat_table();
+
+ ZEND_HASH_FOREACH_PTR(preload_scripts, script) {
+ if (!zend_optimize_script(&script->script, ZCG(accel_directives).optimization_level, ZCG(accel_directives).opt_debug_level)) {
+ return FAILURE;
+ }
+ } ZEND_HASH_FOREACH_END();
+ return SUCCESS;
+}
+
+static zend_persistent_script* preload_script_in_shared_memory(zend_persistent_script *new_persistent_script)
+{
+ zend_accel_hash_entry *bucket;
+ uint32_t memory_used;
+ uint32_t checkpoint;
+
+ if (zend_accel_hash_is_full(&ZCSG(hash))) {
+ zend_accel_error(ACCEL_LOG_FATAL, "Not enough entries in hash table for preloading!");
+ return NULL;
+ }
+
+ checkpoint = zend_shared_alloc_checkpoint_xlat_table();
+
+ /* Calculate the required memory size */
+ memory_used = zend_accel_script_persist_calc(new_persistent_script, NULL, 0, 1);
+
+ /* Allocate shared memory */
+#if defined(__AVX__) || defined(__SSE2__)
+ /* Align to 64-byte boundary */
+ ZCG(mem) = zend_shared_alloc(memory_used + 64);
+ if (ZCG(mem)) {
+ ZCG(mem) = (void*)(((zend_uintptr_t)ZCG(mem) + 63L) & ~63L);
+#if defined(__x86_64__)
+ memset(ZCG(mem), 0, memory_used);
+#elif defined(__AVX__)
+ {
+ char *p = (char*)ZCG(mem);
+ char *end = p + memory_used;
+ __m256i ymm0 = _mm256_setzero_si256();
+
+ while (p < end) {
+ _mm256_store_si256((__m256i*)p, ymm0);
+ _mm256_store_si256((__m256i*)(p+32), ymm0);
+ p += 64;
+ }
+ }
+#else
+ {
+ char *p = (char*)ZCG(mem);
+ char *end = p + memory_used;
+ __m128i xmm0 = _mm_setzero_si128();
+
+ while (p < end) {
+ _mm_store_si128((__m128i*)p, xmm0);
+ _mm_store_si128((__m128i*)(p+16), xmm0);
+ _mm_store_si128((__m128i*)(p+32), xmm0);
+ _mm_store_si128((__m128i*)(p+48), xmm0);
+ p += 64;
+ }
+ }
+#endif
+ }
+#else
+ ZCG(mem) = zend_shared_alloc(memory_used);
+ if (ZCG(mem)) {
+ memset(ZCG(mem), 0, memory_used);
+ }
+#endif
+ if (!ZCG(mem)) {
+ zend_accel_error(ACCEL_LOG_FATAL, "Not enough shared memory for preloading!");
+ return NULL;
+ }
+
+ zend_shared_alloc_restore_xlat_table(checkpoint);
+
+ /* Copy into shared memory */
+ new_persistent_script = zend_accel_script_persist(new_persistent_script, NULL, 0, 1);
+
+ new_persistent_script->is_phar = is_phar_file(new_persistent_script->script.filename);
+
+ /* Consistency check */
+ if ((char*)new_persistent_script->mem + new_persistent_script->size != (char*)ZCG(mem)) {
+ zend_accel_error(
+ ((char*)new_persistent_script->mem + new_persistent_script->size < (char*)ZCG(mem)) ? ACCEL_LOG_ERROR : ACCEL_LOG_WARNING,
+ "Internal error: wrong size calculation: %s start=" ZEND_ADDR_FMT ", end=" ZEND_ADDR_FMT ", real=" ZEND_ADDR_FMT "\n",
+ ZSTR_VAL(new_persistent_script->script.filename),
+ (size_t)new_persistent_script->mem,
+ (size_t)((char *)new_persistent_script->mem + new_persistent_script->size),
+ (size_t)ZCG(mem));
+ }
+
+ new_persistent_script->dynamic_members.checksum = zend_accel_script_checksum(new_persistent_script);
+
+ /* store script structure in the hash table */
+ bucket = zend_accel_hash_update(&ZCSG(hash), ZSTR_VAL(new_persistent_script->script.filename), ZSTR_LEN(new_persistent_script->script.filename), 0, new_persistent_script);
+ if (bucket) {
+ zend_accel_error(ACCEL_LOG_INFO, "Cached script '%s'", ZSTR_VAL(new_persistent_script->script.filename));
+ }
+
+ new_persistent_script->dynamic_members.memory_consumption = ZEND_ALIGNED_SIZE(new_persistent_script->size);
+
+ return new_persistent_script;
+}
+
+static void preload_load(void)
+{
+ /* Load into process tables */
+ if (zend_hash_num_elements(&ZCSG(preload_script)->script.function_table)) {
+ Bucket *p = ZCSG(preload_script)->script.function_table.arData;
+ Bucket *end = p + ZCSG(preload_script)->script.function_table.nNumUsed;
+
+ for (; p != end; p++) {
+ _zend_hash_append_ptr_ex(CG(function_table), p->key, Z_PTR(p->val), 1);
+ }
+ }
+
+ if (zend_hash_num_elements(&ZCSG(preload_script)->script.class_table)) {
+ Bucket *p = ZCSG(preload_script)->script.class_table.arData;
+ Bucket *end = p + ZCSG(preload_script)->script.class_table.nNumUsed;
+
+ for (; p != end; p++) {
+ _zend_hash_append_ptr_ex(CG(class_table), p->key, Z_PTR(p->val), 1);
+ }
+ }
+
+ if (EG(zend_constants)) {
+ EG(persistent_constants_count) = EG(zend_constants)->nNumUsed;
+ }
+ if (EG(function_table)) {
+ EG(persistent_functions_count) = EG(function_table)->nNumUsed;
+ }
+ if (EG(class_table)) {
+ EG(persistent_classes_count) = EG(class_table)->nNumUsed;
+ }
+ CG(map_ptr_last) = ZCSG(map_ptr_last);
+}
+
+static int accel_preload(const char *config)
+{
+ zend_file_handle file_handle;
+ int ret;
+ uint32_t orig_compiler_options;
+
+ ZCG(enabled) = 0;
+ preload_orig_compile_file = accelerator_orig_compile_file;
+ accelerator_orig_compile_file = preload_compile_file;
+
+ orig_compiler_options = CG(compiler_options);
+ CG(compiler_options) |= ZEND_COMPILE_HANDLE_OP_ARRAY;
+// CG(compiler_options) |= ZEND_COMPILE_IGNORE_INTERNAL_CLASSES;
+ CG(compiler_options) |= ZEND_COMPILE_DELAYED_BINDING;
+ CG(compiler_options) |= ZEND_COMPILE_NO_CONSTANT_SUBSTITUTION;
+// CG(compiler_options) |= ZEND_COMPILE_IGNORE_OTHER_FILES;
+
+ /* Compile and execute proloading script */
+ memset(&file_handle, 0, sizeof(file_handle));
+ file_handle.filename = (char*)config;
+ file_handle.free_filename = 0;
+ file_handle.type = ZEND_HANDLE_FILENAME;
+ file_handle.opened_path = NULL;
+ file_handle.handle.fp = NULL;
+
+ preload_scripts = emalloc(sizeof(HashTable));
+ zend_hash_init(preload_scripts, 0, NULL, NULL, 0);
+
+ zend_try {
+ ret = zend_execute_scripts(ZEND_REQUIRE, NULL, 1, &file_handle);
+ } zend_catch {
+ ret = FAILURE;
+ } zend_end_try();
+
+ CG(compiler_options) = orig_compiler_options;
+ accelerator_orig_compile_file = preload_orig_compile_file;
+ ZCG(enabled) = 1;
+
+ zend_destroy_file_handle(&file_handle);
+
+ if (ret == SUCCESS) {
+ zend_persistent_script *script;
+ zend_string *filename;
+ int i;
+
+ /* Release stored values to avoid dangling pointers */
+ zend_hash_graceful_reverse_destroy(&EG(symbol_table));
+ zend_hash_init(&EG(symbol_table), 0, NULL, ZVAL_PTR_DTOR, 0);
+
+ preload_link();
+ preload_remove_empty_includes();
+
+ /* Don't preload constants */
+ if (EG(zend_constants)) {
+ zval *zv;
+ ZEND_HASH_REVERSE_FOREACH_VAL(EG(zend_constants), zv) {
+ zend_constant *c = Z_PTR_P(zv);
+ if (ZEND_CONSTANT_FLAGS(c) & CONST_PERSISTENT) {
+ break;
+ }
+ EG(zend_constants)->pDestructor(zv);
+ } ZEND_HASH_FOREACH_END_DEL();
+ }
+
+ script = create_persistent_script();
+
+ /* Fill in the ping_auto_globals_mask for the new script. If jit for auto globals is enabled we
+ will have to ping the used auto global variables before execution */
+ if (PG(auto_globals_jit)) {
+ script->ping_auto_globals_mask = zend_accel_get_auto_globals();
+ } else {
+ script->ping_auto_globals_mask = zend_accel_get_auto_globals_no_jit();
+ }
+
+ /* Store all functions and classes in a single pseudo-file */
+ filename = zend_string_init("$PRELOAD$", strlen("$PRELOAD$"), 0);
+#if ZEND_USE_ABS_CONST_ADDR
+ init_op_array(&script->script.main_op_array, ZEND_USER_FUNCTION, 1);
+#else
+ init_op_array(&script->script.main_op_array, ZEND_USER_FUNCTION, 2);
+#endif
+ script->script.main_op_array.last = 1;
+ script->script.main_op_array.last_literal = 1;
+#if ZEND_USE_ABS_CONST_ADDR
+ script->script.main_op_array.literals = (zval*)emalloc(sizeof(zval));
+#else
+ script->script.main_op_array.literals = (zval*)(script->script.main_op_array.opcodes + 1);
+#endif
+ ZVAL_NULL(script->script.main_op_array.literals);
+ memset(script->script.main_op_array.opcodes, 0, sizeof(zend_op));
+ script->script.main_op_array.opcodes[0].opcode = ZEND_RETURN;
+ script->script.main_op_array.opcodes[0].op1_type = IS_CONST;
+ script->script.main_op_array.opcodes[0].op1.constant = 0;
+ ZEND_PASS_TWO_UPDATE_CONSTANT(&script->script.main_op_array, script->script.main_op_array.opcodes, script->script.main_op_array.opcodes[0].op1);
+
+ script->script.main_op_array.filename = filename;
+ script->script.filename = zend_string_copy(filename);
+
+ script->script.first_early_binding_opline = (uint32_t)-1;
+
+ preload_move_user_functions(CG(function_table), &script->script.function_table);
+ preload_move_user_classes(CG(class_table), &script->script.class_table);
+
+ zend_hash_sort_ex(&script->script.class_table, preload_sort_classes, NULL, 0);
+
+ if (preload_optimize(script) != SUCCESS) {
+ zend_accel_error(ACCEL_LOG_FATAL, "Optimization error during preloading!");
+ return FAILURE;
+ }
+
+ zend_shared_alloc_init_xlat_table();
+
+ HANDLE_BLOCK_INTERRUPTIONS();
+ SHM_UNPROTECT();
+
+ ZCSG(preload_script) = preload_script_in_shared_memory(script);
+
+ SHM_PROTECT();
+ HANDLE_UNBLOCK_INTERRUPTIONS();
+
+ zend_string_release(filename);
+
+ ZEND_ASSERT(ZCSG(preload_script)->arena_size == 0);
+
+ preload_load();
+
+ /* Store individual scripts with unlinked classes */
+ HANDLE_BLOCK_INTERRUPTIONS();
+ SHM_UNPROTECT();
+
+ i = 0;
+ ZCSG(saved_scripts) = zend_shared_alloc((zend_hash_num_elements(preload_scripts) + 1) * sizeof(void*));
+ ZEND_HASH_FOREACH_PTR(preload_scripts, script) {
+ ZCSG(saved_scripts)[i++] = preload_script_in_shared_memory(script);
+ } ZEND_HASH_FOREACH_END();
+ ZCSG(saved_scripts)[i] = NULL;
+
+ zend_shared_alloc_save_state();
+ accel_interned_strings_save_state();
+
+ SHM_PROTECT();
+ HANDLE_UNBLOCK_INTERRUPTIONS();
+
+ zend_shared_alloc_destroy_xlat_table();
+ }
+
+ zend_hash_destroy(preload_scripts);
+ efree(preload_scripts);
+ preload_scripts = NULL;
+
+ return ret;
+}
+
+size_t preload_ub_write(const char *str, size_t str_length)
+{
+ return fwrite(str, 1, str_length, stdout);
+}
+
+void preload_flush(void *server_context)
+{
+ fflush(stdout);
+}
+
+static int accel_finish_startup(void)
+{
+ if (!ZCG(enabled) || !accel_startup_ok) {
+ return SUCCESS;
+ }
+
+ if (ZCG(accel_directives).preload && *ZCG(accel_directives).preload) {
+ int ret = SUCCESS;
+
+ int (*orig_activate)(TSRMLS_D) = sapi_module.activate;
+ int (*orig_deactivate)(TSRMLS_D) = sapi_module.deactivate;
+ void (*orig_register_server_variables)(zval *track_vars_array TSRMLS_DC) = sapi_module.register_server_variables;
+ int (*orig_header_handler)(sapi_header_struct *sapi_header, sapi_header_op_enum op, sapi_headers_struct *sapi_headers TSRMLS_DC) = sapi_module.header_handler;
+ int (*orig_send_headers)(sapi_headers_struct *sapi_headers TSRMLS_DC) = sapi_module.send_headers;
+ void (*orig_send_header)(sapi_header_struct *sapi_header, void *server_context TSRMLS_DC)= sapi_module.send_header;
+ char *(*orig_getenv)(char *name, size_t name_len TSRMLS_DC) = sapi_module.getenv;
+ size_t (*orig_ub_write)(const char *str, size_t str_length) = sapi_module.ub_write;
+ void (*orig_flush)(void *server_context) = sapi_module.flush;
+#ifdef ZEND_SIGNALS
+ zend_bool old_reset_signals = SIGG(reset);
+#endif
+
+ if (UNEXPECTED(file_cache_only)) {
+ zend_accel_error(ACCEL_LOG_WARNING, "Preloading doesn't work in \"file_cache_only\" mode");
+ return SUCCESS;
+ }
+
+ /* exclusive lock */
+ zend_shared_alloc_lock();
+
+ if (ZCSG(preload_script)) {
+ /* Preloading was done in another process */
+ preload_load();
+ zend_shared_alloc_unlock();
+ return SUCCESS;
+ }
+
+ sapi_module.activate = NULL;
+ sapi_module.deactivate = NULL;
+ sapi_module.register_server_variables = NULL;
+ sapi_module.header_handler = NULL;
+ sapi_module.send_headers = NULL;
+ sapi_module.send_header = NULL;
+ sapi_module.getenv = NULL;
+ sapi_module.ub_write = preload_ub_write;
+ sapi_module.flush = preload_flush;
+
+ zend_interned_strings_switch_storage(1);
+
+#ifdef ZEND_SIGNALS
+ SIGG(reset) = 0;
+#endif
+ if (php_request_startup() == SUCCESS) {
+
+ /* don't send headers */
+ SG(headers_sent) = 1;
+ SG(request_info).no_headers = 1;
+ php_output_set_status(0);
+
+ ZCG(auto_globals_mask) = 0;
+ ZCG(request_time) = (time_t)sapi_get_request_time();
+ ZCG(cache_opline) = NULL;
+ ZCG(cache_persistent_script) = NULL;
+ ZCG(include_path_key_len) = 0;
+ ZCG(include_path_check) = 1;
+
+ ZCG(cwd) = NULL;
+ ZCG(cwd_key_len) = 0;
+ ZCG(cwd_check) = 1;
+
+ if (accel_preload(ZCG(accel_directives).preload) != SUCCESS) {
+ ret = FAILURE;
+ }
+
+ php_request_shutdown(NULL);
+ } else {
+ ret = FAILURE;
+ }
+#ifdef ZEND_SIGNALS
+ SIGG(reset) = old_reset_signals;
+#endif
+
+ sapi_module.activate = orig_activate;
+ sapi_module.deactivate = orig_deactivate;
+ sapi_module.register_server_variables = orig_register_server_variables;
+ sapi_module.header_handler = orig_header_handler;
+ sapi_module.send_headers = orig_send_headers;
+ sapi_module.send_header = orig_send_header;
+ sapi_module.getenv = orig_getenv;
+ sapi_module.ub_write = orig_ub_write;
+ sapi_module.flush = orig_flush;
+
+ zend_shared_alloc_unlock();
+
+ sapi_activate();
+
+ return ret;
+ }
+
+ return SUCCESS;
+}
+
ZEND_EXT_API zend_extension zend_extension_entry = {
ACCELERATOR_PRODUCT_NAME, /* name */
PHP_VERSION, /* version */
diff --git a/ext/opcache/ZendAccelerator.h b/ext/opcache/ZendAccelerator.h
index e9fae11984..2313666725 100644
--- a/ext/opcache/ZendAccelerator.h
+++ b/ext/opcache/ZendAccelerator.h
@@ -143,6 +143,7 @@ typedef struct _zend_persistent_script {
accel_time_t timestamp; /* the script modification time */
zend_bool corrupted;
zend_bool is_phar;
+ zend_bool empty;
void *mem; /* shared memory area used by script structures */
size_t size; /* size of used shared memory */
@@ -212,6 +213,7 @@ typedef struct _zend_accel_directives {
#ifdef HAVE_HUGE_CODE_PAGES
zend_bool huge_code_pages;
#endif
+ char *preload;
} zend_accel_directives;
typedef struct _zend_accel_globals {
@@ -284,6 +286,10 @@ typedef struct _zend_accel_shared_globals {
#endif
zend_bool restart_in_progress;
+ /* Preloading */
+ zend_persistent_script *preload_script;
+ zend_persistent_script **saved_scripts;
+
/* uninitialized HashTable Support */
uint32_t uninitialized_bucket[-HT_MIN_MASK];
diff --git a/ext/opcache/tests/preload.inc b/ext/opcache/tests/preload.inc
new file mode 100644
index 0000000000..af20c4947f
--- /dev/null
+++ b/ext/opcache/tests/preload.inc
@@ -0,0 +1,39 @@
+<?php
+function f1() {
+}
+
+if (isset($rt)) {
+ function f2() {
+ }
+}
+
+interface a {
+ function foo();
+ function bar();
+}
+interface b {
+ function foo();
+}
+
+abstract class c {
+ function bar() { }
+}
+
+class x extends c implements a, b {
+ function foo() { }
+}
+
+trait T1 {
+ static function foo() {
+ var_dump(__METHOD__);
+ }
+}
+trait T2 {
+ use T1;
+ static function bar() {
+ var_dump(__METHOD__);
+ }
+}
+class Y {
+ use T2;
+}
diff --git a/ext/opcache/tests/preload_001.phpt b/ext/opcache/tests/preload_001.phpt
new file mode 100644
index 0000000000..8b1fa1ff31
--- /dev/null
+++ b/ext/opcache/tests/preload_001.phpt
@@ -0,0 +1,24 @@
+--TEST--
+Preloading basic test
+--INI--
+opcache.enable=1
+opcache.enable_cli=1
+opcache.optimization_level=-1
+opcache.preload={PWD}/preload.inc
+--SKIPIF--
+<?php require_once('skipif.inc'); ?>
+--FILE--
+<?php
+var_dump(function_exists("f1"));
+var_dump(function_exists("f2"));
+
+$rt = true;
+include(__DIR__ . "/preload.inc");
+var_dump(function_exists("f2"));
+?>
+OK
+--EXPECTF--
+bool(true)
+bool(false)
+bool(true)
+OK
diff --git a/ext/opcache/tests/preload_002.phpt b/ext/opcache/tests/preload_002.phpt
new file mode 100644
index 0000000000..38c3e8af68
--- /dev/null
+++ b/ext/opcache/tests/preload_002.phpt
@@ -0,0 +1,19 @@
+--TEST--
+Preloading prototypes
+--INI--
+opcache.enable=1
+opcache.enable_cli=1
+opcache.optimization_level=-1
+opcache.preload={PWD}/preload.inc
+--SKIPIF--
+<?php require_once('skipif.inc'); ?>
+--FILE--
+<?php
+var_dump((new ReflectionMethod('x', 'foo'))->getPrototype()->class);
+vaR_dump((new ReflectionMethod('x', 'bar'))->getPrototype()->class);
+?>
+OK
+--EXPECT--
+string(1) "b"
+string(1) "a"
+OK
diff --git a/ext/opcache/tests/preload_003.phpt b/ext/opcache/tests/preload_003.phpt
new file mode 100644
index 0000000000..b808387967
--- /dev/null
+++ b/ext/opcache/tests/preload_003.phpt
@@ -0,0 +1,19 @@
+--TEST--
+Preloading classes linked with traits
+--INI--
+opcache.enable=1
+opcache.enable_cli=1
+opcache.optimization_level=-1
+opcache.preload={PWD}/preload.inc
+--SKIPIF--
+<?php require_once('skipif.inc'); ?>
+--FILE--
+<?php
+Y::foo();
+Y::bar();
+?>
+OK
+--EXPECT--
+string(7) "T1::foo"
+string(7) "T2::bar"
+OK
diff --git a/ext/opcache/zend_accelerator_module.c b/ext/opcache/zend_accelerator_module.c
index 8f89309f13..7a1f46653c 100644
--- a/ext/opcache/zend_accelerator_module.c
+++ b/ext/opcache/zend_accelerator_module.c
@@ -323,6 +323,7 @@ ZEND_INI_BEGIN()
#ifdef HAVE_HUGE_CODE_PAGES
STD_PHP_INI_BOOLEAN("opcache.huge_code_pages" , "0" , PHP_INI_SYSTEM, OnUpdateBool, accel_directives.huge_code_pages, zend_accel_globals, accel_globals)
#endif
+ STD_PHP_INI_ENTRY("opcache.preload" , "" , PHP_INI_SYSTEM, OnUpdateStringUnempty, accel_directives.preload, zend_accel_globals, accel_globals)
ZEND_INI_END()
static int filename_is_in_cache(zend_string *filename)
@@ -661,6 +662,49 @@ static ZEND_FUNCTION(opcache_get_status)
add_assoc_double(&statistics, "opcache_hit_rate", reqs?(((double) ZCSG(hits))/reqs)*100.0:0);
add_assoc_zval(return_value, "opcache_statistics", &statistics);
+ if (ZCSG(preload_script)) {
+ array_init(&statistics);
+
+ add_assoc_long(&statistics, "memory_consumption", ZCSG(preload_script)->dynamic_members.memory_consumption);
+
+ if (zend_hash_num_elements(&ZCSG(preload_script)->script.function_table)) {
+ zend_op_array *op_array;
+
+ array_init(&scripts);
+ ZEND_HASH_FOREACH_PTR(&ZCSG(preload_script)->script.function_table, op_array) {
+ add_next_index_str(&scripts, op_array->function_name);
+ } ZEND_HASH_FOREACH_END();
+ add_assoc_zval(&statistics, "functions", &scripts);
+ }
+
+ if (zend_hash_num_elements(&ZCSG(preload_script)->script.class_table)) {
+ zend_class_entry *ce;
+ zend_string *key;
+
+ array_init(&scripts);
+ ZEND_HASH_FOREACH_STR_KEY_PTR(&ZCSG(preload_script)->script.class_table, key, ce) {
+ if (ce->refcount > 1 && !zend_string_equals_ci(key, ce->name)) {
+ add_next_index_str(&scripts, key);
+ } else {
+ add_next_index_str(&scripts, ce->name);
+ }
+ } ZEND_HASH_FOREACH_END();
+ add_assoc_zval(&statistics, "classes", &scripts);
+ }
+
+ if (ZCSG(saved_scripts)) {
+ zend_persistent_script **p = ZCSG(saved_scripts);
+
+ array_init(&scripts);
+ while (*p) {
+ add_next_index_str(&scripts, (*p)->script.filename);
+ p++;
+ }
+ add_assoc_zval(&statistics, "scripts", &scripts);
+ }
+ add_assoc_zval(return_value, "preload_statistics", &statistics);
+ }
+
if (fetch_scripts) {
/* accelerated scripts */
if (accelerator_get_scripts(&scripts)) {
diff --git a/run-tests.php b/run-tests.php
index aa9f67e07a..43302d1b6f 100755
--- a/run-tests.php
+++ b/run-tests.php
@@ -264,6 +264,7 @@ $ini_overwrites = array(
'log_errors_max_len=0',
'opcache.fast_shutdown=0',
'opcache.file_update_protection=0',
+ 'opcache.preload=',
'zend.assertions=1',
);