diff --git a/TSRM/TSRM.c b/TSRM/TSRM.c index e99993204b6f9..6c22cb608260d 100644 --- a/TSRM/TSRM.c +++ b/TSRM/TSRM.c @@ -12,6 +12,172 @@ #include "TSRM.h" + +#ifdef TSRM_DEBUG +#define TSRM_ERROR(args) tsrm_error args +#define TSRM_SAFE_RETURN_RSRC(array, offset, range) \ + { \ + int unshuffled_offset = TSRM_UNSHUFFLE_RSRC_ID(offset); \ + \ + if (offset==0) { \ + return &array; \ + } else if ((unshuffled_offset)>=0 && (unshuffled_offset)<(range)) { \ + TSRM_ERROR((TSRM_ERROR_LEVEL_INFO, "Successfully fetched resource id %d for thread id %ld - 0x%0.8X", \ + unshuffled_offset, (long) thread_resources->thread_id, array[unshuffled_offset])); \ + return array[unshuffled_offset]; \ + } else { \ + TSRM_ERROR((TSRM_ERROR_LEVEL_ERROR, "Resource id %d is out of range (%d..%d)", \ + unshuffled_offset, TSRM_SHUFFLE_RSRC_ID(0), TSRM_SHUFFLE_RSRC_ID(thread_resources->count-1))); \ + return NULL; \ + } \ + } +#else +#define TSRM_ERROR(args) +#define TSRM_SAFE_RETURN_RSRC(array, offset, range) \ + if (offset==0) { \ + return &array; \ + } else { \ + return array[TSRM_UNSHUFFLE_RSRC_ID(offset)]; \ + } +#endif + + +/* + * Utility Functions + */ + +/* Obtain the current thread id */ +TSRM_API THREAD_T tsrm_thread_id(void) +{/*{{{*/ +#ifdef TSRM_WIN32 + return GetCurrentThreadId(); +#else + return pthread_self(); +#endif +}/*}}}*/ + +TSRM_API COND_T tsrm_cond_alloc(void) +{ + COND_T condp; +#ifdef TSRM_WIN32 + condp = (PCONDITION_VARIABLE)malloc(sizeof(CONDITION_VARIABLE)); + InitializeConditionVariable(condp); +#else + condp = (pthread_cond_t *)malloc(sizeof(pthread_cond_t)); + pthread_cond_init(condp, NULL); +#endif + return( condp ); +} + +TSRM_API int tsrm_cond_wait(COND_T condp, MUTEX_T mutexp) +{ +#ifdef TSRM_WIN32 + return SleepConditionVariableCS(condp, mutexp, INFINITE) ? 0 : -1; +#else + return pthread_cond_wait(condp, mutexp); +#endif +} + +TSRM_API int tsrm_cond_broadcast(COND_T condp) +{ +#ifdef TSRM_WIN32 + WakeAllConditionVariable(condp); + return 0; +#else + return pthread_cond_broadcast(condp); +#endif +} + +/* Allocate a mutex */ +TSRM_API MUTEX_T tsrm_mutex_alloc(void) +{/*{{{*/ + MUTEX_T mutexp; +#ifdef TSRM_WIN32 + mutexp = malloc(sizeof(CRITICAL_SECTION)); + InitializeCriticalSection(mutexp); +#else + mutexp = (pthread_mutex_t *)malloc(sizeof(pthread_mutex_t)); + pthread_mutex_init(mutexp,NULL); +#endif +#ifdef THR_DEBUG + printf("Mutex created thread: %d\n",mythreadid()); +#endif + return( mutexp ); +}/*}}}*/ + + +/* Free a mutex */ +TSRM_API void tsrm_mutex_free(MUTEX_T mutexp) +{/*{{{*/ + if (mutexp) { +#ifdef TSRM_WIN32 + DeleteCriticalSection(mutexp); + free(mutexp); +#else + pthread_mutex_destroy(mutexp); + free(mutexp); +#endif + } +#ifdef THR_DEBUG + printf("Mutex freed thread: %d\n",mythreadid()); +#endif +}/*}}}*/ + +TSRM_API void tsrm_cond_free(COND_T condp) +{ +#ifdef TSRM_WIN32 + free(condp); +#else + if(condp){ + pthread_cond_destroy(condp); + free(condp); + } +#endif +} + +/* + Lock a mutex. + A return value of 0 indicates success +*/ +TSRM_API int tsrm_mutex_lock(MUTEX_T mutexp) +{/*{{{*/ + TSRM_ERROR((TSRM_ERROR_LEVEL_INFO, "Mutex locked thread: %ld", tsrm_thread_id())); +#ifdef TSRM_WIN32 + EnterCriticalSection(mutexp); + return 0; +#else + return pthread_mutex_lock(mutexp); +#endif +}/*}}}*/ + + +/* + Unlock a mutex. + A return value of 0 indicates success +*/ +TSRM_API int tsrm_mutex_unlock(MUTEX_T mutexp) +{/*{{{*/ + TSRM_ERROR((TSRM_ERROR_LEVEL_INFO, "Mutex unlocked thread: %ld", tsrm_thread_id())); +#ifdef TSRM_WIN32 + LeaveCriticalSection(mutexp); + return 0; +#else + return pthread_mutex_unlock(mutexp); +#endif +}/*}}}*/ + +/* + Changes the signal mask of the calling thread +*/ +#ifdef HAVE_SIGPROCMASK +TSRM_API int tsrm_sigmask(int how, const sigset_t *set, sigset_t *oldset) +{/*{{{*/ + TSRM_ERROR((TSRM_ERROR_LEVEL_INFO, "Changed sigmask in thread: %ld", tsrm_thread_id())); + + return pthread_sigmask(how, set, oldset); +}/*}}}*/ +#endif + #ifdef ZTS #include @@ -74,33 +240,6 @@ int tsrm_error(int level, const char *format, ...); static int tsrm_error_level; static FILE *tsrm_error_file; -#ifdef TSRM_DEBUG -#define TSRM_ERROR(args) tsrm_error args -#define TSRM_SAFE_RETURN_RSRC(array, offset, range) \ - { \ - int unshuffled_offset = TSRM_UNSHUFFLE_RSRC_ID(offset); \ - \ - if (offset==0) { \ - return &array; \ - } else if ((unshuffled_offset)>=0 && (unshuffled_offset)<(range)) { \ - TSRM_ERROR((TSRM_ERROR_LEVEL_INFO, "Successfully fetched resource id %d for thread id %ld - 0x%0.8X", \ - unshuffled_offset, (long) thread_resources->thread_id, array[unshuffled_offset])); \ - return array[unshuffled_offset]; \ - } else { \ - TSRM_ERROR((TSRM_ERROR_LEVEL_ERROR, "Resource id %d is out of range (%d..%d)", \ - unshuffled_offset, TSRM_SHUFFLE_RSRC_ID(0), TSRM_SHUFFLE_RSRC_ID(thread_resources->count-1))); \ - return NULL; \ - } \ - } -#else -#define TSRM_ERROR(args) -#define TSRM_SAFE_RETURN_RSRC(array, offset, range) \ - if (offset==0) { \ - return &array; \ - } else { \ - return array[TSRM_UNSHUFFLE_RSRC_ID(offset)]; \ - } -#endif #ifdef TSRM_WIN32 static DWORD tls_key; @@ -598,101 +737,6 @@ TSRM_API void ts_apply_for_id(ts_rsrc_id id, void (*cb)(void *)) tsrm_mutex_unlock(tsmm_mutex); } -/* - * Utility Functions - */ - -/* Obtain the current thread id */ -TSRM_API THREAD_T tsrm_thread_id(void) -{/*{{{*/ -#ifdef TSRM_WIN32 - return GetCurrentThreadId(); -#else - return pthread_self(); -#endif -}/*}}}*/ - - -/* Allocate a mutex */ -TSRM_API MUTEX_T tsrm_mutex_alloc(void) -{/*{{{*/ - MUTEX_T mutexp; -#ifdef TSRM_WIN32 - mutexp = malloc(sizeof(CRITICAL_SECTION)); - InitializeCriticalSection(mutexp); -#else - mutexp = (pthread_mutex_t *)malloc(sizeof(pthread_mutex_t)); - pthread_mutex_init(mutexp,NULL); -#endif -#ifdef THR_DEBUG - printf("Mutex created thread: %d\n",mythreadid()); -#endif - return( mutexp ); -}/*}}}*/ - - -/* Free a mutex */ -TSRM_API void tsrm_mutex_free(MUTEX_T mutexp) -{/*{{{*/ - if (mutexp) { -#ifdef TSRM_WIN32 - DeleteCriticalSection(mutexp); - free(mutexp); -#else - pthread_mutex_destroy(mutexp); - free(mutexp); -#endif - } -#ifdef THR_DEBUG - printf("Mutex freed thread: %d\n",mythreadid()); -#endif -}/*}}}*/ - - -/* - Lock a mutex. - A return value of 0 indicates success -*/ -TSRM_API int tsrm_mutex_lock(MUTEX_T mutexp) -{/*{{{*/ - TSRM_ERROR((TSRM_ERROR_LEVEL_INFO, "Mutex locked thread: %ld", tsrm_thread_id())); -#ifdef TSRM_WIN32 - EnterCriticalSection(mutexp); - return 0; -#else - return pthread_mutex_lock(mutexp); -#endif -}/*}}}*/ - - -/* - Unlock a mutex. - A return value of 0 indicates success -*/ -TSRM_API int tsrm_mutex_unlock(MUTEX_T mutexp) -{/*{{{*/ - TSRM_ERROR((TSRM_ERROR_LEVEL_INFO, "Mutex unlocked thread: %ld", tsrm_thread_id())); -#ifdef TSRM_WIN32 - LeaveCriticalSection(mutexp); - return 0; -#else - return pthread_mutex_unlock(mutexp); -#endif -}/*}}}*/ - -/* - Changes the signal mask of the calling thread -*/ -#ifdef HAVE_SIGPROCMASK -TSRM_API int tsrm_sigmask(int how, const sigset_t *set, sigset_t *oldset) -{/*{{{*/ - TSRM_ERROR((TSRM_ERROR_LEVEL_INFO, "Changed sigmask in thread: %ld", tsrm_thread_id())); - - return pthread_sigmask(how, set, oldset); -}/*}}}*/ -#endif - - TSRM_API void *tsrm_set_new_thread_begin_handler(tsrm_thread_begin_func_t new_thread_begin_handler) {/*{{{*/ void *retval = (void *) tsrm_new_thread_begin_handler; diff --git a/TSRM/TSRM.h b/TSRM/TSRM.h index 80d6cbad04435..6a59717e961a8 100644 --- a/TSRM/TSRM.h +++ b/TSRM/TSRM.h @@ -23,6 +23,14 @@ #include #include +#ifdef TSRM_WIN32 +# ifndef TSRM_INCLUDE_FULL_WINDOWS_HEADERS +# define WIN32_LEAN_AND_MEAN +# endif +#else +# include +#endif + #ifdef TSRM_WIN32 # ifdef TSRM_EXPORTS # define TSRM_API __declspec(dllexport) @@ -38,17 +46,42 @@ typedef intptr_t tsrm_intptr_t; typedef uintptr_t tsrm_uintptr_t; -/* Only compile multi-threading functions if we're in ZTS mode */ -#ifdef ZTS - +/* Define THREAD_T and MUTEX_T */ #ifdef TSRM_WIN32 -# ifndef TSRM_INCLUDE_FULL_WINDOWS_HEADERS -# define WIN32_LEAN_AND_MEAN -# endif +# define THREAD_T DWORD +# define MUTEX_T CRITICAL_SECTION * +# define COND_T PCONDITION_VARIABLE #else -# include +# define THREAD_T pthread_t +# define MUTEX_T pthread_mutex_t * +# define COND_T pthread_cond_t * #endif +#include + +/* Debug support */ +#define TSRM_ERROR_LEVEL_ERROR 1 +#define TSRM_ERROR_LEVEL_CORE 2 +#define TSRM_ERROR_LEVEL_INFO 3 + +/* utility functions */ +TSRM_API THREAD_T tsrm_thread_id(void); +TSRM_API MUTEX_T tsrm_mutex_alloc(void); +TSRM_API COND_T tsrm_cond_alloc(void); +TSRM_API void tsrm_mutex_free(MUTEX_T mutexp); +TSRM_API int tsrm_mutex_lock(MUTEX_T mutexp); +TSRM_API int tsrm_mutex_unlock(MUTEX_T mutexp); +TSRM_API int tsrm_cond_wait(COND_T condp, MUTEX_T mutexp); +TSRM_API int tsrm_cond_broadcast(COND_T condp); +TSRM_API void tsrm_cond_free(COND_T condp); + +#ifdef HAVE_SIGPROCMASK +TSRM_API int tsrm_sigmask(int how, const sigset_t *set, sigset_t *oldset); +#endif + +/* Only compile multi-threading functions if we're in ZTS mode */ +#ifdef ZTS + #if SIZEOF_SIZE_T == 4 # define TSRM_ALIGNED_SIZE(size) \ (((size) + INT32_C(15)) & ~INT32_C(15)) @@ -59,16 +92,7 @@ typedef uintptr_t tsrm_uintptr_t; typedef int ts_rsrc_id; -/* Define THREAD_T and MUTEX_T */ -#ifdef TSRM_WIN32 -# define THREAD_T DWORD -# define MUTEX_T CRITICAL_SECTION * -#else -# define THREAD_T pthread_t -# define MUTEX_T pthread_mutex_t * -#endif -#include typedef void (*ts_allocate_ctor)(void *); typedef void (*ts_allocate_dtor)(void *); @@ -108,11 +132,6 @@ TSRM_API void ts_free_id(ts_rsrc_id id); * The caller is responsible for ensuring the underlying resources don't data-race. */ TSRM_API void ts_apply_for_id(ts_rsrc_id id, void (*cb)(void *)); -/* Debug support */ -#define TSRM_ERROR_LEVEL_ERROR 1 -#define TSRM_ERROR_LEVEL_CORE 2 -#define TSRM_ERROR_LEVEL_INFO 3 - typedef void (*tsrm_thread_begin_func_t)(THREAD_T thread_id); typedef void (*tsrm_thread_end_func_t)(THREAD_T thread_id); typedef void (*tsrm_shutdown_func_t)(void); @@ -121,16 +140,6 @@ typedef void (*tsrm_shutdown_func_t)(void); TSRM_API int tsrm_error(int level, const char *format, ...); TSRM_API void tsrm_error_set(int level, const char *debug_filename); -/* utility functions */ -TSRM_API THREAD_T tsrm_thread_id(void); -TSRM_API MUTEX_T tsrm_mutex_alloc(void); -TSRM_API void tsrm_mutex_free(MUTEX_T mutexp); -TSRM_API int tsrm_mutex_lock(MUTEX_T mutexp); -TSRM_API int tsrm_mutex_unlock(MUTEX_T mutexp); -#ifdef HAVE_SIGPROCMASK -TSRM_API int tsrm_sigmask(int how, const sigset_t *set, sigset_t *oldset); -#endif - TSRM_API void *tsrm_set_new_thread_begin_handler(tsrm_thread_begin_func_t new_thread_begin_handler); TSRM_API void *tsrm_set_new_thread_end_handler(tsrm_thread_end_func_t new_thread_end_handler); TSRM_API void *tsrm_set_shutdown_handler(tsrm_shutdown_func_t shutdown_handler); diff --git a/Zend/zend_fibers.c b/Zend/zend_fibers.c index 97b7cdcc911b7..826cce139e41d 100644 --- a/Zend/zend_fibers.c +++ b/Zend/zend_fibers.c @@ -559,7 +559,7 @@ static void zend_fiber_cleanup(zend_fiber_context *context) fiber->caller = NULL; } -static ZEND_STACK_ALIGNED void zend_fiber_execute(zend_fiber_transfer *transfer) +ZEND_STACK_ALIGNED void zend_fiber_execute(zend_fiber_transfer *transfer) { ZEND_ASSERT(Z_TYPE(transfer->value) == IS_NULL && "Initial transfer value to fiber context must be NULL"); ZEND_ASSERT(!transfer->flags && "No flags should be set on initial transfer"); @@ -631,7 +631,7 @@ static ZEND_STACK_ALIGNED void zend_fiber_execute(zend_fiber_transfer *transfer) } /* Handles forwarding of result / error from a transfer into the running fiber. */ -static zend_always_inline void zend_fiber_delegate_transfer_result( +zend_always_inline void zend_fiber_delegate_transfer_result( zend_fiber_transfer *transfer, INTERNAL_FUNCTION_PARAMETERS ) { if (transfer->flags & ZEND_FIBER_TRANSFER_FLAG_ERROR) { @@ -647,7 +647,7 @@ static zend_always_inline void zend_fiber_delegate_transfer_result( } } -static zend_always_inline zend_fiber_transfer zend_fiber_switch_to( +zend_always_inline zend_fiber_transfer zend_fiber_switch_to( zend_fiber_context *context, zval *value, bool exception ) { zend_fiber_transfer transfer = { @@ -672,7 +672,7 @@ static zend_always_inline zend_fiber_transfer zend_fiber_switch_to( return transfer; } -static zend_always_inline zend_fiber_transfer zend_fiber_resume_internal(zend_fiber *fiber, zval *value, bool exception) +zend_fiber_transfer zend_fiber_resume_internal(zend_fiber *fiber, zval *value, bool exception) { zend_fiber *previous = EG(active_fiber); diff --git a/Zend/zend_fibers.h b/Zend/zend_fibers.h index 9442019bfa259..eacd88f406db3 100644 --- a/Zend/zend_fibers.h +++ b/Zend/zend_fibers.h @@ -138,8 +138,12 @@ ZEND_API void zend_fiber_suspend(zend_fiber *fiber, zval *value, zval *return_va /* These functions may be used to create custom fiber objects using the bundled fiber switching context. */ ZEND_API zend_result zend_fiber_init_context(zend_fiber_context *context, void *kind, zend_fiber_coroutine coroutine, size_t stack_size); +ZEND_STACK_ALIGNED void zend_fiber_execute(zend_fiber_transfer *transfer); ZEND_API void zend_fiber_destroy_context(zend_fiber_context *context); ZEND_API void zend_fiber_switch_context(zend_fiber_transfer *transfer); +ZEND_API zend_fiber_transfer zend_fiber_resume_internal(zend_fiber *fiber, zval *value, bool exception); +ZEND_API void zend_fiber_delegate_transfer_result(zend_fiber_transfer *transfer, INTERNAL_FUNCTION_PARAMETERS); +ZEND_API zend_fiber_transfer zend_fiber_switch_to(zend_fiber_context *context, zval *value, bool exception); #ifdef ZEND_CHECK_STACK_LIMIT ZEND_API void* zend_fiber_stack_limit(zend_fiber_stack *stack); ZEND_API void* zend_fiber_stack_base(zend_fiber_stack *stack); diff --git a/ext/ffi/ffi.c b/ext/ffi/ffi.c index 621e60c6f19a3..284fb900c8190 100644 --- a/ext/ffi/ffi.c +++ b/ext/ffi/ffi.c @@ -14,6 +14,8 @@ +----------------------------------------------------------------------+ */ +#include "zend_API.h" +#include "zend_globals.h" #ifdef HAVE_CONFIG_H # include "config.h" #endif @@ -26,9 +28,11 @@ #include "zend_closures.h" #include "zend_weakrefs.h" #include "main/SAPI.h" +#include "TSRM.h" #include "zend_observer.h" -#include +#include "zend_fibers.h" +#include "zend_call_stack.h" #include #include @@ -228,6 +232,10 @@ static ZEND_COLD void zend_ffi_return_unsupported(zend_ffi_type *type); static ZEND_COLD void zend_ffi_pass_unsupported(zend_ffi_type *type); static ZEND_COLD void zend_ffi_assign_incompatible(const zval *arg, const zend_ffi_type *type); static bool zend_ffi_is_compatible_type(const zend_ffi_type *dst_type, const zend_ffi_type *src_type); +static void zend_ffi_wait_cond( + MUTEX_T mutexp, COND_T condp, + zend_atomic_bool *flag, bool wanted_value, bool release +); #if FFI_CLOSURES static void *zend_ffi_create_callback(zend_ffi_type *type, zval *value); @@ -910,14 +918,17 @@ static zend_always_inline zend_string *zend_ffi_mangled_func_name(zend_string *n /* }}} */ #if FFI_CLOSURES + typedef struct _zend_ffi_callback_data { + zend_fiber fiber; zend_fcall_info_cache fcc; zend_ffi_type *type; void *code; void *callback; ffi_cif cif; - uint32_t arg_count; + zend_ffi_call_data ffi_args; ffi_type *ret_type; + uint32_t arg_count; ffi_type *arg_types[] ZEND_ELEMENT_COUNT(arg_count); } zend_ffi_callback_data; @@ -929,9 +940,10 @@ static void zend_ffi_callback_hash_dtor(zval *zv) /* {{{ */ if (callback_data->fcc.function_handler->common.fn_flags & ZEND_ACC_CLOSURE) { OBJ_RELEASE(ZEND_CLOSURE_OBJECT(callback_data->fcc.function_handler)); } + ffi_type **arg_types = callback_data->arg_types; for (uint32_t i = 0; i < callback_data->arg_count; ++i) { - if (callback_data->arg_types[i]->type == FFI_TYPE_STRUCT) { - efree(callback_data->arg_types[i]); + if (arg_types[i]->type == FFI_TYPE_STRUCT) { + efree(arg_types[i]); } } if (callback_data->ret_type->type == FFI_TYPE_STRUCT) { @@ -941,76 +953,180 @@ static void zend_ffi_callback_hash_dtor(zval *zv) /* {{{ */ } /* }}} */ -static void zend_ffi_callback_trampoline(ffi_cif* cif, void* ret, void** args, void* data) /* {{{ */ -{ - zend_ffi_callback_data *callback_data = (zend_ffi_callback_data*)data; - zend_fcall_info fci; - zend_ffi_type *ret_type; - zval retval; - ALLOCA_FLAG(use_heap) - - fci.size = sizeof(zend_fcall_info); - ZVAL_UNDEF(&fci.function_name); - fci.retval = &retval; - fci.params = do_alloca(sizeof(zval) *callback_data->arg_count, use_heap); - fci.object = NULL; - fci.param_count = callback_data->arg_count; - fci.named_params = NULL; - - if (callback_data->type->func.args) { - int n = 0; - zend_ffi_type *arg_type; +static void (*orig_interrupt_function)(zend_execute_data *execute_data); - ZEND_HASH_PACKED_FOREACH_PTR(callback_data->type->func.args, arg_type) { - arg_type = ZEND_FFI_TYPE(arg_type); - zend_ffi_cdata_to_zval(NULL, args[n], arg_type, BP_VAR_R, &fci.params[n], (zend_ffi_flags)(arg_type->attr & ZEND_FFI_ATTR_CONST), 0, 0); - n++; - } ZEND_HASH_FOREACH_END(); +static void zend_ffi_dispatch_callback_end(void){ /* {{{ */ + zend_atomic_bool_store_ex(&FFI_G(callback_in_progress), false); + bool is_main_thread = FFI_G(callback_tid) == FFI_G(main_tid); + if(!is_main_thread){ + // unlock interrupt handler + tsrm_cond_broadcast(FFI_G(vm_unlock)); + tsrm_mutex_unlock(FFI_G(vm_request_lock)); + } +} +/* }}} */ + +static void zend_ffi_fci_prepare( + zend_ffi_callback_data *data, + zend_fcall_info *fci +){ + fci->size = sizeof(*fci); + ZVAL_UNDEF(&fci->function_name); + fci->retval = &data->fiber.result; + fci->params = emalloc(sizeof(zval) * data->arg_count); + fci->object = NULL; + fci->param_count = data->arg_count; + fci->named_params = NULL; +} + +static void zend_ffi_interrupt_function(zend_execute_data *execute_data){ /* {{{ */ + if(FFI_G(callback_tid) == FFI_G(main_tid)){ + goto end; } - ZVAL_UNDEF(&retval); - if (zend_call_function(&fci, &callback_data->fcc) != SUCCESS) { - zend_throw_error(zend_ffi_exception_ce, "Cannot call callback"); + tsrm_mutex_lock(FFI_G(vm_request_lock)); + if (!zend_atomic_bool_load_ex(&FFI_G(callback_in_progress))) { + goto end; } - if (callback_data->arg_count) { - for (uint32_t n = 0; n < callback_data->arg_count; n++) { - zval_ptr_dtor(&fci.params[n]); + bool is_main_tid = FFI_G(callback_tid) == FFI_G(main_tid); + if(!is_main_tid){ + + // notify calling thread and release + tsrm_cond_broadcast(FFI_G(vm_ack)); + + // release mutex and wait for the unlock signal + tsrm_cond_wait(FFI_G(vm_unlock), FFI_G(vm_request_lock)); + } + + end: + tsrm_mutex_unlock(FFI_G(vm_request_lock)); + if (orig_interrupt_function) { + orig_interrupt_function(execute_data); + } +} +/* }}} */ + +static void zend_ffi_wait_cond( + MUTEX_T mutexp, COND_T condp, + zend_atomic_bool *flag, bool wanted_value, bool release +){ /* {{{ */ + // get lock, first + tsrm_mutex_lock(mutexp); + + // if we acquired the lock before the request could be serviced + // unlock it and wait for the flag + if(flag == NULL){ + tsrm_cond_wait(condp, mutexp); + } else { + while(zend_atomic_bool_load_ex(flag) != wanted_value){ + tsrm_cond_wait(condp, mutexp); } } - free_alloca(fci.params, use_heap); - if (EG(exception)) { - zend_error_noreturn(E_ERROR, "Throwing from FFI callbacks is not allowed"); + if(release){ + tsrm_mutex_unlock(mutexp); } +} +/* }}} */ + +static void zend_ffi_wait_request_barrier(bool release){ /* {{{ */ + zend_ffi_wait_cond(FFI_G(vm_request_lock), FFI_G(vm_unlock), &FFI_G(callback_in_progress), false, release); +} +/* }}} */ + +static void zend_ffi_callback_trampoline(ffi_cif* cif, void* ret, void** args, void* data) /* {{{ */ +{ + // wait for a previously initiated request to complete + zend_ffi_wait_request_barrier(false); + { + // mutex is now locked, and request is not pending. + // start a new one + zend_atomic_bool_store_ex(&FFI_G(callback_in_progress), true); + + zend_ffi_callback_data *callback_data = (zend_ffi_callback_data *)data; + zend_ffi_call_data call_data = { + .cif = cif, + .ret = ret, + .args = args, + .data = callback_data + }; + callback_data->ffi_args = call_data; + + FFI_G(callback_data) = call_data; + + FFI_G(callback_tid) = tsrm_thread_id(); + bool is_main_thread = FFI_G(callback_tid) == FFI_G(main_tid); + + if(!is_main_thread){ + // post interrupt request to synchronize with the main thread + zend_atomic_bool_store_ex(&EG(vm_interrupt), true); + + // release mutex and wait for ack + tsrm_cond_wait(FFI_G(vm_ack), FFI_G(vm_request_lock)); + +#ifdef ZEND_CHECK_STACK_LIMIT + // prepare the stack call info/limits for the current thread + zend_call_stack_init(); +#endif + } - ret_type = ZEND_FFI_TYPE(callback_data->type->func.ret_type); - if (ret_type->kind != ZEND_FFI_TYPE_VOID) { - zend_ffi_zval_to_cdata(ret, ret_type, &retval); + // dispatch the callback + if (call_data.data->type->func.args) { + int n = 0; + zend_ffi_type *arg_type; + + ZEND_HASH_PACKED_FOREACH_PTR(call_data.data->type->func.args, arg_type) { + arg_type = ZEND_FFI_TYPE(arg_type); + zend_ffi_cdata_to_zval(NULL, + call_data.data->ffi_args.args[n], arg_type, BP_VAR_R, + &call_data.data->fiber.fci.params[n], (zend_ffi_flags)(arg_type->attr & ZEND_FFI_ATTR_CONST), + 0, 0); + n++; + } ZEND_HASH_FOREACH_END(); + } + + // call PHP function + zend_fiber_resume_internal(&call_data.data->fiber, NULL, false); + zend_ffi_type *ret_type = ZEND_FFI_TYPE(call_data.data->type->func.ret_type); + if(ret_type->kind != ZEND_FFI_TYPE_VOID){ + // extract return value from fiber + zend_ffi_zval_to_cdata(call_data.ret, ret_type, &call_data.data->fiber.result); + + if (callback_data->arg_count) { + for (uint32_t i = 0; i < callback_data->arg_count; i++) { + zval_ptr_dtor(&call_data.data->fiber.fci.params[i]); + } + } #ifdef WORDS_BIGENDIAN - if (ret_type->size < sizeof(ffi_arg) - && ret_type->kind >= ZEND_FFI_TYPE_UINT8 - && ret_type->kind < ZEND_FFI_TYPE_POINTER) { - /* We need to widen the value (zero extend) */ - switch (ret_type->size) { - case 1: - *(ffi_arg*)ret = *(uint8_t*)ret; - break; - case 2: - *(ffi_arg*)ret = *(uint16_t*)ret; - break; - case 4: - *(ffi_arg*)ret = *(uint32_t*)ret; - break; - default: - break; + zval *ret_type = &call_data.data->fiber.result; + if (ret_type->size < sizeof(ffi_arg) + && ret_type->kind >= ZEND_FFI_TYPE_UINT8 + && ret_type->kind < ZEND_FFI_TYPE_POINTER) { + /* We need to widen the value (zero extend) */ + switch (ret_type->size) { + case 1: + *(ffi_arg*)ret = *(uint8_t*)ret; + break; + case 2: + *(ffi_arg*)ret = *(uint16_t*)ret; + break; + case 4: + *(ffi_arg*)ret = *(uint32_t*)ret; + break; + default: + break; + } } - } #endif - } + } - zval_ptr_dtor(&retval); + efree(call_data.data->fiber.fci.params); + + zend_ffi_dispatch_callback_end(); + } + tsrm_mutex_unlock(FFI_G(vm_request_lock)); } /* }}} */ @@ -1051,6 +1167,16 @@ static void *zend_ffi_create_callback(zend_ffi_type *type, zval *value) /* {{{ * callback_data->callback = callback; callback_data->code = code; callback_data->arg_count = arg_count; + callback_data->fiber.fci_cache = fcc; + + if(zend_fiber_init_context(&callback_data->fiber.context, NULL, zend_fiber_execute, EG(fiber_stack_size)) == FAILURE){ + zend_throw_error(zend_ffi_exception_ce, "Cannot allocate fiber"); + return NULL; + } + callback_data->fiber.previous = &callback_data->fiber.context; + + zend_ffi_fci_prepare(callback_data, &callback_data->fiber.fci); + ffi_type **arg_types = callback_data->arg_types; if (type->func.args) { int n = 0; @@ -1058,12 +1184,12 @@ static void *zend_ffi_create_callback(zend_ffi_type *type, zval *value) /* {{{ * ZEND_HASH_PACKED_FOREACH_PTR(type->func.args, arg_type) { arg_type = ZEND_FFI_TYPE(arg_type); - callback_data->arg_types[n] = zend_ffi_get_type(arg_type); - if (!callback_data->arg_types[n]) { + arg_types[n] = zend_ffi_get_type(arg_type); + if (!arg_types[n]) { zend_ffi_pass_unsupported(arg_type); for (int i = 0; i < n; ++i) { - if (callback_data->arg_types[i]->type == FFI_TYPE_STRUCT) { - efree(callback_data->arg_types[i]); + if (arg_types[i]->type == FFI_TYPE_STRUCT) { + efree(arg_types[i]); } } efree(callback_data); @@ -1077,8 +1203,8 @@ static void *zend_ffi_create_callback(zend_ffi_type *type, zval *value) /* {{{ * if (!callback_data->ret_type) { zend_ffi_return_unsupported(type->func.ret_type); for (int i = 0; i < callback_data->arg_count; ++i) { - if (callback_data->arg_types[i]->type == FFI_TYPE_STRUCT) { - efree(callback_data->arg_types[i]); + if (arg_types[i]->type == FFI_TYPE_STRUCT) { + efree(arg_types[i]); } } efree(callback_data); @@ -1095,8 +1221,8 @@ static void *zend_ffi_create_callback(zend_ffi_type *type, zval *value) /* {{{ * zend_throw_error(zend_ffi_exception_ce, "Cannot prepare callback"); free_on_failure: ; for (int i = 0; i < callback_data->arg_count; ++i) { - if (callback_data->arg_types[i]->type == FFI_TYPE_STRUCT) { - efree(callback_data->arg_types[i]); + if (arg_types[i]->type == FFI_TYPE_STRUCT) { + efree(arg_types); } } if (callback_data->ret_type->type == FFI_TYPE_STRUCT) { @@ -5635,10 +5761,20 @@ ZEND_MINIT_FUNCTION(ffi) zend_ffi_ctype_handlers.get_properties = zend_fake_get_properties; zend_ffi_ctype_handlers.get_gc = zend_fake_get_gc; + FFI_G(vm_request_lock) = tsrm_mutex_alloc(); + FFI_G(vm_ack) = tsrm_cond_alloc(); + FFI_G(vm_unlock) = tsrm_cond_alloc(); + if (FFI_G(preload)) { return zend_ffi_preload(FFI_G(preload)); } + FFI_G(main_tid) = tsrm_thread_id(); + + zend_atomic_bool_store_ex(&FFI_G(callback_in_progress), false); + orig_interrupt_function = zend_interrupt_function; + zend_interrupt_function = zend_ffi_interrupt_function; + return SUCCESS; } /* }}} */ @@ -5646,6 +5782,8 @@ ZEND_MINIT_FUNCTION(ffi) /* {{{ ZEND_RSHUTDOWN_FUNCTION */ ZEND_RSHUTDOWN_FUNCTION(ffi) { + zend_ffi_wait_request_barrier(true); + if (FFI_G(callbacks)) { zend_hash_destroy(FFI_G(callbacks)); efree(FFI_G(callbacks)); @@ -5757,6 +5895,11 @@ static ZEND_GINIT_FUNCTION(ffi) /* {{{ ZEND_GINIT_FUNCTION */ static ZEND_GSHUTDOWN_FUNCTION(ffi) { + zend_ffi_wait_request_barrier(true); + tsrm_cond_free(ffi_globals->vm_ack); + tsrm_cond_free(ffi_globals->vm_unlock); + tsrm_mutex_free(ffi_globals->vm_request_lock); + if (ffi_globals->scopes) { zend_hash_destroy(ffi_globals->scopes); free(ffi_globals->scopes); diff --git a/ext/ffi/php_ffi.h b/ext/ffi/php_ffi.h index ba92947e62bf4..0ef2ee7d9a6ff 100644 --- a/ext/ffi/php_ffi.h +++ b/ext/ffi/php_ffi.h @@ -17,6 +17,11 @@ #ifndef PHP_FFI_H #define PHP_FFI_H +#include +#include "zend_compile.h" +#include "zend_API.h" +#include "TSRM.h" + extern zend_module_entry ffi_module_entry; #define phpext_ffi_ptr &ffi_module_entry @@ -27,6 +32,15 @@ typedef enum _zend_ffi_api_restriction { } zend_ffi_api_restriction; typedef struct _zend_ffi_type zend_ffi_type; +typedef struct _zend_ffi_callback_data zend_ffi_callback_data; + + +typedef struct _zend_ffi_call_data { + ffi_cif* cif; + void* ret; + void** args; + zend_ffi_callback_data* data; +} zend_ffi_call_data; ZEND_BEGIN_MODULE_GLOBALS(ffi) zend_ffi_api_restriction restriction; @@ -35,6 +49,15 @@ ZEND_BEGIN_MODULE_GLOBALS(ffi) /* predefined ffi_types */ HashTable types; + zend_atomic_bool callback_in_progress; + + MUTEX_T vm_request_lock; + COND_T vm_ack; + COND_T vm_unlock; + THREAD_T callback_tid; + THREAD_T main_tid; + zend_ffi_call_data callback_data; + /* preloading */ char *preload; HashTable *scopes; /* list of preloaded scopes */ diff --git a/ext/ffi/tests/bug79177.phpt b/ext/ffi/tests/bug79177.phpt deleted file mode 100644 index fe980f95c6517..0000000000000 --- a/ext/ffi/tests/bug79177.phpt +++ /dev/null @@ -1,30 +0,0 @@ ---TEST-- -Bug #79177 (FFI doesn't handle well PHP exceptions within callback) ---EXTENSIONS-- -ffi -zend_test ---FILE-- -bug79177_cb = function() { - throw new \RuntimeException('Not allowed'); -}; -try { - $ffi->bug79177(); // this is supposed to raise a fatal error -} catch (\Throwable $exception) {} -echo "done\n"; -?> ---EXPECTF-- -Warning: Uncaught RuntimeException: Not allowed in %s:%d -Stack trace: -#0 %s(%d): {closure:%s:%d}() -#1 %s(%d): FFI->bug79177() -#2 {main} - thrown in %s on line %d - -Fatal error: Throwing from FFI callbacks is not allowed in %s on line %d diff --git a/ext/ffi/tests/callback_threads.phpt b/ext/ffi/tests/callback_threads.phpt new file mode 100644 index 0000000000000..186c83cc1c493 --- /dev/null +++ b/ext/ffi/tests/callback_threads.phpt @@ -0,0 +1,59 @@ +--TEST-- +FFI Thread safe callbacks +--EXTENSIONS-- +ffi +--SKIPIF-- + +--INI-- +ffi.enable=1 +--FILE-- +new($libc->type('pthread_t')); +$accum = 0; +$thread_func = function($arg) use($libc, &$accum){ + //$v = $libc->cast('int *', $arg)[0]; + FFI::free($arg); + usleep(10 * 1000); + $accum++; + //print("."); +}; + +for($i=0; $i<100; $i++){ + $arg = $libc->new('int', false); + $arg->cdata = $i; + + $libc->pthread_create( + FFI::addr($tid), NULL, + $thread_func, FFI::addr($arg) + ); + $libc->pthread_detach($tid->cdata); +} + +while($accum != 100){ + //print("w"); + usleep(1000); +} +print($accum); +?> +--EXPECT-- +100 \ No newline at end of file diff --git a/ext/ffi/tests/gh16013.phpt b/ext/ffi/tests/gh16013.phpt index be57846cc42bc..da9053c362b57 100644 --- a/ext/ffi/tests/gh16013.phpt +++ b/ext/ffi/tests/gh16013.phpt @@ -130,7 +130,7 @@ int(10000) int(-100000) int(100000) float(12.34000015258789) -object(FFI\CData:struct bug_gh16013_int_struct)#13 (1) { +object(FFI\CData:struct bug_gh16013_int_struct)#21 (1) { ["field"]=> int(10) }