Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
[mypyc] Fix separate=True under free-threading (Py_GIL_DISABLED)
Two gaps in the preceding commit that only showed up on the py314t CI
matrix (free-threading Python + compiled-mypy test harness):

1. The ensure_deps capsule call was only added to module_shim.tmpl,
   not to module_shim_no_gil_multiphase.tmpl. Free-threaded builds use
   the latter template, so cross-group exports tables were never
   populated on py314t and consumers called through NULL function
   pointers on the first cross-group invocation. Mirroring the
   non-GIL-disabled template fixes it.

2. The `_free_instance` slot (free-list for per-class fast allocation)
   was declared with needs_export=True, which puts it into the
   exports-table struct. Under Py_GIL_DISABLED, CPyThreadLocal
   expands to __thread, which can't legally appear inside a struct
   field (clang: "type name does not allow storage class to be
   specified"). The slot is only read/written by the class's own
   setup/dealloc code inside the defining group -- no cross-group
   access is needed -- so dropping needs_export is the right fix.
  • Loading branch information
VaggelisD committed Apr 23, 2026
commit 0269b7a46afecc9cffdcf4661b45e065356de631
8 changes: 5 additions & 3 deletions mypyc/codegen/emitclass.py
Original file line number Diff line number Diff line change
Expand Up @@ -228,9 +228,11 @@ def generate_class_reuse(
context = c_emitter.context
name = cl.name_prefix(c_emitter.names) + "_free_instance"
struct_name = cl.struct_name(c_emitter.names)
context.declarations[name] = HeaderDeclaration(
f"CPyThreadLocal {struct_name} *{name};", needs_export=True
)
# Not exported: the free-instance slot is only read/written by the class's
# own setup/dealloc code, which lives in the defining group. Exporting it
# also trips a C diagnostic under `Py_GIL_DISABLED`, where `CPyThreadLocal`
# expands to `__thread` and can't legally appear inside the exports struct.
context.declarations[name] = HeaderDeclaration(f"CPyThreadLocal {struct_name} *{name};")


def generate_class(cl: ClassIR, module: str, emitter: Emitter) -> None:
Expand Down
33 changes: 32 additions & 1 deletion mypyc/lib-rt/module_shim_no_gil_multiphase.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,38 @@
static int {modname}_exec(PyObject *module)
{{
PyObject *tmp;
if (!(tmp = PyImport_ImportModule("{libname}"))) return -1;
// Use ImportModuleLevel with a non-empty fromlist so Python returns the
// leaf submodule via sys.modules lookup, avoiding the top-down getattr
// walk done by PyImport_ImportModule. That walk fails when the shim runs
// during a parent package's __init__.py, because the parent's submodule
// attribute isn't set until after __init__ returns.
static PyObject *fromlist = NULL;
if (!fromlist) {{
fromlist = Py_BuildValue("(s)", "*");
if (!fromlist) return -1;
}}
tmp = PyImport_ImportModuleLevel("{libname}", NULL, NULL, fromlist, 0);
if (!tmp) return -1;
// Populate cross-group export tables lazily, just before we instantiate
// the per-module real init. Deferring this out of the shared lib's own
// PyInit keeps separate-mode compiled modules from recursively triggering
// sibling-package __init__.py mid-bootstrap.
PyObject *deps_capsule = PyObject_GetAttrString(tmp, "ensure_deps");
if (deps_capsule != NULL) {{
int (*deps_func)(void) = (int (*)(void))PyCapsule_GetPointer(
deps_capsule, "{libname}.ensure_deps");
Py_DECREF(deps_capsule);
if (deps_func == NULL) {{
Py_DECREF(tmp);
return -1;
}}
if (deps_func() < 0) {{
Py_DECREF(tmp);
return -1;
}}
}} else {{
PyErr_Clear();
}}
PyObject *capsule = PyObject_GetAttrString(tmp, "exec_{full_modname}");
Py_DECREF(tmp);
if (capsule == NULL) return -1;
Expand Down
Loading