Skip to content

Fiber ucontext support #7226

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jul 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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