103 lines
5.5 KiB
Python
103 lines
5.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Guard C89 local ordering in the float32 packing fallback."""
|
|
import sys
|
|
from pathlib import Path
|
|
from typing import NoReturn
|
|
|
|
|
|
def fail(message: str) -> NoReturn:
|
|
print(message, file=sys.stderr)
|
|
raise SystemExit(1)
|
|
|
|
|
|
def extract_function(source: str, signature: str) -> str:
|
|
start = source.find(signature)
|
|
if start < 0:
|
|
fail(f"missing function signature: {signature}")
|
|
brace = source.find("{", start + len(signature))
|
|
if brace < 0:
|
|
fail("missing function body")
|
|
depth = 0
|
|
for end in range(brace, len(source)):
|
|
if source[end] == "{":
|
|
depth += 1
|
|
elif source[end] == "}":
|
|
depth -= 1
|
|
if depth == 0:
|
|
return source[brace + 1:end]
|
|
fail("unterminated function body")
|
|
|
|
|
|
source = Path(sys.argv[1]).read_text(encoding="utf-8")
|
|
body = extract_function(source, "PyFloat_Pack4(double x, char *data, int le)")
|
|
expected = """ else {
|
|
unsigned char s[sizeof(float)];
|
|
float y = (float)x;
|
|
int i, incr = 1;"""
|
|
if expected not in body:
|
|
fail("float32 fallback locals must be declared at the start of its block")
|
|
copy = body.find("memcpy(s, &y, sizeof(float));")
|
|
infinity_check = body.find("if (Py_IS_INFINITY(y) && !Py_IS_INFINITY(x))")
|
|
pack_loop = body.find("for (i = 0; i < 4; i++)")
|
|
if min(copy, infinity_check, pack_loop) < 0 or not infinity_check < copy < pack_loop:
|
|
fail("float32 fallback must preserve overflow-check/copy/packing order")
|
|
|
|
dealloc = extract_function(source, "_PyFloat_ExactDealloc(PyObject *obj)")
|
|
if " PyFloatObject *op;" not in dealloc or " PyFloatObject *op =" in dealloc:
|
|
fail("float exact deallocator op must be declared without initializer at entry")
|
|
if " struct _Py_float_state *state;" not in dealloc or " struct _Py_float_state *state =" in dealloc:
|
|
fail("float exact deallocator state must be declared without initializer at entry")
|
|
if not dealloc.find("assert(PyFloat_CheckExact(obj));") < dealloc.find("op = (PyFloatObject *)obj;") < dealloc.find("state = get_float_state();"):
|
|
fail("float exact deallocator must preserve assert/op/state initialization order")
|
|
|
|
as_double = extract_function(source, "PyFloat_AsDouble(PyObject *op)")
|
|
if " PyObject *res;" not in as_double or " double val;" not in as_double:
|
|
fail("PyFloat_AsDouble temporaries must be declared at function entry")
|
|
if "PyObject *res = _PyNumber_Index(op);" in as_double or "double val = PyLong_AsDouble(res);" in as_double:
|
|
fail("PyFloat_AsDouble must not redeclare its entry temporaries")
|
|
if not as_double.find("res = _PyNumber_Index(op);") < as_double.find("val = PyLong_AsDouble(res);") < as_double.find("Py_DECREF(res);"):
|
|
fail("PyFloat_AsDouble must preserve index-convert/decref order")
|
|
|
|
vectorcall = extract_function(source, "float_vectorcall(PyObject *type, PyObject * const*args,")
|
|
for declaration in (" Py_ssize_t nargs;", " PyObject *x;"):
|
|
if declaration not in vectorcall:
|
|
fail("float_vectorcall locals must be declared at function entry")
|
|
if "Py_ssize_t nargs =" in vectorcall or "PyObject *x =" in vectorcall:
|
|
fail("float_vectorcall locals must not be initialized at declaration")
|
|
if not vectorcall.find("nargs = PyVectorcall_NARGS(nargsf);") < vectorcall.find("x = nargs >= 1 ? args[0] : NULL;"):
|
|
fail("float_vectorcall must preserve nargs-before-argument selection order")
|
|
|
|
type_init = extract_function(source, "PyTypeObject PyFloat_Type =")
|
|
expected_slots = """ float_new, /* tp_new */
|
|
0, /* tp_free */
|
|
0, /* tp_is_gc */
|
|
0, /* tp_bases */
|
|
0, /* tp_mro */
|
|
0, /* tp_cache */
|
|
0, /* tp_subclasses */
|
|
0, /* tp_weaklist */
|
|
0, /* tp_del */
|
|
0, /* tp_version_tag */
|
|
0, /* tp_finalize */
|
|
(vectorcallfunc)float_vectorcall, /* tp_vectorcall */"""
|
|
if expected_slots not in type_init or ".tp_vectorcall =" in type_init:
|
|
fail("PyFloat_Type vectorcall must use C89 positional trailing slots")
|
|
|
|
state_init = extract_function(source, "_PyFloat_InitState(PyInterpreterState *interp)")
|
|
format_declaration = " float_format_type detected_double_format, detected_float_format;"
|
|
if format_declaration not in state_init:
|
|
fail("float format detection locals must be declared at function entry")
|
|
if format_declaration in state_init[state_init.find("if (!_Py_IsMainInterpreter(interp))"):]:
|
|
fail("float format detection locals must not be declared after a statement")
|
|
|
|
clinic = Path(sys.argv[2]).read_text(encoding="utf-8")
|
|
wrapper = extract_function(clinic, "float___getformat__(PyTypeObject *type, PyObject *arg)")
|
|
declaration = " Py_ssize_t typestr_length;"
|
|
if declaration not in wrapper:
|
|
fail("float.__getformat__ typestr_length must be declared at wrapper entry")
|
|
validation = wrapper.find("if (!PyUnicode_Check(arg))")
|
|
assignment = wrapper.find("typestr = PyUnicode_AsUTF8AndSize(arg, &typestr_length);")
|
|
if validation < 0 or assignment < validation or "Py_ssize_t typestr_length;" in wrapper[validation:]:
|
|
fail("Clinic wrapper must preserve type validation before string conversion")
|
|
print("floatobject C89 declarations and generated Clinic validation order: PASS")
|