commit da0a01367c74ddddb3ee26718f074c9e91901994 (HEAD -> knielsen_my_context)
Author: Kristian Nielsen <knielsen@knielsen-hq.org>
Date:   Thu Sep 5 13:56:44 2024 +0200

    Implement boost::context as a fallback for non-blocking API support
    
    The non-blocking API has native (assembler) implementations for x86_64,
    i386, and (with recent patch) aarch64; these implementations are the most
    efficient. For other architectures, a fallback to ucontext is supported.
    
    But ucontext is not the most efficient, and it is not available on all
    platforms (it has been deprecated in POSIX). The boost::context library
    provides an alternative fallback that is available on more architectures and
    should be more efficient than ucontext (if still not quite as fast as the
    native support).
    
    This patch adds a CMake option -DWITH_BOOST_CONTEXT=ON that adds
    boost::context as a dependency of libmariadb to provide a fallback on
    non-natively supported architectures. Boost::context is preferred over
    ucontext when both are available.
    
    The option is off by default and must be explicitly enabled by the
    user. This avoids introducing a C++ dependency (including dependency
    on a C++ compiler and on libstdc++) unless explicitly requested by the
    user (libmariadb is otherwise C-only).
    
    Tested-by: Brad Smith <brad@comstyle.com>
    Signed-off-by: Kristian Nielsen <knielsen@knielsen-hq.org>

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 1f43faa2..616dd702 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -16,8 +16,6 @@ IF(COMMAND CMAKE_POLICY)
 ENDIF()
 
 
-PROJECT(mariadb-connector-c C)
-
 # Is C/C built as subproject?
 get_directory_property(IS_SUBPROJECT PARENT_DIRECTORY)
 
@@ -25,10 +23,28 @@ get_directory_property(IS_SUBPROJECT PARENT_DIRECTORY)
 SET_PROPERTY(DIRECTORY PROPERTY INCLUDE_DIRECTORIES)
 FOREACH(V WITH_MYSQLCOMPAT WITH_MSI WITH_SIGNCODE WITH_RTC WITH_UNIT_TESTS
     WITH_DYNCOL WITH_EXTERNAL_ZLIB WITH_CURL WITH_SQLITE WITH_SSL WITH_ICONV
-    DEFAULT_CHARSET INSTALL_LAYOUT WITH_TEST_SRCPKG)
+    DEFAULT_CHARSET INSTALL_LAYOUT WITH_TEST_SRCPKG WITH_BOOST_CONTEXT)
   SET(${V} ${${OPT}${V}})
 ENDFOREACH()
 
+MACRO(ADD_OPTION _name _text _default)
+  IF(NOT DEFINED ${_name})
+    OPTION(${OPT}${_name} "${_text}" "${_default}")
+  ELSE()
+    OPTION(${OPT}${_name} "${_text}" "${${_name}}")
+  ENDIF()
+ENDMACRO()
+
+ADD_OPTION(WITH_BOOST_CONTEXT
+  "Use Boost::Context for the non-blocking API on platforms without native implementation"
+  OFF)
+
+IF(WITH_BOOST_CONTEXT)
+  PROJECT(mariadb-connector-c LANGUAGES C CXX)
+ELSE()
+  PROJECT(mariadb-connector-c C)
+ENDIF()
+
 # SET(PACKAGE_STATUS_SUFFIX "rc1")
 
 SET(CC_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR})
@@ -42,14 +58,6 @@ MATH(EXPR MARIADB_PACKAGE_VERSION_ID "${CPACK_PACKAGE_VERSION_MAJOR} * 10000 +
                             ${CPACK_PACKAGE_VERSION_MINOR} * 100   +
                             ${CPACK_PACKAGE_VERSION_PATCH}")
 
-MACRO(ADD_OPTION _name _text _default)
-  IF(NOT DEFINED ${_name})
-    OPTION(${OPT}${_name} "${_text}" "${_default}")
-  ELSE()
-    OPTION(${OPT}${_name} "${_text}" "${${_name}}")
-  ENDIF()
-ENDMACRO()
-
 ### Options ###
 IF(NOT WIN32)
   ADD_OPTION(WITH_MYSQLCOMPAT "creates libmysql* symbolic links" OFF)
@@ -361,6 +369,11 @@ IF(NOT WITH_SSL STREQUAL "OFF")
   MARK_AS_ADVANCED(SSL_SOURCES)
 ENDIF()
 
+IF(WITH_BOOST_CONTEXT)
+  FIND_PACKAGE(Boost 1.40 COMPONENTS context REQUIRED)
+  SET(SYSTEM_LIBS ${SYSTEM_LIBS} ${Boost_LIBRARIES})
+ENDIF()
+
 SET(ENABLED_LOCAL_INFILE "AUTO" CACHE STRING "If we should should enable LOAD DATA LOCAL by default (OFF/ON/AUTO)")
 MARK_AS_ADVANCED(ENABLED_LOCAL_INFILE)
 IF (ENABLED_LOCAL_INFILE MATCHES "^(0|FALSE)$")
diff --git a/cmake/check_include_files.cmake b/cmake/check_include_files.cmake
index d8bbffcd..50e289fa 100644
--- a/cmake/check_include_files.cmake
+++ b/cmake/check_include_files.cmake
@@ -43,6 +43,9 @@ CHECK_INCLUDE_FILES (sys/types.h HAVE_SYS_TYPES_H)
 CHECK_INCLUDE_FILES (sys/stat.h HAVE_SYS_STAT_H)
 CHECK_INCLUDE_FILES (sys/un.h HAVE_SYS_UN_H)
 CHECK_INCLUDE_FILES (unistd.h HAVE_UNISTD_H)
+IF(WITH_BOOST_CONTEXT)
+  CHECK_INCLUDE_FILE_CXX (boost/fiber/context.hpp HAVE_BOOST_CONTEXT_H)
+ENDIF()
 
 IF(APPLE)
   SET(CMAKE_REQUIRED_DEFINITIONS -D_XOPEN_SOURCE=600)
diff --git a/include/ma_config.h.in b/include/ma_config.h.in
index c5c758dc..ff825491 100644
--- a/include/ma_config.h.in
+++ b/include/ma_config.h.in
@@ -28,6 +28,7 @@
 #cmakedefine HAVE_SYS_UN_H 1
 #cmakedefine HAVE_UNISTD_H 1
 #cmakedefine HAVE_UCONTEXT_H 1
+#cmakedefine HAVE_BOOST_CONTEXT_H 1
 
 /*
  * function definitions - processed in LibmysqlFunctions.txt 
diff --git a/include/ma_context.h b/include/ma_context.h
index 2c4ab10a..4c3f0d8d 100644
--- a/include/ma_context.h
+++ b/include/ma_context.h
@@ -34,12 +34,18 @@
 #define MY_CONTEXT_USE_I386_GCC_ASM
 #elif defined(__GNUC__) && __GNUC__ >= 3 && defined(__aarch64__)
 #define MY_CONTEXT_USE_AARCH64_GCC_ASM
+#elif defined(HAVE_BOOST_CONTEXT_H)
+#define MY_CONTEXT_USE_BOOST_CONTEXT
 #elif defined(HAVE_UCONTEXT_H)
 #define MY_CONTEXT_USE_UCONTEXT
 #else
 #define MY_CONTEXT_DISABLE
 #endif
 
+#ifdef   __cplusplus
+extern "C" {
+#endif
+
 #ifdef MY_CONTEXT_USE_WIN32_FIBERS
 struct my_context {
   void (*user_func)(void *);
@@ -126,6 +132,32 @@ struct my_context {
 #endif
 
 
+#ifdef MY_CONTEXT_USE_BOOST_CONTEXT
+/*
+  boost::context is a C++-library that provides a portable co-routine fallback
+  for architectures that lack a native my_context implementation, and which is
+  available on some platforms where ucontext is not (ucontext has been
+  deprecated in Posix).
+
+  Since boost::context is in C++, the implementation details must be put into
+  a separate source file ma_boost_context.cc and hidden in an opaque void *.
+*/
+struct my_context {
+  /* Pointer to state that uses C++-types and cannot be compiled in C. */
+  void *internal_context;
+  void *stack;
+  size_t stack_size;
+#ifndef DBUG_OFF
+  void *dbug_state;
+#endif
+  int active;
+#ifdef HAVE_VALGRIND
+  unsigned int valgrind_stack_id;
+#endif
+};
+#endif /* MY_CONTEXT_USE_BOOST_CONTEXT */
+
+
 #ifdef MY_CONTEXT_DISABLE
 struct my_context {
   int dummy;
@@ -259,3 +291,7 @@ struct mysql_async_context {
   */
   struct my_context async_context;
 };
+
+#ifdef   __cplusplus
+}
+#endif
diff --git a/libmariadb/CMakeLists.txt b/libmariadb/CMakeLists.txt
index 43ed67b4..1ce0d4c6 100644
--- a/libmariadb/CMakeLists.txt
+++ b/libmariadb/CMakeLists.txt
@@ -390,6 +390,9 @@ IF(WITH_DYNCOL)
 ENDIF()
 
 SET(LIBMARIADB_SOURCES ${LIBMARIADB_SOURCES} mariadb_async.c ma_context.c)
+IF(WITH_BOOST_CONTEXT)
+  SET(LIBMARIADB_SOURCES ${LIBMARIADB_SOURCES} ma_boost_context.cc)
+ENDIF()
 SET(MARIADB_LIB_SYMBOLS ${MARIADB_LIB_SYMBOLS} ${MARIADB_NONBLOCK_SYMBOLS})
 
 INCLUDE(${CC_SOURCE_DIR}/cmake/export.cmake)
diff --git a/libmariadb/ma_boost_context.cc b/libmariadb/ma_boost_context.cc
new file mode 100644
index 00000000..cb7625d9
--- /dev/null
+++ b/libmariadb/ma_boost_context.cc
@@ -0,0 +1,117 @@
+#include "ma_config.h"
+#include "mysql.h"
+#include "ma_context.h"
+
+#ifdef MY_CONTEXT_USE_BOOST_CONTEXT
+
+#include <boost/fiber/context.hpp>
+
+namespace ctx=boost::context;
+
+struct my_context_intern {
+  ctx::fiber parent, coro;
+
+  void *stack_top(const my_context *c) {
+    return (unsigned char *)(c->stack) + c->stack_size;
+  }
+
+  /* A StackAlloc for ctx::fiber that reuses our stack. */
+  struct my_stack_alloc {
+    typedef ctx::stack_traits traits_type;
+    my_context *c;
+    my_stack_alloc(my_context *c_arg) : c(c_arg) { };
+    ctx::stack_context allocate() {
+      ctx::stack_context sctx;
+      sctx.size= c->stack_size;
+      sctx.sp= ((my_context_intern *)c->internal_context)->stack_top(c);
+#if defined(BOOST_USE_VALGRIND) && defined(HAVE_VALGRIND)
+      sctx.valgrind_stack_id= c->valgrind_stack_id;
+#endif
+      return sctx;
+    }
+    void deallocate(ctx::stack_context & sctx) {
+      /* Empty, we will re-use the stack. */
+    }
+  };
+};
+
+
+extern "C"
+int
+my_context_spawn(struct my_context *c, void (*f)(void *), void *d)
+{
+  my_context_intern *ci= (my_context_intern *)c->internal_context;
+  ci->coro= ctx::fiber(std::allocator_arg, my_context_intern::my_stack_alloc(c),
+                       [c, f, d](ctx::fiber && parent) {
+      my_context_intern *ci= (my_context_intern *)c->internal_context;
+      ci->parent= std::move(parent);
+      (*f)(d);
+      c->active= 0;
+      return std::move(ci->parent);
+    });
+  c->active= 1;
+  ci->coro= std::move(ci->coro).resume();
+  return c->active;
+}
+
+
+extern "C"
+int
+my_context_continue(struct my_context *c)
+{
+  if (!c->active)
+    return 0;
+  my_context_intern *ci= (my_context_intern *)c->internal_context;
+  ci->coro= std::move(ci->coro).resume();
+  return c->active;
+}
+
+
+extern "C"
+int
+my_context_yield(struct my_context *c)
+{
+  if (!c->active)
+    return -1;
+  my_context_intern *ci= (my_context_intern *)c->internal_context;
+  ci->parent= std::move(ci->parent).resume();
+  return 0;
+}
+
+
+extern "C"
+int
+my_context_init(struct my_context *c, size_t stack_size)
+{
+  memset(c, 0, sizeof(*c));
+  if (!(c->stack= malloc(stack_size)))
+    return -1;                                  /* Out of memory */
+  if (!(c->internal_context= new my_context_intern))
+  {
+    free(c->stack);
+    return -1;
+  }
+  c->stack_size= stack_size;
+#ifdef HAVE_VALGRIND
+  c->valgrind_stack_id=
+    VALGRIND_STACK_REGISTER(c->stack, ((unsigned char *)(c->stack))+stack_size);
+#endif
+  return 0;
+}
+
+
+extern "C"
+void
+my_context_destroy(struct my_context *c)
+{
+  delete (my_context_intern *)c->internal_context;
+  if (c->stack)
+  {
+#ifdef HAVE_VALGRIND
+    VALGRIND_STACK_DEREGISTER(c->valgrind_stack_id);
+#endif
+    free(c->stack);
+  }
+}
+
+#endif /* MY_CONTEXT_USE_BOOST_CONTEXT */
diff --git a/libmariadb/ma_context.c b/libmariadb/ma_context.c
index 3edef568..203796c5 100644
--- a/libmariadb/ma_context.c
+++ b/libmariadb/ma_context.c
@@ -675,7 +675,7 @@ my_context_spawn(struct my_context *c, void (*f)(void *), void *d)
     (
      "mov x10, sp\n\t"
      "mov sp, %[stack]\n\t"
-#if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 4) || __clang__) && !defined(__INTEL_COMPILER)
+#if !defined(__INTEL_COMPILER)
      /*
        This emits a DWARF DW_CFA_undefined directive to make the return address
        (UNW_AARCH64_X30) undefined. This indicates that this is the top of the
