Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
eb67609
JIT: widen int fast path to full int64 range
KRRT7 May 20, 2026
1181bf0
cleanup
KRRT7 May 25, 2026
8624d26
Fix widened JIT int fast paths
KRRT7 May 25, 2026
91854c2
Add widened JIT int boundary tests
KRRT7 May 25, 2026
e0089f7
Merge branch 'main' into jit-wide-int-fastpath
KRRT7 May 25, 2026
160d3cd
fix failing unit tests
KRRT7 May 26, 2026
32d6758
Merge branch 'main' into jit-wide-int-fastpath
KRRT7 May 28, 2026
a67837c
WIP
KRRT7 May 28, 2026
fd45492
Merge branch 'main' into jit-wide-int-fastpath
KRRT7 May 28, 2026
1d667d4
Update Misc/NEWS.d/next/Core_and_Builtins/2026-05-28-15-00-00.gh-issu…
KRRT7 May 28, 2026
41ad8fb
Update pycore_long.h
KRRT7 May 28, 2026
d549674
Restore compact fast path in _PyCompactLong_{Add,Subtract,Multiply}; …
KRRT7 May 28, 2026
094a60a
Split BINARY_OP_ADD/SUBTRACT/MULTIPLY_INT into compact and wide speci…
KRRT7 May 28, 2026
8769659
Update jit_int_benchmark_pyperf.py
KRRT7 May 29, 2026
7775456
Merge branch 'main' into jit-wide-int-fastpath
KRRT7 May 29, 2026
f206055
fix failing tests
KRRT7 May 29, 2026
39ae2f2
Fix wide int guard range and optimizer facts
KRRT7 May 29, 2026
f35d39b
Rename int64 guard helper and drop simple benchmark
KRRT7 May 29, 2026
4d49566
Separate compact int and int64 optimizer facts
KRRT7 May 29, 2026
e6fd6d8
Merge branch 'main' into jit-wide-int-fastpath
KRRT7 May 29, 2026
d4a355c
tier1-merged tier2-split: single BINARY_OP_ADD_INT with optimizer gua…
KRRT7 May 29, 2026
d608803
Port merged design from explore/merged-on-main
KRRT7 May 29, 2026
5e01840
Fix SUBSCR_LIST_INT/STORE_SUBSCR_LIST_INT asserting on wide ints
KRRT7 May 29, 2026
24a0d2a
Update Include/internal/pycore_long.h
KRRT7 May 29, 2026
a4c0ddb
edit based on feedback
KRRT7 May 29, 2026
c7800d9
Merge branch 'main' into jit-wide-int-fastpath
KRRT7 Jun 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
JIT: widen int fast path to full int64 range
Replace the 30-bit compact-only range check (is_medium_int) with
__builtin_add_overflow / sub_overflow / mul_overflow, widening
the JIT arithmetic fast path from ±2^30 to ±2^62.

Relax operand guards from _PyLong_CheckExactAndCompact to
PyLong_CheckExact so non-compact inputs also stay in the JIT trace.

Add inline _PyLong_AsInt64 for fast digit extraction from non-compact
PyLongs (avoids calling the heavy PyLong_AsLongLongAndOverflow).

Results (pyperf, ARM64, rigorous):
  intermediate_overflow  2.70x faster
  double_add             2.73x faster
  accumulate             1.65x faster
  geometric mean         1.52x faster

Includes pyperf-based microbenchmark (Tools/scripts/jit_int_benchmark_pyperf.py)
and a simpler timeit-based version (jit_int_benchmark.py).
  • Loading branch information
KRRT7 committed May 25, 2026
commit eb67609cb9746726e3d5e3fefaee3f65d9a25de8
1 change: 1 addition & 0 deletions Include/internal/pycore_long.h
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ _PyLong_BothAreCompact(const PyLongObject* a, const PyLongObject* b) {
static inline bool
_PyLong_IsZero(const PyLongObject *op)
{
assert(op != NULL);
return (op->long_value.lv_tag & SIGN_MASK) == SIGN_ZERO;
}

Expand Down
133 changes: 101 additions & 32 deletions Objects/longobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -322,31 +322,31 @@ _PyLong_FromSTwoDigits(stwodigits x)
return (PyLongObject*)_PyLong_FromLarge(x);
}

/* Create a new medium int object from a medium int.
* Do not raise. Return NULL if not medium or can't allocate. */
static inline _PyStackRef
medium_from_stwodigits(stwodigits x)
static inline bool
_PyLong_AsInt64(PyLongObject *v, int64_t *result)
{
if (IS_SMALL_INT(x)) {
return PyStackRef_FromPyObjectBorrow(get_small_int((sdigit)x));
assert(PyLong_CheckExact((PyObject *)v));
if (_PyLong_IsCompact(v)) {
*result = _PyLong_CompactValue(v);
return true;
}
assert(x != 0);
if(!is_medium_int(x)) {
return PyStackRef_NULL;
Py_ssize_t ndigits = _PyLong_DigitCount(v);
if (ndigits > 2) {
return false;
}
PyLongObject *v = (PyLongObject *)_Py_FREELIST_POP(PyLongObject, ints);
if (v == NULL) {
v = PyObject_Malloc(sizeof(PyLongObject));
if (v == NULL) {
return PyStackRef_NULL;
}
_PyObject_Init((PyObject*)v, &PyLong_Type);
_PyLong_InitTag(v);
assert(ndigits == 2);
int sign = 1 - (v->long_value.lv_tag & SIGN_MASK);
uint64_t abs_val = (uint64_t)v->long_value.ob_digit[0]
| ((uint64_t)v->long_value.ob_digit[1] << PyLong_SHIFT);
if (abs_val <= (uint64_t)INT64_MAX) {
*result = sign < 0 ? -(int64_t)abs_val : (int64_t)abs_val;
return true;
}
digit abs_x = x < 0 ? (digit)(-x) : (digit)x;
_PyLong_SetSignAndDigitCount(v, x<0?-1:1, 1);
v->long_value.ob_digit[0] = abs_x;
return PyStackRef_FromPyObjectStealMortal((PyObject *)v);
if (sign < 0 && abs_val == (uint64_t)INT64_MAX + 1) {
*result = INT64_MIN;
return true;
}
return false;
}


Expand Down Expand Up @@ -1324,7 +1324,6 @@ PyLong_AsNativeBytes(PyObject* vv, void* buffer, Py_ssize_t n, int flags)
}

if (_PyLong_IsCompact(v)) {
res = 0;
cv.v = _PyLong_CompactValue(v);
/* Most paths result in res = sizeof(compact value). Only the case
* where 0 < n < sizeof(compact value) do we need to check and adjust
Expand Down Expand Up @@ -2601,6 +2600,7 @@ long_from_binary_base(const char *start, const char *end, Py_ssize_t digits, int

/* n <- the number of Python digits needed,
= ceiling((digits * bits_per_char) / PyLong_SHIFT). */
assert(bits_per_char > 0);
if (digits > (PY_SSIZE_T_MAX - (PyLong_SHIFT - 1)) / bits_per_char) {
PyErr_SetString(PyExc_ValueError,
"int string too large to convert");
Expand Down Expand Up @@ -3866,9 +3866,32 @@ long_add(PyLongObject *a, PyLongObject *b)
_PyStackRef
_PyCompactLong_Add(PyLongObject *a, PyLongObject *b)
{
assert(_PyLong_BothAreCompact(a, b));
stwodigits v = medium_value(a) + medium_value(b);
return medium_from_stwodigits(v);
if (_PyLong_BothAreCompact(a, b)) {
Comment thread
KRRT7 marked this conversation as resolved.
stwodigits va = medium_value(a);
stwodigits vb = medium_value(b);
stwodigits v;
if (__builtin_add_overflow(va, vb, &v)) {
return PyStackRef_NULL;
}
PyLongObject *result = _PyLong_FromSTwoDigits(v);
if (result == NULL) {
return PyStackRef_NULL;
}
return PyStackRef_FromPyObjectStealMortal((PyObject *)result);
}
int64_t va, vb;
if (_PyLong_AsInt64(a, &va) && _PyLong_AsInt64(b, &vb)) {
int64_t v;
if (__builtin_add_overflow(va, vb, &v)) {
return PyStackRef_NULL;
}
PyLongObject *result = _PyLong_FromSTwoDigits(v);
if (result == NULL) {
return PyStackRef_NULL;
}
return PyStackRef_FromPyObjectStealMortal((PyObject *)result);
}
return PyStackRef_NULL;
}

static PyObject *
Expand Down Expand Up @@ -3911,9 +3934,32 @@ long_sub(PyLongObject *a, PyLongObject *b)
_PyStackRef
_PyCompactLong_Subtract(PyLongObject *a, PyLongObject *b)
{
assert(_PyLong_BothAreCompact(a, b));
stwodigits v = medium_value(a) - medium_value(b);
return medium_from_stwodigits(v);
if (_PyLong_BothAreCompact(a, b)) {
stwodigits va = medium_value(a);
stwodigits vb = medium_value(b);
stwodigits v;
if (__builtin_sub_overflow(va, vb, &v)) {
return PyStackRef_NULL;
}
PyLongObject *result = _PyLong_FromSTwoDigits(v);
if (result == NULL) {
return PyStackRef_NULL;
}
return PyStackRef_FromPyObjectStealMortal((PyObject *)result);
}
int64_t va, vb;
if (_PyLong_AsInt64(a, &va) && _PyLong_AsInt64(b, &vb)) {
int64_t v;
if (__builtin_sub_overflow(va, vb, &v)) {
return PyStackRef_NULL;
}
PyLongObject *result = _PyLong_FromSTwoDigits(v);
if (result == NULL) {
return PyStackRef_NULL;
}
return PyStackRef_FromPyObjectStealMortal((PyObject *)result);
}
return PyStackRef_NULL;
}

static PyObject *
Expand Down Expand Up @@ -4353,14 +4399,37 @@ long_mul(PyLongObject *a, PyLongObject *b)
return z;
}

/* This function returns NULL if the result is not compact,
/* This function returns NULL if the result does not fit in int64 range,
* or if it fails to allocate, but never raises */
_PyStackRef
_PyCompactLong_Multiply(PyLongObject *a, PyLongObject *b)
{
assert(_PyLong_BothAreCompact(a, b));
stwodigits v = medium_value(a) * medium_value(b);
return medium_from_stwodigits(v);
if (_PyLong_BothAreCompact(a, b)) {
stwodigits va = medium_value(a);
stwodigits vb = medium_value(b);
stwodigits v;
if (__builtin_mul_overflow(va, vb, &v)) {
return PyStackRef_NULL;
}
PyLongObject *result = _PyLong_FromSTwoDigits(v);
if (result == NULL) {
return PyStackRef_NULL;
}
return PyStackRef_FromPyObjectStealMortal((PyObject *)result);
}
int64_t va, vb;
if (_PyLong_AsInt64(a, &va) && _PyLong_AsInt64(b, &vb)) {
int64_t v;
if (__builtin_mul_overflow(va, vb, &v)) {
return PyStackRef_NULL;
}
PyLongObject *result = _PyLong_FromSTwoDigits(v);
if (result == NULL) {
return PyStackRef_NULL;
}
return PyStackRef_FromPyObjectStealMortal((PyObject *)result);
}
return PyStackRef_NULL;
}

static PyObject *
Expand Down
6 changes: 2 additions & 4 deletions Python/bytecodes.c
Original file line number Diff line number Diff line change
Expand Up @@ -634,12 +634,12 @@ dummy_func(

op(_GUARD_NOS_INT, (left, unused -- left, unused)) {
PyObject *left_o = PyStackRef_AsPyObjectBorrow(left);
EXIT_IF(!_PyLong_CheckExactAndCompact(left_o));
EXIT_IF(!PyLong_CheckExact(left_o));
}

op(_GUARD_TOS_INT, (value -- value)) {
PyObject *value_o = PyStackRef_AsPyObjectBorrow(value);
EXIT_IF(!_PyLong_CheckExactAndCompact(value_o));
EXIT_IF(!PyLong_CheckExact(value_o));
}

op(_GUARD_NOS_OVERFLOWED, (left, unused -- left, unused)) {
Expand Down Expand Up @@ -674,7 +674,6 @@ dummy_func(
PyObject *right_o = PyStackRef_AsPyObjectBorrow(right);
assert(PyLong_CheckExact(left_o));
assert(PyLong_CheckExact(right_o));
assert(_PyLong_BothAreCompact((PyLongObject *)left_o, (PyLongObject *)right_o));

STAT_INC(BINARY_OP, hit);
res = _PyCompactLong_Add((PyLongObject *)left_o, (PyLongObject *)right_o);
Expand All @@ -689,7 +688,6 @@ dummy_func(
PyObject *right_o = PyStackRef_AsPyObjectBorrow(right);
assert(PyLong_CheckExact(left_o));
assert(PyLong_CheckExact(right_o));
assert(_PyLong_BothAreCompact((PyLongObject *)left_o, (PyLongObject *)right_o));

STAT_INC(BINARY_OP, hit);
res = _PyCompactLong_Subtract((PyLongObject *)left_o, (PyLongObject *)right_o);
Expand Down
22 changes: 8 additions & 14 deletions Python/executor_cases.c.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading