Skip to content

Commit eb1b1e0

Browse files
authored
[mypyc] Native int primitives (#12973)
Add various C primitives that will be used to support native ints. The primitives aren't used for anything yet. I'll prepare follow-up PRs that use the primitives and include tests. I'm splitting these into a separate PR to make this easier to review. All of these are tested in my local branch, at least to a basic level. Most of these are fairly straightforward, but we need to jump through some hoops to make the semantics of // and % operators compatible with Python semantics when using negative operands. Work on mypyc/mypyc#837.
1 parent 145d8a4 commit eb1b1e0

File tree

6 files changed

+303
-4
lines changed

6 files changed

+303
-4
lines changed

mypyc/lib-rt/CPy.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ static inline size_t CPy_FindAttrOffset(PyTypeObject *trait, CPyVTableItem *vtab
122122

123123
CPyTagged CPyTagged_FromSsize_t(Py_ssize_t value);
124124
CPyTagged CPyTagged_FromVoidPtr(void *ptr);
125+
CPyTagged CPyTagged_FromInt64(int64_t value);
125126
CPyTagged CPyTagged_FromObject(PyObject *object);
126127
CPyTagged CPyTagged_StealFromObject(PyObject *object);
127128
CPyTagged CPyTagged_BorrowFromObject(PyObject *object);
@@ -150,6 +151,13 @@ PyObject *CPyLong_FromStrWithBase(PyObject *o, CPyTagged base);
150151
PyObject *CPyLong_FromStr(PyObject *o);
151152
PyObject *CPyLong_FromFloat(PyObject *o);
152153
PyObject *CPyBool_Str(bool b);
154+
int64_t CPyLong_AsInt64(PyObject *o);
155+
int64_t CPyInt64_Divide(int64_t x, int64_t y);
156+
int64_t CPyInt64_Remainder(int64_t x, int64_t y);
157+
int32_t CPyLong_AsInt32(PyObject *o);
158+
int32_t CPyInt32_Divide(int32_t x, int32_t y);
159+
int32_t CPyInt32_Remainder(int32_t x, int32_t y);
160+
void CPyInt32_Overflow(void);
153161

154162
static inline int CPyTagged_CheckLong(CPyTagged x) {
155163
return x & CPY_INT_TAG;
@@ -193,6 +201,12 @@ static inline bool CPyTagged_TooBig(Py_ssize_t value) {
193201
&& (value >= 0 || value < CPY_TAGGED_MIN);
194202
}
195203

204+
static inline bool CPyTagged_TooBigInt64(int64_t value) {
205+
// Micro-optimized for the common case where it fits.
206+
return (uint64_t)value > CPY_TAGGED_MAX
207+
&& (value >= 0 || value < CPY_TAGGED_MIN);
208+
}
209+
196210
static inline bool CPyTagged_IsAddOverflow(CPyTagged sum, CPyTagged left, CPyTagged right) {
197211
// This check was copied from some of my old code I believe that it works :-)
198212
return (Py_ssize_t)(sum ^ left) < 0 && (Py_ssize_t)(sum ^ right) < 0;
@@ -342,8 +356,11 @@ PyObject *CPyList_GetItemUnsafe(PyObject *list, CPyTagged index);
342356
PyObject *CPyList_GetItemShort(PyObject *list, CPyTagged index);
343357
PyObject *CPyList_GetItemBorrow(PyObject *list, CPyTagged index);
344358
PyObject *CPyList_GetItemShortBorrow(PyObject *list, CPyTagged index);
359+
PyObject *CPyList_GetItemInt64(PyObject *list, int64_t index);
360+
PyObject *CPyList_GetItemInt64Borrow(PyObject *list, int64_t index);
345361
bool CPyList_SetItem(PyObject *list, CPyTagged index, PyObject *value);
346362
bool CPyList_SetItemUnsafe(PyObject *list, CPyTagged index, PyObject *value);
363+
bool CPyList_SetItemInt64(PyObject *list, int64_t index, PyObject *value);
347364
PyObject *CPyList_PopLast(PyObject *obj);
348365
PyObject *CPyList_Pop(PyObject *obj, CPyTagged index);
349366
CPyTagged CPyList_Count(PyObject *obj, PyObject *value);

mypyc/lib-rt/int_ops.c

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,15 @@ CPyTagged CPyTagged_FromVoidPtr(void *ptr) {
3535
}
3636
}
3737

38+
CPyTagged CPyTagged_FromInt64(int64_t value) {
39+
if (unlikely(CPyTagged_TooBigInt64(value))) {
40+
PyObject *object = PyLong_FromLongLong(value);
41+
return ((CPyTagged)object) | CPY_INT_TAG;
42+
} else {
43+
return value << 1;
44+
}
45+
}
46+
3847
CPyTagged CPyTagged_FromObject(PyObject *object) {
3948
int overflow;
4049
// The overflow check knows about CPyTagged's width
@@ -504,3 +513,129 @@ CPyTagged CPyTagged_Lshift(CPyTagged left, CPyTagged right) {
504513
}
505514
return CPyTagged_StealFromObject(result);
506515
}
516+
517+
int64_t CPyLong_AsInt64(PyObject *o) {
518+
if (likely(PyLong_Check(o))) {
519+
PyLongObject *lobj = (PyLongObject *)o;
520+
Py_ssize_t size = Py_SIZE(lobj);
521+
if (likely(size == 1)) {
522+
// Fast path
523+
return lobj->ob_digit[0];
524+
} else if (likely(size == 0)) {
525+
return 0;
526+
}
527+
}
528+
// Slow path
529+
int overflow;
530+
int64_t result = PyLong_AsLongLongAndOverflow(o, &overflow);
531+
if (result == -1) {
532+
if (PyErr_Occurred()) {
533+
return CPY_LL_INT_ERROR;
534+
} else if (overflow) {
535+
PyErr_SetString(PyExc_OverflowError, "int too large to convert to i64");
536+
return CPY_LL_INT_ERROR;
537+
}
538+
}
539+
return result;
540+
}
541+
542+
int64_t CPyInt64_Divide(int64_t x, int64_t y) {
543+
if (y == 0) {
544+
PyErr_SetString(PyExc_ZeroDivisionError, "integer division or modulo by zero");
545+
return CPY_LL_INT_ERROR;
546+
}
547+
if (y == -1 && x == -1LL << 63) {
548+
PyErr_SetString(PyExc_OverflowError, "integer division overflow");
549+
return CPY_LL_INT_ERROR;
550+
}
551+
int64_t d = x / y;
552+
// Adjust for Python semantics
553+
if (((x < 0) != (y < 0)) && d * y != x) {
554+
d--;
555+
}
556+
return d;
557+
}
558+
559+
int64_t CPyInt64_Remainder(int64_t x, int64_t y) {
560+
if (y == 0) {
561+
PyErr_SetString(PyExc_ZeroDivisionError, "integer division or modulo by zero");
562+
return CPY_LL_INT_ERROR;
563+
}
564+
// Edge case: avoid core dump
565+
if (y == -1 && x == -1LL << 63) {
566+
return 0;
567+
}
568+
int64_t d = x % y;
569+
// Adjust for Python semantics
570+
if (((x < 0) != (y < 0)) && d != 0) {
571+
d += y;
572+
}
573+
return d;
574+
}
575+
576+
int32_t CPyLong_AsInt32(PyObject *o) {
577+
if (likely(PyLong_Check(o))) {
578+
PyLongObject *lobj = (PyLongObject *)o;
579+
Py_ssize_t size = lobj->ob_base.ob_size;
580+
if (likely(size == 1)) {
581+
// Fast path
582+
return lobj->ob_digit[0];
583+
} else if (likely(size == 0)) {
584+
return 0;
585+
}
586+
}
587+
// Slow path
588+
int overflow;
589+
long result = PyLong_AsLongAndOverflow(o, &overflow);
590+
if (result > 0x7fffffffLL || result < -0x80000000LL) {
591+
overflow = 1;
592+
result = -1;
593+
}
594+
if (result == -1) {
595+
if (PyErr_Occurred()) {
596+
return CPY_LL_INT_ERROR;
597+
} else if (overflow) {
598+
PyErr_SetString(PyExc_OverflowError, "int too large to convert to i32");
599+
return CPY_LL_INT_ERROR;
600+
}
601+
}
602+
return result;
603+
}
604+
605+
int32_t CPyInt32_Divide(int32_t x, int32_t y) {
606+
if (y == 0) {
607+
PyErr_SetString(PyExc_ZeroDivisionError, "integer division or modulo by zero");
608+
return CPY_LL_INT_ERROR;
609+
}
610+
if (y == -1 && x == -1LL << 31) {
611+
PyErr_SetString(PyExc_OverflowError, "integer division overflow");
612+
return CPY_LL_INT_ERROR;
613+
}
614+
int32_t d = x / y;
615+
// Adjust for Python semantics
616+
if (((x < 0) != (y < 0)) && d * y != x) {
617+
d--;
618+
}
619+
return d;
620+
}
621+
622+
int32_t CPyInt32_Remainder(int32_t x, int32_t y) {
623+
if (y == 0) {
624+
PyErr_SetString(PyExc_ZeroDivisionError, "integer division or modulo by zero");
625+
return CPY_LL_INT_ERROR;
626+
}
627+
// Edge case: avoid core dump
628+
if (y == -1 && x == -1LL << 31) {
629+
return 0;
630+
}
631+
int32_t d = x % y;
632+
// Adjust for Python semantics
633+
if (((x < 0) != (y < 0)) && d != 0) {
634+
d += y;
635+
}
636+
return d;
637+
}
638+
639+
void CPyInt32_Overflow() {
640+
PyErr_SetString(PyExc_OverflowError, "int too large to convert to i32");
641+
}

mypyc/lib-rt/list_ops.c

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,44 @@ PyObject *CPyList_GetItemBorrow(PyObject *list, CPyTagged index) {
118118
}
119119
}
120120

121+
PyObject *CPyList_GetItemInt64(PyObject *list, int64_t index) {
122+
size_t size = PyList_GET_SIZE(list);
123+
if (likely((uint64_t)index < size)) {
124+
PyObject *result = PyList_GET_ITEM(list, index);
125+
Py_INCREF(result);
126+
return result;
127+
}
128+
if (index >= 0) {
129+
PyErr_SetString(PyExc_IndexError, "list index out of range");
130+
return NULL;
131+
}
132+
index += size;
133+
if (index < 0) {
134+
PyErr_SetString(PyExc_IndexError, "list index out of range");
135+
return NULL;
136+
}
137+
PyObject *result = PyList_GET_ITEM(list, index);
138+
Py_INCREF(result);
139+
return result;
140+
}
141+
142+
PyObject *CPyList_GetItemInt64Borrow(PyObject *list, int64_t index) {
143+
size_t size = PyList_GET_SIZE(list);
144+
if (likely((uint64_t)index < size)) {
145+
return PyList_GET_ITEM(list, index);
146+
}
147+
if (index >= 0) {
148+
PyErr_SetString(PyExc_IndexError, "list index out of range");
149+
return NULL;
150+
}
151+
index += size;
152+
if (index < 0) {
153+
PyErr_SetString(PyExc_IndexError, "list index out of range");
154+
return NULL;
155+
}
156+
return PyList_GET_ITEM(list, index);
157+
}
158+
121159
bool CPyList_SetItem(PyObject *list, CPyTagged index, PyObject *value) {
122160
if (CPyTagged_CheckShort(index)) {
123161
Py_ssize_t n = CPyTagged_ShortAsSsize_t(index);
@@ -145,6 +183,26 @@ bool CPyList_SetItem(PyObject *list, CPyTagged index, PyObject *value) {
145183
}
146184
}
147185

186+
bool CPyList_SetItemInt64(PyObject *list, int64_t index, PyObject *value) {
187+
size_t size = PyList_GET_SIZE(list);
188+
if (unlikely((uint64_t)index >= size)) {
189+
if (index > 0) {
190+
PyErr_SetString(PyExc_IndexError, "list assignment index out of range");
191+
return false;
192+
}
193+
index += size;
194+
if (index < 0) {
195+
PyErr_SetString(PyExc_IndexError, "list assignment index out of range");
196+
return false;
197+
}
198+
}
199+
// PyList_SET_ITEM doesn't decref the old element, so we do
200+
Py_DECREF(PyList_GET_ITEM(list, index));
201+
// N.B: Steals reference
202+
PyList_SET_ITEM(list, index, value);
203+
return true;
204+
}
205+
148206
// This function should only be used to fill in brand new lists.
149207
bool CPyList_SetItemUnsafe(PyObject *list, CPyTagged index, PyObject *value) {
150208
if (CPyTagged_CheckShort(index)) {

mypyc/lib-rt/mypyc_util.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ typedef PyObject CPyModule;
5353
// Tag bit used for long integers
5454
#define CPY_INT_TAG 1
5555

56+
// Error value for fixed-width (low-level) integers
57+
#define CPY_LL_INT_ERROR -113
58+
5659
typedef void (*CPyVTableItem)(void);
5760

5861
static inline CPyTagged CPyTagged_ShortFromInt(int x) {

mypyc/primitives/int_ops.py

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@
99
"""
1010

1111
from typing import Dict, NamedTuple
12-
from mypyc.ir.ops import ERR_NEVER, ERR_MAGIC, ComparisonOp
12+
from mypyc.ir.ops import ERR_NEVER, ERR_MAGIC, ERR_MAGIC_OVERLAPPING, ERR_ALWAYS, ComparisonOp
1313
from mypyc.ir.rtypes import (
1414
int_rprimitive, bool_rprimitive, float_rprimitive, object_rprimitive,
15-
str_rprimitive, bit_rprimitive, RType
15+
str_rprimitive, bit_rprimitive, int64_rprimitive, int32_rprimitive, void_rtype, RType,
16+
c_pyssize_t_rprimitive
1617
)
1718
from mypyc.primitives.registry import (
1819
load_address_op, unary_op, CFunctionDescription, function_op, binary_op, custom_op
@@ -165,3 +166,59 @@ def int_unary_op(name: str, c_function_name: str) -> CFunctionDescription:
165166
'>': IntComparisonOpDescription(ComparisonOp.SGT, int_less_than_, False, True),
166167
'>=': IntComparisonOpDescription(ComparisonOp.SGE, int_less_than_, True, False),
167168
}
169+
170+
int64_divide_op = custom_op(
171+
arg_types=[int64_rprimitive, int64_rprimitive],
172+
return_type=int64_rprimitive,
173+
c_function_name='CPyInt64_Divide',
174+
error_kind=ERR_MAGIC_OVERLAPPING)
175+
176+
int64_mod_op = custom_op(
177+
arg_types=[int64_rprimitive, int64_rprimitive],
178+
return_type=int64_rprimitive,
179+
c_function_name='CPyInt64_Remainder',
180+
error_kind=ERR_MAGIC_OVERLAPPING)
181+
182+
int32_divide_op = custom_op(
183+
arg_types=[int32_rprimitive, int32_rprimitive],
184+
return_type=int32_rprimitive,
185+
c_function_name='CPyInt32_Divide',
186+
error_kind=ERR_MAGIC_OVERLAPPING)
187+
188+
int32_mod_op = custom_op(
189+
arg_types=[int32_rprimitive, int32_rprimitive],
190+
return_type=int32_rprimitive,
191+
c_function_name='CPyInt32_Remainder',
192+
error_kind=ERR_MAGIC_OVERLAPPING)
193+
194+
# Convert tagged int (as PyObject *) to i64
195+
int_to_int64_op = custom_op(
196+
arg_types=[object_rprimitive],
197+
return_type=int64_rprimitive,
198+
c_function_name='CPyLong_AsInt64',
199+
error_kind=ERR_MAGIC_OVERLAPPING)
200+
201+
ssize_t_to_int_op = custom_op(
202+
arg_types=[c_pyssize_t_rprimitive],
203+
return_type=int_rprimitive,
204+
c_function_name='CPyTagged_FromSsize_t',
205+
error_kind=ERR_MAGIC)
206+
207+
int64_to_int_op = custom_op(
208+
arg_types=[int64_rprimitive],
209+
return_type=int_rprimitive,
210+
c_function_name='CPyTagged_FromInt64',
211+
error_kind=ERR_MAGIC)
212+
213+
# Convert tagged int (as PyObject *) to i32
214+
int_to_int32_op = custom_op(
215+
arg_types=[object_rprimitive],
216+
return_type=int32_rprimitive,
217+
c_function_name='CPyLong_AsInt32',
218+
error_kind=ERR_MAGIC_OVERLAPPING)
219+
220+
int32_overflow = custom_op(
221+
arg_types=[],
222+
return_type=void_rtype,
223+
c_function_name='CPyInt32_Overflow',
224+
error_kind=ERR_ALWAYS)

0 commit comments

Comments
 (0)