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

Skip to content

Commit 0269b7a

Browse files
committed
[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.
1 parent 7ab4c16 commit 0269b7a

2 files changed

Lines changed: 37 additions & 4 deletions

File tree

mypyc/codegen/emitclass.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -228,9 +228,11 @@ def generate_class_reuse(
228228
context = c_emitter.context
229229
name = cl.name_prefix(c_emitter.names) + "_free_instance"
230230
struct_name = cl.struct_name(c_emitter.names)
231-
context.declarations[name] = HeaderDeclaration(
232-
f"CPyThreadLocal {struct_name} *{name};", needs_export=True
233-
)
231+
# Not exported: the free-instance slot is only read/written by the class's
232+
# own setup/dealloc code, which lives in the defining group. Exporting it
233+
# also trips a C diagnostic under `Py_GIL_DISABLED`, where `CPyThreadLocal`
234+
# expands to `__thread` and can't legally appear inside the exports struct.
235+
context.declarations[name] = HeaderDeclaration(f"CPyThreadLocal {struct_name} *{name};")
234236

235237

236238
def generate_class(cl: ClassIR, module: str, emitter: Emitter) -> None:

mypyc/lib-rt/module_shim_no_gil_multiphase.tmpl

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,38 @@
33
static int {modname}_exec(PyObject *module)
44
{{
55
PyObject *tmp;
6-
if (!(tmp = PyImport_ImportModule("{libname}"))) return -1;
6+
// Use ImportModuleLevel with a non-empty fromlist so Python returns the
7+
// leaf submodule via sys.modules lookup, avoiding the top-down getattr
8+
// walk done by PyImport_ImportModule. That walk fails when the shim runs
9+
// during a parent package's __init__.py, because the parent's submodule
10+
// attribute isn't set until after __init__ returns.
11+
static PyObject *fromlist = NULL;
12+
if (!fromlist) {{
13+
fromlist = Py_BuildValue("(s)", "*");
14+
if (!fromlist) return -1;
15+
}}
16+
tmp = PyImport_ImportModuleLevel("{libname}", NULL, NULL, fromlist, 0);
17+
if (!tmp) return -1;
18+
// Populate cross-group export tables lazily, just before we instantiate
19+
// the per-module real init. Deferring this out of the shared lib's own
20+
// PyInit keeps separate-mode compiled modules from recursively triggering
21+
// sibling-package __init__.py mid-bootstrap.
22+
PyObject *deps_capsule = PyObject_GetAttrString(tmp, "ensure_deps");
23+
if (deps_capsule != NULL) {{
24+
int (*deps_func)(void) = (int (*)(void))PyCapsule_GetPointer(
25+
deps_capsule, "{libname}.ensure_deps");
26+
Py_DECREF(deps_capsule);
27+
if (deps_func == NULL) {{
28+
Py_DECREF(tmp);
29+
return -1;
30+
}}
31+
if (deps_func() < 0) {{
32+
Py_DECREF(tmp);
33+
return -1;
34+
}}
35+
}} else {{
36+
PyErr_Clear();
37+
}}
738
PyObject *capsule = PyObject_GetAttrString(tmp, "exec_{full_modname}");
839
Py_DECREF(tmp);
940
if (capsule == NULL) return -1;

0 commit comments

Comments
 (0)