From db1e29f1cbe99d6553a2f5630a6cf8b5838f57f3 Mon Sep 17 00:00:00 2001 From: twosee Date: Fri, 28 May 2021 15:28:37 +0800 Subject: [PATCH 1/3] Align fiber C stack size Also align the macro names and generally macro definitions are always at the top of the file. --- Zend/zend.c | 18 ++++++++++++++++-- Zend/zend_fibers.c | 8 +++----- Zend/zend_fibers.h | 15 +++++++++------ 3 files changed, 28 insertions(+), 13 deletions(-) diff --git a/Zend/zend.c b/Zend/zend.c index e3da71bd4e1bb..cf89e0b978bba 100644 --- a/Zend/zend.c +++ b/Zend/zend.c @@ -177,11 +177,25 @@ static ZEND_INI_MH(OnSetExceptionStringParamMaxLen) /* {{{ */ static ZEND_INI_MH(OnUpdateFiberStackSize) /* {{{ */ { + zend_long size; + if (new_value) { - EG(fiber_stack_size) = zend_atol(ZSTR_VAL(new_value), ZSTR_LEN(new_value)); + size = zend_atol(ZSTR_VAL(new_value), ZSTR_LEN(new_value)); + if (size == 0) { + size = ZEND_FIBER_DEFAULT_C_STACK_SIZE; + } else if (size < ZEND_FIBER_MIN_C_STACK_SIZE) { + size = ZEND_FIBER_MIN_C_STACK_SIZE; + } else if (size > ZEND_FIBER_MAX_C_STACK_SIZE) { + size = ZEND_FIBER_MAX_C_STACK_SIZE; + } else { + size = ZEND_MM_ALIGNED_SIZE_EX(size, ZEND_FIBER_C_STACK_ALIGNMENT); + } } else { - EG(fiber_stack_size) = ZEND_FIBER_DEFAULT_C_STACK_SIZE; + size = ZEND_FIBER_DEFAULT_C_STACK_SIZE; } + + EG(fiber_stack_size) = size; + return SUCCESS; } /* }}} */ diff --git a/Zend/zend_fibers.c b/Zend/zend_fibers.c index 6a8ab80952bd3..821ff89e6d3c0 100644 --- a/Zend/zend_fibers.c +++ b/Zend/zend_fibers.c @@ -76,8 +76,6 @@ typedef struct _transfer_t { extern fcontext_t make_fcontext(void *sp, size_t size, void (*fn)(transfer_t)); extern transfer_t jump_fcontext(fcontext_t to, void *vp); -#define ZEND_FIBER_DEFAULT_PAGE_SIZE 4096 - #define ZEND_FIBER_BACKUP_EG(stack, stack_page_size, execute_data, error_reporting, trace_num, bailout) do { \ stack = EG(vm_stack); \ stack->top = EG(vm_stack_top); \ @@ -108,7 +106,7 @@ static size_t zend_fiber_get_page_size(void) page_size = zend_get_page_size(); if (!page_size || (page_size & (page_size - 1))) { /* anyway, we have to return a valid result */ - page_size = ZEND_FIBER_DEFAULT_PAGE_SIZE; + page_size = ZEND_FIBER_C_STACK_ALIGNMENT; } } @@ -352,11 +350,11 @@ static void ZEND_STACK_ALIGNED zend_fiber_execute(zend_fiber_context *context) EG(vm_stack) = NULL; zend_first_try { - zend_vm_stack stack = zend_fiber_vm_stack_alloc(ZEND_FIBER_VM_STACK_SIZE); + zend_vm_stack stack = zend_fiber_vm_stack_alloc(ZEND_FIBER_DEFAULT_VM_STACK_SIZE); EG(vm_stack) = stack; EG(vm_stack_top) = stack->top + ZEND_CALL_FRAME_SLOT; EG(vm_stack_end) = stack->end; - EG(vm_stack_page_size) = ZEND_FIBER_VM_STACK_SIZE; + EG(vm_stack_page_size) = ZEND_FIBER_DEFAULT_VM_STACK_SIZE; fiber->execute_data = (zend_execute_data *) stack->top; fiber->stack_bottom = fiber->execute_data; diff --git a/Zend/zend_fibers.h b/Zend/zend_fibers.h index d1a083baf1825..85ad436305282 100644 --- a/Zend/zend_fibers.h +++ b/Zend/zend_fibers.h @@ -25,6 +25,15 @@ BEGIN_EXTERN_C() +#define ZEND_FIBER_C_STACK_ALIGNMENT (4 * 1024) +#define ZEND_FIBER_DEFAULT_C_STACK_SIZE (ZEND_FIBER_C_STACK_ALIGNMENT * (sizeof(void *) * 64)) +#define ZEND_FIBER_MIN_C_STACK_SIZE (128 * 1024) +#define ZEND_FIBER_MAX_C_STACK_SIZE (16 * 1024 * 1024) + +#define ZEND_FIBER_DEFAULT_VM_STACK_SIZE (sizeof(zval) * 1024) + +#define ZEND_FIBER_GUARD_PAGES 1 + void zend_register_fiber_ce(void); void zend_fiber_init(void); @@ -105,12 +114,6 @@ ZEND_API void zend_fiber_destroy_context(zend_fiber_context *context); ZEND_API void zend_fiber_switch_context(zend_fiber_context *to); ZEND_API void zend_fiber_suspend_context(zend_fiber_context *current); -#define ZEND_FIBER_GUARD_PAGES 1 - -#define ZEND_FIBER_DEFAULT_C_STACK_SIZE (4096 * (((sizeof(void *)) < 8) ? 256 : 512)) - -#define ZEND_FIBER_VM_STACK_SIZE (1024 * sizeof(zval)) - END_EXTERN_C() #endif From 1cd136e92a276483c427b49263d4333242ac9391 Mon Sep 17 00:00:00 2001 From: twosee Date: Mon, 31 May 2021 15:00:01 +0800 Subject: [PATCH 2/3] Refactor Fiber internal implementation --- Zend/tests/fibers/fiber-this.phpt | 3 +- .../fibers/resume-non-running-fiber.phpt | 2 +- Zend/tests/fibers/resume-running-fiber.phpt | 2 +- .../tests/fibers/resume-terminated-fiber.phpt | 2 +- Zend/tests/fibers/suspend-outside-fiber.phpt | 2 +- .../fibers/throw-into-non-running-fiber.phpt | 2 +- Zend/zend_execute_API.c | 1 + Zend/zend_fibers.c | 839 ++++++++---------- Zend/zend_fibers.h | 136 +-- Zend/zend_globals.h | 16 +- Zend/zend_portability.h | 2 + ext/reflection/php_reflection.c | 18 +- ext/zend_test/test.c | 56 +- ext/zend_test/tests/observer_fiber_05.phpt | 2 - 14 files changed, 512 insertions(+), 571 deletions(-) diff --git a/Zend/tests/fibers/fiber-this.phpt b/Zend/tests/fibers/fiber-this.phpt index 714a848f0aad0..d5d585a23ebbf 100644 --- a/Zend/tests/fibers/fiber-this.phpt +++ b/Zend/tests/fibers/fiber-this.phpt @@ -13,6 +13,7 @@ $fiber->start(); ?> --EXPECTF-- -NULL +object(Fiber)#%d (0) { +} object(Fiber)#%d (0) { } diff --git a/Zend/tests/fibers/resume-non-running-fiber.phpt b/Zend/tests/fibers/resume-non-running-fiber.phpt index 1efd32eda2efd..396cd45ded260 100644 --- a/Zend/tests/fibers/resume-non-running-fiber.phpt +++ b/Zend/tests/fibers/resume-non-running-fiber.phpt @@ -9,7 +9,7 @@ $fiber->resume(); ?> --EXPECTF-- -Fatal error: Uncaught FiberError: Cannot resume a fiber that is not suspended in %sresume-non-running-fiber.php:%d +Fatal error: Uncaught FiberError: Fiber has not started in %sresume-non-running-fiber.php:%d Stack trace: #0 %sresume-non-running-fiber.php(%d): Fiber->resume() #1 {main} diff --git a/Zend/tests/fibers/resume-running-fiber.phpt b/Zend/tests/fibers/resume-running-fiber.phpt index ca52d50c6946b..e852ca101c3c6 100644 --- a/Zend/tests/fibers/resume-running-fiber.phpt +++ b/Zend/tests/fibers/resume-running-fiber.phpt @@ -12,7 +12,7 @@ $fiber->start(); ?> --EXPECTF-- -Fatal error: Uncaught FiberError: Cannot resume a fiber that is not suspended in %sresume-running-fiber.php:%d +Fatal error: Uncaught FiberError: Fiber is running in %sresume-running-fiber.php:%d Stack trace: #0 %sresume-running-fiber.php(%d): Fiber->resume() #1 [internal function]: {closure}() diff --git a/Zend/tests/fibers/resume-terminated-fiber.phpt b/Zend/tests/fibers/resume-terminated-fiber.phpt index dae85d8854824..58eb589e1ddf0 100644 --- a/Zend/tests/fibers/resume-terminated-fiber.phpt +++ b/Zend/tests/fibers/resume-terminated-fiber.phpt @@ -11,7 +11,7 @@ $fiber->resume(); ?> --EXPECTF-- -Fatal error: Uncaught FiberError: Cannot resume a fiber that is not suspended in %sresume-terminated-fiber.php:%d +Fatal error: Uncaught FiberError: Fiber is dead in %sresume-terminated-fiber.php:%d Stack trace: #0 %sresume-terminated-fiber.php(%d): Fiber->resume() #1 {main} diff --git a/Zend/tests/fibers/suspend-outside-fiber.phpt b/Zend/tests/fibers/suspend-outside-fiber.phpt index 5fb524cd90f7a..2c64ff0abfaed 100644 --- a/Zend/tests/fibers/suspend-outside-fiber.phpt +++ b/Zend/tests/fibers/suspend-outside-fiber.phpt @@ -7,7 +7,7 @@ $value = Fiber::suspend(1); ?> --EXPECTF-- -Fatal error: Uncaught FiberError: Cannot suspend outside of a fiber in %ssuspend-outside-fiber.php:%d +Fatal error: Uncaught FiberError: Fiber has nowhere to go in %ssuspend-outside-fiber.php:%d Stack trace: #0 %ssuspend-outside-fiber.php(%d): Fiber::suspend(1) #1 {main} diff --git a/Zend/tests/fibers/throw-into-non-running-fiber.phpt b/Zend/tests/fibers/throw-into-non-running-fiber.phpt index c195865a494d4..5b7970512c013 100644 --- a/Zend/tests/fibers/throw-into-non-running-fiber.phpt +++ b/Zend/tests/fibers/throw-into-non-running-fiber.phpt @@ -9,7 +9,7 @@ $fiber->throw(new Exception('test')); ?> --EXPECTF-- -Fatal error: Uncaught FiberError: Cannot resume a fiber that is not suspended in %sthrow-into-non-running-fiber.php:%d +Fatal error: Uncaught FiberError: Fiber has not started in %sthrow-into-non-running-fiber.php:%d Stack trace: #0 %sthrow-into-non-running-fiber.php(%d): Fiber->throw(Object(Exception)) #1 {main} diff --git a/Zend/zend_execute_API.c b/Zend/zend_execute_API.c index 0a6c28e59e58a..c038bc58d75f8 100644 --- a/Zend/zend_execute_API.c +++ b/Zend/zend_execute_API.c @@ -379,6 +379,7 @@ void shutdown_executor(void) /* {{{ */ zend_objects_store_free_object_storage(&EG(objects_store), fast_shutdown); + zend_fiber_shutdown(); zend_weakrefs_shutdown(); zend_try { diff --git a/Zend/zend_fibers.c b/Zend/zend_fibers.c index 821ff89e6d3c0..0856cd315c4ef 100644 --- a/Zend/zend_fibers.c +++ b/Zend/zend_fibers.c @@ -25,6 +25,7 @@ #include "zend_exceptions.h" #include "zend_builtin_functions.h" #include "zend_observer.h" +#include "zend_weakrefs.h" #include "zend_fibers.h" #include "zend_fibers_arginfo.h" @@ -33,6 +34,10 @@ # include #endif +#ifdef __SANITIZE_ADDRESS__ +# include +#endif + #ifndef ZEND_WIN32 # include # include @@ -55,48 +60,23 @@ # endif #endif -#ifdef __SANITIZE_ADDRESS__ -# include -#endif +#define ZEND_VM_STACK_MAIN_FIBER_SLOTS \ + ((ZEND_MM_ALIGNED_SIZE(sizeof(zend_fiber) + sizeof(zend_fiber_executor)) + ZEND_MM_ALIGNED_SIZE(sizeof(zval)) - 1) / ZEND_MM_ALIGNED_SIZE(sizeof(zval))); + +#define ZEND_FIBER_GRACEFUL_EXIT_MAGIC ((zend_object *) -1) ZEND_API zend_class_entry *zend_ce_fiber; static zend_class_entry *zend_ce_fiber_error; static zend_object_handlers zend_fiber_handlers; -static zend_function zend_fiber_function = { ZEND_INTERNAL_FUNCTION }; - -typedef void *fcontext_t; - -typedef struct _transfer_t { - fcontext_t context; - void *data; -} transfer_t; - -extern fcontext_t make_fcontext(void *sp, size_t size, void (*fn)(transfer_t)); -extern transfer_t jump_fcontext(fcontext_t to, void *vp); - -#define ZEND_FIBER_BACKUP_EG(stack, stack_page_size, execute_data, error_reporting, trace_num, bailout) do { \ - stack = EG(vm_stack); \ - stack->top = EG(vm_stack_top); \ - stack->end = EG(vm_stack_end); \ - stack_page_size = EG(vm_stack_page_size); \ - execute_data = EG(current_execute_data); \ - error_reporting = EG(error_reporting); \ - trace_num = EG(jit_trace_num); \ - bailout = EG(bailout); \ -} while (0) - -#define ZEND_FIBER_RESTORE_EG(stack, stack_page_size, execute_data, error_reporting, trace_num, bailout) do { \ - EG(vm_stack) = stack; \ - EG(vm_stack_top) = stack->top; \ - EG(vm_stack_end) = stack->end; \ - EG(vm_stack_page_size) = stack_page_size; \ - EG(current_execute_data) = execute_data; \ - EG(error_reporting) = error_reporting; \ - EG(jit_trace_num) = trace_num; \ - EG(bailout) = bailout; \ -} while (0) +typedef struct { + void *from; + zval *data; +} zend_fiber_transfer; + +void *make_fcontext(void *stack, size_t stack_size, void (*fn)(zend_fiber_transfer)); +zend_fiber_transfer jump_fcontext(void const *target, zval *transfer_data); static size_t zend_fiber_get_page_size(void) { @@ -113,548 +93,455 @@ static size_t zend_fiber_get_page_size(void) return page_size; } -static bool zend_fiber_stack_allocate(zend_fiber_stack *stack, size_t size) +static zend_object *zend_fiber_object_create(zend_class_entry *ce) { - void *pointer; - const size_t page_size = zend_fiber_get_page_size(); - - ZEND_ASSERT(size >= page_size + ZEND_FIBER_GUARD_PAGES * page_size); - - stack->size = (size + page_size - 1) / page_size * page_size; - const size_t msize = stack->size + ZEND_FIBER_GUARD_PAGES * page_size; - -#ifdef ZEND_WIN32 - pointer = VirtualAlloc(0, msize, MEM_COMMIT, PAGE_READWRITE); - - if (!pointer) { - DWORD err = GetLastError(); - char *errmsg = php_win32_error_to_msg(err); - zend_throw_exception_ex(NULL, 0, "Fiber make context failed: VirtualAlloc failed: [0x%08lx] %s", err, errmsg[0] ? errmsg : "Unknown"); - php_win32_error_msg_free(errmsg); - return false; - } - -# if ZEND_FIBER_GUARD_PAGES - DWORD protect; - - if (!VirtualProtect(pointer, ZEND_FIBER_GUARD_PAGES * page_size, PAGE_READWRITE | PAGE_GUARD, &protect)) { - DWORD err = GetLastError(); - char *errmsg = php_win32_error_to_msg(err); - zend_throw_exception_ex(NULL, 0, "Fiber protect stack failed: VirtualProtect failed: [0x%08lx] %s", err, errmsg[0] ? errmsg : "Unknown"); - php_win32_error_msg_free(errmsg); - VirtualFree(pointer, 0, MEM_RELEASE); - return false; - } -# endif -#else - pointer = mmap(NULL, msize, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0); - - if (pointer == MAP_FAILED) { - zend_throw_exception_ex(NULL, 0, "Fiber make context failed: mmap failed: %s (%d)", strerror(errno), errno); - return false; - } - -# if ZEND_FIBER_GUARD_PAGES - if (mprotect(pointer, ZEND_FIBER_GUARD_PAGES * page_size, PROT_NONE) < 0) { - zend_throw_exception_ex(NULL, 0, "Fiber protect stack failed: mmap failed: %s (%d)", strerror(errno), errno); - munmap(pointer, msize); - return false; - } -# endif -#endif + zend_fiber *fiber; - stack->pointer = (void *) ((uintptr_t) pointer + ZEND_FIBER_GUARD_PAGES * page_size); + fiber = emalloc(sizeof(zend_fiber)); + memset(fiber, 0, sizeof(zend_fiber)); -#ifdef VALGRIND_STACK_REGISTER - uintptr_t base = (uintptr_t) stack->pointer; - stack->valgrind = VALGRIND_STACK_REGISTER(base, base + stack->size); -#endif + zend_object_std_init(&fiber->std, ce); + fiber->std.handlers = &zend_fiber_handlers; - return true; + return &fiber->std; } -static void zend_fiber_stack_free(zend_fiber_stack *stack) +static void zend_fiber_object_destroy(zend_object *object) { - if (!stack->pointer) { + zend_fiber *fiber = (zend_fiber *) object; + + fiber->flags |= ZEND_FIBER_FLAG_DESTROYED; + if (fiber->status != ZEND_FIBER_STATUS_SUSPENDED) { return; } -#ifdef VALGRIND_STACK_DEREGISTER - VALGRIND_STACK_DEREGISTER(stack->valgrind); -#endif + zend_object *exception = EG(exception); + EG(exception) = NULL; - const size_t page_size = zend_fiber_get_page_size(); + EG(fiber_exception) = ZEND_FIBER_GRACEFUL_EXIT_MAGIC; + zend_fiber_jump(fiber, NULL, NULL); - void *pointer = (void *) ((uintptr_t) stack->pointer - ZEND_FIBER_GUARD_PAGES * page_size); + if (EG(exception)) { + if (!exception && EG(current_execute_data) && EG(current_execute_data)->func + && ZEND_USER_CODE(EG(current_execute_data)->func->common.type)) { + zend_rethrow_exception(EG(current_execute_data)); + } -#ifdef ZEND_WIN32 - VirtualFree(pointer, 0, MEM_RELEASE); -#else - munmap(pointer, stack->size + ZEND_FIBER_GUARD_PAGES * page_size); -#endif + zend_exception_set_previous(EG(exception), exception); - stack->pointer = NULL; + if (!EG(current_execute_data)) { + zend_exception_error(EG(exception), E_ERROR); + } + } else { + EG(exception) = exception; + } } -static ZEND_NORETURN void zend_fiber_trampoline(transfer_t transfer) +static void zend_fiber_object_free(zend_object *object) { - zend_fiber_context *context = transfer.data; - -#ifdef __SANITIZE_ADDRESS__ - __sanitizer_finish_switch_fiber(NULL, &context->stack.prior_pointer, &context->stack.prior_size); -#endif - - context->caller = transfer.context; - - context->function(context); - - context->self = NULL; - -#ifdef __SANITIZE_ADDRESS__ - __sanitizer_start_switch_fiber(NULL, context->stack.prior_pointer, context->stack.prior_size); -#endif + zend_fiber *fiber = (zend_fiber *) object; - jump_fcontext(context->caller, NULL); + if (fiber->status == ZEND_FIBER_STATUS_INIT) { + // Fiber was never started, so we need to release the reference to the callback. + zval_ptr_dtor(&fiber->fci.function_name); + } + zval_ptr_dtor(&fiber->retval); - abort(); + zend_object_std_dtor(&fiber->std); } -ZEND_API bool zend_fiber_init_context(zend_fiber_context *context, zend_fiber_coroutine coroutine, size_t stack_size) -{ - if (UNEXPECTED(!zend_fiber_stack_allocate(&context->stack, stack_size))) { - return false; +static ZEND_COLD void zend_fiber_status_error(const zend_fiber *fiber) +{ + switch (fiber->status) { + case ZEND_FIBER_STATUS_RUNNING: + zend_throw_error(zend_ce_fiber_error, "Fiber is running"); + break; + case ZEND_FIBER_STATUS_INIT: + zend_throw_error(zend_ce_fiber_error, "Fiber has not started"); + break; + case ZEND_FIBER_STATUS_DEAD: + zend_throw_error(zend_ce_fiber_error, "Fiber is dead"); + break; + default: + ZEND_UNREACHABLE(); + break; } - - // Stack grows down, calculate the top of the stack. make_fcontext then shifts pointer to lower 16-byte boundary. - void *stack = (void *) ((uintptr_t) context->stack.pointer + context->stack.size); - - context->self = make_fcontext(stack, context->stack.size, zend_fiber_trampoline); - ZEND_ASSERT(context->self != NULL && "make_fcontext() never returns NULL"); - context->function = coroutine; - context->caller = NULL; - - return true; } -ZEND_API void zend_fiber_destroy_context(zend_fiber_context *context) +static ZEND_COLD void zend_fiber_handle_exception(zend_fiber *fiber) { - zend_fiber_stack_free(&context->stack); + if (zend_is_graceful_exit(EG(exception)) || zend_is_unwind_exit(EG(exception))) { + if (fiber->flags & ZEND_FIBER_FLAG_DESTROYED) { + zend_clear_exception(); + } + } else { + fiber->flags |= ZEND_FIBER_FLAG_THREW; + } } -ZEND_API void zend_fiber_switch_context(zend_fiber_context *to) +static ZEND_COLD void zend_fiber_handle_cross_exception(zend_fiber *fiber) { - ZEND_ASSERT(to && to->self && to->stack.pointer && "Invalid fiber context"); - -#ifdef __SANITIZE_ADDRESS__ - void *fake_stack; - __sanitizer_start_switch_fiber(&fake_stack, to->stack.pointer, to->stack.size); -#endif - - transfer_t transfer = jump_fcontext(to->self, to); + zend_object *exception = EG(fiber_exception); -#ifdef __SANITIZE_ADDRESS__ - __sanitizer_finish_switch_fiber(fake_stack, &to->stack.prior_pointer, &to->stack.prior_size); -#endif - - to->self = transfer.context; + EG(fiber_exception) = NULL; + if (exception == ZEND_FIBER_GRACEFUL_EXIT_MAGIC) { + zend_throw_graceful_exit(); + } else { + zend_throw_exception_internal(exception); + } } -ZEND_API void zend_fiber_suspend_context(zend_fiber_context *current) +static void ZEND_STACK_ALIGNED zend_fiber_execute(zend_fiber_transfer transfer) { - ZEND_ASSERT(current && current->caller && current->stack.pointer && "Invalid fiber context"); - -#ifdef __SANITIZE_ADDRESS__ - void *fake_stack; - __sanitizer_start_switch_fiber(&fake_stack, current->stack.prior_pointer, current->stack.prior_size); -#endif - - transfer_t transfer = jump_fcontext(current->caller, NULL); + zend_fiber *fiber = EG(current_fiber); #ifdef __SANITIZE_ADDRESS__ - __sanitizer_finish_switch_fiber(fake_stack, ¤t->stack.prior_pointer, ¤t->stack.prior_size); + __sanitizer_finish_switch_fiber(NULL, &fiber->from->context.asan_stack_bottom, &fiber->from->context.asan_stack_size); #endif - current->caller = transfer.context; -} + /* update from context */ + fiber->from->context.ptr = transfer.from; -static void zend_fiber_suspend_from(zend_fiber *fiber) -{ - zend_vm_stack stack; - size_t stack_page_size; - zend_execute_data *execute_data; - int error_reporting; - uint32_t jit_trace_num; - JMP_BUF *bailout; - - ZEND_FIBER_BACKUP_EG(stack, stack_page_size, execute_data, error_reporting, jit_trace_num, bailout); - - zend_fiber_suspend_context(&fiber->context); - - ZEND_FIBER_RESTORE_EG(stack, stack_page_size, execute_data, error_reporting, jit_trace_num, bailout); -} - -static void zend_fiber_switch_to(zend_fiber *fiber) -{ - zend_fiber *previous; - zend_vm_stack stack; - size_t stack_page_size; - zend_execute_data *execute_data; - int error_reporting; - uint32_t jit_trace_num; - JMP_BUF *bailout; - - previous = EG(current_fiber); - - zend_observer_fiber_switch_notify(previous, fiber); - - ZEND_FIBER_BACKUP_EG(stack, stack_page_size, execute_data, error_reporting, jit_trace_num, bailout); - - EG(current_fiber) = fiber; - - zend_fiber_switch_context(&fiber->context); - - EG(current_fiber) = previous; - - ZEND_FIBER_RESTORE_EG(stack, stack_page_size, execute_data, error_reporting, jit_trace_num, bailout); - - zend_observer_fiber_switch_notify(fiber, previous); - - if (UNEXPECTED(fiber->status == ZEND_FIBER_STATUS_BAILOUT)) { - // zend_bailout() was called in the fiber, so call it again in the previous fiber or {main}. - zend_bailout(); + /* prepare function call info (call from API) */ + if (transfer.data != NULL) { + if (Z_TYPE_P(transfer.data) != IS_PTR) { + fiber->fci.param_count = 1; + fiber->fci.params = transfer.data; + fiber->fci.named_params = NULL; + } else { + zend_fcall_info *fci = Z_PTR_P(transfer.data); + fiber->fci.param_count = fci->param_count; + fiber->fci.params = fci->params; + fiber->fci.named_params = fci->named_params; + } } -} - - -static zend_always_inline zend_vm_stack zend_fiber_vm_stack_alloc(size_t size) -{ - zend_vm_stack page = emalloc(size); - - page->top = ZEND_VM_STACK_ELEMENTS(page); - page->end = (zval *) ((uintptr_t) page + size); - page->prev = NULL; - - return page; -} - -static void ZEND_STACK_ALIGNED zend_fiber_execute(zend_fiber_context *context) -{ - zend_fiber *fiber = EG(current_fiber); - ZEND_ASSERT(fiber); + fiber->fci.retval = &fiber->retval; zend_long error_reporting = INI_INT("error_reporting"); if (!error_reporting && !INI_STR("error_reporting")) { error_reporting = E_ALL; } + EG(error_reporting) = error_reporting; - EG(vm_stack) = NULL; - - zend_first_try { - zend_vm_stack stack = zend_fiber_vm_stack_alloc(ZEND_FIBER_DEFAULT_VM_STACK_SIZE); - EG(vm_stack) = stack; - EG(vm_stack_top) = stack->top + ZEND_CALL_FRAME_SLOT; - EG(vm_stack_end) = stack->end; - EG(vm_stack_page_size) = ZEND_FIBER_DEFAULT_VM_STACK_SIZE; - - fiber->execute_data = (zend_execute_data *) stack->top; - fiber->stack_bottom = fiber->execute_data; - - memset(fiber->execute_data, 0, sizeof(zend_execute_data)); - - fiber->execute_data->func = &zend_fiber_function; - fiber->stack_bottom->prev_execute_data = EG(current_execute_data); - - EG(current_execute_data) = fiber->execute_data; - EG(jit_trace_num) = 0; - EG(error_reporting) = error_reporting; - - fiber->fci.retval = &fiber->value; - - fiber->status = ZEND_FIBER_STATUS_RUNNING; - + zend_try { zend_call_function(&fiber->fci, &fiber->fci_cache); - + if (UNEXPECTED(EG(exception))) { + zend_fiber_handle_exception(fiber); + } zval_ptr_dtor(&fiber->fci.function_name); - - if (EG(exception)) { - if (fiber->status == ZEND_FIBER_STATUS_SHUTDOWN) { - if (EXPECTED(zend_is_graceful_exit(EG(exception)) || zend_is_unwind_exit(EG(exception)))) { - zend_clear_exception(); - } - } else { - fiber->status = ZEND_FIBER_STATUS_THREW; - } - } else { - fiber->status = ZEND_FIBER_STATUS_RETURNED; + if (UNEXPECTED(EG(exception))) { + zend_fiber_handle_exception(fiber); } + ZVAL_NULL(&fiber->fci.function_name); } zend_catch { - fiber->status = ZEND_FIBER_STATUS_BAILOUT; + fiber->flags |= ZEND_FIBER_FLAG_BAILOUT; } zend_end_try(); - zend_vm_stack_destroy(); - fiber->execute_data = NULL; - fiber->stack_bottom = NULL; + /* release extra vm stack pages */ + zend_vm_stack stack = EG(vm_stack); + while (stack != fiber->executor->vm_stack) { + zend_vm_stack prev = stack->prev; + efree(stack); + stack = prev; + } + + fiber->status = ZEND_FIBER_STATUS_DEAD; + zend_fiber_jump(fiber->previous, NULL, NULL); + + abort(); } -static zend_object *zend_fiber_object_create(zend_class_entry *ce) +ZEND_API zend_fiber *zend_fiber_create(const zend_fcall_info *fci, const zend_fcall_info_cache *fci_cache) { - zend_fiber *fiber; + zend_fiber *fiber = (zend_fiber *) zend_fiber_object_create(zend_ce_fiber); - fiber = emalloc(sizeof(zend_fiber)); - memset(fiber, 0, sizeof(zend_fiber)); + fiber->fci = *fci; + fiber->fci_cache = *fci_cache; - zend_object_std_init(&fiber->std, ce); - fiber->std.handlers = &zend_fiber_handlers; + Z_TRY_ADDREF(fiber->fci.function_name); - return &fiber->std; + return fiber; } -static void zend_fiber_object_destroy(zend_object *object) +ZEND_API bool zend_fiber_start(zend_fiber *fiber, zval *data, zval *retval) { - zend_fiber *fiber = (zend_fiber *) object; - - if (fiber->status != ZEND_FIBER_STATUS_SUSPENDED) { - return; + if (fiber->status != ZEND_FIBER_STATUS_INIT) { + zend_throw_error(zend_ce_fiber_error, "Cannot start a fiber that has already been started"); + return false; } - zend_object *exception = EG(exception); - EG(exception) = NULL; - - fiber->status = ZEND_FIBER_STATUS_SHUTDOWN; + size_t stack_size = EG(fiber_stack_size), user_stack_size; + void *stack, *stack_start; + zend_vm_stack vm_stack; + uint32_t vm_stack_size = ZEND_FIBER_DEFAULT_VM_STACK_SIZE; // TODO: make it configurable? + zend_fiber_executor *executor; + zend_execute_data *execute_data; + static const zend_function dummy_function = { ZEND_INTERNAL_FUNCTION }; // constify it to make sure it's readonly - zend_fiber_switch_to(fiber); +#ifdef ZEND_WIN32 + stack = VirtualAlloc(0, stack_size, MEM_COMMIT, PAGE_READWRITE); - if (EG(exception)) { - if (!exception && EG(current_execute_data) && EG(current_execute_data)->func - && ZEND_USER_CODE(EG(current_execute_data)->func->common.type)) { - zend_rethrow_exception(EG(current_execute_data)); - } + if (!stack) { + DWORD err = GetLastError(); + char *errmsg = php_win32_error_to_msg(err); + zend_throw_exception_ex(NULL, 0, "Fiber make context failed: VirtualAlloc failed: [0x%08lx] %s", err, errmsg[0] ? errmsg : "Unknown"); + php_win32_error_msg_free(errmsg); + return false; + } - zend_exception_set_previous(EG(exception), exception); +# if ZEND_FIBER_GUARD_PAGES + DWORD protect; - if (!EG(current_execute_data)) { - zend_exception_error(EG(exception), E_ERROR); - } - } else { - EG(exception) = exception; + if (!VirtualProtect(stack, zend_fiber_get_page_size(), PAGE_READWRITE | PAGE_GUARD, &protect)) { + DWORD err = GetLastError(); + char *errmsg = php_win32_error_to_msg(err); + zend_throw_exception_ex(NULL, 0, "Fiber protect stack failed: VirtualProtect failed: [0x%08lx] %s", err, errmsg[0] ? errmsg : "Unknown"); + php_win32_error_msg_free(errmsg); + VirtualFree(stack, 0, MEM_RELEASE); + return false; } -} - -static void zend_fiber_object_free(zend_object *object) -{ - zend_fiber *fiber = (zend_fiber *) object; +# endif +#else + stack = mmap(NULL, stack_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0); - if (fiber->status == ZEND_FIBER_STATUS_INIT) { - // Fiber was never started, so we need to release the reference to the callback. - zval_ptr_dtor(&fiber->fci.function_name); + if (stack == MAP_FAILED) { + zend_throw_exception_ex(NULL, 0, "Fiber make context failed: mmap failed: %s (%d)", strerror(errno), errno); + return false; } - zval_ptr_dtor(&fiber->value); +# if ZEND_FIBER_GUARD_PAGES + if (mprotect(stack, zend_fiber_get_page_size(), PROT_NONE) < 0) { + zend_throw_exception_ex(NULL, 0, "Fiber protect stack failed: mmap failed: %s (%d)", strerror(errno), errno); + munmap(stack, stack_size); + return false; + } +# endif +#endif + user_stack_size = stack_size - (vm_stack_size + sizeof(*executor) + sizeof(*execute_data)); + stack_start = ((char *) stack) + user_stack_size; + /* Use stack memory to store VM stack, executor and execute_data */ + vm_stack = (zend_vm_stack) stack_start; + vm_stack->top = ZEND_VM_STACK_ELEMENTS(vm_stack); + vm_stack->end = (zval*) ((char*) vm_stack + vm_stack_size); + vm_stack->prev = NULL; + memset(((char *) vm_stack) + vm_stack_size, 0, sizeof(*executor) + sizeof(*execute_data)); + executor = (zend_fiber_executor *) (((char *) stack_start) + vm_stack_size); + executor->vm_stack = vm_stack; + executor->vm_stack_top = vm_stack->top; + executor->vm_stack_end = vm_stack->end; + executor->vm_stack_page_size = vm_stack_size; + execute_data = (zend_execute_data *) (((char *) stack_start) + vm_stack_size + sizeof(*executor)); + execute_data->func = (zend_function *) &dummy_function; + executor->current_execute_data = execute_data; + + fiber->context.stack = stack; + fiber->context.stack_size = stack_size; + fiber->context.ptr = make_fcontext(stack_start, user_stack_size, zend_fiber_execute); + ZEND_ASSERT(fiber->context.ptr != NULL && "make_fcontext() never returns NULL"); +#ifdef VALGRIND_STACK_REGISTER + fiber->context.valgrind_stack_id = VALGRIND_STACK_REGISTER(stack_start, stack); +#endif +#ifdef __SANITIZE_ADDRESS__ + fiber->context.asan_fake_stack = NULL; + fiber->context.asan_stack_bottom = stack; + fiber->context.asan_stack_size = user_stack_size; +#endif + fiber->executor = executor; + fiber->execute_data = fiber->executor->current_execute_data = execute_data; - zend_fiber_destroy_context(&fiber->context); + zend_fiber_jump(fiber, data, retval); - zend_object_std_dtor(&fiber->std); + return true; } -ZEND_API zend_fiber *zend_fiber_create(const zend_fcall_info *fci, const zend_fcall_info_cache *fci_cache) +ZEND_API void zend_fiber_jump(zend_fiber *fiber, zval *data, zval *retval) { - zend_fiber *fiber = (zend_fiber *) zend_fiber_object_create(zend_ce_fiber); + zend_fiber *current_fiber = EG(current_fiber); - fiber->fci = *fci; - fiber->fci_cache = *fci_cache; - - Z_TRY_ADDREF(fiber->fci.function_name); + zend_observer_fiber_switch_notify(current_fiber, fiber); - return fiber; -} + fiber->from = current_fiber; + if (current_fiber->previous == fiber) { + /* if it is suspend, update current status to waiting and break the previous */ + if (current_fiber->status == ZEND_FIBER_STATUS_RUNNING) { + /* if it is not finished, set status to suspended */ + current_fiber->status = ZEND_FIBER_STATUS_SUSPENDED; + } + current_fiber->previous = NULL; + if (current_fiber->execute_data != NULL) { + current_fiber->execute_data->prev_execute_data = NULL; + } + } else { + /* it is not suspend, current becomes target's previous */ + ZEND_ASSERT(fiber->previous == NULL); + fiber->previous = current_fiber; + if (fiber->execute_data != NULL && !(fiber->flags & ZEND_FIBER_FLAG_DESTROYED)) { + fiber->execute_data->prev_execute_data = EG(current_execute_data); + } + } + fiber->status = ZEND_FIBER_STATUS_RUNNING; + EG(current_fiber) = fiber; -ZEND_METHOD(Fiber, __construct) -{ - zend_fiber *fiber = (zend_fiber *) Z_OBJ_P(getThis()); + /* ZendVM executor jump */ + memcpy(current_fiber->executor, &EG(bailout), ZEND_FIBER_EXECUTOR_UNALIGNED_SIZE); + memcpy(&EG(bailout), fiber->executor, ZEND_FIBER_EXECUTOR_UNALIGNED_SIZE); - ZEND_PARSE_PARAMETERS_START(1, 1) - Z_PARAM_FUNC(fiber->fci, fiber->fci_cache) - ZEND_PARSE_PARAMETERS_END(); +#ifdef __SANITIZE_ADDRESS__ + __sanitizer_start_switch_fiber( + current_fiber->status != ZEND_FIBER_STATUS_DEAD ? ¤t_fiber->context.asan_fake_stack : NULL, + fiber->context.asan_stack_bottom, fiber->context.asan_stack_size + ); +#endif - // Keep a reference to closures or callable objects while the fiber is running. - Z_TRY_ADDREF(fiber->fci.function_name); -} + /* C stack jump */ + zend_fiber_transfer transfer; + transfer = jump_fcontext(fiber->context.ptr, data); -ZEND_API void zend_fiber_start(zend_fiber *fiber, zval *params, uint32_t param_count, zend_array *named_params, zval *return_value) -{ - if (fiber->status != ZEND_FIBER_STATUS_INIT) { - zend_throw_error(zend_ce_fiber_error, "Cannot start a fiber that has already been started"); - RETURN_THROWS(); - } + /* update from context */ + fiber = current_fiber->from; + fiber->context.ptr = transfer.from; - fiber->fci.params = params; - fiber->fci.param_count = param_count; - fiber->fci.named_params = named_params; +#ifdef __SANITIZE_ADDRESS__ + __sanitizer_finish_switch_fiber(current_fiber->context.asan_fake_stack, &fiber->context.asan_stack_bottom, &fiber->context.asan_stack_size); +#endif - if (!zend_fiber_init_context(&fiber->context, zend_fiber_execute, EG(fiber_stack_size))) { - RETURN_THROWS(); + /* copy return data */ + if (retval != NULL) { + if (transfer.data == NULL) { + ZVAL_NULL(retval); + } else { + ZVAL_COPY(retval, transfer.data); + } } - zend_fiber_switch_to(fiber); - - if (fiber->status & ZEND_FIBER_STATUS_FINISHED) { - RETURN_NULL(); + /* close the fiber if it is finished */ + if (UNEXPECTED(fiber->status == ZEND_FIBER_STATUS_DEAD)) { +#ifdef VALGRIND_STACK_DEREGISTER + VALGRIND_STACK_DEREGISTER(fiber->context.valgrind_stack_id); +#endif +#ifdef ZEND_WIN32 + VirtualFree(fiber->context.stack, 0, MEM_RELEASE); +#else + munmap(fiber->context.stack, fiber->context.stack_size); +#endif + } else if (UNEXPECTED(EG(fiber_exception) != NULL)) { + zend_fiber_handle_cross_exception(current_fiber); } - - RETVAL_COPY_VALUE(&fiber->value); - ZVAL_UNDEF(&fiber->value); } -ZEND_METHOD(Fiber, start) +ZEND_API bool zend_fiber_resume(zend_fiber *fiber, zval *data, zval *retval) { - zend_fiber *fiber = (zend_fiber *) Z_OBJ_P(getThis()); - zval *params; - uint32_t param_count; - zend_array *named_params; + if (fiber != EG(current_fiber)->previous) { + if (UNEXPECTED(fiber->status != ZEND_FIBER_STATUS_SUSPENDED)) { + zend_fiber_status_error(fiber); + return false; + } + } - ZEND_PARSE_PARAMETERS_START(0, -1) - Z_PARAM_VARIADIC_WITH_NAMED(params, param_count, named_params); - ZEND_PARSE_PARAMETERS_END(); + zend_fiber_jump(fiber, data, retval); - zend_fiber_start(fiber, params, param_count, named_params, return_value); + return true; } -ZEND_API void zend_fiber_suspend(zval *value, zval *return_value) +ZEND_API bool zend_fiber_suspend(zval *data, zval *retval) { - zend_fiber *fiber = EG(current_fiber); + zend_fiber *fiber = EG(current_fiber)->previous; - if (UNEXPECTED(!fiber)) { - zend_throw_error(zend_ce_fiber_error, "Cannot suspend outside of a fiber"); - RETURN_THROWS(); + if (UNEXPECTED(fiber == NULL)) { + zend_throw_error(zend_ce_fiber_error, "Fiber has nowhere to go"); + return false; } - - if (UNEXPECTED(fiber->status == ZEND_FIBER_STATUS_SHUTDOWN)) { + if (UNEXPECTED(EG(current_fiber)->flags & ZEND_FIBER_FLAG_DESTROYED)) { zend_throw_error(zend_ce_fiber_error, "Cannot suspend in a force-closed fiber"); - RETURN_THROWS(); - } - - ZEND_ASSERT(fiber->status == ZEND_FIBER_STATUS_RUNNING); - - if (value) { - ZVAL_COPY(&fiber->value, value); - } else { - ZVAL_NULL(&fiber->value); + return false; } - fiber->execute_data = EG(current_execute_data); - fiber->status = ZEND_FIBER_STATUS_SUSPENDED; - fiber->stack_bottom->prev_execute_data = NULL; - - zend_fiber_suspend_from(fiber); + zend_fiber_jump(fiber, data, retval); - if (fiber->status == ZEND_FIBER_STATUS_SHUTDOWN) { - // This occurs when the fiber is GC'ed while suspended. - zend_throw_graceful_exit(); - RETURN_THROWS(); - } - - fiber->status = ZEND_FIBER_STATUS_RUNNING; + return true; +} - if (fiber->exception) { - zval *exception = fiber->exception; - fiber->exception = NULL; +ZEND_API bool zend_fiber_throw(zend_fiber *fiber, zend_object *exception, zval *retval) +{ + GC_ADDREF(exception); + EG(fiber_exception) = exception; - zend_throw_exception_object(exception); - RETURN_THROWS(); + if (!zend_fiber_resume(fiber, NULL, retval)) { + EG(fiber_exception) = NULL; + GC_DELREF(exception); + return false; } - RETVAL_COPY_VALUE(&fiber->value); - ZVAL_UNDEF(&fiber->value); + return true; } -ZEND_METHOD(Fiber, suspend) +ZEND_METHOD(Fiber, __construct) { - zval *value = NULL; + zend_fiber *fiber = (zend_fiber *) Z_OBJ_P(getThis()); - ZEND_PARSE_PARAMETERS_START(0, 1) - Z_PARAM_OPTIONAL - Z_PARAM_ZVAL(value); + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_FUNC(fiber->fci, fiber->fci_cache) ZEND_PARSE_PARAMETERS_END(); - zend_fiber_suspend(value, return_value); + // Keep a reference to closures or callable objects while the fiber is running. + Z_TRY_ADDREF(fiber->fci.function_name); } -ZEND_API void zend_fiber_resume(zend_fiber *fiber, zval *value, zval *return_value) +ZEND_METHOD(Fiber, start) { - if (UNEXPECTED(fiber->status != ZEND_FIBER_STATUS_SUSPENDED)) { - zend_throw_error(zend_ce_fiber_error, "Cannot resume a fiber that is not suspended"); - RETURN_THROWS(); - } - - if (value) { - ZVAL_COPY(&fiber->value, value); - } else { - ZVAL_NULL(&fiber->value); - } - - fiber->status = ZEND_FIBER_STATUS_RUNNING; - fiber->stack_bottom->prev_execute_data = EG(current_execute_data); + zend_fiber *fiber = (zend_fiber *) Z_OBJ_P(getThis()); + zend_fcall_info *fci = &fiber->fci; - zend_fiber_switch_to(fiber); + ZEND_PARSE_PARAMETERS_START(0, -1) + Z_PARAM_VARIADIC_WITH_NAMED(fci->params, fci->param_count, fci->named_params); + ZEND_PARSE_PARAMETERS_END(); - if (fiber->status & ZEND_FIBER_STATUS_FINISHED) { - RETURN_NULL(); + if (!zend_fiber_start(fiber, NULL, return_value)) { + RETURN_THROWS(); } - - RETVAL_COPY_VALUE(&fiber->value); - ZVAL_UNDEF(&fiber->value); } ZEND_METHOD(Fiber, resume) { zend_fiber *fiber; - zval *value = NULL; + zval *data = NULL; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL - Z_PARAM_ZVAL(value); + Z_PARAM_ZVAL(data); ZEND_PARSE_PARAMETERS_END(); fiber = (zend_fiber *) Z_OBJ_P(getThis()); - zend_fiber_resume(fiber, value, return_value); + zend_fiber_resume(fiber, data, return_value); } -ZEND_API void zend_fiber_throw(zend_fiber *fiber, zval *exception, zval *return_value) +ZEND_METHOD(Fiber, suspend) { - if (UNEXPECTED(fiber->status != ZEND_FIBER_STATUS_SUSPENDED)) { - zend_throw_error(zend_ce_fiber_error, "Cannot resume a fiber that is not suspended"); - RETURN_THROWS(); - } - - Z_ADDREF_P(exception); - fiber->exception = exception; - - fiber->status = ZEND_FIBER_STATUS_RUNNING; - fiber->stack_bottom->prev_execute_data = EG(current_execute_data); - - zend_fiber_switch_to(fiber); + zval *data = NULL; - if (fiber->status & ZEND_FIBER_STATUS_FINISHED) { - RETURN_NULL(); - } + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_ZVAL(data); + ZEND_PARSE_PARAMETERS_END(); - RETVAL_COPY_VALUE(&fiber->value); - ZVAL_UNDEF(&fiber->value); + zend_fiber_suspend(data, return_value); } ZEND_METHOD(Fiber, throw) { zend_fiber *fiber; - zval *exception; + zend_object *exception; ZEND_PARSE_PARAMETERS_START(1, 1) - Z_PARAM_OBJECT_OF_CLASS(exception, zend_ce_throwable) + Z_PARAM_OBJ_OF_CLASS(exception, zend_ce_throwable) ZEND_PARSE_PARAMETERS_END(); fiber = (zend_fiber *) Z_OBJ_P(getThis()); - zend_fiber_throw(fiber, exception, return_value); + if (!zend_fiber_throw(fiber, exception, return_value)) { + RETURN_THROWS(); + } } ZEND_METHOD(Fiber, isStarted) @@ -698,48 +585,44 @@ ZEND_METHOD(Fiber, isTerminated) fiber = (zend_fiber *) Z_OBJ_P(getThis()); - RETURN_BOOL(fiber->status & ZEND_FIBER_STATUS_FINISHED); + RETURN_BOOL(fiber->status == ZEND_FIBER_STATUS_DEAD); } ZEND_METHOD(Fiber, getReturn) { zend_fiber *fiber; + const char *error = NULL; ZEND_PARSE_PARAMETERS_NONE(); fiber = (zend_fiber *) Z_OBJ_P(getThis()); - if (fiber->status != ZEND_FIBER_STATUS_RETURNED) { - const char *message; - + if (fiber->status != ZEND_FIBER_STATUS_DEAD) { if (fiber->status == ZEND_FIBER_STATUS_INIT) { - message = "The fiber has not been started"; - } else if (fiber->status == ZEND_FIBER_STATUS_THREW) { - message = "The fiber threw an exception"; + error = "The fiber has not been started"; } else { - message = "The fiber has not returned"; + error = "The fiber has not returned"; } - - zend_throw_error(zend_ce_fiber_error, "Cannot get fiber return value: %s", message); + } else { + if (fiber->flags & ZEND_FIBER_FLAG_BAILOUT) { + error = "The fiber exited with fatal error"; + } else if (fiber->flags & ZEND_FIBER_FLAG_THREW) { + error = "The fiber threw an exception"; + } + } + if (error != NULL) { + zend_throw_error(zend_ce_fiber_error, "Cannot get fiber return value: %s", error); RETURN_THROWS(); } - RETURN_COPY(&fiber->value); + RETURN_COPY(&fiber->retval); } ZEND_METHOD(Fiber, this) { - zend_fiber *fiber; - ZEND_PARSE_PARAMETERS_NONE(); - fiber = EG(current_fiber); - - if (!fiber) { - RETURN_NULL(); - } - - RETURN_OBJ_COPY(&fiber->std); + RETURN_OBJ_COPY(&EG(current_fiber)->std); } ZEND_METHOD(FiberError, __construct) @@ -751,7 +634,6 @@ ZEND_METHOD(FiberError, __construct) ); } - void zend_register_fiber_ce(void) { zend_ce_fiber = register_class_Fiber(); @@ -768,7 +650,52 @@ void zend_register_fiber_ce(void) zend_ce_fiber_error->create_object = zend_ce_error->create_object; } +static zend_fiber *zend_fiber_main_create(void) +{ + zend_fiber *fiber = (zend_fiber *) EG(vm_stack_top); + memset(fiber, 0, sizeof(*fiber) - sizeof(fiber->std)); + fiber->status = ZEND_FIBER_STATUS_RUNNING; + fiber->executor = (zend_fiber_executor *) (fiber + 1); + + zend_object *object = &fiber->std; + GC_SET_REFCOUNT(object, 1); + GC_TYPE_INFO(object) = GC_OBJECT; + object->ce = zend_ce_fiber; + object->properties = NULL; + object->handlers = &zend_fiber_handlers; + + EG(vm_stack_top) += ZEND_VM_STACK_MAIN_FIBER_SLOTS; + EG(main_fiber) = fiber; + + return fiber; +} + +static void zend_fiber_main_free(zend_fiber *fiber) +{ + zend_object *object = &fiber->std; + + ZEND_ASSERT(fiber == EG(main_fiber)); + ZEND_ASSERT(GC_REFCOUNT(object) == 1); + + GC_DELREF(object); + + /* may be created by get_properties() */ + if (object->properties && GC_DELREF(object->properties) == 0) { + zend_array_destroy(object->properties); + } + if (UNEXPECTED(GC_FLAGS(object) & IS_OBJ_WEAKLY_REFERENCED)) { + zend_weakrefs_notify(object); + } +} + void zend_fiber_init(void) { + EG(current_fiber) = zend_fiber_main_create(); + EG(fiber_exception) = NULL; +} + +void zend_fiber_shutdown(void) +{ + zend_fiber_main_free(EG(current_fiber)); EG(current_fiber) = NULL; } diff --git a/Zend/zend_fibers.h b/Zend/zend_fibers.h index 85ad436305282..86ef875e4010b 100644 --- a/Zend/zend_fibers.h +++ b/Zend/zend_fibers.h @@ -34,85 +34,107 @@ BEGIN_EXTERN_C() #define ZEND_FIBER_GUARD_PAGES 1 -void zend_register_fiber_ce(void); -void zend_fiber_init(void); - extern ZEND_API zend_class_entry *zend_ce_fiber; +typedef struct _zend_fiber zend_fiber; typedef struct _zend_fiber_context zend_fiber_context; - -typedef void (*zend_fiber_coroutine)(zend_fiber_context *context); - -typedef struct _zend_fiber_stack { - void *pointer; - size_t size; - +typedef struct _zend_fiber_executor zend_fiber_executor; + +#define ZEND_FIBER_STATUS_MAP(XX) \ + XX(INIT, 0) \ + XX(RUNNING, 1) \ + XX(SUSPENDED, 2) \ + XX(DEAD, 3) \ + +typedef enum { +#define ZEND_FIBER_STATUS_GEN(name, value) ZEND_FIBER_STATUS_##name = (value), + ZEND_FIBER_STATUS_MAP(ZEND_FIBER_STATUS_GEN) +#undef ZEND_FIBER_STATUS_GEN +} zend_fiber_status; + +#define ZEND_FIBER_FLAG_MAP(XX) \ + XX(THREW, 1 << 0) \ + XX(BAILOUT, 1 << 1) \ + XX(DESTROYED, 1 << 2) \ + +typedef enum { +#define ZEND_FIBER_FLAG_GEN(name, value) ZEND_FIBER_FLAG_##name = (value), + ZEND_FIBER_FLAG_MAP(ZEND_FIBER_FLAG_GEN) +#undef ZEND_FIBER_FLAG_GEN +} zend_fiber_flag; + +struct _zend_fiber_context { + void *ptr; + void *stack; + size_t stack_size; #ifdef HAVE_VALGRIND - int valgrind; + unsigned int valgrind_stack_id; #endif - #ifdef __SANITIZE_ADDRESS__ - const void *prior_pointer; - size_t prior_size; + void *asan_fake_stack; + const void *asan_stack_bottom; + size_t asan_stack_size; #endif -} zend_fiber_stack; - -typedef struct _zend_fiber_context { - void *self; - void *caller; - zend_fiber_coroutine function; - zend_fiber_stack stack; -} zend_fiber_context; - -typedef struct _zend_fiber { +}; + +struct _zend_fiber_executor +{ + JMP_BUF *bailout; + int error_reporting; + int exit_status; + zval *vm_stack_top; + zval *vm_stack_end; + zend_vm_stack vm_stack; + size_t vm_stack_page_size; + zend_execute_data *current_execute_data; + uint32_t jit_trace_num; +}; + +/* unrelated fields on executor_globals may be overwritten + * if the we use aligned size, so we define unaligned size here. */ +#define ZEND_FIBER_EXECUTOR_UNALIGNED_SIZE (XtOffsetOf(zend_fiber_executor, jit_trace_num) + sizeof(uint32_t)) + +ZEND_STATIC_ASSERT(ZEND_FIBER_EXECUTOR_UNALIGNED_SIZE == + XtOffsetOf(zend_executor_globals, jit_trace_num) - + XtOffsetOf(zend_executor_globals, bailout) + sizeof(uint32_t) +); + +struct _zend_fiber { /* Fiber PHP object handle. */ zend_object std; - + /* The one who resumed this fiber */ + zend_fiber *from; + /* Previous one, the target when we suspend. */ + zend_fiber *previous; /* Status of the fiber, one of the ZEND_FIBER_STATUS_* constants. */ zend_uchar status; - + /* Flags of the fiber, collection of the ZEND_FIBER_FLAG_* constants. */ + zend_uchar flags; /* Callback and info / cache to be used when fiber is started. */ zend_fcall_info fci; zend_fcall_info_cache fci_cache; - /* Context of this fiber, will be initialized during call to Fiber::start(). */ zend_fiber_context context; - - /* Current Zend VM execute data being run by the fiber. */ + /* Executor globals. */ + zend_fiber_executor *executor; + /* Root execute data. */ zend_execute_data *execute_data; + /* Storage for fiber return value. */ + zval retval; +}; - /* Frame on the bottom of the fiber vm stack. */ - zend_execute_data *stack_bottom; - - /* Exception to be thrown from Fiber::suspend(). */ - zval *exception; - - /* Storage for temporaries and fiber return value. */ - zval value; -} zend_fiber; - -static const zend_uchar ZEND_FIBER_STATUS_INIT = 0x0; -static const zend_uchar ZEND_FIBER_STATUS_SUSPENDED = 0x1; -static const zend_uchar ZEND_FIBER_STATUS_RUNNING = 0x2; -static const zend_uchar ZEND_FIBER_STATUS_RETURNED = 0x4; -static const zend_uchar ZEND_FIBER_STATUS_THREW = 0x8; -static const zend_uchar ZEND_FIBER_STATUS_SHUTDOWN = 0x10; -static const zend_uchar ZEND_FIBER_STATUS_BAILOUT = 0x20; +void zend_register_fiber_ce(void); -static const zend_uchar ZEND_FIBER_STATUS_FINISHED = 0x2c; +void zend_fiber_init(void); +void zend_fiber_shutdown(void); /* These functions create and manipulate a Fiber object, allowing any internal function to start, resume, or suspend a fiber. */ ZEND_API zend_fiber *zend_fiber_create(const zend_fcall_info *fci, const zend_fcall_info_cache *fci_cache); -ZEND_API void zend_fiber_start(zend_fiber *fiber, zval *params, uint32_t param_count, zend_array *named_params, zval *return_value); -ZEND_API void zend_fiber_suspend(zval *value, zval *return_value); -ZEND_API void zend_fiber_resume(zend_fiber *fiber, zval *value, zval *return_value); -ZEND_API void zend_fiber_throw(zend_fiber *fiber, zval *exception, zval *return_value); - -/* These functions may be used to create custom fibers (coroutines) using the bundled fiber switching context. */ -ZEND_API zend_bool zend_fiber_init_context(zend_fiber_context *context, zend_fiber_coroutine coroutine, size_t stack_size); -ZEND_API void zend_fiber_destroy_context(zend_fiber_context *context); -ZEND_API void zend_fiber_switch_context(zend_fiber_context *to); -ZEND_API void zend_fiber_suspend_context(zend_fiber_context *current); +ZEND_API bool zend_fiber_start(zend_fiber *fiber, zval *data, zval *retval); +ZEND_API void zend_fiber_jump(zend_fiber *fiber, zval *data, zval *retval); +ZEND_API bool zend_fiber_resume(zend_fiber *fiber, zval *data, zval *retval); +ZEND_API bool zend_fiber_suspend(zval *data, zval *retval); +ZEND_API bool zend_fiber_throw(zend_fiber *fiber, zend_object *exception, zval *retval); END_EXTERN_C() diff --git a/Zend/zend_globals.h b/Zend/zend_globals.h index cb627377ae629..0e8ad7023423f 100644 --- a/Zend/zend_globals.h +++ b/Zend/zend_globals.h @@ -155,25 +155,26 @@ struct _zend_executor_globals { HashTable included_files; /* files already included */ + HashTable *function_table; /* function symbol table */ + HashTable *class_table; /* class table */ + HashTable *zend_constants; /* constants table */ + JMP_BUF *bailout; int error_reporting; int exit_status; - HashTable *function_table; /* function symbol table */ - HashTable *class_table; /* class table */ - HashTable *zend_constants; /* constants table */ - zval *vm_stack_top; zval *vm_stack_end; zend_vm_stack vm_stack; size_t vm_stack_page_size; struct _zend_execute_data *current_execute_data; - zend_class_entry *fake_scope; /* used to avoid checks accessing properties */ uint32_t jit_trace_num; /* Used by tracing JIT to reference the currently running trace */ + zend_class_entry *fake_scope; /* used to avoid checks accessing properties */ + zend_long precision; int ticks_count; @@ -250,11 +251,14 @@ struct _zend_executor_globals { zend_get_gc_buffer get_gc_buffer; + /* Main fiber. */ + zend_fiber *main_fiber; /* Active fiber, NULL when in main thread. */ zend_fiber *current_fiber; - /* Default fiber C stack size. */ zend_long fiber_stack_size; + /* Exception thrown from the other fiber */ + zend_object *fiber_exception; /* If record_errors is enabled, all emitted diagnostics will be recorded, * in addition to being processed as usual. */ diff --git a/Zend/zend_portability.h b/Zend/zend_portability.h index d67057899064d..3573ce8c87f1b 100644 --- a/Zend/zend_portability.h +++ b/Zend/zend_portability.h @@ -107,6 +107,8 @@ # define ZEND_ASSERT(c) ZEND_ASSUME(c) #endif +#define ZEND_STATIC_ASSERT(c) void zend_static_assert(int static_assert_failed[1 - 2 * !(c)]) + #if ZEND_DEBUG # define ZEND_UNREACHABLE() do {ZEND_ASSERT(0); ZEND_ASSUME(0);} while (0) #else diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 379fde06b63fa..fa5d95116165d 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -6873,7 +6873,7 @@ ZEND_METHOD(ReflectionFiber, getFiber) } #define REFLECTION_CHECK_VALID_FIBER(fiber) do { \ - if (fiber == NULL || fiber->status == ZEND_FIBER_STATUS_INIT || fiber->status & ZEND_FIBER_STATUS_FINISHED) { \ + if (fiber == NULL || fiber->status == ZEND_FIBER_STATUS_INIT || fiber->status == ZEND_FIBER_STATUS_DEAD) { \ zend_throw_error(NULL, "Cannot fetch information from a fiber that has not been started or is terminated"); \ RETURN_THROWS(); \ } \ @@ -6892,18 +6892,18 @@ ZEND_METHOD(ReflectionFiber, getTrace) REFLECTION_CHECK_VALID_FIBER(fiber); - prev_execute_data = fiber->stack_bottom->prev_execute_data; - fiber->stack_bottom->prev_execute_data = NULL; + prev_execute_data = fiber->execute_data->prev_execute_data; + fiber->execute_data->prev_execute_data = NULL; if (EG(current_fiber) != fiber) { // No need to replace current execute data if within the current fiber. - EG(current_execute_data) = fiber->execute_data; + EG(current_execute_data) = fiber->executor->current_execute_data; } zend_fetch_debug_backtrace(return_value, 0, options, 0); EG(current_execute_data) = execute_data; // Restore original execute data. - fiber->stack_bottom->prev_execute_data = prev_execute_data; // Restore prev execute data on fiber stack. + fiber->execute_data->prev_execute_data = prev_execute_data; // Restore prev execute data on fiber stack. } ZEND_METHOD(ReflectionFiber, getExecutingLine) @@ -6918,7 +6918,7 @@ ZEND_METHOD(ReflectionFiber, getExecutingLine) if (EG(current_fiber) == fiber) { prev_execute_data = execute_data->prev_execute_data; } else { - prev_execute_data = fiber->execute_data->prev_execute_data; + prev_execute_data = fiber->executor->current_execute_data->prev_execute_data; } RETURN_LONG(prev_execute_data->opline->lineno); @@ -6936,7 +6936,7 @@ ZEND_METHOD(ReflectionFiber, getExecutingFile) if (EG(current_fiber) == fiber) { prev_execute_data = execute_data->prev_execute_data; } else { - prev_execute_data = fiber->execute_data->prev_execute_data; + prev_execute_data = fiber->executor->current_execute_data->prev_execute_data; } RETURN_STR_COPY(prev_execute_data->func->op_array.filename); @@ -6948,8 +6948,8 @@ ZEND_METHOD(ReflectionFiber, getCallable) ZEND_PARSE_PARAMETERS_NONE(); - if (fiber == NULL || fiber->status & ZEND_FIBER_STATUS_FINISHED) { - zend_throw_error(NULL, "Cannot fetch the callable from a fiber that has terminated"); \ + if (fiber == NULL || fiber->status == ZEND_FIBER_STATUS_DEAD) { + zend_throw_error(NULL, "Cannot fetch the callable from a fiber that has terminated"); RETURN_THROWS(); } diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c index 54e70a9ab25b3..fcaf8010fb648 100644 --- a/ext/zend_test/test.c +++ b/ext/zend_test/test.c @@ -411,41 +411,29 @@ static void observer_set_user_opcode_handler(const char *opcode_names, user_opco } } -static void fiber_address_observer(zend_fiber *from, zend_fiber *to) +static void fiber_observer(zend_fiber *from, zend_fiber *to) { - if (ZT_G(observer_fiber_switch)) { - php_printf("\n", from, to); + if (!ZT_G(observer_fiber_switch)) { + return; } -} - -static void fiber_enter_observer(zend_fiber *from, zend_fiber *to) -{ - if (ZT_G(observer_fiber_switch)) { - if (to) { - if (to->status == ZEND_FIBER_STATUS_INIT) { - php_printf("\n", to); - } else if (to->status == ZEND_FIBER_STATUS_RUNNING && (!from || from->status == ZEND_FIBER_STATUS_RUNNING)) { - php_printf("\n", to); - } else if (to->status == ZEND_FIBER_STATUS_SHUTDOWN) { - php_printf("\n", to); - } + php_printf("\n", from != EG(main_fiber) ? from : 0, to != EG(main_fiber) ? to : 0); + if (to->status == ZEND_FIBER_STATUS_INIT) { + php_printf("\n", to); + } else if (to->status == ZEND_FIBER_STATUS_SUSPENDED) { + if (!(to->flags & ZEND_FIBER_FLAG_DESTROYED)) { + php_printf("\n", to); + } else { + php_printf("\n", to); } - } -} - -static void fiber_suspend_observer(zend_fiber *from, zend_fiber *to) -{ - if (ZT_G(observer_fiber_switch)) { - if (from) { - if (from->status == ZEND_FIBER_STATUS_SUSPENDED) { - php_printf("\n", from); - } else if (from->status == ZEND_FIBER_STATUS_RETURNED) { - php_printf("\n", from); - } else if (from->status == ZEND_FIBER_STATUS_THREW) { - php_printf("\n", from); - } else if (from->status == ZEND_FIBER_STATUS_SHUTDOWN) { - php_printf("\n", from); - } + } else if (to->status == ZEND_FIBER_STATUS_RUNNING) { + if (from->status != ZEND_FIBER_STATUS_DEAD) { + php_printf("\n", from); + } else if (from->flags & ZEND_FIBER_FLAG_THREW) { + php_printf("\n", from); + } else if (from->flags & ZEND_FIBER_FLAG_DESTROYED) { + php_printf("\n", from); + } else { + php_printf("\n", from); } } } @@ -498,9 +486,7 @@ PHP_MINIT_FUNCTION(zend_test) } if (ZT_G(observer_enabled)) { - zend_observer_fiber_switch_register(fiber_address_observer); - zend_observer_fiber_switch_register(fiber_enter_observer); - zend_observer_fiber_switch_register(fiber_suspend_observer); + zend_observer_fiber_switch_register(fiber_observer); } return SUCCESS; diff --git a/ext/zend_test/tests/observer_fiber_05.phpt b/ext/zend_test/tests/observer_fiber_05.phpt index 574efb36aff1d..9cb0ab0254f78 100644 --- a/ext/zend_test/tests/observer_fiber_05.phpt +++ b/ext/zend_test/tests/observer_fiber_05.phpt @@ -44,9 +44,7 @@ $fiber->resume(); - - From b5bfdfdb78f775c8701744e57b8b4b797fe9eff7 Mon Sep 17 00:00:00 2001 From: twosee Date: Wed, 2 Jun 2021 11:49:22 +0800 Subject: [PATCH 3/3] Fix BC break at the user level --- Zend/tests/fibers/fiber-this.phpt | 3 +-- Zend/zend_fibers.c | 5 +++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Zend/tests/fibers/fiber-this.phpt b/Zend/tests/fibers/fiber-this.phpt index d5d585a23ebbf..714a848f0aad0 100644 --- a/Zend/tests/fibers/fiber-this.phpt +++ b/Zend/tests/fibers/fiber-this.phpt @@ -13,7 +13,6 @@ $fiber->start(); ?> --EXPECTF-- -object(Fiber)#%d (0) { -} +NULL object(Fiber)#%d (0) { } diff --git a/Zend/zend_fibers.c b/Zend/zend_fibers.c index 0856cd315c4ef..c5394b35f5f45 100644 --- a/Zend/zend_fibers.c +++ b/Zend/zend_fibers.c @@ -622,6 +622,11 @@ ZEND_METHOD(Fiber, this) { ZEND_PARSE_PARAMETERS_NONE(); + /* Follow RFC for now */ + if (EG(current_fiber) == EG(main_fiber)) { + RETURN_NULL(); + } + RETURN_OBJ_COPY(&EG(current_fiber)->std); }