gh-132775: Add _PyCode_GetVarCounts() (gh-133128)

This helper is useful in a variety of ways, including in demonstrating how the different counts relate to one another.

It will be used in a later change to help identify if a function is "stateless", meaning it doesn't have any free vars or globals.

Note that a majority of this change is tests.
This commit is contained in:
Eric Snow 2025-04-30 12:19:20 -06:00 committed by GitHub
parent 26c0248b54
commit 94b4fcd806
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 689 additions and 0 deletions

View file

@ -999,6 +999,172 @@ get_co_localskinds(PyObject *self, PyObject *arg)
return kinds;
}
static PyObject *
get_code_var_counts(PyObject *self, PyObject *_args, PyObject *_kwargs)
{
PyThreadState *tstate = _PyThreadState_GET();
PyObject *codearg;
PyObject *globalnames = NULL;
PyObject *attrnames = NULL;
PyObject *globalsns = NULL;
PyObject *builtinsns = NULL;
static char *kwlist[] = {"code", "globalnames", "attrnames", "globalsns",
"builtinsns", NULL};
if (!PyArg_ParseTupleAndKeywords(_args, _kwargs,
"O|OOO!O!:get_code_var_counts", kwlist,
&codearg, &globalnames, &attrnames,
&PyDict_Type, &globalsns, &PyDict_Type, &builtinsns))
{
return NULL;
}
if (PyFunction_Check(codearg)) {
if (globalsns == NULL) {
globalsns = PyFunction_GET_GLOBALS(codearg);
}
if (builtinsns == NULL) {
builtinsns = PyFunction_GET_BUILTINS(codearg);
}
codearg = PyFunction_GET_CODE(codearg);
}
else if (!PyCode_Check(codearg)) {
PyErr_SetString(PyExc_TypeError,
"argument must be a code object or a function");
return NULL;
}
PyCodeObject *code = (PyCodeObject *)codearg;
_PyCode_var_counts_t counts = {0};
_PyCode_GetVarCounts(code, &counts);
if (_PyCode_SetUnboundVarCounts(
tstate, code, &counts, globalnames, attrnames,
globalsns, builtinsns) < 0)
{
return NULL;
}
#define SET_COUNT(DICT, STRUCT, NAME) \
do { \
PyObject *count = PyLong_FromLong(STRUCT.NAME); \
int res = PyDict_SetItemString(DICT, #NAME, count); \
Py_DECREF(count); \
if (res < 0) { \
goto error; \
} \
} while (0)
PyObject *locals = NULL;
PyObject *args = NULL;
PyObject *cells = NULL;
PyObject *hidden = NULL;
PyObject *unbound = NULL;
PyObject *globals = NULL;
PyObject *countsobj = PyDict_New();
if (countsobj == NULL) {
return NULL;
}
SET_COUNT(countsobj, counts, total);
// locals
locals = PyDict_New();
if (locals == NULL) {
goto error;
}
if (PyDict_SetItemString(countsobj, "locals", locals) < 0) {
goto error;
}
SET_COUNT(locals, counts.locals, total);
// locals.args
args = PyDict_New();
if (args == NULL) {
goto error;
}
if (PyDict_SetItemString(locals, "args", args) < 0) {
goto error;
}
SET_COUNT(args, counts.locals.args, total);
SET_COUNT(args, counts.locals.args, numposonly);
SET_COUNT(args, counts.locals.args, numposorkw);
SET_COUNT(args, counts.locals.args, numkwonly);
SET_COUNT(args, counts.locals.args, varargs);
SET_COUNT(args, counts.locals.args, varkwargs);
// locals.numpure
SET_COUNT(locals, counts.locals, numpure);
// locals.cells
cells = PyDict_New();
if (cells == NULL) {
goto error;
}
if (PyDict_SetItemString(locals, "cells", cells) < 0) {
goto error;
}
SET_COUNT(cells, counts.locals.cells, total);
SET_COUNT(cells, counts.locals.cells, numargs);
SET_COUNT(cells, counts.locals.cells, numothers);
// locals.hidden
hidden = PyDict_New();
if (hidden == NULL) {
goto error;
}
if (PyDict_SetItemString(locals, "hidden", hidden) < 0) {
goto error;
}
SET_COUNT(hidden, counts.locals.hidden, total);
SET_COUNT(hidden, counts.locals.hidden, numpure);
SET_COUNT(hidden, counts.locals.hidden, numcells);
// numfree
SET_COUNT(countsobj, counts, numfree);
// unbound
unbound = PyDict_New();
if (unbound == NULL) {
goto error;
}
if (PyDict_SetItemString(countsobj, "unbound", unbound) < 0) {
goto error;
}
SET_COUNT(unbound, counts.unbound, total);
SET_COUNT(unbound, counts.unbound, numattrs);
SET_COUNT(unbound, counts.unbound, numunknown);
// unbound.globals
globals = PyDict_New();
if (globals == NULL) {
goto error;
}
if (PyDict_SetItemString(unbound, "globals", globals) < 0) {
goto error;
}
SET_COUNT(globals, counts.unbound.globals, total);
SET_COUNT(globals, counts.unbound.globals, numglobal);
SET_COUNT(globals, counts.unbound.globals, numbuiltin);
SET_COUNT(globals, counts.unbound.globals, numunknown);
#undef SET_COUNT
Py_DECREF(locals);
Py_DECREF(args);
Py_DECREF(cells);
Py_DECREF(hidden);
Py_DECREF(unbound);
Py_DECREF(globals);
return countsobj;
error:
Py_DECREF(countsobj);
Py_XDECREF(locals);
Py_XDECREF(args);
Py_XDECREF(cells);
Py_XDECREF(hidden);
Py_XDECREF(unbound);
Py_XDECREF(globals);
return NULL;
}
static PyObject *
jit_enabled(PyObject *self, PyObject *arg)
{
@ -2120,6 +2286,8 @@ static PyMethodDef module_functions[] = {
{"code_returns_only_none", code_returns_only_none, METH_O, NULL},
{"get_co_framesize", get_co_framesize, METH_O, NULL},
{"get_co_localskinds", get_co_localskinds, METH_O, NULL},
{"get_code_var_counts", _PyCFunction_CAST(get_code_var_counts),
METH_VARARGS | METH_KEYWORDS, NULL},
{"jit_enabled", jit_enabled, METH_NOARGS, NULL},
#ifdef _Py_TIER2
{"add_executor_dependency", add_executor_dependency, METH_VARARGS, NULL},