You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
eudsl-llvmpy builds its nanobind extension with FREE_THREADED (Py_MOD_GIL_NOT_USED, see projects/eudsl-llvmpy/CMakeLists.txt:106), so it can be imported under a free-threaded (no-GIL) CPython. Several process-wide caches are plain unsynchronizedstd::unordered_maps. Concurrent writes and reads on them from multiple Python threads are data races.
This is flagged (not urgent): typical usage is register/populate at import, then run, so writes and reads don't actually overlap in practice, and the whole extension currently relies on nb::gil_scoped_acquire in the hot callbacks. But once we care about genuine multi-threaded use under the free-threaded build, these should be made safe together.
Affected registries
Python-pass registries (added in the Python-IR-passes stack, src/IR/Passes.cpp):
register_python_pass(...) writes; the PassBuilder::registerPipelineParsingCallback lambdas read them during run_passes(...). Concurrent register_python_pass (write) + run_passes (read) races.
Interim mitigation already in place: the register_python_pass docstring tells callers to register from a single thread (e.g. at import) before running pipelines concurrently.
Written by the caster-registration path, read on every value cast. Same shape of race. The Python-pass registries were intentionally modeled on this existing/accepted pattern.
Possible fixes (to decide later)
Wrap each registry in a small accessor guarded by a std::mutex (or std::shared_mutex for read-mostly access), or
Document + enforce a "populate-before-use, then immutable" contract (freeze after import), or
Use a concurrent map / copy-on-write snapshot for the read-heavy lookup paths.
Whatever we pick, it should be applied consistently to bothcasterMap() and the two Python-pass registries, since they share the pattern.
References
projects/eudsl-llvmpy/src/IR/Passes.cpp — pythonModulePassRegistry() / pythonFunctionPassRegistry() and the parsing-callback readers
Summary
eudsl-llvmpybuilds its nanobind extension withFREE_THREADED(Py_MOD_GIL_NOT_USED, seeprojects/eudsl-llvmpy/CMakeLists.txt:106), so it can be imported under a free-threaded (no-GIL) CPython. Several process-wide caches are plain unsynchronizedstd::unordered_maps. Concurrent writes and reads on them from multiple Python threads are data races.This is flagged (not urgent): typical usage is register/populate at import, then run, so writes and reads don't actually overlap in practice, and the whole extension currently relies on
nb::gil_scoped_acquirein the hot callbacks. But once we care about genuine multi-threaded use under the free-threaded build, these should be made safe together.Affected registries
Python-pass registries (added in the Python-IR-passes stack,
src/IR/Passes.cpp):register_python_pass(...)writes; thePassBuilder::registerPipelineParsingCallbacklambdas read them duringrun_passes(...). Concurrentregister_python_pass(write) +run_passes(read) races.Interim mitigation already in place: the
register_python_passdocstring tells callers to register from a single thread (e.g. at import) before running pipelines concurrently.Caster registry (
src/IR/Casters.cpp:20):Written by the caster-registration path, read on every value cast. Same shape of race. The Python-pass registries were intentionally modeled on this existing/accepted pattern.
Possible fixes (to decide later)
std::mutex(orstd::shared_mutexfor read-mostly access), orWhatever we pick, it should be applied consistently to both
casterMap()and the two Python-pass registries, since they share the pattern.References
projects/eudsl-llvmpy/src/IR/Passes.cpp—pythonModulePassRegistry()/pythonFunctionPassRegistry()and the parsing-callback readersprojects/eudsl-llvmpy/src/IR/Casters.cpp:20—casterMap()projects/eudsl-llvmpy/CMakeLists.txt:106—FREE_THREADEDbuild