From 37f4822720ae88377761258ffd3aa478613e71fe Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 15 Oct 2024 05:56:46 +0300 Subject: [PATCH 1/4] Support PyPy Closes #523 --- .github/workflows/pip_install_gmpy2.yml | 2 +- pyproject.toml | 1 + src/gmpy2.c | 8 ++++ src/gmpy2_cache.c | 2 +- src/gmpy2_convert_gmp.c | 54 +++++++++++++++++++++++++ test/test_misc.py | 5 +++ 6 files changed, 70 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pip_install_gmpy2.yml b/.github/workflows/pip_install_gmpy2.yml index d820bc2b..a0df594c 100644 --- a/.github/workflows/pip_install_gmpy2.yml +++ b/.github/workflows/pip_install_gmpy2.yml @@ -26,7 +26,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: [3.8, 3.11] + python-version: [3.8, 3.11, pypy3.10-nightly] os: [ubuntu-22.04] runs-on: ${{ matrix.os }} steps: diff --git a/pyproject.toml b/pyproject.toml index 2956a749..a5342f07 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,6 +28,7 @@ classifiers = ['Development Status :: 5 - Production/Stable', 'Programming Language :: Python :: 3.12', 'Programming Language :: Python :: 3.13', 'Programming Language :: Python :: Implementation :: CPython', + "Programming Language :: Python :: Implementation :: PyPy", 'Topic :: Scientific/Engineering :: Mathematics', 'Topic :: Software Development :: Libraries :: Python Modules'] requires-python = '>=3.8' diff --git a/src/gmpy2.c b/src/gmpy2.c index 56da4d6b..4e896531 100644 --- a/src/gmpy2.c +++ b/src/gmpy2.c @@ -129,7 +129,11 @@ LGPL 3 or later."; /* The following global structures are used by gmpy_cache.c. */ +#ifndef PYPY_VERSION #define CACHE_SIZE (100) +#else +#define CACHE_SIZE (0) +#endif #define MAX_CACHE_MPZ_LIMBS (64) #define MAX_CACHE_MPFR_BITS (1024) @@ -177,6 +181,7 @@ static PyObject *GMPyExc_Overflow = NULL; static PyObject *GMPyExc_Underflow = NULL; static PyObject *GMPyExc_Erange = NULL; +#ifndef PYPY_VERSION /* * Parameters of Python’s internal representation of integers. */ @@ -184,6 +189,7 @@ static PyObject *GMPyExc_Erange = NULL; size_t int_digit_size, int_nails, int_bits_per_digit; int int_digits_order, int_endianness; +#endif /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * @@ -603,6 +609,7 @@ PyMODINIT_FUNC PyInit_gmpy2(void) PyObject* xmpz = NULL; PyObject* limb_size = NULL; +#ifndef PYPY_VERSION /* Query parameters of Python’s internal representation of integers. */ const PyLongLayout *layout = PyLong_GetNativeLayout(); @@ -611,6 +618,7 @@ PyMODINIT_FUNC PyInit_gmpy2(void) int_bits_per_digit = layout->bits_per_digit; int_nails = int_digit_size*8 - int_bits_per_digit; int_endianness = layout->digit_endianness; +#endif #ifndef STATIC static void *GMPy_C_API[GMPy_API_pointers]; diff --git a/src/gmpy2_cache.c b/src/gmpy2_cache.c index 7fd00038..b8e7858d 100644 --- a/src/gmpy2_cache.c +++ b/src/gmpy2_cache.c @@ -206,7 +206,7 @@ GMPy_XMPZ_New(CTXT_Object *context) if (result == NULL) { return NULL; } - mpz_init(result->z); + mpz_init(result->z); } return result; } diff --git a/src/gmpy2_convert_gmp.c b/src/gmpy2_convert_gmp.c index d82d86f1..b6759b09 100644 --- a/src/gmpy2_convert_gmp.c +++ b/src/gmpy2_convert_gmp.c @@ -44,6 +44,7 @@ static int mpz_set_PyLong(mpz_t z, PyObject *obj) { +#ifndef PYPY_VERSION static PyLongExport long_export; if (PyLong_Export(obj, &long_export) < 0) { @@ -77,6 +78,44 @@ mpz_set_PyLong(mpz_t z, PyObject *obj) } } return 0; +#else + int overflow; + long value = PyLong_AsLongAndOverflow(obj, &overflow); + if (!overflow) { + mpz_set_si(z, value); + return 0; + } + + PyObject *s = PyNumber_ToBase(obj, 16); + + if (!s) { + /* LCOV_EXCL_START */ + return -1; + /* LCOV_EXCL_STOP */ + } + + const char *str = PyUnicode_AsUTF8(s), *p = str; + + if (!str) { + /* LCOV_EXCL_START */ + Py_DECREF(s); + return -1; + /* LCOV_EXCL_STOP */ + } + + int negative = (str[0] == '-'); + + p += 2; + if (negative) { + p++; + } + mpz_init_set_str(z, p, 16); + Py_DECREF(s); + if (negative) { + mpz_neg(z, z); + } + return 0; +#endif } static MPZ_Object * @@ -148,6 +187,7 @@ GMPy_PyLong_From_MPZ(MPZ_Object *obj, CTXT_Object *context) return PyLong_FromLong(mpz_get_si(obj->z)); } +#ifndef PYPY_VERSION size_t size = (mpz_sizeinbase(obj->z, 2) + int_bits_per_digit - 1) / int_bits_per_digit; void *digits; @@ -163,6 +203,20 @@ GMPy_PyLong_From_MPZ(MPZ_Object *obj, CTXT_Object *context) int_endianness, int_nails, obj->z); return PyLongWriter_Finish(writer); +#else + PyObject *str = GMPy_PyStr_From_MPZ(obj, 16, 0, NULL); + + if (!str) { + /* LCOV_EXCL_START */ + return NULL; + /* LCOV_EXCL_STOP */ + } + + PyObject *res = PyLong_FromUnicodeObject(str, 16); + + Py_DECREF(str); + return res; +#endif } static PyObject * diff --git a/test/test_misc.py b/test/test_misc.py index ab5e5c08..89a1211b 100644 --- a/test/test_misc.py +++ b/test/test_misc.py @@ -1,5 +1,8 @@ +import platform import sys +import pytest + import gmpy2 @@ -17,6 +20,8 @@ def test_misc(): 'under LGPL 3 or later.') +@pytest.mark.skipif(platform.python_implementation() == "PyPy", + reason="sys.getsizeof raises TypeError") def test_sizeof(): assert sys.getsizeof(gmpy2.mpz(10)) > 0 assert sys.getsizeof(gmpy2.mpfr('1.0')) > 0 From 550f652837fc282547062836db2b4fcd1eb06525 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Fri, 3 Jan 2025 15:15:16 +0300 Subject: [PATCH 2/4] Ignore .hypothesis in pytest --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a5342f07..db542649 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -57,5 +57,5 @@ gmpy2 = ['*.pxd', '*.h', '*.dll', '*.lib'] [tool.pytest.ini_options] addopts = "--durations=10" -norecursedirs = ['build', '.eggs', '.git'] +norecursedirs = ['build', '.eggs', '.git', '.hypothesis'] xfail_strict = true From 7a7b7dcd83a73c1d224f46f6709d2e1b59263c4a Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Fri, 3 Jan 2025 15:15:40 +0300 Subject: [PATCH 3/4] Fix typo in GMPy_XMPZ_getseters --- src/gmpy2_xmpz.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/gmpy2_xmpz.c b/src/gmpy2_xmpz.c index 73a88830..ea048751 100644 --- a/src/gmpy2_xmpz.c +++ b/src/gmpy2_xmpz.c @@ -84,7 +84,7 @@ static PyGetSetDef GMPy_XMPZ_getseters[] = "the denominator of a rational number in lowest terms", NULL }, { "real", (getter)GMPy_XMPZ_Attrib_GetReal, NULL, "the real part of a complex number", NULL }, - { "denominator", (getter)GMPy_XMPZ_Attrib_GetImag, NULL, + { "imag", (getter)GMPy_XMPZ_Attrib_GetImag, NULL, "the imaginary part of a complex number", NULL }, {NULL} }; From b6bd13b50acd2050786d1da4ab86cc516c0ce122 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Fri, 3 Jan 2025 15:56:35 +0300 Subject: [PATCH 4/4] Correct GMPy_MPC_To_Binary() Docs (for PyBytes_AsString) says: The data must not be modified in any way, unless the object was just created using PyBytes_FromStringAndSize(NULL, size). That's not the case for GMPy_MPFR_To_Binary(). So, PyPy seems to be right in rejecting modifications. --- src/gmpy2_binary.c | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/src/gmpy2_binary.c b/src/gmpy2_binary.c index ce50501d..5a9c9936 100644 --- a/src/gmpy2_binary.c +++ b/src/gmpy2_binary.c @@ -646,11 +646,39 @@ GMPy_MPC_To_Binary(MPC_Object *obj) Py_DECREF((PyObject*)real); Py_DECREF((PyObject*)imag); - PyBytes_AS_STRING(result)[0] = 0x05; - PyBytes_AS_STRING(temp)[0] = 0x05; + Py_ssize_t result_size, temp_size; + char *result_str, *temp_str; - PyBytes_ConcatAndDel(&result, temp); - return result; + if ((PyBytes_AsStringAndSize(result, &result_str, &result_size) < 0) + || (PyBytes_AsStringAndSize(temp, &temp_str, &temp_size) < 0)) + { + /* LCOV_EXCL_START */ + Py_DECREF(result); + Py_DECREF(temp); + return NULL; + /* LCOV_EXCL_STOP */ + } + result_str[0] = 0x05; + temp_str[0] = 0x05; + + char *buf = PyMem_Malloc(result_size + temp_size); + + if (!buf) { + /* LCOV_EXCL_START */ + Py_DECREF(result); + Py_DECREF(temp); + return NULL; + /* LCOV_EXCL_STOP */ + } + memcpy(buf, result_str, result_size); + memcpy(buf + result_size, temp_str, temp_size); + Py_DECREF(result); + Py_DECREF(temp); + + PyObject *ret = PyBytes_FromStringAndSize(buf, result_size + temp_size); + + PyMem_Free(buf); + return ret; } PyDoc_STRVAR(doc_from_binary,