diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 31ac4fa3..56cdab09 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -47,7 +47,7 @@ jobs: rm -rf dist/ python -m pip install build python -m build --sdist - + echo "sdist_artifact_name=$(ls ./dist)" >> "$GITHUB_OUTPUT" echo "package_version=$(ls ./dist | sed -En 's/cffi-(.+)\.tar\.gz/\1/p')" >> "$GITHUB_OUTPUT" @@ -76,20 +76,20 @@ jobs: - spec: cp39-manylinux_x86_64 omit: ${{ env.skip_ci_redundant_jobs }} - + - spec: cp310-manylinux_x86_64 omit: ${{ env.skip_ci_redundant_jobs }} - spec: cp311-manylinux_x86_64 omit: ${{ env.skip_ci_redundant_jobs }} - + - spec: cp312-manylinux_x86_64 omit: ${{ env.skip_ci_redundant_jobs }} - spec: cp313-manylinux_x86_64 - + - spec: cp313t-manylinux_x86_64 - + - spec: cp38-manylinux_i686 omit: ${{ env.skip_ci_redundant_jobs }} @@ -107,10 +107,13 @@ jobs: - spec: cp313-manylinux_i686 omit: ${{ env.skip_ci_redundant_jobs }} - + + - spec: cp313t-manylinux_i686 + omit: ${{ env.skip_ci_redundant_jobs }} + - spec: cp39-musllinux_x86_64 omit: ${{ env.skip_ci_redundant_jobs }} - + - spec: cp310-musllinux_x86_64 omit: ${{ env.skip_ci_redundant_jobs }} @@ -119,9 +122,11 @@ jobs: - spec: cp312-musllinux_x86_64 omit: ${{ env.skip_ci_redundant_jobs }} - + - spec: cp313-musllinux_x86_64 - + + - spec: cp313t-musllinux_x86_64 + - spec: cp39-musllinux_i686 omit: ${{ env.skip_ci_redundant_jobs }} @@ -133,14 +138,14 @@ jobs: #- spec: cp312-musllinux_i686 # busted as of 2024-05-17 # omit: ${{ env.skip_ci_redundant_jobs }} - + #- spec: cp313-musllinux_i686 # busted as of 2024-05-17 - + - spec: cp39-musllinux_aarch64 foreign_arch: true test_args: '{package}/src/c' omit: ${{ env.skip_ci_redundant_jobs || env.skip_slow_jobs }} - + - spec: cp310-musllinux_aarch64 foreign_arch: true test_args: '{package}/src/c' @@ -155,12 +160,17 @@ jobs: foreign_arch: true test_args: '{package}/src/c' omit: ${{ env.skip_ci_redundant_jobs || env.skip_slow_jobs }} - + - spec: cp313-musllinux_aarch64 foreign_arch: true # test_args: '{package}/src/c' omit: ${{ env.skip_slow_jobs}} - + + - spec: cp313t-musllinux_aarch64 + foreign_arch: true + # test_args: '{package}/src/c' + omit: ${{ env.skip_slow_jobs}} + - spec: cp38-manylinux_aarch64 foreign_arch: true test_args: '{package}/src/c' @@ -185,12 +195,17 @@ jobs: foreign_arch: true test_args: '{package}/src/c' omit: ${{ env.skip_slow_jobs || env.skip_ci_redundant_jobs }} - + - spec: cp313-manylinux_aarch64 foreign_arch: true # test_args: '{package}/src/c' omit: ${{ env.skip_slow_jobs }} - + + - spec: cp313t-manylinux_aarch64 + foreign_arch: true + # test_args: '{package}/src/c' + omit: ${{ env.skip_slow_jobs }} + - spec: cp38-manylinux_ppc64le foreign_arch: true test_args: '{package}/src/c' @@ -220,7 +235,12 @@ jobs: foreign_arch: true test_args: '{package}/src/c' omit: ${{ env.skip_slow_jobs }} - + + - spec: cp313t-manylinux_ppc64le + foreign_arch: true + test_args: '{package}/src/c' + omit: ${{ env.skip_slow_jobs }} + - spec: cp38-manylinux_s390x foreign_arch: true test_args: '{package}/src/c' @@ -251,6 +271,11 @@ jobs: # test_args: '{package}/src/c' omit: ${{ env.skip_slow_jobs }} + - spec: cp313t-manylinux_s390x + foreign_arch: true + # test_args: '{package}/src/c' + omit: ${{ env.skip_slow_jobs }} + linux: needs: [python_sdist, make_linux_matrix] @@ -301,11 +326,11 @@ jobs: CIBW_TEST_COMMAND: PYTHONUNBUFFERED=1 python -m pytest ${{ matrix.test_args || '{project}' }} # default to test all run: | set -eux - + mkdir cffi - + tar zxf ${{ steps.fetch_sdist.outputs.download-path }}/cffi*.tar.gz --strip-components=1 -C cffi - python -m pip install --upgrade "${{ matrix.cibw_version || 'cibuildwheel' }}" + python -m pip install cibuildwheel==2.23.3 # actually build libffi + wheel (using env tweaks above) python -m cibuildwheel --output-dir dist ./cffi @@ -408,7 +433,7 @@ jobs: - name: build wheel prereqs run: | set -eux - python3 -m pip install --user --upgrade "${{ matrix.cibw_version || 'cibuildwheel' }}" + python3 -m pip install --user --upgrade cibuildwheel==2.23.3 brew uninstall --ignore-dependencies libffi 2>&1 || true - name: build/test wheels @@ -416,19 +441,20 @@ jobs: env: CIBW_BUILD: ${{ matrix.spec }} CIBW_PRERELEASE_PYTHONS: 'True' + CIBW_FREE_THREADED_SUPPORT: 'True' CIBW_TEST_REQUIRES: pytest setuptools CIBW_TEST_COMMAND: pip install pip --upgrade; cd {project}; PYTHONUNBUFFERED=1 pytest MACOSX_DEPLOYMENT_TARGET: ${{ matrix.deployment_target || '10.9' }} SDKROOT: ${{ matrix.sdkroot || 'macosx' }} run: | set -eux - + mkdir cffi - + tar zxf ${{ steps.fetch_sdist.outputs.download-path }}/cffi*.tar.gz --strip-components=1 -C cffi python3 -m cibuildwheel --output-dir dist cffi - + echo "artifact_name=$(ls ./dist/)" >> "$GITHUB_OUTPUT" - name: upload artifacts @@ -468,7 +494,8 @@ jobs: omit: ${{ env.skip_ci_redundant_jobs }} - spec: cp313-win_amd64 - # omit: ${{ env.skip_ci_redundant_jobs }} + + - spec: cp313t-win_amd64 - spec: cp38-win32 omit: ${{ env.skip_ci_redundant_jobs }} @@ -488,6 +515,9 @@ jobs: - spec: cp313-win32 omit: ${{ env.skip_ci_redundant_jobs }} + - spec: cp313t-win32 + omit: ${{ env.skip_ci_redundant_jobs }} + windows: needs: [python_sdist, make_windows_matrix] runs-on: windows-2022 @@ -507,21 +537,22 @@ jobs: env: CIBW_BUILD: ${{ matrix.spec }} CIBW_PRERELEASE_PYTHONS: 'True' + CIBW_FREE_THREADED_SUPPORT: 'True' CIBW_TEST_REQUIRES: pytest setuptools CIBW_TEST_COMMAND: 'python -m pytest {package}/src/c' # FIXME: /testing takes ~45min on Windows and has some failures... # CIBW_TEST_COMMAND='python -m pytest {package}/src/c {project}/testing' run: | set -eux - + mkdir cffi - + tar zxf cffi*.tar.gz --strip-components=1 -C cffi - + python -m pip install --upgrade pip - pip install "${{ matrix.cibw_version || 'cibuildwheel'}}" + pip install cibuildwheel==2.23.3 python -m cibuildwheel --output-dir dist cffi - + echo "artifact_name=$(ls ./dist/)" >> "$GITHUB_OUTPUT" shell: bash @@ -545,10 +576,55 @@ jobs: delete-merged: true if: ${{ env.skip_artifact_upload != 'true' }} + pytest-run-parallel: + strategy: + matrix: + runner-image: ['ubuntu-latest', 'macos-latest', 'windows-latest'] + python-version: ['3.13t', '3.14t-dev'] + runs-on: ${{ matrix.runner-image }} + steps: + - name: clone repo + uses: actions/checkout@v4 + + - name: install python + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: build and install + run: | + python -m pip install pytest setuptools pytest-run-parallel + python -m pip install . + + - name: run tests under pytest-run-parallel + if: runner.os == 'Windows' + run: | + python -m pytest --parallel-threads=4 src/c + + - name: run tests under pytest-run-parallel + if: runner.os != 'Windows' + run: | + python -m pytest --parallel-threads=4 + + clang_TSAN: + runs-on: ubuntu-latest + container: ghcr.io/nascheme/numpy-tsan:3.14t + steps: + - uses: actions/checkout@v4 + + - name: build and install + run: | + python -m pip install setuptools pytest pytest-run-parallel + CFLAGS="-g -O3 -fsanitize=thread" python -m pip install -v . + + - name: run tests under pytest-run-parallel + run: | + TSAN_OPTIONS="suppressions=$PWD/suppressions_free_threading.txt" \ + python -m pytest --parallel-threads=4 --skip-thread-unsafe=True -sv check: if: always() - needs: [python_sdist, linux, macos, windows, merge_artifacts] + needs: [python_sdist, linux, macos, windows, clang_TSAN, pytest-run-parallel, merge_artifacts] runs-on: ubuntu-latest steps: - name: Verify all previous jobs succeeded (provides a single check to sample for gating purposes) diff --git a/src/c/_cffi_backend.c b/src/c/_cffi_backend.c index 0482bb67..53ed95eb 100644 --- a/src/c/_cffi_backend.c +++ b/src/c/_cffi_backend.c @@ -1,7 +1,7 @@ #define PY_SSIZE_T_CLEAN #include #include "structmember.h" - +#include "misc_thread_common.h" #define CFFI_VERSION "1.18.0.dev0" #ifdef MS_WIN32 @@ -14,6 +14,7 @@ #include #include #include +#include "misc_thread_posix.h" #endif /* this block of #ifs should be kept exactly identical between @@ -238,10 +239,6 @@ static int PyWeakref_GetRef(PyObject *ref, PyObject **pobj) } #endif -#if PY_VERSION_HEX <= 0x030d00b3 -# define Py_BEGIN_CRITICAL_SECTION(op) { -# define Py_END_CRITICAL_SECTION() } -#endif #ifdef Py_GIL_DISABLED # define LOCK_UNIQUE_CACHE() PyMutex_Lock(&unique_cache_lock) @@ -277,7 +274,7 @@ static int PyWeakref_GetRef(PyObject *ref, PyObject **pobj) #define CT_IS_BOOL 0x00080000 #define CT_IS_FILE 0x00100000 #define CT_IS_VOID_PTR 0x00200000 -#define CT_WITH_VAR_ARRAY 0x00400000 /* with open-ended array, anywhere */ +/* unused 0x00400000 */ /* unused 0x00800000 */ /* unused 0x01000000 */ /* unused 0x02000000 */ @@ -289,16 +286,6 @@ static int PyWeakref_GetRef(PyObject *ref, PyObject **pobj) CT_PRIMITIVE_COMPLEX) -/* - Values for mutable ct_flags_mut flags. - - These may be set transiently if fields are lazily constructed. -*/ -#define CT_LAZY_FIELD_LIST 0x00000001 -#define CT_UNDER_CONSTRUCTION 0x00000002 -#define CT_CUSTOM_FIELD_POS 0x00000004 -#define CT_WITH_PACKED_CHANGE 0x00000008 - typedef struct _ctypedescr { PyObject_VAR_HEAD @@ -322,8 +309,12 @@ typedef struct _ctypedescr { or alignment of primitive and struct types; always -1 for pointers */ int ct_flags; /* Immutable CT_xxx flags */ - int ct_flags_mut; /* Mutable flags (e.g., CT_LAZY_FIELD_LIST) */ - + uint8_t ct_under_construction; + uint8_t ct_lazy_field_list; + uint8_t ct_unrealized_struct_or_union; + uint8_t ct_custom_field_pos; + uint8_t ct_with_packed_change; + uint8_t ct_with_var_array; /* with open-ended array, anywhere */ int ct_name_position; /* index in ct_name of where to put a var name */ char ct_name[1]; /* string, e.g. "int *" for pointers to ints */ } CTypeDescrObject; @@ -478,6 +469,12 @@ ctypedescr_new(int name_size) ct->ct_stuff = NULL; ct->ct_weakreflist = NULL; ct->ct_unique_key = NULL; + ct->ct_lazy_field_list = 0; + ct->ct_under_construction = 0; + ct->ct_unrealized_struct_or_union = 0; + ct->ct_custom_field_pos = 0; + ct->ct_with_packed_change = 0; + ct->ct_with_var_array = 0; PyObject_GC_Track(ct); return ct; } @@ -622,18 +619,32 @@ static PyObject *ctypeget_length(CTypeDescrObject *ct, void *context) static PyObject * get_field_name(CTypeDescrObject *ct, CFieldObject *cf); /* forward */ +static int do_realize_lazy_struct(CTypeDescrObject *ct); +/* forward, implemented in realize_c_type.c */ + /* returns 0 if the struct ctype is opaque, 1 if it is not, or -1 if an exception occurs */ -#define force_lazy_struct(ct) \ - ((ct)->ct_stuff != NULL ? 1 : do_realize_lazy_struct(ct)) +static inline int +force_lazy_struct(CTypeDescrObject *ct) +{ +#ifdef Py_GIL_DISABLED + PyObject *ct_stuff = cffi_atomic_load((void**)&ct->ct_stuff); +#else + PyObject *ct_stuff = ct->ct_stuff; +#endif + if (ct_stuff != NULL) { + /* already realized */ + return 1; + } + return do_realize_lazy_struct(ct); +} -static int do_realize_lazy_struct(CTypeDescrObject *ct); -/* forward, implemented in realize_c_type.c */ static PyObject *ctypeget_fields(CTypeDescrObject *ct, void *context) { if (ct->ct_flags & (CT_STRUCT | CT_UNION)) { - if (!(ct->ct_flags & CT_IS_OPAQUE)) { + assert((ct->ct_flags & CT_IS_OPAQUE) == 0); + if (!cffi_check_flag(ct->ct_unrealized_struct_or_union)) { CFieldObject *cf; PyObject *res; if (force_lazy_struct(ct) < 0) @@ -890,7 +901,7 @@ _my_PyLong_AsLongLong(PyObject *ob) if (PyInt_Check(ob)) { return PyInt_AS_LONG(ob); } - else + else #endif if (PyLong_Check(ob)) { return PyLong_AsLongLong(ob); @@ -1161,13 +1172,9 @@ convert_to_object(char *data, CTypeDescrObject *ct) ct->ct_name); return NULL; } - else if (ct->ct_flags_mut & CT_UNDER_CONSTRUCTION) { - PyErr_Format(PyExc_TypeError, - "'%s' is not completed yet", - ct->ct_name); - return NULL; - } else if (ct->ct_flags & (CT_STRUCT|CT_UNION)) { + if (force_lazy_struct(ct) < 0) + return NULL; return new_simple_cdata(data, ct); } else if (ct->ct_flags & CT_ARRAY) { @@ -1508,7 +1515,7 @@ convert_vfield_from_object(char *data, CFieldObject *cf, PyObject *value, if (optvarsize == NULL) { return convert_field_from_object(data, cf, value); } - else if ((cf->cf_type->ct_flags & CT_WITH_VAR_ARRAY) != 0 && + else if (cffi_check_flag(cf->cf_type->ct_with_var_array) != 0 && !CData_Check(value)) { Py_ssize_t subsize = cf->cf_type->ct_size; if (convert_struct_from_object(NULL, cf->cf_type, value, &subsize) < 0) @@ -1679,7 +1686,11 @@ convert_struct_from_object(char *data, CTypeDescrObject *ct, PyObject *init, CFieldObject *cf; while (PyDict_Next(init, &i, &d_key, &d_value)) { +#ifdef Py_GIL_DISABLED + cf = (CFieldObject *)PyDict_GetItem((PyObject *)cffi_atomic_load((void **)&ct->ct_stuff), d_key); +#else cf = (CFieldObject *)PyDict_GetItem(ct->ct_stuff, d_key); +#endif if (cf == NULL) { PyErr_SetObject(PyExc_KeyError, d_key); return -1; @@ -1856,8 +1867,8 @@ convert_from_object(char *data, CTypeDescrObject *ct, PyObject *init) if (ct->ct_flags & (CT_STRUCT|CT_UNION)) { if (CData_Check(init)) { - if (((CDataObject *)init)->c_type == ct && ct->ct_size >= 0) { - memcpy(data, ((CDataObject *)init)->c_data, ct->ct_size); + if (((CDataObject *)init)->c_type == ct && cffi_get_size(ct) >= 0) { + memcpy(data, ((CDataObject *)init)->c_data, cffi_get_size(ct)); return 0; } } @@ -1944,12 +1955,13 @@ get_alignment(CTypeDescrObject *ct) int align; retry: if ((ct->ct_flags & (CT_PRIMITIVE_ANY|CT_STRUCT|CT_UNION)) && - !(ct->ct_flags & CT_IS_OPAQUE)) { - align = ct->ct_length; - if (align == -1 && (ct->ct_flags_mut & CT_LAZY_FIELD_LIST)) { - force_lazy_struct(ct); - align = ct->ct_length; + !((ct->ct_flags & CT_IS_OPAQUE) || cffi_check_flag(ct->ct_unrealized_struct_or_union))) { + if (cffi_check_flag(ct->ct_lazy_field_list)) { + if (force_lazy_struct(ct) < 0) { + return -1; + } } + align = ct->ct_length; } else if (ct->ct_flags & (CT_POINTER|CT_FUNCTIONPTR)) { struct aligncheck_ptr { char x; char *y; }; @@ -2263,7 +2275,7 @@ static Py_ssize_t _cdata_var_byte_size(CDataObject *cd) if (cd->c_type->ct_flags & CT_IS_PTR_TO_OWNED) { cd = (CDataObject *)((CDataObject_own_structptr *)cd)->structobj; } - if (cd->c_type->ct_flags & CT_WITH_VAR_ARRAY) { + if (cffi_check_flag(cd->c_type->ct_with_var_array)) { return ((CDataObject_own_length *)cd)->length; } return -1; @@ -2307,7 +2319,7 @@ static Py_ssize_t cdataowning_size_bytes(CDataObject *cd) else if (cd->c_type->ct_flags & CT_ARRAY) size = get_array_length(cd) * cd->c_type->ct_itemdescr->ct_size; else - size = cd->c_type->ct_size; + size = cffi_get_size(cd->c_type); } return size; } @@ -2600,7 +2612,7 @@ _cdata_get_indexed_ptr(CDataObject *cd, PyObject *key) cd->c_type->ct_name); return NULL; } - return cd->c_data + i * cd->c_type->ct_itemdescr->ct_size; + return cd->c_data + i * cffi_get_size(cd->c_type->ct_itemdescr); } static PyObject * @@ -2667,19 +2679,25 @@ cdata_slice(CDataObject *cd, PySliceObject *slice) CTypeDescrObject *ct = _cdata_getslicearg(cd, slice, bounds); if (ct == NULL) return NULL; - + CTypeDescrObject *array_type = NULL; Py_BEGIN_CRITICAL_SECTION(ct); + array_type = (CTypeDescrObject *)ct->ct_stuff; if (ct->ct_stuff == NULL) { - ct->ct_stuff = new_array_type(ct, -1); + array_type = (CTypeDescrObject *)new_array_type(ct, -1); +#ifdef Py_GIL_DISABLED + cffi_atomic_store((void **)&ct->ct_stuff, array_type); +#else + ct->ct_stuff = (PyObject *)array_type; +#endif } Py_END_CRITICAL_SECTION(); - if (ct->ct_stuff == NULL) + if (array_type == NULL) { return NULL; - ct = (CTypeDescrObject *)ct->ct_stuff; + } - cdata = cd->c_data + ct->ct_itemdescr->ct_size * bounds[0]; - return new_sized_cdata(cdata, ct, bounds[1]); + cdata = cd->c_data + array_type->ct_itemdescr->ct_size * bounds[0]; + return new_sized_cdata(cdata, array_type, bounds[1]); } static int @@ -2862,7 +2880,7 @@ _cdata_add_or_sub(PyObject *v, PyObject *w, int sign) cd->c_type->ct_name); return NULL; } - itemsize = ctptr->ct_itemdescr->ct_size; + itemsize = cffi_get_size(ctptr->ct_itemdescr); if (itemsize < 0) { if (ctptr->ct_flags & CT_IS_VOID_PTR) { itemsize = 1; @@ -2955,7 +2973,11 @@ cdata_getattro(CDataObject *cd, PyObject *attr) if (ct->ct_flags & (CT_STRUCT|CT_UNION)) { switch (force_lazy_struct(ct)) { case 1: +#ifdef Py_GIL_DISABLED + cf = (CFieldObject *)PyDict_GetItem((PyObject *)cffi_atomic_load((void **)&ct->ct_stuff), attr); +#else cf = (CFieldObject *)PyDict_GetItem(ct->ct_stuff, attr); +#endif if (cf != NULL) { /* read the field 'cf' */ char *data = cd->c_data + cf->cf_offset; @@ -3008,7 +3030,11 @@ cdata_setattro(CDataObject *cd, PyObject *attr, PyObject *value) if (ct->ct_flags & (CT_STRUCT|CT_UNION)) { switch (force_lazy_struct(ct)) { case 1: +#ifdef Py_GIL_DISABLED + cf = (CFieldObject *)PyDict_GetItem((PyObject *)cffi_atomic_load((void **)&ct->ct_stuff), attr); +#else cf = (CFieldObject *)PyDict_GetItem(ct->ct_stuff, attr); +#endif if (cf != NULL) { /* write the field 'cf' */ if (value != NULL) { @@ -3108,6 +3134,11 @@ _prepare_pointer_call_argument(CTypeDescrObject *ctptr, PyObject *init, goto convert_default; } + if (ctitem->ct_flags & (CT_STRUCT|CT_UNION)) { + if (force_lazy_struct(ctitem) < 0) { + return -1; + } + } if (ctitem->ct_size <= 0) goto convert_default; datasize = MUL_WRAPAROUND(length, ctitem->ct_size); @@ -3342,11 +3373,12 @@ static PyObject *cdata_dir(PyObject *cd, PyObject *noarg) ct = ct->ct_itemdescr; } if ((ct->ct_flags & (CT_STRUCT | CT_UNION)) && - !(ct->ct_flags & CT_IS_OPAQUE)) { - + !(cffi_check_flag(ct->ct_unrealized_struct_or_union))) { + assert((ct->ct_flags & CT_IS_OPAQUE) == 0); /* for non-opaque structs or unions */ if (force_lazy_struct(ct) < 0) return NULL; + assert(ct->ct_stuff); return PyDict_Keys(ct->ct_stuff); } else { @@ -3845,7 +3877,7 @@ convert_struct_to_owning_object(char *data, CTypeDescrObject *ct) "return type is an opaque structure or union"); return NULL; } - if (ct->ct_flags & CT_WITH_VAR_ARRAY) { + if (cffi_check_flag(ct->ct_with_var_array)) { PyErr_SetString(PyExc_TypeError, "return type is a struct/union with a varsize array member"); return NULL; @@ -3939,7 +3971,11 @@ static PyObject *direct_newp(CTypeDescrObject *ct, PyObject *init, if (ct->ct_flags & CT_POINTER) { dataoffset = offsetof(CDataObject_own_nolength, alignment); ctitem = ct->ct_itemdescr; - datasize = ctitem->ct_size; + if (ctitem->ct_flags & (CT_STRUCT | CT_UNION)) { + if (force_lazy_struct(ctitem) < 0) + return NULL; + } + datasize = cffi_get_size(ctitem); if (datasize < 0) { PyErr_Format(PyExc_TypeError, "cannot instantiate ctype '%s' of unknown size", @@ -3950,10 +3986,7 @@ static PyObject *direct_newp(CTypeDescrObject *ct, PyObject *init, datasize *= 2; /* forcefully add another character: a null */ if (ctitem->ct_flags & (CT_STRUCT | CT_UNION)) { - if (force_lazy_struct(ctitem) < 0) /* for CT_WITH_VAR_ARRAY */ - return NULL; - - if (ctitem->ct_flags & CT_WITH_VAR_ARRAY) { + if (cffi_check_flag(ctitem->ct_with_var_array)) { assert(ct->ct_flags & CT_IS_PTR_TO_OWNED); dataoffset = offsetof(CDataObject_own_length, alignment); @@ -4575,7 +4608,7 @@ static void *b_do_dlopen(PyObject *args, const char **p_printable_filename, int flags = 0; *p_temp = NULL; *auto_close = 1; - + if (PyTuple_GET_SIZE(args) == 0 || PyTuple_GET_ITEM(args, 0) == Py_None) { PyObject *dummy; if (!PyArg_ParseTuple(args, "|Oi:load_library", @@ -4703,7 +4736,7 @@ static PyObject *b_load_library(PyObject *self, PyObject *args) dlobj->dl_handle = handle; dlobj->dl_name = strdup(printable_filename); dlobj->dl_auto_close = auto_close; - + error: Py_XDECREF(temp); return (PyObject *)dlobj; @@ -4976,7 +5009,6 @@ static PyObject *new_primitive_type(const char *name) td->ct_length = ptypes->align; td->ct_extra = ffitype; td->ct_flags = ptypes->flags; - td->ct_flags_mut = 0; if (td->ct_flags & (CT_PRIMITIVE_SIGNED | CT_PRIMITIVE_CHAR)) { if (td->ct_size <= (Py_ssize_t)sizeof(long)) td->ct_flags |= CT_PRIMITIVE_FITS_LONG; @@ -5030,7 +5062,6 @@ static PyObject *new_pointer_type(CTypeDescrObject *ctitem) ((ctitem->ct_flags & CT_PRIMITIVE_CHAR) && ctitem->ct_size == sizeof(char))) td->ct_flags |= CT_IS_VOIDCHAR_PTR; /* 'void *' or 'char *' only */ - td->ct_flags_mut = 0; unique_key[0] = ctitem; return get_unique_type(td, unique_key, 1); } @@ -5082,7 +5113,7 @@ new_array_type(CTypeDescrObject *ctptr, Py_ssize_t length) return NULL; } ctitem = ctptr->ct_itemdescr; - if (ctitem->ct_size < 0) { + if (cffi_get_size(ctitem) < 0) { PyErr_Format(PyExc_ValueError, "array item of unknown size: '%s'", ctitem->ct_name); return NULL; @@ -5095,8 +5126,8 @@ new_array_type(CTypeDescrObject *ctptr, Py_ssize_t length) } else { sprintf(extra_text, "[%llu]", (unsigned PY_LONG_LONG)length); - arraysize = MUL_WRAPAROUND(length, ctitem->ct_size); - if (length > 0 && (arraysize / length) != ctitem->ct_size) { + arraysize = MUL_WRAPAROUND(length, cffi_get_size(ctitem)); + if (length > 0 && (arraysize / length) != cffi_get_size(ctitem)) { PyErr_SetString(PyExc_OverflowError, "array size would overflow a Py_ssize_t"); return NULL; @@ -5111,7 +5142,6 @@ new_array_type(CTypeDescrObject *ctptr, Py_ssize_t length) td->ct_size = arraysize; td->ct_length = length; td->ct_flags = flags; - td->ct_flags_mut = 0; unique_key[0] = ctptr; unique_key[1] = (void *)length; return get_unique_type(td, unique_key, 2); @@ -5128,7 +5158,6 @@ static PyObject *new_void_type(void) memcpy(td->ct_name, "void", name_size); td->ct_size = -1; td->ct_flags = CT_VOID | CT_IS_OPAQUE; - td->ct_flags_mut = 0; td->ct_name_position = strlen("void"); unique_key[0] = "void"; return get_unique_type(td, unique_key, 1); @@ -5148,9 +5177,8 @@ static PyObject *new_struct_or_union_type(const char *name, int flag) td->ct_size = -1; td->ct_length = -1; - // may be unset later if this type needs lazy init - td->ct_flags = flag | CT_IS_OPAQUE; - td->ct_flags_mut = 0; + td->ct_flags = flag; + td->ct_unrealized_struct_or_union = 1; td->ct_extra = NULL; memcpy(td->ct_name, name, namelen + 1); td->ct_name_position = namelen; @@ -5273,34 +5301,27 @@ static int detect_custom_layout(CTypeDescrObject *ct, int sflags, ct->ct_name); return -1; } - ct->ct_flags_mut |= CT_CUSTOM_FIELD_POS; + cffi_set_flag(ct->ct_custom_field_pos, 1); } return 0; } #define ROUNDUP_BYTES(bytes, bits) ((bytes) + ((bits) > 0)) -static PyObject *b_complete_struct_or_union(PyObject *self, PyObject *args) + + +static PyObject *b_complete_struct_or_union_lock_held(CTypeDescrObject *ct, + PyObject *fields, + Py_ssize_t totalsize, int totalalignment, int sflags, + int pack) { - CTypeDescrObject *ct; - PyObject *fields, *interned_fields, *ignored; int is_union, alignment; Py_ssize_t byteoffset, i, nb_fields, byteoffsetmax, alignedsize; - int bitoffset; + int bitoffset, fflags; Py_ssize_t byteoffsetorg; - Py_ssize_t totalsize = -1; - int totalalignment = -1; CFieldObject **previous; int prev_bitfield_size, prev_bitfield_free; - int sflags = 0, fflags; - int pack = 0; - - if (!PyArg_ParseTuple(args, "O!O!|Oniii:complete_struct_or_union", - &CTypeDescr_Type, &ct, - &PyList_Type, &fields, - &ignored, &totalsize, &totalalignment, &sflags, - &pack)) - return NULL; + PyObject *interned_fields; sflags = complete_sflags(sflags); if (sflags & SF_PACKED) @@ -5310,15 +5331,16 @@ static PyObject *b_complete_struct_or_union(PyObject *self, PyObject *args) else sflags |= SF_PACKED; + PyObject *res = NULL; is_union = ct->ct_flags & CT_UNION; if (!((ct->ct_flags & CT_UNION) || (ct->ct_flags & CT_STRUCT)) || - !((ct->ct_flags & CT_IS_OPAQUE) || (ct->ct_flags_mut & CT_UNDER_CONSTRUCTION))) { + !(cffi_check_flag(ct->ct_unrealized_struct_or_union) || cffi_check_flag(ct->ct_under_construction))) { PyErr_SetString(PyExc_TypeError, "first arg must be a non-initialized struct or union ctype"); - return NULL; + goto finally; } - assert((ct->ct_flags & CT_IS_OPAQUE) ^ (ct->ct_flags_mut & CT_UNDER_CONSTRUCTION)); - ct->ct_flags_mut &= ~(CT_CUSTOM_FIELD_POS | CT_WITH_PACKED_CHANGE); + cffi_set_flag(ct->ct_custom_field_pos, 0); + cffi_set_flag(ct->ct_with_packed_change, 0); alignment = 1; byteoffset = 0; /* the real value is 'byteoffset+bitoffset*8', which */ @@ -5329,7 +5351,7 @@ static PyObject *b_complete_struct_or_union(PyObject *self, PyObject *args) nb_fields = PyList_GET_SIZE(fields); interned_fields = PyDict_New(); if (interned_fields == NULL) - return NULL; + goto finally; previous = (CFieldObject **)&ct->ct_extra; @@ -5343,37 +5365,39 @@ static PyObject *b_complete_struct_or_union(PyObject *self, PyObject *args) &PyText_Type, &fname, &CTypeDescr_Type, &ftype, &fbitsize, &foffset)) - goto error; + goto finally; if ((ftype->ct_flags & (CT_STRUCT | CT_UNION)) && - !(ftype->ct_flags & CT_IS_OPAQUE)) { + !(cffi_check_flag(ct->ct_unrealized_struct_or_union))) { + assert((ftype->ct_flags & CT_IS_OPAQUE) == 0); /* force now the type of the nested field */ if (force_lazy_struct(ftype) < 0) - return NULL; + goto finally; } - if (ftype->ct_size < 0) { + if (cffi_get_size(ftype) < 0) { if ((ftype->ct_flags & CT_ARRAY) && fbitsize < 0 && (i == nb_fields - 1 || foffset != -1)) { - ct->ct_flags |= CT_WITH_VAR_ARRAY; + cffi_set_flag(ct->ct_with_var_array, 1); } else { PyErr_Format(PyExc_TypeError, "field '%s.%s' has ctype '%s' of unknown size", ct->ct_name, PyText_AS_UTF8(fname), ftype->ct_name); - goto error; + goto finally; } } else if (ftype->ct_flags & (CT_STRUCT|CT_UNION)) { /* GCC (or maybe C99) accepts var-sized struct fields that are not the last field of a larger struct. That's why there is no check here for "last field": we propagate the flag - CT_WITH_VAR_ARRAY to any struct that contains either an open- + ct_with_var_array to any struct that contains either an open- ended array or another struct that recursively contains an open-ended array. */ - if (ftype->ct_flags & CT_WITH_VAR_ARRAY) - ct->ct_flags |= CT_WITH_VAR_ARRAY; + if (cffi_check_flag(ftype->ct_with_var_array)) { + cffi_set_flag(ct->ct_with_var_array, 1); + } } if (is_union) @@ -5383,7 +5407,7 @@ static PyObject *b_complete_struct_or_union(PyObject *self, PyObject *args) field is an anonymous bitfield or if SF_PACKED */ falignorg = get_alignment(ftype); if (falignorg < 0) - goto error; + goto finally; falign = (pack < falignorg) ? pack : falignorg; do_align = 1; @@ -5421,16 +5445,16 @@ static PyObject *b_complete_struct_or_union(PyObject *self, PyObject *args) byteoffset = (byteoffset + falign-1) & ~(falign-1); if (byteoffsetorg != byteoffset) { - ct->ct_flags_mut |= CT_WITH_PACKED_CHANGE; + cffi_set_flag(ct->ct_with_packed_change, 1); } if (foffset >= 0) { /* a forced field position: ignore the offset just computed, - except to know if we must set CT_CUSTOM_FIELD_POS */ + except to know if we must set ct_custom_field_pos */ if (detect_custom_layout(ct, sflags, byteoffset, foffset, "wrong offset for field '", PyText_AS_UTF8(fname), "'") < 0) - goto error; + goto finally; byteoffset = foffset; } @@ -5450,17 +5474,17 @@ static PyObject *b_complete_struct_or_union(PyObject *self, PyObject *args) cfsrc->cf_bitsize, cfsrc->cf_flags | fflags); if (*previous == NULL) - goto error; + goto finally; previous = &(*previous)->cf_next; } /* always forbid such structures from being passed by value */ - ct->ct_flags_mut |= CT_CUSTOM_FIELD_POS; + cffi_set_flag(ct->ct_custom_field_pos, 1); } else { *previous = _add_field(interned_fields, fname, ftype, byteoffset, bs_flag, -1, fflags); if (*previous == NULL) - goto error; + goto finally; previous = &(*previous)->cf_next; } if (ftype->ct_size >= 0) @@ -5477,7 +5501,7 @@ static PyObject *b_complete_struct_or_union(PyObject *self, PyObject *args) "field '%s.%s' is a bitfield, " "but a fixed offset is specified", ct->ct_name, PyText_AS_UTF8(fname)); - goto error; + goto finally; } if (!(ftype->ct_flags & (CT_PRIMITIVE_SIGNED | @@ -5487,7 +5511,7 @@ static PyObject *b_complete_struct_or_union(PyObject *self, PyObject *args) "field '%s.%s' declared as '%s' cannot be a bit field", ct->ct_name, PyText_AS_UTF8(fname), ftype->ct_name); - goto error; + goto finally; } if (fbitsize > 8 * ftype->ct_size) { PyErr_Format(PyExc_TypeError, @@ -5495,7 +5519,7 @@ static PyObject *b_complete_struct_or_union(PyObject *self, PyObject *args) "exceeds the width of the type", ct->ct_name, PyText_AS_UTF8(fname), ftype->ct_name, fbitsize); - goto error; + goto finally; } /* compute the starting position of the theoretical field @@ -5509,7 +5533,7 @@ static PyObject *b_complete_struct_or_union(PyObject *self, PyObject *args) PyErr_Format(PyExc_TypeError, "field '%s.%s' is declared with :0", ct->ct_name, PyText_AS_UTF8(fname)); - goto error; + goto finally; } if (!(sflags & SF_MSVC_BITFIELDS)) { /* GCC's notion of "ftype :0;" */ @@ -5549,7 +5573,7 @@ static PyObject *b_complete_struct_or_union(PyObject *self, PyObject *args) "with 'packed', gcc would compile field " "'%s.%s' to reuse some bits in the previous " "field", ct->ct_name, PyText_AS_UTF8(fname)); - goto error; + goto finally; } field_offset_bytes += falign; assert(byteoffset < field_offset_bytes); @@ -5600,7 +5624,7 @@ static PyObject *b_complete_struct_or_union(PyObject *self, PyObject *args) field_offset_bytes, bitshift, fbitsize, fflags); if (*previous == NULL) - goto error; + goto finally; previous = &(*previous)->cf_next; } } @@ -5625,12 +5649,12 @@ static PyObject *b_complete_struct_or_union(PyObject *self, PyObject *args) else { if (detect_custom_layout(ct, sflags, alignedsize, totalsize, "wrong total size", "", "") < 0) - goto error; + goto finally; if (totalsize < byteoffsetmax) { PyErr_Format(PyExc_TypeError, "%s cannot be of size %zd: there are fields at least " "up to %zd", ct->ct_name, totalsize, byteoffsetmax); - goto error; + goto finally; } } if (totalalignment < 0) { @@ -5639,23 +5663,54 @@ static PyObject *b_complete_struct_or_union(PyObject *self, PyObject *args) else { if (detect_custom_layout(ct, sflags, alignment, totalalignment, "wrong total alignment", "", "") < 0) - goto error; + goto finally; } - ct->ct_size = totalsize; + cffi_set_size(ct, totalsize); ct->ct_length = totalalignment; + cffi_set_flag(ct->ct_unrealized_struct_or_union, 0); +#ifdef Py_GIL_DISABLED + /* use seq consistency at last after writing other fields + to ensure other fields are visible to threads */ + cffi_atomic_store((void **)&ct->ct_stuff, interned_fields); +#else ct->ct_stuff = interned_fields; - ct->ct_flags &= ~CT_IS_OPAQUE; +#endif + res = Py_None; + Py_INCREF(res); - Py_INCREF(Py_None); - return Py_None; +finally:; + if (res == NULL) { + ct->ct_extra = NULL; + Py_XDECREF(interned_fields); + } + return res; +} - error: - ct->ct_extra = NULL; - Py_DECREF(interned_fields); - return NULL; +static PyObject *b_complete_struct_or_union(PyObject *self, PyObject *args) +{ + CTypeDescrObject *ct; + PyObject *fields, *ignored; + Py_ssize_t totalsize = -1; + int totalalignment = -1; + int sflags = 0; + int pack = 0; + + if (!PyArg_ParseTuple(args, "O!O!|Oniii:complete_struct_or_union", + &CTypeDescr_Type, &ct, + &PyList_Type, &fields, + &ignored, &totalsize, &totalalignment, &sflags, + &pack)) + return NULL; + + PyObject *res; + Py_BEGIN_CRITICAL_SECTION(ct); + res = b_complete_struct_or_union_lock_held(ct, fields, totalsize, totalalignment, sflags, pack); + Py_END_CRITICAL_SECTION(); + return res; } + struct funcbuilder_s { Py_ssize_t nb_bytes; char *bufferp; @@ -5709,7 +5764,7 @@ static ffi_type *fb_fill_type(struct funcbuilder_s *fb, CTypeDescrObject *ct, return &ffi_type_void; } - if (ct->ct_size <= 0) { + if (cffi_get_size(ct) <= 0) { PyErr_Format(PyExc_TypeError, ct->ct_size < 0 ? "ctype '%s' has incomplete type" : "ctype '%s' has size 0", @@ -5730,7 +5785,7 @@ static ffi_type *fb_fill_type(struct funcbuilder_s *fb, CTypeDescrObject *ct, differently: e.g. on x86-64, "b" ends up in register "rsi" in the first case and "rdi" in the second case. - Another reason for CT_CUSTOM_FIELD_POS would be anonymous + Another reason for ct_custom_field_pos would be anonymous nested structures: we lost the information about having it here, so better safe (and forbid it) than sorry (and maybe crash). Note: it seems we only get in this case with @@ -5738,7 +5793,7 @@ static ffi_type *fb_fill_type(struct funcbuilder_s *fb, CTypeDescrObject *ct, */ if (force_lazy_struct(ct) < 0) return NULL; - if (ct->ct_flags_mut & CT_CUSTOM_FIELD_POS) { + if (cffi_check_flag(ct->ct_custom_field_pos)) { /* these NotImplementedErrors may be caught and ignored until a real call is made to a function of this type */ return fb_unsupported(ct, place, @@ -5748,7 +5803,7 @@ static ffi_type *fb_fill_type(struct funcbuilder_s *fb, CTypeDescrObject *ct, } /* Another reason: __attribute__((packed)) is not supported by libffi. */ - if (ct->ct_flags_mut & CT_WITH_PACKED_CHANGE) { + if (cffi_check_flag(ct->ct_with_packed_change)) { return fb_unsupported(ct, place, "It is a 'packed' structure, with a different layout than " "expected by libffi"); @@ -6021,7 +6076,6 @@ static CTypeDescrObject *fb_prepare_ctype(struct funcbuilder_s *fb, fct->ct_extra = NULL; fct->ct_size = sizeof(void(*)(void)); fct->ct_flags = CT_FUNCTIONPTR; - fct->ct_flags_mut = 0; return fct; error: @@ -6100,12 +6154,14 @@ static PyObject *new_function_type(PyObject *fargs, /* tuple */ Py_ssize_t i; const void **unique_key; - if ((fresult->ct_size < 0 && !(fresult->ct_flags & CT_VOID)) || + if ((cffi_get_size(fresult) < 0 && !(fresult->ct_flags & CT_VOID)) || (fresult->ct_flags & CT_ARRAY)) { char *msg; if (fresult->ct_flags & CT_IS_OPAQUE) msg = "result type '%s' is opaque"; - else if (fresult->ct_flags_mut & CT_UNDER_CONSTRUCTION) + else if (cffi_check_flag(fresult->ct_unrealized_struct_or_union)) + msg = "result type '%s' is not yet initialized"; + else if (cffi_check_flag(fresult->ct_under_construction)) msg = "result type '%s' is under construction"; else msg = "invalid result type: '%s'"; @@ -6729,7 +6785,9 @@ static PyObject *b_new_enum_type(PyObject *self, PyObject *args) td->ct_length = basetd->ct_length; /* alignment */ td->ct_extra = basetd->ct_extra; /* ffi type */ td->ct_flags = basetd->ct_flags | CT_IS_ENUM; - td->ct_flags_mut = basetd->ct_flags_mut; + td->ct_custom_field_pos = cffi_check_flag(basetd->ct_custom_field_pos); + td->ct_with_packed_change = cffi_check_flag(basetd->ct_with_packed_change); + td->ct_with_var_array = cffi_check_flag(basetd->ct_with_var_array); td->ct_name_position = name_size - 1; return (PyObject *)td; @@ -6827,7 +6885,11 @@ static CTypeDescrObject *direct_typeoffsetof(CTypeDescrObject *ct, PyErr_SetString(PyExc_TypeError, "struct/union is opaque"); return NULL; } +#ifdef Py_GIL_DISABLED + cf = (CFieldObject *)PyDict_GetItem((PyObject *)cffi_atomic_load((void **)&ct->ct_stuff), fieldname); +#else cf = (CFieldObject *)PyDict_GetItem(ct->ct_stuff, fieldname); +#endif if (cf == NULL) { PyErr_SetObject(PyExc_KeyError, fieldname); return NULL; diff --git a/src/c/ffi_obj.c b/src/c/ffi_obj.c index 6a86a413..0ff8bbd6 100644 --- a/src/c/ffi_obj.c +++ b/src/c/ffi_obj.c @@ -16,6 +16,13 @@ need to call ffi.cdef() to add more information to it. */ +#ifdef MS_WIN32 +#include "misc_win32.h" +#else +#include "misc_thread_posix.h" +#endif +#include "misc_thread_common.h" + #define FFI_COMPLEXITY_OUTPUT 1200 /* xxx should grow as needed */ #define FFIObject_Check(op) PyObject_TypeCheck(op, &FFI_Type) @@ -280,7 +287,7 @@ static PyObject *ffi_sizeof(FFIObject *self, PyObject *arg) CTypeDescrObject *ct = _ffi_type(self, arg, ACCEPT_ALL); if (ct == NULL) return NULL; - size = ct->ct_size; + size = cffi_get_size(ct); if (size < 0) { PyErr_Format(FFIError, "don't know the size of ctype '%s'", ct->ct_name); diff --git a/src/c/lib_obj.c b/src/c/lib_obj.c index bd9ba3fc..46e5fe00 100644 --- a/src/c/lib_obj.c +++ b/src/c/lib_obj.c @@ -146,11 +146,19 @@ static PyObject *lib_build_cpython_func(LibObject *lib, random even value. But OP_FUNCTION_END is odd, so the condition below still works correctly. */ i = type_index + 1; +#ifdef Py_GIL_DISABLED + while (_CFFI_GETOP(cffi_atomic_load(&opcodes[i])) != _CFFI_OP_FUNCTION_END) +#else while (_CFFI_GETOP(opcodes[i]) != _CFFI_OP_FUNCTION_END) +#endif i++; pfargs = alloca(sizeof(CTypeDescrObject *) * (i - type_index - 1)); i = type_index + 1; +#ifdef Py_GIL_DISABLED + while (_CFFI_GETOP(cffi_atomic_load(&opcodes[i])) != _CFFI_OP_FUNCTION_END) { +#else while (_CFFI_GETOP(opcodes[i]) != _CFFI_OP_FUNCTION_END) { +#endif CTypeDescrObject *ct = realize_c_type(lib->l_types_builder, opcodes, i); if (ct == NULL) goto error; @@ -541,7 +549,7 @@ static PyObject *lib_getattr(LibObject *lib, PyObject *name) Py_INCREF(x); return x; } - /* this hack is for Python 3.5, and also to give a more + /* this hack is for Python 3.5, and also to give a more module-like behavior */ if (strcmp(p, "__name__") == 0) { PyErr_Clear(); diff --git a/src/c/misc_thread_common.h b/src/c/misc_thread_common.h index 62271f20..8f4f211b 100644 --- a/src/c/misc_thread_common.h +++ b/src/c/misc_thread_common.h @@ -394,4 +394,23 @@ static void gil_release(PyGILState_STATE oldstate) PyGILState_Release(oldstate); } + +#if PY_VERSION_HEX <= 0x030d00b3 +# define Py_BEGIN_CRITICAL_SECTION(op) { +# define Py_END_CRITICAL_SECTION() } +#endif + +#ifdef Py_GIL_DISABLED +#define cffi_check_flag(arg) cffi_atomic_load_uint8(&(arg)) +#define cffi_set_flag(arg, value) cffi_atomic_store_uint8(&(arg), (value)) +#define cffi_set_size(arg, value) cffi_atomic_store_ssize(&(arg)->ct_size, (value)) +#define cffi_get_size(arg) cffi_atomic_load_ssize(&(arg)->ct_size) +#else +#define cffi_check_flag(arg) (arg) +#define cffi_set_flag(arg, value) (arg) = (value) +#define cffi_set_size(arg, value) (arg)->ct_size = (value) +#define cffi_get_size(arg) (arg)->ct_size +#endif + + #endif /* CFFI_MISC_THREAD_COMMON_H */ diff --git a/src/c/misc_thread_posix.h b/src/c/misc_thread_posix.h index 5a322a8e..a8056302 100644 --- a/src/c/misc_thread_posix.h +++ b/src/c/misc_thread_posix.h @@ -65,6 +65,27 @@ static void cffi_atomic_store(void **ptr, void *value) { __atomic_store_n(ptr, value, __ATOMIC_SEQ_CST); } + +static uint8_t cffi_atomic_load_uint8(uint8_t *ptr) +{ + return __atomic_load_n(ptr, __ATOMIC_SEQ_CST); +} + +static void cffi_atomic_store_uint8(uint8_t *ptr, uint8_t value) +{ + __atomic_store_n(ptr, value, __ATOMIC_SEQ_CST); +} + +static Py_ssize_t cffi_atomic_load_ssize(Py_ssize_t *ptr) +{ + return __atomic_load_n(ptr, __ATOMIC_SEQ_CST); +} + +static void cffi_atomic_store_ssize(Py_ssize_t *ptr, Py_ssize_t value) +{ + __atomic_store_n(ptr, value, __ATOMIC_SEQ_CST); +} + #endif #endif /* CFFI_MISC_THREAD_POSIX_H */ diff --git a/src/c/misc_win32.h b/src/c/misc_win32.h index 43648b95..d7172e1c 100644 --- a/src/c/misc_win32.h +++ b/src/c/misc_win32.h @@ -284,9 +284,41 @@ static void *cffi_atomic_load(void **ptr) #endif } +static uint8_t cffi_atomic_load_uint8(uint8_t *ptr) +{ +#if defined(_M_X64) || defined(_M_IX86) + return *(volatile uint8_t *)ptr; +#elif defined(_M_ARM64) + return (uint8_t)__ldar8((volatile uint8_t *)ptr); +#else +# error "no implementation of cffi_atomic_load_uint8" +#endif +} + +static Py_ssize_t cffi_atomic_load_ssize(Py_ssize_t *ptr) +{ +#if defined(_M_X64) || defined(_M_IX86) + return *(volatile Py_ssize_t *)ptr; +#elif defined(_M_ARM64) + return (Py_ssize_t)__ldar64((volatile unsigned __int64 *)ptr); +#else +# error "no implementation of cffi_atomic_load_ssize" +#endif +} + +static void cffi_atomic_store_ssize(Py_ssize_t *ptr, Py_ssize_t value) +{ + _InterlockedExchangePointer(ptr, value); +} + static void cffi_atomic_store(void **ptr, void *value) { _InterlockedExchangePointer(ptr, value); } +static void cffi_atomic_store_uint8(uint8_t *ptr, uint8_t value) +{ + _InterlockedExchange8(ptr, value); +} + #endif /* CFFI_MISC_WIN32_H */ diff --git a/src/c/realize_c_type.c b/src/c/realize_c_type.c index 4aadc2bf..3241e486 100644 --- a/src/c/realize_c_type.c +++ b/src/c/realize_c_type.c @@ -1,4 +1,11 @@ +#ifdef MS_WIN32 +#include "misc_win32.h" +#else +#include "misc_thread_posix.h" +#endif + + typedef struct { struct _cffi_type_context_s ctx; /* inlined substructure */ PyObject *types_dict; @@ -388,6 +395,8 @@ _realize_c_struct_or_union(builder_c_t *builder, int sindex) if (!(s->flags & _CFFI_F_EXTERNAL)) { int flags = (s->flags & _CFFI_F_UNION) ? CT_UNION : CT_STRUCT; + int is_opaque = (s->flags & _CFFI_F_OPAQUE); + flags |= is_opaque ? CT_IS_OPAQUE : 0; char *name = alloca(8 + strlen(s->name)); _realize_name(name, (s->flags & _CFFI_F_UNION) ? "union " : "struct ", @@ -399,19 +408,17 @@ _realize_c_struct_or_union(builder_c_t *builder, int sindex) if (x == NULL) return NULL; - if (!(s->flags & _CFFI_F_OPAQUE)) { + if (!is_opaque) { assert(s->first_field_index >= 0); ct = (CTypeDescrObject *)x; ct->ct_size = (Py_ssize_t)s->size; - // unset opaque flag temporarily added in - // new_struct_or_union_type since _CFFI_F_OPAUE isn't set - ct->ct_flags &= ~CT_IS_OPAQUE; ct->ct_length = s->alignment; /* may be -1 */ - ct->ct_flags_mut |= CT_LAZY_FIELD_LIST; + ct->ct_lazy_field_list = 1; ct->ct_extra = builder; } - else + else { assert(s->first_field_index < 0); + } } else { assert(s->first_field_index < 0); @@ -440,6 +447,9 @@ _realize_c_struct_or_union(builder_c_t *builder, int sindex) } } } + if (ct != NULL) { + cffi_set_flag(ct->ct_unrealized_struct_or_union, 0); + } LOCK_REALIZE(); /* Update the "primary" OP_STRUCT_UNION slot */ @@ -733,7 +743,13 @@ realize_c_type_or_func_now(builder_c_t *builder, _cffi_opcode_t op, } #ifdef Py_GIL_DISABLED -__thread int _realize_recursion_level; +#ifdef MS_WIN32 +static __declspec(thread) int _realize_recursion_level; +#elif defined(USE__THREAD) +static __thread int _realize_recursion_level; +#else +#error "Cannot detect thread-local keyword" +#endif #else static int _realize_recursion_level; #endif @@ -823,20 +839,20 @@ realize_c_func_return_type(builder_c_t *builder, } } -static int do_realize_lazy_struct(CTypeDescrObject *ct) +static int do_realize_lazy_struct_lock_held(CTypeDescrObject *ct) { /* This is called by force_lazy_struct() in _cffi_backend.c */ assert(ct->ct_flags & (CT_STRUCT | CT_UNION)); - assert(!(ct->ct_flags_mut & CT_UNDER_CONSTRUCTION)); - if (ct->ct_flags_mut & CT_LAZY_FIELD_LIST) { + + if (cffi_check_flag(ct->ct_lazy_field_list)) { builder_c_t *builder; char *p; int n, i, sflags; const struct _cffi_struct_union_s *s; const struct _cffi_field_s *fld; - PyObject *fields, *args, *res; + PyObject *fields, *res; - // opaque types should never set CT_LAZY_FIELD_LIST + // opaque types should never set ct->ct_lazy_field_list assert(!(ct->ct_flags & CT_IS_OPAQUE)); builder = ct->ct_extra; @@ -912,19 +928,12 @@ static int do_realize_lazy_struct(CTypeDescrObject *ct) if (s->flags & _CFFI_F_PACKED) sflags |= SF_PACKED; - args = Py_BuildValue("(OOOnii)", ct, fields, Py_None, - (Py_ssize_t)s->size, - s->alignment, - sflags); - Py_DECREF(fields); - if (args == NULL) - return -1; - ct->ct_extra = NULL; - ct->ct_flags_mut |= CT_UNDER_CONSTRUCTION; - res = b_complete_struct_or_union(NULL, args); - ct->ct_flags_mut &= ~CT_UNDER_CONSTRUCTION; - Py_DECREF(args); + cffi_set_flag(ct->ct_under_construction, 1); + res = b_complete_struct_or_union_lock_held(ct, fields, s->size, s->alignment, + sflags, 0); + cffi_set_flag(ct->ct_under_construction, 0); + Py_DECREF(fields); if (res == NULL) { ct->ct_extra = builder; @@ -932,12 +941,18 @@ static int do_realize_lazy_struct(CTypeDescrObject *ct) } assert(ct->ct_stuff != NULL); - ct->ct_flags_mut &= ~CT_LAZY_FIELD_LIST; - assert(!(ct->ct_flags_mut & CT_UNDER_CONSTRUCTION)); + cffi_set_flag(ct->ct_lazy_field_list, 0); Py_DECREF(res); - return 1; - } - else { - return 0; } + return ct->ct_stuff != NULL; +} + + +static int do_realize_lazy_struct(CTypeDescrObject *ct) +{ + int res = 0; + Py_BEGIN_CRITICAL_SECTION(ct); + res = do_realize_lazy_struct_lock_held(ct); + Py_END_CRITICAL_SECTION(); + return res; } diff --git a/suppressions_free_threading.txt b/suppressions_free_threading.txt new file mode 100644 index 00000000..4437b870 --- /dev/null +++ b/suppressions_free_threading.txt @@ -0,0 +1,52 @@ +# This file contains suppressions for the free-threaded build. It contains the +# suppressions for the default build and additional suppressions needed only in +# the free-threaded build. +# +# reference: https://github.com/google/sanitizers/wiki/ThreadSanitizerSuppressions + +## Free-threaded suppressions + + +# These entries are for warnings that trigger in a library function, as called +# by a CPython function. + +# These warnings trigger directly in a CPython function. + +race_top:dump_traceback +race_top:fatal_error +race_top:_PyFrame_GetCode +race_top:_PyFrame_Initialize +race_top:_PyObject_TryGetInstanceAttribute +race_top:PyUnstable_InterpreterFrame_GetLine +race_top:write_thread_id + +# gh-129068: race on shared range iterators (test_free_threading.test_zip.ZipThreading.test_threading) +race_top:rangeiter_next + +# gh-129748: test.test_free_threading.test_slots.TestSlots.test_object +race_top:mi_block_set_nextx + +# https://gist.github.com/mpage/6962e8870606cfc960e159b407a0cb40 +thread:pthread_create + +# Range iteration is not thread-safe yet (issue #129068) +race_top:rangeiter_next + +# List resizing happens through different paths ending in memcpy or memmove +# (for efficiency), which will probably need to rewritten as explicit loops +# of ptr-sized copies to be thread-safe. (Issue #129069) +race:list_ass_slice_lock_held +race:list_inplace_repeat_lock_held + +# PyObject_Realloc internally does memcpy which isn't atomic so can race +# with non-locking reads. See #132070 +race:PyObject_Realloc + +# gh-133467. Some of these could be hard to trigger. +race_top:_Py_slot_tp_getattr_hook +race_top:slot_tp_descr_get +race_top:type_set_name +race_top:set_tp_bases +race_top:type_set_bases_unlocked + +race:partial_vectorcall_fallback diff --git a/testing/cffi0/test_ownlib.py b/testing/cffi0/test_ownlib.py index 70d95df0..ad116f33 100644 --- a/testing/cffi0/test_ownlib.py +++ b/testing/cffi0/test_ownlib.py @@ -210,6 +210,7 @@ def test_setting_errno(self): assert res == 42 assert ffi.errno == 42 + @pytest.mark.thread_unsafe def test_my_array_7(self): if self.module is None: pytest.skip("fix the auto-generation of the tiny test lib") @@ -230,6 +231,7 @@ def test_my_array_7(self): for i in range(7): assert ownlib.my_array[i] == i + @pytest.mark.thread_unsafe def test_my_array_no_length(self): if self.module is None: pytest.skip("fix the auto-generation of the tiny test lib") @@ -304,7 +306,7 @@ def test_struct_by_value(self): long right; long bottom; } RECT; - + extern long left, top, right, bottom; RECT ReturnRect(int i, RECT ar, RECT* br, POINT cp, RECT dr, @@ -320,7 +322,7 @@ def test_struct_by_value(self): rect[0].right = ownlib.right rect[0].top = ownlib.top rect[0].bottom = ownlib.bottom - + for i in range(4): ret = ownlib.ReturnRect(i, rect[0], rect, pt[0], rect[0], rect, pt[0], rect[0])