114 lines
5.1 KiB
Python
114 lines
5.1 KiB
Python
#!/usr/bin/env python3
|
|
"""Compile the exact set types as C89 and verify every static slot."""
|
|
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
source_path = Path(sys.argv[1])
|
|
source = source_path.read_text()
|
|
initializers = {}
|
|
for name in ("PySet_Type", "PyFrozenSet_Type"):
|
|
match = re.search(r"PyTypeObject " + name + r" = \{.*?\n\};", source, re.S)
|
|
assert match, name + " initializer not found"
|
|
initializers[name] = match.group(0)
|
|
header = (source_path.parent.parent / "Include/cpython/object.h").read_text()
|
|
layout = re.search(r"struct _typeobject \{(.*?)\n\};", header, re.S)
|
|
assert layout, "PyTypeObject layout not found"
|
|
body = re.sub(r"/\*.*?\*/|//[^\n]*", "", layout.group(1), flags=re.S)
|
|
fields = re.findall(r"\b(tp_\w+)\b", body)
|
|
assert len(fields) == len(set(fields)) == 48, "review changed PyTypeObject layout"
|
|
expected = {
|
|
"tp_basicsize": "sizeof(PySetObject)",
|
|
"tp_dealloc": "(destructor)set_dealloc",
|
|
"tp_repr": "(reprfunc)set_repr",
|
|
"tp_as_sequence": "&set_as_sequence",
|
|
"tp_getattro": "PyObject_GenericGetAttr",
|
|
"tp_flags": "Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | Py_TPFLAGS_BASETYPE | _Py_TPFLAGS_MATCH_SELF",
|
|
"tp_traverse": "(traverseproc)set_traverse",
|
|
"tp_clear": "(inquiry)set_clear_internal",
|
|
"tp_richcompare": "(richcmpfunc)set_richcompare",
|
|
"tp_weaklistoffset": "offsetof(PySetObject, weakreflist)",
|
|
"tp_iter": "(getiterfunc)set_iter",
|
|
"tp_alloc": "PyType_GenericAlloc",
|
|
"tp_free": "PyObject_GC_Del",
|
|
}
|
|
assert expected.keys() <= set(fields)
|
|
# Use the real field declarations/order, with minimal stand-ins for dependencies.
|
|
harness = r'''
|
|
#include <assert.h>
|
|
#include <stddef.h>
|
|
#include <string.h>
|
|
typedef long Py_ssize_t;
|
|
typedef struct { int unused; } PyObject;
|
|
typedef struct { int unused; } PyAsyncMethods;
|
|
typedef struct { int unused; } PyNumberMethods;
|
|
typedef struct { int unused; } PySequenceMethods;
|
|
typedef struct { int unused; } PyMappingMethods;
|
|
typedef struct { int unused; } PyBufferProcs;
|
|
typedef struct { int unused; } PyMethodDef;
|
|
typedef struct { int unused; } PyMemberDef;
|
|
typedef struct { int unused; } PyGetSetDef;
|
|
typedef struct _typeobject PyTypeObject;
|
|
typedef struct { long head; PyObject *weakreflist; } PySetObject;
|
|
#define PyObject_VAR_HEAD long ob_refcnt; PyTypeObject *ob_type; long ob_size;
|
|
#define PyVarObject_HEAD_INIT(type, size) 1, type, size,
|
|
#define Py_TPFLAGS_DEFAULT 1UL
|
|
#define Py_TPFLAGS_HAVE_GC 2UL
|
|
#define Py_TPFLAGS_BASETYPE 4UL
|
|
#define _Py_TPFLAGS_MATCH_SELF 8UL
|
|
'''
|
|
for name in (
|
|
"destructor getattrfunc setattrfunc reprfunc hashfunc ternaryfunc "
|
|
"getattrofunc setattrofunc traverseproc inquiry richcmpfunc getiterfunc "
|
|
"iternextfunc descrgetfunc descrsetfunc initproc allocfunc newfunc "
|
|
"freefunc vectorcallfunc"
|
|
).split():
|
|
harness += f"typedef void (*{name})(void);\n"
|
|
harness += "struct _typeobject {" + body + "};\n"
|
|
for name in (
|
|
"set_new frozenset_new set_dealloc set_repr PyObject_HashNotImplemented "
|
|
"frozenset_hash PyObject_GenericGetAttr set_traverse set_clear_internal "
|
|
"set_richcompare set_iter set_init PyType_GenericAlloc PyObject_GC_Del "
|
|
"set_vectorcall frozenset_vectorcall"
|
|
).split():
|
|
harness += f"static void {name}(void) {{ }}\n"
|
|
harness += "static PyTypeObject PyType_Type;\n"
|
|
harness += "static PySequenceMethods set_as_sequence;\n"
|
|
for prefix in ("set", "frozenset"):
|
|
harness += f"static PyNumberMethods {prefix}_as_number;\n"
|
|
harness += f"static PyMethodDef {prefix}_methods[1];\n"
|
|
harness += f'static const char {prefix}_doc[] = "{prefix} documentation";\n'
|
|
harness += "\n".join(initializers.values()) + "\nint main(void)\n{\n"
|
|
for name, prefix in (("PySet_Type", "set"), ("PyFrozenSet_Type", "frozenset")):
|
|
values = dict(expected, tp_name=f'"{prefix}"',
|
|
tp_as_number=f"&{prefix}_as_number", tp_doc=f"{prefix}_doc",
|
|
tp_methods=f"{prefix}_methods", tp_new=f"{prefix}_new",
|
|
tp_vectorcall=f"{prefix}_vectorcall",
|
|
tp_hash="PyObject_HashNotImplemented" if prefix == "set" else "frozenset_hash",
|
|
tp_init="(initproc)set_init" if prefix == "set" else "0")
|
|
assert values.keys() <= set(fields)
|
|
harness += f" assert({name}.ob_refcnt == 1);\n"
|
|
harness += f" assert({name}.ob_type == &PyType_Type);\n"
|
|
harness += f" assert({name}.ob_size == 0);\n"
|
|
for field in fields:
|
|
value = values.get(field, "0")
|
|
if field in ("tp_name", "tp_doc"):
|
|
harness += f" assert(strcmp({name}.{field}, {value}) == 0);\n"
|
|
else:
|
|
harness += f" assert({name}.{field} == ({value}));\n"
|
|
harness += " return 0;\n}\n"
|
|
with tempfile.TemporaryDirectory(prefix="vc6-setobject-type-vectorcall-") as directory:
|
|
path = Path(directory)
|
|
(path / "test.c").write_text(harness)
|
|
result = subprocess.run([
|
|
"cc", "-std=c89", "-pedantic-errors", "-Wall", "-Wextra", "-Werror",
|
|
str(path / "test.c"), "-o", str(path / "test"),
|
|
])
|
|
if result.returncode:
|
|
sys.exit(result.returncode)
|
|
subprocess.run([str(path / "test")], check=True)
|
|
print("PySet_Type and PyFrozenSet_Type C89 compile and all 48 semantic field mappings each: PASS")
|