Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
103 changes: 103 additions & 0 deletions pyre/extra_tests/parity_tests/builtin_sum_python314.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,57 @@
import itertools
import math
import random


# `pypy/module/__builtin__/app_functional.py:54 _regular_sum` folds over the
# live iterator, so `next` and `__add__` interleave rather than the whole
# iterable being materialised first.
order = []


class Item:
def __init__(self, value):
self.value = value

def __radd__(self, other):
order.append("add%d" % self.value)
return other + self.value


def items():
for i in range(3):
order.append("next%d" % i)
yield Item(i)


assert sum(items()) == 3
assert order == ["next0", "add0", "next1", "add1", "next2", "add2"], order


# Streaming also means an unbounded iterable is folded rather than collected.
class Stop(Exception):
pass


seen = 0


class Counted:
def __radd__(self, other):
global seen
seen += 1
if seen > 10_000:
raise Stop
return other


try:
sum(Counted() for _ in itertools.count())
except Stop:
pass
assert seen == 10_001, seen


assert repr(sum([-0.0])) == "0.0"
assert repr(sum([-0.0], -0.0)) == "-0.0"
assert repr(sum([], -0.0)) == "-0.0"
Expand All @@ -10,6 +60,32 @@
assert math.isinf(sum([float("inf"), float("inf")]))
assert math.isinf(sum([1e308, 1e308]))

# An `int` wider than a machine word is folded into the compensated
# accumulators as a double, so the compensation term survives it; only a
# magnitude beyond f64 range raises.
assert sum([1.0, 10**100, 1.0, -(10**100)]) == 2.0
assert sum([2j, 1.0, 10**100, 1.0, -(10**100)]) == 2 + 2j
assert sum([1.0, 2**63]) == 1.0 + float(2**63)

# ...but a wide *running total* ends the integer fast path for good, the way
# `PyLong_AsLongAndOverflow` signalling overflow does: what leaves that path is
# still an `int`, so no compensated phase can claim it.
assert sum([2**63, 0.1, 1, -(2**63)]) == 0.0
assert sum([0.1, 1, -(2**63)], 2**63) == 0.0
assert sum([2**63, 0.1, -(2**63)]) == 0.0
assert sum([2**63, 1, -(2**63)]) == 1
# A wide int reached *after* the total is already a float stays on the
# compensated path.
assert sum([0.1, 2**63, 1, -(2**63)]) == 1.1

for values in ([1.0, 10**1000], [1j, 10**1000]):
try:
sum(values)
except OverflowError:
pass
else:
raise AssertionError("expected OverflowError for %r" % (values,))

Comment thread
coderabbitai[bot] marked this conversation as resolved.
random.seed(0)
values = [
complex(random.random() - 0.5, random.random() - 0.5)
Expand All @@ -20,6 +96,33 @@
sum(value.imag for value in values),
)

# The complex fast path takes `PyFloat_Check` / `PyLong_Check`, not the
# exactness test the float phase uses: a `float` or `int` subclass stays on it
# and its reflected addition is skipped, while a `complex` subclass leaves.
class SubFloat(float):
def __radd__(self, other):
return "radd-float"


class SubInt(int):
def __radd__(self, other):
return "radd-int"


class SubComplex(complex):
def __radd__(self, other):
return "radd-complex"


assert sum([1j, SubFloat(1.0)]) == 1 + 1j
assert sum([1j, SubInt(1)]) == 1 + 1j
assert sum([1j, True]) == 1 + 1j
assert sum([1j, SubComplex(1)]) == "radd-complex"
# The float phase does test exactness, so a `float` subclass leaves it there.
assert sum([1.0, SubFloat(1.0)]) == "radd-float"
assert sum([1, SubFloat(1.0)]) == "radd-float"
assert sum([1.0, SubInt(1)]) == 2.0

for values in (
[complex(1, -0.0), 1],
[1, complex(1, -0.0)],
Expand Down
198 changes: 161 additions & 37 deletions pyre/pyre-interpreter/src/builtins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15196,86 +15196,210 @@ fn builtin_sum(args: &[PyObjectRef]) -> Result<PyObjectRef, crate::PyError> {
"sum() can't sum bytearray [use b''.join(seq) instead]",
));
}
// `_regular_sum`: `last = last + x` over the generic iterator protocol
// (so generators, ranges, sets, dict views, ... all work). Very
// intentionally `last + x`, not `+=` — preserving a mutable `start`
// (e.g. a list) matches PyPy's app-level definition.
let items = crate::builtins::collect_iterable(iterable)?;
let mut last = start;
let mut i = 0;
// `_regular_sum`: `for x in sequence: last = last + x` over the generic
// iterator protocol (so generators, ranges, sets, dict views, ... all
// work). Very intentionally `last + x`, not `+=` — preserving a mutable
// `start` (e.g. a list) matches PyPy's app-level definition.
//
// That fold runs over the *live* iterator, so `next` and the addition
// interleave. Materialising the iterable up front reorders those side
// effects and turns an unbounded iterable into an unbounded allocation, so
// pump the iterator here instead and keep the running total in a
// shadow-stack slot: every `next` and every `__add__` can run allocating
// Python code and move it.
// Every slot here is read and written once per item, so the whole fold
// goes through the scope's cached root-stack cell rather than the free
// `shadow_stack_*` functions, which re-resolve the thread local on each
// call.
let roots = pyre_object::gc_roots::push_roots();
// `iter(iterable)` runs arbitrary allocating code before the fold begins,
// so both operands are already live across a collection point here: root
// them first and read every later use back out of the slot, never from the
// local a foreign collection has left stale.
let last_slot = roots.base();
roots.pin_root(start);
let iterable_slot = last_slot + 1;
roots.pin_root(iterable);
Comment on lines +15220 to +15222

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Publish both live operands before normalization

If another thread starts a moving collection while the first pin_root calls try_gc_current_object_address, iterable is still only a raw local; the collection can move it, after which the second pin_root publishes the stale address. The DictOperationGuard added in this same change documents and avoids this exact race by publishing the complete live set before normalizing it. Publish start and iterable together, then normalize both before either is read.

Useful? React with 👍 / 👎.

let it_slot = iterable_slot + 1;
roots.pin_root(crate::baseobjspace::iter(roots.get(iterable_slot))?);
// Seeded with `start` purely to own a slot; every read is guarded by
// `pending`, which is only set once a real item has been written here.
let item_slot = it_slot + 1;
roots.pin_root(roots.get(last_slot));
// A dict-view iterator is an off-GC carrier whose own walker does not cover
// this native loop; retain its source dict for the duration, as
// `collect_iterator` does.
if unsafe { pyre_object::dictmultiobject::is_dict_view_iterator(roots.get(it_slot)) } {
let w_dict = unsafe {
pyre_object::dictmultiobject::w_dict_view_iterator_get_dict(roots.get(it_slot))
};
roots.pin_root(w_dict);
}
let mut pending = false;
let mut exhausted = false;
let mut ensure_item =
|pending: &mut bool, exhausted: &mut bool| -> Result<(), crate::PyError> {
if *pending || *exhausted {
return Ok(());
}
// The iterator may have moved during a prior step; reload it from its
// (post-relocation) slot before each call.
match crate::baseobjspace::next(roots.get(it_slot)) {
Ok(v) => {
roots.set(item_slot, v);
*pending = true;
}
Err(e) if e.kind == crate::PyErrorKind::StopIteration => *exhausted = true,
Err(e) => return Err(e),
}
Ok(())
};
// `builtin_sum_impl` runs an exact-int accumulator until the running
// total turns into an exact float, then a float accumulator that carries
// the improved Kahan-Babuška (Neumaier) compensation term — so
// `sum([0.1] * 10)` is exactly `1.0` rather than the naive partial sum
// `functional.py:_sum` produces. The int phase is the generic `last + x`
// loop, which already keeps exact ints exact and promotes to a float on
// the first float item, so only the float phase needs its own arithmetic.
while i < items.len()
&& unsafe { is_exact_int_operand(last) }
&& !unsafe { is_exact_float_operand(last) }
{
last = crate::baseobjspace::add(last, items[i])?;
i += 1;
//
// The C accumulator is a machine word, and `PyLong_AsLongAndOverflow`
// signalling overflow ends the int fast path for good: the value that
// leaves it is still an `int`, so neither compensated phase below can
// claim it and the rest of the fold is generic. Leaving on a wide total
// reproduces that — `sum([2**63, 0.1, 1, -(2**63)])` is `0.0`, not the
// `1.0` a compensated float phase would produce.
loop {
ensure_item(&mut pending, &mut exhausted)?;
if !pending {
break;
}
// Nothing between this read and the `add` can collect, so the running
// total is read once per item rather than re-read for the call.
let last = roots.get(last_slot);
if !unsafe { is_machine_word_int_operand(last) } {
break;
}
pending = false;
let total = crate::baseobjspace::add(last, roots.get(item_slot))?;
roots.set(last_slot, total);
}
if unsafe { is_exact_float_operand(last) } {
let mut real_sum =
compensated_sum_from_double(unsafe { pyre_object::w_float_get_value(last) });
while i < items.len() {
let item = items[i];
if unsafe { is_exact_float_operand(roots.get(last_slot)) } {
let mut real_sum = compensated_sum_from_double(unsafe {
pyre_object::w_float_get_value(roots.get(last_slot))
});
loop {
ensure_item(&mut pending, &mut exhausted)?;
if !pending {
break;
}
let item = roots.get(item_slot);
// A float *subclass* leaves the fast path — its `__add__` may be
// overridden — while `bool` and an `int` subclass stay in it,
// matching the `PyFloat_CheckExact` / `PyLong_Check` asymmetry of
// the C loop. An int too wide for a machine word leaves it too,
// the way `PyLong_AsLongAndOverflow` signals overflow.
// the C loop. An item that leaves it stays `pending` for the
// generic fold below.
let x = unsafe {
if pyre_object::is_float(item) && pyre_object::is_exact_builtin_instance(item) {
pyre_object::w_float_get_value(item)
} else if pyre_object::pyobject::is_int(item) {
pyre_object::w_int_get_value(item) as f64
} else if pyre_object::pyobject::is_int_or_long(item) {
let v = sum_int_as_double(item);
if !v.is_finite() {
return Err(crate::PyError::overflow_error(
"int too large to convert to float",
));
}
v
} else {
break;
}
};
pending = false;
real_sum = compensated_sum_add(real_sum, x);
i += 1;
}
last = pyre_object::w_float_new(compensated_sum_to_double(real_sum));
roots.set(
last_slot,
pyre_object::w_float_new(compensated_sum_to_double(real_sum)),
);
}
// CPython 3.14's complex fast path carries independent compensated real
// and imaginary accumulators. Exact complex items, all ints, and all
// floats stay on the path; an arbitrary numeric object falls back to the
// generic left-fold below.
if unsafe { is_exact_complex_operand(last) } {
if unsafe { is_exact_complex_operand(roots.get(last_slot)) } {
Comment on lines +15319 to +15328

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

CPython bltinmodule.c builtin_sum float loop fallback PyNumber_Add then PyComplex_CheckExact result complex compensated block order

💡 Result:

In CPython, the sum() built-in function (implemented in Python/bltinmodule.c) utilizes specialized loops to enhance performance and accuracy for specific types, such as floats and complexes [1][2][3]. When processing inputs, sum() attempts to use these specialized loops. If it encounters types that fall outside the specialization (such as when a non-float or non-complex type is introduced in a sequence), it performs a fallback to the generic PyNumber_Add() operation to ensure correct behavior according to Python's general arithmetic rules [2][4][5]. Specifically, regarding compensated summation and type handling: 1. Compensated Summation: Recent improvements (e.g., in Python 3.12+ and subsequent updates) introduced compensated summation (a Kahan-like algorithm) for both float and complex accumulations to reduce floating-point rounding errors [4][6][7]. 2. Fallback Mechanism: If the specialization loop (e.g., for PyFloat_CheckExact or PyComplex_CheckExact) can no longer proceed—either because the input type changes or because the accumulation result is no longer compatible with the specialized storage—the implementation falls back to PyNumber_Add() [2][4][5]. 3. Interaction with Complex Numbers: For complex summation, the code specifically checks PyComplex_CheckExact [5]. If the accumulated result is a complex number, the logic ensures that subsequent additions are handled correctly, often using PyNumber_Add() to combine the current result with the next item if the item itself is not a simple float or complex that can be handled by the specialized in-place accumulation [5][3]. 4. Block Order/Compensated Block: The compensated summation logic maintains high-order and low-order components (often referred to as a CompensatedSum structure or similar in internal implementations) to track accumulated values and compensation for lost bits [3]. If the type of the sum result or the item requires moving out of this compensated loop, the implementation stops the compensated summation and reverts to the standard, non-compensated PyNumber_Add() flow [2][4][5]. These changes were part of ongoing efforts to improve the precision of sum() for floating-point and complex data while maintaining the flexibility to handle arbitrary object types through PyNumber_Add() [2][6][7].

Citations:


🏁 Script executed:

#!/bin/sh
set -eu

# Locate the relevant builtin implementation and inspect the sum loop around the cited region.
printf 'Files named builtins.rs:\n'
fd -a 'builtins\.rs$' . | sed 's#^\./##'

printf '\nLine count:\n'
wc -l pyre/pyre-interpreter/src/builtins.rs

printf '\nRelevant region (15260-15365):\n'
sed -n '15260,15365p' pyre/pyre-interpreter/src/builtins.rs | nl -ba -v15260

printf '\nSearch for exact_complex_operand and sum implementation context:\n'
rg -n "is_exact_complex_operand|PyComplex_CheckExact|compensated_sum_to_double|generic left-fold|sum\\(" pyre/pyre-interpreter/src/builtins.rs

Repository: youknowone/pyre

Length of output: 365


🏁 Script executed:

#!/bin/sh
set -eu

printf 'Relevant region (15260-15365):\n'
awk '{printf "%8d\t%s\n", NR, $0}' pyre/pyre-interpreter/src/builtins.rs | sed -n '15260,15365p'

printf '\nSearch for exact_complex_operand and sum implementation context:\n'
rg -n "is_exact_complex_operand|PyComplex_CheckExact|compensated_sum_to_double|generic left-fold|sum\\(" pyre/pyre-interpreter/src/builtins.rs

Repository: youknowone/pyre

Length of output: 7115


🌐 Web query:

CPython bltinmodule.c builtin_sum 3.14 float item PyComplex_CheckExact result add fallback order

💡 Result:

In CPython's builtin_sum (implemented in Python/bltinmodule.c), the summation logic has been progressively optimized with specialized fast paths to handle numeric types like integers, floats, and complex numbers efficiently [1][2][3]. When builtin_sum processes items, it maintains a running result. If the internal specialized summation detects an item type that does not match the current specialized path (e.g., encountering a complex object while summing float items), the implementation breaks out of the optimized loop and falls back to a more generic addition mechanism [1][2]. Regarding the specific interaction with PyComplex_CheckExact: 1. Logic Flow: Recent optimizations have introduced specialized paths for complex numbers, using PyComplex_CheckExact to identify complex inputs directly [1][3]. When the sum contains complex numbers, the implementation tracks the real and imaginary components separately using compensated summation techniques (e.g., cs_add helpers) to maintain precision [4][3]. 2. Fallback Mechanism: If the current item is not a simple numeric type that can be handled by the specialized loop (or if a type mismatch occurs, such as encountering a non-numeric type or a complex number where only floats were expected), the specialized loop terminates [1][2]. It then falls back to using standard Python object operations, typically PyNumber_Add(result, item), to compute the next step of the sum [2]. 3. Item Handling: The implementation attempts to avoid the overhead of generic PyNumber_Add by using specialized checks like PyFloat_CheckExact or PyLong_Check for performance [1][2]. If an item fails these exact type checks but is still a valid numeric type, it may be handled by the generic fallback or, in newer versions, by broader type checks (like PyFloat_Check) if the complexity is deemed acceptable for that specific execution path [1][3]. In summary, the fallback order generally prioritizes: - Specialized exact-type summation loops (e.g., using PyFloat_CheckExact or PyComplex_CheckExact) [1][2][3]. - Falling back to the generic PyNumber_Add() method when the item type deviates from the current specialized accumulator's type [1][2]. This design ensures that sum() remains high-performance for homogeneous numeric iterables while maintaining correctness for mixed-type inputs by deferring to the standard Python numeric protocol [2][3].

Citations:


🌐 Web query:

site:github.com/python/cpython/bltinmodule.c builtin_sum PyObject_Add PyComplex_CheckExact cs_add

💡 Result:

Based on the search results, I found information about the code you're looking for in CPython's bltinmodule.c.

In the builtin_sum function, when PyComplex_CheckExact(result) is true, the code uses compensated summation with cs_add to accumulate complex number values by separately summing the real and imaginary parts.

Specifically, the implementation checks if items are exact complex types using PyComplex_CheckExact(item), and if so, uses cs_add to add the real and imaginary components to running sums (re_sum and im_sum).

The code utilizes a specialized CompensatedSum structure (cs_add) to improve numerical accuracy when summing complex numbers, which is more sophisticated than the basic PyObject_Add approach used for general object addition. You can view the complete implementation in the CPython repository's bltinmodule.c file.

Citations:


🌐 Web query:

github.com python cpython Python/bltinmodule.c "PyComplex_CheckExact(result)" "cs_add" result PyObject_Add(item)

💡 Result:

In the CPython source code, PyComplex_CheckExact is a macro defined in Include/complexobject.h that checks if a given PyObject is exactly an instance of the complex type, explicitly excluding its subclasses [1][2]. It is defined as: #define PyComplex_CheckExact(op) (Py_TYPE(op) == &PyComplex_Type) This macro is widely used within CPython's internal implementation (such as in Objects/complexobject.c) to optimize operations by avoiding the overhead or safety concerns associated with handling arbitrary subclasses [3][4][5]. When CPython code uses PyComplex_CheckExact, it ensures that it is dealing with the standard, native complex number type, allowing it to safely access the underlying structure (e.g., the cval member) without needing to worry about overridden methods or non-standard behavior that might be introduced by a user-defined subclass [3][4][5]. While your query mentions Python/bltinmodule.c, the primary usage of PyComplex_CheckExact is concentrated in Objects/complexobject.c for handling complex arithmetic and internal object management [3][4][5]. The function mentioned in your query—PyObject_Add—is a general C-API function used to perform addition on Python objects, though it is not typically part of a direct pattern named cs_add in the context of PyComplex_CheckExact. Most internal arithmetic operations involving complexes utilize specific complex-type functions or macros to ensure correct dispatching [4][5]. top_results: [1][3][2][4][5]

Citations:


🏁 Script executed:

#!/bin/sh
set -eu

python3 - <<'PY'
try:
    import math
    from _pyre_interpreter import builtin_sum
except Exception as e:
    print("IMPORT_ERROR", repr(e))
    import sys
    sys.exit(0)

cases = [
    ("float_float_complex", [0.1] + [[complex(1.0)])],
    ("float_complex", [0.1 + 0.0, 1+2j],
    ("complex_float_float", [1+0j] + [0.1, 0.2, 0.3]),
]
missing = []
for name, args in cases:
    try:
        print(name, "REPR", repr(builtin_sum(list(args))))
    except Exception as e:
        print(name, "EXCEPTION", type(e).__name__, repr(e))

print("PYTHON_FLOAT_FLOAT_COMPLEX", repr(sum([0.1, complex(1.0, 2.0)])))
print("PYTHON_FLOAT_COMPLEX", repr(sum([0.1+0j, 1+2j])))
print("PYTHON_COMPLEX_FLOAT_FLOAT", repr(sum([1+0j, 0.1, 0.2, 0.3])))
PY

Repository: youknowone/pyre

Length of output: 363


🏁 Script executed:

#!/bin/sh
set -eu

python3 - <<'PY'
try:
    import math
    from _pyre_interpreter import builtin_sum
except Exception as e:
    print("IMPORT_ERROR", repr(e))
    import sys
    sys.exit(0)

cases = [
    ("float_float_complex", [0.1, complex(1.0, 2.0)]),
    ("float_complex", [0.1+0j, 1+2j]),
    ("complex_float_float", [1+0j, 0.1, 0.2, 0.3]),
]

for name, args in cases:
    try:
        print(name, "REPR", repr(builtin_sum(list(args))))
    except Exception as e:
        print(name, "EXCEPTION", type(e).__name__, repr(e))

for args in [
    [0.1, complex(1.0, 2.0)],
    [0.1+0j, 1+2j],
    [1+0j, 0.1, 0.2, 0.3],
]:
    print("python", repr(args), "=>", repr(sum(args)))
PY

Repository: youknowone/pyre

Length of output: 225


🏁 Script executed:

#!/bin/sh
set -eu

# Read the next phase to understand how the pending complex falls into the generic fold and any post-fold checks.
printf 'Generic fold and end checks (15361-15415):\n'
awk '{printf "%8d\t%s\n", NR, $0}' pyre/pyre-interpreter/src/builtins.rs | sed -n '15361,15415p'

printf '\nOperand predicates and helpers:\n'
rg -n "is_exact_float_operand|is_exact_int_operand|is_exact_complex_operand|w_complex_get_real|w_complex_get_imag" pyre/pyre-interpreter/src -A8 -B4

printf '\nBuiltins tests mentioning sum or complex:\n'
rg -n "sum\\(|complex|isnan|float\\+complex|float.*complex|complex.*float" pyre -g '*.py' -g '*.rs' -A4 -B4

Repository: youknowone/pyre

Length of output: 50372


🏁 Script executed:

#!/bin/sh
set -eu

printf 'Generic fold and post-general checks (15361-15445):\n'
awk '{printf "%8d\t%s\n", NR, $0}' pyre/pyre-interpreter/src/builtins.rs | sed -n '15361,15445p'

printf '\nOperand predicates (15404-15430):\n'
awk '{printf "%8d\t%s\n", NR, $0}' pyre/pyre-interpreter/src/builtins.rs | sed -n '15404,15430p'

Repository: youknowone/pyre

Length of output: 5501


🌐 Web query:

github.com python cpython Python/bltinmodule.c lines "compensated" "PyComplex_CheckExact(result)" "PyObject_Add"

💡 Result:

The functions PyComplex_CheckExact and PyObject_Add are both part of the CPython C API, but they serve different purposes and are used in different contexts within the CPython source code [1]. PyComplex_CheckExact(op) is a macro defined in the CPython C API that returns true if its argument is a PyComplexObject but not a subtype of PyComplexObject [1]. In the context of Python/bltinmodule.c, it is used to perform type checks on objects, often to allow for specialized, faster handling of complex numbers without invoking general, slower methods [2]. For example, development discussions have included proposing the use of PyComplex_CheckExact within the builtin sum function to specialize summation for complex numbers [2]. PyObject_Add(o1, o2) is a C API function that adds two objects together, essentially performing the equivalent of the Python expression o1 + o2. It is a general-purpose function that handles various object types by invoking their type-specific number methods (e.g., nb_add) [1]. While both may appear in Python/bltinmodule.c—which implements many of Python's built-in functions—they are not typically used together as a pair [3][4][2]. PyComplex_CheckExact is used to verify an object's exact type, while PyObject_Add is used to perform arithmetic operations [2][1]. Their proximity in search results or code discussions generally reflects distinct operations being performed on objects within built-in functions, rather than them being linked as a single mechanism.

Citations:


🌐 Web query:

raw "cs_add" "PyComplex_CheckExact(result)" "PyNumber_Add(result, item)" bltinmodule.c

💡 Result:

The terms and code snippet provided originate from two distinct and unrelated domains: the Python C API and the CSparse library. There is no native function named cs_add in Python's bltinmodule.c or any other core CPython source file that uses PyComplex_CheckExact or PyNumber_Add. 1. Python C API (PyComplex_CheckExact, PyNumber_Add) These are standard functions within the CPython source code [1][2]. - PyComplex_CheckExact(result) is a C API function used to determine if an object is an exact instance of the built-in complex type, excluding subclasses [1][3]. - PyNumber_Add(result, item) is a generic C API function that performs addition between two Python objects, equivalent to the expression result + item in Python [2][4]. 2. CSparse Library (cs_add) The symbol cs_add belongs to the CSparse library, a suite of sparse matrix algorithms [5][6]. The function signature is typically cs *cs_add (const cs A, const cs B, double alpha, double beta) [5][6]. It performs sparse matrix addition (C = alphaA + betaB) [5][6] and is implemented in C, completely unrelated to Python's complex number or object addition APIs [5][7]. The snippet likely represents a user-defined function or a misunderstanding of merging two separate codebases. No such integration exists in the standard CPython distribution [8][9].

Citations:


🌐 Web query:

site:raw.githubusercontent.com python cpython Python/bltinmodule.c "PyComplex_CheckExact(item)"

💡 Result:

PyComplex_CheckExact(item) is a C macro in the CPython API used to determine if a given PyObject* is specifically an instance of the complex type (PyComplex_Type), excluding subclasses [1][2][3]. It is defined in the Python header files, typically within Include/complexobject.h, as a check against the object's type pointer [1][4]. The macro essentially performs the following logic: #define PyComplex_CheckExact(op) Py_IS_TYPE(op, &PyComplex_Type) It is widely used across the CPython codebase, including in modules like Python/bltinmodule.c (often in specialized logic or optimizations), Python/ast.c, and various internal parsers to verify that an object is exactly a complex number without triggering potential subclass methods or overhead [4][2][5]. Code using this macro typically follows it with functions like PyComplex_AsCComplex to access the underlying data [6][1][4].

Citations:


🌐 Web query:

"builtin_sum" "PyComplex_CheckExact" "PyNumber_Add" "builtin_sum" "bltinmodule.c"

💡 Result:

The functions PyComplex_CheckExact and PyNumber_Add are part of the Python/C API and are utilized within the implementation of the built-in sum function located in Python/bltinmodule.c [1][2]. The sum function employs fast-path optimizations for specific types (such as integers, floats, and complex numbers) to improve performance [3][2]. 1. PyComplex_CheckExact: This function is used to verify if an object is an exact instance of a complex number, excluding subclasses [4]. In the context of builtin_sum_impl within bltinmodule.c, it is used to guard or enter specialized loops that perform high-performance, in-place summation for complex numbers [3]. 2. PyNumber_Add: When the inputs do not meet the criteria for specialized fast-path handling (e.g., they are not exact instances of the expected types or a fallback is required), the implementation falls back to the generic PyNumber_Add function [3][2]. This ensures that the summation remains compatible with arbitrary Python objects that support the addition protocol [3]. Recent developments in the CPython source code, such as PR #150869, have highlighted the importance of using exact type checks (like PyFloat_CheckExact or PyComplex_CheckExact) alongside specialized deallocation routines (like _Py_DECREF_SPECIALIZED with _PyFloat_ExactDealloc) to avoid incorrect deallocation of object subclasses [2]. Failure to use exact checks can cause specialized deallocation to bypass an object's actual deallocator, potentially leading to memory leaks or inconsistent internal states [2].

Citations:


🌐 Web query:

"builtin_sum_impl" "PyComplex_CheckExact" "PyNumber_Add(result, item)"

💡 Result:

The identifiers builtin_sum_impl, PyComplex_CheckExact, and PyNumber_Add are associated with the implementation of the built-in sum() function in the CPython source code (Python/bltinmodule.c) [1][2]. Recent development work has introduced optimizations and accuracy improvements for the sum() function, particularly for complex number inputs [3][4][5]. In the CPython implementation, builtin_sum_impl performs the summation by iterating through the input [5]. To improve performance and floating-point accuracy, the implementation checks if the accumulator (result) is a complex number using PyComplex_CheckExact [3][5]. When PyComplex_CheckExact identifies the accumulator as a complex type, the implementation can branch into a specialized, high-accuracy summation path that avoids repeated, generic calls to PyNumber_Add [3][5]. When this optimization is not applicable or when falling back to a more general case, the code uses PyNumber_Add(result, item) to add each subsequent item to the running total [3][5]. Historically, the use of PyNumber_Add (rather than PyNumber_InPlaceAdd) in sum() has been a deliberate design choice to prevent unintended side effects, such as mutating objects passed in as the start argument when performing operations like list concatenation [6]. While PyNumber_InPlaceAdd could improve performance for certain types like list or collections.Counter, it would represent a breaking change in semantics for those types [6]. Consequently, recent improvements have focused on type-specific specializations (like the complex number case) rather than a global switch to in-place addition [3][4].

Citations:


🌐 Web query:

"cs_add" "PyComplex_CheckExact(result)" "fs"

💡 Result:

The terms you are referencing appear to be related to the internal implementation of Python's C API, specifically concerning numeric operations and object checking. PyComplex_CheckExact(p) is a macro in the Python/C API that returns a non-zero value if the provided object p is exactly a standard Python complex number object, and zero if it is a subtype or a different type [1][2]. It is commonly used in CPython's internal code to optimize operations by ensuring the object has the expected standard memory layout, avoiding the overhead or complexity associated with subclasses [2][3]. The terms "cs_add" and "fs" (often seen as part of fsum or similar accuracy-improving logic) are related to internal helper functions used within CPython to improve the numerical precision of summation operations [4][5]. Specifically: - cs_add: This is an internal helper function (Compensated Summation) used in recent CPython implementations (notably within the builtin sum function) to maintain higher precision when adding numbers [4][5]. It uses a compensated summation algorithm to track high-order and low-order bits, mitigating precision loss during floating-point or complex number accumulation [4]. - Context: These are separate components. PyComplex_CheckExact is for type verification, while cs_add is an internal algorithm for accurate numerical summation [4][3]. They appear together in CPython's codebase when the sum function handles complex or float inputs, ensuring that the inputs are correctly identified and processed using these high-precision helpers [5].

Citations:


Fix the mixed float-then-complex sum() path.

sum([0.1, 1+2j]) writes the compensated float total back to last_slot, makes the accumulating object a float, and then skips the exact complex fast path. CPython adds the pending complex item first while still in the float loop, and only later enters the complex compensated path with a complex accumulator. Promote the pending exact complex to real_sum/imag_sum before breaking and writing the float accumulator back.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pyre/pyre-interpreter/src/builtins.rs` around lines 15312 - 15321, Update the
float-accumulation loop and transition into the exact-complex path so a pending
exact complex item is consumed into the compensated real_sum and imag_sum before
writing the float result to last_slot and breaking. Ensure the accumulator
becomes complex afterward, allowing is_exact_complex_operand and the complex
compensated path to run for mixed float-then-complex inputs while preserving the
existing all-float behavior.

let last = roots.get(last_slot);
let mut real_sum =
compensated_sum_from_double(unsafe { pyre_object::w_complex_get_real(last) });
let mut imag_sum =
compensated_sum_from_double(unsafe { pyre_object::w_complex_get_imag(last) });
while i < items.len() {
let item = items[i];
loop {
ensure_item(&mut pending, &mut exhausted)?;
if !pending {
break;
}
let item = roots.get(item_slot);
unsafe {
if is_exact_complex_operand(item) {
real_sum = compensated_sum_add(real_sum, pyre_object::w_complex_get_real(item));
imag_sum = compensated_sum_add(imag_sum, pyre_object::w_complex_get_imag(item));
} else if pyre_object::pyobject::is_int(item) {
real_sum =
compensated_sum_add(real_sum, pyre_object::w_int_get_value(item) as f64);
} else if pyre_object::pyobject::is_int_or_long(item) {
let v = sum_int_as_double(item);
if !v.is_finite() {
return Err(crate::PyError::overflow_error(
"int too large to convert to float",
));
}
real_sum = compensated_sum_add(real_sum, v);
} else if pyre_object::is_float(item) {
real_sum = compensated_sum_add(real_sum, pyre_object::w_float_get_value(item));
Comment on lines 15352 to 15353

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Require an exact float in the complex fast path.

Line 15345 tests pyre_object::is_float(item) without an exactness test. A float subclass therefore enters the compensated complex accumulator, so its __add__/__radd__ override never runs. The float phase at line 15295 pairs is_float with is_exact_builtin_instance for that reason, and CPython's complex loop uses PyFloat_CheckExact after issue gh-122234 reported the merged specialization's non-exact PyFloat_Check(item) as a bug.

A float subclass item must leave the fast path and stay pending for the generic fold.

🐛 Proposed fix
-                } else if pyre_object::is_float(item) {
+                } else if pyre_object::is_float(item)
+                    && pyre_object::is_exact_builtin_instance(item)
+                {
                     real_sum = compensated_sum_add(real_sum, pyre_object::w_float_get_value(item));
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} else if pyre_object::is_float(item) {
real_sum = compensated_sum_add(real_sum, pyre_object::w_float_get_value(item));
} else if pyre_object::is_float(item)
&& pyre_object::is_exact_builtin_instance(item)
{
real_sum = compensated_sum_add(real_sum, pyre_object::w_float_get_value(item));
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pyre/pyre-interpreter/src/builtins.rs` around lines 15345 - 15346, Update the
complex fast-path branch around compensated_sum_add to require both
pyre_object::is_float(item) and pyre_object::is_exact_builtin_instance(item),
matching the existing float phase. Ensure float subclasses bypass this
accumulator and remain pending for the generic fold.

} else {
break;
}
}
i += 1;
pending = false;
}
last = pyre_object::w_complex_new(
compensated_sum_to_double(real_sum),
compensated_sum_to_double(imag_sum),
roots.set(
last_slot,
pyre_object::w_complex_new(
compensated_sum_to_double(real_sum),
compensated_sum_to_double(imag_sum),
),
);
}
for &item in &items[i..] {
last = crate::baseobjspace::add(last, item)?;
loop {
ensure_item(&mut pending, &mut exhausted)?;
if !pending {
break;
}
pending = false;
let total = crate::baseobjspace::add(roots.get(last_slot), roots.get(item_slot))?;
roots.set(last_slot, total);
}
Ok(last)
Ok(roots.get(last_slot))
}

/// `PyLong_AsDouble` for `builtin_sum`'s compensated fast paths: an `int` of
/// any width as a double. A Python `int` is always finite, so a non-finite
/// result means the magnitude exceeds f64 range and the caller raises.
///
/// Signalling over-range through the value rather than a `Result` keeps a
/// float-payload `Result` out of the JIT codewriter, which cannot flatten a
/// payload mixing `Float` (`Ok`) and `Ref` (`Err`) into one register kind.
unsafe fn sum_int_as_double(obj: PyObjectRef) -> f64 {
unsafe {
if pyre_object::pyobject::is_int(obj) {
pyre_object::w_int_get_value(obj) as f64
} else {
pyre_object::jit_bigint_to_f64_or_nan(pyre_object::w_long_get_value(obj))
}
}
}

/// An exact `int` the C fast path's `long` accumulator can hold — the
/// `PyLong_AsLongAndOverflow` precondition guarding `builtin_sum`'s integer
/// loop. pyre splits a wide `int` into its own `LONG_TYPE`, so overflow is
/// the type test rather than a range check.
unsafe fn is_machine_word_int_operand(obj: PyObjectRef) -> bool {
unsafe { is_exact_int_operand(obj) && !pyre_object::is_long(obj) }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Accept fitting W_Long values in the integer fast path

When start is an exact integer stored as W_LongObject but still fits in i64—for example (2**63) - (2**63 - 1), because long_sub returns w_long_new even for the value 1—this type-only check incorrectly exits the integer fast path. CPython's PyLong_AsLongAndOverflow accepts that value, so sum([0.1] * 10, start) uses compensated summation and returns 2.0; pyre instead takes the generic float fold and returns 2.000000000000001. Test w_long_fits_int rather than treating every LONG_TYPE as overflow.

AGENTS.md reference: AGENTS.md:L231-L233

Useful? React with 👍 / 👎.

}

/// `PyLong_CheckExact` — an exact `int`, excluding `bool` and any subclass.
Expand Down
Loading
Loading