Skip to content

Commit 9bd8563

Browse files
JukkaLhauntsaninja
andauthored
[mypyc] Fix int operations on Python 3.12 (#15470)
The representation of `int` objects was changed in Python 3.12. Define some helper macros to make it possible to support all Python versions we care about without too much code duplication. Work on mypyc/mypyc#995. Co-authored-by: Shantanu <[email protected]>
1 parent 1fab96b commit 9bd8563

File tree

3 files changed

+126
-14
lines changed

3 files changed

+126
-14
lines changed

mypyc/lib-rt/int_ops.c

+22-11
Original file line numberDiff line numberDiff line change
@@ -308,10 +308,10 @@ PyObject *CPyBool_Str(bool b) {
308308
}
309309

310310
static void CPyLong_NormalizeUnsigned(PyLongObject *v) {
311-
Py_ssize_t i = v->ob_base.ob_size;
312-
while (i > 0 && v->ob_digit[i - 1] == 0)
311+
Py_ssize_t i = CPY_LONG_SIZE_UNSIGNED(v);
312+
while (i > 0 && CPY_LONG_DIGIT(v, i - 1) == 0)
313313
i--;
314-
v->ob_base.ob_size = i;
314+
CPyLong_SetUnsignedSize(v, i);
315315
}
316316

317317
// Bitwise op '&', '|' or '^' using the generic (slow) API
@@ -361,8 +361,8 @@ static digit *GetIntDigits(CPyTagged n, Py_ssize_t *size, digit *buf) {
361361
return buf;
362362
} else {
363363
PyLongObject *obj = (PyLongObject *)CPyTagged_LongAsObject(n);
364-
*size = obj->ob_base.ob_size;
365-
return obj->ob_digit;
364+
*size = CPY_LONG_SIZE_SIGNED(obj);
365+
return &CPY_LONG_DIGIT(obj, 0);
366366
}
367367
}
368368

@@ -399,20 +399,20 @@ static CPyTagged BitwiseLongOp(CPyTagged a, CPyTagged b, char op) {
399399
Py_ssize_t i;
400400
if (op == '&') {
401401
for (i = 0; i < asize; i++) {
402-
r->ob_digit[i] = adigits[i] & bdigits[i];
402+
CPY_LONG_DIGIT(r, i) = adigits[i] & bdigits[i];
403403
}
404404
} else {
405405
if (op == '|') {
406406
for (i = 0; i < asize; i++) {
407-
r->ob_digit[i] = adigits[i] | bdigits[i];
407+
CPY_LONG_DIGIT(r, i) = adigits[i] | bdigits[i];
408408
}
409409
} else {
410410
for (i = 0; i < asize; i++) {
411-
r->ob_digit[i] = adigits[i] ^ bdigits[i];
411+
CPY_LONG_DIGIT(r, i) = adigits[i] ^ bdigits[i];
412412
}
413413
}
414414
for (; i < bsize; i++) {
415-
r->ob_digit[i] = bdigits[i];
415+
CPY_LONG_DIGIT(r, i) = bdigits[i];
416416
}
417417
}
418418
CPyLong_NormalizeUnsigned(r);
@@ -521,7 +521,7 @@ int64_t CPyLong_AsInt64(PyObject *o) {
521521
Py_ssize_t size = Py_SIZE(lobj);
522522
if (likely(size == 1)) {
523523
// Fast path
524-
return lobj->ob_digit[0];
524+
return CPY_LONG_DIGIT(lobj, 0);
525525
} else if (likely(size == 0)) {
526526
return 0;
527527
}
@@ -576,14 +576,25 @@ int64_t CPyInt64_Remainder(int64_t x, int64_t y) {
576576

577577
int32_t CPyLong_AsInt32(PyObject *o) {
578578
if (likely(PyLong_Check(o))) {
579+
#if CPY_3_12_FEATURES
580+
PyLongObject *lobj = (PyLongObject *)o;
581+
size_t tag = CPY_LONG_TAG(lobj);
582+
if (likely(tag == (1 << CPY_NON_SIZE_BITS))) {
583+
// Fast path
584+
return CPY_LONG_DIGIT(lobj, 0);
585+
} else if (likely(tag == CPY_SIGN_ZERO)) {
586+
return 0;
587+
}
588+
#else
579589
PyLongObject *lobj = (PyLongObject *)o;
580590
Py_ssize_t size = lobj->ob_base.ob_size;
581591
if (likely(size == 1)) {
582592
// Fast path
583-
return lobj->ob_digit[0];
593+
return CPY_LONG_DIGIT(lobj, 0);
584594
} else if (likely(size == 0)) {
585595
return 0;
586596
}
597+
#endif
587598
}
588599
// Slow path
589600
int overflow;

mypyc/lib-rt/mypyc_util.h

+40
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,44 @@ static inline CPyTagged CPyTagged_ShortFromSsize_t(Py_ssize_t x) {
7272
// Are we targeting Python 3.12 or newer?
7373
#define CPY_3_12_FEATURES (PY_VERSION_HEX >= 0x030c0000)
7474

75+
#if CPY_3_12_FEATURES
76+
77+
// Same as macros in CPython internal/pycore_long.h, but with a CPY_ prefix
78+
#define CPY_NON_SIZE_BITS 3
79+
#define CPY_SIGN_ZERO 1
80+
#define CPY_SIGN_NEGATIVE 2
81+
#define CPY_SIGN_MASK 3
82+
83+
#define CPY_LONG_DIGIT(o, n) ((o)->long_value.ob_digit[n])
84+
85+
// Only available on Python 3.12 and later
86+
#define CPY_LONG_TAG(o) ((o)->long_value.lv_tag)
87+
#define CPY_LONG_IS_NEGATIVE(o) (((o)->long_value.lv_tag & CPY_SIGN_MASK) == CPY_SIGN_NEGATIVE)
88+
// Only available on Python 3.12 and later
89+
#define CPY_LONG_SIZE(o) ((o)->long_value.lv_tag >> CPY_NON_SIZE_BITS)
90+
// Number of digits; negative for negative ints
91+
#define CPY_LONG_SIZE_SIGNED(o) (CPY_LONG_IS_NEGATIVE(o) ? -CPY_LONG_SIZE(o) : CPY_LONG_SIZE(o))
92+
// Number of digits, assuming int is non-negative
93+
#define CPY_LONG_SIZE_UNSIGNED(o) CPY_LONG_SIZE(o)
94+
95+
static inline void CPyLong_SetUnsignedSize(PyLongObject *o, Py_ssize_t n) {
96+
if (n == 0)
97+
o->long_value.lv_tag = CPY_SIGN_ZERO;
98+
else
99+
o->long_value.lv_tag = n << CPY_NON_SIZE_BITS;
100+
}
101+
102+
#else
103+
104+
#define CPY_LONG_DIGIT(o, n) ((o)->ob_digit[n])
105+
#define CPY_LONG_IS_NEGATIVE(o) (((o)->ob_base.ob_size < 0)
106+
#define CPY_LONG_SIZE_SIGNED(o) ((o)->ob_base.ob_size)
107+
#define CPY_LONG_SIZE_UNSIGNED(o) ((o)->ob_base.ob_size)
108+
109+
static inline void CPyLong_SetUnsignedSize(PyLongObject *o, Py_ssize_t n) {
110+
o->ob_base.ob_size = n;
111+
}
112+
113+
#endif
114+
75115
#endif

mypyc/lib-rt/pythonsupport.h

+64-3
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,65 @@ init_subclass(PyTypeObject *type, PyObject *kwds)
129129
return 0;
130130
}
131131

132+
#if CPY_3_12_FEATURES
133+
134+
static inline Py_ssize_t
135+
CPyLong_AsSsize_tAndOverflow(PyObject *vv, int *overflow)
136+
{
137+
/* This version by Tim Peters */
138+
PyLongObject *v = (PyLongObject *)vv;
139+
size_t x, prev;
140+
Py_ssize_t res;
141+
Py_ssize_t i;
142+
int sign;
143+
144+
*overflow = 0;
145+
146+
res = -1;
147+
i = CPY_LONG_TAG(v);
148+
149+
// TODO: Combine zero and non-zero cases helow?
150+
if (likely(i == (1 << CPY_NON_SIZE_BITS))) {
151+
res = CPY_LONG_DIGIT(v, 0);
152+
} else if (likely(i == CPY_SIGN_ZERO)) {
153+
res = 0;
154+
} else if (i == ((1 << CPY_NON_SIZE_BITS) | CPY_SIGN_NEGATIVE)) {
155+
res = -(sdigit)CPY_LONG_DIGIT(v, 0);
156+
} else {
157+
sign = 1;
158+
x = 0;
159+
if (i & CPY_SIGN_NEGATIVE) {
160+
sign = -1;
161+
}
162+
i >>= CPY_NON_SIZE_BITS;
163+
while (--i >= 0) {
164+
prev = x;
165+
x = (x << PyLong_SHIFT) + CPY_LONG_DIGIT(v, i);
166+
if ((x >> PyLong_SHIFT) != prev) {
167+
*overflow = sign;
168+
goto exit;
169+
}
170+
}
171+
/* Haven't lost any bits, but casting to long requires extra
172+
* care (see comment above).
173+
*/
174+
if (x <= (size_t)CPY_TAGGED_MAX) {
175+
res = (Py_ssize_t)x * sign;
176+
}
177+
else if (sign < 0 && x == CPY_TAGGED_ABS_MIN) {
178+
res = CPY_TAGGED_MIN;
179+
}
180+
else {
181+
*overflow = sign;
182+
/* res is already set to -1 */
183+
}
184+
}
185+
exit:
186+
return res;
187+
}
188+
189+
#else
190+
132191
// Adapted from longobject.c in Python 3.7.0
133192

134193
/* This function adapted from PyLong_AsLongLongAndOverflow, but with
@@ -156,11 +215,11 @@ CPyLong_AsSsize_tAndOverflow(PyObject *vv, int *overflow)
156215
i = Py_SIZE(v);
157216

158217
if (likely(i == 1)) {
159-
res = v->ob_digit[0];
218+
res = CPY_LONG_DIGIT(v, 0);
160219
} else if (likely(i == 0)) {
161220
res = 0;
162221
} else if (i == -1) {
163-
res = -(sdigit)v->ob_digit[0];
222+
res = -(sdigit)CPY_LONG_DIGIT(v, 0);
164223
} else {
165224
sign = 1;
166225
x = 0;
@@ -170,7 +229,7 @@ CPyLong_AsSsize_tAndOverflow(PyObject *vv, int *overflow)
170229
}
171230
while (--i >= 0) {
172231
prev = x;
173-
x = (x << PyLong_SHIFT) + v->ob_digit[i];
232+
x = (x << PyLong_SHIFT) + CPY_LONG_DIGIT(v, i);
174233
if ((x >> PyLong_SHIFT) != prev) {
175234
*overflow = sign;
176235
goto exit;
@@ -194,6 +253,8 @@ CPyLong_AsSsize_tAndOverflow(PyObject *vv, int *overflow)
194253
return res;
195254
}
196255

256+
#endif
257+
197258
// Adapted from listobject.c in Python 3.7.0
198259
static int
199260
list_resize(PyListObject *self, Py_ssize_t newsize)

0 commit comments

Comments
 (0)