Skip to content

Commit 7559f5f

Browse files
authored
GH-101291: Rearrange the size bits in PyLongObject (GH-102464)
* Eliminate all remaining uses of Py_SIZE and Py_SET_SIZE on PyLongObject, adding asserts. * Change layout of size/sign bits in longobject to support future addition of immortal ints and tagged medium ints. * Add functions to hide some internals of long object, and for setting sign and digit count. * Replace uses of IS_MEDIUM_VALUE macro with _PyLong_IsCompact().
1 parent 713df2c commit 7559f5f

25 files changed

+982
-898
lines changed

Diff for: Include/cpython/longintrepr.h

+5-1
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ typedef long stwodigits; /* signed variant of twodigits */
8080
*/
8181

8282
typedef struct _PyLongValue {
83-
Py_ssize_t ob_size; /* Number of items in variable part */
83+
uintptr_t lv_tag; /* Number of digits, sign and flags */
8484
digit ob_digit[1];
8585
} _PyLongValue;
8686

@@ -94,6 +94,10 @@ PyAPI_FUNC(PyLongObject *) _PyLong_New(Py_ssize_t);
9494
/* Return a copy of src. */
9595
PyAPI_FUNC(PyObject *) _PyLong_Copy(PyLongObject *src);
9696

97+
PyAPI_FUNC(PyLongObject *)
98+
_PyLong_FromDigits(int negative, Py_ssize_t digit_count, digit *digits);
99+
100+
97101
#ifdef __cplusplus
98102
}
99103
#endif

Diff for: Include/internal/pycore_long.h

+146-18
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,6 @@ PyObject *_PyLong_Add(PyLongObject *left, PyLongObject *right);
8282
PyObject *_PyLong_Multiply(PyLongObject *left, PyLongObject *right);
8383
PyObject *_PyLong_Subtract(PyLongObject *left, PyLongObject *right);
8484

85-
int _PyLong_AssignValue(PyObject **target, Py_ssize_t value);
86-
8785
/* Used by Python/mystrtoul.c, _PyBytes_FromHex(),
8886
_PyBytes_DecodeEscape(), etc. */
8987
PyAPI_DATA(unsigned char) _PyLong_DigitValue[256];
@@ -110,25 +108,155 @@ PyAPI_FUNC(char*) _PyLong_FormatBytesWriter(
110108
int base,
111109
int alternate);
112110

113-
/* Return 1 if the argument is positive single digit int */
111+
/* Long value tag bits:
112+
* 0-1: Sign bits value = (1-sign), ie. negative=2, positive=0, zero=1.
113+
* 2: Reserved for immortality bit
114+
* 3+ Unsigned digit count
115+
*/
116+
#define SIGN_MASK 3
117+
#define SIGN_ZERO 1
118+
#define SIGN_NEGATIVE 2
119+
#define NON_SIZE_BITS 3
120+
121+
/* All *compact" values are guaranteed to fit into
122+
* a Py_ssize_t with at least one bit to spare.
123+
* In other words, for 64 bit machines, compact
124+
* will be signed 63 (or fewer) bit values
125+
*/
126+
127+
/* Return 1 if the argument is compact int */
128+
static inline int
129+
_PyLong_IsNonNegativeCompact(const PyLongObject* op) {
130+
assert(PyLong_Check(op));
131+
return op->long_value.lv_tag <= (1 << NON_SIZE_BITS);
132+
}
133+
134+
static inline int
135+
_PyLong_IsCompact(const PyLongObject* op) {
136+
assert(PyLong_Check(op));
137+
return op->long_value.lv_tag < (2 << NON_SIZE_BITS);
138+
}
139+
114140
static inline int
115-
_PyLong_IsPositiveSingleDigit(PyObject* sub) {
116-
/* For a positive single digit int, the value of Py_SIZE(sub) is 0 or 1.
117-
118-
We perform a fast check using a single comparison by casting from int
119-
to uint which casts negative numbers to large positive numbers.
120-
For details see Section 14.2 "Bounds Checking" in the Agner Fog
121-
optimization manual found at:
122-
https://www.agner.org/optimize/optimizing_cpp.pdf
123-
124-
The function is not affected by -fwrapv, -fno-wrapv and -ftrapv
125-
compiler options of GCC and clang
126-
*/
127-
assert(PyLong_CheckExact(sub));
128-
Py_ssize_t signed_size = Py_SIZE(sub);
129-
return ((size_t)signed_size) <= 1;
141+
_PyLong_BothAreCompact(const PyLongObject* a, const PyLongObject* b) {
142+
assert(PyLong_Check(a));
143+
assert(PyLong_Check(b));
144+
return (a->long_value.lv_tag | b->long_value.lv_tag) < (2 << NON_SIZE_BITS);
145+
}
146+
147+
/* Returns a *compact* value, iff `_PyLong_IsCompact` is true for `op`.
148+
*
149+
* "Compact" values have at least one bit to spare,
150+
* so that addition and subtraction can be performed on the values
151+
* without risk of overflow.
152+
*/
153+
static inline Py_ssize_t
154+
_PyLong_CompactValue(const PyLongObject *op)
155+
{
156+
assert(PyLong_Check(op));
157+
assert(_PyLong_IsCompact(op));
158+
Py_ssize_t sign = 1 - (op->long_value.lv_tag & SIGN_MASK);
159+
return sign * (Py_ssize_t)op->long_value.ob_digit[0];
160+
}
161+
162+
static inline bool
163+
_PyLong_IsZero(const PyLongObject *op)
164+
{
165+
return (op->long_value.lv_tag & SIGN_MASK) == SIGN_ZERO;
166+
}
167+
168+
static inline bool
169+
_PyLong_IsNegative(const PyLongObject *op)
170+
{
171+
return (op->long_value.lv_tag & SIGN_MASK) == SIGN_NEGATIVE;
172+
}
173+
174+
static inline bool
175+
_PyLong_IsPositive(const PyLongObject *op)
176+
{
177+
return (op->long_value.lv_tag & SIGN_MASK) == 0;
178+
}
179+
180+
static inline Py_ssize_t
181+
_PyLong_DigitCount(const PyLongObject *op)
182+
{
183+
assert(PyLong_Check(op));
184+
return op->long_value.lv_tag >> NON_SIZE_BITS;
130185
}
131186

187+
/* Equivalent to _PyLong_DigitCount(op) * _PyLong_NonCompactSign(op) */
188+
static inline Py_ssize_t
189+
_PyLong_SignedDigitCount(const PyLongObject *op)
190+
{
191+
assert(PyLong_Check(op));
192+
Py_ssize_t sign = 1 - (op->long_value.lv_tag & SIGN_MASK);
193+
return sign * (Py_ssize_t)(op->long_value.lv_tag >> NON_SIZE_BITS);
194+
}
195+
196+
static inline int
197+
_PyLong_CompactSign(const PyLongObject *op)
198+
{
199+
assert(PyLong_Check(op));
200+
assert(_PyLong_IsCompact(op));
201+
return 1 - (op->long_value.lv_tag & SIGN_MASK);
202+
}
203+
204+
static inline int
205+
_PyLong_NonCompactSign(const PyLongObject *op)
206+
{
207+
assert(PyLong_Check(op));
208+
assert(!_PyLong_IsCompact(op));
209+
return 1 - (op->long_value.lv_tag & SIGN_MASK);
210+
}
211+
212+
/* Do a and b have the same sign? */
213+
static inline int
214+
_PyLong_SameSign(const PyLongObject *a, const PyLongObject *b)
215+
{
216+
return (a->long_value.lv_tag & SIGN_MASK) == (b->long_value.lv_tag & SIGN_MASK);
217+
}
218+
219+
#define TAG_FROM_SIGN_AND_SIZE(sign, size) ((1 - (sign)) | ((size) << NON_SIZE_BITS))
220+
221+
static inline void
222+
_PyLong_SetSignAndDigitCount(PyLongObject *op, int sign, Py_ssize_t size)
223+
{
224+
assert(size >= 0);
225+
assert(-1 <= sign && sign <= 1);
226+
assert(sign != 0 || size == 0);
227+
op->long_value.lv_tag = TAG_FROM_SIGN_AND_SIZE(sign, (size_t)size);
228+
}
229+
230+
static inline void
231+
_PyLong_SetDigitCount(PyLongObject *op, Py_ssize_t size)
232+
{
233+
assert(size >= 0);
234+
op->long_value.lv_tag = (((size_t)size) << NON_SIZE_BITS) | (op->long_value.lv_tag & SIGN_MASK);
235+
}
236+
237+
#define NON_SIZE_MASK ~((1 << NON_SIZE_BITS) - 1)
238+
239+
static inline void
240+
_PyLong_FlipSign(PyLongObject *op) {
241+
unsigned int flipped_sign = 2 - (op->long_value.lv_tag & SIGN_MASK);
242+
op->long_value.lv_tag &= NON_SIZE_MASK;
243+
op->long_value.lv_tag |= flipped_sign;
244+
}
245+
246+
#define _PyLong_DIGIT_INIT(val) \
247+
{ \
248+
.ob_base = _PyObject_IMMORTAL_INIT(&PyLong_Type), \
249+
.long_value = { \
250+
.lv_tag = TAG_FROM_SIGN_AND_SIZE( \
251+
(val) == 0 ? 0 : ((val) < 0 ? -1 : 1), \
252+
(val) == 0 ? 0 : 1), \
253+
{ ((val) >= 0 ? (val) : -(val)) }, \
254+
} \
255+
}
256+
257+
#define _PyLong_FALSE_TAG TAG_FROM_SIGN_AND_SIZE(0, 0)
258+
#define _PyLong_TRUE_TAG TAG_FROM_SIGN_AND_SIZE(1, 1)
259+
132260
#ifdef __cplusplus
133261
}
134262
#endif

Diff for: Include/internal/pycore_object.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -137,8 +137,9 @@ static inline void
137137
_PyObject_InitVar(PyVarObject *op, PyTypeObject *typeobj, Py_ssize_t size)
138138
{
139139
assert(op != NULL);
140-
Py_SET_SIZE(op, size);
140+
assert(typeobj != &PyLong_Type);
141141
_PyObject_Init((PyObject *)op, typeobj);
142+
Py_SET_SIZE(op, size);
142143
}
143144

144145

Diff for: Include/internal/pycore_runtime_init.h

+1-9
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ extern "C" {
88
# error "this header requires Py_BUILD_CORE define"
99
#endif
1010

11+
#include "pycore_long.h"
1112
#include "pycore_object.h"
1213
#include "pycore_parser.h"
1314
#include "pycore_pymem_init.h"
@@ -130,15 +131,6 @@ extern PyTypeObject _PyExc_MemoryError;
130131

131132
// global objects
132133

133-
#define _PyLong_DIGIT_INIT(val) \
134-
{ \
135-
.ob_base = _PyObject_IMMORTAL_INIT(&PyLong_Type), \
136-
.long_value = { \
137-
((val) == 0 ? 0 : ((val) > 0 ? 1 : -1)), \
138-
{ ((val) >= 0 ? (val) : -(val)) }, \
139-
} \
140-
}
141-
142134
#define _PyBytes_SIMPLE_INIT(CH, LEN) \
143135
{ \
144136
_PyVarObject_IMMORTAL_INIT(&PyBytes_Type, (LEN)), \

Diff for: Include/object.h

+7-1
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,13 @@ static inline PyTypeObject* Py_TYPE(PyObject *ob) {
138138
# define Py_TYPE(ob) Py_TYPE(_PyObject_CAST(ob))
139139
#endif
140140

141+
PyAPI_DATA(PyTypeObject) PyLong_Type;
142+
PyAPI_DATA(PyTypeObject) PyBool_Type;
143+
141144
// bpo-39573: The Py_SET_SIZE() function must be used to set an object size.
142145
static inline Py_ssize_t Py_SIZE(PyObject *ob) {
146+
assert(ob->ob_type != &PyLong_Type);
147+
assert(ob->ob_type != &PyBool_Type);
143148
PyVarObject *var_ob = _PyVarObject_CAST(ob);
144149
return var_ob->ob_size;
145150
}
@@ -171,8 +176,9 @@ static inline void Py_SET_TYPE(PyObject *ob, PyTypeObject *type) {
171176
# define Py_SET_TYPE(ob, type) Py_SET_TYPE(_PyObject_CAST(ob), type)
172177
#endif
173178

174-
175179
static inline void Py_SET_SIZE(PyVarObject *ob, Py_ssize_t size) {
180+
assert(ob->ob_base.ob_type != &PyLong_Type);
181+
assert(ob->ob_base.ob_type != &PyBool_Type);
176182
ob->ob_size = size;
177183
}
178184
#if !defined(Py_LIMITED_API) || Py_LIMITED_API+0 < 0x030b0000
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Rearrage bits in first field (after header) of PyLongObject.
2+
* Bits 0 and 1: 1 - sign. I.e. 0 for positive numbers, 1 for zero and 2 for negative numbers.
3+
* Bit 2 reserved (probably for the immortal bit)
4+
* Bits 3+ the unsigned size.
5+
6+
This makes a few operations slightly more efficient, and will enable a more
7+
compact and faster 2s-complement representation of most ints in future.

Diff for: Modules/_decimal/_decimal.c

+8-35
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#endif
3131

3232
#include <Python.h>
33+
#include "pycore_long.h" // _PyLong_IsZero()
3334
#include "pycore_pystate.h" // _PyThreadState_GET()
3435
#include "complexobject.h"
3536
#include "mpdecimal.h"
@@ -2146,35 +2147,25 @@ dec_from_long(PyTypeObject *type, PyObject *v,
21462147
{
21472148
PyObject *dec;
21482149
PyLongObject *l = (PyLongObject *)v;
2149-
Py_ssize_t ob_size;
2150-
size_t len;
2151-
uint8_t sign;
21522150

21532151
dec = PyDecType_New(type);
21542152
if (dec == NULL) {
21552153
return NULL;
21562154
}
21572155

2158-
ob_size = Py_SIZE(l);
2159-
if (ob_size == 0) {
2156+
if (_PyLong_IsZero(l)) {
21602157
_dec_settriple(dec, MPD_POS, 0, 0);
21612158
return dec;
21622159
}
21632160

2164-
if (ob_size < 0) {
2165-
len = -ob_size;
2166-
sign = MPD_NEG;
2167-
}
2168-
else {
2169-
len = ob_size;
2170-
sign = MPD_POS;
2171-
}
2161+
uint8_t sign = _PyLong_IsNegative(l) ? MPD_NEG : MPD_POS;
21722162

2173-
if (len == 1) {
2174-
_dec_settriple(dec, sign, *l->long_value.ob_digit, 0);
2163+
if (_PyLong_IsCompact(l)) {
2164+
_dec_settriple(dec, sign, l->long_value.ob_digit[0], 0);
21752165
mpd_qfinalize(MPD(dec), ctx, status);
21762166
return dec;
21772167
}
2168+
size_t len = _PyLong_DigitCount(l);
21782169

21792170
#if PYLONG_BITS_IN_DIGIT == 30
21802171
mpd_qimport_u32(MPD(dec), l->long_value.ob_digit, len, sign, PyLong_BASE,
@@ -3482,7 +3473,6 @@ dec_as_long(PyObject *dec, PyObject *context, int round)
34823473
PyLongObject *pylong;
34833474
digit *ob_digit;
34843475
size_t n;
3485-
Py_ssize_t i;
34863476
mpd_t *x;
34873477
mpd_context_t workctx;
34883478
uint32_t status = 0;
@@ -3536,26 +3526,9 @@ dec_as_long(PyObject *dec, PyObject *context, int round)
35363526
}
35373527

35383528
assert(n > 0);
3539-
pylong = _PyLong_New(n);
3540-
if (pylong == NULL) {
3541-
mpd_free(ob_digit);
3542-
mpd_del(x);
3543-
return NULL;
3544-
}
3545-
3546-
memcpy(pylong->long_value.ob_digit, ob_digit, n * sizeof(digit));
3529+
assert(!mpd_iszero(x));
3530+
pylong = _PyLong_FromDigits(mpd_isnegative(x), n, ob_digit);
35473531
mpd_free(ob_digit);
3548-
3549-
i = n;
3550-
while ((i > 0) && (pylong->long_value.ob_digit[i-1] == 0)) {
3551-
i--;
3552-
}
3553-
3554-
Py_SET_SIZE(pylong, i);
3555-
if (mpd_isnegative(x) && !mpd_iszero(x)) {
3556-
Py_SET_SIZE(pylong, -i);
3557-
}
3558-
35593532
mpd_del(x);
35603533
return (PyObject *) pylong;
35613534
}

Diff for: Modules/_testcapi/mem.c

+1-1
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,7 @@ test_pyobject_new(PyObject *self, PyObject *Py_UNUSED(ignored))
347347
{
348348
PyObject *obj;
349349
PyTypeObject *type = &PyBaseObject_Type;
350-
PyTypeObject *var_type = &PyLong_Type;
350+
PyTypeObject *var_type = &PyBytes_Type;
351351

352352
// PyObject_New()
353353
obj = PyObject_New(PyObject, type);

Diff for: Modules/_tkinter.c

+5-2
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ Copyright (C) 1994 Steen Lumholt.
3232
# include "pycore_fileutils.h" // _Py_stat()
3333
#endif
3434

35+
#include "pycore_long.h"
36+
3537
#ifdef MS_WINDOWS
3638
#include <windows.h>
3739
#endif
@@ -886,7 +888,8 @@ asBignumObj(PyObject *value)
886888
const char *hexchars;
887889
mp_int bigValue;
888890

889-
neg = Py_SIZE(value) < 0;
891+
assert(PyLong_Check(value));
892+
neg = _PyLong_IsNegative((PyLongObject *)value);
890893
hexstr = _PyLong_Format(value, 16);
891894
if (hexstr == NULL)
892895
return NULL;
@@ -1950,7 +1953,7 @@ _tkinter_tkapp_getboolean(TkappObject *self, PyObject *arg)
19501953
int v;
19511954

19521955
if (PyLong_Check(arg)) { /* int or bool */
1953-
return PyBool_FromLong(Py_SIZE(arg) != 0);
1956+
return PyBool_FromLong(!_PyLong_IsZero((PyLongObject *)arg));
19541957
}
19551958

19561959
if (PyTclObject_Check(arg)) {

0 commit comments

Comments
 (0)