From 0044b49cd4daf2622b8a564d5386ace53f3ddba9 Mon Sep 17 00:00:00 2001 From: Dima Pasechnik Date: Tue, 7 May 2024 23:06:11 +0100 Subject: [PATCH 1/4] WIP - port to Cython 3, and GAP 4.12 --- gappy/core.pyx | 11 ++++++----- gappy/gap_includes.pxd | 27 +++------------------------ gappy/gapobj.pyx | 30 ++++++++++++++---------------- pyproject.toml | 2 +- setup.cfg | 2 +- 5 files changed, 25 insertions(+), 47 deletions(-) diff --git a/gappy/core.pyx b/gappy/core.pyx index 8a80964..4619c91 100644 --- a/gappy/core.pyx +++ b/gappy/core.pyx @@ -163,7 +163,7 @@ cdef void dereference_obj(Obj obj): owned_objects_refcount[wrapped] = refcount - 1 -cdef void gasman_callback() with gil: +cdef void gasman_callback() noexcept with gil: """ Callback before each GAP garbage collection """ @@ -454,7 +454,7 @@ cdef str extract_errout(): return msg_py -cdef void error_handler() with gil: +cdef void error_handler() noexcept with gil: """ The gappy error handler. @@ -463,7 +463,7 @@ cdef void error_handler() with gil: TODO: We should probably prevent re-entering this function if we are already handling an error; if there is an error in our stream - handling code below it could result in a stack overflow. + h/andling code below it could result in a stack overflow. """ cdef PyObject* exc_type = NULL cdef PyObject* exc_val = NULL @@ -1387,8 +1387,9 @@ cdef class Gap: >>> gap.collect() """ self.initialize() - rc = GAP_CollectBags(1) - if rc != 1: + try: + GAP_CollectBags(1) + except: raise RuntimeError('Garbage collection failed.') diff --git a/gappy/gap_includes.pxd b/gappy/gap_includes.pxd index bd8f93e..0a2ba0e 100644 --- a/gappy/gap_includes.pxd +++ b/gappy/gap_includes.pxd @@ -125,27 +125,6 @@ cdef extern from "gap/libgap-api.h" nogil: Obj GAP_NewPrecord(Int) -cdef extern from "gap/gasman.h" nogil: - """ - #define GAP_CollectBags(full) CollectBags(0, full) - """ - void GAP_MarkBag "MarkBag" (Obj bag) - UInt GAP_CollectBags(UInt full) - - -cdef extern from "gap/io.h" nogil: - UInt OpenOutputStream(Obj stream) - UInt CloseOutput() - - -# TODO: Replace this with a GAP_MakeStringWithLen from the public API; -# see https://github.com/gap-system/gap/issues/4211 -cdef extern from "gap/stringobj.h" nogil: - """ - static inline Obj GAP_MakeStringWithLen(char *s, size_t len) { - Obj ret; - C_NEW_STRING(ret, len, s); - return ret; - } - """ - Obj GAP_MakeStringWithLen(char *, size_t) + void GAP_MarkBag (Obj bag) + void GAP_CollectBags(UInt) + Obj GAP_MakeStringWithLen(const char *, UInt) diff --git a/gappy/gapobj.pyx b/gappy/gapobj.pyx index 0b84ea5..a5dc2f1 100644 --- a/gappy/gapobj.pyx +++ b/gappy/gapobj.pyx @@ -101,18 +101,18 @@ cdef void capture_stdout(Obj func, Obj obj, Obj out): args[0] = out args[1] = GAP_True stream = GAP_CallFuncArray(output_text_string, 2, args) - stream_ok = OpenOutputStream(stream) + # stream_ok = OpenOutputStream(stream) sig_off() - if not stream_ok: - raise GAPError("failed to open output capture stream for " - "representing GAP object") + #if not stream_ok: + # raise GAPError("failed to open output capture stream for " + # "representing GAP object") args[0] = obj - sig_on() - GAP_CallFuncArray(func, 1, args) - CloseOutput() - sig_off() + #sig_on() + #GAP_CallFuncArray(func, 1, args) + #CloseOutput() + #sig_off() finally: sig_GAP_Leave() @@ -271,16 +271,14 @@ cdef Obj make_gap_string(s) except NULL: ``Obj`` A GAP C ``Obj`` representing a GAP string. """ - - cdef bytes b - + cdef Obj res + cdef UInt slen try: GAP_Enter() - if not isinstance(s, bytes): - b = s.encode('utf-8') - else: - b = s - return GAP_MakeStringWithLen(b, len(b)) + b = s.encode() + slen = len(b) + res = GAP_MakeStringWithLen(b, slen) + return res finally: GAP_Leave() diff --git a/pyproject.toml b/pyproject.toml index a291bf5..cd4ac11 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [build-system] requires = [ - "Cython<3.0.0", + "Cython", "cysignals", "setuptools>=42", "setuptools_scm[toml]>=3.4", diff --git a/setup.cfg b/setup.cfg index c40469d..6e91d58 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,7 +1,7 @@ [metadata] name = gappy-system author = E. Madison Bray -author-email = embray@lri.fr +author_email = embray@lri.fr description = Python interface to GAP url = https://github.com/embray/gappy long_description = file: README.rst, CHANGES.rst From 13f85280dab5d7c22500e594a9fb28a368e8c32f Mon Sep 17 00:00:00 2001 From: Dima Pasechnik Date: Thu, 9 May 2024 12:37:02 +0100 Subject: [PATCH 2/4] bump various python etc versions --- .github/workflows/ci.yml | 15 ++++----------- pyproject.toml | 2 +- setup.cfg | 7 ++++--- 3 files changed, 9 insertions(+), 15 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 883b1b7..61d70b1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,19 +13,12 @@ jobs: fail-fast: false matrix: platform: ["ubuntu-latest", "macos-latest"] - python-version: ["3.6", "3.7", "3.8"] - gap-version: ["4.10.2", "4.11.0"] - exclude: - - python-version: "3.6" - gap-version: "4.11.0" - - python-version: "3.7" - gap-version: "4.11.0" - - python-version: "3.8" - gap-version: "4.10.2" + python-version: ["3.8", "3.9", "3.10", "3.11"] + gap-version: ["4.11.0", "4.12.2", "4.13.0"] runs-on: "${{ matrix.platform }}" steps: - - uses: actions/checkout@v2 - - uses: actions/cache@v1 + - uses: actions/checkout@v4 + - uses: actions/cache@v4 env: # Increase this value to reset cache manually CACHE_NUMBER: 0 diff --git a/pyproject.toml b/pyproject.toml index cd4ac11..0fbbf72 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,7 @@ requires = [ "Cython", "cysignals", - "setuptools>=42", + "setuptools>=61.2", "setuptools_scm[toml]>=3.4", "wheel" ] diff --git a/setup.cfg b/setup.cfg index 6e91d58..b8cd54c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -16,15 +16,16 @@ classifiers = License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+) Operating System :: POSIX :: Linux Programming Language :: Cython - Programming Language :: Python :: 3.6 - Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 + Programming Language :: Python :: 3.10 + Programming Language :: Python :: 3.11 Topic :: Scientific/Engineering :: Mathematics [options] # We set packages to find: to automatically find all sub-packages packages = find: -python_requires = >=3.6 +python_requires = >=3.8 setup_requires = setuptools_scm install_requires = cysignals From bb5d73244cf82b319f4ae370f34ac124912329fe Mon Sep 17 00:00:00 2001 From: Dima Pasechnik Date: Thu, 9 May 2024 16:58:33 +0100 Subject: [PATCH 3/4] port to python 3.12, following cypari2/fpylll/sage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The layout for python integers changed in python 3.12. We add a module `gappy.cpython.pycore_long` which copies the new (internal) api of PyLong from python 3.12. We also implement fallback version of these functions suitable for python pre-3.12. Note the files implementing the `pycore_long` module (`pycore_long.pxd` and `pycore_long.h`) are shared with SageMath, fpylll and cypari2. cf. sagemath/sage, commit dc71bd03c4db0e50cf85b8b3d1cdd4c44b9bd385 Author: Gonzalo TornarĂ­a Date: Mon Oct 2 13:20:32 2023 -0300 py312: changes in PyLong internals The layout for python integers changed in python 3.12. We add a module `sage.cpython.pycore_long` which copies the new (internal) api of PyLong from python 3.12. We also implement fallback version of these functions suitable for python pre-3.12. Note the files implementing the `pycore_long` module (`pycore_long.pxd` and `pycore_long.h`) are shared with fpylll and cypari2. --- gappy/cpython/pycore_long.h | 98 +++++++++++++++++++++++++++++++++++ gappy/cpython/pycore_long.pxd | 9 ++++ gappy/gapobj.pyx | 7 +-- setup.cfg | 1 + 4 files changed, 112 insertions(+), 3 deletions(-) create mode 100644 gappy/cpython/pycore_long.h create mode 100644 gappy/cpython/pycore_long.pxd diff --git a/gappy/cpython/pycore_long.h b/gappy/cpython/pycore_long.h new file mode 100644 index 0000000..99561f1 --- /dev/null +++ b/gappy/cpython/pycore_long.h @@ -0,0 +1,98 @@ +#include "Python.h" +#include + +#if PY_VERSION_HEX >= 0x030C00A5 +#define ob_digit(o) (((PyLongObject*)o)->long_value.ob_digit) +#else +#define ob_digit(o) (((PyLongObject*)o)->ob_digit) +#endif + +#if PY_VERSION_HEX >= 0x030C00A7 +// taken from cpython:Include/internal/pycore_long.h @ 3.12 + +/* Long value tag bits: + * 0-1: Sign bits value = (1-sign), ie. negative=2, positive=0, zero=1. + * 2: Reserved for immortality bit + * 3+ Unsigned digit count + */ +#define SIGN_MASK 3 +#define SIGN_ZERO 1 +#define SIGN_NEGATIVE 2 +#define NON_SIZE_BITS 3 + +static inline bool +_PyLong_IsZero(const PyLongObject *op) +{ + return (op->long_value.lv_tag & SIGN_MASK) == SIGN_ZERO; +} + +static inline bool +_PyLong_IsNegative(const PyLongObject *op) +{ + return (op->long_value.lv_tag & SIGN_MASK) == SIGN_NEGATIVE; +} + +static inline bool +_PyLong_IsPositive(const PyLongObject *op) +{ + return (op->long_value.lv_tag & SIGN_MASK) == 0; +} + +static inline Py_ssize_t +_PyLong_DigitCount(const PyLongObject *op) +{ + assert(PyLong_Check(op)); + return op->long_value.lv_tag >> NON_SIZE_BITS; +} + +#define TAG_FROM_SIGN_AND_SIZE(sign, size) ((1 - (sign)) | ((size) << NON_SIZE_BITS)) + +static inline void +_PyLong_SetSignAndDigitCount(PyLongObject *op, int sign, Py_ssize_t size) +{ + assert(size >= 0); + assert(-1 <= sign && sign <= 1); + assert(sign != 0 || size == 0); + op->long_value.lv_tag = TAG_FROM_SIGN_AND_SIZE(sign, (size_t)size); +} + +#else +// fallback for < 3.12 + +static inline bool +_PyLong_IsZero(const PyLongObject *op) +{ + return Py_SIZE(op) == 0; +} + +static inline bool +_PyLong_IsNegative(const PyLongObject *op) +{ + return Py_SIZE(op) < 0; +} + +static inline bool +_PyLong_IsPositive(const PyLongObject *op) +{ + return Py_SIZE(op) > 0; +} + +static inline Py_ssize_t +_PyLong_DigitCount(const PyLongObject *op) +{ + Py_ssize_t size = Py_SIZE(op); + return size < 0 ? -size : size; +} + +static inline void +_PyLong_SetSignAndDigitCount(PyLongObject *op, int sign, Py_ssize_t size) +{ +#if (PY_MAJOR_VERSION == 3) && (PY_MINOR_VERSION < 9) +// The function Py_SET_SIZE is defined starting with python 3.9. + Py_SIZE(op) = size; +#else + Py_SET_SIZE(op, sign < 0 ? -size : size); +#endif +} + +#endif diff --git a/gappy/cpython/pycore_long.pxd b/gappy/cpython/pycore_long.pxd new file mode 100644 index 0000000..41de637 --- /dev/null +++ b/gappy/cpython/pycore_long.pxd @@ -0,0 +1,9 @@ +from cpython.longintrepr cimport py_long, digit + +cdef extern from "pycore_long.h": + digit* ob_digit(py_long o) + bint _PyLong_IsZero(py_long o) + bint _PyLong_IsNegative(py_long o) + bint _PyLong_IsPositive(py_long o) + Py_ssize_t _PyLong_DigitCount(py_long o) + void _PyLong_SetSignAndDigitCount(py_long o, int sign, Py_ssize_t size) diff --git a/gappy/gapobj.pyx b/gappy/gapobj.pyx index a5dc2f1..0105ab0 100644 --- a/gappy/gapobj.pyx +++ b/gappy/gapobj.pyx @@ -35,6 +35,7 @@ from .exceptions import GAPError from .operations import OperationInspector from .utils import _SPECIAL_ATTRS, _converter_for_type +from .cpython.pycore_long cimport (ob_digit) ############################################################################ ### helper functions to construct lists and records ######################## @@ -205,13 +206,13 @@ cdef Obj make_gap_integer(x) except NULL: if -1 <= size <= 1: # Shortcut for smaller ints (up to 30 bits) - s = ((x).ob_digit[0]) + s = ((ob_digit(x))[0]) limbs = &s else: # See https://github.com/gap-system/gap/issues/4209 mpz_init(z) mpz_import(z, size * sign, -1, sizeof(digit), 0, - (sizeof(digit) * 8) - PyLong_SHIFT, (x).ob_digit) + (sizeof(digit) * 8) - PyLong_SHIFT, ob_digit(x)) do_clear = 1 if sign < 0: mpz_neg(z, z) @@ -1805,7 +1806,7 @@ cdef class GapInteger(GapObj): # e.g. if 2**30 we require 31 bits and with PyLong_SHIFT = 30 # this returns 2 x = _PyLong_New((nbits + PyLong_SHIFT - 1) // PyLong_SHIFT) - mpz_export(x.ob_digit, NULL, -1, sizeof(digit), 0, + mpz_export(ob_digit(x), NULL, -1, sizeof(digit), 0, (sizeof(digit) * 8) - PyLong_SHIFT, z) sign = mpz_sgn(z) x *= sign diff --git a/setup.cfg b/setup.cfg index b8cd54c..b396e8b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -20,6 +20,7 @@ classifiers = Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 + Programming Language :: Python :: 3.12 Topic :: Scientific/Engineering :: Mathematics [options] From f0829d56cea1779e1cb53976eb4f3827837af511 Mon Sep 17 00:00:00 2001 From: Dima Pasechnik Date: Wed, 5 Jun 2024 19:12:15 +0100 Subject: [PATCH 4/4] allow other than bz2 formats for conda --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0096436..ce32f36 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,7 +29,7 @@ jobs: with: auto-update-conda: true python-version: "${{ matrix.python-version }}" - use-only-tar-bz2: true + use-only-tar-bz2: false - name: "Conda info" run: "conda info" - name: "Conda install dependencies"