From c9c32c64d2fa4326908f63428e3202e17d52e6a2 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Mon, 9 Dec 2024 04:17:30 +0300 Subject: [PATCH] Use PEP 757 C-API to import/export ints Closes #467 --- src/gmpy2.c | 16 ++++ src/gmpy2_convert.h | 20 ----- src/gmpy2_convert_gmp.c | 69 ++++++++------- src/pythoncapi_compat.h | 185 +++++++++++++++++++++++++++++++++++++++- 4 files changed, 236 insertions(+), 54 deletions(-) diff --git a/src/gmpy2.c b/src/gmpy2.c index 0179a5e8..836a106f 100644 --- a/src/gmpy2.c +++ b/src/gmpy2.c @@ -177,6 +177,14 @@ static PyObject *GMPyExc_Overflow = NULL; static PyObject *GMPyExc_Underflow = NULL; static PyObject *GMPyExc_Erange = NULL; +/* + * Parameters of Python’s internal representation of integers. + */ + + +size_t int_digit_size, int_nails, int_bits_per_digit; +int int_digits_order, int_endianness; + /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * End of global data declarations. * @@ -595,6 +603,14 @@ PyMODINIT_FUNC PyInit_gmpy2(void) PyObject* xmpz = NULL; PyObject* limb_size = NULL; + /* Query parameters of Python’s internal representation of integers. */ + const PyLongLayout *layout = PyLong_GetNativeLayout(); + + int_digit_size = layout->digit_size; + int_digits_order = layout->digits_order; + int_bits_per_digit = layout->bits_per_digit; + int_nails = int_digit_size*8 - int_bits_per_digit; + int_endianness = layout->digit_endianness; #ifndef STATIC static void *GMPy_C_API[GMPy_API_pointers]; diff --git a/src/gmpy2_convert.h b/src/gmpy2_convert.h index 50dd2386..842597a6 100644 --- a/src/gmpy2_convert.h +++ b/src/gmpy2_convert.h @@ -133,26 +133,6 @@ extern "C" { #define IS_TYPE_COMPLEX_ONLY(x) ((x > OBJ_TYPE_REAL) && \ (x < OBJ_TYPE_COMPLEX)) -/* Compatibility macros (to work with PyLongObject internals). - */ - -#if PY_VERSION_HEX >= 0x030C0000 -# define TAG_FROM_SIGN_AND_SIZE(is_neg, size) ((is_neg?2:(size==0)) | (((size_t)size) << 3)) -# define _PyLong_SetSignAndDigitCount(obj, is_neg, size) (obj->long_value.lv_tag = TAG_FROM_SIGN_AND_SIZE(is_neg, size)) -#elif PY_VERSION_HEX >= 0x030900A4 -# define _PyLong_SetSignAndDigitCount(obj, is_neg, size) (Py_SET_SIZE(obj, (is_neg?-1:1)*size)) -#else -# define _PyLong_SetSignAndDigitCount(obj, is_neg, size) (Py_SIZE(obj) = (is_neg?-1:1)*size) -#endif - -#if PY_VERSION_HEX >= 0x030C0000 -# define GET_OB_DIGIT(obj) ((PyLongObject*)obj)->long_value.ob_digit -# define _PyLong_DigitCount(obj) (((PyLongObject*)obj)->long_value.lv_tag >> 3) -#else -# define GET_OB_DIGIT(obj) obj->ob_digit -# define _PyLong_DigitCount(obj) (PyLong_IsNegative(obj) ? -Py_SIZE(obj):Py_SIZE(obj)) -#endif - /* Since the macros are used in gmpy2's codebase, these functions are skipped * until they are needed for the C API in the future. */ diff --git a/src/gmpy2_convert_gmp.c b/src/gmpy2_convert_gmp.c index 1e2d9b4c..61fef136 100644 --- a/src/gmpy2_convert_gmp.c +++ b/src/gmpy2_convert_gmp.c @@ -44,24 +44,37 @@ static int mpz_set_PyLong(mpz_t z, PyObject *obj) { - Py_ssize_t len = _PyLong_DigitCount(obj); - PyLongObject *templong = (PyLongObject*)obj; - - switch (len) { - case 1: - mpz_set_si(z, (sdigit)GET_OB_DIGIT(templong)[0]); - break; - case 0: - mpz_set_si(z, 0); - break; - default: - mpz_import(z, len, -1, sizeof(digit), 0, - sizeof(digit)*8 - PyLong_SHIFT, - GET_OB_DIGIT(templong)); + static PyLongExport long_export; + + if (PyLong_Export(obj, &long_export) < 0) { + /* LCOV_EXCL_START */ + return -1; + /* LCOV_EXCL_STOP */ + } + if (long_export.digits) { + mpz_import(z, long_export.ndigits, int_digits_order, int_digit_size, + int_endianness, int_nails, long_export.digits); + if (long_export.negative) { + mpz_neg(z, z); + } + PyLong_FreeExport(&long_export); } + else { + const int64_t value = long_export.value; - if (PyLong_IsNegative(obj)) { - mpz_neg(z, z); + if (LONG_MIN <= value && value <= LONG_MAX) { + mpz_set_si(z, value); + } + else { + mpz_import(z, 1, -1, sizeof(int64_t), 0, 0, &value); + if (value < 0) { + mpz_t tmp; + mpz_init(tmp); + mpz_ui_pow_ui(tmp, 2, 64); + mpz_sub(z, z, tmp); + mpz_clear(tmp); + } + } } return 0; } @@ -135,27 +148,21 @@ GMPy_PyLong_From_MPZ(MPZ_Object *obj, CTXT_Object *context) return PyLong_FromLong(mpz_get_si(obj->z)); } - /* Assume gmp uses limbs as least as large as the builtin longs do */ - - size_t count, size = (mpz_sizeinbase(obj->z, 2) + - PyLong_SHIFT - 1) / PyLong_SHIFT; - PyLongObject *result; - - if (!(result = _PyLong_New(size))) { + size_t size = (mpz_sizeinbase(obj->z, 2) + + int_bits_per_digit - 1) / int_bits_per_digit; + void *digits; + PyLongWriter *writer = PyLongWriter_Create(mpz_sgn(obj->z) < 0, size, + &digits); + if (writer == NULL) { /* LCOV_EXCL_START */ return NULL; /* LCOV_EXCL_STOP */ } - mpz_export(GET_OB_DIGIT(result), &count, -1, sizeof(digit), 0, - sizeof(digit)*8 - PyLong_SHIFT, obj->z); + mpz_export(digits, NULL, int_digits_order, int_digit_size, + int_endianness, int_nails, obj->z); - for (size_t i = count; i < size; i++) { - GET_OB_DIGIT(result)[i] = 0; - } - _PyLong_SetSignAndDigitCount(result, mpz_sgn(obj->z) < 0, count); - - return (PyObject*)result; + return PyLongWriter_Finish(writer); } static PyObject * diff --git a/src/pythoncapi_compat.h b/src/pythoncapi_compat.h index 7c768586..5e22e7d8 100644 --- a/src/pythoncapi_compat.h +++ b/src/pythoncapi_compat.h @@ -287,7 +287,7 @@ PyFrame_GetVarString(PyFrameObject *frame, const char *name) // bpo-39947 added PyThreadState_GetInterpreter() to Python 3.9.0a5 -#if PY_VERSION_HEX < 0x030900A5 || defined(PYPY_VERSION) +#if PY_VERSION_HEX < 0x030900A5 || (defined(PYPY_VERSION) && PY_VERSION_HEX < 0x030B0000) static inline PyInterpreterState * PyThreadState_GetInterpreter(PyThreadState *tstate) { @@ -918,7 +918,7 @@ static inline int PyObject_VisitManagedDict(PyObject *obj, visitproc visit, void *arg) { PyObject **dict = _PyObject_GetDictPtr(obj); - if (*dict == NULL) { + if (dict == NULL || *dict == NULL) { return -1; } Py_VISIT(*dict); @@ -929,7 +929,7 @@ static inline void PyObject_ClearManagedDict(PyObject *obj) { PyObject **dict = _PyObject_GetDictPtr(obj); - if (*dict == NULL) { + if (dict == NULL || *dict == NULL) { return; } Py_CLEAR(*dict); @@ -1720,6 +1720,185 @@ static inline int PyLong_AsUInt64(PyObject *obj, uint64_t *pvalue) #endif +// gh-102471 added import and export API for integers to 3.14.0a2. +#if PY_VERSION_HEX < 0x030E00A2 && PY_VERSION_HEX >= 0x03000000 && !defined(PYPY_VERSION) +// Helpers to access PyLongObject internals. +static inline void +_PyLong_SetSignAndDigitCount(PyLongObject *op, int sign, Py_ssize_t size) +{ +#if PY_VERSION_HEX >= 0x030C0000 + op->long_value.lv_tag = (uintptr_t)(1 - sign) | ((uintptr_t)(size) << 3); +#elif PY_VERSION_HEX >= 0x030900A4 + Py_SET_SIZE(op, sign * size); +#else + Py_SIZE(op) = sign * size; +#endif +} + +static inline Py_ssize_t +_PyLong_DigitCount(const PyLongObject *op) +{ +#if PY_VERSION_HEX >= 0x030C0000 + return (Py_ssize_t)(op->long_value.lv_tag >> 3); +#else + return _PyLong_Sign((PyObject*)op) < 0 ? -Py_SIZE(op) : Py_SIZE(op); +#endif +} + +static inline digit* +_PyLong_GetDigits(const PyLongObject *op) +{ +#if PY_VERSION_HEX >= 0x030C0000 + return (digit*)(op->long_value.ob_digit); +#else + return (digit*)(op->ob_digit); +#endif +} + +typedef struct PyLongLayout { + uint8_t bits_per_digit; + uint8_t digit_size; + int8_t digits_order; + int8_t digit_endianness; +} PyLongLayout; + +typedef struct PyLongExport { + int64_t value; + uint8_t negative; + Py_ssize_t ndigits; + const void *digits; + Py_uintptr_t _reserved; +} PyLongExport; + +typedef struct PyLongWriter PyLongWriter; + +static inline const PyLongLayout* +PyLong_GetNativeLayout(void) +{ + static const PyLongLayout PyLong_LAYOUT = { + PyLong_SHIFT, + sizeof(digit), + -1, // least significant first + PY_LITTLE_ENDIAN ? -1 : 1, + }; + + return &PyLong_LAYOUT; +} + +static inline int +PyLong_Export(PyObject *obj, PyLongExport *export_long) +{ + if (!PyLong_Check(obj)) { + memset(export_long, 0, sizeof(*export_long)); + PyErr_Format(PyExc_TypeError, "expected int, got %s", + Py_TYPE(obj)->tp_name); + return -1; + } + + // Fast-path: try to convert to a int64_t + PyLongObject *self = (PyLongObject*)obj; + int overflow; +#if SIZEOF_LONG == 8 + long value = PyLong_AsLongAndOverflow(obj, &overflow); +#else + // Windows has 32-bit long, so use 64-bit long long instead + long long value = PyLong_AsLongLongAndOverflow(obj, &overflow); +#endif + Py_BUILD_ASSERT(sizeof(value) == sizeof(int64_t)); + // the function cannot fail since obj is a PyLongObject + assert(!(value == -1 && PyErr_Occurred())); + + if (!overflow) { + export_long->value = value; + export_long->negative = 0; + export_long->ndigits = 0; + export_long->digits = 0; + export_long->_reserved = 0; + } + else { + export_long->value = 0; + export_long->negative = _PyLong_Sign(obj) < 0; + export_long->ndigits = _PyLong_DigitCount(self); + if (export_long->ndigits == 0) { + export_long->ndigits = 1; + } + export_long->digits = _PyLong_GetDigits(self); + export_long->_reserved = (Py_uintptr_t)Py_NewRef(obj); + } + return 0; +} + +static inline void +PyLong_FreeExport(PyLongExport *export_long) +{ + PyObject *obj = (PyObject*)export_long->_reserved; + + if (obj) { + export_long->_reserved = 0; + Py_DECREF(obj); + } +} + +static inline PyLongWriter* +PyLongWriter_Create(int negative, Py_ssize_t ndigits, void **digits) +{ + if (ndigits <= 0) { + PyErr_SetString(PyExc_ValueError, "ndigits must be positive"); + return NULL; + } + assert(digits != NULL); + + PyLongObject *obj = _PyLong_New(ndigits); + if (obj == NULL) { + return NULL; + } + _PyLong_SetSignAndDigitCount(obj, negative?-1:1, ndigits); + + *digits = _PyLong_GetDigits(obj); + return (PyLongWriter*)obj; +} + +static inline void +PyLongWriter_Discard(PyLongWriter *writer) +{ + PyLongObject *obj = (PyLongObject *)writer; + + assert(Py_REFCNT(obj) == 1); + Py_DECREF(obj); +} + +static inline PyObject* +PyLongWriter_Finish(PyLongWriter *writer) +{ + PyObject *obj = (PyObject *)writer; + PyLongObject *self = (PyLongObject*)obj; + Py_ssize_t j = _PyLong_DigitCount(self); + Py_ssize_t i = j; + int sign = _PyLong_Sign(obj); + + assert(Py_REFCNT(obj) == 1); + + // Normalize and get singleton if possible + while (i > 0 && _PyLong_GetDigits(self)[i-1] == 0) { + --i; + } + if (i != j) { + if (i == 0) { + sign = 0; + } + _PyLong_SetSignAndDigitCount(self, sign, i); + } + if (i <= 1) { + long val = sign * (long)(_PyLong_GetDigits(self)[0]); + Py_DECREF(obj); + return PyLong_FromLong(val); + } + + return obj; +} +#endif + + #ifdef __cplusplus } #endif