From 03d6c6a5032845048219f61c386a54b5fb78a196 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Thu, 9 Jul 2026 11:37:49 +0300 Subject: [PATCH 1/6] Use PyLong_As/FromNativeBytes API for mpz <-> int Closes #313 --- gmp.c | 121 +++++++++++++++++++++++++++++++++------------------------- 1 file changed, 68 insertions(+), 53 deletions(-) diff --git a/gmp.c b/gmp.c index 9485fd03..2972ddc4 100644 --- a/gmp.c +++ b/gmp.c @@ -180,49 +180,12 @@ MPZ_from_str(PyObject *obj, int base) return res; } +static zz_err zz_set_bytes(const unsigned char *buffer, size_t length, + bool is_signed, zz_t *u); + static MPZ_Object * MPZ_from_int(PyObject *obj) { -#if defined(ON_CPYTHON) && !defined(Py_LIMITED_API) - PyLongExport long_export = {0, 0, 0, 0, 0}; - const zz_layout *int_layout = (zz_layout *)PyLong_GetNativeLayout(); - MPZ_Object *res = NULL; - - if (PyLong_Export(obj, &long_export) < 0) { - return res; /* LCOV_EXCL_LINE */ - } - if (long_export.digits) { - res = MPZ_new(); - if (!res) { - return NULL; /* LCOV_EXCL_LINE */ - } - - zz_err ret = zz_import((size_t)long_export.ndigits, - long_export.digits, *int_layout, &res->z); - - if (ret) { - /* LCOV_EXCL_START */ - if (ret == ZZ_MEM) { - return (MPZ_Object *)PyErr_NoMemory(); - } - PyErr_SetString(PyExc_OverflowError, - "too many digits in integer"); - return NULL; - /* LCOV_EXCL_STOP */ - } - if (long_export.negative) { - (void)zz_neg(&res->z, &res->z); - } - PyLong_FreeExport(&long_export); - } - else { - res = MPZ_new(); - if (res && zz_set(long_export.value, &res->z)) { - PyErr_NoMemory(); /* LCOV_EXCL_LINE */ - } - } - return res; -#else int64_t value; if (!PyLong_AsInt64(obj, &value)) { @@ -235,6 +198,50 @@ MPZ_from_int(PyObject *obj) } PyErr_Clear(); +#if defined(ON_CPYTHON) && PY_VERSION_HEX > 0x030D00A0 \ + && !defined(Py_LIMITED_API) + Py_ssize_t expected = PyLong_AsNativeBytes(obj, NULL, 0, 0); + + if (expected < 0) { + return NULL; /* LCOV_EXCL_LINE */ + } + + unsigned char *buffer = malloc((size_t)expected); + + if (!buffer) { + return (MPZ_Object *)PyErr_NoMemory(); /* LCOV_EXCL_LINE */ + } + + expected = PyLong_AsNativeBytes(obj, buffer, expected, 0); + if (expected < 0) { +free: + /* LCOV_EXCL_START */ + free(buffer); + return NULL; + /* LCOV_EXCL_STOP */ + } + + MPZ_Object *res = MPZ_new(); + + if (!res) { + goto free; /* LCOV_EXCL_LINE */ + } + + zz_err ret = zz_set_bytes(buffer, (size_t)expected, true, &res->z); + + free(buffer); + if (ret) { + /* LCOV_EXCL_START */ + if (ret == ZZ_MEM) { + return (MPZ_Object *)PyErr_NoMemory(); + } + PyErr_SetString(PyExc_OverflowError, + "too many digits in integer"); + return NULL; + /* LCOV_EXCL_STOP */ + } + return res; +#else PyObject *str = PyNumber_ToBase(obj, 16); if (!str) { @@ -245,9 +252,14 @@ MPZ_from_int(PyObject *obj) Py_DECREF(str); return res; -#endif /* defined(ON_CPYTHON) && !defined(Py_LIMITED_API) */ +#endif /* defined(ON_CPYTHON) && PY_VERSION_HEX > 0x030D00A0 + && !defined(Py_LIMITED_API) */ } +static zz_err zz_get_bytes(const zz_t *u, size_t length, + bool is_signed, unsigned char **buffer); +static void revstr(unsigned char *s, Py_ssize_t l, Py_ssize_t r); + static PyObject * MPZ_to_int(MPZ_Object *u) { @@ -257,19 +269,21 @@ MPZ_to_int(MPZ_Object *u) return PyLong_FromInt64(value); } -#if defined(ON_CPYTHON) && !defined(Py_LIMITED_API) - const zz_layout *int_layout = (zz_layout *)PyLong_GetNativeLayout(); - size_t size = (zz_bitlen(&u->z) + int_layout->bits_per_digit - - 1)/int_layout->bits_per_digit; - void *digits; - PyLongWriter *writer = PyLongWriter_Create(zz_isneg(&u->z), - (Py_ssize_t)size, &digits); +#if defined(ON_CPYTHON) && PY_VERSION_HEX > 0x030D00A0 \ + && !defined(Py_LIMITED_API) + size_t size = (zz_bitlen(&u->z) + 7)/8 + 1; + unsigned char *buffer = malloc(size); - if (!writer) { - return NULL; /* LCOV_EXCL_LINE */ + if (!buffer) { + return PyErr_NoMemory(); /* LCOV_EXCL_LINE */ } - (void)zz_export(&u->z, *int_layout, size, digits); - return PyLongWriter_Finish(writer); + (void)zz_get_bytes(&u->z, size, true, &buffer); + revstr(buffer, 0, (Py_ssize_t)size - 1); + + PyObject *res = PyLong_FromNativeBytes(buffer, size, -1); + + free(buffer); + return res; #else size_t len; @@ -289,7 +303,8 @@ MPZ_to_int(MPZ_Object *u) free(buf); return res; -#endif /* defined(ON_CPYTHON) && !defined(Py_LIMITED_API) */ +#endif /* defined(ON_CPYTHON) && PY_VERSION_HEX > 0x030D00A0 + && !defined(Py_LIMITED_API) */ } static void From d315d7c349bf75ae3a0e36f052a4f1b96a41d264 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Fri, 10 Jul 2026 02:26:07 +0300 Subject: [PATCH 2/6] +1 --- gmp.c | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/gmp.c b/gmp.c index 2972ddc4..81204ecf 100644 --- a/gmp.c +++ b/gmp.c @@ -182,6 +182,7 @@ MPZ_from_str(PyObject *obj, int base) static zz_err zz_set_bytes(const unsigned char *buffer, size_t length, bool is_signed, zz_t *u); +static void revstr(unsigned char *s, Py_ssize_t l, Py_ssize_t r); static MPZ_Object * MPZ_from_int(PyObject *obj) @@ -200,7 +201,7 @@ MPZ_from_int(PyObject *obj) #if defined(ON_CPYTHON) && PY_VERSION_HEX > 0x030D00A0 \ && !defined(Py_LIMITED_API) - Py_ssize_t expected = PyLong_AsNativeBytes(obj, NULL, 0, 0); + Py_ssize_t expected = PyLong_AsNativeBytes(obj, NULL, 0, -1); if (expected < 0) { return NULL; /* LCOV_EXCL_LINE */ @@ -212,7 +213,7 @@ MPZ_from_int(PyObject *obj) return (MPZ_Object *)PyErr_NoMemory(); /* LCOV_EXCL_LINE */ } - expected = PyLong_AsNativeBytes(obj, buffer, expected, 0); + expected = PyLong_AsNativeBytes(obj, buffer, expected, -1); if (expected < 0) { free: /* LCOV_EXCL_START */ @@ -227,8 +228,16 @@ MPZ_from_int(PyObject *obj) goto free; /* LCOV_EXCL_LINE */ } - zz_err ret = zz_set_bytes(buffer, (size_t)expected, true, &res->z); + zz_err ret; + if (PyLong_IsNegative(obj)) { + revstr(buffer, 0, expected - 1); + ret = zz_set_bytes(buffer, (size_t)expected, true, &res->z); + } + else { + ret = zz_import((size_t)expected, buffer, + (zz_layout){8, 1, -1, 0}, &res->z); + } free(buffer); if (ret) { /* LCOV_EXCL_START */ @@ -258,7 +267,6 @@ MPZ_from_int(PyObject *obj) static zz_err zz_get_bytes(const zz_t *u, size_t length, bool is_signed, unsigned char **buffer); -static void revstr(unsigned char *s, Py_ssize_t l, Py_ssize_t r); static PyObject * MPZ_to_int(MPZ_Object *u) From 0b2d0b563abfea9e9bad8aa7017c83059ce93507 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Fri, 10 Jul 2026 03:45:31 +0300 Subject: [PATCH 3/6] Revert "+1" This reverts commit d315d7c349bf75ae3a0e36f052a4f1b96a41d264. --- gmp.c | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/gmp.c b/gmp.c index 81204ecf..2972ddc4 100644 --- a/gmp.c +++ b/gmp.c @@ -182,7 +182,6 @@ MPZ_from_str(PyObject *obj, int base) static zz_err zz_set_bytes(const unsigned char *buffer, size_t length, bool is_signed, zz_t *u); -static void revstr(unsigned char *s, Py_ssize_t l, Py_ssize_t r); static MPZ_Object * MPZ_from_int(PyObject *obj) @@ -201,7 +200,7 @@ MPZ_from_int(PyObject *obj) #if defined(ON_CPYTHON) && PY_VERSION_HEX > 0x030D00A0 \ && !defined(Py_LIMITED_API) - Py_ssize_t expected = PyLong_AsNativeBytes(obj, NULL, 0, -1); + Py_ssize_t expected = PyLong_AsNativeBytes(obj, NULL, 0, 0); if (expected < 0) { return NULL; /* LCOV_EXCL_LINE */ @@ -213,7 +212,7 @@ MPZ_from_int(PyObject *obj) return (MPZ_Object *)PyErr_NoMemory(); /* LCOV_EXCL_LINE */ } - expected = PyLong_AsNativeBytes(obj, buffer, expected, -1); + expected = PyLong_AsNativeBytes(obj, buffer, expected, 0); if (expected < 0) { free: /* LCOV_EXCL_START */ @@ -228,16 +227,8 @@ MPZ_from_int(PyObject *obj) goto free; /* LCOV_EXCL_LINE */ } - zz_err ret; + zz_err ret = zz_set_bytes(buffer, (size_t)expected, true, &res->z); - if (PyLong_IsNegative(obj)) { - revstr(buffer, 0, expected - 1); - ret = zz_set_bytes(buffer, (size_t)expected, true, &res->z); - } - else { - ret = zz_import((size_t)expected, buffer, - (zz_layout){8, 1, -1, 0}, &res->z); - } free(buffer); if (ret) { /* LCOV_EXCL_START */ @@ -267,6 +258,7 @@ MPZ_from_int(PyObject *obj) static zz_err zz_get_bytes(const zz_t *u, size_t length, bool is_signed, unsigned char **buffer); +static void revstr(unsigned char *s, Py_ssize_t l, Py_ssize_t r); static PyObject * MPZ_to_int(MPZ_Object *u) From f70837123099ce85917f33551c1539d76dee32e9 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sun, 15 Nov 2026 03:17:11 +0300 Subject: [PATCH 4/6] +1 --- gmp.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/gmp.c b/gmp.c index 4818df7b..0ba7c28e 100644 --- a/gmp.c +++ b/gmp.c @@ -156,9 +156,6 @@ MPZ_from_str(PyObject *obj, int base) return res; } -static zz_err zz_set_bytes(const unsigned char *buffer, size_t length, - bool is_signed, zz_t *u); - Py_LOCAL(MPZ_Object *) MPZ_from_int(PyObject *obj) { @@ -232,9 +229,7 @@ MPZ_from_int(PyObject *obj) && !defined(Py_LIMITED_API) */ } -static zz_err zz_get_bytes(const zz_t *u, size_t length, - bool is_signed, unsigned char **buffer); -static void revstr(unsigned char *s, Py_ssize_t l, Py_ssize_t r); +Py_LOCAL(void) revstr(unsigned char *s, Py_ssize_t l, Py_ssize_t r); Py_LOCAL(PyObject *) MPZ_to_int(MPZ_Object *u) From fc85e2e19a8ad9e66d8c26b82dab6534240f6214 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sun, 15 Nov 2026 05:11:44 +0300 Subject: [PATCH 5/6] new patch, XXX: assume little endian machine --- gmp.c | 54 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 40 insertions(+), 14 deletions(-) diff --git a/gmp.c b/gmp.c index 0ba7c28e..776b911e 100644 --- a/gmp.c +++ b/gmp.c @@ -156,6 +156,8 @@ MPZ_from_str(PyObject *obj, int base) return res; } +#include + Py_LOCAL(MPZ_Object *) MPZ_from_int(PyObject *obj) { @@ -173,23 +175,24 @@ MPZ_from_int(PyObject *obj) #if defined(ON_CPYTHON) && PY_VERSION_HEX > 0x030D00A0 \ && !defined(Py_LIMITED_API) - Py_ssize_t expected = PyLong_AsNativeBytes(obj, NULL, 0, 0); + Py_ssize_t expected = PyLong_AsNativeBytes(obj, NULL, 0, 1); if (expected < 0) { return NULL; /* LCOV_EXCL_LINE */ } - unsigned char *buffer = malloc((size_t)expected); + Py_ssize_t len = (8*expected + bits_per_digit - 1)/bits_per_digit; + unsigned char *buf = malloc((size_t)len*8); - if (!buffer) { + if (!buf) { return (MPZ_Object *)PyErr_NoMemory(); /* LCOV_EXCL_LINE */ } - expected = PyLong_AsNativeBytes(obj, buffer, expected, 0); + expected = PyLong_AsNativeBytes(obj, buf, len*8, 1); if (expected < 0) { free: /* LCOV_EXCL_START */ - free(buffer); + free(buf); return NULL; /* LCOV_EXCL_STOP */ } @@ -200,9 +203,11 @@ MPZ_from_int(PyObject *obj) goto free; /* LCOV_EXCL_LINE */ } - zz_err ret = zz_set_bytes(buffer, (size_t)expected, true, &res->z); + /* XXX: assume little endian machine */ + const zz_layout *native_layout = zz_get_layout(); + zz_err ret = zz_import((size_t)len, buf, *native_layout, &res->z); - free(buffer); + free(buf); if (ret) { /* LCOV_EXCL_START */ if (ret == ZZ_MEM) { @@ -213,6 +218,21 @@ MPZ_from_int(PyObject *obj) return NULL; /* LCOV_EXCL_STOP */ } + if (PyLong_IsNegative(obj)) { +#define SIGN_EXT(x) (-(uint64_t)((int64_t)(x) < 0)) + zz_size_t n = res->z.size; + uint64_t csign = SIGN_EXT(res->z.digits[n - 1]); + + while (n > 0 && res->z.digits[n - 1] == csign) { + n--; + } + res->z.size = n; + if (!mpn_neg(res->z.digits, res->z.digits, res->z.size)) { + res->z.digits[n] = 1; + res->z.size++; + } + res->z.negative = true; + } return res; #else PyObject *str = PyNumber_ToBase(obj, 16); @@ -242,18 +262,24 @@ MPZ_to_int(MPZ_Object *u) #if defined(ON_CPYTHON) && PY_VERSION_HEX > 0x030D00A0 \ && !defined(Py_LIMITED_API) - size_t size = (zz_bitlen(&u->z) + 7)/8 + 1; - unsigned char *buffer = malloc(size); + /* XXX: little endian machine */ + size_t size = (zz_bitlen(&u->z) + 7)/8; + unsigned char *buf = malloc(size); - if (!buffer) { + if (!buf) { return PyErr_NoMemory(); /* LCOV_EXCL_LINE */ } - (void)zz_get_bytes(&u->z, size, true, &buffer); - revstr(buffer, 0, (Py_ssize_t)size - 1); - PyObject *res = PyLong_FromNativeBytes(buffer, size, -1); + const zz_layout *native_layout = zz_get_layout(); + + (void)zz_export(&u->z, *native_layout, size, buf); + + PyObject *res = PyLong_FromNativeBytes(buf, size, 5); - free(buffer); + free(buf); + if (zz_isneg(&u->z)) { + Py_SETREF(res, PyNumber_Negative(res)); + } return res; #else size_t len; From 45b99c0a6c44223ae61265d444d893738be52b66 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sun, 15 Nov 2026 06:59:21 +0300 Subject: [PATCH 6/6] + PyLong_AsInt64AndOverflow --- gmp.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/gmp.c b/gmp.c index 776b911e..f36de6b1 100644 --- a/gmp.c +++ b/gmp.c @@ -158,12 +158,19 @@ MPZ_from_str(PyObject *obj, int base) #include +Py_LOCAL_INLINE(int64_t) PyLong_AsInt64AndOverflow(PyObject *obj, int *error); + Py_LOCAL(MPZ_Object *) MPZ_from_int(PyObject *obj) { - int64_t value; + int error; + int64_t value = PyLong_AsInt64AndOverflow(obj, &error); + + if (!error) { + if (value == -1 && PyErr_Occurred()) { + return NULL; + } - if (!PyLong_AsInt64(obj, &value)) { MPZ_Object *res = MPZ_new(); if (res && zz_set(value, &res->z)) { @@ -171,7 +178,6 @@ MPZ_from_int(PyObject *obj) } return res; } - PyErr_Clear(); #if defined(ON_CPYTHON) && PY_VERSION_HEX > 0x030D00A0 \ && !defined(Py_LIMITED_API)