Skip to content

Commit

Permalink
pythongh-102471: Add PyLong import and export API
Browse files Browse the repository at this point in the history
Add PyLong_Export() and PyLong_Import() functions and PyLong_LAYOUT
structure.
  • Loading branch information
vstinner committed Jul 8, 2024
1 parent 59be79a commit 51f48fe
Show file tree
Hide file tree
Showing 8 changed files with 391 additions and 1 deletion.
108 changes: 108 additions & 0 deletions Doc/c-api/long.rst
Original file line number Diff line number Diff line change
Expand Up @@ -529,10 +529,118 @@ distinguished from a number. Use :c:func:`PyErr_Occurred` to disambiguate.
Exactly what values are considered compact is an implementation detail
and is subject to change.
.. versionadded:: 3.12
.. c:function:: Py_ssize_t PyUnstable_Long_CompactValue(const PyLongObject* op)
If *op* is compact, as determined by :c:func:`PyUnstable_Long_IsCompact`,
return its value.
Otherwise, the return value is undefined.
.. versionadded:: 3.12
Import/Export API
^^^^^^^^^^^^^^^^^
.. versionadded:: 3.14
.. c:type:: Py_digit
A single unsigned digit.
It is usually used in an *array of digits*, such as the
:c:member:`PyUnstable_LongExport.digits` array.
Its size depend on the :c:macro:`PYLONG_BITS_IN_DIGIT` macro:
see the ``configure`` :option:`--enable-big-digits` option.
See :c:member:`PyUnstable_Long_LAYOUT.bits_per_digit` for the number of bits per
digit and :c:member:`PyUnstable_Long_LAYOUT.digit_size` for the size of a digit (in
bytes).
.. c:struct:: PyUnstable_Long_LAYOUT
Internal layout of a Python :class:`int` object.
See also :attr:`sys.int_info` which exposes similar information to Python.
.. c:member:: uint8_t bits_per_digit;
Bits per digit.
.. c:member:: uint8_t digit_size;
Digit size in bytes.
.. c:member:: int8_t word_endian;
Word endian:
- 1 for most significant byte first (big endian)
- 0 for least significant first (little endian)
.. c:member:: int8_t array_endian;
Array endian:
- 1 for most significant byte first (big endian)
- 0 for least significant first (little endian)
.. c:function:: PyObject* PyUnstable_Long_Import(int negative, size_t ndigits, Py_digit *digits)
Create a Python :class:`int` object from an array of digits.
* Return a Python :class:`int` object on success.
* Set an exception and return ``NULL`` on error.
*negative* is ``1`` if the number is negative, or ``0`` otherwise.
*ndigits* is the number of digits in the *digits* array.
*digits* is an array of unsigned digits.
See :c:struct:`PyUnstable_Long_LAYOUT` for the internal layout of an integer.
.. c:struct:: PyUnstable_LongExport
A Python :class:`int` object exported as an array of digits.
See :c:struct:`PyUnstable_Long_LAYOUT` for the internal layout of an integer.
.. c:member:: PyLongObject *obj
Strong reference to the Python :class:`int` object.
.. c:member:: int negative
1 if the number is negative, 0 otherwise.
.. c:member:: size_t ndigits
Number of digits in :c:member:`digits` array.
.. c:member:: Py_digit *digits
Array of unsigned digits.
.. c:function:: int PyUnstable_Long_Export(PyLongObject *obj, PyUnstable_LongExport *export)
Export a Python :class:`int` object as an array of digits.
* Set *\*export* and return 0 on success.
* Set an exception and return -1 on error.
:c:func:`PyUnstable_Long_ReleaseExport` must be called once done with using
*export*.
.. c:function:: void PyUnstable_Long_ReleaseExport(PyUnstable_LongExport *export)
Release an export created by :c:func:`PyUnstable_Long_Export`.
3 changes: 2 additions & 1 deletion Doc/using/configure.rst
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,8 @@ General Options

Define the ``PYLONG_BITS_IN_DIGIT`` to ``15`` or ``30``.

See :data:`sys.int_info.bits_per_digit <sys.int_info>`.
See :data:`sys.int_info.bits_per_digit <sys.int_info>` and the
:c:type:`Py_digit` type.

.. option:: --with-suffix=SUFFIX

Expand Down
8 changes: 8 additions & 0 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,14 @@ New Features

(Contributed by Victor Stinner in :gh:`119182`.)

* Add a new unstable import and export API for Python :class:`int` objects:

* :c:func:`PyUnstable_Long_Import`;
* :c:func:`PyUnstable_Long_Export`;
* :c:struct:`PyUnstable_Long_LAYOUT`.

(Contributed by Victor Stinner in :gh:`102471`.)

Porting to Python 3.14
----------------------

Expand Down
39 changes: 39 additions & 0 deletions Include/cpython/longintrepr.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ typedef long stwodigits; /* signed variant of twodigits */
#define PyLong_BASE ((digit)1 << PyLong_SHIFT)
#define PyLong_MASK ((digit)(PyLong_BASE - 1))

typedef digit Py_digit;

/* Long integer representation.
Long integers are made up of a number of 30- or 15-bit digits, depending on
Expand Down Expand Up @@ -139,6 +141,43 @@ _PyLong_CompactValue(PyLongObject *op)
#define PyUnstable_Long_CompactValue _PyLong_CompactValue


/* --- Import/Export API -------------------------------------------------- */

typedef struct PyUnstable_LongLayout {
// Bits per digit
uint8_t bits_per_digit;

// Digit size in bytes: sizeof(digit)
uint8_t digit_size;

// Word endian:
// - 1 for most significant byte first (big endian)
// - 0 for least significant first (little endian)
int8_t word_endian;

// Array endian:
// - 1 for most significant byte first (big endian)
// - 0 for least significant first (little endian)
int8_t array_endian;
} PyUnstable_LongLayout;

PyAPI_DATA(const PyUnstable_LongLayout) PyUnstable_Long_LAYOUT;

PyAPI_FUNC(PyObject*) PyUnstable_Long_Import(
int negative,
size_t ndigits,
Py_digit *digits);

typedef struct PyUnstable_LongExport {
PyLongObject *obj;
int negative;
size_t ndigits;
Py_digit *digits;
} PyUnstable_LongExport;

PyAPI_FUNC(int) PyUnstable_Long_Export(PyLongObject *obj, PyUnstable_LongExport *export);
PyAPI_FUNC(void) PyUnstable_Long_ReleaseExport(PyUnstable_LongExport *export);

#ifdef __cplusplus
}
#endif
Expand Down
44 changes: 44 additions & 0 deletions Lib/test/test_capi/test_long.py
Original file line number Diff line number Diff line change
Expand Up @@ -744,6 +744,50 @@ def test_long_getsign(self):

# CRASHES getsign(NULL)

def test_long_layout(self):
# Test PyLong_LAYOUT
int_info = sys.int_info
layout = _testcapi.get_pylong_layout()
expected = {
'array_endian': 0,
'bits_per_digit': int_info.bits_per_digit,
'digit_size': int_info.sizeof_digit,
'word_endian': 1 if sys.byteorder == 'little' else 0,
}
self.assertEqual(layout, expected)

def test_long_export(self):
# Test PyLong_Export()
layout = _testcapi.get_pylong_layout()
shift = 2 ** layout['bits_per_digit']

pylong_export = _testcapi.pylong_export
self.assertEqual(pylong_export(0), (0, [0]))
self.assertEqual(pylong_export(123), (0, [123]))
self.assertEqual(pylong_export(-123), (1, [123]))
self.assertEqual(pylong_export(shift**2 * 3 + shift * 2 + 1),
(0, [1, 2, 3]))

def test_long_import(self):
# Test PyLong_Import()
layout = _testcapi.get_pylong_layout()
shift = 2 ** layout['bits_per_digit']

pylong_import = _testcapi.pylong_import
self.assertEqual(pylong_import(0, [0]), 0)
self.assertEqual(pylong_import(0, [123]), 123)
self.assertEqual(pylong_import(1, [123]), -123)
self.assertEqual(pylong_import(0, [1, 2, 3]),
shift**2 * 3 + shift * 2 + 1)

# round trip: Python int -> export -> Python int
pylong_export = _testcapi.pylong_export
numbers = [*range(0, 10), 12345, 0xdeadbeef, 2**100, 2**100-1]
numbers.extend(-num for num in list(numbers))
for num in numbers:
with self.subTest(num=num):
export = pylong_export(num)
self.assertEqual(pylong_import(*export), num, export)

if __name__ == "__main__":
unittest.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Add a new unstable import and export API for Python :class:`int` objects:

* :c:func:`PyUnstable_Long_Import`;
* :c:func:`PyUnstable_Long_Export`;
* :c:struct:`PyUnstable_Long_LAYOUT`.

Patch by Victor Stinner.
Loading

0 comments on commit 51f48fe

Please sign in to comment.