8000 Add PyLong Import/Export API (#121) · python/pythoncapi-compat@61709bf · GitHub
[go: up one dir, main page]

Skip to content

Commit 61709bf

Browse files
skirpichevvstinner
andauthored
Add PyLong Import/Export API (#121)
PyPy is not supported, as well as Python 2. In the later case it's possible, but hardly worth code complications: most real-word potential consumers (e.g. Sage, gmpy2 or python-flint) support only Python 3. Co-authored-by: Victor Stinner <vstinner@python.org>
1 parent 900c130 commit 61709bf

File tree

4 files changed

+271
-0
lines changed

4 files changed

+271
-0
lines changed

docs/api.rst

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,42 @@ Latest version of the header file:
2929
Python 3.14
3030
-----------
3131

32+
.. c:struct:: PyLongLayout
33+
34+
See `PyLongLayout documentation <https://docs.python.org/dev/c-api/long.html#c.PyLongLayout>`__.
35+
36+
.. c:function:: const PyLongLayout* PyLong_GetNativeLayout(void)
37+
38+
See `PyLong_GetNativeLayout() documentation <https://docs.python.org/dev/c-api/long.html#c.PyLong_GetNativeLayout>`__.
39+
40+
.. c:struct:: PyLongExport
41+
42+
See `PyLongExport documentation <https://docs.python.org/dev/c-api/long.html#c.PyLongExport>`__.
43+
44+
.. c:function:: int PyLong_Export(PyObject *obj, PyLongExport *export_long)
45+
46+
See `PyLong_Export() documentation <https://docs.python.org/dev/c-api/long.html#c.PyLong_Export>`__.
47+
48+
.. c:function:: void PyLong_FreeExport(PyLongExport *export_long)
49+
50+
See `PyLong_FreeExport() documentation <https://docs.python.org/dev/c-api/long.html#c.PyLong_FreeExport>`__.
51+
52+
.. c:struct:: PyLongWriter
53+
54+
See `PyLongWriter documentation <https://docs.python.org/dev/c-api/long.html#c.PyLongWriter>`__.
55+
56+
.. c:function:: PyLongWriter* PyLongWriter_Create(int negative, Py_ssize_t ndigits, void **digits)
57+
58+
See `PyLongWriter_Create() documentation <https://docs.python.org/dev/c-api/long.html#c.PyLongWriter_Create>`__.
59+
60+
.. c:function:: PyObject* PyLongWriter_Finish(PyLongWriter *writer)
61+
62+
See `PyLongWriter_Finish() documentation <https://docs.python.org/dev/c-api/long.html#c.PyLongWriter_Finish>`__.
63+
64+
.. c:function:: void PyLongWriter_Discard(PyLongWriter *writer)
65+
66+
See `PyLongWriter_Discard() documentation <https://docs.python.org/dev/c-api/long.html#c.PyLongWriter_Discard>`__.
67+
3268
.. c:function:: int PyLong_IsPositive(PyObject *obj)
3369

3470
See `PyLong_IsPositive() documentation <https://docs.python.org/dev/c-api/long.html#c.PyLong_IsPositive>`__.

docs/changelog.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,18 @@
11
Changelog
22
=========
33

4+
* 2024-12-13: Add functions and structs:
5+
6+
* ``PyLongLayout``
7+
* ``PyLong_GetNativeLayout()``
8+
* ``PyLongExport``
9+
* ``PyLong_Export()``
10+
* ``PyLong_FreeExport()``
11+
* ``PyLongWriter``
12+
* ``PyLongWriter_Create()``
13+
* ``PyLongWriter_Finish()``
14+
* ``PyLongWriter_Discard()``
15+
416
* 2024-11-12: Add functions:
517

618
* ``PyLong_IsPositive()``

pythoncapi_compat.h

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1720,6 +1720,185 @@ static inline int PyLong_AsUInt64(PyObject *obj, uint64_t *pvalue)
17201720
#endif
17211721

17221722

1723+
// gh-102471 added import and export API for integers to 3.14.0a2.
1724+
#if PY_VERSION_HEX < 0x030E00A2 && PY_VERSION_HEX >= 0x03000000 && !defined(PYPY_VERSION)
1725+
// Helpers to access PyLongObject internals.
1726+
static inline void
1727+
_PyLong_SetSignAndDigitCount(PyLongObject *op, int sign, Py_ssize_t size)
1728+
{
1729+
#if PY_VERSION_HEX >= 0x030C0000
1730+
op->long_value.lv_tag = (uintptr_t)(1 - sign) | ((uintptr_t)(size) << 3);
1731+
#elif PY_VERSION_HEX >= 0x030900A4
1732+
Py_SET_SIZE(op, sign * size);
1733+
#else
1734+
Py_SIZE(op) = sign * size;
1735+
#endif
1736+
}
1737+
1738+
static inline Py_ssize_t
1739+
_PyLong_DigitCount(const PyLongObject *op)
1740+
{
1741+
#if PY_VERSION_HEX >= 0x030C0000
1742+
return (Py_ssize_t)(op->long_value.lv_tag >> 3);
1743+
#else
1744+
return _PyLong_Sign((PyObject*)op) < 0 ? -Py_SIZE(op) : Py_SIZE(op);
1745+
#endif
1746+
}
1747+
1748+
static inline digit*
1749+
_PyLong_GetDigits(const PyLongObject *op)
1750+
{
1751+
#if PY_VERSION_HEX >= 0x030C0000
1752+
return (digit*)(op->long_value.ob_digit);
1753+
#else
1754+
return (digit*)(op->ob_digit);
1755+
#endif
1756+
}
1757+
1758+
typedef struct PyLongLayout {
1759+
uint8_t bits_per_digit;
1760+
uint8_t digit_size;
1761+
int8_t digits_order;
1762+
int8_t digit_endianness;
1763+
} PyLongLayout;
1764+
1765+
typedef struct PyLongExport {
1766+
int64_t value;
1767+
uint8_t negative;
1768+
Py_ssize_t ndigits;
1769+
const void *digits;
1770+
Py_uintptr_t _reserved;
1771+
} PyLongExport;
1772+
1773+
typedef struct PyLongWriter PyLongWriter;
1774+
1775+
static inline const PyLongLayout*
1776+
PyLong_GetNativeLayout(void)
1777+
{
1778+
static const PyLongLayout PyLong_LAYOUT = {
1779+
PyLong_SHIFT,
1780+
sizeof(digit),
1781+
-1, // least significant first
1782+
PY_LITTLE_ENDIAN ? -1 : 1,
1783+
};
1784+
1785+
return &PyLong_LAYOUT;
1786+
}
1787+
1788+
static inline int
1789+
PyLong_Export(PyObject *obj, PyLongExport *export_long)
1790+
{
1791+
if (!PyLong_Check(obj)) {
1792+
memset(export_long, 0, sizeof(*export_long));
1793+
PyErr_Format(PyExc_TypeError, "expected int, got %s",
1794+
Py_TYPE(obj)->tp_name);
1795+
return -1;
1796+
}
1797+
1798+
// Fast-path: try to convert to a int64_t
1799+
PyLongObject *self = (PyLongObject*)obj;
1800+
int overflow;
1801+
#if SIZEOF_LONG == 8
1802+
long value = PyLong_AsLongAndOverflow(obj, &overflow);
1803+
#else
1804+
// Windows has 32-bit long, so use 64-bit long long instead
1805+
long long value = PyLong_AsLongLongAndOverflow(obj, &overflow);
1806+
#endif
1807+
Py_BUILD_ASSERT(sizeof(value) == sizeof(int64_t));
1808+
// the function cannot fail since obj is a PyLongObject
1809+
assert(!(value == -1 && PyErr_Occurred()));
1810+
1811+
if (!overflow) {
1812+
export_long->value = value;
1813+
export_long->negative = 0;
1814+
export_long->ndigits = 0;
1815+
export_long->digits = 0;
1816+
export_long->_reserved = 0;
1817+
}
1818+
else {
1819+
export_long->value = 0;
1820+
export_long->negative = _PyLong_Sign(obj) < 0;
1821+
export_long->ndigits = _PyLong_DigitCount(self);
1822+
if (export_long->ndigits == 0) {
1823+
export_long->ndigits = 1;
1824+
}
1825+
export_long->digits = _PyLong_GetDigits(self);
1826+
export_long->_reserved = (Py_uintptr_t)Py_NewRef(obj);
1827+
}
1828+
return 0;
1829+
}
1830+
1831+
static inline void
1832+
PyLong_FreeExport(PyLongExport *export_long)
1833+
{
1834+
PyObject *obj = (PyObject*)export_long->_reserved;
1835+
1836+
if (obj) {
1837+
export_long->_reserved = 0;
1838+
Py_DECREF(obj);
1839+
}
1840+
}
1841+
1842+
static inline PyLongWriter*
1843+
PyLongWriter_Create(int negative, Py_ssize_t ndigits, void **digits)
1844+
{
1845+
if (ndigits <= 0) {
1846+
PyErr_SetString(PyExc_ValueError, "ndigits must be positive");
1847+
return NULL;
1848+
}
1849+
assert(digits != NULL);
1850+
1851+
PyLongObject *obj = _PyLong_New(ndigits);
1852+
if (obj == NULL) {
1853+
return NULL;
1854+
}
1855+
_PyLong_SetSignAndDigitCount(obj, negative?-1:1, ndigits);
1856+
1857+
*digits = _PyLong_GetDigits(obj);
1858+
return (PyLongWriter*)obj;
1859+
}
1860+
1861+
static inline void
1862+
PyLongWriter_Discard(PyLongWriter *writer)
1863+
{
1864+
PyLongObject *obj = (PyLongObject *)writer;
1865+
1866+
assert(Py_REFCNT(obj) == 1);
1867+
Py_DECREF(obj);
1868+
}
1869+
1870+
static inline PyObject*
1871+
PyLongWriter_Finish(PyLongWriter *writer)
1872+
{
1873+
PyObject *obj = (PyObject *)writer;
1874+
PyLongObject *self = (PyLongObject*)obj;
1875+
Py_ssize_t j = _PyLong_DigitCount(self);
1876+
Py_ssize_t i = j;
1877+
int sign = _PyLong_Sign(obj);
1878+
1879+
assert(Py_REFCNT(obj) == 1);
1880+
1881+
// Normalize and get singleton if possible
1882+
while (i > 0 && _PyLong_GetDigits(self)[i-1] == 0) {
1883+
--i;
1884+
}
1885+
if (i != j) {
1886+
if (i == 0) {
1887+
sign = 0;
1888+
}
1889+
_PyLong_SetSignAndDigitCount(self, sign, i);
1890+
}
1891+
if (i <= 1) {
1892+
long val = sign * (long)(_PyLong_GetDigits(self)[0]);
1893+
Py_DECREF(obj);
1894+
return PyLong_FromLong(val);
1895+
}
1896+
1897+
return obj;
1898+
}
1899+
#endif
1900+
1901+
17231902
#ifdef __cplusplus
17241903
}
17251904
#endif

tests/test_pythoncapi_compat_cext.c

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1426,6 +1426,50 @@ test_long_api(PyObject *Py_UNUSED(module), PyObject *Py_UNUSED(args))
14261426
assert(PyLong_IsNegative(obj) == 0);
14271427
assert(PyLong_IsZero(obj) == 0);
14281428

1429+
#if defined(PYTHON3) && !defined(PYPY_VERSION)
1430+
// test import/export API
1431+
digit *digits;
1432+
PyLongWriter *writer;
1433+
static PyLongExport long_export;
1434+
1435+
writer = PyLongWriter_Create(1, 1, (void**)&digits);
1436+
PyLongWriter_Discard(writer);
1437+
1438+
writer = PyLongWriter_Create(1, 1, (void**)&digits);
1439+
digits[0] = 123;
1440+
obj = PyLongWriter_Finish(writer);
1441+
1442+
check_int(obj, -123);
1443+
PyLong_Export(obj, &long_export);
1444+
assert(long_export.value == -123);
1445+
assert(long_export.digits == NULL);
1446+
PyLong_FreeExport(&long_export);
1447+
Py_DECREF(obj);
1448+
1449+
writer = PyLongWriter_Create(0, 5, (void**)&digits);
1450+
digits[0] = 1;
1451+
digits[1] = 0;
1452+
digits[2] = 0;
1453+
digits[3] = 0;
1454+
digits[4] = 1;
1455+
obj = PyLongWriter_Finish(writer);
1456+
1457+
PyLong_Export(obj, &long_export);
1458+
assert(long_export.value == 0);
1459+
digits = (digit*)long_export.digits;
1460+
assert(digits[0] == 1);
1461+
assert(digits[1] == 0);
1462+
assert(digits[2] == 0);
1463+
assert(digits[3] == 0);
1464+
assert(digits[4] == 1);
1465+
PyLong_FreeExport(&long_export);
1466+
Py_DECREF(obj);
1467+
1468+
const PyLongLayout *layout = PyLong_GetNativeLayout();
1469+
assert(layout->digits_order == -1);
1470+
assert(layout->digit_size == sizeof(digit));
1471+
#endif // defined(PYTHON3) && !defined(PYPY_VERSION)
1472+
14291473
Py_RETURN_NONE;
14301474
}
14311475

0 commit comments

Comments
 (0)
0