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
| Lines | Symbol | Role | gopy |
|---|---|---|---|
| 1-60 | lv_tag bit layout, NON_SIZE_BITS, SIGN_* constants, digit typedef | Defines 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_CompactValue | Inline 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_IsNegative | Arithmetic 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.