Skip to main content

Include/internal/pycore_long.h

cpython 3.14 @ ab2d84fe1023/Include/internal/pycore_long.h

The private extension of PyLongObject. Since CPython 3.12 the integer representation was restructured around _PyLongValue, a struct that packs the sign, the digit count, and two flag bits into a single uintptr_t tag word (lv_tag) alongside the digit array. Extension code sees only an opaque PyObject *; this header exposes the tag layout, the compact-integer fast path, and the arithmetic helpers the eval loop calls without going through the number protocol.

The design has two tiers. "Compact" integers are small enough that the value fits in a single signed machine word extracted from lv_tag itself: no digit array is needed and the value is available with a single right-shift. "Non-compact" integers carry a full digit array of uint32_t limbs in base 2^30.

gopy uses math/big.Int for all magnitudes, so the compact-integer fast path and the digit array are not ported at the struct level. Instead, gopy mirrors the predicates and arithmetic fast paths as ordinary Go functions that test whether a big.Int fits a machine word and take the cheap branch when it does.

Map

LinesSymbolRolegopy
1-60lv_tag bit layout, NON_SIZE_BITS, SIGN_* constants, digit typedefDefines how sign, digit count, and flag bits share one uintptr_t; digit is uint32_t; PyLong_SHIFT is 30.objects/int.go
60-130_PyLong_IsCompact, _PyLong_IsNonNegativeCompact, _PyLong_CompactValueInline predicates that test the tag word and extract a compact value with a single arithmetic shift.objects/int.go
130-200_PyLong_FastAdd, _PyLong_FastSub, _PyLong_IsZero, _PyLong_IsPositive, _PyLong_IsNegativeArithmetic fast paths: add/subtract two compact integers without allocating a digit array when the result stays compact.objects/long_arith.go

Reading

lv_tag encoding (lines 1 to 60)

cpython 3.14 @ ab2d84fe1023/Include/internal/pycore_long.h#L1-60

/* Digits are stored in base 2^PyLong_SHIFT. */
#define PyLong_SHIFT 30
#define PyLong_BASE ((digit)1 << PyLong_SHIFT)
#define PyLong_MASK ((digit)(PyLong_BASE - 1))

typedef uint32_t digit;

/* lv_tag layout (uintptr_t):
* bits [0:1] = sign (SIGN_ZERO=1, SIGN_NEGATIVE=2, SIGN_POSITIVE=4)
* bit [2] = NON_SIZE_BITS start (reserved flags)
* bits [NON_SIZE_BITS:...] = digit count (ndigits)
*/
#define NON_SIZE_BITS 3

#define SIGN_ZERO 1
#define SIGN_NEGATIVE 2
#define SIGN_POSITIVE 4

/* Compact: value fits in one signed Py_ssize_t.
lv_tag has ndigits == 1 and no reserved bits set. */
#define _PyLong_IsCompact(op) \
((op)->long_value.lv_tag < (2 << NON_SIZE_BITS))

static inline Py_ssize_t
_PyLong_CompactValue(const PyLongObject *op)
{
Py_ssize_t sign = 1 - (op->long_value.lv_tag & 3);
return sign * (Py_ssize_t)(op->long_value.ob_digit[0]);
}

lv_tag is a uintptr_t that encodes three things: the sign of the integer (via the two-bit field in positions 0-1), a set of reserved flag bits in positions 1 through NON_SIZE_BITS-1, and the digit count (ndigits) starting at bit NON_SIZE_BITS. This packing lets CPython determine sign and digit count with pure bitwise operations, avoiding an indirect struct field read.

The sign encoding is unusual: SIGN_ZERO = 1 (not 0) so that a zero lv_tag does not collide with a valid compact one-digit positive. The actual sign multiplier extracted by _PyLong_CompactValue is 1 - (lv_tag & 3), which maps: tag-bits=00 to +1 (positive), tag-bits=01 to 0 (zero), tag-bits=10 to -1 (negative).

A "compact" integer is one whose lv_tag < (2 << NON_SIZE_BITS), which means at most one digit and no reserved flag bits set. The value is then simply sign * ob_digit[0], extractable without any loop over the digit array. CPython uses this predicate in BINARY_OP_ADD_INT and similar specialized instructions to skip the general PyLong_AsLong call.

PyLong_SHIFT = 30 means each digit holds 30 bits of magnitude. Using 30 rather than 32 leaves two zero bits at the top of each limb, which lets the schoolbook multiplication routines accumulate products into uint64_t without overflow before reducing modulo 2^30.

In gopy, objects/int.go uses math/big.Int for all magnitudes and does not expose lv_tag or the digit array directly. The compact-integer concept maps to big.Int.IsInt64(): when a value fits a 64-bit signed word, the arithmetic paths take the cheap branch.

_PyLong_IsCompact and _PyLong_CompactValue (lines 60 to 130)

cpython 3.14 @ ab2d84fe1023/Include/internal/pycore_long.h#L60-130

static inline bool
_PyLong_IsNonNegativeCompact(const PyLongObject *op) {
assert(PyLong_Check(op));
return op->long_value.lv_tag <= (1 << NON_SIZE_BITS);
}

/* Returns true only when op == 0. */
static inline bool
_PyLong_IsZero(const PyLongObject *op) {
return (op->long_value.lv_tag & SIGN_ZERO) != 0;
}

static inline bool
_PyLong_IsPositive(const PyLongObject *op) {
return (op->long_value.lv_tag & SIGN_POSITIVE) != 0;
}

static inline bool
_PyLong_IsNegative(const PyLongObject *op) {
return (op->long_value.lv_tag & SIGN_NEGATIVE) != 0;
}

These predicates are used pervasively in the eval loop and in the object layer wherever sign-checking or zero-testing must be cheap. Each is a single bitwise operation on lv_tag with no memory indirection beyond reading the struct field itself. CPython's COMPARE_OP_INT specialized instruction, for example, calls _PyLong_IsCompact on both operands and falls back to the slow PyObject_RichCompareBool path only when either is non-compact.

_PyLong_IsNonNegativeCompact accepts both zero and compact positives because lv_tag <= (1 << NON_SIZE_BITS) covers SIGN_ZERO (tag=1) and one-digit positives (tag=4, 8, ...) with no reserved flags set. This specific predicate is used by slice-index helpers that need an upper bound on the integer before calling _PyLong_CompactValue.

In gopy, the three sign predicates map to big.Int.Sign(): Sign() == 0 for zero, Sign() > 0 for positive, Sign() < 0 for negative. There is no separate tag word; math/big maintains the sign internally.

_PyLong_FastAdd and compact arithmetic (lines 130 to 200)

cpython 3.14 @ ab2d84fe1023/Include/internal/pycore_long.h#L130-200

/* Fast add: returns NULL (not an error) when the result would overflow
the compact range; caller falls back to the general add. */
static inline PyObject *
_PyLong_FastAdd(PyLongObject *left, PyLongObject *right)
{
if (!_PyLong_IsCompact(left) || !_PyLong_IsCompact(right)) {
return NULL;
}
Py_ssize_t a = _PyLong_CompactValue(left);
Py_ssize_t b = _PyLong_CompactValue(right);
/* Overflow check for signed addition. */
Py_ssize_t res;
if (__builtin_add_overflow(a, b, &res)) {
return NULL;
}
return PyLong_FromSsize_t(res);
}

static inline PyObject *
_PyLong_FastSub(PyLongObject *left, PyLongObject *right)
{
if (!_PyLong_IsCompact(left) || !_PyLong_IsCompact(right)) {
return NULL;
}
Py_ssize_t a = _PyLong_CompactValue(left);
Py_ssize_t b = _PyLong_CompactValue(right);
Py_ssize_t res;
if (__builtin_sub_overflow(a, b, &res)) {
return NULL;
}
return PyLong_FromSsize_t(res);
}

_PyLong_FastAdd and _PyLong_FastSub are the tight-loop helpers invoked by the specializing interpreter's BINARY_OP_ADD_INT and BINARY_OP_SUBTRACT_INT instructions. The contract is: return a new PyLongObject * when both inputs are compact and the result fits a Py_ssize_t; return NULL (not an exception) to signal "slow path needed" otherwise. The caller never checks PyErr_Occurred() after getting NULL from these; it simply despecializes the instruction and calls the full PyNumber_Add.

The overflow check uses GCC/Clang's __builtin_add_overflow rather than manual sign-bit arithmetic. On platforms without that built-in, CPython falls back to a portable bounds check before the addition.

The result is immediately handed to PyLong_FromSsize_t, which checks the small-int cache (values -5 to 256) before allocating. For the common case of incrementing a small loop counter the entire fast path never touches the allocator.

In gopy, objects/long_arith.go implements intAdd using math/big.Int.Add for all magnitudes. The small-int cache in objects/long_cache.go covers -5 through 256 and is consulted by NewIntFromBig before allocating, providing the same single-allocation short-circuit for typical loop counters without the separate compact predicate machinery.

gopy mirror

gopy does not replicate the lv_tag bit field. The CPython layout is an optimization that avoids a heap allocation for integers that fit in one Py_ssize_t; Go's math/big.Int already stores small values inline within the struct without a separate heap allocation, so the same goal is achieved at a different layer.

The digit type (uint32_t, base 2^30) is not exposed in gopy. The PyLong_SHIFT and PyLong_MASK constants have no counterparts because gopy never manually packs or unpacks limbs; math/big handles the multiprecision arithmetic internally.

The compact predicates (_PyLong_IsCompact, _PyLong_IsNonNegativeCompact, _PyLong_IsZero, _PyLong_IsPositive, _PyLong_IsNegative) are not needed as separate functions in gopy because big.Int.Sign() and big.Int.IsInt64() cover the same ground. The fast-path arithmetic in _PyLong_FastAdd / _PyLong_FastSub is implicit in intAdd / intSub via the small-int cache lookup inside NewIntFromBig.