Skip to content

Commit

Permalink
Fiber ucontext support (#7226)
Browse files Browse the repository at this point in the history
Co-authored-by: Martin Schröder <m.schroeder2007@gmail.com>
  • Loading branch information
trowski and kooldev committed Jul 11, 2021
1 parent 1f42777 commit 8fd747a
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 18 deletions.
3 changes: 3 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ PHP NEWS
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
?? ??? ????, PHP 8.1.0beta1

- Core:
. Fixed bug #81238 (Fiber support missing for Solaris Sparc). (trowski)

- Reflection:
. Fixed bug #80097 (ReflectionAttribute is not a Reflector). (beberlei)

Expand Down
73 changes: 62 additions & 11 deletions Zend/zend_fibers.c
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@ struct _zend_fiber_stack {
const void *asan_pointer;
size_t asan_size;
#endif

#ifdef ZEND_FIBER_UCONTEXT
/* Embedded ucontext to avoid unnecessary memory allocations. */
ucontext_t ucontext;
#endif
};

/* Zend VM state that needs to be captured / restored during fiber context switch. */
Expand Down Expand Up @@ -113,6 +118,10 @@ static zend_always_inline void zend_fiber_restore_vm_state(zend_fiber_vm_state *
EG(active_fiber) = state->active_fiber;
}

#ifdef ZEND_FIBER_UCONTEXT
# include <ucontext.h>
ZEND_TLS zend_fiber_transfer *transfer_data;
#else
/* boost_context_data is our customized definition of struct transfer_t as
* provided by boost.context in fcontext.hpp:
*
Expand All @@ -130,7 +139,8 @@ typedef struct {

/* These functions are defined in assembler files provided by boost.context (located in "Zend/asm"). */
extern void *make_fcontext(void *sp, size_t size, void (*fn)(boost_context_data));
extern boost_context_data jump_fcontext(void *to, zend_fiber_transfer *data);
extern boost_context_data jump_fcontext(void *to, zend_fiber_transfer *transfer);
#endif

ZEND_API zend_class_entry *zend_ce_fiber;
static zend_class_entry *zend_ce_fiber_error;
Expand Down Expand Up @@ -244,20 +254,29 @@ static void zend_fiber_stack_free(zend_fiber_stack *stack)

efree(stack);
}

#ifdef ZEND_FIBER_UCONTEXT
static ZEND_NORETURN void zend_fiber_trampoline(void)
#else
static ZEND_NORETURN void zend_fiber_trampoline(boost_context_data data)
#endif
{
zend_fiber_context *from = data.transfer->context;
/* Initialize transfer struct with a copy of passed data. */
#ifdef ZEND_FIBER_UCONTEXT
zend_fiber_transfer transfer = *transfer_data;
#else
zend_fiber_transfer transfer = *data.transfer;
#endif

zend_fiber_context *from = transfer.context;

#ifdef __SANITIZE_ADDRESS__
__sanitizer_finish_switch_fiber(NULL, &from->stack->asan_pointer, &from->stack->asan_size);
#endif

/* Get a hold of the context that resumed us and update its handle to allow for symmetric coroutines. */
#ifndef ZEND_FIBER_UCONTEXT
/* Get the context that resumed us and update its handle to allow for symmetric coroutines. */
from->handle = data.handle;

/* Initialize transfer struct with a copy of passed data. */
zend_fiber_transfer transfer = *data.transfer;
#endif

/* Ensure that previous fiber will be cleaned up (needed by symmetric coroutines). */
if (from->status == ZEND_FIBER_STATUS_DEAD) {
Expand Down Expand Up @@ -300,11 +319,26 @@ ZEND_API bool zend_fiber_init_context(zend_fiber_context *context, void *kind, z
return false;
}

#ifdef ZEND_FIBER_UCONTEXT
ucontext_t *handle = &context->stack->ucontext;

getcontext(handle);

handle->uc_stack.ss_size = context->stack->size;
handle->uc_stack.ss_sp = context->stack->pointer;
handle->uc_stack.ss_flags = 0;
handle->uc_link = NULL;

makecontext(handle, (void (*)(void)) zend_fiber_trampoline, 0);

context->handle = handle;
#else
// 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->handle = make_fcontext(stack, context->stack->size, zend_fiber_trampoline);
ZEND_ASSERT(context->handle != NULL && "make_fcontext() never returns NULL");
#endif

context->kind = kind;
context->function = coroutine;
Expand Down Expand Up @@ -363,14 +397,26 @@ ZEND_API void zend_fiber_switch_context(zend_fiber_transfer *transfer)
to->stack->asan_size);
#endif

#ifdef ZEND_FIBER_UCONTEXT
transfer_data = transfer;

swapcontext(from->handle, to->handle);

/* Copy transfer struct because it might live on the other fiber's stack that will eventually be destroyed. */
*transfer = *transfer_data;
#else
boost_context_data data = jump_fcontext(to->handle, transfer);

/* Copy transfer struct because it might live on the other fiber's stack that will eventually be destroyed. */
*transfer = *data.transfer;
#endif

/* Get a hold of the context that resumed us and update its handle to allow for symmetric coroutines. */
to = transfer->context;

#ifndef ZEND_FIBER_UCONTEXT
/* Get the context that resumed us and update its handle to allow for symmetric coroutines. */
to->handle = data.handle;
#endif

#ifdef __SANITIZE_ADDRESS__
__sanitizer_finish_switch_fiber(fake_stack, &to->stack->asan_pointer, &to->stack->asan_size);
Expand Down Expand Up @@ -839,9 +885,13 @@ void zend_fiber_init(void)
{
zend_fiber_context *context = ecalloc(1, sizeof(zend_fiber_context));

#ifdef __SANITIZE_ADDRESS__
// Main fiber context stack is only accessed if ASan is enabled.
#if defined(__SANITIZE_ADDRESS__) || defined(ZEND_FIBER_UCONTEXT)
// Main fiber stack is only needed if ASan or ucontext is enabled.
context->stack = emalloc(sizeof(zend_fiber_stack));

#ifdef ZEND_FIBER_UCONTEXT
context->handle = &context->stack->ucontext;
#endif
#endif

context->status = ZEND_FIBER_STATUS_RUNNING;
Expand All @@ -855,9 +905,10 @@ void zend_fiber_init(void)

void zend_fiber_shutdown(void)
{
#ifdef __SANITIZE_ADDRESS__
#if defined(__SANITIZE_ADDRESS__) || defined(ZEND_FIBER_UCONTEXT)
efree(EG(main_fiber_context)->stack);
#endif

efree(EG(main_fiber_context));

zend_fiber_switch_block();
Expand Down
2 changes: 1 addition & 1 deletion Zend/zend_fibers.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ typedef struct _zend_fiber_transfer {
typedef void (*zend_fiber_coroutine)(zend_fiber_transfer *transfer);

struct _zend_fiber_context {
/* Handle to fiber state as needed by boost.context */
/* Pointer to boost.context or ucontext_t data. */
void *handle;

/* Pointer that identifies the fiber type. */
Expand Down
23 changes: 17 additions & 6 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -1189,12 +1189,14 @@ fi
dnl Configuring Zend and TSRM.
dnl ----------------------------------------------------------------------------

AC_ARG_ENABLE([fiber-asm],
[AS_HELP_STRING([--disable-fiber-asm],
[Disable the use of boost fiber assembly files])],
[fiber_asm=$enableval], [fiber_asm='yes'])

PHP_HELP_SEPARATOR([Zend:])
PHP_CONFIGURE_PART(Configuring Zend)

AC_MSG_CHECKING(for fiber switching context)
fibers="yes"

AS_CASE([$host_cpu],
[x86_64*|amd64*], [fiber_cpu="x86_64"],
[x86*|amd*|i?86*|pentium], [fiber_cpu="i386"],
Expand Down Expand Up @@ -1231,14 +1233,23 @@ if test "$fiber_os" = 'mac'; then
elif test "$fiber_asm_file_prefix" != 'unknown'; then
fiber_asm_file="${fiber_asm_file_prefix}_elf_gas"
else
fibers="no"
fiber_asm="no"
fi

if test "$fibers" = 'yes'; then
if test "$fiber_asm" = 'yes'; then
AC_MSG_CHECKING([for fiber switching context])
AC_DEFINE([ZEND_FIBER_ASM], 1, [ ])
PHP_ADD_SOURCES(Zend/asm, make_${fiber_asm_file}.S jump_${fiber_asm_file}.S)
AC_MSG_RESULT([$fiber_asm_file])
else
AC_MSG_ERROR([Unable to determine platform!])
if test "$fiber_os" = 'mac'; then
AC_DEFINE([_XOPEN_SOURCE], 1, [ ])
fi
AC_CHECK_HEADER(ucontext.h, [
AC_DEFINE([ZEND_FIBER_UCONTEXT], 1, [ ])
], [
AC_MSG_ERROR([fibers not available on this platform])
])
fi

LIBZEND_BASIC_CHECKS
Expand Down

0 comments on commit 8fd747a

Please sign in to comment.