From dc71bd03c4db0e50cf85b8b3d1cdd4c44b9bd385 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gonzalo=20Tornar=C3=ADa?= Date: Mon, 2 Oct 2023 13:20:32 -0300 Subject: [PATCH] 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. --- src/sage/arith/long.pxd | 9 ++- src/sage/cpython/pycore_long.h | 98 +++++++++++++++++++++++++++++ src/sage/cpython/pycore_long.pxd | 9 +++ src/sage/libs/gmp/pylong.pxd | 3 +- src/sage/libs/gmp/pylong.pyx | 22 +++---- src/sage/symbolic/ginac/numeric.cpp | 13 ++-- 6 files changed, 129 insertions(+), 25 deletions(-) create mode 100644 src/sage/cpython/pycore_long.h create mode 100644 src/sage/cpython/pycore_long.pxd diff --git a/src/sage/arith/long.pxd b/src/sage/arith/long.pxd index 0031a0ae337..3ea70cef571 100644 --- a/src/sage/arith/long.pxd +++ b/src/sage/arith/long.pxd @@ -19,6 +19,8 @@ from libc.limits cimport LONG_MIN, LONG_MAX from cpython.object cimport Py_SIZE from cpython.number cimport PyNumber_Index, PyIndex_Check from cpython.longintrepr cimport py_long, PyLong_SHIFT, digit +from sage.cpython.pycore_long cimport ( + ob_digit, _PyLong_IsNegative, _PyLong_DigitCount) from sage.libs.gmp.mpz cimport mpz_fits_slong_p, mpz_get_si from sage.rings.integer_fake cimport is_Integer, Integer_AS_MPZ @@ -299,8 +301,11 @@ cdef inline bint integer_check_long_py(x, long* value, int* err): return 0 # x is a Python "int" (aka PyLongObject or py_long in cython) - cdef const digit* D = (x).ob_digit - cdef Py_ssize_t size = Py_SIZE(x) + cdef const digit* D = ob_digit(x) + cdef Py_ssize_t size = _PyLong_DigitCount(x) + + if _PyLong_IsNegative(x): + size = -size # We assume PyLong_SHIFT <= BITS_IN_LONG <= 3 * PyLong_SHIFT. # This is true in all the default configurations: diff --git a/src/sage/cpython/pycore_long.h b/src/sage/cpython/pycore_long.h new file mode 100644 index 00000000000..ff1a73d097a --- /dev/null +++ b/src/sage/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(o) = size; +#else + Py_SET_SIZE(op, sign < 0 ? -size : size); +#endif +} + +#endif diff --git a/src/sage/cpython/pycore_long.pxd b/src/sage/cpython/pycore_long.pxd new file mode 100644 index 00000000000..41de637ff18 --- /dev/null +++ b/src/sage/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/src/sage/libs/gmp/pylong.pxd b/src/sage/libs/gmp/pylong.pxd index a73f1da6d6f..84e1bb8cd87 100644 --- a/src/sage/libs/gmp/pylong.pxd +++ b/src/sage/libs/gmp/pylong.pxd @@ -2,9 +2,10 @@ Various functions to deal with conversion mpz <-> Python int/long """ +from cpython.longintrepr cimport py_long from sage.libs.gmp.types cimport * cdef mpz_get_pylong(mpz_srcptr z) cdef mpz_get_pyintlong(mpz_srcptr z) -cdef int mpz_set_pylong(mpz_ptr z, L) except -1 +cdef int mpz_set_pylong(mpz_ptr z, py_long L) except -1 cdef Py_hash_t mpz_pythonhash(mpz_srcptr z) diff --git a/src/sage/libs/gmp/pylong.pyx b/src/sage/libs/gmp/pylong.pyx index d5993cca5a5..1a36c29d3fa 100644 --- a/src/sage/libs/gmp/pylong.pyx +++ b/src/sage/libs/gmp/pylong.pyx @@ -28,6 +28,8 @@ AUTHORS: from cpython.object cimport Py_SIZE from cpython.long cimport PyLong_FromLong from cpython.longintrepr cimport _PyLong_New, py_long, digit, PyLong_SHIFT +from sage.cpython.pycore_long cimport (ob_digit, _PyLong_IsNegative, + _PyLong_DigitCount, _PyLong_SetSignAndDigitCount) from .mpz cimport * cdef extern from *: @@ -60,12 +62,9 @@ cdef mpz_get_pylong_large(mpz_srcptr z): """ cdef size_t nbits = mpz_sizeinbase(z, 2) cdef size_t pylong_size = (nbits + PyLong_SHIFT - 1) // PyLong_SHIFT - L = _PyLong_New(pylong_size) - mpz_export(L.ob_digit, NULL, - -1, sizeof(digit), 0, PyLong_nails, z) - if mpz_sgn(z) < 0: - # Set correct size - Py_SET_SIZE(L, -pylong_size) + cdef py_long L = _PyLong_New(pylong_size) + mpz_export(ob_digit(L), NULL, -1, sizeof(digit), 0, PyLong_nails, z) + _PyLong_SetSignAndDigitCount(L, mpz_sgn(z), pylong_size) return L @@ -88,16 +87,13 @@ cdef mpz_get_pyintlong(mpz_srcptr z): return mpz_get_pylong_large(z) -cdef int mpz_set_pylong(mpz_ptr z, L) except -1: +cdef int mpz_set_pylong(mpz_ptr z, py_long L) except -1: """ Convert a Python ``long`` `L` to an ``mpz``. """ - cdef Py_ssize_t pylong_size = Py_SIZE(L) - if pylong_size < 0: - pylong_size = -pylong_size - mpz_import(z, pylong_size, -1, sizeof(digit), 0, PyLong_nails, - (L).ob_digit) - if Py_SIZE(L) < 0: + cdef Py_ssize_t pylong_size = _PyLong_DigitCount(L) + mpz_import(z, pylong_size, -1, sizeof(digit), 0, PyLong_nails, ob_digit(L)) + if _PyLong_IsNegative(L): mpz_neg(z, z) diff --git a/src/sage/symbolic/ginac/numeric.cpp b/src/sage/symbolic/ginac/numeric.cpp index c4152e092e3..4bcf45e8793 100644 --- a/src/sage/symbolic/ginac/numeric.cpp +++ b/src/sage/symbolic/ginac/numeric.cpp @@ -67,6 +67,7 @@ #include "archive.h" #include "tostring.h" #include "utils.h" +#include "../../cpython/pycore_long.h" #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-register" @@ -706,18 +707,12 @@ static long _mpq_pythonhash(mpq_t the_rat) // Initialize an mpz_t from a Python long integer static void _mpz_set_pylong(mpz_t z, PyLongObject* l) { - Py_ssize_t pylong_size = Py_SIZE(l); - int sign = 1; - - if (pylong_size < 0) { - pylong_size = -pylong_size; - sign = -1; - } + Py_ssize_t pylong_size = _PyLong_DigitCount(l); mpz_import(z, pylong_size, -1, sizeof(digit), 0, - 8*sizeof(digit) - PyLong_SHIFT, l->ob_digit); + 8*sizeof(digit) - PyLong_SHIFT, ob_digit(l)); - if (sign < 0) + if (_PyLong_IsNegative(l)) mpz_neg(z, z); }