forked from STI/Astroport.ONE
551 lines
18 KiB
C
551 lines
18 KiB
C
|
|
||
|
/***** Support code for embedding *****/
|
||
|
|
||
|
#ifdef __cplusplus
|
||
|
extern "C" {
|
||
|
#endif
|
||
|
|
||
|
|
||
|
#if defined(_WIN32)
|
||
|
# define CFFI_DLLEXPORT __declspec(dllexport)
|
||
|
#elif defined(__GNUC__)
|
||
|
# define CFFI_DLLEXPORT __attribute__((visibility("default")))
|
||
|
#else
|
||
|
# define CFFI_DLLEXPORT /* nothing */
|
||
|
#endif
|
||
|
|
||
|
|
||
|
/* There are two global variables of type _cffi_call_python_fnptr:
|
||
|
|
||
|
* _cffi_call_python, which we declare just below, is the one called
|
||
|
by ``extern "Python"`` implementations.
|
||
|
|
||
|
* _cffi_call_python_org, which on CPython is actually part of the
|
||
|
_cffi_exports[] array, is the function pointer copied from
|
||
|
_cffi_backend. If _cffi_start_python() fails, then this is set
|
||
|
to NULL; otherwise, it should never be NULL.
|
||
|
|
||
|
After initialization is complete, both are equal. However, the
|
||
|
first one remains equal to &_cffi_start_and_call_python until the
|
||
|
very end of initialization, when we are (or should be) sure that
|
||
|
concurrent threads also see a completely initialized world, and
|
||
|
only then is it changed.
|
||
|
*/
|
||
|
#undef _cffi_call_python
|
||
|
typedef void (*_cffi_call_python_fnptr)(struct _cffi_externpy_s *, char *);
|
||
|
static void _cffi_start_and_call_python(struct _cffi_externpy_s *, char *);
|
||
|
static _cffi_call_python_fnptr _cffi_call_python = &_cffi_start_and_call_python;
|
||
|
|
||
|
|
||
|
#ifndef _MSC_VER
|
||
|
/* --- Assuming a GCC not infinitely old --- */
|
||
|
# define cffi_compare_and_swap(l,o,n) __sync_bool_compare_and_swap(l,o,n)
|
||
|
# define cffi_write_barrier() __sync_synchronize()
|
||
|
# if !defined(__amd64__) && !defined(__x86_64__) && \
|
||
|
!defined(__i386__) && !defined(__i386)
|
||
|
# define cffi_read_barrier() __sync_synchronize()
|
||
|
# else
|
||
|
# define cffi_read_barrier() (void)0
|
||
|
# endif
|
||
|
#else
|
||
|
/* --- Windows threads version --- */
|
||
|
# include <Windows.h>
|
||
|
# define cffi_compare_and_swap(l,o,n) \
|
||
|
(InterlockedCompareExchangePointer(l,n,o) == (o))
|
||
|
# define cffi_write_barrier() InterlockedCompareExchange(&_cffi_dummy,0,0)
|
||
|
# define cffi_read_barrier() (void)0
|
||
|
static volatile LONG _cffi_dummy;
|
||
|
#endif
|
||
|
|
||
|
#ifdef WITH_THREAD
|
||
|
# ifndef _MSC_VER
|
||
|
# include <pthread.h>
|
||
|
static pthread_mutex_t _cffi_embed_startup_lock;
|
||
|
# else
|
||
|
static CRITICAL_SECTION _cffi_embed_startup_lock;
|
||
|
# endif
|
||
|
static char _cffi_embed_startup_lock_ready = 0;
|
||
|
#endif
|
||
|
|
||
|
static void _cffi_acquire_reentrant_mutex(void)
|
||
|
{
|
||
|
static void *volatile lock = NULL;
|
||
|
|
||
|
while (!cffi_compare_and_swap(&lock, NULL, (void *)1)) {
|
||
|
/* should ideally do a spin loop instruction here, but
|
||
|
hard to do it portably and doesn't really matter I
|
||
|
think: pthread_mutex_init() should be very fast, and
|
||
|
this is only run at start-up anyway. */
|
||
|
}
|
||
|
|
||
|
#ifdef WITH_THREAD
|
||
|
if (!_cffi_embed_startup_lock_ready) {
|
||
|
# ifndef _MSC_VER
|
||
|
pthread_mutexattr_t attr;
|
||
|
pthread_mutexattr_init(&attr);
|
||
|
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
|
||
|
pthread_mutex_init(&_cffi_embed_startup_lock, &attr);
|
||
|
# else
|
||
|
InitializeCriticalSection(&_cffi_embed_startup_lock);
|
||
|
# endif
|
||
|
_cffi_embed_startup_lock_ready = 1;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
while (!cffi_compare_and_swap(&lock, (void *)1, NULL))
|
||
|
;
|
||
|
|
||
|
#ifndef _MSC_VER
|
||
|
pthread_mutex_lock(&_cffi_embed_startup_lock);
|
||
|
#else
|
||
|
EnterCriticalSection(&_cffi_embed_startup_lock);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
static void _cffi_release_reentrant_mutex(void)
|
||
|
{
|
||
|
#ifndef _MSC_VER
|
||
|
pthread_mutex_unlock(&_cffi_embed_startup_lock);
|
||
|
#else
|
||
|
LeaveCriticalSection(&_cffi_embed_startup_lock);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
|
||
|
/********** CPython-specific section **********/
|
||
|
#ifndef PYPY_VERSION
|
||
|
|
||
|
#include "_cffi_errors.h"
|
||
|
|
||
|
|
||
|
#define _cffi_call_python_org _cffi_exports[_CFFI_CPIDX]
|
||
|
|
||
|
PyMODINIT_FUNC _CFFI_PYTHON_STARTUP_FUNC(void); /* forward */
|
||
|
|
||
|
static void _cffi_py_initialize(void)
|
||
|
{
|
||
|
/* XXX use initsigs=0, which "skips initialization registration of
|
||
|
signal handlers, which might be useful when Python is
|
||
|
embedded" according to the Python docs. But review and think
|
||
|
if it should be a user-controllable setting.
|
||
|
|
||
|
XXX we should also give a way to write errors to a buffer
|
||
|
instead of to stderr.
|
||
|
|
||
|
XXX if importing 'site' fails, CPython (any version) calls
|
||
|
exit(). Should we try to work around this behavior here?
|
||
|
*/
|
||
|
Py_InitializeEx(0);
|
||
|
}
|
||
|
|
||
|
static int _cffi_initialize_python(void)
|
||
|
{
|
||
|
/* This initializes Python, imports _cffi_backend, and then the
|
||
|
present .dll/.so is set up as a CPython C extension module.
|
||
|
*/
|
||
|
int result;
|
||
|
PyGILState_STATE state;
|
||
|
PyObject *pycode=NULL, *global_dict=NULL, *x;
|
||
|
PyObject *builtins;
|
||
|
|
||
|
state = PyGILState_Ensure();
|
||
|
|
||
|
/* Call the initxxx() function from the present module. It will
|
||
|
create and initialize us as a CPython extension module, instead
|
||
|
of letting the startup Python code do it---it might reimport
|
||
|
the same .dll/.so and get maybe confused on some platforms.
|
||
|
It might also have troubles locating the .dll/.so again for all
|
||
|
I know.
|
||
|
*/
|
||
|
(void)_CFFI_PYTHON_STARTUP_FUNC();
|
||
|
if (PyErr_Occurred())
|
||
|
goto error;
|
||
|
|
||
|
/* Now run the Python code provided to ffi.embedding_init_code().
|
||
|
*/
|
||
|
pycode = Py_CompileString(_CFFI_PYTHON_STARTUP_CODE,
|
||
|
"<init code for '" _CFFI_MODULE_NAME "'>",
|
||
|
Py_file_input);
|
||
|
if (pycode == NULL)
|
||
|
goto error;
|
||
|
global_dict = PyDict_New();
|
||
|
if (global_dict == NULL)
|
||
|
goto error;
|
||
|
builtins = PyEval_GetBuiltins();
|
||
|
if (builtins == NULL)
|
||
|
goto error;
|
||
|
if (PyDict_SetItemString(global_dict, "__builtins__", builtins) < 0)
|
||
|
goto error;
|
||
|
x = PyEval_EvalCode(
|
||
|
#if PY_MAJOR_VERSION < 3
|
||
|
(PyCodeObject *)
|
||
|
#endif
|
||
|
pycode, global_dict, global_dict);
|
||
|
if (x == NULL)
|
||
|
goto error;
|
||
|
Py_DECREF(x);
|
||
|
|
||
|
/* Done! Now if we've been called from
|
||
|
_cffi_start_and_call_python() in an ``extern "Python"``, we can
|
||
|
only hope that the Python code did correctly set up the
|
||
|
corresponding @ffi.def_extern() function. Otherwise, the
|
||
|
general logic of ``extern "Python"`` functions (inside the
|
||
|
_cffi_backend module) will find that the reference is still
|
||
|
missing and print an error.
|
||
|
*/
|
||
|
result = 0;
|
||
|
done:
|
||
|
Py_XDECREF(pycode);
|
||
|
Py_XDECREF(global_dict);
|
||
|
PyGILState_Release(state);
|
||
|
return result;
|
||
|
|
||
|
error:;
|
||
|
{
|
||
|
/* Print as much information as potentially useful.
|
||
|
Debugging load-time failures with embedding is not fun
|
||
|
*/
|
||
|
PyObject *ecap;
|
||
|
PyObject *exception, *v, *tb, *f, *modules, *mod;
|
||
|
PyErr_Fetch(&exception, &v, &tb);
|
||
|
ecap = _cffi_start_error_capture();
|
||
|
f = PySys_GetObject((char *)"stderr");
|
||
|
if (f != NULL && f != Py_None) {
|
||
|
PyFile_WriteString(
|
||
|
"Failed to initialize the Python-CFFI embedding logic:\n\n", f);
|
||
|
}
|
||
|
|
||
|
if (exception != NULL) {
|
||
|
PyErr_NormalizeException(&exception, &v, &tb);
|
||
|
PyErr_Display(exception, v, tb);
|
||
|
}
|
||
|
Py_XDECREF(exception);
|
||
|
Py_XDECREF(v);
|
||
|
Py_XDECREF(tb);
|
||
|
|
||
|
if (f != NULL && f != Py_None) {
|
||
|
PyFile_WriteString("\nFrom: " _CFFI_MODULE_NAME
|
||
|
"\ncompiled with cffi version: 1.16.0"
|
||
|
"\n_cffi_backend module: ", f);
|
||
|
modules = PyImport_GetModuleDict();
|
||
|
mod = PyDict_GetItemString(modules, "_cffi_backend");
|
||
|
if (mod == NULL) {
|
||
|
PyFile_WriteString("not loaded", f);
|
||
|
}
|
||
|
else {
|
||
|
v = PyObject_GetAttrString(mod, "__file__");
|
||
|
PyFile_WriteObject(v, f, 0);
|
||
|
Py_XDECREF(v);
|
||
|
}
|
||
|
PyFile_WriteString("\nsys.path: ", f);
|
||
|
PyFile_WriteObject(PySys_GetObject((char *)"path"), f, 0);
|
||
|
PyFile_WriteString("\n\n", f);
|
||
|
}
|
||
|
_cffi_stop_error_capture(ecap);
|
||
|
}
|
||
|
result = -1;
|
||
|
goto done;
|
||
|
}
|
||
|
|
||
|
#if PY_VERSION_HEX < 0x03080000
|
||
|
PyAPI_DATA(char *) _PyParser_TokenNames[]; /* from CPython */
|
||
|
#endif
|
||
|
|
||
|
static int _cffi_carefully_make_gil(void)
|
||
|
{
|
||
|
/* This does the basic initialization of Python. It can be called
|
||
|
completely concurrently from unrelated threads. It assumes
|
||
|
that we don't hold the GIL before (if it exists), and we don't
|
||
|
hold it afterwards.
|
||
|
|
||
|
(What it really does used to be completely different in Python 2
|
||
|
and Python 3, with the Python 2 solution avoiding the spin-lock
|
||
|
around the Py_InitializeEx() call. However, after recent changes
|
||
|
to CPython 2.7 (issue #358) it no longer works. So we use the
|
||
|
Python 3 solution everywhere.)
|
||
|
|
||
|
This initializes Python by calling Py_InitializeEx().
|
||
|
Important: this must not be called concurrently at all.
|
||
|
So we use a global variable as a simple spin lock. This global
|
||
|
variable must be from 'libpythonX.Y.so', not from this
|
||
|
cffi-based extension module, because it must be shared from
|
||
|
different cffi-based extension modules.
|
||
|
|
||
|
In Python < 3.8, we choose
|
||
|
_PyParser_TokenNames[0] as a completely arbitrary pointer value
|
||
|
that is never written to. The default is to point to the
|
||
|
string "ENDMARKER". We change it temporarily to point to the
|
||
|
next character in that string. (Yes, I know it's REALLY
|
||
|
obscure.)
|
||
|
|
||
|
In Python >= 3.8, this string array is no longer writable, so
|
||
|
instead we pick PyCapsuleType.tp_version_tag. We can't change
|
||
|
Python < 3.8 because someone might use a mixture of cffi
|
||
|
embedded modules, some of which were compiled before this file
|
||
|
changed.
|
||
|
|
||
|
In Python >= 3.12, this stopped working because that particular
|
||
|
tp_version_tag gets modified during interpreter startup. It's
|
||
|
arguably a bad idea before 3.12 too, but again we can't change
|
||
|
that because someone might use a mixture of cffi embedded
|
||
|
modules, and no-one reported a bug so far. In Python >= 3.12
|
||
|
we go instead for PyCapsuleType.tp_as_buffer, which is supposed
|
||
|
to always be NULL. We write to it temporarily a pointer to
|
||
|
a struct full of NULLs, which is semantically the same.
|
||
|
*/
|
||
|
|
||
|
#ifdef WITH_THREAD
|
||
|
# if PY_VERSION_HEX < 0x03080000
|
||
|
char *volatile *lock = (char *volatile *)_PyParser_TokenNames;
|
||
|
char *old_value, *locked_value;
|
||
|
|
||
|
while (1) { /* spin loop */
|
||
|
old_value = *lock;
|
||
|
locked_value = old_value + 1;
|
||
|
if (old_value[0] == 'E') {
|
||
|
assert(old_value[1] == 'N');
|
||
|
if (cffi_compare_and_swap(lock, old_value, locked_value))
|
||
|
break;
|
||
|
}
|
||
|
else {
|
||
|
assert(old_value[0] == 'N');
|
||
|
/* should ideally do a spin loop instruction here, but
|
||
|
hard to do it portably and doesn't really matter I
|
||
|
think: PyEval_InitThreads() should be very fast, and
|
||
|
this is only run at start-up anyway. */
|
||
|
}
|
||
|
}
|
||
|
# else
|
||
|
# if PY_VERSION_HEX < 0x030C0000
|
||
|
int volatile *lock = (int volatile *)&PyCapsule_Type.tp_version_tag;
|
||
|
int old_value, locked_value = -42;
|
||
|
assert(!(PyCapsule_Type.tp_flags & Py_TPFLAGS_HAVE_VERSION_TAG));
|
||
|
# else
|
||
|
static struct ebp_s { PyBufferProcs buf; int mark; } empty_buffer_procs;
|
||
|
empty_buffer_procs.mark = -42;
|
||
|
PyBufferProcs *volatile *lock = (PyBufferProcs *volatile *)
|
||
|
&PyCapsule_Type.tp_as_buffer;
|
||
|
PyBufferProcs *old_value, *locked_value = &empty_buffer_procs.buf;
|
||
|
# endif
|
||
|
|
||
|
while (1) { /* spin loop */
|
||
|
old_value = *lock;
|
||
|
if (old_value == 0) {
|
||
|
if (cffi_compare_and_swap(lock, old_value, locked_value))
|
||
|
break;
|
||
|
}
|
||
|
else {
|
||
|
# if PY_VERSION_HEX < 0x030C0000
|
||
|
assert(old_value == locked_value);
|
||
|
# else
|
||
|
/* The pointer should point to a possibly different
|
||
|
empty_buffer_procs from another C extension module */
|
||
|
assert(((struct ebp_s *)old_value)->mark == -42);
|
||
|
# endif
|
||
|
/* should ideally do a spin loop instruction here, but
|
||
|
hard to do it portably and doesn't really matter I
|
||
|
think: PyEval_InitThreads() should be very fast, and
|
||
|
this is only run at start-up anyway. */
|
||
|
}
|
||
|
}
|
||
|
# endif
|
||
|
#endif
|
||
|
|
||
|
/* call Py_InitializeEx() */
|
||
|
if (!Py_IsInitialized()) {
|
||
|
_cffi_py_initialize();
|
||
|
#if PY_VERSION_HEX < 0x03070000
|
||
|
PyEval_InitThreads();
|
||
|
#endif
|
||
|
PyEval_SaveThread(); /* release the GIL */
|
||
|
/* the returned tstate must be the one that has been stored into the
|
||
|
autoTLSkey by _PyGILState_Init() called from Py_Initialize(). */
|
||
|
}
|
||
|
else {
|
||
|
#if PY_VERSION_HEX < 0x03070000
|
||
|
/* PyEval_InitThreads() is always a no-op from CPython 3.7 */
|
||
|
PyGILState_STATE state = PyGILState_Ensure();
|
||
|
PyEval_InitThreads();
|
||
|
PyGILState_Release(state);
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
#ifdef WITH_THREAD
|
||
|
/* release the lock */
|
||
|
while (!cffi_compare_and_swap(lock, locked_value, old_value))
|
||
|
;
|
||
|
#endif
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/********** end CPython-specific section **********/
|
||
|
|
||
|
|
||
|
#else
|
||
|
|
||
|
|
||
|
/********** PyPy-specific section **********/
|
||
|
|
||
|
PyMODINIT_FUNC _CFFI_PYTHON_STARTUP_FUNC(const void *[]); /* forward */
|
||
|
|
||
|
static struct _cffi_pypy_init_s {
|
||
|
const char *name;
|
||
|
void *func; /* function pointer */
|
||
|
const char *code;
|
||
|
} _cffi_pypy_init = {
|
||
|
_CFFI_MODULE_NAME,
|
||
|
_CFFI_PYTHON_STARTUP_FUNC,
|
||
|
_CFFI_PYTHON_STARTUP_CODE,
|
||
|
};
|
||
|
|
||
|
extern int pypy_carefully_make_gil(const char *);
|
||
|
extern int pypy_init_embedded_cffi_module(int, struct _cffi_pypy_init_s *);
|
||
|
|
||
|
static int _cffi_carefully_make_gil(void)
|
||
|
{
|
||
|
return pypy_carefully_make_gil(_CFFI_MODULE_NAME);
|
||
|
}
|
||
|
|
||
|
static int _cffi_initialize_python(void)
|
||
|
{
|
||
|
return pypy_init_embedded_cffi_module(0xB011, &_cffi_pypy_init);
|
||
|
}
|
||
|
|
||
|
/********** end PyPy-specific section **********/
|
||
|
|
||
|
|
||
|
#endif
|
||
|
|
||
|
|
||
|
#ifdef __GNUC__
|
||
|
__attribute__((noinline))
|
||
|
#endif
|
||
|
static _cffi_call_python_fnptr _cffi_start_python(void)
|
||
|
{
|
||
|
/* Delicate logic to initialize Python. This function can be
|
||
|
called multiple times concurrently, e.g. when the process calls
|
||
|
its first ``extern "Python"`` functions in multiple threads at
|
||
|
once. It can also be called recursively, in which case we must
|
||
|
ignore it. We also have to consider what occurs if several
|
||
|
different cffi-based extensions reach this code in parallel
|
||
|
threads---it is a different copy of the code, then, and we
|
||
|
can't have any shared global variable unless it comes from
|
||
|
'libpythonX.Y.so'.
|
||
|
|
||
|
Idea:
|
||
|
|
||
|
* _cffi_carefully_make_gil(): "carefully" call
|
||
|
PyEval_InitThreads() (possibly with Py_InitializeEx() first).
|
||
|
|
||
|
* then we use a (local) custom lock to make sure that a call to this
|
||
|
cffi-based extension will wait if another call to the *same*
|
||
|
extension is running the initialization in another thread.
|
||
|
It is reentrant, so that a recursive call will not block, but
|
||
|
only one from a different thread.
|
||
|
|
||
|
* then we grab the GIL and (Python 2) we call Py_InitializeEx().
|
||
|
At this point, concurrent calls to Py_InitializeEx() are not
|
||
|
possible: we have the GIL.
|
||
|
|
||
|
* do the rest of the specific initialization, which may
|
||
|
temporarily release the GIL but not the custom lock.
|
||
|
Only release the custom lock when we are done.
|
||
|
*/
|
||
|
static char called = 0;
|
||
|
|
||
|
if (_cffi_carefully_make_gil() != 0)
|
||
|
return NULL;
|
||
|
|
||
|
_cffi_acquire_reentrant_mutex();
|
||
|
|
||
|
/* Here the GIL exists, but we don't have it. We're only protected
|
||
|
from concurrency by the reentrant mutex. */
|
||
|
|
||
|
/* This file only initializes the embedded module once, the first
|
||
|
time this is called, even if there are subinterpreters. */
|
||
|
if (!called) {
|
||
|
called = 1; /* invoke _cffi_initialize_python() only once,
|
||
|
but don't set '_cffi_call_python' right now,
|
||
|
otherwise concurrent threads won't call
|
||
|
this function at all (we need them to wait) */
|
||
|
if (_cffi_initialize_python() == 0) {
|
||
|
/* now initialization is finished. Switch to the fast-path. */
|
||
|
|
||
|
/* We would like nobody to see the new value of
|
||
|
'_cffi_call_python' without also seeing the rest of the
|
||
|
data initialized. However, this is not possible. But
|
||
|
the new value of '_cffi_call_python' is the function
|
||
|
'cffi_call_python()' from _cffi_backend. So: */
|
||
|
cffi_write_barrier();
|
||
|
/* ^^^ we put a write barrier here, and a corresponding
|
||
|
read barrier at the start of cffi_call_python(). This
|
||
|
ensures that after that read barrier, we see everything
|
||
|
done here before the write barrier.
|
||
|
*/
|
||
|
|
||
|
assert(_cffi_call_python_org != NULL);
|
||
|
_cffi_call_python = (_cffi_call_python_fnptr)_cffi_call_python_org;
|
||
|
}
|
||
|
else {
|
||
|
/* initialization failed. Reset this to NULL, even if it was
|
||
|
already set to some other value. Future calls to
|
||
|
_cffi_start_python() are still forced to occur, and will
|
||
|
always return NULL from now on. */
|
||
|
_cffi_call_python_org = NULL;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
_cffi_release_reentrant_mutex();
|
||
|
|
||
|
return (_cffi_call_python_fnptr)_cffi_call_python_org;
|
||
|
}
|
||
|
|
||
|
static
|
||
|
void _cffi_start_and_call_python(struct _cffi_externpy_s *externpy, char *args)
|
||
|
{
|
||
|
_cffi_call_python_fnptr fnptr;
|
||
|
int current_err = errno;
|
||
|
#ifdef _MSC_VER
|
||
|
int current_lasterr = GetLastError();
|
||
|
#endif
|
||
|
fnptr = _cffi_start_python();
|
||
|
if (fnptr == NULL) {
|
||
|
fprintf(stderr, "function %s() called, but initialization code "
|
||
|
"failed. Returning 0.\n", externpy->name);
|
||
|
memset(args, 0, externpy->size_of_result);
|
||
|
}
|
||
|
#ifdef _MSC_VER
|
||
|
SetLastError(current_lasterr);
|
||
|
#endif
|
||
|
errno = current_err;
|
||
|
|
||
|
if (fnptr != NULL)
|
||
|
fnptr(externpy, args);
|
||
|
}
|
||
|
|
||
|
|
||
|
/* The cffi_start_python() function makes sure Python is initialized
|
||
|
and our cffi module is set up. It can be called manually from the
|
||
|
user C code. The same effect is obtained automatically from any
|
||
|
dll-exported ``extern "Python"`` function. This function returns
|
||
|
-1 if initialization failed, 0 if all is OK. */
|
||
|
_CFFI_UNUSED_FN
|
||
|
static int cffi_start_python(void)
|
||
|
{
|
||
|
if (_cffi_call_python == &_cffi_start_and_call_python) {
|
||
|
if (_cffi_start_python() == NULL)
|
||
|
return -1;
|
||
|
}
|
||
|
cffi_read_barrier();
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
#undef cffi_compare_and_swap
|
||
|
#undef cffi_write_barrier
|
||
|
#undef cffi_read_barrier
|
||
|
|
||
|
#ifdef __cplusplus
|
||
|
}
|
||
|
#endif
|