340 lines
21 KiB
Python
340 lines
21 KiB
Python
#!/usr/bin/env python3
|
|
"""Focused VC6 C89 regression checks for Objects/longobject.c."""
|
|
import pathlib
|
|
import re
|
|
import sys
|
|
|
|
source = pathlib.Path(sys.argv[1]).read_text(encoding="utf-8")
|
|
match = re.search(r"static PyObject \*\s*get_small_int\(sdigit ival\)\s*\{(?P<body>.*?)\n\}", source, re.S)
|
|
assert match, "get_small_int definition not found"
|
|
body = match.group("body")
|
|
assert re.search(r"\A\s*PyObject \*v;\s*assert\(IS_SMALL_INT\(ival\)\);\s*v = \(PyObject \*\)&_PyLong_SMALL_INTS\[_PY_NSMALLNEGINTS \+ ival\];\s*Py_INCREF\(v\);\s*return v;", body), "get_small_int must declare v at function entry and retain assertion, lookup, incref, and return order"
|
|
print("longobject get_small_int C89 declaration and evaluation order: PASS")
|
|
|
|
match = re.search(r"PyLongObject \*\s*_PyLong_New\(Py_ssize_t size\)\s*\{(?P<body>.*?)\n\}", source, re.S)
|
|
assert match, "_PyLong_New definition not found"
|
|
body = re.sub(r"/\*.*?\*/", "", match.group("body"), flags=re.S)
|
|
expected = """
|
|
PyLongObject *result;
|
|
Py_ssize_t ndigits;
|
|
if (size > (Py_ssize_t)MAX_LONG_DIGITS) {
|
|
PyErr_SetString(PyExc_OverflowError,
|
|
"too many digits in integer");
|
|
return NULL;
|
|
}
|
|
ndigits = size ? size : 1;
|
|
result = PyObject_Malloc(offsetof(PyLongObject, ob_digit) +
|
|
ndigits*sizeof(digit));
|
|
if (!result) {
|
|
PyErr_NoMemory();
|
|
return NULL;
|
|
}
|
|
_PyObject_InitVar((PyVarObject*)result, &PyLong_Type, size);
|
|
return result;
|
|
"""
|
|
assert body.split() == expected.split(), "_PyLong_New must declare ndigits at function entry and retain overflow check, digit count, allocation, error, initialization, and return order"
|
|
print("longobject _PyLong_New C89 declaration and allocation/error order: PASS")
|
|
|
|
match = re.search(r"static PyObject \*\s*_PyLong_FromMedium\(sdigit x\)\s*\{(?P<body>.*?)\n\}", source, re.S)
|
|
assert match, "_PyLong_FromMedium definition not found"
|
|
body = re.sub(r"/\*.*?\*/", "", match.group("body"), flags=re.S)
|
|
expected = """
|
|
PyLongObject *v;
|
|
Py_ssize_t sign;
|
|
digit abs_x;
|
|
assert(!IS_SMALL_INT(x));
|
|
assert(is_medium_int(x));
|
|
v = PyObject_Malloc(sizeof(PyLongObject));
|
|
if (v == NULL) {
|
|
PyErr_NoMemory();
|
|
return NULL;
|
|
}
|
|
sign = x < 0 ? -1: 1;
|
|
abs_x = x < 0 ? -x : x;
|
|
_PyObject_InitVar((PyVarObject*)v, &PyLong_Type, sign);
|
|
v->ob_digit[0] = abs_x;
|
|
return (PyObject*)v;
|
|
"""
|
|
assert body.split() == expected.split(), "_PyLong_FromMedium must declare v, sign, and abs_x at function entry and retain assertions, allocation, NULL/error/return, sign, abs_x, initialization, digit write, and return order"
|
|
print("longobject _PyLong_FromMedium C89 declarations and evaluation/error order: PASS")
|
|
|
|
match = re.search(r"static PyObject \*\s*_PyLong_FromLarge\(stwodigits ival\)\s*\{(?P<body>.*?)\n\}", source, re.S)
|
|
assert match, "_PyLong_FromLarge definition not found"
|
|
body = re.sub(r"/\*.*?\*/", "", match.group("body"), flags=re.S)
|
|
expected = """
|
|
twodigits abs_ival;
|
|
twodigits t;
|
|
Py_ssize_t ndigits;
|
|
PyLongObject *v;
|
|
digit *p;
|
|
int sign;
|
|
assert(!is_medium_int(ival));
|
|
if (ival < 0) {
|
|
abs_ival = 0U-(twodigits)ival;
|
|
sign = -1;
|
|
}
|
|
else {
|
|
abs_ival = (twodigits)ival;
|
|
sign = 1;
|
|
}
|
|
assert(abs_ival >> PyLong_SHIFT != 0);
|
|
t = abs_ival >> (PyLong_SHIFT * 2);
|
|
ndigits = 2;
|
|
while (t) {
|
|
++ndigits;
|
|
t >>= PyLong_SHIFT;
|
|
}
|
|
v = _PyLong_New(ndigits);
|
|
if (v != NULL) {
|
|
p = v->ob_digit;
|
|
Py_SET_SIZE(v, ndigits * sign);
|
|
t = abs_ival;
|
|
while (t) {
|
|
*p++ = Py_SAFE_DOWNCAST(
|
|
t & PyLong_MASK, twodigits, digit);
|
|
t >>= PyLong_SHIFT;
|
|
}
|
|
}
|
|
return (PyObject *)v;
|
|
"""
|
|
assert body.split() == expected.split(), "_PyLong_FromLarge must declare abs_ival, t, ndigits, v, p, and sign at function entry and retain assertions, sign/absolute-value branches, digit count, allocation, initialization, digit writes, and return order"
|
|
print("longobject _PyLong_FromLarge C89 declarations and evaluation order: PASS")
|
|
|
|
match = re.search(r"^#define PYLONG_FROM_UINT\(INT_TYPE, ival\) (?P<body>.*?)^ \} while\(0\)$", source, re.S | re.M)
|
|
assert match, "PYLONG_FROM_UINT definition not found"
|
|
body = match.group("body") + " } while(0)"
|
|
body = re.sub(r"\\\n", "\n", body)
|
|
body = re.sub(r"/\*.*?\*/", "", body, flags=re.S)
|
|
assert re.search(r"\A\s*do\s*\{\s*Py_ssize_t ndigits;\s*INT_TYPE t;\s*PyLongObject \*v;\s*digit \*p;", body), "PYLONG_FROM_UINT must declare ndigits, t, v, and p at do-block entry"
|
|
expected = """
|
|
do {
|
|
Py_ssize_t ndigits;
|
|
INT_TYPE t;
|
|
PyLongObject *v;
|
|
digit *p;
|
|
if (IS_SMALL_UINT(ival)) {
|
|
return get_small_int((sdigit)(ival));
|
|
}
|
|
ndigits = 0;
|
|
t = (ival);
|
|
while (t) {
|
|
++ndigits;
|
|
t >>= PyLong_SHIFT;
|
|
}
|
|
v = _PyLong_New(ndigits);
|
|
if (v == NULL) {
|
|
return NULL;
|
|
}
|
|
p = v->ob_digit;
|
|
while ((ival)) {
|
|
*p++ = (digit)((ival) & PyLong_MASK);
|
|
(ival) >>= PyLong_SHIFT;
|
|
}
|
|
return (PyObject *)v;
|
|
} while(0)
|
|
"""
|
|
assert body.split() == expected.split(), "PYLONG_FROM_UINT must retain small-int early return, ndigits/t evaluation, digit count, allocation, NULL return, p initialization, ival evaluation/mutation, digit writes, and return order"
|
|
print("longobject PYLONG_FROM_UINT C89 declarations and evaluation/error order: PASS")
|
|
|
|
match = re.search(r"PyObject \*\s*PyLong_FromUnsignedLongLong\((?P<parameter>[^)]*)\)\s*\{(?P<body>.*?)\n\}", source, re.S)
|
|
assert match, "PyLong_FromUnsignedLongLong definition not found"
|
|
assert match.group("parameter").split() == "unsigned PY_LONG_LONG ival".split(), "PyLong_FromUnsignedLongLong must use unsigned PY_LONG_LONG to match the public declaration and VC6 __int64 spelling"
|
|
assert match.group("body").split() == "PYLONG_FROM_UINT(unsigned PY_LONG_LONG, ival);".split(), "PyLong_FromUnsignedLongLong must pass unsigned PY_LONG_LONG to PYLONG_FROM_UINT, preserving the unsigned 64-bit temporary"
|
|
print("longobject PyLong_FromUnsignedLongLong VC6 parameter and macro type spelling: PASS")
|
|
|
|
match = re.search(r"PyObject \*\s*PyLong_FromDouble\(double dval\)\s*\{(?P<body>.*?)\n\}", source, re.S)
|
|
assert match, "PyLong_FromDouble definition not found"
|
|
body = re.sub(r"/\*.*?\*/", "", match.group("body"), flags=re.S)
|
|
assert re.search(r"\A\s*PyLongObject \*v;\s*double frac;\s*int i, ndig, expo, neg;\s*const double int_max = \(unsigned long\)LONG_MAX \+ 1;", body), "PyLong_FromDouble locals must precede the fast-path statement"
|
|
ordered = (
|
|
"const double int_max = (unsigned long)LONG_MAX + 1;",
|
|
"if (-int_max < dval && dval < int_max)",
|
|
"return PyLong_FromLong((long)dval);",
|
|
"neg = 0;",
|
|
"if (Py_IS_INFINITY(dval))",
|
|
"if (Py_IS_NAN(dval))",
|
|
"if (dval < 0.0)",
|
|
"frac = frexp(dval, &expo);",
|
|
"ndig = (expo-1) / PyLong_SHIFT + 1;",
|
|
"v = _PyLong_New(ndig);",
|
|
"frac = ldexp(frac, (expo-1) % PyLong_SHIFT + 1);",
|
|
"for (i = ndig; --i >= 0; )",
|
|
"if (neg)",
|
|
"return (PyObject *)v;",
|
|
)
|
|
positions = [body.find(token) for token in ordered]
|
|
assert all(position >= 0 for position in positions) and positions == sorted(positions), "PyLong_FromDouble must preserve fast-path, validation, sign, conversion, allocation, digit-loop, and return order"
|
|
print("longobject PyLong_FromDouble C89 declarations and evaluation/error order: PASS")
|
|
|
|
code = re.sub(r"/\*.*?\*/|//[^\n]*|\"(?:\\.|[^\"\\])*\"", "", source, flags=re.S)
|
|
assert not re.search(r"\blong\s+long\b", code), "longobject must spell 64-bit integers with PY_LONG_LONG for VC6"
|
|
assert not re.search(r"\bLLONG_(?:MAX|MIN)\b", code), "longobject must use PY_LLONG_MAX/PY_LLONG_MIN instead of unavailable LLONG limits"
|
|
for signature in (
|
|
r"PyLong_FromLongLong\(PY_LONG_LONG ival\)",
|
|
r"PyLong_AsLongLong\(PyObject \*vv\)",
|
|
r"PyLong_AsUnsignedLongLong\(PyObject \*vv\)",
|
|
r"PyLong_AsUnsignedLongLongMask\(PyObject \*vv\)",
|
|
r"PyLong_AsLongLongAndOverflow\(PyObject \*vv, int \*overflow\)",
|
|
):
|
|
assert re.search(signature, source), "missing VC6-compatible long API declaration/definition: " + signature
|
|
print("longobject 64-bit API uses VC6 PY_LONG_LONG spellings: PASS")
|
|
|
|
match = re.search(r"static digit\s+inplace_divrem1\(digit \*pout, digit \*pin, Py_ssize_t size, digit n\)\s*\{(?P<body>.*?)\n\}", source, re.S)
|
|
assert match, "inplace_divrem1 definition not found"
|
|
body = re.sub(r"/\*.*?\*/", "", match.group("body"), flags=re.S)
|
|
loop = re.search(r"while \(--size >= 0\)\s*\{(?P<body>.*?)\n \}", body, re.S)
|
|
assert loop, "inplace_divrem1 digit loop not found"
|
|
loop_body = loop.group("body")
|
|
assert re.search(r"\A\s*twodigits dividend;\s*digit quotient;\s*dividend = \(\(twodigits\)remainder << PyLong_SHIFT\) \| pin\[size\];\s*quotient = \(digit\)\(dividend / n\);\s*remainder = dividend % n;\s*pout\[size\] = quotient;", loop_body), "inplace_divrem1 loop temporaries must be declared before the executable dividend calculation, preserving divide/remainder/store order"
|
|
print("longobject inplace_divrem1 loop C89 declaration and division order: PASS")
|
|
|
|
late_ucs4 = "else {\n assert (kind == PyUnicode_4BYTE_KIND);\n Py_UCS4 *p;"
|
|
entry_ucs4 = "else {\n Py_UCS4 *p;\n assert (kind == PyUnicode_4BYTE_KIND);"
|
|
assert source.count(late_ucs4) == 0, "Py_UCS4 locals must be hoisted before their branch assertions"
|
|
assert source.count(entry_ucs4) == 2, "both long-formatting branches must declare Py_UCS4 before checking the kind"
|
|
print("longobject Unicode formatting Py_UCS4 C89 branch declarations: PASS")
|
|
|
|
match = re.search(r"PyObject \*\s*PyLong_FromString\(const char \*str, char \*\*pend, int base\)\s*\{(?P<body>.*?)\n\}", source, re.S)
|
|
assert match, "PyLong_FromString definition not found"
|
|
body = re.sub(r"/\*.*?\*/", "", match.group("body"), flags=re.S)
|
|
assert re.search(r"\A\s*int sign = 1, error_if_nonzero = 0;\s*const char \*start, \*orig_str = str;\s*PyLongObject \*z = NULL;\s*PyObject \*strobj;\s*Py_ssize_t slen;\s*double fsize_z;", body), "PyLong_FromString must declare fsize_z in the function's opening declaration group"
|
|
ordered = (
|
|
"if (digits > _PY_LONG_MAX_STR_DIGITS_THRESHOLD)",
|
|
"fsize_z = (double)digits * log_base_BASE[base] + 1.0;",
|
|
"if (fsize_z > (double)MAX_LONG_DIGITS)",
|
|
"size_z = (Py_ssize_t)fsize_z;",
|
|
"z = _PyLong_New(size_z);",
|
|
)
|
|
positions = [body.find(token) for token in ordered]
|
|
assert all(position >= 0 for position in positions) and positions == sorted(positions), "PyLong_FromString must preserve digit-limit, size calculation, overflow, allocation, and conversion order"
|
|
print("longobject PyLong_FromString C89 size declaration and parsing order: PASS")
|
|
|
|
match = re.search(r"static int\s+long_divrem\(PyLongObject \*a, PyLongObject \*b,\s*PyLongObject \*\*pdiv, PyLongObject \*\*prem\)\s*\{(?P<body>.*?)\n\}", source, re.S)
|
|
assert match, "long_divrem definition not found"
|
|
body = match.group("body")
|
|
short_case = re.search(r"if \(size_a < size_b.*?\) \{(?P<body>.*?)\n \}", body, re.S)
|
|
assert short_case, "long_divrem smaller-dividend branch not found"
|
|
branch = short_case.group("body")
|
|
tokens = (
|
|
"PyObject *zero;",
|
|
"*prem = (PyLongObject *)long_long((PyObject *)a);",
|
|
"if (*prem == NULL)",
|
|
"zero = _PyLong_GetZero();",
|
|
"Py_INCREF(zero);",
|
|
"*pdiv = (PyLongObject*)zero;",
|
|
)
|
|
positions = [branch.find(token) for token in tokens]
|
|
assert all(position >= 0 for position in positions) and positions == sorted(positions), "long_divrem must declare zero at branch entry and preserve remainder, error, singleton, incref, and quotient ordering"
|
|
print("longobject long_divrem C89 local and reference order: PASS")
|
|
|
|
match = re.search(r"PyObject \*\s*_PyLong_Add\(PyLongObject \*a, PyLongObject \*b\)\s*\{(?P<body>.*?)\n\}", source, re.S)
|
|
assert match, "_PyLong_Add definition not found"
|
|
body = match.group("body")
|
|
assert re.match(r"\s*PyLongObject \*z;", body), "_PyLong_Add must declare z at function entry before the small-value fast path"
|
|
assert " PyLongObject *z;" not in body[body.find("if (IS_MEDIUM_VALUE(a)"):], "_PyLong_Add must not declare z after an executable fast-path statement"
|
|
ordered = (
|
|
"PyLongObject *z;",
|
|
"if (IS_MEDIUM_VALUE(a) && IS_MEDIUM_VALUE(b))",
|
|
"return _PyLong_FromSTwoDigits(medium_value(a) + medium_value(b));",
|
|
"if (Py_SIZE(a) < 0)",
|
|
"z = x_add(a, b);",
|
|
"return (PyObject *)z;",
|
|
)
|
|
positions = [body.find(token) for token in ordered]
|
|
assert all(position >= 0 for position in positions) and positions == sorted(positions), "_PyLong_Add must preserve small-integer fast path and sign/operation order"
|
|
print("longobject _PyLong_Add C89 declaration and arithmetic order: PASS")
|
|
|
|
match = re.search(r"static PyObject \*\s*long_pow\(PyObject \*v, PyObject \*w, PyObject \*x\)\s*\{(?P<body>.*?)\n\}", source, re.S)
|
|
assert match, "long_pow definition not found"
|
|
body = match.group("body")
|
|
assert 0 <= body.find("digit bi, bit;") < body.find("CHECK_BINOP(v, w);"), "long_pow must declare exponent digit temporaries with its opening locals"
|
|
assert "digit bi = i ?" not in body and "digit bit;" not in body[body.find("i = Py_SIZE(b);"):], "long_pow must not declare digit temporaries after executable statements"
|
|
ordered = ("i = Py_SIZE(b);", "bi = i ? b->ob_digit[i-1] : 0;", "if (i <= 1 && bi <= 3)")
|
|
positions = [body.find(token) for token in ordered]
|
|
assert all(position >= 0 for position in positions) and positions == sorted(positions), "long_pow must preserve exponent digit setup and branch ordering"
|
|
print("longobject long_pow C89 declarations and exponent setup order: PASS")
|
|
branch_start = body.find("else {\n int pending;\n int blen;")
|
|
branch_end = body.find("\n }\n\n if (negativeOutput", branch_start)
|
|
assert branch_start >= 0 and branch_end > branch_start, "long_pow k-ary branch boundaries not found"
|
|
branch = body[branch_start:branch_end]
|
|
assert branch.find("int pending;") < branch.find("Py_INCREF(a);") and branch.find("int blen;") < branch.find("Py_INCREF(a);"), "long_pow pending and bit-length locals must be declared at branch entry"
|
|
assert "int pending = 0, blen = 0;" not in branch, "long_pow pending locals must be declared separately from their original initialization point"
|
|
ordered = ("Py_CLEAR(a2);", "pending = 0;", "blen = 0;", "#define ABSORB_PENDING")
|
|
positions = [branch.find(token) for token in ordered]
|
|
assert all(position >= 0 for position in positions) and positions == sorted(positions), "long_pow must preserve pending initialization after table setup and before the sliding-window macro"
|
|
print("longobject long_pow C89 pending declarations and initialization order: PASS")
|
|
|
|
match = re.search(r"static int\s+divmod_shift\(PyObject \*shiftby, Py_ssize_t \*wordshift, digit \*remshift\)\s*\{(?P<body>.*?)\n\}", source, re.S)
|
|
assert match, "divmod_shift definition not found"
|
|
body = match.group("body")
|
|
assert re.match(r"\s*Py_ssize_t lshiftby;\s*PyLongObject \*wordshift_obj;", body), "divmod_shift must declare both conversion temporaries at function entry"
|
|
assert "Py_ssize_t lshiftby =" not in body and "PyLongObject *wordshift_obj =" not in body, "divmod_shift must preserve original conversion points as assignments"
|
|
ordered = ("assert(PyLong_Check(shiftby));", "assert(Py_SIZE(shiftby) >= 0);", "lshiftby = PyLong_AsSsize_t((PyObject *)shiftby);", "wordshift_obj = divrem1((PyLongObject *)shiftby, PyLong_SHIFT, remshift);", "*wordshift = PyLong_AsSsize_t((PyObject *)wordshift_obj);")
|
|
positions = [body.find(token) for token in ordered]
|
|
assert all(position >= 0 for position in positions) and positions == sorted(positions), "divmod_shift must preserve validation, conversion, fallback division, and result extraction order"
|
|
print("longobject divmod_shift C89 locals and overflow-fallback order: PASS")
|
|
|
|
match = re.search(r"static PyObject \*\s*long_rshift1\(PyLongObject \*a, Py_ssize_t wordshift, digit remshift\)\s*\{(?P<body>.*?)\n\}", source, re.S)
|
|
assert match, "long_rshift1 definition not found"
|
|
body = match.group("body")
|
|
assert "digit sticky = 0;" not in body and "for (Py_ssize_t j =" not in body and "for (Py_ssize_t i =" not in body, "long_rshift1 must not declare locals in executable branches or C99 for initializers"
|
|
negative_matches = list(re.finditer(r"if \(a_negative\)\s*\{(?P<body>.*?)\n \}", body, re.S))
|
|
assert negative_matches, "long_rshift1 negative-input branch not found"
|
|
negative_body = negative_matches[-1].group("body")
|
|
assert negative_body.find("digit sticky;") < negative_body.find("Py_SET_SIZE(z, -newsize);"), "long_rshift1 must declare sticky before branch statements"
|
|
assert negative_body.find("sticky = 0;") > negative_body.find("Py_SET_SIZE(z, -newsize);"), "long_rshift1 must initialize sticky after setting the result sign"
|
|
assert "{\n Py_ssize_t j;\n for (j = 0; j < wordshift; j++)" in negative_body, "long_rshift1 must keep the sticky-scan counter block-scoped with C89 declarations"
|
|
assert "{\n Py_ssize_t i, j;\n i = 0;\n j = wordshift + 1;\n for (; j < size_a; i++, j++)" in body, "long_rshift1 must keep copy-loop counters block-scoped and preserve initial values"
|
|
print("longobject long_rshift1 C89 locals and shifted-digit order: PASS")
|
|
|
|
for name, op in (("long_and", "&"), ("long_xor", "^"), ("long_or", "|")):
|
|
match = re.search(r"static PyObject \*\s*" + name + r"\(PyObject \*a, PyObject \*b\)\s*\{(?P<body>.*?)\n\}", source, re.S)
|
|
assert match, name + " definition not found"
|
|
body = match.group("body")
|
|
expected = """
|
|
PyLongObject *x;
|
|
PyLongObject *y;
|
|
CHECK_BINOP(a, b);
|
|
x = (PyLongObject*)a;
|
|
y = (PyLongObject*)b;
|
|
"""
|
|
assert body.split()[:len(expected.split())] == expected.split(), name + " must declare x/y before type checking, then cast and preserve the small-value fast path"
|
|
assert "PyLongObject *x =" not in body and "PyLongObject *y =" not in body, name + " must not declare cast locals after CHECK_BINOP"
|
|
assert "return long_bitwise(x, '" + op + "', y);" in body, name + " must retain the expected general bitwise operation"
|
|
print("longobject bitwise entry points C89 locals and dispatch order: PASS")
|
|
|
|
match = re.search(r"PyObject \*\s*_PyLong_DivmodNear\(PyObject \*a, PyObject \*b\)\s*\{(?P<body>.*?)\n\}", source, re.S)
|
|
assert match, "_PyLong_DivmodNear definition not found"
|
|
body = match.group("body")
|
|
assert re.match(r"\s*PyLongObject \*quo = NULL, \*rem = NULL;\s*PyObject \*twice_rem, \*result, \*temp;\s*PyObject \*one;\s*int quo_is_odd, quo_is_neg;\s*Py_ssize_t cmp;", body), "_PyLong_DivmodNear must declare one with the function's opening locals"
|
|
assert "PyObject *one =" not in body, "_PyLong_DivmodNear must preserve _PyLong_GetOne lookup at its original execution point"
|
|
ordered = ("if (!PyLong_Check(a) || !PyLong_Check(b))", "quo_is_neg =", "long_divrem(", "one = _PyLong_GetOne();", "twice_rem = long_lshift((PyObject *)rem, one);")
|
|
positions = [body.find(token) for token in ordered]
|
|
assert all(position >= 0 for position in positions) and positions == sorted(positions), "_PyLong_DivmodNear must preserve validation, divrem, singleton lookup, and shift order"
|
|
print("longobject _PyLong_DivmodNear C89 local and singleton lookup order: PASS")
|
|
|
|
match = re.search(r"static PyObject \*\s*int_bit_count_impl\(PyObject \*self\)\s*/\*.*?\*/\s*\{(?P<body>.*?)\n\}", source, re.S)
|
|
assert match, "int_bit_count_impl definition not found"
|
|
body = match.group("body")
|
|
expected = """
|
|
PyLongObject *z;
|
|
Py_ssize_t ndigits, bit_count, ndigits_fast, i;
|
|
PyObject *result;
|
|
assert(self != NULL);
|
|
assert(PyLong_Check(self));
|
|
z = (PyLongObject *)self;
|
|
ndigits = Py_ABS(Py_SIZE(z));
|
|
bit_count = 0;
|
|
"""
|
|
assert body.split()[:len(expected.split())] == expected.split(), "int_bit_count_impl must place declarations before assertions and preserve initialization order"
|
|
assert "Py_ssize_t ndigits =" not in body and "Py_ssize_t bit_count =" not in body and "for (Py_ssize_t i =" not in body, "int_bit_count_impl must not use late declarations or C99 loop variables"
|
|
assert "PyObject *result =" not in body and "PyObject *x =" not in body and "PyObject *y =" not in body, "int_bit_count_impl must initialize object locals at their original execution points"
|
|
assert "Py_ssize_t ndigits_fast =" not in body, "int_bit_count_impl must calculate the fast digit bound at the original point"
|
|
ordered = ("ndigits_fast = Py_MIN(ndigits, PY_SSIZE_T_MAX/PyLong_SHIFT);", "for (i = 0; i < ndigits_fast; i++)", "result = PyLong_FromSsize_t(bit_count);", "for (i = ndigits_fast; i < ndigits; i++)")
|
|
positions = [body.find(token) for token in ordered]
|
|
assert all(position >= 0 for position in positions) and positions == sorted(positions), "int_bit_count_impl must preserve fast and overflow-safe accumulation order"
|
|
loop = re.search(r"for \(i = ndigits_fast; i < ndigits; i\+\+\)\s*\{(?P<body>.*?)\n \}", body, re.S)
|
|
assert loop and re.match(r"\s*PyObject \*x;\s*PyObject \*y;\s*x = PyLong_FromLong\(popcount_digit\(z->ob_digit\[i\]\)\);", loop.group("body")), "int_bit_count_impl must declare per-digit temporaries before the loop body statements"
|
|
assert 0 <= loop.group("body").find("y = long_add(") < loop.group("body").find("Py_DECREF(x);"), "int_bit_count_impl must preserve accumulation before releasing the temporary digit count"
|
|
print("longobject int_bit_count_impl C89 declarations and accumulation order: PASS")
|