From 80f1a04d79b216b917c5b6d42f5b77084b426545 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sat, 6 Jul 2024 10:01:15 +0300 Subject: [PATCH 01/26] gh-102471: convert decimal module to use PyLongWriter API (PEP 757) --- Modules/_decimal/_decimal.c | 42 ++++++++++++++++++++++++------------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index c564813036e504..5bc3cc26cc6363 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -3639,12 +3639,13 @@ dec_format(PyObject *dec, PyObject *args) static PyObject * dec_as_long(PyObject *dec, PyObject *context, int round) { - PyLongObject *pylong; + PyObject *pylong; digit *ob_digit; size_t n; mpd_t *x; mpd_context_t workctx; uint32_t status = 0; + const PyLongLayout *layout = PyLong_GetNativeLayout(); if (mpd_isspecial(MPD(dec))) { if (mpd_isnan(MPD(dec))) { @@ -3672,34 +3673,45 @@ dec_as_long(PyObject *dec, PyObject *context, int round) } status = 0; - ob_digit = NULL; + + int64_t val = mpd_qget_i64(x, &status); + if (!status) { + mpd_del(x); + return PyLong_FromInt64(val); + } + + n = (mpd_sizeinbase(x, 2) + + layout->bits_per_digit - 1) / layout->bits_per_digit; + PyLongWriter *writer = PyLongWriter_Create(mpd_isnegative(x), n, + (void**)&ob_digit); + /* mpd_sizeinbase can overestimate size by 1 digit, set it to zero. */ + ob_digit[n-1] = 0; + if (writer == NULL) { + PyLongWriter_Discard(writer); + mpd_del(x); + return NULL; + } + + status = 0; #if PYLONG_BITS_IN_DIGIT == 30 - n = mpd_qexport_u32(&ob_digit, 0, PyLong_BASE, x, &status); + n = mpd_qexport_u32(&ob_digit, n, PyLong_BASE, x, &status); #elif PYLONG_BITS_IN_DIGIT == 15 - n = mpd_qexport_u16(&ob_digit, 0, PyLong_BASE, x, &status); + n = mpd_qexport_u16(&ob_digit, n, PyLong_BASE, x, &status); #else #error "PYLONG_BITS_IN_DIGIT should be 15 or 30" #endif if (n == SIZE_MAX) { PyErr_NoMemory(); + PyLongWriter_Discard(writer); mpd_del(x); return NULL; } - if (n == 1) { - sdigit val = mpd_arith_sign(x) * ob_digit[0]; - mpd_free(ob_digit); - mpd_del(x); - return PyLong_FromLong(val); - } - assert(n > 0); - assert(!mpd_iszero(x)); - pylong = _PyLong_FromDigits(mpd_isnegative(x), n, ob_digit); - mpd_free(ob_digit); + pylong = PyLongWriter_Finish(writer); mpd_del(x); - return (PyObject *) pylong; + return pylong; } /* Convert a Decimal to its exact integer ratio representation. */ From c13b7d23e6347ca8eaa4f8c8e287d433c7e76372 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sat, 14 Dec 2024 03:40:58 +0300 Subject: [PATCH 02/26] + news --- .../next/C_API/2024-12-14-03-40-15.gh-issue-127925.FF7aov.rst | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 Misc/NEWS.d/next/C_API/2024-12-14-03-40-15.gh-issue-127925.FF7aov.rst diff --git a/Misc/NEWS.d/next/C_API/2024-12-14-03-40-15.gh-issue-127925.FF7aov.rst b/Misc/NEWS.d/next/C_API/2024-12-14-03-40-15.gh-issue-127925.FF7aov.rst new file mode 100644 index 00000000000000..2e91b4d60d1bc2 --- /dev/null +++ b/Misc/NEWS.d/next/C_API/2024-12-14-03-40-15.gh-issue-127925.FF7aov.rst @@ -0,0 +1,3 @@ +Convert the :mod:`decimal` module to use :pep:`757` API. That offer some +speedup, if integer part of the :class:`~decimal.Decimal` instance is small. +Patch by Sergey B Kirpichev. From 589f926c01687833860f157ae88cbd8b6e5ccc45 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sat, 14 Dec 2024 03:44:02 +0300 Subject: [PATCH 03/26] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Modules/_decimal/_decimal.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index 5bc3cc26cc6363..33c9d95e4a073f 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -3680,8 +3680,8 @@ dec_as_long(PyObject *dec, PyObject *context, int round) return PyLong_FromInt64(val); } - n = (mpd_sizeinbase(x, 2) + - layout->bits_per_digit - 1) / layout->bits_per_digit; + const uint8_t bpd = layout->bits_per_digit; + n = (mpd_sizeinbase(x, 2) + bpd - 1) / bpd; PyLongWriter *writer = PyLongWriter_Create(mpd_isnegative(x), n, (void**)&ob_digit); /* mpd_sizeinbase can overestimate size by 1 digit, set it to zero. */ From 6669b89214b1804ad0f3d76f8e9e3b61f6d41f33 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sat, 14 Dec 2024 09:43:16 +0300 Subject: [PATCH 04/26] + adapt dec_from_long() to use PEP 757 --- Modules/_decimal/_decimal.c | 53 ++++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 25 deletions(-) diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index 33c9d95e4a073f..4077c560d32654 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -30,7 +30,6 @@ #endif #include -#include "pycore_long.h" // _PyLong_IsZero() #include "pycore_pystate.h" // _PyThreadState_GET() #include "pycore_typeobject.h" #include "complexobject.h" @@ -2323,38 +2322,42 @@ static PyObject * dec_from_long(decimal_state *state, PyTypeObject *type, PyObject *v, const mpd_context_t *ctx, uint32_t *status) { - PyObject *dec; - PyLongObject *l = (PyLongObject *)v; + PyObject *dec = PyDecType_New(state, type); - dec = PyDecType_New(state, type); if (dec == NULL) { return NULL; } - if (_PyLong_IsZero(l)) { - _dec_settriple(dec, MPD_POS, 0, 0); - return dec; - } - - uint8_t sign = _PyLong_IsNegative(l) ? MPD_NEG : MPD_POS; + PyLongExport export_long; - if (_PyLong_IsCompact(l)) { - _dec_settriple(dec, sign, l->long_value.ob_digit[0], 0); - mpd_qfinalize(MPD(dec), ctx, status); - return dec; + if (PyLong_Export(v, &export_long) == -1) { + Py_DECREF(dec); + return NULL; } - size_t len = _PyLong_DigitCount(l); - -#if PYLONG_BITS_IN_DIGIT == 30 - mpd_qimport_u32(MPD(dec), l->long_value.ob_digit, len, sign, PyLong_BASE, - ctx, status); -#elif PYLONG_BITS_IN_DIGIT == 15 - mpd_qimport_u16(MPD(dec), l->long_value.ob_digit, len, sign, PyLong_BASE, - ctx, status); -#else - #error "PYLONG_BITS_IN_DIGIT should be 15 or 30" -#endif + if (export_long.digits) { + const PyLongLayout *layout = PyLong_GetNativeLayout(); + const uint8_t bpd = layout->bits_per_digit; + const uint8_t sign = export_long.negative ? MPD_NEG : MPD_POS; + const Py_ssize_t len = export_long.ndigits; + if (bpd == 30) { + mpd_qimport_u32(MPD(dec), export_long.digits, len, sign, + 1 << bpd, ctx, status); + } + else if (bpd == 15) { + mpd_qimport_u16(MPD(dec), export_long.digits, len, sign, + 1 << bpd, ctx, status); + } + else { + PyLong_FreeExport(&export_long); + Py_DECREF(dec); + return NULL; + } + PyLong_FreeExport(&export_long); + } + else { + mpd_qset_i64(MPD(dec), export_long.value, ctx, status); + } return dec; } From 6e46bc1de119a5759c68401a05dd167ba5300cc7 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Mon, 16 Dec 2024 10:56:54 +0300 Subject: [PATCH 05/26] Don't use PyLong_GetNativeLayout() --- Modules/_decimal/_decimal.c | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index 4077c560d32654..a83cc7ce95bf89 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -2335,24 +2335,18 @@ dec_from_long(decimal_state *state, PyTypeObject *type, PyObject *v, return NULL; } if (export_long.digits) { - const PyLongLayout *layout = PyLong_GetNativeLayout(); - const uint8_t bpd = layout->bits_per_digit; const uint8_t sign = export_long.negative ? MPD_NEG : MPD_POS; const Py_ssize_t len = export_long.ndigits; - if (bpd == 30) { - mpd_qimport_u32(MPD(dec), export_long.digits, len, sign, - 1 << bpd, ctx, status); - } - else if (bpd == 15) { - mpd_qimport_u16(MPD(dec), export_long.digits, len, sign, - 1 << bpd, ctx, status); - } - else { - PyLong_FreeExport(&export_long); - Py_DECREF(dec); - return NULL; - } +#if PYLONG_BITS_IN_DIGIT == 30 + mpd_qimport_u32(MPD(dec), export_long.digits, len, sign, + PyLong_BASE, ctx, status); +#elif PYLONG_BITS_IN_DIGIT == 15 + mpd_qimport_u16(MPD(dec), export_long.digits, len, sign, + PyLong_BASE, ctx, status); +#else + #error "PYLONG_BITS_IN_DIGIT should be 15 or 30" +#endif PyLong_FreeExport(&export_long); } else { @@ -3648,7 +3642,6 @@ dec_as_long(PyObject *dec, PyObject *context, int round) mpd_t *x; mpd_context_t workctx; uint32_t status = 0; - const PyLongLayout *layout = PyLong_GetNativeLayout(); if (mpd_isspecial(MPD(dec))) { if (mpd_isnan(MPD(dec))) { @@ -3683,8 +3676,7 @@ dec_as_long(PyObject *dec, PyObject *context, int round) return PyLong_FromInt64(val); } - const uint8_t bpd = layout->bits_per_digit; - n = (mpd_sizeinbase(x, 2) + bpd - 1) / bpd; + n = (mpd_sizeinbase(x, 2) + PyLong_SHIFT - 1)/PyLong_SHIFT; PyLongWriter *writer = PyLongWriter_Create(mpd_isnegative(x), n, (void**)&ob_digit); /* mpd_sizeinbase can overestimate size by 1 digit, set it to zero. */ From 7f0061f249a0633cfc56daa4614ea5a4505993cf Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Mon, 16 Dec 2024 11:53:33 +0300 Subject: [PATCH 06/26] Address review: * cleanup: forgotten PyLongWriter_Discard, pylong variable * clarify news --- .../C_API/2024-12-14-03-40-15.gh-issue-127925.FF7aov.rst | 6 +++--- Modules/_decimal/_decimal.c | 5 +---- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/Misc/NEWS.d/next/C_API/2024-12-14-03-40-15.gh-issue-127925.FF7aov.rst b/Misc/NEWS.d/next/C_API/2024-12-14-03-40-15.gh-issue-127925.FF7aov.rst index 2e91b4d60d1bc2..93eb2e73d314fc 100644 --- a/Misc/NEWS.d/next/C_API/2024-12-14-03-40-15.gh-issue-127925.FF7aov.rst +++ b/Misc/NEWS.d/next/C_API/2024-12-14-03-40-15.gh-issue-127925.FF7aov.rst @@ -1,3 +1,3 @@ -Convert the :mod:`decimal` module to use :pep:`757` API. That offer some -speedup, if integer part of the :class:`~decimal.Decimal` instance is small. -Patch by Sergey B Kirpichev. +Convert the :mod:`decimal` module to use :pep:`757` C API (export-import +integers). That offer some speedup, if integer part of the +:class:`~decimal.Decimal` instance is small. Patch by Sergey B Kirpichev. diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index a83cc7ce95bf89..0c03a01139c3b3 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -3636,7 +3636,6 @@ dec_format(PyObject *dec, PyObject *args) static PyObject * dec_as_long(PyObject *dec, PyObject *context, int round) { - PyObject *pylong; digit *ob_digit; size_t n; mpd_t *x; @@ -3682,7 +3681,6 @@ dec_as_long(PyObject *dec, PyObject *context, int round) /* mpd_sizeinbase can overestimate size by 1 digit, set it to zero. */ ob_digit[n-1] = 0; if (writer == NULL) { - PyLongWriter_Discard(writer); mpd_del(x); return NULL; } @@ -3704,9 +3702,8 @@ dec_as_long(PyObject *dec, PyObject *context, int round) } assert(n > 0); - pylong = PyLongWriter_Finish(writer); mpd_del(x); - return pylong; + return PyLongWriter_Finish(writer); } /* Convert a Decimal to its exact integer ratio representation. */ From bae0234a265387ba195e520bff819a3b15abbdcf Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Mon, 16 Dec 2024 13:22:28 +0300 Subject: [PATCH 07/26] Apply suggestions from code review MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- .../next/C_API/2024-12-14-03-40-15.gh-issue-127925.FF7aov.rst | 2 +- Modules/_decimal/_decimal.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Misc/NEWS.d/next/C_API/2024-12-14-03-40-15.gh-issue-127925.FF7aov.rst b/Misc/NEWS.d/next/C_API/2024-12-14-03-40-15.gh-issue-127925.FF7aov.rst index 93eb2e73d314fc..c54c0f541ebd1e 100644 --- a/Misc/NEWS.d/next/C_API/2024-12-14-03-40-15.gh-issue-127925.FF7aov.rst +++ b/Misc/NEWS.d/next/C_API/2024-12-14-03-40-15.gh-issue-127925.FF7aov.rst @@ -1,3 +1,3 @@ Convert the :mod:`decimal` module to use :pep:`757` C API (export-import -integers). That offer some speedup, if integer part of the +integers). That offer some speedup, if the integer part of the :class:`~decimal.Decimal` instance is small. Patch by Sergey B Kirpichev. diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index 0c03a01139c3b3..f26d953b7e040b 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -2335,7 +2335,7 @@ dec_from_long(decimal_state *state, PyTypeObject *type, PyObject *v, return NULL; } if (export_long.digits) { - const uint8_t sign = export_long.negative ? MPD_NEG : MPD_POS; + const uint8_t sign = export_long.negative ? MPD_NEG : MPD_POS; const Py_ssize_t len = export_long.ndigits; #if PYLONG_BITS_IN_DIGIT == 30 From 4e0460c80dd6021ecd154b5e27655e0ff28f07c3 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Mon, 16 Dec 2024 16:46:53 +0300 Subject: [PATCH 08/26] address review: don't use digit type --- Modules/_decimal/_decimal.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index f26d953b7e040b..5d9e4bc1fb0c1b 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -3636,7 +3636,7 @@ dec_format(PyObject *dec, PyObject *args) static PyObject * dec_as_long(PyObject *dec, PyObject *context, int round) { - digit *ob_digit; + void *digits; size_t n; mpd_t *x; mpd_context_t workctx; @@ -3676,20 +3676,20 @@ dec_as_long(PyObject *dec, PyObject *context, int round) } n = (mpd_sizeinbase(x, 2) + PyLong_SHIFT - 1)/PyLong_SHIFT; - PyLongWriter *writer = PyLongWriter_Create(mpd_isnegative(x), n, - (void**)&ob_digit); - /* mpd_sizeinbase can overestimate size by 1 digit, set it to zero. */ - ob_digit[n-1] = 0; + PyLongWriter *writer = PyLongWriter_Create(mpd_isnegative(x), n, &digits); if (writer == NULL) { mpd_del(x); return NULL; } status = 0; + /* mpd_sizeinbase can overestimate size by 1 digit, set it to zero. */ #if PYLONG_BITS_IN_DIGIT == 30 - n = mpd_qexport_u32(&ob_digit, n, PyLong_BASE, x, &status); + memset(digits + sizeof(uint32_t)*(n - 1), 0, sizeof(uint32_t)); + n = mpd_qexport_u32((uint32_t **)&digits, n, PyLong_BASE, x, &status); #elif PYLONG_BITS_IN_DIGIT == 15 - n = mpd_qexport_u16(&ob_digit, n, PyLong_BASE, x, &status); + memset(digits + sizeof(unit16_t)*(n - 1), 0, sizeof(uint16_t)); + n = mpd_qexport_u16((uint16_t **)&digits, n, PyLong_BASE, x, &status); #else #error "PYLONG_BITS_IN_DIGIT should be 15 or 30" #endif From 87ded2e8c779d41d48e071547a893ddeb3781af6 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Mon, 16 Dec 2024 17:00:49 +0300 Subject: [PATCH 09/26] + forgot (char*) cast --- Modules/_decimal/_decimal.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index 5d9e4bc1fb0c1b..4d18326cdccd83 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -3685,10 +3685,10 @@ dec_as_long(PyObject *dec, PyObject *context, int round) status = 0; /* mpd_sizeinbase can overestimate size by 1 digit, set it to zero. */ #if PYLONG_BITS_IN_DIGIT == 30 - memset(digits + sizeof(uint32_t)*(n - 1), 0, sizeof(uint32_t)); + memset((char *)digits + sizeof(uint32_t)*(n - 1), 0, sizeof(uint32_t)); n = mpd_qexport_u32((uint32_t **)&digits, n, PyLong_BASE, x, &status); #elif PYLONG_BITS_IN_DIGIT == 15 - memset(digits + sizeof(unit16_t)*(n - 1), 0, sizeof(uint16_t)); + memset((char *)digits + sizeof(uint16_t)*(n - 1), 0, sizeof(uint16_t)); n = mpd_qexport_u16((uint16_t **)&digits, n, PyLong_BASE, x, &status); #else #error "PYLONG_BITS_IN_DIGIT should be 15 or 30" From 541441e706bbe4a1f740235ed8baae69f4ae7008 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Mon, 16 Dec 2024 18:24:44 +0300 Subject: [PATCH 10/26] Apply Serhiy suggestion --- Modules/_decimal/_decimal.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index 4d18326cdccd83..eace46b9beb632 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -3685,10 +3685,10 @@ dec_as_long(PyObject *dec, PyObject *context, int round) status = 0; /* mpd_sizeinbase can overestimate size by 1 digit, set it to zero. */ #if PYLONG_BITS_IN_DIGIT == 30 - memset((char *)digits + sizeof(uint32_t)*(n - 1), 0, sizeof(uint32_t)); + ((uint32_t *)digits)[n - 1] = 0; n = mpd_qexport_u32((uint32_t **)&digits, n, PyLong_BASE, x, &status); #elif PYLONG_BITS_IN_DIGIT == 15 - memset((char *)digits + sizeof(uint16_t)*(n - 1), 0, sizeof(uint16_t)); + ((uint16_t *)digits)[n - 1] = 0; n = mpd_qexport_u16((uint16_t **)&digits, n, PyLong_BASE, x, &status); #else #error "PYLONG_BITS_IN_DIGIT should be 15 or 30" From 6ab20f10a969cfd25eb0d316beddf72e5bf3d0a2 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 17 Dec 2024 05:02:51 +0300 Subject: [PATCH 11/26] add asserts --- Modules/_decimal/_decimal.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index eace46b9beb632..8a0938236b5b84 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -3675,8 +3675,8 @@ dec_as_long(PyObject *dec, PyObject *context, int round) return PyLong_FromInt64(val); } - n = (mpd_sizeinbase(x, 2) + PyLong_SHIFT - 1)/PyLong_SHIFT; - PyLongWriter *writer = PyLongWriter_Create(mpd_isnegative(x), n, &digits); + size_t len = mpd_sizeinbase(x, PyLong_BASE); + PyLongWriter *writer = PyLongWriter_Create(mpd_isnegative(x), len, &digits); if (writer == NULL) { mpd_del(x); return NULL; @@ -3685,14 +3685,15 @@ dec_as_long(PyObject *dec, PyObject *context, int round) status = 0; /* mpd_sizeinbase can overestimate size by 1 digit, set it to zero. */ #if PYLONG_BITS_IN_DIGIT == 30 - ((uint32_t *)digits)[n - 1] = 0; - n = mpd_qexport_u32((uint32_t **)&digits, n, PyLong_BASE, x, &status); + ((uint32_t *)digits)[len - 1] = 0; + n = mpd_qexport_u32((uint32_t **)&digits, len, PyLong_BASE, x, &status); #elif PYLONG_BITS_IN_DIGIT == 15 - ((uint16_t *)digits)[n - 1] = 0; - n = mpd_qexport_u16((uint16_t **)&digits, n, PyLong_BASE, x, &status); + ((uint16_t *)digits)[len - 1] = 0; + n = mpd_qexport_u16((uint16_t **)&digits, len, PyLong_BASE, x, &status); #else #error "PYLONG_BITS_IN_DIGIT should be 15 or 30" #endif + assert(n == len || n == len - 1); if (n == SIZE_MAX) { PyErr_NoMemory(); From 83471fdbe58a1ffa857359684e57c805e0ccbddf Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 17 Dec 2024 09:19:26 +0300 Subject: [PATCH 12/26] speed up export --- Modules/_decimal/_decimal.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index 8a0938236b5b84..2b246c7d9cd80d 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -2350,7 +2350,16 @@ dec_from_long(decimal_state *state, PyTypeObject *type, PyObject *v, PyLong_FreeExport(&export_long); } else { - mpd_qset_i64(MPD(dec), export_long.value, ctx, status); + const int64_t value = export_long.value; + + if (INT32_MIN <= value && value <= INT32_MAX) { + _dec_settriple(dec, value < 0 ? MPD_NEG : MPD_POS, + Py_ABS(value), 0); + mpd_qfinalize(MPD(dec), ctx, status); + } + else { + mpd_qset_i64(MPD(dec), value, ctx, status); + } } return dec; } From cb169f7b5dfac4908f987453e94a4bf2766a5906 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 17 Dec 2024 15:38:29 +0300 Subject: [PATCH 13/26] Apply suggestions from code review Co-authored-by: Victor Stinner --- Modules/_decimal/_decimal.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index 2b246c7d9cd80d..a13cfdd175f10a 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -2352,9 +2352,9 @@ dec_from_long(decimal_state *state, PyTypeObject *type, PyObject *v, else { const int64_t value = export_long.value; - if (INT32_MIN <= value && value <= INT32_MAX) { + if (-UINT32_MAX <= value && value <= UINT32_MAX) { _dec_settriple(dec, value < 0 ? MPD_NEG : MPD_POS, - Py_ABS(value), 0); + (uint32_t)Py_ABS(value), 0); mpd_qfinalize(MPD(dec), ctx, status); } else { From 61e76d2aaab98d4b0397e749e33c2e2c7abf82e4 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Thu, 19 Dec 2024 06:54:51 +0300 Subject: [PATCH 14/26] use PyLong_GetNativeLayout() to query layout --- Modules/_decimal/_decimal.c | 42 +++++++++++++++++++------------------ 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index a13cfdd175f10a..f302cc77f9fff6 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -2335,18 +2335,19 @@ dec_from_long(decimal_state *state, PyTypeObject *type, PyObject *v, return NULL; } if (export_long.digits) { + const PyLongLayout *layout = PyLong_GetNativeLayout(); + const uint32_t base = 1 << layout->bits_per_digit; const uint8_t sign = export_long.negative ? MPD_NEG : MPD_POS; const Py_ssize_t len = export_long.ndigits; -#if PYLONG_BITS_IN_DIGIT == 30 - mpd_qimport_u32(MPD(dec), export_long.digits, len, sign, - PyLong_BASE, ctx, status); -#elif PYLONG_BITS_IN_DIGIT == 15 - mpd_qimport_u16(MPD(dec), export_long.digits, len, sign, - PyLong_BASE, ctx, status); -#else - #error "PYLONG_BITS_IN_DIGIT should be 15 or 30" -#endif + if (base > UINT16_MAX) { + mpd_qimport_u32(MPD(dec), export_long.digits, len, sign, + base, ctx, status); + } + else { + mpd_qimport_u16(MPD(dec), export_long.digits, len, sign, + base, ctx, status); + } PyLong_FreeExport(&export_long); } else { @@ -3684,7 +3685,9 @@ dec_as_long(PyObject *dec, PyObject *context, int round) return PyLong_FromInt64(val); } - size_t len = mpd_sizeinbase(x, PyLong_BASE); + const PyLongLayout *layout = PyLong_GetNativeLayout(); + const uint32_t base = 1 << layout->bits_per_digit; + size_t len = mpd_sizeinbase(x, base); PyLongWriter *writer = PyLongWriter_Create(mpd_isnegative(x), len, &digits); if (writer == NULL) { mpd_del(x); @@ -3692,16 +3695,15 @@ dec_as_long(PyObject *dec, PyObject *context, int round) } status = 0; - /* mpd_sizeinbase can overestimate size by 1 digit, set it to zero. */ -#if PYLONG_BITS_IN_DIGIT == 30 - ((uint32_t *)digits)[len - 1] = 0; - n = mpd_qexport_u32((uint32_t **)&digits, len, PyLong_BASE, x, &status); -#elif PYLONG_BITS_IN_DIGIT == 15 - ((uint16_t *)digits)[len - 1] = 0; - n = mpd_qexport_u16((uint16_t **)&digits, len, PyLong_BASE, x, &status); -#else - #error "PYLONG_BITS_IN_DIGIT should be 15 or 30" -#endif + /* mpd_sizeinbase can overestimate size by 1 digit, set it first to zero. */ + if (base > UINT16_MAX) { + ((uint32_t *)digits)[len - 1] = 0; + n = mpd_qexport_u32((uint32_t **)&digits, len, base, x, &status); + } + else { + ((uint16_t *)digits)[len - 1] = 0; + n = mpd_qexport_u16((uint16_t **)&digits, len, base, x, &status); + } assert(n == len || n == len - 1); if (n == SIZE_MAX) { From 90bafc1a35e5e9a4d5ac18692b8abc38370c348a Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Thu, 19 Dec 2024 12:37:11 +0300 Subject: [PATCH 15/26] + comment --- Modules/_decimal/_decimal.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index f302cc77f9fff6..79973a668433d3 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -3704,6 +3704,8 @@ dec_as_long(PyObject *dec, PyObject *context, int round) ((uint16_t *)digits)[len - 1] = 0; n = mpd_qexport_u16((uint16_t **)&digits, len, base, x, &status); } + /* mpd_qexport_*() functions above used with assumption, that no + resizing occur, i.e. len was obtained by a call to mpd_sizeinbase. */ assert(n == len || n == len - 1); if (n == SIZE_MAX) { From c117956c8f0893539d683c6cbd87701d5195566a Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Thu, 19 Dec 2024 14:51:01 +0300 Subject: [PATCH 16/26] Apply suggestions from code review Co-authored-by: Victor Stinner --- Modules/_decimal/_decimal.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index 79973a668433d3..891e001586a32b 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -2336,7 +2336,7 @@ dec_from_long(decimal_state *state, PyTypeObject *type, PyObject *v, } if (export_long.digits) { const PyLongLayout *layout = PyLong_GetNativeLayout(); - const uint32_t base = 1 << layout->bits_per_digit; + const uint32_t base = (uint32_t)1 << layout->bits_per_digit; const uint8_t sign = export_long.negative ? MPD_NEG : MPD_POS; const Py_ssize_t len = export_long.ndigits; @@ -2353,7 +2353,7 @@ dec_from_long(decimal_state *state, PyTypeObject *type, PyObject *v, else { const int64_t value = export_long.value; - if (-UINT32_MAX <= value && value <= UINT32_MAX) { + if (-(int64_t)UINT32_MAX <= value && value <= (int64_t)UINT32_MAX) { _dec_settriple(dec, value < 0 ? MPD_NEG : MPD_POS, (uint32_t)Py_ABS(value), 0); mpd_qfinalize(MPD(dec), ctx, status); @@ -3686,7 +3686,7 @@ dec_as_long(PyObject *dec, PyObject *context, int round) } const PyLongLayout *layout = PyLong_GetNativeLayout(); - const uint32_t base = 1 << layout->bits_per_digit; + const uint32_t base = (uint32_t)1 << layout->bits_per_digit; size_t len = mpd_sizeinbase(x, base); PyLongWriter *writer = PyLongWriter_Create(mpd_isnegative(x), len, &digits); if (writer == NULL) { From 7b97855a752d76250ad346a4f58c3f63b8fb3f16 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Fri, 20 Dec 2024 05:55:55 +0300 Subject: [PATCH 17/26] address review: * move comment up * move assert down * remove redundant assert & restore nonzero assert --- Modules/_decimal/_decimal.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index 891e001586a32b..31bb7b798cb38d 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -3684,6 +3684,7 @@ dec_as_long(PyObject *dec, PyObject *context, int round) mpd_del(x); return PyLong_FromInt64(val); } + assert(!mpd_iszero(x)); const PyLongLayout *layout = PyLong_GetNativeLayout(); const uint32_t base = (uint32_t)1 << layout->bits_per_digit; @@ -3695,7 +3696,9 @@ dec_as_long(PyObject *dec, PyObject *context, int round) } status = 0; - /* mpd_sizeinbase can overestimate size by 1 digit, set it first to zero. */ + /* mpd_qexport_*() functions used here with assumption, that no resizing + occur, i.e. len was obtained by a call to mpd_sizeinbase. Note that + it can overestimate size by 1 digit, so set it first to zero. */ if (base > UINT16_MAX) { ((uint32_t *)digits)[len - 1] = 0; n = mpd_qexport_u32((uint32_t **)&digits, len, base, x, &status); @@ -3704,9 +3707,6 @@ dec_as_long(PyObject *dec, PyObject *context, int round) ((uint16_t *)digits)[len - 1] = 0; n = mpd_qexport_u16((uint16_t **)&digits, len, base, x, &status); } - /* mpd_qexport_*() functions above used with assumption, that no - resizing occur, i.e. len was obtained by a call to mpd_sizeinbase. */ - assert(n == len || n == len - 1); if (n == SIZE_MAX) { PyErr_NoMemory(); @@ -3715,7 +3715,7 @@ dec_as_long(PyObject *dec, PyObject *context, int round) return NULL; } - assert(n > 0); + assert(n == len || n == len - 1); mpd_del(x); return PyLongWriter_Finish(writer); } From 7e0e9a9826c67d96a17f4977f8209d15a56fece9 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sun, 22 Dec 2024 17:10:40 +0300 Subject: [PATCH 18/26] Update Misc/NEWS.d/next/C_API/2024-12-14-03-40-15.gh-issue-127925.FF7aov.rst MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- .../next/C_API/2024-12-14-03-40-15.gh-issue-127925.FF7aov.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/C_API/2024-12-14-03-40-15.gh-issue-127925.FF7aov.rst b/Misc/NEWS.d/next/C_API/2024-12-14-03-40-15.gh-issue-127925.FF7aov.rst index c54c0f541ebd1e..6cf5fd2872cd43 100644 --- a/Misc/NEWS.d/next/C_API/2024-12-14-03-40-15.gh-issue-127925.FF7aov.rst +++ b/Misc/NEWS.d/next/C_API/2024-12-14-03-40-15.gh-issue-127925.FF7aov.rst @@ -1,3 +1,3 @@ Convert the :mod:`decimal` module to use :pep:`757` C API (export-import -integers). That offer some speedup, if the integer part of the +integers), offering some speed-up if the integer part of the :class:`~decimal.Decimal` instance is small. Patch by Sergey B Kirpichev. From 5a00ee2f1d27ac539129987b3d11fa3e0935a34f Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Thu, 26 Dec 2024 09:41:08 +0300 Subject: [PATCH 19/26] set all digits to 0 --- Modules/_decimal/_decimal.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index 31bb7b798cb38d..6df517698d7980 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -3697,14 +3697,14 @@ dec_as_long(PyObject *dec, PyObject *context, int round) status = 0; /* mpd_qexport_*() functions used here with assumption, that no resizing - occur, i.e. len was obtained by a call to mpd_sizeinbase. Note that - it can overestimate size by 1 digit, so set it first to zero. */ + occur, i.e. len was obtained by a call to mpd_sizeinbase. Set digits + to zero, as size can be overestimated. */ if (base > UINT16_MAX) { - ((uint32_t *)digits)[len - 1] = 0; + memset(digits, 0, len*sizeof(uint32_t)); n = mpd_qexport_u32((uint32_t **)&digits, len, base, x, &status); } else { - ((uint16_t *)digits)[len - 1] = 0; + memset(digits, 0, len*sizeof(uint16_t)); n = mpd_qexport_u16((uint16_t **)&digits, len, base, x, &status); } From f6a4afb08e96087471c4d6da19ae6e601c42078e Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 7 Jan 2025 05:02:11 +0300 Subject: [PATCH 20/26] + cleanup, add asserts --- Modules/_decimal/_decimal.c | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index bd323ba1fca147..dc9e901c8c8c91 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -3642,12 +3642,6 @@ dec_format(PyObject *dec, PyObject *args) static PyObject * dec_as_long(PyObject *dec, PyObject *context, int round) { - void *digits; - size_t n; - mpd_t *x; - mpd_context_t workctx; - uint32_t status = 0; - if (mpd_isspecial(MPD(dec))) { if (mpd_isnan(MPD(dec))) { PyErr_SetString(PyExc_ValueError, @@ -3660,12 +3654,16 @@ dec_as_long(PyObject *dec, PyObject *context, int round) return NULL; } - x = mpd_qnew(); + mpd_t *x = mpd_qnew(); + if (x == NULL) { PyErr_NoMemory(); return NULL; } - workctx = *CTX(context); + + mpd_context_t workctx = *CTX(context); + uint32_t status = 0; + workctx.round = round; mpd_qround_to_int(x, MPD(dec), &workctx, &status); if (dec_addstatus(context, status)) { @@ -3674,8 +3672,8 @@ dec_as_long(PyObject *dec, PyObject *context, int round) } status = 0; - int64_t val = mpd_qget_i64(x, &status); + if (!status) { mpd_del(x); return PyLong_FromInt64(val); @@ -3684,8 +3682,16 @@ dec_as_long(PyObject *dec, PyObject *context, int round) const PyLongLayout *layout = PyLong_GetNativeLayout(); const uint32_t base = (uint32_t)1 << layout->bits_per_digit; - size_t len = mpd_sizeinbase(x, base); + + assert(layout->bits_per_digit <= 32); + assert(layout->digits_order == -1); + assert(layout->digit_endianness == (PY_LITTLE_ENDIAN ? -1 : 1)); + assert(layout->digit_size == 2 || layout->digit_size == 4); + + size_t n, len = mpd_sizeinbase(x, base); + void *digits; PyLongWriter *writer = PyLongWriter_Create(mpd_isnegative(x), len, &digits); + if (writer == NULL) { mpd_del(x); return NULL; @@ -3695,7 +3701,7 @@ dec_as_long(PyObject *dec, PyObject *context, int round) /* mpd_qexport_*() functions used here with assumption, that no resizing occur, i.e. len was obtained by a call to mpd_sizeinbase. Set digits to zero, as size can be overestimated. */ - if (base > UINT16_MAX) { + if (layout->digit_size == 4) { memset(digits, 0, len*sizeof(uint32_t)); n = mpd_qexport_u32((uint32_t **)&digits, len, base, x, &status); } From 0fec6e17bba3c3fdcf405dafb44972aa7b83c248 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 7 Jan 2025 14:15:29 +0300 Subject: [PATCH 21/26] address review --- Modules/_decimal/_decimal.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index dc9e901c8c8c91..79f4f56521d07f 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -2340,7 +2340,7 @@ dec_from_long(decimal_state *state, PyTypeObject *type, PyObject *v, uint8_t sign = export_long.negative ? MPD_NEG : MPD_POS; Py_ssize_t len = export_long.ndigits; - assert(layout->bits_per_digit <= 32); + assert(layout->bits_per_digit < 32); assert(layout->digits_order == -1); assert(layout->digit_endianness == (PY_LITTLE_ENDIAN ? -1 : 1)); assert(layout->digit_size == 2 || layout->digit_size == 4); @@ -3681,9 +3681,9 @@ dec_as_long(PyObject *dec, PyObject *context, int round) assert(!mpd_iszero(x)); const PyLongLayout *layout = PyLong_GetNativeLayout(); - const uint32_t base = (uint32_t)1 << layout->bits_per_digit; + uint32_t base = (uint32_t)1 << layout->bits_per_digit; - assert(layout->bits_per_digit <= 32); + assert(layout->bits_per_digit < 32); assert(layout->digits_order == -1); assert(layout->digit_endianness == (PY_LITTLE_ENDIAN ? -1 : 1)); assert(layout->digit_size == 2 || layout->digit_size == 4); @@ -3702,11 +3702,11 @@ dec_as_long(PyObject *dec, PyObject *context, int round) occur, i.e. len was obtained by a call to mpd_sizeinbase. Set digits to zero, as size can be overestimated. */ if (layout->digit_size == 4) { - memset(digits, 0, len*sizeof(uint32_t)); + memset(digits, 0, 4*len); n = mpd_qexport_u32((uint32_t **)&digits, len, base, x, &status); } else { - memset(digits, 0, len*sizeof(uint16_t)); + memset(digits, 0, 2*len); n = mpd_qexport_u16((uint16_t **)&digits, len, base, x, &status); } @@ -3717,7 +3717,7 @@ dec_as_long(PyObject *dec, PyObject *context, int round) return NULL; } - assert(n == len || n == len - 1); + assert(n <= len); mpd_del(x); return PyLongWriter_Finish(writer); } From 59636f9259d8b70d304ba8a57c011315f23a084b Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Tue, 7 Jan 2025 15:10:20 +0300 Subject: [PATCH 22/26] address review --- Modules/_decimal/_decimal.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index 79f4f56521d07f..e6e608639f4c5f 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -2336,15 +2336,16 @@ dec_from_long(decimal_state *state, PyTypeObject *type, PyObject *v, } if (export_long.digits) { const PyLongLayout *layout = PyLong_GetNativeLayout(); - uint32_t base = (uint32_t)1 << layout->bits_per_digit; - uint8_t sign = export_long.negative ? MPD_NEG : MPD_POS; - Py_ssize_t len = export_long.ndigits; assert(layout->bits_per_digit < 32); assert(layout->digits_order == -1); assert(layout->digit_endianness == (PY_LITTLE_ENDIAN ? -1 : 1)); assert(layout->digit_size == 2 || layout->digit_size == 4); + uint32_t base = (uint32_t)1 << layout->bits_per_digit; + uint8_t sign = export_long.negative ? MPD_NEG : MPD_POS; + Py_ssize_t len = export_long.ndigits; + if (layout->digit_size == 4) { mpd_qimport_u32(MPD(dec), export_long.digits, len, sign, base, ctx, status); @@ -3681,13 +3682,13 @@ dec_as_long(PyObject *dec, PyObject *context, int round) assert(!mpd_iszero(x)); const PyLongLayout *layout = PyLong_GetNativeLayout(); - uint32_t base = (uint32_t)1 << layout->bits_per_digit; assert(layout->bits_per_digit < 32); assert(layout->digits_order == -1); assert(layout->digit_endianness == (PY_LITTLE_ENDIAN ? -1 : 1)); assert(layout->digit_size == 2 || layout->digit_size == 4); + uint32_t base = (uint32_t)1 << layout->bits_per_digit; size_t n, len = mpd_sizeinbase(x, base); void *digits; PyLongWriter *writer = PyLongWriter_Create(mpd_isnegative(x), len, &digits); From b8bf49ff0731b8220ae5061da8ce31eb0f11d538 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Wed, 8 Jan 2025 05:36:06 +0300 Subject: [PATCH 23/26] Update Modules/_decimal/_decimal.c Co-authored-by: Victor Stinner --- Modules/_decimal/_decimal.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index e6e608639f4c5f..0689db5cd09e1c 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -3701,7 +3701,7 @@ dec_as_long(PyObject *dec, PyObject *context, int round) status = 0; /* mpd_qexport_*() functions used here with assumption, that no resizing occur, i.e. len was obtained by a call to mpd_sizeinbase. Set digits - to zero, as size can be overestimated. */ + to zero, as len can be overestimated. */ if (layout->digit_size == 4) { memset(digits, 0, 4*len); n = mpd_qexport_u32((uint32_t **)&digits, len, base, x, &status); From a8189e62b8914acf55732386dea5a770e5e16ed2 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Sun, 12 Jan 2025 04:07:50 +0300 Subject: [PATCH 24/26] Update Modules/_decimal/_decimal.c MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com> --- Modules/_decimal/_decimal.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index 0689db5cd09e1c..c14b1b05bd1007 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -3699,9 +3699,9 @@ dec_as_long(PyObject *dec, PyObject *context, int round) } status = 0; - /* mpd_qexport_*() functions used here with assumption, that no resizing - occur, i.e. len was obtained by a call to mpd_sizeinbase. Set digits - to zero, as len can be overestimated. */ + /* The mpd_qexport_*() functions used here assume that no resizing occurs, + that is, 'len' was obtained via mpd_sizeinbase(). We also fill 'digits' + with zeros first since 'len' can be overestimated. */ if (layout->digit_size == 4) { memset(digits, 0, 4*len); n = mpd_qexport_u32((uint32_t **)&digits, len, base, x, &status); From 336e881229d57de722ca451bf7d05e7e703800c7 Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Fri, 24 Jan 2025 06:58:11 +0300 Subject: [PATCH 25/26] use temporary buffer of digits, tmp_digits --- Modules/_decimal/_decimal.c | 31 ++++++++++++++----------------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index c14b1b05bd1007..d04ebe036c325a 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -3689,37 +3689,34 @@ dec_as_long(PyObject *dec, PyObject *context, int round) assert(layout->digit_size == 2 || layout->digit_size == 4); uint32_t base = (uint32_t)1 << layout->bits_per_digit; - size_t n, len = mpd_sizeinbase(x, base); - void *digits; - PyLongWriter *writer = PyLongWriter_Create(mpd_isnegative(x), len, &digits); - - if (writer == NULL) { - mpd_del(x); - return NULL; - } + void *tmp_digits = NULL; + size_t n; status = 0; - /* The mpd_qexport_*() functions used here assume that no resizing occurs, - that is, 'len' was obtained via mpd_sizeinbase(). We also fill 'digits' - with zeros first since 'len' can be overestimated. */ if (layout->digit_size == 4) { - memset(digits, 0, 4*len); - n = mpd_qexport_u32((uint32_t **)&digits, len, base, x, &status); + n = mpd_qexport_u32((uint32_t **)&tmp_digits, 0, base, x, &status); } else { - memset(digits, 0, 2*len); - n = mpd_qexport_u16((uint16_t **)&digits, len, base, x, &status); + n = mpd_qexport_u16((uint16_t **)&tmp_digits, 0, base, x, &status); } if (n == SIZE_MAX) { PyErr_NoMemory(); - PyLongWriter_Discard(writer); mpd_del(x); + mpd_free(tmp_digits); return NULL; } - assert(n <= len); + void *digits; + PyLongWriter *writer = PyLongWriter_Create(mpd_isnegative(x), n, &digits); + mpd_del(x); + if (writer == NULL) { + mpd_free(tmp_digits); + return NULL; + } + memcpy(digits, tmp_digits, layout->digit_size*n); + mpd_free(tmp_digits); return PyLongWriter_Finish(writer); } From e658f2b86a3da4d26f61054edc5c912f6cb8ad4d Mon Sep 17 00:00:00 2001 From: Sergey B Kirpichev Date: Fri, 24 Jan 2025 13:42:31 +0300 Subject: [PATCH 26/26] address review: comment --- Modules/_decimal/_decimal.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Modules/_decimal/_decimal.c b/Modules/_decimal/_decimal.c index d04ebe036c325a..b9abd8bd2e7a53 100644 --- a/Modules/_decimal/_decimal.c +++ b/Modules/_decimal/_decimal.c @@ -3689,6 +3689,11 @@ dec_as_long(PyObject *dec, PyObject *context, int round) assert(layout->digit_size == 2 || layout->digit_size == 4); uint32_t base = (uint32_t)1 << layout->bits_per_digit; + /* We use a temporary buffer for digits for now, as for nonzero rdata + mpd_qexport_u32/u16() require either space "allocated by one of + libmpdec’s allocation functions" or "rlen MUST be correct" (to avoid + reallocation). This can be further optimized by using rlen from + mpd_sizeinbase(). See gh-127925. */ void *tmp_digits = NULL; size_t n;