Skip to content

Commit e011952

Browse files
authored
Preload unlinked classes, remove preload autoload (#7311)
Currently, classes that can't be linked get moved back into the original script and are not preloaded. As such classes may be referenced from functions that did get preloaded, there is a preload autoload mechanism to load them at runtime. Since PHP 8.1, we can safely preload unlinked classes, which will then go through usual lazy loading. This means that we no longer need the preload autoload mechanism. However, we need to be careful not to modify any hash table buckets in-place, and should create new buckets for lazy loaded classes.
1 parent fb52b3c commit e011952

File tree

8 files changed

+35
-157
lines changed

8 files changed

+35
-157
lines changed

Zend/zend.c

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,6 @@ ZEND_API char *(*zend_getenv)(const char *name, size_t name_len);
8585
ZEND_API zend_string *(*zend_resolve_path)(zend_string *filename);
8686
ZEND_API zend_result (*zend_post_startup_cb)(void) = NULL;
8787
ZEND_API void (*zend_post_shutdown_cb)(void) = NULL;
88-
ZEND_API zend_result (*zend_preload_autoload)(zend_string *filename) = NULL;
8988

9089
/* This callback must be signal handler safe! */
9190
void (*zend_on_timeout)(int seconds);

Zend/zend.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -332,9 +332,6 @@ extern ZEND_API zend_string *(*zend_resolve_path)(zend_string *filename);
332332
extern ZEND_API zend_result (*zend_post_startup_cb)(void);
333333
extern ZEND_API void (*zend_post_shutdown_cb)(void);
334334

335-
/* Callback for loading of not preloaded part of the script */
336-
extern ZEND_API zend_result (*zend_preload_autoload)(zend_string *filename);
337-
338335
ZEND_API ZEND_COLD void zend_error(int type, const char *format, ...) ZEND_ATTRIBUTE_FORMAT(printf, 2, 3);
339336
ZEND_API ZEND_COLD ZEND_NORETURN void zend_error_noreturn(int type, const char *format, ...) ZEND_ATTRIBUTE_FORMAT(printf, 2, 3);
340337
/* For custom format specifiers like H */

Zend/zend_compile.c

Lines changed: 20 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1104,9 +1104,16 @@ ZEND_API zend_class_entry *zend_bind_class_in_slot(
11041104
zval *class_table_slot, zval *lcname, zend_string *lc_parent_name)
11051105
{
11061106
zend_class_entry *ce = Z_PTR_P(class_table_slot);
1107-
zval *zv = zend_hash_set_bucket_key(
1108-
EG(class_table), (Bucket *) class_table_slot, Z_STR_P(lcname));
1109-
if (UNEXPECTED(!zv)) {
1107+
bool is_preloaded =
1108+
(ce->ce_flags & ZEND_ACC_PRELOADED) && !(CG(compiler_options) & ZEND_COMPILE_PRELOAD);
1109+
bool success;
1110+
if (EXPECTED(!is_preloaded)) {
1111+
success = zend_hash_set_bucket_key(EG(class_table), (Bucket*) class_table_slot, Z_STR_P(lcname)) != NULL;
1112+
} else {
1113+
/* If preloading is used, don't replace the existing bucket, add a new one. */
1114+
success = zend_hash_add_ptr(EG(class_table), Z_STR_P(lcname), ce) != NULL;
1115+
}
1116+
if (UNEXPECTED(!success)) {
11101117
zend_error_noreturn(E_COMPILE_ERROR, "Cannot declare %s %s, because the name is already in use", zend_get_object_type(ce), ZSTR_VAL(ce->name));
11111118
return NULL;
11121119
}
@@ -1120,9 +1127,13 @@ ZEND_API zend_class_entry *zend_bind_class_in_slot(
11201127
return ce;
11211128
}
11221129

1123-
/* Reload bucket pointer, the hash table may have been reallocated */
1124-
zv = zend_hash_find(EG(class_table), Z_STR_P(lcname));
1125-
zend_hash_set_bucket_key(EG(class_table), (Bucket *) zv, Z_STR_P(lcname + 1));
1130+
if (!is_preloaded) {
1131+
/* Reload bucket pointer, the hash table may have been reallocated */
1132+
zval *zv = zend_hash_find(EG(class_table), Z_STR_P(lcname));
1133+
zend_hash_set_bucket_key(EG(class_table), (Bucket *) zv, Z_STR_P(lcname + 1));
1134+
} else {
1135+
zend_hash_del(EG(class_table), Z_STR_P(lcname));
1136+
}
11261137
return NULL;
11271138
}
11281139

@@ -1137,23 +1148,9 @@ ZEND_API zend_result do_bind_class(zval *lcname, zend_string *lc_parent_name) /*
11371148

11381149
if (UNEXPECTED(!zv)) {
11391150
ce = zend_hash_find_ptr(EG(class_table), Z_STR_P(lcname));
1140-
if (ce) {
1141-
zend_error_noreturn(E_COMPILE_ERROR, "Cannot declare %s %s, because the name is already in use", zend_get_object_type(ce), ZSTR_VAL(ce->name));
1142-
return FAILURE;
1143-
} else {
1144-
do {
1145-
ZEND_ASSERT(EG(current_execute_data)->func->op_array.fn_flags & ZEND_ACC_PRELOADED);
1146-
if (zend_preload_autoload
1147-
&& zend_preload_autoload(EG(current_execute_data)->func->op_array.filename) == SUCCESS) {
1148-
zv = zend_hash_find_known_hash(EG(class_table), Z_STR_P(rtd_key));
1149-
if (EXPECTED(zv != NULL)) {
1150-
break;
1151-
}
1152-
}
1153-
zend_error_noreturn(E_ERROR, "Class %s wasn't preloaded", Z_STRVAL_P(lcname));
1154-
return FAILURE;
1155-
} while (0);
1156-
}
1151+
ZEND_ASSERT(ce);
1152+
zend_error_noreturn(E_COMPILE_ERROR, "Cannot declare %s %s, because the name is already in use", zend_get_object_type(ce), ZSTR_VAL(ce->name));
1153+
return FAILURE;
11571154
}
11581155

11591156
/* Register the derived class */

Zend/zend_inheritance.c

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3017,12 +3017,19 @@ static inheritance_status zend_can_early_bind(zend_class_entry *ce, zend_class_e
30173017

30183018
static zend_always_inline bool register_early_bound_ce(zval *delayed_early_binding, zend_string *lcname, zend_class_entry *ce) {
30193019
if (delayed_early_binding) {
3020-
if (UNEXPECTED(zend_hash_set_bucket_key(EG(class_table), (Bucket *)delayed_early_binding, lcname) == NULL)) {
3021-
zend_error_noreturn(E_COMPILE_ERROR, "Cannot declare %s %s, because the name is already in use", zend_get_object_type(ce), ZSTR_VAL(ce->name));
3022-
return false;
3020+
if (EXPECTED(!(ce->ce_flags & ZEND_ACC_PRELOADED))) {
3021+
if (zend_hash_set_bucket_key(EG(class_table), (Bucket *)delayed_early_binding, lcname) != NULL) {
3022+
Z_CE_P(delayed_early_binding) = ce;
3023+
return true;
3024+
}
3025+
} else {
3026+
/* If preloading is used, don't replace the existing bucket, add a new one. */
3027+
if (zend_hash_add_ptr(EG(class_table), lcname, ce) != NULL) {
3028+
return true;
3029+
}
30233030
}
3024-
Z_CE_P(delayed_early_binding) = ce;
3025-
return true;
3031+
zend_error_noreturn(E_COMPILE_ERROR, "Cannot declare %s %s, because the name is already in use", zend_get_object_type(ce), ZSTR_VAL(ce->name));
3032+
return false;
30263033
}
30273034
return zend_hash_add_ptr(CG(class_table), lcname, ce) != NULL;
30283035
}

Zend/zend_opcode.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ ZEND_API void destroy_zend_class(zval *zv)
277277
zend_class_entry *ce = Z_PTR_P(zv);
278278
zend_function *fn;
279279

280-
if (ce->ce_flags & (ZEND_ACC_IMMUTABLE|ZEND_ACC_PRELOADED)) {
280+
if (ce->ce_flags & ZEND_ACC_IMMUTABLE) {
281281
return;
282282
}
283283

Zend/zend_vm_def.h

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7609,20 +7609,6 @@ ZEND_VM_HANDLER(146, ZEND_DECLARE_ANON_CLASS, ANY, ANY, CACHE_SLOT)
76097609
if (UNEXPECTED(ce == NULL)) {
76107610
zend_string *rtd_key = Z_STR_P(RT_CONSTANT(opline, opline->op1));
76117611
zv = zend_hash_find_known_hash(EG(class_table), rtd_key);
7612-
if (UNEXPECTED(zv == NULL)) {
7613-
SAVE_OPLINE();
7614-
do {
7615-
ZEND_ASSERT(EX(func)->op_array.fn_flags & ZEND_ACC_PRELOADED);
7616-
if (zend_preload_autoload
7617-
&& zend_preload_autoload(EX(func)->op_array.filename) == SUCCESS) {
7618-
zv = zend_hash_find_known_hash(EG(class_table), rtd_key);
7619-
if (EXPECTED(zv != NULL)) {
7620-
break;
7621-
}
7622-
}
7623-
zend_error_noreturn(E_ERROR, "Anonymous class wasn't preloaded");
7624-
} while (0);
7625-
}
76267612
ZEND_ASSERT(zv != NULL);
76277613
ce = Z_CE_P(zv);
76287614
if (!(ce->ce_flags & ZEND_ACC_LINKED)) {

Zend/zend_vm_execute.h

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2937,20 +2937,6 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DECLARE_ANON_CLASS_SPEC_HANDLE
29372937
if (UNEXPECTED(ce == NULL)) {
29382938
zend_string *rtd_key = Z_STR_P(RT_CONSTANT(opline, opline->op1));
29392939
zv = zend_hash_find_known_hash(EG(class_table), rtd_key);
2940-
if (UNEXPECTED(zv == NULL)) {
2941-
SAVE_OPLINE();
2942-
do {
2943-
ZEND_ASSERT(EX(func)->op_array.fn_flags & ZEND_ACC_PRELOADED);
2944-
if (zend_preload_autoload
2945-
&& zend_preload_autoload(EX(func)->op_array.filename) == SUCCESS) {
2946-
zv = zend_hash_find_known_hash(EG(class_table), rtd_key);
2947-
if (EXPECTED(zv != NULL)) {
2948-
break;
2949-
}
2950-
}
2951-
zend_error_noreturn(E_ERROR, "Anonymous class wasn't preloaded");
2952-
} while (0);
2953-
}
29542940
ZEND_ASSERT(zv != NULL);
29552941
ce = Z_CE_P(zv);
29562942
if (!(ce->ce_flags & ZEND_ACC_LINKED)) {

ext/opcache/ZendAccelerator.c

Lines changed: 2 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -3911,8 +3911,6 @@ static void preload_link(void)
39113911
zend_string *key;
39123912
bool found, changed;
39133913
uint32_t i;
3914-
dtor_func_t orig_dtor;
3915-
zend_function *function;
39163914

39173915
/* Resolve class dependencies */
39183916
do {
@@ -4031,9 +4029,7 @@ static void preload_link(void)
40314029
} ZEND_HASH_FOREACH_END();
40324030
} while (changed);
40334031

4034-
/* Move unlinked clases (and with unresolved constants) back to scripts */
4035-
orig_dtor = EG(class_table)->pDestructor;
4036-
EG(class_table)->pDestructor = NULL;
4032+
/* Warn for classes that could not be linked. */
40374033
ZEND_HASH_REVERSE_FOREACH_STR_KEY_VAL(EG(class_table), key, zv) {
40384034
ce = Z_PTR_P(zv);
40394035
if (ce->type == ZEND_INTERNAL_CLASS) {
@@ -4055,25 +4051,8 @@ static void preload_link(void)
40554051
ZSTR_VAL(ce->name), kind, name);
40564052
}
40574053
zend_string_release(key);
4058-
} else {
4059-
continue;
40604054
}
4061-
ce->ce_flags &= ~ZEND_ACC_PRELOADED;
4062-
ZEND_HASH_FOREACH_PTR(&ce->function_table, function) {
4063-
if (EXPECTED(function->type == ZEND_USER_FUNCTION)
4064-
&& function->common.scope == ce) {
4065-
function->common.fn_flags &= ~ZEND_ACC_PRELOADED;
4066-
}
4067-
} ZEND_HASH_FOREACH_END();
4068-
script = zend_hash_find_ptr(preload_scripts, ce->info.user.filename);
4069-
ZEND_ASSERT(script);
4070-
zend_hash_add(&script->script.class_table, key, zv);
4071-
ZVAL_UNDEF(zv);
4072-
zend_string_release(key);
4073-
EG(class_table)->nNumOfElements--;
40744055
} ZEND_HASH_FOREACH_END();
4075-
EG(class_table)->pDestructor = orig_dtor;
4076-
zend_hash_rehash(EG(class_table));
40774056

40784057
/* Remove DECLARE opcodes */
40794058
ZEND_HASH_FOREACH_PTR(preload_scripts, script) {
@@ -4087,7 +4066,7 @@ static void preload_link(void)
40874066
case ZEND_DECLARE_CLASS:
40884067
case ZEND_DECLARE_CLASS_DELAYED:
40894068
key = Z_STR_P(RT_CONSTANT(opline, opline->op1) + 1);
4090-
if (!zend_hash_exists(&script->script.class_table, key)) {
4069+
if (!zend_hash_exists(CG(class_table), key)) {
40914070
MAKE_NOP(opline);
40924071
}
40934072
break;
@@ -4405,8 +4384,6 @@ static zend_persistent_script* preload_script_in_shared_memory(zend_persistent_s
44054384
return new_persistent_script;
44064385
}
44074386

4408-
static zend_result preload_autoload(zend_string *filename);
4409-
44104387
static void preload_load(void)
44114388
{
44124389
/* Load into process tables */
@@ -4450,77 +4427,6 @@ static void preload_load(void)
44504427
memset((void **) ZEND_MAP_PTR_REAL_BASE(CG(map_ptr_base)) + old_map_ptr_last, 0,
44514428
(CG(map_ptr_last) - old_map_ptr_last) * sizeof(void *));
44524429
}
4453-
4454-
zend_preload_autoload = preload_autoload;
4455-
}
4456-
4457-
static zend_result preload_autoload(zend_string *filename)
4458-
{
4459-
zend_persistent_script *persistent_script;
4460-
zend_op_array *op_array;
4461-
zend_execute_data *old_execute_data;
4462-
zend_class_entry *old_fake_scope;
4463-
bool do_bailout = 0;
4464-
int ret = SUCCESS;
4465-
4466-
if (zend_hash_exists(&EG(included_files), filename)) {
4467-
return FAILURE;
4468-
}
4469-
4470-
persistent_script = zend_accel_hash_find(&ZCSG(hash), filename);
4471-
if (!persistent_script) {
4472-
return FAILURE;
4473-
}
4474-
4475-
zend_hash_add_empty_element(&EG(included_files), filename);
4476-
4477-
if (persistent_script->ping_auto_globals_mask & ~ZCG(auto_globals_mask)) {
4478-
zend_accel_set_auto_globals(persistent_script->ping_auto_globals_mask & ~ZCG(auto_globals_mask));
4479-
}
4480-
4481-
op_array = zend_accel_load_script(persistent_script, 1);
4482-
if (!op_array) {
4483-
return FAILURE;
4484-
}
4485-
4486-
/* Execute in global context */
4487-
old_execute_data = EG(current_execute_data);
4488-
EG(current_execute_data) = NULL;
4489-
old_fake_scope = EG(fake_scope);
4490-
EG(fake_scope) = NULL;
4491-
zend_exception_save();
4492-
4493-
zend_try {
4494-
zend_execute(op_array, NULL);
4495-
} zend_catch {
4496-
do_bailout = 1;
4497-
} zend_end_try();
4498-
4499-
if (EG(exception)) {
4500-
ret = FAILURE;
4501-
}
4502-
4503-
zend_exception_restore();
4504-
EG(fake_scope) = old_fake_scope;
4505-
EG(current_execute_data) = old_execute_data;
4506-
while (old_execute_data) {
4507-
if (old_execute_data->func && (ZEND_CALL_INFO(old_execute_data) & ZEND_CALL_HAS_SYMBOL_TABLE)) {
4508-
if (old_execute_data->symbol_table == &EG(symbol_table)) {
4509-
zend_attach_symbol_table(old_execute_data);
4510-
}
4511-
break;
4512-
}
4513-
old_execute_data = old_execute_data->prev_execute_data;
4514-
}
4515-
4516-
destroy_op_array(op_array);
4517-
efree_size(op_array, sizeof(zend_op_array));
4518-
4519-
if (do_bailout) {
4520-
zend_bailout();
4521-
}
4522-
4523-
return ret;
45244430
}
45254431

45264432
static int accel_preload(const char *config, bool in_child)

0 commit comments

Comments
 (0)