diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 95473337..e91e720d 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -76,10 +76,9 @@ jobs: - { spec: cp310-manylinux_x86_64, arch: x86_64, omit: ${{ env.skip_ci_redundant_jobs }} } - { spec: cp311-manylinux_x86_64, arch: x86_64, omit: ${{ env.skip_ci_redundant_jobs }} } - { spec: cp312-manylinux_x86_64, arch: x86_64, omit: ${{ env.skip_ci_redundant_jobs }} } - - { spec: cp313-manylinux_x86_64, arch: x86_64, omit: ${{ env.skip_ci_redundant_jobs }} } - - { spec: cp314-manylinux_x86_64, arch: x86_64, cibw_version: cibuildwheel~=3.0b1 } - # FIXME: need to run tests with PYTHON_GIL=0 on this build to actually test sans-GIL, but breaks packaging tests that use the wrong `virtualenv` script wrapper - - { spec: cp314t-manylinux_x86_64, skip_artifact_upload: 'true', cibw_version: cibuildwheel~=3.0b1 } + - { spec: cp313-manylinux_x86_64, arch: x86_64, omit: ${{ env.skip_ci_redundant_jobs }} } + - { spec: cp314-manylinux_x86_64, arch: x86_64 } + - { spec: cp314t-manylinux_x86_64, arch: x86_64 } # x86_64 musllinux - { spec: cp39-musllinux_x86_64, arch: x86_64, omit: ${{ env.skip_ci_redundant_jobs }} } @@ -87,7 +86,8 @@ jobs: - { spec: cp311-musllinux_x86_64, arch: x86_64, omit: ${{ env.skip_ci_redundant_jobs }} } - { spec: cp312-musllinux_x86_64, arch: x86_64, omit: ${{ env.skip_ci_redundant_jobs }} } - { spec: cp313-musllinux_x86_64, arch: x86_64, omit: ${{ env.skip_ci_redundant_jobs }} } - - { spec: cp314-musllinux_x86_64, arch: x86_64, cibw_version: cibuildwheel~=3.0b1 } + - { spec: cp314-musllinux_x86_64, arch: x86_64 } + - { spec: cp314t-musllinux_x86_64, arch: x86_64 } # i686 manylinux - { spec: cp39-manylinux_i686, arch: i686, omit: ${{ env.skip_ci_redundant_jobs }} } @@ -109,7 +109,8 @@ jobs: - { spec: cp311-manylinux_aarch64, arch: aarch64, omit: ${{ env.skip_ci_redundant_jobs }} } - { spec: cp312-manylinux_aarch64, arch: aarch64, omit: ${{ env.skip_ci_redundant_jobs }} } - { spec: cp313-manylinux_aarch64, arch: aarch64, omit: ${{ env.skip_ci_redundant_jobs }} } - - { spec: cp314-manylinux_aarch64, arch: aarch64, cibw_version: cibuildwheel~=3.0b1 } + - { spec: cp314-manylinux_aarch64, arch: aarch64 } + - { spec: cp314t-manylinux_aarch64, arch: aarch64 } # aarch64 musllinux - { spec: cp39-musllinux_aarch64, arch: aarch64, omit: ${{ env.skip_ci_redundant_jobs }} } @@ -117,7 +118,8 @@ jobs: - { spec: cp311-musllinux_aarch64, arch: aarch64, omit: ${{ env.skip_ci_redundant_jobs }} } - { spec: cp312-musllinux_aarch64, arch: aarch64, omit: ${{ env.skip_ci_redundant_jobs }} } - { spec: cp313-musllinux_aarch64, arch: aarch64, omit: ${{ env.skip_ci_redundant_jobs }} } - - { spec: cp314-musllinux_aarch64, arch: aarch64, cibw_version: cibuildwheel~=3.0b1 } + - { spec: cp314-musllinux_aarch64, arch: aarch64 } + - { spec: cp314t-musllinux_aarch64, arch: aarch64 } # ppc64le manylinux - { spec: cp39-manylinux_ppc64le, arch: ppc64le, test_args: '{package}/src/c', omit: ${{ env.skip_slow_jobs }} } @@ -125,7 +127,8 @@ jobs: - { spec: cp311-manylinux_ppc64le, arch: ppc64le, test_args: '{package}/src/c', omit: ${{ env.skip_slow_jobs || env.skip_ci_redundant_jobs }} } - { spec: cp312-manylinux_ppc64le, arch: ppc64le, test_args: '{package}/src/c', omit: ${{ env.skip_slow_jobs || env.skip_ci_redundant_jobs }} } - { spec: cp313-manylinux_ppc64le, arch: ppc64le, test_args: '{package}/src/c', omit: ${{ env.skip_slow_jobs || env.skip_ci_redundant_jobs }} } - - { spec: cp314-manylinux_ppc64le, arch: ppc64le, omit: ${{ env.skip_slow_jobs }}, cibw_version: cibuildwheel~=3.0b1 } + - { spec: cp314-manylinux_ppc64le, arch: ppc64le, omit: ${{ env.skip_slow_jobs }} } + - { spec: cp314t-manylinux_ppc64le, arch: ppc64le, omit: ${{ env.skip_slow_jobs }} } # s390x manylinux - { spec: cp39-manylinux_s390x, arch: s390x, test_args: '{package}/src/c', omit: ${{ env.skip_slow_jobs }} } @@ -133,7 +136,8 @@ jobs: - { spec: cp311-manylinux_s390x, arch: s390x, test_args: '{package}/src/c', omit: ${{ env.skip_slow_jobs || env.skip_ci_redundant_jobs }} } - { spec: cp312-manylinux_s390x, arch: s390x, test_args: '{package}/src/c', omit: ${{ env.skip_slow_jobs || env.skip_ci_redundant_jobs }} } - { spec: cp313-manylinux_s390x, arch: s390x, test_args: '{package}/src/c', omit: ${{ env.skip_slow_jobs || env.skip_ci_redundant_jobs }} } - - { spec: cp314-manylinux_s390x, arch: s390x, omit: ${{ env.skip_slow_jobs }}, cibw_version: cibuildwheel~=3.0b1 } + - { spec: cp314-manylinux_s390x, arch: s390x, omit: ${{ env.skip_slow_jobs }} } + - { spec: cp314t-manylinux_s390x, arch: s390x, omit: ${{ env.skip_slow_jobs }} } linux: needs: [python_sdist, make_linux_matrix] @@ -178,7 +182,6 @@ jobs: CIBW_MUSLLINUX_X86_64_IMAGE: ${{ matrix.musllinux_img || 'musllinux_1_2' }} CIBW_MUSLLINUX_I686_IMAGE: ${{ matrix.musllinux_img || 'musllinux_1_2' }} CIBW_MUSLLINUX_AARCH64_IMAGE: ${{ matrix.musllinux_img || 'musllinux_1_2' }} - CIBW_ENABLE: cpython-prerelease cpython-freethreading CIBW_TEST_REQUIRES: pytest setuptools # 3.12+ no longer includes distutils, just always ensure setuptools is present CIBW_TEST_COMMAND: PYTHONUNBUFFERED=1 python -m pytest ${{ matrix.test_args || '{project}' }} # default to test all run: | @@ -220,7 +223,8 @@ jobs: - { spec: cp311-macosx_x86_64, runs_on: [macos-13], omit: ${{ env.skip_ci_redundant_jobs }} } - { spec: cp312-macosx_x86_64, runs_on: [macos-13], omit: ${{ env.skip_ci_redundant_jobs }} } - { spec: cp313-macosx_x86_64, runs_on: [macos-13], omit: ${{ env.skip_ci_redundant_jobs }} } - - { spec: cp314-macosx_x86_64, runs_on: [macos-13], cibw_version: cibuildwheel~=3.0b1 } + - { spec: cp314-macosx_x86_64, runs_on: [macos-13] } + - { spec: cp314t-macosx_x86_64, runs_on: [macos-13] } # arm64 macos - { spec: cp39-macosx_arm64, deployment_target: '11.0', run_wrapper: 'arch -arm64 bash --noprofile --norc -eo pipefail {0}', omit: ${{ env.skip_ci_redundant_jobs }} } @@ -228,7 +232,8 @@ jobs: - { spec: cp311-macosx_arm64, deployment_target: '11.0', run_wrapper: 'arch -arm64 bash --noprofile --norc -eo pipefail {0}', omit: ${{ env.skip_ci_redundant_jobs }} } - { spec: cp312-macosx_arm64, deployment_target: '11.0', run_wrapper: 'arch -arm64 bash --noprofile --norc -eo pipefail {0}', omit: ${{ env.skip_ci_redundant_jobs }} } - { spec: cp313-macosx_arm64, deployment_target: '11.0', run_wrapper: 'arch -arm64 bash --noprofile --norc -eo pipefail {0}', omit: ${{ env.skip_ci_redundant_jobs }} } - - { spec: cp314-macosx_arm64, deployment_target: '11.0', run_wrapper: 'arch -arm64 bash --noprofile --norc -eo pipefail {0}', cibw_version: cibuildwheel~=3.0b1 } + - { spec: cp314-macosx_arm64, deployment_target: '11.0', run_wrapper: 'arch -arm64 bash --noprofile --norc -eo pipefail {0}' } + - { spec: cp314t-macosx_arm64, deployment_target: '11.0', run_wrapper: 'arch -arm64 bash --noprofile --norc -eo pipefail {0}' } macos: needs: [python_sdist, make_macos_matrix] @@ -263,7 +268,6 @@ jobs: id: build env: CIBW_BUILD: ${{ matrix.spec }} - CIBW_ENABLE: cpython-prerelease CIBW_TEST_REQUIRES: pytest setuptools CIBW_TEST_COMMAND: pip install pip --upgrade; cd {project}; PYTHONUNBUFFERED=1 pytest MACOSX_DEPLOYMENT_TARGET: ${{ matrix.deployment_target || '10.13' }} @@ -306,7 +310,8 @@ jobs: - { spec: cp311-win_amd64, omit: ${{ env.skip_ci_redundant_jobs }} } - { spec: cp312-win_amd64, omit: ${{ env.skip_ci_redundant_jobs }} } - { spec: cp313-win_amd64, omit: ${{ env.skip_ci_redundant_jobs }} } - - { spec: cp314-win_amd64, cibw_version: cibuildwheel~=3.0b1 } + - { spec: cp314-win_amd64 } + - { spec: cp314t-win_amd64 } # x86 windows - { spec: cp39-win32, omit: ${{ env.skip_ci_redundant_jobs }} } @@ -314,13 +319,15 @@ jobs: - { spec: cp311-win32, omit: ${{ env.skip_ci_redundant_jobs }} } - { spec: cp312-win32, omit: ${{ env.skip_ci_redundant_jobs }} } - { spec: cp313-win32, omit: ${{ env.skip_ci_redundant_jobs }} } - - { spec: cp314-win32, cibw_version: cibuildwheel~=3.0b1 } + - { spec: cp314-win32 } + - { spec: cp314t-win32 } # arm64 windows - - { spec: cp311-win_arm64, runs_on: windows-11-arm, omit: ${{ env.skip_ci_redundant_jobs }}, cibw_version: cibuildwheel~=3.0b1 } - - { spec: cp312-win_arm64, runs_on: windows-11-arm, omit: ${{ env.skip_ci_redundant_jobs }}, cibw_version: cibuildwheel~=3.0b1 } - - { spec: cp313-win_arm64, runs_on: windows-11-arm, omit: ${{ env.skip_ci_redundant_jobs }}, cibw_version: cibuildwheel~=3.0b1 } - - { spec: cp314-win_arm64, runs_on: windows-11-arm, cibw_version: cibuildwheel~=3.0b1 } + - { spec: cp311-win_arm64, runs_on: windows-11-arm, omit: ${{ env.skip_ci_redundant_jobs }} } + - { spec: cp312-win_arm64, runs_on: windows-11-arm, omit: ${{ env.skip_ci_redundant_jobs }} } + - { spec: cp313-win_arm64, runs_on: windows-11-arm, omit: ${{ env.skip_ci_redundant_jobs }} } + - { spec: cp314-win_arm64, runs_on: windows-11-arm } + - { spec: cp314t-win_arm64, runs_on: windows-11-arm } windows: needs: [python_sdist, make_windows_matrix] @@ -345,7 +352,6 @@ jobs: id: build env: CIBW_BUILD: ${{ matrix.spec }} - CIBW_ENABLE: cpython-prerelease CIBW_TEST_REQUIRES: pytest setuptools CIBW_TEST_COMMAND: ${{ matrix.test_cmd || 'python -m pytest {package}/src/c' }} # FIXME: /testing takes ~45min on Windows and has some failures... @@ -358,7 +364,7 @@ jobs: tar zxf cffi*.tar.gz --strip-components=1 -C cffi python -m pip install --upgrade pip - pip install "${{ matrix.cibw_version || 'cibuildwheel'}}" + pip install "${{ matrix.cibw_version || 'cibuildwheel' }}" python -m cibuildwheel --output-dir dist cffi echo "artifact_name=$(ls ./dist/)" >> "$GITHUB_OUTPUT" @@ -384,10 +390,73 @@ jobs: delete-merged: true if: ${{ env.skip_artifact_upload != 'true' }} + make_run_parallel_matrix: + runs-on: ubuntu-24.04 + outputs: + matrix_json: ${{ steps.make_matrix.outputs.matrix_json }} + steps: + - uses: actions/checkout@v4 + - name: make a matrix + id: make_matrix + uses: ./.github/actions/dynamatrix + with: + matrix_yaml: | + include: + - { runner: ubuntu-latest, python-version: 3.14t-dev } + - { runner: macos-latest, python-version: 3.14t-dev } + - { runner: windows-latest, python-version: 3.14t-dev } + + + pytest-run-parallel: + needs: make_run_parallel_matrix + strategy: + fail-fast: false + matrix: ${{ fromJSON(needs.make_run_parallel_matrix.outputs.matrix_json) }} + + runs-on: ${{ matrix.runner }} + 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-24.04 steps: - name: Verify all previous jobs succeeded (provides a single check to sample for gating purposes) diff --git a/doc/source/conf.py b/doc/source/conf.py index 0204ddd1..54046b96 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -45,9 +45,9 @@ # built documents. # # The short X.Y version. -version = '1.18' +version = '2.0' # The full version, including alpha/beta/rc tags. -release = '1.18.0.dev0' +release = '2.0.0.dev0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/doc/source/whatsnew.rst b/doc/source/whatsnew.rst index cf8d6313..f414157f 100644 --- a/doc/source/whatsnew.rst +++ b/doc/source/whatsnew.rst @@ -2,11 +2,19 @@ What's New ====================== -v1.18.0.dev0 +v2.0.0.dev0 ============ +* Added support for free threaded Python. (`#178`_) + Note that the free-threaded build does not yet support building extensions + with the limited API, so you must set py_limited_api=False when building + extensions for the free-threaded build. +* Added support for Python 3.14. (`#177`_) * WIP +.. _`#177`: https://github.com/python-cffi/cffi/pull/177 +.. _`#178`: https://github.com/python-cffi/cffi/pull/178 + v1.17.1 ======= diff --git a/pyproject.toml b/pyproject.toml index fd556994..07584951 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ build-backend = "setuptools.build_meta" [project] name = "cffi" -version = "1.18.0.dev0" +version = "2.0.0.dev0" dependencies = [ "pycparser; implementation_name != 'PyPy'", ] @@ -26,8 +26,8 @@ classifiers = [ "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Programming Language :: Python :: 3.14", + "Programming Language :: Python :: Free Threading :: 2 - Beta", "Programming Language :: Python :: Implementation :: CPython", - "Programming Language :: Python :: Implementation :: PyPy", ] authors = [ {name = "Armin Rigo"}, diff --git a/setup.py b/setup.py index 16f3cc8b..c75812c2 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,4 @@ -import sys, os, platform +import sys, sysconfig, os, platform import subprocess import errno @@ -16,6 +16,12 @@ extra_compile_args = [] extra_link_args = [] +FREE_THREADED_BUILD = bool(sysconfig.get_config_var('Py_GIL_DISABLED')) + +if FREE_THREADED_BUILD and sys.version_info < (3, 14): + raise RuntimeError("CFFI does not support the free-threaded build of CPython 3.13. " + "Upgrade to free-threaded 3.14 or newer to use CFFI with the " + "free-threaded build.") def _ask_pkg_config(resultlist, option, result_prefix='', sysroot=False): pkg_config = os.environ.get('PKG_CONFIG','pkg-config') diff --git a/src/c/_cffi_backend.c b/src/c/_cffi_backend.c index e5ff9d5b..355a18a7 100644 --- a/src/c/_cffi_backend.c +++ b/src/c/_cffi_backend.c @@ -1,8 +1,8 @@ #define PY_SSIZE_T_CLEAN #include #include "structmember.h" - -#define CFFI_VERSION "1.18.0.dev0" +#include "misc_thread_common.h" +#define CFFI_VERSION "2.0.0.dev0" #ifdef MS_WIN32 #include @@ -14,6 +14,7 @@ #include #include #include +#include "misc_thread_posix.h" #endif /* this block of #ifs should be kept exactly identical between @@ -204,6 +205,49 @@ # define USE_WRITEUNRAISABLEMSG #endif +#if PY_VERSION_HEX <= 0x030d00a1 +static int PyDict_GetItemRef(PyObject *mp, PyObject *key, PyObject **result) +{ + PyObject *obj = PyDict_GetItemWithError(mp, key); + Py_XINCREF(obj); + *result = obj; + if (obj == NULL) { + if (PyErr_Occurred()) { + return -1; + } + return 0; + } + return 1; +} +#endif + +#if PY_VERSION_HEX < 0x030d00a1 +static int PyWeakref_GetRef(PyObject *ref, PyObject **pobj) +{ + PyObject *obj = PyWeakref_GetObject(ref); + if (obj == NULL) { + *pobj = NULL; + return -1; + } + if (obj == Py_None) { + *pobj = NULL; + return 0; + } + Py_INCREF(obj); + *pobj = obj; + return 1; +} +#endif + + +#ifdef Py_GIL_DISABLED +# define LOCK_UNIQUE_CACHE() PyMutex_Lock(&unique_cache_lock) +# define UNLOCK_UNIQUE_CACHE() PyMutex_Unlock(&unique_cache_lock) +#else +# define LOCK_UNIQUE_CACHE() ((void)0) +# define UNLOCK_UNIQUE_CACHE() ((void)0) +#endif + /************************************************************/ /* base type flag: exactly one of the following: */ @@ -225,15 +269,15 @@ #define CT_IS_OPAQUE 0x00004000 #define CT_IS_ENUM 0x00008000 #define CT_IS_PTR_TO_OWNED 0x00010000 /* only owned if CDataOwning_Type */ -#define CT_CUSTOM_FIELD_POS 0x00020000 +/* unused 0x00020000 */ #define CT_IS_LONGDOUBLE 0x00040000 #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 */ -#define CT_LAZY_FIELD_LIST 0x01000000 -#define CT_WITH_PACKED_CHANGE 0x02000000 +/* unused 0x01000000 */ +/* unused 0x02000000 */ #define CT_IS_SIGNED_WCHAR 0x04000000 #define CT_PRIMITIVE_ANY (CT_PRIMITIVE_SIGNED | \ CT_PRIMITIVE_UNSIGNED | \ @@ -241,6 +285,16 @@ CT_PRIMITIVE_FLOAT | \ CT_PRIMITIVE_COMPLEX) +/* flags that are mutable at runtime, stored separately in ct_flags_mut to avoid + races with ct_flags + + these are read and set without using atomic operations because CFFI_LOCK is held + while mutating ct_flags_mut + */ +#define CT_CUSTOM_FIELD_POS 0x00000001 +#define CT_WITH_VAR_ARRAY 0x00000002 /* with open-ended array, anywhere */ +#define CT_WITH_PACKED_CHANGE 0x00000004 + typedef struct _ctypedescr { PyObject_VAR_HEAD @@ -263,8 +317,11 @@ typedef struct _ctypedescr { Py_ssize_t ct_length; /* length of arrays, or -1 if unknown; or alignment of primitive and struct types; always -1 for pointers */ - int ct_flags; /* CT_xxx flags */ - + int ct_flags; /* Immutable CT_xxx flags */ + int ct_flags_mut; /* Mutable CT_xxx flags */ + uint8_t ct_under_construction; + uint8_t ct_lazy_field_list; + uint8_t ct_unrealized_struct_or_union; 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; @@ -398,6 +455,10 @@ typedef struct _cffi_allocator_s { } cffi_allocator_t; static const cffi_allocator_t default_allocator = { NULL, NULL, 0 }; static PyObject *FFIError; + +#ifdef Py_GIL_DISABLED +static PyMutex unique_cache_lock; +#endif static PyObject *unique_cache; /************************************************************/ @@ -415,6 +476,10 @@ 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_flags_mut = 0; PyObject_GC_Track(ct); return ct; } @@ -451,19 +516,17 @@ ctypedescr_repr(CTypeDescrObject *ct) return PyText_FromFormat("", ct->ct_name); } +static void remove_dead_unique_reference(PyObject *unique_key); + static void ctypedescr_dealloc(CTypeDescrObject *ct) { PyObject_GC_UnTrack(ct); - if (ct->ct_weakreflist != NULL) - PyObject_ClearWeakRefs((PyObject *) ct); + PyObject_ClearWeakRefs((PyObject *) ct); if (ct->ct_unique_key != NULL) { - /* revive dead object temporarily for DelItem */ - Py_SET_REFCNT(ct, 43); - PyDict_DelItem(unique_cache, ct->ct_unique_key); - assert(Py_REFCNT(ct) == 42); - Py_SET_REFCNT(ct, 0); + /* delete the weak reference from unique_cache */ + remove_dead_unique_reference(ct->ct_unique_key); Py_DECREF(ct->ct_unique_key); } Py_XDECREF(ct->ct_itemdescr); @@ -560,18 +623,29 @@ 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) +{ + assert(ct->ct_flags & (CT_STRUCT | CT_UNION)); + uint8_t lazy_fields = cffi_check_flag(ct->ct_lazy_field_list); + if (lazy_fields) { + // not realized yet + return do_realize_lazy_struct(ct); + } + return ct->ct_stuff != NULL; +} -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) @@ -828,7 +902,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); @@ -1100,6 +1174,8 @@ convert_to_object(char *data, CTypeDescrObject *ct) 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) { @@ -1440,8 +1516,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 && - !CData_Check(value)) { + else if ((cf->cf_type->ct_flags_mut & CT_WITH_VAR_ARRAY) && !CData_Check(value)) { Py_ssize_t subsize = cf->cf_type->ct_size; if (convert_struct_from_object(NULL, cf->cf_type, value, &subsize) < 0) return -1; @@ -1687,21 +1762,21 @@ convert_from_object(char *data, CTypeDescrObject *ct, PyObject *init) error. The warning is turned off if both types are pointers to single bytes. */ char *msg = (ct->ct_flags & CT_IS_VOIDCHAR_PTR ? - "implicit cast to 'char *' from a different pointer type: " - "will be forbidden in the future (check that the types " - "are as you expect; use an explicit ffi.cast() if they " - "are correct)" : + "implicit cast to 'char *' from a different pointer type." + "(check that the types are as you expect; use an explicit " + "ffi.cast() if they are correct)" : "implicit cast from 'char *' to a different pointer type: " - "will be forbidden in the future (check that the types " - "are as you expect; use an explicit ffi.cast() if they " - "are correct)"); + "(check that the types are as you expect; use an explicit " + "ffi.cast() if they are correct)"); if ((ct->ct_flags & ctinit->ct_flags & CT_POINTER) && ct->ct_itemdescr->ct_size == 1 && ctinit->ct_itemdescr->ct_size == 1) { - /* no warning */ + /* no error */ } - else if (PyErr_WarnEx(PyExc_UserWarning, msg, 1)) + else { + PyErr_SetString(PyExc_TypeError, msg); return -1; + } } else { expected = "pointer to same type"; @@ -1788,8 +1863,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; } } @@ -1876,12 +1951,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 & 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; }; @@ -1908,8 +1984,7 @@ get_alignment(CTypeDescrObject *ct) static void cdata_dealloc(CDataObject *cd) { - if (cd->c_weakreflist != NULL) - PyObject_ClearWeakRefs((PyObject *) cd); + PyObject_ClearWeakRefs((PyObject *) cd); Py_DECREF(cd->c_type); #ifndef CFFI_MEM_LEAK /* never release anything, tests only */ @@ -2195,7 +2270,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 (cd->c_type->ct_flags_mut & CT_WITH_VAR_ARRAY) { return ((CDataObject_own_length *)cd)->length; } return -1; @@ -2239,7 +2314,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; } @@ -2532,7 +2607,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 * @@ -2599,16 +2674,21 @@ 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); - if (ct->ct_stuff == NULL) - return NULL; + array_type = (CTypeDescrObject *)new_array_type(ct, -1); + ct->ct_stuff = (PyObject *)array_type; } - ct = (CTypeDescrObject *)ct->ct_stuff; + Py_END_CRITICAL_SECTION(); - cdata = cd->c_data + ct->ct_itemdescr->ct_size * bounds[0]; - return new_sized_cdata(cdata, ct, bounds[1]); + if (array_type == NULL) { + return NULL; + } + + cdata = cd->c_data + array_type->ct_itemdescr->ct_size * bounds[0]; + return new_sized_cdata(cdata, array_type, bounds[1]); } static int @@ -2791,7 +2871,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; @@ -2971,16 +3051,8 @@ static cif_description_t * fb_prepare_cif(PyObject *fargs, CTypeDescrObject *, Py_ssize_t, ffi_abi); /*forward*/ -static PyObject *new_primitive_type(const char *name); /*forward*/ - -static CTypeDescrObject *_get_ct_int(void) -{ - static CTypeDescrObject *ct_int = NULL; - if (ct_int == NULL) { - ct_int = (CTypeDescrObject *)new_primitive_type("int"); - } - return ct_int; -} +static CTypeDescrObject *_get_ct_int(void); +/* forward, implemented in realize_c_type.c */ static Py_ssize_t _prepare_pointer_call_argument(CTypeDescrObject *ctptr, PyObject *init, @@ -3045,6 +3117,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); @@ -3279,11 +3356,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 { @@ -3782,7 +3860,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 (ct->ct_flags_mut & CT_WITH_VAR_ARRAY) { PyErr_SetString(PyExc_TypeError, "return type is a struct/union with a varsize array member"); return NULL; @@ -3876,7 +3954,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", @@ -3887,10 +3969,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 (ctitem->ct_flags_mut & CT_WITH_VAR_ARRAY) { assert(ct->ct_flags & CT_IS_PTR_TO_OWNED); dataoffset = offsetof(CDataObject_own_length, alignment); @@ -4512,7 +4591,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", @@ -4640,7 +4719,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; @@ -4648,6 +4727,46 @@ static PyObject *b_load_library(PyObject *self, PyObject *args) /************************************************************/ +static PyObject *get_or_insert_unique_type(CTypeDescrObject *x, + PyObject *key) +{ + PyObject *wr, *obj; + + if (PyDict_GetItemRef(unique_cache, key, &wr) < 0) { + return NULL; + } + + if (wr != NULL) { + if (PyWeakref_GetRef(wr, &obj) < 0) { + Py_DECREF(wr); + return NULL; + } + Py_DECREF(wr); + if (obj != NULL) { + return obj; + } + } + + /* Use a weakref so that the dictionary does not keep 'x' alive */ + wr = PyWeakref_NewRef((PyObject *)x, NULL); + if (wr == NULL) { + return NULL; + } + + if (PyDict_SetItem(unique_cache, key, wr) < 0) { + Py_DECREF(wr); + return NULL; + } + + assert(x->ct_unique_key == NULL); + Py_INCREF(key); + x->ct_unique_key = key; /* the key will be freed in ctypedescr_dealloc() */ + + Py_DECREF(wr); + Py_INCREF(x); + return (PyObject *)x; +} + static PyObject *get_unique_type(CTypeDescrObject *x, const void *unique_key[], long keylength) { @@ -4665,45 +4784,48 @@ static PyObject *get_unique_type(CTypeDescrObject *x, array [ctype, length] funcptr [ctresult, ellipsis+abi, num_args, ctargs...] */ - PyObject *key, *y, *res; - void *pkey; + PyObject *key, *y; - key = PyBytes_FromStringAndSize(NULL, keylength * sizeof(void *)); - if (key == NULL) - goto error; - - pkey = PyBytes_AS_STRING(key); - memcpy(pkey, unique_key, keylength * sizeof(void *)); - - y = PyDict_GetItem(unique_cache, key); - if (y != NULL) { - Py_DECREF(key); - Py_INCREF(y); + key = PyBytes_FromStringAndSize((const char *)unique_key, keylength * sizeof(void *)); + if (key == NULL) { Py_DECREF(x); - return y; - } - if (PyDict_SetItem(unique_cache, key, (PyObject *)x) < 0) { - Py_DECREF(key); - goto error; + return NULL; } - /* Haaaack for our reference count hack: gcmodule.c must not see this - dictionary. The problem is that any PyDict_SetItem() notices that - 'x' is tracked and re-tracks the unique_cache dictionary. So here - we re-untrack it again... */ - PyObject_GC_UnTrack(unique_cache); - assert(x->ct_unique_key == NULL); - x->ct_unique_key = key; /* the key will be freed in ctypedescr_dealloc() */ - /* the 'value' in unique_cache doesn't count as 1, but don't use - Py_DECREF(x) here because it will confuse debug builds into thinking - there was an extra DECREF in total. */ - res = (PyObject *)x; - Py_SET_REFCNT(res, Py_REFCNT(res) - 1); - return res; + LOCK_UNIQUE_CACHE(); + y = get_or_insert_unique_type(x, key); + UNLOCK_UNIQUE_CACHE(); - error: + Py_DECREF(key); Py_DECREF(x); - return NULL; + return y; +} + +/* Delete a dead weakref from the unique cache */ +static void remove_dead_unique_reference(PyObject *unique_key) +{ + PyObject *wr; + PyObject *tmp = NULL; + int err = 0; + + LOCK_UNIQUE_CACHE(); + /* We need to check that it's not already been replaced by a live weakref + to a different object. */ + wr = PyDict_GetItemWithError(unique_cache, unique_key); + if (wr != NULL) { + err = PyWeakref_GetRef(wr, &tmp); + if (err == 0) { + /* The weakref is dead, delete it. */ + assert(tmp == NULL); + err = PyDict_DelItem(unique_cache, unique_key); + } + } + UNLOCK_UNIQUE_CACHE(); + + Py_XDECREF(tmp); + if (err < 0) { + PyErr_WriteUnraisable(NULL); + } } /* according to the C standard, these types should be equivalent to the @@ -4974,7 +5096,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; @@ -4987,8 +5109,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; @@ -5038,7 +5160,8 @@ static PyObject *new_struct_or_union_type(const char *name, int flag) td->ct_size = -1; td->ct_length = -1; - td->ct_flags = flag | CT_IS_OPAQUE; + 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; @@ -5161,34 +5284,27 @@ static int detect_custom_layout(CTypeDescrObject *ct, int sflags, ct->ct_name); return -1; } - ct->ct_flags |= CT_CUSTOM_FIELD_POS; + ct->ct_flags_mut |= CT_CUSTOM_FIELD_POS; } 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) @@ -5198,20 +5314,16 @@ static PyObject *b_complete_struct_or_union(PyObject *self, PyObject *args) else sflags |= SF_PACKED; - if ((ct->ct_flags & (CT_STRUCT|CT_IS_OPAQUE)) == - (CT_STRUCT|CT_IS_OPAQUE)) { - is_union = 0; - } - else if ((ct->ct_flags & (CT_UNION|CT_IS_OPAQUE)) == - (CT_UNION|CT_IS_OPAQUE)) { - is_union = 1; - } - else { + PyObject *res = NULL; + is_union = ct->ct_flags & CT_UNION; + if (!((ct->ct_flags & CT_UNION) || (ct->ct_flags & CT_STRUCT)) || + !(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; + "first arg must be a non-initialized struct or union ctype"); + goto finally; } - ct->ct_flags &= ~(CT_CUSTOM_FIELD_POS | CT_WITH_PACKED_CHANGE); + ct->ct_flags_mut &= ~CT_CUSTOM_FIELD_POS; + ct->ct_flags_mut &= ~CT_WITH_PACKED_CHANGE; alignment = 1; byteoffset = 0; /* the real value is 'byteoffset+bitoffset*8', which */ @@ -5222,7 +5334,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; @@ -5236,26 +5348,27 @@ 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; + ct->ct_flags_mut |= CT_WITH_VAR_ARRAY; } 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)) { @@ -5265,8 +5378,9 @@ static PyObject *b_complete_struct_or_union(PyObject *self, PyObject *args) 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 (ftype->ct_flags_mut & CT_WITH_VAR_ARRAY) { + ct->ct_flags_mut |= CT_WITH_VAR_ARRAY; + } } if (is_union) @@ -5276,7 +5390,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; @@ -5314,16 +5428,16 @@ static PyObject *b_complete_struct_or_union(PyObject *self, PyObject *args) byteoffset = (byteoffset + falign-1) & ~(falign-1); if (byteoffsetorg != byteoffset) { - ct->ct_flags |= CT_WITH_PACKED_CHANGE; + ct->ct_flags_mut |= CT_WITH_PACKED_CHANGE; } 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; } @@ -5343,17 +5457,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 |= CT_CUSTOM_FIELD_POS; + ct->ct_flags_mut |= CT_CUSTOM_FIELD_POS; } 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) @@ -5370,7 +5484,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 | @@ -5380,7 +5494,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, @@ -5388,7 +5502,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 @@ -5402,7 +5516,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;" */ @@ -5442,7 +5556,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); @@ -5493,7 +5607,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; } } @@ -5518,12 +5632,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) { @@ -5532,23 +5646,48 @@ 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; ct->ct_stuff = interned_fields; - ct->ct_flags &= ~CT_IS_OPAQUE; + cffi_set_flag(ct->ct_unrealized_struct_or_union, 0); + 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; + CFFI_LOCK(); + res = b_complete_struct_or_union_lock_held(ct, fields, totalsize, totalalignment, sflags, pack); + CFFI_UNLOCK(); + return res; } + struct funcbuilder_s { Py_ssize_t nb_bytes; char *bufferp; @@ -5602,7 +5741,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", @@ -5631,7 +5770,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 & CT_CUSTOM_FIELD_POS) { + if (ct->ct_flags_mut & 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, @@ -5641,7 +5780,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 & CT_WITH_PACKED_CHANGE) { + if (ct->ct_flags_mut & CT_WITH_PACKED_CHANGE) { return fb_unsupported(ct, place, "It is a 'packed' structure, with a different layout than " "expected by libffi"); @@ -5992,11 +6131,15 @@ 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 (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'"; PyErr_Format(PyExc_TypeError, msg, fresult->ct_name); @@ -8089,6 +8232,10 @@ init_cffi_backend(void) if (m == NULL) INITERROR; +#ifdef Py_GIL_DISABLED + PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED); +#endif + if (unique_cache == NULL) { unique_cache = PyDict_New(); if (unique_cache == NULL) diff --git a/src/c/cffi1_module.c b/src/c/cffi1_module.c index 06a84fea..70d9ee8e 100644 --- a/src/c/cffi1_module.c +++ b/src/c/cffi1_module.c @@ -130,7 +130,13 @@ static PyObject *_my_Py_InitModule(char *module_name) if (module_def == NULL) return PyErr_NoMemory(); *module_def = local_module_def; - return PyModule_Create(module_def); + PyObject *m = PyModule_Create(module_def); +# ifdef Py_GIL_DISABLED + if (m != NULL) { + PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED); + } +# endif + return m; #else return Py_InitModule(module_name, NULL); #endif diff --git a/src/c/ffi_obj.c b/src/c/ffi_obj.c index f1541466..0c4933ee 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) @@ -25,7 +32,6 @@ struct FFIObject_s { PyObject_HEAD PyObject *gc_wrefs, *gc_wrefs_freelist; PyObject *init_once_cache; - struct _cffi_parse_info_s info; char ctx_is_static, ctx_is_nonempty; builder_c_t types_builder; }; @@ -33,8 +39,6 @@ struct FFIObject_s { static FFIObject *ffi_internal_new(PyTypeObject *ffitype, const struct _cffi_type_context_s *static_ctx) { - static _cffi_opcode_t internal_output[FFI_COMPLEXITY_OUTPUT]; - FFIObject *ffi; if (static_ctx != NULL) { ffi = (FFIObject *)PyObject_GC_New(FFIObject, ffitype); @@ -54,9 +58,6 @@ static FFIObject *ffi_internal_new(PyTypeObject *ffitype, ffi->gc_wrefs = NULL; ffi->gc_wrefs_freelist = NULL; ffi->init_once_cache = NULL; - ffi->info.ctx = &ffi->types_builder.ctx; - ffi->info.output = internal_output; - ffi->info.output_size = FFI_COMPLEXITY_OUTPUT; ffi->ctx_is_static = (static_ctx != NULL); ffi->ctx_is_nonempty = (static_ctx != NULL); return ffi; @@ -145,7 +146,8 @@ static PyObject *ffi_fetch_int_constant(FFIObject *ffi, const char *name, #define ACCEPT_ALL (ACCEPT_STRING | ACCEPT_CTYPE | ACCEPT_CDATA) #define CONSIDER_FN_AS_FNPTR 8 -static CTypeDescrObject *_ffi_bad_type(FFIObject *ffi, const char *input_text) +static PyObject *_ffi_bad_type(struct _cffi_parse_info_s *info, + const char *input_text) { size_t length = strlen(input_text); char *extra; @@ -155,7 +157,7 @@ static CTypeDescrObject *_ffi_bad_type(FFIObject *ffi, const char *input_text) } else { char *p; - size_t i, num_spaces = ffi->info.error_location; + size_t i, num_spaces = info->error_location; extra = alloca(length + num_spaces + 4); p = extra; *p++ = '\n'; @@ -173,7 +175,7 @@ static CTypeDescrObject *_ffi_bad_type(FFIObject *ffi, const char *input_text) *p++ = '^'; *p++ = 0; } - PyErr_Format(FFIError, "%s%s", ffi->info.error_message, extra); + PyErr_Format(FFIError, "%s%s", info->error_message, extra); return NULL; } @@ -185,16 +187,28 @@ static CTypeDescrObject *_ffi_type(FFIObject *ffi, PyObject *arg, */ if ((accept & ACCEPT_STRING) && PyText_Check(arg)) { PyObject *types_dict = ffi->types_builder.types_dict; + /* The types_dict keeps the reference alive. Items are never removed */ PyObject *x = PyDict_GetItem(types_dict, arg); if (x == NULL) { const char *input_text = PyText_AS_UTF8(arg); - int err, index = parse_c_type(&ffi->info, input_text); - if (index < 0) - return _ffi_bad_type(ffi, input_text); - - x = realize_c_type_or_func(&ffi->types_builder, - ffi->info.output, index); + struct _cffi_parse_info_s info; + info.ctx = &ffi->types_builder.ctx; + info.output_size = FFI_COMPLEXITY_OUTPUT; + info.output = PyMem_Malloc(FFI_COMPLEXITY_OUTPUT * sizeof(_cffi_opcode_t)); + if (info.output == NULL) { + PyErr_NoMemory(); + return NULL; + } + int index = parse_c_type(&info, input_text); + if (index < 0) { + x = _ffi_bad_type(&info, input_text); + } + else { + x = realize_c_type_or_func(&ffi->types_builder, + info.output, index); + } + PyMem_Free(info.output); if (x == NULL) return NULL; @@ -206,11 +220,12 @@ static CTypeDescrObject *_ffi_type(FFIObject *ffi, PyObject *arg, sure that in any case the next _ffi_type() with the same 'arg' will succeed early, in PyDict_GetItem() above. */ - err = PyDict_SetItem(types_dict, arg, x); - Py_DECREF(x); /* we know it was written in types_dict (unless out - of mem), so there is at least that ref left */ - if (err < 0) + PyObject *value = PyDict_SetDefault(types_dict, arg, x); + if (value == NULL) { return NULL; + } + Py_DECREF(x); /* we know types_dict holds a strong ref */ + x = value; } if (CTypeDescr_Check(x)) @@ -265,7 +280,11 @@ 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; + if (ct->ct_flags & (CT_STRUCT | CT_UNION)) { + if (force_lazy_struct(ct) < 0) + return NULL; + } + size = cffi_get_size(ct); if (size < 0) { PyErr_Format(FFIError, "don't know the size of ctype '%s'", ct->ct_name); @@ -1001,16 +1020,21 @@ static PyObject *ffi_init_once(FFIObject *self, PyObject *args, PyObject *kwds) in this function */ /* atomically get or create a new dict (no GIL release) */ + Py_BEGIN_CRITICAL_SECTION(self); cache = self->init_once_cache; if (cache == NULL) { - cache = PyDict_New(); - if (cache == NULL) - return NULL; - self->init_once_cache = cache; + self->init_once_cache = cache = PyDict_New(); + } + Py_END_CRITICAL_SECTION(); + + if (cache == NULL) { + return NULL; } /* get the tuple from cache[tag], or make a new one: (False, lock) */ - tup = PyDict_GetItem(cache, tag); + if (PyDict_GetItemRef(cache, tag, &tup) < 0) { + return NULL; + } if (tup == NULL) { lock = PyThread_allocate_lock(); if (lock == NULL) @@ -1033,8 +1057,6 @@ static PyObject *ffi_init_once(FFIObject *self, PyObject *args, PyObject *kwds) Py_DECREF(x); if (tup == NULL) return NULL; - - Py_DECREF(tup); /* there is still a ref inside the dict */ } res = PyTuple_GET_ITEM(tup, 1); @@ -1042,8 +1064,10 @@ static PyObject *ffi_init_once(FFIObject *self, PyObject *args, PyObject *kwds) if (PyTuple_GET_ITEM(tup, 0) == Py_True) { /* tup == (True, result): return the result. */ + Py_DECREF(tup); return res; } + Py_DECREF(tup); /* tup == (False, lock) */ lockobj = res; diff --git a/src/c/lib_obj.c b/src/c/lib_obj.c index bd9ba3fc..ccb2fde6 100644 --- a/src/c/lib_obj.c +++ b/src/c/lib_obj.c @@ -146,11 +146,11 @@ 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; - while (_CFFI_GETOP(opcodes[i]) != _CFFI_OP_FUNCTION_END) + while (_CFFI_GETOP(_CFFI_LOAD_OP(opcodes[i])) != _CFFI_OP_FUNCTION_END) i++; pfargs = alloca(sizeof(CTypeDescrObject *) * (i - type_index - 1)); i = type_index + 1; - while (_CFFI_GETOP(opcodes[i]) != _CFFI_OP_FUNCTION_END) { + while (_CFFI_GETOP(_CFFI_LOAD_OP(opcodes[i])) != _CFFI_OP_FUNCTION_END) { CTypeDescrObject *ct = realize_c_type(lib->l_types_builder, opcodes, i); if (ct == NULL) goto error; @@ -541,7 +541,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/malloc_closure.h b/src/c/malloc_closure.h index bebb93dc..f51a70c6 100644 --- a/src/c/malloc_closure.h +++ b/src/c/malloc_closure.h @@ -154,23 +154,38 @@ static void more_core(void) /******************************************************************/ +#ifdef Py_GIL_DISABLED +static PyMutex malloc_closure_lock; +# define MALLOC_CLOSURE_LOCK() PyMutex_Lock(&malloc_closure_lock) +# define MALLOC_CLOSURE_UNLOCK() PyMutex_Unlock(&malloc_closure_lock) +#else +# define MALLOC_CLOSURE_LOCK() ((void)0) +# define MALLOC_CLOSURE_UNLOCK() ((void)0) +#endif + /* put the item back into the free list */ static void cffi_closure_free(ffi_closure *p) { + MALLOC_CLOSURE_LOCK(); union mmaped_block *item = (union mmaped_block *)p; item->next = free_list; free_list = item; + MALLOC_CLOSURE_UNLOCK(); } /* return one item from the free list, allocating more if needed */ static ffi_closure *cffi_closure_alloc(void) { union mmaped_block *item; + MALLOC_CLOSURE_LOCK(); if (!free_list) more_core(); - if (!free_list) + if (!free_list) { + MALLOC_CLOSURE_UNLOCK(); return NULL; + } item = free_list; free_list = item->next; + MALLOC_CLOSURE_UNLOCK(); return &item->closure; } diff --git a/src/c/minibuffer.h b/src/c/minibuffer.h index c2956eb1..d92da317 100644 --- a/src/c/minibuffer.h +++ b/src/c/minibuffer.h @@ -140,8 +140,7 @@ static void mb_dealloc(MiniBufferObj *ob) { PyObject_GC_UnTrack(ob); - if (ob->mb_weakreflist != NULL) - PyObject_ClearWeakRefs((PyObject *)ob); + PyObject_ClearWeakRefs((PyObject *)ob); Py_XDECREF(ob->mb_keepalive); Py_TYPE(ob)->tp_free((PyObject *)ob); } diff --git a/src/c/misc_thread_common.h b/src/c/misc_thread_common.h index 7d29634b..b0710acc 100644 --- a/src/c/misc_thread_common.h +++ b/src/c/misc_thread_common.h @@ -1,7 +1,10 @@ +#ifndef CFFI_MISC_THREAD_COMMON_H +#define CFFI_MISC_THREAD_COMMON_H #ifndef WITH_THREAD # error "xxx no-thread configuration not tested, please report if you need that" #endif #include "pythread.h" +#include "inttypes.h" struct cffi_tls_s { @@ -22,7 +25,7 @@ struct cffi_tls_s { #endif }; -static struct cffi_tls_s *get_cffi_tls(void); /* in misc_thread_posix.h +static struct cffi_tls_s *get_cffi_tls(void); /* in misc_thread_posix.h or misc_win32.h */ @@ -312,7 +315,7 @@ static void restore_errno_only(void) /* MESS. We can't use PyThreadState_GET(), because that calls PyThreadState_Get() which fails an assert if the result is NULL. - + * in Python 2.7 and <= 3.4, the variable _PyThreadState_Current is directly available, so use that. @@ -390,3 +393,29 @@ static void gil_release(PyGILState_STATE oldstate) //fprintf(stderr, "%p: gil_release(%d), tls=%p\n", get_cffi_tls(), (int)oldstate, get_cffi_tls()); PyGILState_Release(oldstate); } + + +#if PY_VERSION_HEX <= 0x030d00b3 +# define Py_BEGIN_CRITICAL_SECTION(op) { +# define Py_END_CRITICAL_SECTION() } +#endif + +#ifdef Py_GIL_DISABLED +static PyObject _dummy = {0}; +#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) +#define _CFFI_LOAD_OP(arg) cffi_atomic_load(&(arg)) +#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 +#define _CFFI_LOAD_OP(arg) (arg) +#endif + +#define CFFI_LOCK() Py_BEGIN_CRITICAL_SECTION(&_dummy) +#define CFFI_UNLOCK() Py_END_CRITICAL_SECTION() + +#endif /* CFFI_MISC_THREAD_COMMON_H */ diff --git a/src/c/misc_thread_posix.h b/src/c/misc_thread_posix.h index bcc01773..a8056302 100644 --- a/src/c/misc_thread_posix.h +++ b/src/c/misc_thread_posix.h @@ -12,6 +12,8 @@ see a given thread, and keep it alive until the thread is really shut down, using a destructor on the tls key. */ +#ifndef CFFI_MISC_THREAD_POSIX_H +#define CFFI_MISC_THREAD_POSIX_H #include #include "misc_thread_common.h" @@ -47,3 +49,43 @@ static struct cffi_tls_s *get_cffi_tls(void) #define save_errno save_errno_only #define restore_errno restore_errno_only + +#ifdef Py_GIL_DISABLED +# ifndef __ATOMIC_SEQ_CST +# error "The free threading build needs atomic support" +# endif + +/* Minimal atomic support */ +static void *cffi_atomic_load(void **ptr) +{ + return __atomic_load_n(ptr, __ATOMIC_SEQ_CST); +} + +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 f332940c..d7172e1c 100644 --- a/src/c/misc_win32.h +++ b/src/c/misc_win32.h @@ -1,3 +1,7 @@ +#ifndef CFFI_MISC_WIN32_H +#define CFFI_MISC_WIN32_H + + #include /* for alloca() */ @@ -250,9 +254,71 @@ static int dlclose(void *handle) static const char *dlerror(void) { static char buf[32]; - DWORD dw = GetLastError(); + DWORD dw = GetLastError(); if (dw == 0) return NULL; sprintf(buf, "error 0x%x", (unsigned int)dw); return buf; } + +/* Minimal atomic support */ +static int cffi_atomic_compare_exchange(void **ptr, void **expected, + void *value) +{ + void *initial = _InterlockedCompareExchangePointer(ptr, value, expected); + if (initial == *expected) { + return 1; + } + *expected = initial; + return 0; +} + +static void *cffi_atomic_load(void **ptr) +{ +#if defined(_M_X64) || defined(_M_IX86) + return *(volatile void **)ptr; +#elif defined(_M_ARM64) + return (void *)__ldar64((volatile unsigned __int64 *)ptr); +#else +# error "no implementation of cffi_atomic_load" +#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 d9b9f19b..fbfa68ca 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; @@ -10,7 +17,7 @@ typedef struct { static PyObject *all_primitives[_CFFI__NUM_PRIM]; -static CTypeDescrObject *g_ct_voidp, *g_ct_chararray; +static CTypeDescrObject *g_ct_voidp, *g_ct_chararray, *g_ct_int, *g_file_struct; static PyObject *build_primitive_type(int num); /* forward */ @@ -22,6 +29,7 @@ static PyObject *build_primitive_type(int num); /* forward */ static int init_global_types_dict(PyObject *ffi_type_dict) { int err; + Py_ssize_t i; PyObject *ct_void, *ct_char, *ct2, *pnull; /* XXX some leaks in case these functions fail, but well, MemoryErrors during importing an extension module are kind @@ -49,12 +57,40 @@ static int init_global_types_dict(PyObject *ffi_type_dict) return -1; g_ct_chararray = (CTypeDescrObject *)ct2; + g_ct_int = (CTypeDescrObject *)get_primitive_type(_CFFI_PRIM_INT); // 'int' + if (g_ct_int == NULL) + return -1; + + ct2 = new_struct_or_union_type("FILE", + CT_STRUCT | CT_IS_FILE); // 'FILE' + if (ct2 == NULL) + return -1; + g_file_struct = (CTypeDescrObject *)ct2; + pnull = new_simple_cdata(NULL, g_ct_voidp); if (pnull == NULL) return -1; err = PyDict_SetItemString(ffi_type_dict, "NULL", pnull); Py_DECREF(pnull); - return err; + if (err < 0) + return err; + +#ifdef Py_GIL_DISABLED + // Ensure that all primitive types are initialised to avoid race conditions + // on the first access. + for (i = 0; i < _CFFI__NUM_PRIM; i++) { + ct2 = get_primitive_type(i); + if (ct2 == NULL) + return -1; + } +#endif + + return 0; +} + +static CTypeDescrObject *_get_ct_int(void) +{ + return g_ct_int; } static void free_builder_c(builder_c_t *builder, int ctx_is_static) @@ -246,11 +282,17 @@ unexpected_fn_type(PyObject *x) CTypeDescrObject *ct = unwrap_fn_as_fnptr(x); char *text1 = ct->ct_name; char *text2 = text1 + ct->ct_name_position + 1; + size_t prefix_size = ct->ct_name_position - 2; + char *buf = PyMem_Malloc(prefix_size + 1); + if (buf == NULL) { + return NULL; + } + memcpy(buf, text1, prefix_size); + buf[prefix_size] = '\0'; assert(text2[-3] == '('); - text2[-3] = '\0'; PyErr_Format(FFIError, "the type '%s%s' is a function type, not a " - "pointer-to-function type", text1, text2); - text2[-3] = '('; + "pointer-to-function type", buf, text2); + PyMem_Free(buf); return NULL; } @@ -323,16 +365,12 @@ _realize_c_struct_or_union(builder_c_t *builder, int sindex) if (sindex == _CFFI__IO_FILE_STRUCT) { /* returns a single global cached opaque type */ - static PyObject *file_struct = NULL; - if (file_struct == NULL) - file_struct = new_struct_or_union_type("FILE", - CT_STRUCT | CT_IS_FILE); - Py_XINCREF(file_struct); - return file_struct; + Py_INCREF(g_file_struct); + return (PyObject *)g_file_struct; } s = &builder->ctx.struct_unions[sindex]; - op2 = builder->ctx.types[s->type_index]; + op2 = _CFFI_LOAD_OP(builder->ctx.types[s->type_index]); if ((((uintptr_t)op2) & 1) == 0) { x = (PyObject *)op2; /* found already in the "primary" slot */ Py_INCREF(x); @@ -342,6 +380,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 ", @@ -353,17 +393,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; - ct->ct_length = s->alignment; /* may be -1 */ - ct->ct_flags &= ~CT_IS_OPAQUE; - ct->ct_flags |= CT_LAZY_FIELD_LIST; + ct->ct_length = s->alignment; /* may be -1 */ + 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); @@ -392,19 +432,29 @@ _realize_c_struct_or_union(builder_c_t *builder, int sindex) } } } + if (ct != NULL) { + cffi_set_flag(ct->ct_unrealized_struct_or_union, 0); + } /* Update the "primary" OP_STRUCT_UNION slot */ assert((((uintptr_t)x) & 1) == 0); assert(builder->ctx.types[s->type_index] == op2); Py_INCREF(x); +#ifdef Py_GIL_DISABLED + cffi_atomic_store(&builder->ctx.types[s->type_index], x); +#else builder->ctx.types[s->type_index] = x; - +#endif if (ct != NULL && s->size == (size_t)-2) { /* oops, this struct is unnamed and we couldn't generate a C expression to get its size. We have to rely on complete_struct_or_union() to compute it now. */ if (do_realize_lazy_struct(ct) < 0) { +#ifdef Py_GIL_DISABLED + cffi_atomic_store(&builder->ctx.types[s->type_index], op2); +#else builder->ctx.types[s->type_index] = op2; +#endif return NULL; } } @@ -466,7 +516,7 @@ realize_c_type_or_func_now(builder_c_t *builder, _cffi_opcode_t op, _cffi_opcode_t op2; e = &builder->ctx.enums[_CFFI_GETARG(op)]; - op2 = builder->ctx.types[e->type_index]; + op2 = _CFFI_LOAD_OP(builder->ctx.types[e->type_index]); if ((((uintptr_t)op2) & 1) == 0) { x = (PyObject *)op2; Py_INCREF(x); @@ -541,7 +591,11 @@ realize_c_type_or_func_now(builder_c_t *builder, _cffi_opcode_t op, assert((((uintptr_t)x) & 1) == 0); assert(builder->ctx.types[e->type_index] == op2); Py_INCREF(x); +#ifdef Py_GIL_DISABLED + cffi_atomic_store(&builder->ctx.types[e->type_index], x); +#else builder->ctx.types[e->type_index] = x; +#endif /* Done, leave without updating the "current" slot because it may be done already above. If not, never mind, the @@ -566,12 +620,12 @@ realize_c_type_or_func_now(builder_c_t *builder, _cffi_opcode_t op, pointer in the 'opcodes' array, and GETOP() returns a random even value. But OP_FUNCTION_END is odd, so the condition below still works correctly. */ - while (_CFFI_GETOP(opcodes[base_index + num_args]) != - _CFFI_OP_FUNCTION_END) + while (_CFFI_GETOP(_CFFI_LOAD_OP(opcodes[base_index + num_args])) + != _CFFI_OP_FUNCTION_END) num_args++; ellipsis = _CFFI_GETARG(opcodes[base_index + num_args]) & 0x01; - abi = _CFFI_GETARG(opcodes[base_index + num_args]) & 0xFE; + abi = _CFFI_GETARG(opcodes[base_index + num_args]) & 0xFE; switch (abi) { case 0: abi = FFI_DEFAULT_ABI; @@ -639,14 +693,25 @@ realize_c_type_or_func_now(builder_c_t *builder, _cffi_opcode_t op, return x; } +#ifdef Py_GIL_DISABLED +#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 static PyObject * -realize_c_type_or_func(builder_c_t *builder, +realize_c_type_or_func_lock_held(builder_c_t *builder, _cffi_opcode_t opcodes[], int index) { + PyObject *x; - _cffi_opcode_t op = opcodes[index]; + _cffi_opcode_t op = _CFFI_LOAD_OP(opcodes[index]); if ((((uintptr_t)op) & 1) == 0) { x = (PyObject *)op; @@ -670,8 +735,25 @@ realize_c_type_or_func(builder_c_t *builder, assert((((uintptr_t)x) & 1) == 0); assert((((uintptr_t)opcodes[index]) & 1) == 1); Py_INCREF(x); +#ifdef Py_GIL_DISABLED + cffi_atomic_store(&opcodes[index], x); +#else opcodes[index] = x; +#endif } + + return x; +} + + +static PyObject * +realize_c_type_or_func(builder_c_t *builder, + _cffi_opcode_t opcodes[], int index) +{ + PyObject *x; + CFFI_LOCK(); + x = realize_c_type_or_func_lock_held(builder, opcodes, index); + CFFI_UNLOCK(); return x; } @@ -701,19 +783,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)); - if (ct->ct_flags & 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->ct_lazy_field_list assert(!(ct->ct_flags & CT_IS_OPAQUE)); builder = ct->ct_extra; @@ -789,19 +872,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 |= CT_IS_OPAQUE; - res = b_complete_struct_or_union(NULL, args); - ct->ct_flags &= ~CT_IS_OPAQUE; - 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; @@ -809,12 +885,18 @@ static int do_realize_lazy_struct(CTypeDescrObject *ct) } assert(ct->ct_stuff != NULL); - ct->ct_flags &= ~CT_LAZY_FIELD_LIST; + cffi_set_flag(ct->ct_lazy_field_list, 0); Py_DECREF(res); - return 1; - } - else { - assert(ct->ct_flags & CT_IS_OPAQUE); - return 0; } + return ct->ct_stuff != NULL; +} + + +static int do_realize_lazy_struct(CTypeDescrObject *ct) +{ + int res = 0; + CFFI_LOCK(); + res = do_realize_lazy_struct_lock_held(ct); + CFFI_UNLOCK(); + return res; } diff --git a/src/c/test_c.py b/src/c/test_c.py index e1121ed8..1ba62c21 100644 --- a/src/c/test_c.py +++ b/src/c/test_c.py @@ -63,9 +63,9 @@ def _capture_unraisable_hook(ur_args): # ____________________________________________________________ import sys -assert __version__ == "1.18.0.dev0", ("This test_c.py file is for testing a version" - " of cffi that differs from the one that we" - " get from 'import _cffi_backend'") +assert __version__ == "2.0.0.dev0", ("This test_c.py file is for testing a version" + " of cffi that differs from the one that we" + " get from 'import _cffi_backend'") if sys.version_info < (3,): type_or_class = "type" mandatory_b_prefix = '' @@ -1090,6 +1090,7 @@ def test_call_function_5(): f = cast(BFunc5, _testfunc(5)) f() # did not crash +@pytest.mark.thread_unsafe(reason="_testfunc6 uses global state") def test_call_function_6(): BInt = new_primitive_type("int") BIntPtr = new_pointer_type(BInt) @@ -1342,6 +1343,7 @@ def test_read_variable_as_unknown_length_array(): assert repr(stderr).startswith("" + assert list(p[0:3]) == [0, 1, 2] + with pytest.raises(IndexError): + _ = p[0:11] + def test_new_array_args(self): ffi = FFI(backend=self.Backend()) # this tries to be closer to C: where we say "int x[5] = {10, 20, ..}" diff --git a/testing/cffi0/test_ctypes.py b/testing/cffi0/test_ctypes.py index 2a3b84fd..f3a50a12 100644 --- a/testing/cffi0/test_ctypes.py +++ b/testing/cffi0/test_ctypes.py @@ -11,6 +11,10 @@ class TestCTypes(backend_tests.BackendTests): Backend = CTypesBackend TypeRepr = "'>" + def test_array_slicing(self): + pytest.skip("ctypes backend: not supported: " + "slice of array") + def test_array_of_func_ptr(self): pytest.skip("ctypes backend: not supported: " "initializers for function pointers") diff --git a/testing/cffi0/test_ffi_backend.py b/testing/cffi0/test_ffi_backend.py index 399fb777..986f7081 100644 --- a/testing/cffi0/test_ffi_backend.py +++ b/testing/cffi0/test_ffi_backend.py @@ -249,11 +249,13 @@ def _fieldcheck(self, ffi, lib, fnames, name, value): buff2 = ffi.buffer(t, len(buff1)) assert buff1 == buff2 + @pytest.mark.thread_unsafe(reason="FFI verifier is not thread-safe") def test_bitfield_basic(self): self.check("int a; int b:9; int c:20; int y;", 8, 4, 12) self.check("int a; short b:9; short c:7; int y;", 8, 4, 12) self.check("int a; short b:9; short c:9; int y;", 8, 4, 12) + @pytest.mark.thread_unsafe(reason="FFI verifier is not thread-safe") def test_bitfield_reuse_if_enough_space(self): self.check("int a:2; char y;", 1, 4, 4) self.check("int a:1; char b ; int c:1; char y;", 3, 4, 4) @@ -269,6 +271,7 @@ def test_bitfield_reuse_if_enough_space(self): "not (sys.platform == 'darwin' and platform.machine() == 'arm64')" " and " "platform.machine().startswith(('arm', 'aarch64'))") + @pytest.mark.thread_unsafe(reason="FFI verifier is not thread-safe") def test_bitfield_anonymous_no_align(self): L = FFI().alignof("long long") self.check("char y; int :1;", 0, 1, 2) @@ -285,6 +288,7 @@ def test_bitfield_anonymous_no_align(self): "(sys.platform == 'darwin' and platform.machine() == 'arm64')" " or " "not platform.machine().startswith(('arm', 'aarch64'))") + @pytest.mark.thread_unsafe(reason="FFI verifier is not thread-safe") def test_bitfield_anonymous_align_arm(self): L = FFI().alignof("long long") self.check("char y; int :1;", 0, 4, 4) @@ -301,6 +305,7 @@ def test_bitfield_anonymous_align_arm(self): "not (sys.platform == 'darwin' and platform.machine() == 'arm64')" " and " "platform.machine().startswith(('arm', 'aarch64'))") + @pytest.mark.thread_unsafe(reason="FFI verifier is not thread-safe") def test_bitfield_zero(self): L = FFI().alignof("long long") self.check("char y; int :0;", 0, 1, 4) @@ -315,6 +320,7 @@ def test_bitfield_zero(self): "(sys.platform == 'darwin' and platform.machine() == 'arm64')" " or " "not platform.machine().startswith(('arm', 'aarch64'))") + @pytest.mark.thread_unsafe(reason="FFI verifier is not thread-safe") def test_bitfield_zero_arm(self): L = FFI().alignof("long long") self.check("char y; int :0;", 0, 4, 4) diff --git a/testing/cffi0/test_function.py b/testing/cffi0/test_function.py index 6e741575..d23d55fb 100644 --- a/testing/cffi0/test_function.py +++ b/testing/cffi0/test_function.py @@ -114,6 +114,7 @@ def test_tlsalloc(self): y = lib.TlsFree(x) assert y != 0 + @pytest.mark.thread_unsafe(reason="manipulates stderr") def test_fputs(self): if not sys.platform.startswith('linux'): pytest.skip("probably no symbol 'stderr' in the lib") @@ -131,6 +132,7 @@ def test_fputs(self): res = fd.getvalue() assert res == b'hello\n world\n' + @pytest.mark.thread_unsafe(reason="manipulates stderr") def test_fputs_without_const(self): if not sys.platform.startswith('linux'): pytest.skip("probably no symbol 'stderr' in the lib") @@ -148,6 +150,7 @@ def test_fputs_without_const(self): res = fd.getvalue() assert res == b'hello\n world\n' + @pytest.mark.thread_unsafe(reason="manipulates stderr") def test_vararg(self): if not sys.platform.startswith('linux'): pytest.skip("probably no symbol 'stderr' in the lib") @@ -205,6 +208,7 @@ def test_function_has_a_c_type(self): if self.Backend is CTypesBackend: assert repr(fptr).startswith("