gh-102471, PEP 757: Add PyLong import and export API (#121339) · python/cpython@6446408 · GitHub
Skip to content

Commit 6446408

Browse files
vstinnerskirpichevzoobapicnixz
authored
gh-102471, PEP 757: Add PyLong import and export API (#121339)
Co-authored-by: Sergey B Kirpichev <skirpichev@gmail.com> Co-authored-by: Steve Dower <steve.dower@microsoft.com> Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
1 parent d05a4e6 commit 6446408

9 files changed

Lines changed: 576 additions & 0 deletions

File tree

Doc/c-api/long.rst

Lines changed: 174 additions & 0 deletions

Doc/data/refcounts.dat

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1299,6 +1299,13 @@ PyLong_GetSign:int:::
12991299
PyLong_GetSign:PyObject*:v:0:
13001300
PyLong_GetSign:int*:sign::
13011301

1302+
PyLong_Export:int:::
1303+
PyLong_Export:PyObject*:obj:0:
1304+
PyLong_Export:PyLongExport*:export_long::
1305+
1306+
PyLongWriter_Finish:PyObject*::+1:
1307+
PyLongWriter_Finish:PyLongWriter*:writer::
1308+
13021309
PyMapping_Check:int:::
13031310
PyMapping_Check:PyObject*:o:0:
13041311

Doc/whatsnew/3.14.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1018,6 +1018,17 @@ New features
10181018

10191019
(Contributed by Victor Stinner in :gh:`107954`.)
10201020

1021+
* Add a new import and export API for Python :class:`int` objects (:pep:`757`):
1022+
1023+
* :c:func:`PyLong_GetNativeLayout`;
1024+
* :c:func:`PyLong_Export`;
1025+
* :c:func:`PyLong_FreeExport`;
1026+
* :c:func:`PyLongWriter_Create`;
1027+
* :c:func:`PyLongWriter_Finish`;
1028+
* :c:func:`PyLongWriter_Discard`.
1029+
1030+
(Contributed by Victor Stinner in :gh:`102471`.)
1031+
10211032
* Add :c:func:`PyType_GetBaseByToken` and :c:data:`Py_tp_token` slot for easier
10221033
superclass identification, which attempts to resolve the `type checking issue
10231034
<https://peps.python.org/pep-0630/#type-checking>`__ mentioned in :pep:`630`

Include/cpython/longintrepr.h

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,44 @@ _PyLong_CompactValue(const PyLongObject *op)
139139
#define PyUnstable_Long_CompactValue _PyLong_CompactValue
140140

141141

142+
/* --- Import/Export API -------------------------------------------------- */
143+
144+
typedef struct PyLongLayout {
145+
uint8_t bits_per_digit;
146+
uint8_t digit_size;
147+
int8_t digits_order;
148+
int8_t digit_endianness;
149+
} PyLongLayout;
150+
151+
PyAPI_FUNC(const PyLongLayout*) PyLong_GetNativeLayout(void);
152+
153+
typedef struct PyLongExport {
154+
int64_t value;
155+
uint8_t negative;
156+
Py_ssize_t ndigits;
157+
const void *digits;
158+
// Member used internally, must not be used for other purpose.
159+
Py_uintptr_t _reserved;
160+
} PyLongExport;
161+
162+
PyAPI_FUNC(int) PyLong_Export(
163+
PyObject *obj,
164+
PyLongExport *export_long);
165+
PyAPI_FUNC(void) PyLong_FreeExport(
166+
PyLongExport *export_long);
167+
168+
169+
/* --- PyLongWriter API --------------------------------------------------- */
170+
171+
typedef struct PyLongWriter PyLongWriter;
172+
173+
PyAPI_FUNC(PyLongWriter*) PyLongWriter_Create(
174+
int negative,
175+
Py_ssize_t ndigits,
176+
void **digits);
177+
PyAPI_FUNC(PyObject*) PyLongWriter_Finish(PyLongWriter *writer);
178+
PyAPI_FUNC(void) PyLongWriter_Discard(PyLongWriter *writer);
179+
142180
#ifdef __cplusplus
143181
}
144182
#endif

Lib/test/test_capi/test_long.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
NULL = None
1212

13+
1314
class IntSubclass(int):
1415
pass
1516

@@ -714,5 +715,95 @@ def test_long_asuint64(self):
714715
self.check_long_asint(as_uint64, 0, UINT64_MAX,
715716
negative_value_error=ValueError)
716717

718+
def test_long_layout(self):
719+
# Test PyLong_GetNativeLayout()
720+
int_info = sys.int_info
721+
layout = _testcapi.get_pylong_layout()
722+
expected = {
723+
'bits_per_digit': int_info.bits_per_digit,
724+
'digit_size': int_info.sizeof_digit,
725+
'digits_order': -1,
726+
'digit_endianness': -1 if sys.byteorder == 'little' else 1,
727+
}
728+
self.assertEqual(layout, expected)
729+
730+
def test_long_export(self):
731+
# Test PyLong_Export()
732+
layout = _testcapi.get_pylong_layout()
733+
base = 2 ** layout['bits_per_digit']
734+
735+
pylong_export = _testcapi.pylong_export
736+
737+
# value fits into int64_t
738+
self.assertEqual(pylong_export(0), 0)
739+
self.assertEqual(pylong_export(123), 123)
740+
self.assertEqual(pylong_export(-123), -123)
741+
self.assertEqual(pylong_export(IntSubclass(123)), 123)
742+
743+
# use an array, doesn't fit into int64_t
744+
self.assertEqual(pylong_export(base**10 * 2 + 1),
745+
(0, [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2]))
746+
self.assertEqual(pylong_export(-(base**10 * 2 + 1)),
747+
(1, [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2]))
748+
self.assertEqual(pylong_export(IntSubclass(base**10 * 2 + 1)),
749+
(0, [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2]))
750+
751+
self.assertRaises(TypeError, pylong_export, 1.0)
752+
self.assertRaises(TypeError, pylong_export, 0+1j)
753+
self.assertRaises(TypeError, pylong_export, "abc")
754+
755+
def test_longwriter_create(self):
756+
# Test PyLongWriter_Create()
757+
layout = _testcapi.get_pylong_layout()
758+
base = 2 ** layout['bits_per_digit']
759+
760+
pylongwriter_create = _testcapi.pylongwriter_create
761+
self.assertRaises(ValueError, pylongwriter_create, 0, [])
762+
self.assertRaises(ValueError, pylongwriter_create, -123, [])
763+
self.assertEqual(pylongwriter_create(0, [0]), 0)
764+
self.assertEqual(pylongwriter_create(0, [123]), 123)
765+
self.assertEqual(pylongwriter_create(1, [123]), -123)
766+
self.assertEqual(pylongwriter_create(1, [1, 2]),
767+
-(base * 2 + 1))
768+
self.assertEqual(pylongwriter_create(0, [1, 2, 3]),
769+
base**2 * 3 + base * 2 + 1)
770+
max_digit = base - 1
771+
self.assertEqual(pylongwriter_create(0, [max_digit, max_digit, max_digit]),
772+
base**2 * max_digit + base * max_digit + max_digit)
773+
774+
# normalize
775+
self.assertEqual(pylongwriter_create(0, [123, 0, 0]), 123)
776+
777+
# test singletons + normalize
778+
for num in (-2, 0, 1, 5, 42, 100):
779+
self.assertIs(pylongwriter_create(bool(num < 0), [abs(num), 0]),
780+
num)
781+
782+
def to_digits(num):
783+
digits = []
784+
while True:
785+
num, digit = divmod(num, base)
786+
digits.append(digit)
787+
if not num:
788+
break
789+
return digits
790+
791+
# round trip: Python int -> export -> Python int
792+
pylong_export = _testcapi.pylong_export
793+
numbers = [*range(0, 10), 12345, 0xdeadbeef, 2**100, 2**100-1]
794+
numbers.extend(-num for num in list(numbers))
795+
for num in numbers:
796+
with self.subTest(num=num):
797+
data = pylong_export(num)
798+
if isinstance(data, tuple):
799+
negative, digits = data
800+
else:
801+
value = data
802+
negative = int(value < 0)
803+
digits = to_digits(abs(value))
804+
self.assertEqual(pylongwriter_create(negative, digits), num,
805+
(negative, digits))
806+
807+
717808
if __name__ == "__main__":
718809
unittest.main()
Lines changed: 10 additions & 0 deletions

0 commit comments

Comments
 (0)