diff --git a/TSRM/TSRM.c b/TSRM/TSRM.c index 09ff8cc49279e..2139d430ae266 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; @@ -577,101 +716,6 @@ void ts_free_id(ts_rsrc_id id) }/*}}}*/ -/* - * 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 3bb32e4da289a..0bd9bb6282621 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 *); @@ -105,11 +129,6 @@ TSRM_API void ts_free_thread(void); TSRM_API void ts_free_id(ts_rsrc_id id); -/* 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); @@ -118,16 +137,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 e669ab6b53382..77e4228fd026f 100644 --- a/Zend/zend_fibers.c +++ b/Zend/zend_fibers.c @@ -543,7 +543,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"); @@ -615,7 +615,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) { @@ -627,7 +627,7 @@ static zend_always_inline void zend_fiber_delegate_transfer_result( RETURN_COPY_VALUE(&transfer->value); } -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 = { @@ -652,7 +652,7 @@ static zend_always_inline zend_fiber_transfer zend_fiber_switch_to( return transfer; } -static zend_always_inline zend_fiber_transfer zend_fiber_resume(zend_fiber *fiber, zval *value, bool exception) +zend_always_inline zend_fiber_transfer zend_fiber_resume(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 5c81f44a642e4..9de1d79f4d555 100644 --- a/Zend/zend_fibers.h +++ b/Zend/zend_fibers.h @@ -134,8 +134,12 @@ struct _zend_fiber { /* 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(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/Zend/zend_globals_macros.h b/Zend/zend_globals_macros.h index 59b3daca53fd4..d3cd9f923eeea 100644 --- a/Zend/zend_globals_macros.h +++ b/Zend/zend_globals_macros.h @@ -20,6 +20,8 @@ #ifndef ZEND_GLOBALS_MACROS_H #define ZEND_GLOBALS_MACROS_H +#include "zend_portability.h" + typedef struct _zend_compiler_globals zend_compiler_globals; typedef struct _zend_executor_globals zend_executor_globals; typedef struct _zend_php_scanner_globals zend_php_scanner_globals; diff --git a/ext/ffi/ffi.c b/ext/ffi/ffi.c index b0a59ca85cc3f..fbf85e57a90d0 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,8 +28,9 @@ #include "zend_closures.h" #include "zend_weakrefs.h" #include "main/SAPI.h" - -#include +#include "TSRM.h" +#include "zend_fibers.h" +#include "zend_call_stack.h" #include #include @@ -227,6 +230,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(zval *arg, zend_ffi_type *type); static bool zend_ffi_is_compatible_type(zend_ffi_type *dst_type, 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); @@ -904,14 +911,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[0] ZEND_ELEMENT_COUNT(arg_count); } zend_ffi_callback_data; @@ -923,9 +933,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 (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]); } } if (callback_data->ret_type->type == FFI_TYPE_STRUCT) { @@ -935,57 +946,151 @@ 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)); } +} +/* }}} */ - ZVAL_UNDEF(&retval); - if (zend_call_function(&fci, &callback_data->fcc) != SUCCESS) { - zend_throw_error(zend_ffi_exception_ce, "Cannot call callback"); +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; } - if (callback_data->arg_count) { - int n = 0; + tsrm_mutex_lock(FFI_G(vm_request_lock)); + if (!zend_atomic_bool_load_ex(&FFI_G(callback_in_progress))) { + goto end; + } - for (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); } - free_alloca(fci.params, use_heap); +} +/* }}} */ - if (EG(exception)) { - zend_error_noreturn(E_ERROR, "Throwing from FFI callbacks is not allowed"); +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); + } } - 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); + 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_call_data call_data = { + .cif = cif, + .ret = ret, + .args = args, + .data = (zend_ffi_callback_data *)data + }; + ((zend_ffi_callback_data *)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)); + + // prepare the stack call info/limits for the current thread + zend_call_stack_init(); + } + + // 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(); + } + + if (zend_atomic_bool_load_ex(&FFI_G(callback_in_progress))) { + // call PHP function + zend_fiber_resume(&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); + } - zval_ptr_dtor(&retval); + efree(call_data.data->fiber.fci.params); + } + + zend_ffi_dispatch_callback_end(); + } + tsrm_mutex_unlock(FFI_G(vm_request_lock)); } /* }}} */ @@ -1026,6 +1131,13 @@ 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; + + zend_fiber_init_context(&callback_data->fiber.context, NULL, zend_fiber_execute, EG(fiber_stack_size)); + 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; @@ -1033,12 +1145,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); @@ -1052,8 +1164,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); @@ -1070,8 +1182,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) { @@ -5514,10 +5626,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; } /* }}} */ @@ -5525,6 +5647,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)); @@ -5636,6 +5760,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 02a241c6bb691..2b31e48056363 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/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