// Int primitive operations (tagged arbitrary-precision integers) // // These are registered in mypyc.primitives.int_ops. #include #include "CPy.h" #ifndef _WIN32 // On 64-bit Linux and macOS, ssize_t and long are both 64 bits, and // PyLong_FromLong is faster than PyLong_FromSsize_t, so use the faster one #define CPyLong_FromSsize_t PyLong_FromLong #else // On 64-bit Windows, ssize_t is 64 bits but long is 32 bits, so we // can't use the above trick #define CPyLong_FromSsize_t PyLong_FromSsize_t #endif CPyTagged CPyTagged_FromSsize_t(Py_ssize_t value) { // We use a Python object if the value shifted left by 1 is too // large for Py_ssize_t if (unlikely(CPyTagged_TooBig(value))) { PyObject *object = PyLong_FromSsize_t(value); return ((CPyTagged)object) | CPY_INT_TAG; } else { return value << 1; } } CPyTagged CPyTagged_FromVoidPtr(void *ptr) { if ((uintptr_t)ptr > PY_SSIZE_T_MAX) { PyObject *object = PyLong_FromVoidPtr(ptr); return ((CPyTagged)object) | CPY_INT_TAG; } else { return CPyTagged_FromSsize_t((Py_ssize_t)ptr); } } CPyTagged CPyTagged_FromInt64(int64_t value) { if (unlikely(CPyTagged_TooBigInt64(value))) { PyObject *object = PyLong_FromLongLong(value); return ((CPyTagged)object) | CPY_INT_TAG; } else { return value << 1; } } CPyTagged CPyTagged_FromObject(PyObject *object) { int overflow; // The overflow check knows about CPyTagged's width Py_ssize_t value = CPyLong_AsSsize_tAndOverflow(object, &overflow); if (unlikely(overflow != 0)) { Py_INCREF(object); return ((CPyTagged)object) | CPY_INT_TAG; } else { return value << 1; } } CPyTagged CPyTagged_StealFromObject(PyObject *object) { int overflow; // The overflow check knows about CPyTagged's width Py_ssize_t value = CPyLong_AsSsize_tAndOverflow(object, &overflow); if (unlikely(overflow != 0)) { return ((CPyTagged)object) | CPY_INT_TAG; } else { Py_DECREF(object); return value << 1; } } CPyTagged CPyTagged_BorrowFromObject(PyObject *object) { int overflow; // The overflow check knows about CPyTagged's width Py_ssize_t value = CPyLong_AsSsize_tAndOverflow(object, &overflow); if (unlikely(overflow != 0)) { return ((CPyTagged)object) | CPY_INT_TAG; } else { return value << 1; } } PyObject *CPyTagged_AsObject(CPyTagged x) { PyObject *value; if (unlikely(CPyTagged_CheckLong(x))) { value = CPyTagged_LongAsObject(x); Py_INCREF(value); } else { value = CPyLong_FromSsize_t(CPyTagged_ShortAsSsize_t(x)); if (value == NULL) { CPyError_OutOfMemory(); } } return value; } PyObject *CPyTagged_StealAsObject(CPyTagged x) { PyObject *value; if (unlikely(CPyTagged_CheckLong(x))) { value = CPyTagged_LongAsObject(x); } else { value = CPyLong_FromSsize_t(CPyTagged_ShortAsSsize_t(x)); if (value == NULL) { CPyError_OutOfMemory(); } } return value; } Py_ssize_t CPyTagged_AsSsize_t(CPyTagged x) { if (likely(CPyTagged_CheckShort(x))) { return CPyTagged_ShortAsSsize_t(x); } else { return PyLong_AsSsize_t(CPyTagged_LongAsObject(x)); } } CPy_NOINLINE void CPyTagged_IncRef(CPyTagged x) { if (unlikely(CPyTagged_CheckLong(x))) { Py_INCREF(CPyTagged_LongAsObject(x)); } } CPy_NOINLINE void CPyTagged_DecRef(CPyTagged x) { if (unlikely(CPyTagged_CheckLong(x))) { Py_DECREF(CPyTagged_LongAsObject(x)); } } CPy_NOINLINE void CPyTagged_XDecRef(CPyTagged x) { if (unlikely(CPyTagged_CheckLong(x))) { Py_XDECREF(CPyTagged_LongAsObject(x)); } } CPyTagged CPyTagged_Negate(CPyTagged num) { if (CPyTagged_CheckShort(num) && num != (CPyTagged) ((Py_ssize_t)1 << (CPY_INT_BITS - 1))) { // The only possibility of an overflow error happening when negating a short is if we // attempt to negate the most negative number. return -num; } PyObject *num_obj = CPyTagged_AsObject(num); PyObject *result = PyNumber_Negative(num_obj); if (result == NULL) { CPyError_OutOfMemory(); } Py_DECREF(num_obj); return CPyTagged_StealFromObject(result); } CPyTagged CPyTagged_Add(CPyTagged left, CPyTagged right) { // TODO: Use clang/gcc extension __builtin_saddll_overflow instead. if (likely(CPyTagged_CheckShort(left) && CPyTagged_CheckShort(right))) { CPyTagged sum = left + right; if (likely(!CPyTagged_IsAddOverflow(sum, left, right))) { return sum; } } PyObject *left_obj = CPyTagged_AsObject(left); PyObject *right_obj = CPyTagged_AsObject(right); PyObject *result = PyNumber_Add(left_obj, right_obj); if (result == NULL) { CPyError_OutOfMemory(); } Py_DECREF(left_obj); Py_DECREF(right_obj); return CPyTagged_StealFromObject(result); } CPyTagged CPyTagged_Subtract(CPyTagged left, CPyTagged right) { // TODO: Use clang/gcc extension __builtin_saddll_overflow instead. if (likely(CPyTagged_CheckShort(left) && CPyTagged_CheckShort(right))) { CPyTagged diff = left - right; if (likely(!CPyTagged_IsSubtractOverflow(diff, left, right))) { return diff; } } PyObject *left_obj = CPyTagged_AsObject(left); PyObject *right_obj = CPyTagged_AsObject(right); PyObject *result = PyNumber_Subtract(left_obj, right_obj); if (result == NULL) { CPyError_OutOfMemory(); } Py_DECREF(left_obj); Py_DECREF(right_obj); return CPyTagged_StealFromObject(result); } CPyTagged CPyTagged_Multiply(CPyTagged left, CPyTagged right) { // TODO: Consider using some clang/gcc extension if (CPyTagged_CheckShort(left) && CPyTagged_CheckShort(right)) { if (!CPyTagged_IsMultiplyOverflow(left, right)) { return left * CPyTagged_ShortAsSsize_t(right); } } PyObject *left_obj = CPyTagged_AsObject(left); PyObject *right_obj = CPyTagged_AsObject(right); PyObject *result = PyNumber_Multiply(left_obj, right_obj); if (result == NULL) { CPyError_OutOfMemory(); } Py_DECREF(left_obj); Py_DECREF(right_obj); return CPyTagged_StealFromObject(result); } CPyTagged CPyTagged_FloorDivide(CPyTagged left, CPyTagged right) { if (CPyTagged_CheckShort(left) && CPyTagged_CheckShort(right) && !CPyTagged_MaybeFloorDivideFault(left, right)) { Py_ssize_t result = CPyTagged_ShortAsSsize_t(left) / CPyTagged_ShortAsSsize_t(right); if (((Py_ssize_t)left < 0) != (((Py_ssize_t)right) < 0)) { if (result * right != left) { // Round down result--; } } return result << 1; } PyObject *left_obj = CPyTagged_AsObject(left); PyObject *right_obj = CPyTagged_AsObject(right); PyObject *result = PyNumber_FloorDivide(left_obj, right_obj); Py_DECREF(left_obj); Py_DECREF(right_obj); // Handle exceptions honestly because it could be ZeroDivisionError if (result == NULL) { return CPY_INT_TAG; } else { return CPyTagged_StealFromObject(result); } } CPyTagged CPyTagged_Remainder(CPyTagged left, CPyTagged right) { if (CPyTagged_CheckShort(left) && CPyTagged_CheckShort(right) && !CPyTagged_MaybeRemainderFault(left, right)) { Py_ssize_t result = (Py_ssize_t)left % (Py_ssize_t)right; if (((Py_ssize_t)right < 0) != ((Py_ssize_t)left < 0) && result != 0) { result += right; } return result; } PyObject *left_obj = CPyTagged_AsObject(left); PyObject *right_obj = CPyTagged_AsObject(right); PyObject *result = PyNumber_Remainder(left_obj, right_obj); Py_DECREF(left_obj); Py_DECREF(right_obj); // Handle exceptions honestly because it could be ZeroDivisionError if (result == NULL) { return CPY_INT_TAG; } else { return CPyTagged_StealFromObject(result); } } bool CPyTagged_IsEq_(CPyTagged left, CPyTagged right) { if (CPyTagged_CheckShort(right)) { return false; } else { PyObject *left_obj = CPyTagged_AsObject(left); PyObject *right_obj = CPyTagged_AsObject(right); int result = PyObject_RichCompareBool(left_obj, right_obj, Py_EQ); Py_DECREF(left_obj); Py_DECREF(right_obj); if (result == -1) { CPyError_OutOfMemory(); } return result; } } bool CPyTagged_IsLt_(CPyTagged left, CPyTagged right) { PyObject *left_obj = CPyTagged_AsObject(left); PyObject *right_obj = CPyTagged_AsObject(right); int result = PyObject_RichCompareBool(left_obj, right_obj, Py_LT); Py_DECREF(left_obj); Py_DECREF(right_obj); if (result == -1) { CPyError_OutOfMemory(); } return result; } PyObject *CPyLong_FromStrWithBase(PyObject *o, CPyTagged base) { Py_ssize_t base_size_t = CPyTagged_AsSsize_t(base); return PyLong_FromUnicodeObject(o, base_size_t); } PyObject *CPyLong_FromStr(PyObject *o) { CPyTagged base = CPyTagged_FromSsize_t(10); return CPyLong_FromStrWithBase(o, base); } PyObject *CPyLong_FromFloat(PyObject *o) { if (PyLong_Check(o)) { CPy_INCREF(o); return o; } else { return PyLong_FromDouble(PyFloat_AS_DOUBLE(o)); } } PyObject *CPyBool_Str(bool b) { return PyObject_Str(b ? Py_True : Py_False); } static void CPyLong_NormalizeUnsigned(PyLongObject *v) { Py_ssize_t i = v->ob_base.ob_size; while (i > 0 && v->ob_digit[i - 1] == 0) i--; v->ob_base.ob_size = i; } // Bitwise op '&', '|' or '^' using the generic (slow) API static CPyTagged GenericBitwiseOp(CPyTagged a, CPyTagged b, char op) { PyObject *aobj = CPyTagged_AsObject(a); PyObject *bobj = CPyTagged_AsObject(b); PyObject *r; if (op == '&') { r = PyNumber_And(aobj, bobj); } else if (op == '|') { r = PyNumber_Or(aobj, bobj); } else { r = PyNumber_Xor(aobj, bobj); } if (unlikely(r == NULL)) { CPyError_OutOfMemory(); } Py_DECREF(aobj); Py_DECREF(bobj); return CPyTagged_StealFromObject(r); } // Return pointer to digits of a PyLong object. If it's a short // integer, place digits in the buffer buf instead to avoid memory // allocation (it's assumed to be big enough). Return the number of // digits in *size. *size is negative if the integer is negative. static digit *GetIntDigits(CPyTagged n, Py_ssize_t *size, digit *buf) { if (CPyTagged_CheckShort(n)) { Py_ssize_t val = CPyTagged_ShortAsSsize_t(n); bool neg = val < 0; int len = 1; if (neg) { val = -val; } buf[0] = val & PyLong_MASK; if (val > PyLong_MASK) { val >>= PyLong_SHIFT; buf[1] = val & PyLong_MASK; if (val > PyLong_MASK) { buf[2] = val >> PyLong_SHIFT; len = 3; } else { len = 2; } } *size = neg ? -len : len; return buf; } else { PyLongObject *obj = (PyLongObject *)CPyTagged_LongAsObject(n); *size = obj->ob_base.ob_size; return obj->ob_digit; } } // Shared implementation of bitwise '&', '|' and '^' (specified by op) for at least // one long operand. This is somewhat optimized for performance. static CPyTagged BitwiseLongOp(CPyTagged a, CPyTagged b, char op) { // Directly access the digits, as there is no fast C API function for this. digit abuf[3]; digit bbuf[3]; Py_ssize_t asize; Py_ssize_t bsize; digit *adigits = GetIntDigits(a, &asize, abuf); digit *bdigits = GetIntDigits(b, &bsize, bbuf); PyLongObject *r; if (unlikely(asize < 0 || bsize < 0)) { // Negative operand. This is slower, but bitwise ops on them are pretty rare. return GenericBitwiseOp(a, b, op); } // Optimized implementation for two non-negative integers. // Swap a and b as needed to ensure a is no longer than b. if (asize > bsize) { digit *tmp = adigits; adigits = bdigits; bdigits = tmp; Py_ssize_t tmp_size = asize; asize = bsize; bsize = tmp_size; } r = _PyLong_New(op == '&' ? asize : bsize); if (unlikely(r == NULL)) { CPyError_OutOfMemory(); } Py_ssize_t i; if (op == '&') { for (i = 0; i < asize; i++) { r->ob_digit[i] = adigits[i] & bdigits[i]; } } else { if (op == '|') { for (i = 0; i < asize; i++) { r->ob_digit[i] = adigits[i] | bdigits[i]; } } else { for (i = 0; i < asize; i++) { r->ob_digit[i] = adigits[i] ^ bdigits[i]; } } for (; i < bsize; i++) { r->ob_digit[i] = bdigits[i]; } } CPyLong_NormalizeUnsigned(r); return CPyTagged_StealFromObject((PyObject *)r); } // Bitwise '&' CPyTagged CPyTagged_And(CPyTagged left, CPyTagged right) { if (likely(CPyTagged_CheckShort(left) && CPyTagged_CheckShort(right))) { return left & right; } return BitwiseLongOp(left, right, '&'); } // Bitwise '|' CPyTagged CPyTagged_Or(CPyTagged left, CPyTagged right) { if (likely(CPyTagged_CheckShort(left) && CPyTagged_CheckShort(right))) { return left | right; } return BitwiseLongOp(left, right, '|'); } // Bitwise '^' CPyTagged CPyTagged_Xor(CPyTagged left, CPyTagged right) { if (likely(CPyTagged_CheckShort(left) && CPyTagged_CheckShort(right))) { return left ^ right; } return BitwiseLongOp(left, right, '^'); } // Bitwise '~' CPyTagged CPyTagged_Invert(CPyTagged num) { if (likely(CPyTagged_CheckShort(num) && num != CPY_TAGGED_ABS_MIN)) { return ~num & ~CPY_INT_TAG; } else { PyObject *obj = CPyTagged_AsObject(num); PyObject *result = PyNumber_Invert(obj); if (unlikely(result == NULL)) { CPyError_OutOfMemory(); } Py_DECREF(obj); return CPyTagged_StealFromObject(result); } } // Bitwise '>>' CPyTagged CPyTagged_Rshift(CPyTagged left, CPyTagged right) { if (likely(CPyTagged_CheckShort(left) && CPyTagged_CheckShort(right) && (Py_ssize_t)right >= 0)) { CPyTagged count = CPyTagged_ShortAsSsize_t(right); if (unlikely(count >= CPY_INT_BITS)) { if ((Py_ssize_t)left >= 0) { return 0; } else { return CPyTagged_ShortFromInt(-1); } } return ((Py_ssize_t)left >> count) & ~CPY_INT_TAG; } else { // Long integer or negative shift -- use generic op PyObject *lobj = CPyTagged_AsObject(left); PyObject *robj = CPyTagged_AsObject(right); PyObject *result = PyNumber_Rshift(lobj, robj); Py_DECREF(lobj); Py_DECREF(robj); if (result == NULL) { // Propagate error (could be negative shift count) return CPY_INT_TAG; } return CPyTagged_StealFromObject(result); } } static inline bool IsShortLshiftOverflow(Py_ssize_t short_int, Py_ssize_t shift) { return ((Py_ssize_t)(short_int << shift) >> shift) != short_int; } // Bitwise '<<' CPyTagged CPyTagged_Lshift(CPyTagged left, CPyTagged right) { if (likely(CPyTagged_CheckShort(left) && CPyTagged_CheckShort(right) && (Py_ssize_t)right >= 0 && right < CPY_INT_BITS * 2)) { CPyTagged shift = CPyTagged_ShortAsSsize_t(right); if (!IsShortLshiftOverflow(left, shift)) // Short integers, no overflow return left << shift; } // Long integer or out of range shift -- use generic op PyObject *lobj = CPyTagged_AsObject(left); PyObject *robj = CPyTagged_AsObject(right); PyObject *result = PyNumber_Lshift(lobj, robj); Py_DECREF(lobj); Py_DECREF(robj); if (result == NULL) { // Propagate error (could be negative shift count) return CPY_INT_TAG; } return CPyTagged_StealFromObject(result); } int64_t CPyLong_AsInt64(PyObject *o) { if (likely(PyLong_Check(o))) { PyLongObject *lobj = (PyLongObject *)o; Py_ssize_t size = Py_SIZE(lobj); if (likely(size == 1)) { // Fast path return lobj->ob_digit[0]; } else if (likely(size == 0)) { return 0; } } // Slow path int overflow; int64_t result = PyLong_AsLongLongAndOverflow(o, &overflow); if (result == -1) { if (PyErr_Occurred()) { return CPY_LL_INT_ERROR; } else if (overflow) { PyErr_SetString(PyExc_OverflowError, "int too large to convert to i64"); return CPY_LL_INT_ERROR; } } return result; } int64_t CPyInt64_Divide(int64_t x, int64_t y) { if (y == 0) { PyErr_SetString(PyExc_ZeroDivisionError, "integer division or modulo by zero"); return CPY_LL_INT_ERROR; } if (y == -1 && x == -1LL << 63) { PyErr_SetString(PyExc_OverflowError, "integer division overflow"); return CPY_LL_INT_ERROR; } int64_t d = x / y; // Adjust for Python semantics if (((x < 0) != (y < 0)) && d * y != x) { d--; } return d; } int64_t CPyInt64_Remainder(int64_t x, int64_t y) { if (y == 0) { PyErr_SetString(PyExc_ZeroDivisionError, "integer division or modulo by zero"); return CPY_LL_INT_ERROR; } // Edge case: avoid core dump if (y == -1 && x == -1LL << 63) { return 0; } int64_t d = x % y; // Adjust for Python semantics if (((x < 0) != (y < 0)) && d != 0) { d += y; } return d; } int32_t CPyLong_AsInt32(PyObject *o) { if (likely(PyLong_Check(o))) { PyLongObject *lobj = (PyLongObject *)o; Py_ssize_t size = lobj->ob_base.ob_size; if (likely(size == 1)) { // Fast path return lobj->ob_digit[0]; } else if (likely(size == 0)) { return 0; } } // Slow path int overflow; long result = PyLong_AsLongAndOverflow(o, &overflow); if (result > 0x7fffffffLL || result < -0x80000000LL) { overflow = 1; result = -1; } if (result == -1) { if (PyErr_Occurred()) { return CPY_LL_INT_ERROR; } else if (overflow) { PyErr_SetString(PyExc_OverflowError, "int too large to convert to i32"); return CPY_LL_INT_ERROR; } } return result; } int32_t CPyInt32_Divide(int32_t x, int32_t y) { if (y == 0) { PyErr_SetString(PyExc_ZeroDivisionError, "integer division or modulo by zero"); return CPY_LL_INT_ERROR; } if (y == -1 && x == -1LL << 31) { PyErr_SetString(PyExc_OverflowError, "integer division overflow"); return CPY_LL_INT_ERROR; } int32_t d = x / y; // Adjust for Python semantics if (((x < 0) != (y < 0)) && d * y != x) { d--; } return d; } int32_t CPyInt32_Remainder(int32_t x, int32_t y) { if (y == 0) { PyErr_SetString(PyExc_ZeroDivisionError, "integer division or modulo by zero"); return CPY_LL_INT_ERROR; } // Edge case: avoid core dump if (y == -1 && x == -1LL << 31) { return 0; } int32_t d = x % y; // Adjust for Python semantics if (((x < 0) != (y < 0)) && d != 0) { d += y; } return d; } void CPyInt32_Overflow() { PyErr_SetString(PyExc_OverflowError, "int too large to convert to i32"); }