From 38d216e3cd32a392979e632f5fc703a36fbc3068 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Mon, 3 Mar 2025 10:54:16 +0100 Subject: [PATCH 1/2] Snapshot --- Zend/zend_alloc.c | 104 +++++- Zend/zend_alloc.h | 11 +- Zend/zend_builtin_functions.c | 482 ++++++++++++++++++++++++- Zend/zend_builtin_functions.stub.php | 4 + Zend/zend_builtin_functions_arginfo.h | 15 +- Zend/zend_closures.c | 2 + Zend/zend_exceptions.c | 1 + Zend/zend_execute_API.c | 1 + Zend/zend_globals.h | 2 + Zend/zend_hash.h | 17 + Zend/zend_inheritance.c | 2 +- Zend/zend_iterators.c | 3 +- Zend/zend_object_handlers.c | 17 + Zend/zend_object_handlers.h | 6 + Zend/zend_snapshot.c | 11 + Zend/zend_snapshot.h | 39 ++ Zend/zend_weakrefs.c | 15 +- Zend/zend_weakrefs.h | 1 + configure.ac | 1 + ext/date/php_date.c | 27 +- ext/date/php_date.h | 4 + ext/intl/dateformat/dateformat_class.c | 17 +- ext/intl/dateformat/dateformat_class.h | 1 + ext/intl/dateformat/dateformat_data.c | 2 +- ext/intl/msgformat/msgformat_class.c | 15 +- ext/intl/msgformat/msgformat_class.h | 1 + ext/opcache/zend_accelerator_module.c | 14 + ext/reflection/php_reflection.c | 2 + ext/spl/php_spl.c | 23 ++ ext/spl/php_spl.h | 4 + ext/spl/spl_array.c | 2 + ext/spl/spl_observer.c | 33 ++ ext/standard/basic_functions.c | 23 ++ ext/standard/basic_functions.h | 2 + main/main.c | 2 - 35 files changed, 886 insertions(+), 20 deletions(-) create mode 100644 Zend/zend_snapshot.c create mode 100644 Zend/zend_snapshot.h diff --git a/Zend/zend_alloc.c b/Zend/zend_alloc.c index 12e322d0347b3..fac6149d3674a 100644 --- a/Zend/zend_alloc.c +++ b/Zend/zend_alloc.c @@ -226,7 +226,6 @@ typedef zend_mm_bitset zend_mm_page_map[ZEND_MM_PAGE_MAP_LEN]; /* 64B */ typedef struct _zend_mm_page zend_mm_page; typedef struct _zend_mm_bin zend_mm_bin; typedef struct _zend_mm_free_slot zend_mm_free_slot; -typedef struct _zend_mm_chunk zend_mm_chunk; typedef struct _zend_mm_huge_list zend_mm_huge_list; static bool zend_mm_use_huge_pages = false; @@ -322,6 +321,9 @@ struct _zend_mm_chunk { zend_mm_heap heap_slot; /* used only in main chunk */ zend_mm_page_map free_map; /* 512 bits or 64 bytes */ zend_mm_page_info map[ZEND_MM_PAGES]; /* 2 KB = 512 * 4 */ + bool preserve; /* Never free this chunk. + Ensures the address space can + not be re-used. */ }; struct _zend_mm_page { @@ -804,7 +806,7 @@ static void *zend_mm_chunk_alloc_int(size_t size, size_t alignment) } } -static void *zend_mm_chunk_alloc(zend_mm_heap *heap, size_t size, size_t alignment) +ZEND_API void *zend_mm_chunk_alloc(zend_mm_heap *heap, size_t size, size_t alignment) { #if ZEND_MM_STORAGE if (UNEXPECTED(heap->storage)) { @@ -1172,11 +1174,15 @@ static zend_always_inline void zend_mm_delete_chunk(zend_mm_heap *heap, zend_mm_ } } if (!heap->cached_chunks || chunk->num > heap->cached_chunks->num) { - zend_mm_chunk_free(heap, chunk, ZEND_MM_CHUNK_SIZE); + if (!chunk->preserve) { + zend_mm_chunk_free(heap, chunk, ZEND_MM_CHUNK_SIZE); + } } else { //TODO: select the best chunk to delete??? chunk->next = heap->cached_chunks->next; - zend_mm_chunk_free(heap, heap->cached_chunks, ZEND_MM_CHUNK_SIZE); + if (!heap->cached_chunks->preserve) { + zend_mm_chunk_free(heap, heap->cached_chunks, ZEND_MM_CHUNK_SIZE); + } heap->cached_chunks = chunk; } } @@ -2195,6 +2201,53 @@ ZEND_API size_t zend_mm_gc(zend_mm_heap *heap) return collected * ZEND_MM_PAGE_SIZE; } +ZEND_API zend_mm_chunk *zend_mm_get_chunk_list(zend_mm_heap *heap) +{ + return heap->main_chunk; +} + +ZEND_API int zend_mm_get_chunks_count(zend_mm_heap *heap) +{ + return heap->chunks_count; +} + +ZEND_API void *zend_mm_get_huge_list(zend_mm_heap *heap) +{ + return heap->huge_list; +} + +ZEND_API zend_mm_chunk *zend_mm_get_next_chunk(zend_mm_heap *heap, zend_mm_chunk *chunk) +{ + ZEND_ASSERT(chunk->heap == heap); + zend_mm_chunk *next = chunk->next; + if (next == heap->main_chunk) { + return NULL; + } + return next; +} + +/* Adds the given chunk to the heap. The chunk is not initialized, and can have + * allocated slots and pages. */ +ZEND_API void zend_mm_adopt_chunk(zend_mm_heap *heap, zend_mm_chunk *chunk) +{ + /* Do not import free lists, as the chunk may have been created with a + * different key. However, free pages can be allocated. */ + chunk->heap = heap; + chunk->next = heap->main_chunk; + chunk->prev = heap->main_chunk->prev; + chunk->prev->next = chunk; + chunk->next->prev = chunk; + chunk->num = chunk->prev->num + 1; + heap->chunks_count++; + heap->peak_chunks_count++; +} + +ZEND_API void zend_mm_preserve_chunk(zend_mm_heap *heap, zend_mm_chunk *chunk) +{ + ZEND_ASSERT(chunk->heap == heap); + chunk->preserve = true; +} + #if ZEND_DEBUG /******************/ /* Leak detection */ @@ -2460,23 +2513,44 @@ ZEND_API void zend_mm_shutdown(zend_mm_heap *heap, bool full, bool silent) while (heap->cached_chunks) { p = heap->cached_chunks; heap->cached_chunks = p->next; - zend_mm_chunk_free(heap, p, ZEND_MM_CHUNK_SIZE); + if (!p->preserve) { + zend_mm_chunk_free(heap, p, ZEND_MM_CHUNK_SIZE); + } } /* free the first chunk */ - zend_mm_chunk_free(heap, heap->main_chunk, ZEND_MM_CHUNK_SIZE); + if (!heap->main_chunk->preserve) { + zend_mm_chunk_free(heap, heap->main_chunk, ZEND_MM_CHUNK_SIZE); + } } else { + /* unlink preserved chunks from the cache */ + p = heap->cached_chunks; + while (p) { + zend_mm_chunk *q = p->next; + while (q && q->preserve) { + p->next = q = q->next; + heap->cached_chunks_count--; + } + p = q; + } + if (heap->cached_chunks && heap->cached_chunks->preserve) { + heap->cached_chunks_count--; + heap->cached_chunks = heap->cached_chunks->next; + } + /* free some cached chunks to keep average count */ heap->avg_chunks_count = (heap->avg_chunks_count + (double)heap->peak_chunks_count) / 2.0; while ((double)heap->cached_chunks_count + 0.9 > heap->avg_chunks_count && heap->cached_chunks) { p = heap->cached_chunks; heap->cached_chunks = p->next; + ZEND_ASSERT(!p->preserve); zend_mm_chunk_free(heap, p, ZEND_MM_CHUNK_SIZE); heap->cached_chunks_count--; } /* clear cached chunks */ p = heap->cached_chunks; while (p != NULL) { + ZEND_ASSERT(!p->preserve); zend_mm_chunk *q = p->next; memset(p, 0, sizeof(zend_mm_chunk)); p->next = q; @@ -2864,7 +2938,9 @@ ZEND_API zend_result zend_set_memory_limit(size_t memory_limit) do { zend_mm_chunk *p = heap->cached_chunks; heap->cached_chunks = p->next; - zend_mm_chunk_free(heap, p, ZEND_MM_CHUNK_SIZE); + if (!p->preserve) { + zend_mm_chunk_free(heap, p, ZEND_MM_CHUNK_SIZE); + } heap->cached_chunks_count--; heap->real_size -= ZEND_MM_CHUNK_SIZE; } while (memory_limit < heap->real_size); @@ -2919,8 +2995,22 @@ ZEND_API void zend_memory_reset_peak_usage(void) #endif } +static void alloc_globals_ctor(zend_alloc_globals *alloc_globals); + ZEND_API void shutdown_memory_manager(bool silent, bool full_shutdown) { + if (!full_shutdown) { + zend_mm_heap *heap = AG(mm_heap); + if (heap->main_chunk->preserve) { + /* The main chunk is preserved, so we can not re-use it in the next + * request: We have to full shutdown and start a new heap. + * This happens when snapshot_state() was called during the request. + */ + zend_mm_shutdown(AG(mm_heap), 1, silent); + alloc_globals_ctor(&alloc_globals); + return; + } + } zend_mm_shutdown(AG(mm_heap), full_shutdown, silent); } diff --git a/Zend/zend_alloc.h b/Zend/zend_alloc.h index 541989a2a13e0..03a3cf6c68821 100644 --- a/Zend/zend_alloc.h +++ b/Zend/zend_alloc.h @@ -241,7 +241,8 @@ ZEND_API void zend_memory_reset_peak_usage(void); efree_size_rel(ht, sizeof(HashTable)) /* Heap functions */ -typedef struct _zend_mm_heap zend_mm_heap; +typedef struct _zend_mm_heap zend_mm_heap; +typedef struct _zend_mm_chunk zend_mm_chunk; ZEND_API zend_mm_heap *zend_mm_startup(void); ZEND_API void zend_mm_shutdown(zend_mm_heap *heap, bool full_shutdown, bool silent); @@ -316,6 +317,14 @@ struct _zend_mm_storage { ZEND_API zend_mm_storage *zend_mm_get_storage(zend_mm_heap *heap); ZEND_API zend_mm_heap *zend_mm_startup_ex(const zend_mm_handlers *handlers, void *data, size_t data_size); +ZEND_API zend_mm_chunk *zend_mm_get_chunk_list(zend_mm_heap *heap); +ZEND_API zend_mm_chunk *zend_mm_get_next_chunk(zend_mm_heap *heap, zend_mm_chunk *chunk); +ZEND_API int zend_mm_get_chunks_count(zend_mm_heap *heap); +ZEND_API void zend_mm_adopt_chunk(zend_mm_heap *heap, zend_mm_chunk *chunk); +ZEND_API void *zend_mm_chunk_alloc(zend_mm_heap *heap, size_t size, size_t alignment); +ZEND_API void *zend_mm_get_huge_list(zend_mm_heap *heap); +ZEND_API void zend_mm_preserve_chunk(zend_mm_heap *heap, zend_mm_chunk *chunk); + /* // The following example shows how to use zend_mm_heap API with custom storage diff --git a/Zend/zend_builtin_functions.c b/Zend/zend_builtin_functions.c index bb8bb28bf6e4f..7e39f2f11f585 100644 --- a/Zend/zend_builtin_functions.c +++ b/Zend/zend_builtin_functions.c @@ -19,7 +19,9 @@ #include "zend.h" #include "zend_API.h" +#include "zend_alloc_sizes.h" #include "zend_attributes.h" +#include "zend_compile.h" #include "zend_gc.h" #include "zend_builtin_functions.h" #include "zend_constants.h" @@ -30,9 +32,14 @@ #include "zend_closures.h" #include "zend_generators.h" #include "zend_builtin_functions_arginfo.h" +#include "zend_portability.h" #include "zend_smart_str.h" - -/* }}} */ +#include "zend_snapshot.h" +// TODO: do not depend on these directly +#include "ext/spl/php_spl.h" +#include "ext/standard/basic_functions.h" +#include "ext/date/php_date.h" +#include "zend_string.h" ZEND_MINIT_FUNCTION(core) { /* {{{ */ zend_register_default_classes(); @@ -2231,3 +2238,474 @@ ZEND_FUNCTION(get_extension_funcs) } } /* }}} */ + +static void restore_map_ptr(zend_snapshot *s) { + /* There may be a more efficient way to do this */ + /* We have to restore the whole map_ptr buffer, or none at all, as there are + * dependencies between slots. E.g., a non-null runtime cache slot in + * INIT_FCALL implies that the fbc has a runtime cache slot initialized. + * If we chose to not restore the map_ptr buffer, we have to reset + * non-offset run_time_caches (e.g. in closures). + */ + void **src = (void**)((char*)s->map_ptr_real_base + s->map_ptr_static_size * sizeof(void*)); + void **end = (void**)((char*)src + s->map_ptr_last * sizeof(void*)); + void **dst = (void**)((char*)CG(map_ptr_real_base) + zend_map_ptr_static_size * sizeof(void*)); + while (src < end) { + if (*dst == NULL) { + *dst = *src; + } + src++; + dst++; + } +} + +ZEND_FUNCTION(snapshot_state) +{ + if (EG(snapshot)) { + // TODO: we can support multiple snapshots + zend_throw_error(NULL, "Can not use snapshot_state() when a snapshot already exists (yet)"); + RETURN_THROWS(); + } + + zend_execute_data *ex = EG(current_execute_data); + if (ex->prev_execute_data->prev_execute_data) { + zend_throw_error(NULL, "snapshot_state() must be called from the initial script"); + RETURN_THROWS(); + } + + zend_snapshot *s = malloc(sizeof(zend_snapshot)); + s->restores = 0; + + /* Avoids implementing snapshotting support for PDO, for now */ + for (uint32_t i = 1; i < EG(objects_store).top; i++) { + zend_object *obj = EG(objects_store).object_buckets[i]; + if (IS_OBJ_VALID(obj)) { + if (zend_string_equals_cstr(obj->ce->name, "Doctrine\\DBAL\\Connection", strlen("Doctrine\\DBAL\\Connection"))) { + zval zv; + ZVAL_NULL(&zv); + zend_update_property(obj->ce, obj, "_conn", strlen("_conn"), &zv); + } + } + } + + /* Run GC to clean every zend_refcounted's GC ID. Also, avoids either leaks + * or GC runs after a restore. */ + for (int i = 0; i < 10; i++) { + gc_collect_cycles(); + + zend_gc_status gc_status; + zend_gc_get_status(&gc_status); + + if (gc_status.num_roots == 0) { + break; + } else if (i == 9) { + // TODO: Would happen if destructors create new garbage repeatedly. + // In this case, we can clear zend_refcounted's GC IDs manually and + // assume we are possibly leaking cycles + zend_throw_error(NULL, "Could not clear GC buffer"); + } + } + + zend_array *ht = zend_rebuild_symbol_table(); + zend_hash_init(&s->symbol_table, 0, NULL, NULL, 1); + zend_hash_copy(&s->symbol_table, ht, NULL); + + zval *zv; + zend_string *key; + ZEND_HASH_MAP_FOREACH_STR_KEY_VAL(&s->symbol_table, key, zv) { + if (strcmp(ZSTR_VAL(key), "_GET") == 0 + || strcmp(ZSTR_VAL(key), "_POST") == 0 + || strcmp(ZSTR_VAL(key), "_COOKIE") == 0 + || strcmp(ZSTR_VAL(key), "_FILES") == 0 + // || strcmp(ZSTR_VAL(key), "_SERVER") == 0 + // || strcmp(ZSTR_VAL(key), "_ENV") == 0 + || strcmp(ZSTR_VAL(key), "argv") == 0 + || strcmp(ZSTR_VAL(key), "argc") == 0) { + zend_hash_del(&s->symbol_table, key); + continue; + } + if (Z_TYPE_P(zv) == IS_INDIRECT) { + *zv = *Z_INDIRECT_P(zv); + } + } ZEND_HASH_FOREACH_END(); + + // TODO: call via a new extension handlers and store in a ht + s->date_snapshot = date_snapshot(); + s->spl_snapshot = spl_snapshot(); + s->standard_snapshot = php_standard_snapshot(); + + s->user_error_handlers_error_reporting = EG(user_error_handlers_error_reporting); + s->user_error_handler_error_reporting = EG(user_error_handler_error_reporting); + + s->user_error_handlers = EG(user_error_handlers); + s->user_error_handler = EG(user_error_handler); + + s->user_exception_handlers = EG(user_exception_handlers); + s->user_exception_handler = EG(user_exception_handler); + + zend_hash_init(&s->included_files, 0, NULL, NULL, 1); + zend_hash_copy(&s->included_files, &EG(included_files), NULL); + + zend_hash_init(&s->constant_table, 0, NULL, NULL, 1); + zend_constant *c; + ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&s->constant_table, key, c) { + if (ZEND_CONSTANT_MODULE_NUMBER(c) == PHP_USER_CONSTANT) { + zend_hash_add_ptr(&s->constant_table, key, c); + } + } ZEND_HASH_FOREACH_END(); + + zend_hash_init(&s->class_table, EG(class_table)->nNumUsed - EG(persistent_classes_count), NULL, NULL, 1); + zend_hash_real_init_mixed(&s->class_table); + ZEND_HASH_FOREACH_STR_KEY_VAL_FROM(EG(class_table), key, zv, EG(persistent_classes_count)) { + zend_class_entry *ce = Z_CE_P(zv); + if (ce->type != ZEND_USER_CLASS) { + zend_throw_error(NULL, "Can not snapshot class loaded by dl(): %s", + ZSTR_VAL(Z_CE_P(zv)->name)); + RETURN_THROWS(); + } + /* + if (!(ce->ce_flags & ZEND_ACC_IMMUTABLE)) { + zend_throw_error(NULL, "Can not snapshot non-cached class: %s", + ZSTR_VAL(Z_CE_P(zv)->name)); + RETURN_THROWS(); + } + */ + + zend_function *func; + ZEND_HASH_MAP_FOREACH_PTR(&ce->function_table, func) { + if (ZEND_USER_CODE(func->type) && !RUN_TIME_CACHE(&func->op_array)) { + void *run_time_cache = zend_arena_alloc(&CG(arena), func->op_array.cache_size); + memset(run_time_cache, 0, func->op_array.cache_size); + ZEND_MAP_PTR_SET(func->op_array.run_time_cache, run_time_cache); + } + } ZEND_HASH_FOREACH_END(); + + zend_hash_add(&s->class_table, key, zv); + } ZEND_HASH_FOREACH_END(); + + zend_hash_init(&s->function_table, EG(function_table)->nNumUsed - EG(persistent_functions_count), NULL, NULL, 1); + zend_hash_real_init_mixed(&s->function_table); + ZEND_HASH_FOREACH_STR_KEY_VAL_FROM(EG(function_table), key, zv, EG(persistent_functions_count)) { + zend_function *func = Z_FUNC_P(zv); + if (!ZEND_USER_CODE(func->type)) { + zend_throw_error(NULL, "Can not snapshot function loaded by dl(): %s", + ZSTR_VAL(func->common.function_name)); + RETURN_THROWS(); + } + /* + if (!(func->common.fn_flags & ZEND_ACC_IMMUTABLE)) { + zend_throw_error(NULL, + "Can not snapshot non-cached function %s (TODO)", + ZSTR_VAL(func->common.function_name)); + RETURN_THROWS(); + } + */ + + if (ZEND_USER_CODE(func->type) && !RUN_TIME_CACHE(&func->op_array)) { + void *run_time_cache = zend_arena_alloc(&CG(arena), func->op_array.cache_size); + memset(run_time_cache, 0, func->op_array.cache_size); + ZEND_MAP_PTR_SET(func->op_array.run_time_cache, run_time_cache); + } + + zend_hash_add(&s->function_table, key, zv); + } ZEND_HASH_FOREACH_END(); + + s->objects_store = EG(objects_store); + + for (uint32_t i = 1; i < s->objects_store.top; i++) { + zend_object *obj = s->objects_store.object_buckets[i]; + if (IS_OBJ_VALID(obj)) { + if (!obj->handlers->snapshot_obj(obj)) { + RETURN_THROWS(); + } + } + } + + s->map_ptr_real_base = malloc((zend_map_ptr_static_size + CG(map_ptr_size)) * sizeof(void*)); + s->map_ptr_base = ZEND_MAP_PTR_BIASED_BASE(s->map_ptr_real_base); + s->map_ptr_static_size = zend_map_ptr_static_size; + s->map_ptr_last = CG(map_ptr_last); + memcpy(s->map_ptr_real_base, CG(map_ptr_real_base), (zend_map_ptr_static_size + CG(map_ptr_size)) * sizeof(void*)); + + EG(snapshot) = s; + + zend_mm_heap *heap = zend_mm_get_heap(); + zend_mm_gc(heap); + + if (zend_mm_get_huge_list(heap)) { + free(s); + zend_throw_error(NULL, "TODO: support zend mm huge blocks"); + RETURN_THROWS(); + } + + zend_mm_chunk *chunk = zend_mm_get_chunk_list(heap); + s->chunks_count = zend_mm_get_chunks_count(heap); + s->chunks = malloc(s->chunks_count * sizeof(void*)); + s->copy = malloc(s->chunks_count * ZEND_MM_CHUNK_SIZE); + void **chunk_dest = s->chunks; + void *copy_dest = s->copy; + do { + zend_mm_preserve_chunk(heap, chunk); + copy_dest = mempcpy(copy_dest, chunk, ZEND_MM_CHUNK_SIZE); + *chunk_dest = chunk; + chunk_dest++; + chunk = zend_mm_get_next_chunk(heap, chunk); + } while (chunk); + + zend_gc_status gc_status; + zend_gc_get_status(&gc_status); + ZEND_ASSERT(gc_status.num_roots == 0); +} + +void *resolve_snapshot_addr(zend_snapshot *s, void *addr) +{ + for (int i = 0; i < s->chunks_count; i++) { + if (addr >= s->chunks[i] && addr < s->chunks[i] + ZEND_MM_CHUNK_SIZE) { + uintptr_t base = (uintptr_t)s->copy + i * ZEND_MM_CHUNK_SIZE; + uintptr_t offset = (uintptr_t)addr - (uintptr_t)s->chunks[i]; + return (void*)(base + offset); + } + } + + ZEND_UNREACHABLE(); +} + +ZEND_FUNCTION(snapshot_run_time_caches) +{ + zend_snapshot *s = EG(snapshot); + if (!s) { + zend_throw_error(NULL, "No snapshot"); + } + + zend_class_entry *ce; + zend_function *func; + + ZEND_HASH_FOREACH_PTR(&s->class_table, ce) { + ZEND_HASH_MAP_FOREACH_PTR(&ce->function_table, func) { + if (ZEND_USER_CODE(func->type)) { + void *run_time_cache = RUN_TIME_CACHE(&func->op_array); + ZEND_ASSERT(run_time_cache); + + void *dest = resolve_snapshot_addr(s, run_time_cache); + memcpy(dest, run_time_cache, func->op_array.cache_size); + } + } ZEND_HASH_FOREACH_END(); + } ZEND_HASH_FOREACH_END(); + + ZEND_HASH_FOREACH_PTR(&s->function_table, func) { + if (ZEND_USER_CODE(func->type)) { + void *run_time_cache = RUN_TIME_CACHE(&func->op_array); + ZEND_ASSERT(run_time_cache); + + void *dest = resolve_snapshot_addr(s, run_time_cache); + memcpy(dest, run_time_cache, func->op_array.cache_size); + } + } ZEND_HASH_FOREACH_END(); +} + +static zend_always_inline void zend_snapshot_class_table_copy(zend_array *target, zend_array *source, void *map_ptr_base, bool call_observers); +static zend_always_inline void zend_snapshot_function_table_copy(HashTable *target, HashTable *source, bool call_observers); +static zend_always_inline void zend_snapshot_constants_table_copy(HashTable *target, HashTable *source); + +ZEND_FUNCTION(restore_state) +{ + zend_execute_data *ex = EG(current_execute_data); + if (ex->prev_execute_data->prev_execute_data) { + zend_throw_error(NULL, "restore_state() must be called from the initial script"); + RETURN_THROWS(); + } + + zend_snapshot *s = EG(snapshot); + + if (!s) { + RETURN_FALSE; + } + + /** + * In theory we can restore state even if some objects already exist, but + * there will be object ID (object handle) clashes. + * Possible improvements here: + * - Allow to restore on a non-empty store as long as there is no ID clash + * - Assign new IDs to restored objects. Non-ideal as this is observable. + * This also requires some work to update SplObjectStorage. + * */ + if (EG(objects_store).top != 1) { + zend_throw_error(NULL, "Can not restore on a non-empty objects store"); + RETURN_THROWS(); + } + + zend_mm_heap *heap = zend_mm_get_heap(); + char *src = s->copy; + for (int i = 0; i < s->chunks_count; i++) { + zend_mm_chunk *chunk = s->chunks[i]; + memcpy(chunk, src, ZEND_MM_CHUNK_SIZE); + src += ZEND_MM_CHUNK_SIZE; + zend_mm_adopt_chunk(heap, chunk); + } + + restore_map_ptr(s); + + zend_hash_copy(&EG(included_files), &s->included_files, NULL); + zend_snapshot_class_table_copy(EG(class_table), &s->class_table, s->map_ptr_base, 0); + zend_snapshot_function_table_copy(EG(function_table), &s->function_table, 0); + zend_snapshot_constants_table_copy(EG(zend_constants), &s->constant_table); + EG(objects_store) = s->objects_store; + + EG(user_error_handler_error_reporting) = s->user_error_handler_error_reporting; + EG(user_error_handler) = s->user_error_handler; + EG(user_exception_handler) = s->user_exception_handler; + + zend_stack_destroy(&EG(user_error_handlers_error_reporting)); + EG(user_error_handlers_error_reporting) = s->user_error_handlers_error_reporting; + + ZEND_ASSERT(EG(user_error_handlers).size == sizeof(zval)); + for (int i = 0, l = EG(user_error_handlers).top; i < l; i++) { + zval *zv = &((zval*)EG(user_error_handlers).elements)[i]; + zval_ptr_dtor(zv); + } + zend_stack_destroy(&EG(user_error_handlers)); + EG(user_error_handlers) = s->user_error_handlers; + + ZEND_ASSERT(EG(user_exception_handlers).size == sizeof(zval)); + for (int i = 0, l = EG(user_exception_handlers).top; i < l; i++) { + zval *zv = &((zval*)EG(user_exception_handlers).elements)[i]; + zval_ptr_dtor(zv); + } + zend_stack_destroy(&EG(user_exception_handlers)); + EG(user_exception_handlers) = s->user_exception_handlers; + + php_standard_restore(s->standard_snapshot); + spl_restore(s->spl_snapshot); + date_restore(s->date_snapshot); + + zend_array *current_symbol_table = zend_rebuild_symbol_table(); + zend_array *garbage = zend_new_array(current_symbol_table->nNumUsed); + + zend_string *key; + zval *zv; + ZEND_HASH_MAP_FOREACH_STR_KEY_VAL(&s->symbol_table, key, zv) { + zval *entry = zend_hash_lookup(current_symbol_table, key); + if (Z_TYPE_P(entry) == IS_INDIRECT) { + entry = Z_INDIRECT_P(entry); + } + if (Z_REFCOUNTED_P(entry)) { + zend_hash_next_index_insert(garbage, entry); + } + ZVAL_COPY_VALUE(entry, zv); + } ZEND_HASH_FOREACH_END(); + + zend_array_destroy(garbage); + + RETURN_LONG(++s->restores); +} + +static zend_always_inline void zend_snapshot_class_table_copy(zend_array *target, zend_array *source, void *map_ptr_base, bool call_observers) +{ + Bucket *p, *end; + zval *t; + + zend_hash_extend(target, target->nNumUsed + source->nNumUsed, 0); + p = source->arData; + end = p + source->nNumUsed; + for (; p != end; p++) { + ZEND_ASSERT(Z_TYPE(p->val) != IS_UNDEF); + ZEND_ASSERT(p->key); + t = zend_hash_find_known_hash(target, p->key); + if (UNEXPECTED(t != NULL)) { + zend_class_entry *ce1 = Z_PTR(p->val); + CG(in_compilation) = 1; + zend_set_compiled_filename(ce1->info.user.filename); + CG(zend_lineno) = ce1->info.user.line_start; + zend_class_redeclaration_error(E_ERROR, Z_PTR_P(t)); + return; + } else { + zend_class_entry *ce = Z_PTR(p->val); + _zend_hash_append_ex(target, p->key, &p->val, 1); + if ((ce->ce_flags & ZEND_ACC_LINKED) && ZSTR_VAL(p->key)[0]) { + if (ZSTR_HAS_CE_CACHE(ce->name)) { + ZSTR_SET_CE_CACHE_EX(ce->name, ce, 1); + } + if (UNEXPECTED(call_observers)) { + // TODO _zend_observer_class_linked_notify(ce, p->key); + } + } + } + } + target->nInternalPointer = 0; +} + +static zend_always_inline void zend_snapshot_function_table_copy(HashTable *target, HashTable *source, bool call_observers) +{ + zend_function *function1, *function2; + Bucket *p, *end; + zval *t; + + zend_hash_extend(target, target->nNumUsed + source->nNumUsed, 0); + p = source->arData; + end = p + source->nNumUsed; + for (; p != end; p++) { + ZEND_ASSERT(Z_TYPE(p->val) != IS_UNDEF); + ZEND_ASSERT(p->key); + t = zend_hash_find_known_hash(target, p->key); + if (UNEXPECTED(t != NULL)) { + goto failure; + } + _zend_hash_append_ptr_ex(target, p->key, Z_PTR(p->val), 1); + if (UNEXPECTED(call_observers) && *ZSTR_VAL(p->key)) { // if not rtd key + // TODO + // _zend_observer_function_declared_notify(Z_PTR(p->val), p->key); + } + } + target->nInternalPointer = 0; + + return; + +failure: + function1 = Z_PTR(p->val); + function2 = Z_PTR_P(t); + CG(in_compilation) = 1; + zend_set_compiled_filename(function1->op_array.filename); + CG(zend_lineno) = function1->op_array.line_start; + if (function2->type == ZEND_USER_FUNCTION + && function2->op_array.last > 0) { + zend_error_noreturn(E_ERROR, "Cannot redeclare function %s() (previously declared in %s:%d)", + ZSTR_VAL(function1->common.function_name), + ZSTR_VAL(function2->op_array.filename), + (int)function2->op_array.line_start); + } else { + zend_error_noreturn(E_ERROR, "Cannot redeclare function %s()", ZSTR_VAL(function1->common.function_name)); + } +} + +static zend_always_inline void zend_snapshot_constants_table_copy(HashTable *target, HashTable *source) +{ + zend_constant *c1, *c2; + Bucket *p, *end; + zval *t; + + zend_hash_extend(target, target->nNumUsed + source->nNumUsed, 0); + p = source->arData; + end = p + source->nNumUsed; + for (; p != end; p++) { + ZEND_ASSERT(Z_TYPE(p->val) != IS_UNDEF); + ZEND_ASSERT(p->key); + t = zend_hash_find_known_hash(target, p->key); + if (UNEXPECTED(t != NULL)) { + goto failure; + } + _zend_hash_append_ptr_ex(target, p->key, Z_PTR(p->val), 1); + } + target->nInternalPointer = 0; + + return; + +failure: + c1 = Z_PTR(p->val); + c2 = Z_PTR_P(t); + CG(in_compilation) = 1; + zend_set_compiled_filename(c1->filename); + CG(zend_lineno) = 0; + zend_error_noreturn(E_ERROR, "Cannot redeclare constant %s (previously declared in %s)", + ZSTR_VAL(c1->name), + ZSTR_VAL(c2->filename)); +} diff --git a/Zend/zend_builtin_functions.stub.php b/Zend/zend_builtin_functions.stub.php index f7009c4ffba6e..1ee52b137c4b0 100644 --- a/Zend/zend_builtin_functions.stub.php +++ b/Zend/zend_builtin_functions.stub.php @@ -210,3 +210,7 @@ function gc_disable(): void {} * @refcount 1 */ function gc_status(): array {} + +function restore_state(): false|int {} +function snapshot_state(): void {} +function snapshot_run_time_caches(): void {} diff --git a/Zend/zend_builtin_functions_arginfo.h b/Zend/zend_builtin_functions_arginfo.h index cf34b1c0012d5..fd450ed4dede3 100644 --- a/Zend/zend_builtin_functions_arginfo.h +++ b/Zend/zend_builtin_functions_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 3dbc84896823c9aaa9ac8aeef8841266920c3e50 */ + * Stub hash: 5f993b3b802ab00dd6fa05346a490803d6dba027 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_exit, 0, 0, IS_NEVER, 0) ZEND_ARG_TYPE_MASK(0, status, MAY_BE_STRING|MAY_BE_LONG, "0") @@ -223,6 +223,13 @@ ZEND_END_ARG_INFO() #define arginfo_gc_status arginfo_func_get_args +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_restore_state, 0, 0, MAY_BE_FALSE|MAY_BE_LONG) +ZEND_END_ARG_INFO() + +#define arginfo_snapshot_state arginfo_gc_enable + +#define arginfo_snapshot_run_time_caches arginfo_gc_enable + ZEND_FRAMELESS_FUNCTION(property_exists, 2); static const zend_frameless_function_info frameless_function_infos_property_exists[] = { @@ -297,6 +304,9 @@ ZEND_FUNCTION(gc_enabled); ZEND_FUNCTION(gc_enable); ZEND_FUNCTION(gc_disable); ZEND_FUNCTION(gc_status); +ZEND_FUNCTION(restore_state); +ZEND_FUNCTION(snapshot_state); +ZEND_FUNCTION(snapshot_run_time_caches); static const zend_function_entry ext_functions[] = { ZEND_FE(exit, arginfo_exit) @@ -361,6 +371,9 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(gc_enable, arginfo_gc_enable) ZEND_FE(gc_disable, arginfo_gc_disable) ZEND_FE(gc_status, arginfo_gc_status) + ZEND_FE(restore_state, arginfo_restore_state) + ZEND_FE(snapshot_state, arginfo_snapshot_state) + ZEND_FE(snapshot_run_time_caches, arginfo_snapshot_run_time_caches) ZEND_FE_END }; diff --git a/Zend/zend_closures.c b/Zend/zend_closures.c index e9945b3284cd9..e47bb4f989a81 100644 --- a/Zend/zend_closures.c +++ b/Zend/zend_closures.c @@ -27,6 +27,7 @@ #include "zend_objects_API.h" #include "zend_globals.h" #include "zend_closures_arginfo.h" +#include "zend_snapshot.h" typedef struct _zend_closure { zend_object std; @@ -721,6 +722,7 @@ void zend_register_closure_ce(void) /* {{{ */ closure_handlers.get_debug_info = zend_closure_get_debug_info; closure_handlers.get_closure = zend_closure_get_closure; closure_handlers.get_gc = zend_closure_get_gc; + closure_handlers.snapshot_obj = zend_internal_object_snapshottable; } /* }}} */ diff --git a/Zend/zend_exceptions.c b/Zend/zend_exceptions.c index f9d0ae8ea8173..47b8f3fd5379d 100644 --- a/Zend/zend_exceptions.c +++ b/Zend/zend_exceptions.c @@ -759,6 +759,7 @@ void zend_register_default_exception(void) /* {{{ */ memcpy(&default_exception_handlers, &std_object_handlers, sizeof(zend_object_handlers)); default_exception_handlers.clone_obj = NULL; + default_exception_handlers.snapshot_obj = zend_internal_object_snapshottable; zend_ce_exception = register_class_Exception(zend_ce_throwable); zend_init_exception_class_entry(zend_ce_exception); diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c index 1f55521fb72f1..a6716e814ec45 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -1438,6 +1438,7 @@ ZEND_API ZEND_NORETURN void ZEND_FASTCALL zend_timeout(void) /* {{{ */ zend_set_timeout_ex(0, 1); #endif + kill(getpid(), SIGSEGV); zend_error_noreturn(E_ERROR, "Maximum execution time of " ZEND_LONG_FMT " second%s exceeded", EG(timeout_seconds), EG(timeout_seconds) == 1 ? "" : "s"); } /* }}} */ diff --git a/Zend/zend_globals.h b/Zend/zend_globals.h index 079bfb99caccf..9161ff1bc5dde 100644 --- a/Zend/zend_globals.h +++ b/Zend/zend_globals.h @@ -318,6 +318,8 @@ struct _zend_executor_globals { zend_strtod_state strtod_state; + struct _zend_snapshot *snapshot; + void *reserved[ZEND_MAX_RESERVED_RESOURCES]; }; diff --git a/Zend/zend_hash.h b/Zend/zend_hash.h index d543dae2bab59..b370b8a378b76 100644 --- a/Zend/zend_hash.h +++ b/Zend/zend_hash.h @@ -1020,6 +1020,15 @@ static zend_always_inline void *zend_hash_get_current_data_ptr_ex(HashTable *ht, for (; _count > 0; _z = ZEND_HASH_NEXT_ELEMENT(_z, _size), _count--) { \ if (UNEXPECTED(Z_TYPE_P(_z) == IS_UNDEF)) continue; +#define _ZEND_HASH_FOREACH_VAL_FROM(_ht, _from) do { \ + const HashTable *__ht = (_ht); \ + uint32_t _idx = (_from); \ + size_t _size = ZEND_HASH_ELEMENT_SIZE(__ht); \ + zval *_z = ZEND_HASH_ELEMENT_EX(__ht, _idx, _size); \ + uint32_t _count = __ht->nNumUsed - _idx; \ + for (; _count > 0; _z = ZEND_HASH_NEXT_ELEMENT(_z, _size), _count--) { \ + if (UNEXPECTED(Z_TYPE_P(_z) == IS_UNDEF)) continue; + #define _ZEND_HASH_REVERSE_FOREACH_VAL(_ht) do { \ const HashTable *__ht = (_ht); \ uint32_t _idx = __ht->nNumUsed; \ @@ -1104,6 +1113,14 @@ static zend_always_inline void *zend_hash_get_current_data_ptr_ex(HashTable *ht, _ZEND_HASH_FOREACH_VAL(ht); \ _val = _z; +#define ZEND_HASH_FOREACH_VAL_FROM(ht, _val, _from) \ + _ZEND_HASH_FOREACH_VAL_FROM(ht, _from); \ + _val = _z; + +#define ZEND_HASH_FOREACH_PTR_FROM(ht, _ptr, _from) \ + ZEND_HASH_FOREACH_FROM(ht, 0, _from); \ + _ptr = Z_PTR_P(_z); + #define ZEND_HASH_REVERSE_FOREACH_VAL(ht, _val) \ _ZEND_HASH_REVERSE_FOREACH_VAL(ht); \ _val = _z; diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index f5496514fcb7d..3fe26e532262e 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -3580,7 +3580,7 @@ ZEND_API zend_class_entry *zend_do_link_class(zend_class_entry *ce, zend_string #ifndef ZEND_WIN32 if (ce->ce_flags & ZEND_ACC_ENUM) { /* We will add internal methods. */ - is_cacheable = false; + // TODO: is_cacheable = false; } #endif diff --git a/Zend/zend_iterators.c b/Zend/zend_iterators.c index f67033b11161c..8a20d2b0f5955 100644 --- a/Zend/zend_iterators.c +++ b/Zend/zend_iterators.c @@ -51,7 +51,8 @@ static const zend_object_handlers iterator_object_handlers = { iter_wrapper_get_gc, NULL, /* do_operation */ NULL, /* compare */ - NULL /* get_properties_for */ + NULL, /* get_properties_for */ + NULL, /* snapshot_obj */ }; ZEND_API void zend_register_iterator_wrapper(void) diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index 56bea75922673..21dfaa1a830a5 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -19,8 +19,10 @@ */ #include "zend.h" +#include "zend_alloc.h" #include "zend_globals.h" #include "zend_lazy_objects.h" +#include "zend_types.h" #include "zend_variables.h" #include "zend_API.h" #include "zend_objects.h" @@ -33,6 +35,7 @@ #include "zend_hash.h" #include "zend_property_hooks.h" #include "zend_observer.h" +#include "zend_snapshot.h" #define DEBUG_OBJECT_HANDLERS 0 @@ -2447,6 +2450,19 @@ ZEND_API HashTable *zend_get_properties_for(zval *obj, zend_prop_purpose purpose return zend_std_get_properties_for(zobj, purpose); } +ZEND_API zend_object *zend_std_snapshot_obj(zend_object *object) { + if (object->ce->create_object) { + zend_throw_error(NULL, "Can not snapshot instance of class %s", ZSTR_VAL(object->ce->name)); + return NULL; + } + + return object; +} + +ZEND_API zend_object *zend_internal_object_snapshottable(zend_object *object) { + return object; +} + ZEND_API const zend_object_handlers std_object_handlers = { 0, /* offset */ @@ -2475,4 +2491,5 @@ ZEND_API const zend_object_handlers std_object_handlers = { NULL, /* do_operation */ zend_std_compare_objects, /* compare */ NULL, /* get_properties_for */ + zend_std_snapshot_obj, /* snapshot_obj */ }; diff --git a/Zend/zend_object_handlers.h b/Zend/zend_object_handlers.h index 7e7d3df37a6ad..9d2967718d8f7 100644 --- a/Zend/zend_object_handlers.h +++ b/Zend/zend_object_handlers.h @@ -202,6 +202,9 @@ typedef HashTable *(*zend_object_get_gc_t)(zend_object *object, zval **table, in typedef zend_result (*zend_object_do_operation_t)(uint8_t opcode, zval *result, zval *op1, zval *op2); +typedef zend_object *(*zend_object_snapshot_t)(zend_object *object); +// typedef zend_object *(*zend_object_restore_t)(zend_object *object); + struct _zend_object_handlers { /* offset of real object header (usually zero) */ int offset; @@ -230,6 +233,8 @@ struct _zend_object_handlers { zend_object_do_operation_t do_operation; /* optional */ zend_object_compare_t compare; /* required */ zend_object_get_properties_for_t get_properties_for; /* optional */ + zend_object_snapshot_t snapshot_obj; /* optional */ + // zend_object_restore_t restore_obj; /* optional */ }; BEGIN_EXTERN_C() @@ -274,6 +279,7 @@ ZEND_API zend_result zend_std_get_closure(zend_object *obj, zend_class_entry **c ZEND_API HashTable *rebuild_object_properties_internal(zend_object *zobj); ZEND_API ZEND_COLD zend_never_inline void zend_bad_method_call(zend_function *fbc, zend_string *method_name, zend_class_entry *scope); ZEND_API ZEND_COLD zend_never_inline void zend_abstract_method_call(zend_function *fbc); +ZEND_API zend_object *zend_internal_object_snapshottable(zend_object *object); static zend_always_inline HashTable *zend_std_get_properties_ex(zend_object *object) { diff --git a/Zend/zend_snapshot.c b/Zend/zend_snapshot.c new file mode 100644 index 0000000000000..1facbde6e0ade --- /dev/null +++ b/Zend/zend_snapshot.c @@ -0,0 +1,11 @@ +#include "zend_compile.h" +#include "zend_types.h" +#include "zend_string.h" +#include "zend_alloc.h" +#include "zend_hash.h" +#include "zend_globals.h" +#include "zend.h" +#include "zend_API.h" +#include "zend_snapshot.h" + + diff --git a/Zend/zend_snapshot.h b/Zend/zend_snapshot.h new file mode 100644 index 0000000000000..8459b62f6a8b0 --- /dev/null +++ b/Zend/zend_snapshot.h @@ -0,0 +1,39 @@ + +#ifndef ZEND_SNAPSHOT_H +#define ZEND_SNAPSHOT_H + +#include "zend_config.h" +#include "zend_types.h" +#include "zend_alloc.h" +#include "zend_stack.h" +#include "zend_objects_API.h" + +typedef struct _zend_snapshot { + int restores; + zend_array symbol_table; + int chunks_count; + void **chunks; + void *copy; + void *map_ptr_real_base; + void *map_ptr_base; + size_t map_ptr_static_size; + size_t map_ptr_last; + zend_array class_table; + zend_array function_table; + zend_array constant_table; + zend_array included_files; + zend_objects_store objects_store; + int user_error_handler_error_reporting; + zval user_error_handler; + zval user_exception_handler; + zend_stack user_error_handlers_error_reporting; + zend_stack user_error_handlers; + zend_stack user_exception_handlers; + + // exts (TODO: ht and ext handlers) + void *date_snapshot; + void *spl_snapshot; + void *standard_snapshot; +} zend_snapshot; + +#endif /* ZEND_SNAPSHOT_H */ diff --git a/Zend/zend_weakrefs.c b/Zend/zend_weakrefs.c index cf363cd12595c..707fb6dbf62b0 100644 --- a/Zend/zend_weakrefs.c +++ b/Zend/zend_weakrefs.c @@ -17,6 +17,7 @@ #include "zend.h" #include "zend_interfaces.h" #include "zend_objects_API.h" +#include "zend_snapshot.h" #include "zend_types.h" #include "zend_weakrefs.h" #include "zend_weakrefs_arginfo.h" @@ -54,7 +55,7 @@ typedef struct _zend_weakmap_iterator { #define ZEND_WEAKREF_ENCODE(p, t) ((void *) (((uintptr_t) (p)) | (t))) zend_class_entry *zend_ce_weakref; -static zend_class_entry *zend_ce_weakmap; +zend_class_entry *zend_ce_weakmap; static zend_object_handlers zend_weakref_handlers; static zend_object_handlers zend_weakmap_handlers; @@ -348,6 +349,17 @@ static void zend_weakmap_free_obj(zend_object *object) zend_object_std_dtor(&wm->std); } +static zend_object *zend_weakmap_snapshot_obj(zend_object *object) +{ + zend_weakmap *wm = zend_weakmap_from(object); + if (wm->ht.nNumOfElements) { + zend_throw_error(NULL, "Can not snapshot non-emtpy WeakMap (TODO)"); + return NULL; + } + + return object; +} + static zval *zend_weakmap_read_dimension(zend_object *object, zval *offset, int type, zval *rv) { if (offset == NULL) { @@ -800,6 +812,7 @@ void zend_register_weakref_ce(void) /* {{{ */ zend_weakmap_handlers.get_properties_for = zend_weakmap_get_properties_for; zend_weakmap_handlers.get_gc = zend_weakmap_get_gc; zend_weakmap_handlers.clone_obj = zend_weakmap_clone_obj; + zend_weakmap_handlers.snapshot_obj = zend_weakmap_snapshot_obj; } /* }}} */ diff --git a/Zend/zend_weakrefs.h b/Zend/zend_weakrefs.h index 00d4fbcc6bc4c..520d140f680c1 100644 --- a/Zend/zend_weakrefs.h +++ b/Zend/zend_weakrefs.h @@ -22,6 +22,7 @@ BEGIN_EXTERN_C() extern ZEND_API zend_class_entry *zend_ce_weakref; +extern ZEND_API zend_class_entry *zend_ce_weakmap; void zend_register_weakref_ce(void); diff --git a/configure.ac b/configure.ac index 01d9ded69b920..3bebc62278fb7 100644 --- a/configure.ac +++ b/configure.ac @@ -1760,6 +1760,7 @@ PHP_ADD_SOURCES([Zend], m4_normalize([ zend_ptr_stack.c zend_signal.c zend_smart_str.c + zend_snapshot.c zend_sort.c zend_stack.c zend_stream.c diff --git a/ext/date/php_date.c b/ext/date/php_date.c index 55d9647714d30..fdd49ef719efd 100644 --- a/ext/date/php_date.c +++ b/ext/date/php_date.c @@ -25,6 +25,7 @@ #include "zend_exceptions.h" #include "lib/timelib.h" #include "lib/timelib_private.h" +#include "zend_snapshot.h" #ifndef PHP_WIN32 #include #else @@ -265,7 +266,7 @@ PHP_INI_BEGIN() PHP_INI_END() /* }}} */ -static zend_class_entry *date_ce_date, *date_ce_timezone, *date_ce_interval, *date_ce_period; +PHPAPI zend_class_entry *date_ce_date, *date_ce_timezone, *date_ce_interval, *date_ce_period; static zend_class_entry *date_ce_immutable, *date_ce_interface; static zend_class_entry *date_ce_date_error, *date_ce_date_object_error, *date_ce_date_range_error; static zend_class_entry *date_ce_date_exception, *date_ce_date_invalid_timezone_exception, *date_ce_date_invalid_operation_exception, *date_ce_date_malformed_string_exception, *date_ce_date_malformed_interval_string_exception, *date_ce_date_malformed_period_string_exception; @@ -1750,6 +1751,7 @@ static void date_register_classes(void) /* {{{ */ date_object_handlers_date.compare = date_object_compare_date; date_object_handlers_date.get_properties_for = date_object_get_properties_for; date_object_handlers_date.get_gc = date_object_get_gc; + date_object_handlers_date.snapshot_obj = zend_internal_object_snapshottable; date_ce_immutable = register_class_DateTimeImmutable(date_ce_interface); date_ce_immutable->create_object = date_object_new_date; @@ -1759,6 +1761,7 @@ static void date_register_classes(void) /* {{{ */ date_object_handlers_immutable.compare = date_object_compare_date; date_object_handlers_immutable.get_properties_for = date_object_get_properties_for; date_object_handlers_immutable.get_gc = date_object_get_gc; + date_object_handlers_immutable.snapshot_obj = zend_internal_object_snapshottable; date_ce_timezone = register_class_DateTimeZone(); date_ce_timezone->create_object = date_object_new_timezone; @@ -1771,6 +1774,7 @@ static void date_register_classes(void) /* {{{ */ date_object_handlers_timezone.get_gc = date_object_get_gc_timezone; date_object_handlers_timezone.get_debug_info = date_object_get_debug_info_timezone; date_object_handlers_timezone.compare = date_object_compare_timezone; + date_object_handlers_timezone.snapshot_obj = zend_internal_object_snapshottable; date_ce_interval = register_class_DateInterval(); date_ce_interval->create_object = date_object_new_interval; @@ -2024,6 +2028,27 @@ static int date_object_compare_timezone(zval *tz1, zval *tz2) /* {{{ */ } } /* }}} */ +PHPAPI void date_restore(void *data) +{ + if (!data) { + return; + } + + zend_array *snapshot = (zend_array*)data; + + if (DATEG(tzcache)) { + zend_throw_error(NULL, "Restoring when DATEG(tzcache) is non-empty is not supported (TODO)"); + return; + } + + DATEG(tzcache) = snapshot; +} + +PHPAPI void *date_snapshot(void) +{ + return DATEG(tzcache); +} + static void php_timezone_to_string(php_timezone_obj *tzobj, zval *zv) { switch (tzobj->type) { diff --git a/ext/date/php_date.h b/ext/date/php_date.h index f1b7c892001a9..e3848eaa1bd05 100644 --- a/ext/date/php_date.h +++ b/ext/date/php_date.h @@ -163,4 +163,8 @@ PHPAPI bool php_date_initialize(php_date_obj *dateobj, const char *time_str, siz PHPAPI void php_date_initialize_from_ts_long(php_date_obj *dateobj, zend_long sec, int usec); PHPAPI bool php_date_initialize_from_ts_double(php_date_obj *dateobj, double ts); +PHPAPI void date_restore(void *data); +PHPAPI void *date_snapshot(void); + + #endif /* PHP_DATE_H */ diff --git a/ext/intl/dateformat/dateformat_class.c b/ext/intl/dateformat/dateformat_class.c index 15bf5bf23ce57..ed7dcba79e8f9 100644 --- a/ext/intl/dateformat/dateformat_class.c +++ b/ext/intl/dateformat/dateformat_class.c @@ -39,10 +39,24 @@ void IntlDateFormatter_object_free( zend_object *object ) efree( dfo->requested_locale ); } - dateformat_data_free( &dfo->datef_data ); + if (!dfo->preserve) { + dateformat_data_free( &dfo->datef_data ); + } } /* }}} */ +zend_object *IntlDateFormatter_object_snapshot( zend_object *object ) +{ + IntlDateFormatter_object* dfo = php_intl_dateformatter_fetch_object(object); + + // TODO: this is a quick way to make IntlDateFormatter snapshottable, but + // a proper way would ensure that any state in dfo->datef_data is not + // changed after that. + dfo->preserve = true; + + return object; +} + /* {{{ IntlDateFormatter_object_create */ zend_object *IntlDateFormatter_object_create(zend_class_entry *ce) { @@ -105,5 +119,6 @@ void dateformat_register_IntlDateFormatter_class( void ) IntlDateFormatter_handlers.offset = XtOffsetOf(IntlDateFormatter_object, zo); IntlDateFormatter_handlers.clone_obj = IntlDateFormatter_object_clone; IntlDateFormatter_handlers.free_obj = IntlDateFormatter_object_free; + IntlDateFormatter_handlers.snapshot_obj = IntlDateFormatter_object_snapshot; } /* }}} */ diff --git a/ext/intl/dateformat/dateformat_class.h b/ext/intl/dateformat/dateformat_class.h index 18afb55023b42..8987a076a88e7 100644 --- a/ext/intl/dateformat/dateformat_class.h +++ b/ext/intl/dateformat/dateformat_class.h @@ -27,6 +27,7 @@ typedef struct { int time_type; int calendar; char *requested_locale; + bool preserve; // TODO hack zend_object zo; } IntlDateFormatter_object; diff --git a/ext/intl/dateformat/dateformat_data.c b/ext/intl/dateformat/dateformat_data.c index 9c12af6fb6059..e1691611f7c6a 100644 --- a/ext/intl/dateformat/dateformat_data.c +++ b/ext/intl/dateformat/dateformat_data.c @@ -35,7 +35,7 @@ void dateformat_data_init( dateformat_data* datef_data ) */ void dateformat_data_free( dateformat_data* datef_data ) { - if( !datef_data ) + if( !datef_data ) return; if( datef_data->udatf ) diff --git a/ext/intl/msgformat/msgformat_class.c b/ext/intl/msgformat/msgformat_class.c index 4e0766a911b9a..f7992f4c8e7f5 100644 --- a/ext/intl/msgformat/msgformat_class.c +++ b/ext/intl/msgformat/msgformat_class.c @@ -35,10 +35,22 @@ void MessageFormatter_object_free( zend_object *object ) zend_object_std_dtor( &mfo->zo ); - msgformat_data_free( &mfo->mf_data ); + if (!mfo->preserve ) { + msgformat_data_free( &mfo->mf_data ); + } } /* }}} */ +zend_object *MessageFormatter_object_snapshot( zend_object *object ) +{ + MessageFormatter_object* mfo = php_intl_messageformatter_fetch_object(object); + + // TODO: see IntlDateFormatter_object_snapshot + mfo->preserve = true; + + return object; +} + /* {{{ MessageFormatter_object_create */ zend_object *MessageFormatter_object_create(zend_class_entry *ce) { @@ -97,5 +109,6 @@ void msgformat_register_class( void ) MessageFormatter_handlers.offset = XtOffsetOf(MessageFormatter_object, zo); MessageFormatter_handlers.clone_obj = MessageFormatter_object_clone; MessageFormatter_handlers.free_obj = MessageFormatter_object_free; + MessageFormatter_handlers.snapshot_obj = MessageFormatter_object_snapshot; } /* }}} */ diff --git a/ext/intl/msgformat/msgformat_class.h b/ext/intl/msgformat/msgformat_class.h index 809fdec654189..0f86f57ce4b19 100644 --- a/ext/intl/msgformat/msgformat_class.h +++ b/ext/intl/msgformat/msgformat_class.h @@ -27,6 +27,7 @@ typedef struct { msgformat_data mf_data; + bool preserve; zend_object zo; } MessageFormatter_object; diff --git a/ext/opcache/zend_accelerator_module.c b/ext/opcache/zend_accelerator_module.c index 0c52b98dc453c..9166257990331 100644 --- a/ext/opcache/zend_accelerator_module.c +++ b/ext/opcache/zend_accelerator_module.c @@ -23,17 +23,31 @@ #include "php.h" #include "ZendAccelerator.h" +#include "zend.h" #include "zend_API.h" +#include "zend_alloc.h" +#include "zend_alloc_sizes.h" #include "zend_closures.h" +#include "zend_compile.h" +#include "zend_constants.h" +#include "zend_hash.h" +#include "zend_objects_API.h" +#include "zend_portability.h" #include "zend_shared_alloc.h" #include "zend_accelerator_blacklist.h" #include "php_ini.h" #include "SAPI.h" +#include "zend_smart_str.h" +#include "zend_snapshot.h" +#include "zend_string.h" +#include "zend_types.h" #include "zend_virtual_cwd.h" #include "ext/standard/info.h" #include "ext/standard/php_filestat.h" #include "ext/date/php_date.h" #include "opcache_arginfo.h" +#include "zend_weakrefs.h" +#include "zend_iterators.h" #ifdef HAVE_JIT #include "jit/zend_jit.h" diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index bc8ffbdd8bd8e..6957fc9d6039c 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -22,6 +22,7 @@ #include "zend_execute.h" #include "zend_lazy_objects.h" #include "zend_object_handlers.h" +#include "zend_snapshot.h" #include "zend_type_info.h" #include "zend_types.h" #ifdef HAVE_CONFIG_H @@ -7771,6 +7772,7 @@ PHP_MINIT_FUNCTION(reflection) /* {{{ */ reflection_object_handlers.clone_obj = NULL; reflection_object_handlers.write_property = _reflection_write_property; reflection_object_handlers.get_gc = reflection_get_gc; + reflection_object_handlers.snapshot_obj = zend_internal_object_snapshottable; reflection_exception_ptr = register_class_ReflectionException(zend_ce_exception); diff --git a/ext/spl/php_spl.c b/ext/spl/php_spl.c index 1149be29bd46e..2eb7c5596a3fd 100644 --- a/ext/spl/php_spl.c +++ b/ext/spl/php_spl.c @@ -641,6 +641,29 @@ PHP_FUNCTION(spl_autoload_functions) } } /* }}} */ +PHPAPI void *spl_snapshot(void) +{ + return spl_autoload_functions; +} + +PHPAPI void spl_restore(void *data) +{ + if (!data) { + return; + } + + zend_array *snapshot = (zend_array*)data; + + if (spl_autoload_functions) { + zend_hash_clean(spl_autoload_functions); + zend_hash_copy(spl_autoload_functions, snapshot, NULL); + zend_hash_destroy(snapshot); + efree(snapshot); + } else { + spl_autoload_functions = snapshot; + } +} + /* {{{ Return hash id for given object */ PHP_FUNCTION(spl_object_hash) { diff --git a/ext/spl/php_spl.h b/ext/spl/php_spl.h index c2f7ebf9eee17..096beec198875 100644 --- a/ext/spl/php_spl.h +++ b/ext/spl/php_spl.h @@ -18,6 +18,7 @@ #define PHP_SPL_H #include "php.h" +#include "Zend/zend_snapshot.h" #define PHP_SPL_VERSION PHP_VERSION @@ -32,4 +33,7 @@ PHP_MINFO_FUNCTION(spl); PHPAPI zend_string *php_spl_object_hash(zend_object *obj); +PHPAPI void *spl_snapshot(void); +PHPAPI void spl_restore(void *data); + #endif /* PHP_SPL_H */ diff --git a/ext/spl/spl_array.c b/ext/spl/spl_array.c index 5021bc441f945..0e9d9e10af00b 100644 --- a/ext/spl/spl_array.c +++ b/ext/spl/spl_array.c @@ -1931,6 +1931,8 @@ PHP_MINIT_FUNCTION(spl_array) spl_handler_ArrayObject.compare = spl_array_compare_objects; spl_handler_ArrayObject.free_obj = spl_array_object_free_storage; + spl_handler_ArrayObject.snapshot_obj = zend_internal_object_snapshottable; + spl_ce_ArrayIterator = register_class_ArrayIterator(spl_ce_SeekableIterator, zend_ce_arrayaccess, zend_ce_serializable, zend_ce_countable); spl_ce_ArrayIterator->create_object = spl_array_object_new; spl_ce_ArrayIterator->default_object_handlers = &spl_handler_ArrayObject; diff --git a/ext/spl/spl_observer.c b/ext/spl/spl_observer.c index af9c598f08b08..1397859248d3d 100644 --- a/ext/spl/spl_observer.c +++ b/ext/spl/spl_observer.c @@ -15,6 +15,8 @@ +----------------------------------------------------------------------+ */ +#include "zend.h" +#include "zend_hash.h" #ifdef HAVE_CONFIG_H # include "config.h" #endif @@ -499,6 +501,36 @@ static void spl_object_storage_unset_dimension(zend_object *object, zval *offset zend_hash_index_del(&intern->storage, Z_OBJ_HANDLE_P(offset)); } +static zend_object *spl_object_storage_snapshot_obj(zend_object *object) +{ + return object; +} + +#if 0 +static zend_object *spl_object_storage_restore_obj(zend_object *object) +{ + spl_SplObjectStorage *intern = spl_object_storage_from_obj(object); + if (intern->storage.nNumOfElements) { + zend_array new_storage; + zend_hash_init(&new_storage, intern->storage.nNumOfElements, + NULL, intern->storage.pDestructor, 0); + + // TODO: Use a variant of zend_hash_set_bucket_key + zval *zv; + ZEND_HASH_FOREACH_VAL(&intern->storage, zv) { + spl_SplObjectStorageElement *element = Z_PTR_P(zv); + zend_hash_index_add(&new_storage, element->obj->handle, zv); + } ZEND_HASH_FOREACH_END(); + + intern->storage.pDestructor = NULL; + zend_hash_destroy(&intern->storage); + intern->storage = new_storage; + } + + return object; +} +#endif + /* {{{ Detaches an object from the storage */ PHP_METHOD(SplObjectStorage, detach) { @@ -1390,6 +1422,7 @@ PHP_MINIT_FUNCTION(spl_observer) spl_handler_SplObjectStorage.write_dimension = spl_object_storage_write_dimension; spl_handler_SplObjectStorage.has_dimension = spl_object_storage_has_dimension; spl_handler_SplObjectStorage.unset_dimension = spl_object_storage_unset_dimension; + spl_handler_SplObjectStorage.snapshot_obj = spl_object_storage_snapshot_obj; spl_ce_MultipleIterator = register_class_MultipleIterator(zend_ce_iterator); spl_ce_MultipleIterator->create_object = spl_SplObjectStorage_new; diff --git a/ext/standard/basic_functions.c b/ext/standard/basic_functions.c index 1aae3fcd2c255..2f73d363288a1 100644 --- a/ext/standard/basic_functions.c +++ b/ext/standard/basic_functions.c @@ -1735,6 +1735,29 @@ PHPAPI bool append_user_shutdown_function(php_shutdown_function_entry *shutdown_ } /* }}} */ +PHPAPI void *php_standard_snapshot(void) +{ + return BG(user_shutdown_function_names); +} + +PHPAPI void php_standard_restore(void *data) +{ + if (!data) { + return; + } + + zend_array *ht = (zend_array*)data; + if (BG(user_shutdown_function_names)) { + zend_hash_clean(BG(user_shutdown_function_names)); + zend_hash_copy(BG(user_shutdown_function_names), ht, NULL); + ht->pDestructor = NULL; + zend_hash_destroy(ht); + efree(ht); + } else { + BG(user_shutdown_function_names) = ht; + } +} + ZEND_API void php_get_highlight_struct(zend_syntax_highlighter_ini *syntax_highlighter_ini) /* {{{ */ { syntax_highlighter_ini->highlight_comment = INI_STR("highlight.comment"); diff --git a/ext/standard/basic_functions.h b/ext/standard/basic_functions.h index bad6fcf4e645e..c486655aa563b 100644 --- a/ext/standard/basic_functions.h +++ b/ext/standard/basic_functions.h @@ -135,5 +135,7 @@ PHPAPI extern bool append_user_shutdown_function(php_shutdown_function_entry *sh PHPAPI void php_call_shutdown_functions(void); PHPAPI void php_free_shutdown_functions(void); +PHPAPI void *php_standard_snapshot(void); +PHPAPI void php_standard_restore(void *data); #endif /* BASIC_FUNCTIONS_H */ diff --git a/main/main.c b/main/main.c index 4075be8b377e3..4a4c77292dcb6 100644 --- a/main/main.c +++ b/main/main.c @@ -1895,8 +1895,6 @@ void php_request_shutdown(void *dummy) { bool report_memleaks; - EG(flags) |= EG_FLAGS_IN_SHUTDOWN; - report_memleaks = PG(report_memleaks); /* EG(current_execute_data) points into nirvana and therefore cannot be safely accessed From b9df0cac5317796be2a80fb144c09b1bec31d302 Mon Sep 17 00:00:00 2001 From: Arnaud Le Blanc Date: Wed, 12 Mar 2025 12:33:31 +0100 Subject: [PATCH 2/2] Implement copy-on-write snapshot Eliminate snapshot restoration cost by using a MAP_PRIVATE / copy-on-write mapping. To prevent object destruction from faulting most of our mapping, we skip snapshotted objects in zend_objects_store_call_destructors(), unless they have destructors. Credit for these ideas belong to Bob. We also increase the refcount of snapshotted global variables and objects to prevent freeing them, which would also generate a lot of page faults. Co-authored-by: Bob Weinand --- Zend/zend_builtin_functions.c | 73 ++++++++++++++++++++++++++++++++--- Zend/zend_objects_API.c | 27 +++++++++++-- Zend/zend_objects_API.h | 3 ++ Zend/zend_snapshot.h | 1 + 4 files changed, 96 insertions(+), 8 deletions(-) diff --git a/Zend/zend_builtin_functions.c b/Zend/zend_builtin_functions.c index 7e39f2f11f585..4e9deaff8a7a1 100644 --- a/Zend/zend_builtin_functions.c +++ b/Zend/zend_builtin_functions.c @@ -41,6 +41,8 @@ #include "ext/date/php_date.h" #include "zend_string.h" +#include + ZEND_MINIT_FUNCTION(core) { /* {{{ */ zend_register_default_classes(); @@ -2327,6 +2329,8 @@ ZEND_FUNCTION(snapshot_state) if (Z_TYPE_P(zv) == IS_INDIRECT) { *zv = *Z_INDIRECT_P(zv); } + /* Prevent page-faulting */ + Z_TRY_ADDREF_P(zv); } ZEND_HASH_FOREACH_END(); // TODO: call via a new extension handlers and store in a ht @@ -2410,17 +2414,42 @@ ZEND_FUNCTION(snapshot_state) zend_hash_add(&s->function_table, key, zv); } ZEND_HASH_FOREACH_END(); - s->objects_store = EG(objects_store); + uint32_t *objects_with_dtor = NULL; + uint32_t num_objects_with_dtor = 0; + uint32_t objects_with_dtor_cap = 0; - for (uint32_t i = 1; i < s->objects_store.top; i++) { - zend_object *obj = s->objects_store.object_buckets[i]; + for (uint32_t i = 1; i < EG(objects_store).top; i++) { + zend_object *obj = EG(objects_store).object_buckets[i]; if (IS_OBJ_VALID(obj)) { if (!obj->handlers->snapshot_obj(obj)) { RETURN_THROWS(); } + /* Prevent page-faulting */ + GC_ADDREF(obj); + if ((obj->handlers->dtor_obj != zend_objects_destroy_object + || obj->ce->destructor + || obj->handlers->free_obj != zend_object_std_dtor) + && obj->ce != zend_ce_closure) { + if (objects_with_dtor_cap == num_objects_with_dtor) { + if (objects_with_dtor_cap == 0) { + objects_with_dtor_cap = 16; + } else { + objects_with_dtor_cap *= 1.5; + } + objects_with_dtor = erealloc(objects_with_dtor, objects_with_dtor_cap * sizeof(uint32_t)); + } + objects_with_dtor[num_objects_with_dtor++] = obj->handle; + } } } + objects_with_dtor = erealloc(objects_with_dtor, num_objects_with_dtor * sizeof(uint32_t)); + EG(objects_store).snapshot_objects_with_dtor = objects_with_dtor; + EG(objects_store).num_snapshot_objects_with_dtor = num_objects_with_dtor; + EG(objects_store).snapshot_top = EG(objects_store).top; + + s->objects_store = EG(objects_store); + s->map_ptr_real_base = malloc((zend_map_ptr_static_size + CG(map_ptr_size)) * sizeof(void*)); s->map_ptr_base = ZEND_MAP_PTR_BIASED_BASE(s->map_ptr_real_base); s->map_ptr_static_size = zend_map_ptr_static_size; @@ -2441,14 +2470,38 @@ ZEND_FUNCTION(snapshot_state) zend_mm_chunk *chunk = zend_mm_get_chunk_list(heap); s->chunks_count = zend_mm_get_chunks_count(heap); s->chunks = malloc(s->chunks_count * sizeof(void*)); +#if 0 s->copy = malloc(s->chunks_count * ZEND_MM_CHUNK_SIZE); - void **chunk_dest = s->chunks; void *copy_dest = s->copy; +#else + s->memfd = memfd_create("zend_mm shapshot", MFD_CLOEXEC); + if (ftruncate(s->memfd, (off_t)s->chunks_count * ZEND_MM_CHUNK_SIZE) == -1) { + zend_throw_error(NULL, "Snapshot failed: %s", strerror(errno)); + RETURN_THROWS(); + } +#endif + void **chunk_dest = s->chunks; do { zend_mm_preserve_chunk(heap, chunk); - copy_dest = mempcpy(copy_dest, chunk, ZEND_MM_CHUNK_SIZE); *chunk_dest = chunk; chunk_dest++; +#if 0 + copy_dest = mempcpy(copy_dest, chunk, ZEND_MM_CHUNK_SIZE); +#else + ssize_t written = 0; + do { + ssize_t ret = write(s->memfd, (char*)chunk + written, ZEND_MM_CHUNK_SIZE - written); + if (ret < 0) { + if (ret == EINTR) { + continue; + } + zend_throw_error(NULL, "Failed to snapshot: %s", strerror(errno)); + RETURN_THROWS(); + } else { + written += ret; + } + } while (written < ZEND_MM_CHUNK_SIZE); +#endif chunk = zend_mm_get_next_chunk(heap, chunk); } while (chunk); @@ -2535,11 +2588,21 @@ ZEND_FUNCTION(restore_state) } zend_mm_heap *heap = zend_mm_get_heap(); +#if 0 char *src = s->copy; +#endif for (int i = 0; i < s->chunks_count; i++) { zend_mm_chunk *chunk = s->chunks[i]; +#if 0 memcpy(chunk, src, ZEND_MM_CHUNK_SIZE); src += ZEND_MM_CHUNK_SIZE; +#else + void *ret = mmap(chunk, ZEND_MM_CHUNK_SIZE, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_PRIVATE, s->memfd, i * ZEND_MM_CHUNK_SIZE); + if (ret == MAP_FAILED) { + zend_throw_error(NULL, "Failed to restore state: %s", strerror(errno)); + RETURN_THROWS(); + } +#endif zend_mm_adopt_chunk(heap, chunk); } diff --git a/Zend/zend_objects_API.c b/Zend/zend_objects_API.c index 1ba250bec6439..f653e67f891a4 100644 --- a/Zend/zend_objects_API.c +++ b/Zend/zend_objects_API.c @@ -31,6 +31,9 @@ ZEND_API void ZEND_FASTCALL zend_objects_store_init(zend_objects_store *objects, objects->top = 1; /* Skip 0 so that handles are true */ objects->size = init_size; objects->free_list_head = -1; + objects->snapshot_top = 1; + objects->snapshot_objects_with_dtor = NULL; + objects->num_snapshot_objects_with_dtor = 0; memset(&objects->object_buckets[0], 0, sizeof(zend_object*)); } @@ -45,12 +48,30 @@ ZEND_API void ZEND_FASTCALL zend_objects_store_call_destructors(zend_objects_sto EG(flags) |= EG_FLAGS_OBJECT_STORE_NO_REUSE; if (objects->top > 1) { uint32_t i; - for (i = 1; i < objects->top; i++) { + for (i = objects->snapshot_top; i < objects->top; i++) { zend_object *obj = objects->object_buckets[i]; if (IS_OBJ_VALID(obj)) { if (!(OBJ_FLAGS(obj) & IS_OBJ_DESTRUCTOR_CALLED)) { GC_ADD_FLAGS(obj, IS_OBJ_DESTRUCTOR_CALLED); + if (obj->handlers->dtor_obj != zend_objects_destroy_object + || obj->ce->destructor) { + GC_ADDREF(obj); + obj->handlers->dtor_obj(obj); + GC_DELREF(obj); + } + } + } + } + } + if (objects->num_snapshot_objects_with_dtor > 0) { + for (uint32_t i = objects->num_snapshot_objects_with_dtor; i > 0; i--) { + uint32_t handle = objects->snapshot_objects_with_dtor[i-1]; + zend_object *obj = objects->object_buckets[handle]; + if (IS_OBJ_VALID(obj)) { + if (!(OBJ_FLAGS(obj) & IS_OBJ_DESTRUCTOR_CALLED)) { + GC_ADD_FLAGS(obj, IS_OBJ_DESTRUCTOR_CALLED); + if (obj->handlers->dtor_obj != zend_objects_destroy_object || obj->ce->destructor) { GC_ADDREF(obj); @@ -66,7 +87,7 @@ ZEND_API void ZEND_FASTCALL zend_objects_store_call_destructors(zend_objects_sto ZEND_API void ZEND_FASTCALL zend_objects_store_mark_destructed(zend_objects_store *objects) { if (objects->object_buckets && objects->top > 1) { - zend_object **obj_ptr = objects->object_buckets + 1; + zend_object **obj_ptr = objects->object_buckets + objects->snapshot_top; zend_object **end = objects->object_buckets + objects->top; do { @@ -90,7 +111,7 @@ ZEND_API void ZEND_FASTCALL zend_objects_store_free_object_storage(zend_objects_ /* Free object contents, but don't free objects themselves, so they show up as leaks. * Also add a ref to all objects, so the object can't be freed by something else later. */ - end = objects->object_buckets + 1; + end = objects->object_buckets + objects->snapshot_top; obj_ptr = objects->object_buckets + objects->top; if (fast_shutdown) { diff --git a/Zend/zend_objects_API.h b/Zend/zend_objects_API.h index 242bf212ba9c6..dcf20432e08c9 100644 --- a/Zend/zend_objects_API.h +++ b/Zend/zend_objects_API.h @@ -49,6 +49,9 @@ typedef struct _zend_objects_store { uint32_t top; uint32_t size; int free_list_head; + uint32_t snapshot_top; + uint32_t *snapshot_objects_with_dtor; /* handles */ + uint32_t num_snapshot_objects_with_dtor; } zend_objects_store; /* Global store handling functions */ diff --git a/Zend/zend_snapshot.h b/Zend/zend_snapshot.h index 8459b62f6a8b0..51b8543b83791 100644 --- a/Zend/zend_snapshot.h +++ b/Zend/zend_snapshot.h @@ -14,6 +14,7 @@ typedef struct _zend_snapshot { int chunks_count; void **chunks; void *copy; + int memfd; void *map_ptr_real_base; void *map_ptr_base; size_t map_ptr_static_size;