1010#endif
1111
1212#include " internal/pycore_pystate.h"
13+ #if PY_VERSION_HEX >= 0x030E0000
14+ #include " internal/pycore_interp_structs.h"
15+ #endif
1316
1417#include " cinderx/Common/audit.h"
1518#include " cinderx/Common/code.h"
@@ -761,6 +764,14 @@ FlagProcessor initFlagProcessor() {
761764 getMutableConfig ().specialized_opcodes ,
762765 " JIT specialized opcodes or to fall back to their generic counterparts." );
763766
767+ #if PY_VERSION_HEX >= 0x030C0000
768+ flag_processor.addOption (
769+ " jit-support-monitoring" ,
770+ " PYTHONJITSUPPORTMONITORING" ,
771+ getMutableConfig ().support_monitoring ,
772+ " Support monitoring (e.g. debugging/profiling)" );
773+ #endif
774+
764775 flag_processor.setFlags (PySys_GetXOptions ());
765776
766777 // T198250666: Bit of a hack but this makes other things easier. In 3.12 all
@@ -1294,17 +1305,9 @@ bool deoptFunc(BorrowedRef<PyFunctionObject> func) {
12941305 return false ;
12951306}
12961307
1297- PyObject* disable_jit (PyObject* /* self */ , PyObject* args, PyObject* kwargs) {
1298- int deopt_all = 0 ;
1299-
1300- const char * keywords[] = {" deopt_all" , nullptr };
1301-
1302- if (!PyArg_ParseTupleAndKeywords (
1303- args, kwargs, " |p" , const_cast <char **>(keywords), &deopt_all)) {
1304- return nullptr ;
1305- }
1308+ void disable_jit_impl (bool deopt_all) {
13061309 if (jitCtx () == nullptr ) {
1307- Py_RETURN_NONE ;
1310+ return ;
13081311 }
13091312
13101313 if (deopt_all) {
@@ -1325,19 +1328,32 @@ PyObject* disable_jit(PyObject* /* self */, PyObject* args, PyObject* kwargs) {
13251328 getMutableConfig ().state = State::kPaused ;
13261329 JIT_DLOG (" Disabled the JIT" );
13271330 }
1331+ }
1332+
1333+ PyObject* disable_jit (PyObject* /* self */ , PyObject* args, PyObject* kwargs) {
1334+ int deopt_all = 0 ;
1335+
1336+ const char * keywords[] = {" deopt_all" , nullptr };
1337+
1338+ if (!PyArg_ParseTupleAndKeywords (
1339+ args, kwargs, " |p" , const_cast <char **>(keywords), &deopt_all)) {
1340+ return nullptr ;
1341+ }
1342+
1343+ disable_jit_impl (deopt_all);
13281344
13291345 Py_RETURN_NONE;
13301346}
13311347
1332- PyObject* enable_jit (PyObject* /* self */ , PyObject* /* arg */ ) {
1348+ bool enable_jit_impl ( ) {
13331349 if (jitCtx () == nullptr ) {
13341350 PyErr_SetString (
13351351 PyExc_RuntimeError,
13361352 " Trying to re-enable the JIT but the JIT context is missing" );
1337- return nullptr ;
1353+ return false ;
13381354 }
13391355 if (isJitUsable ()) {
1340- Py_RETURN_NONE ;
1356+ return true ;
13411357 }
13421358
13431359 size_t count = 0 ;
@@ -1350,9 +1366,69 @@ PyObject* enable_jit(PyObject* /* self */, PyObject* /* arg */) {
13501366
13511367 JIT_DLOG (" Re-enabled the JIT and re-optimized {} functions" , count);
13521368
1369+ return true ;
1370+ }
1371+
1372+ PyObject* enable_jit (PyObject* /* self */ , PyObject* /* arg */ ) {
1373+ if (!enable_jit_impl ()) {
1374+ return nullptr ;
1375+ }
13531376 Py_RETURN_NONE;
13541377}
13551378
1379+ #if PY_VERSION_HEX >= 0x030C0000
1380+
1381+ // Check if sys.monitoring has any active callbacks registered.
1382+ bool hasRegisteredMonitoringCallbacks () {
1383+ auto is = PyInterpreterState_Get ();
1384+ for (int tool_id = 0 ; tool_id < PY_MONITORING_TOOL_IDS; ++tool_id) {
1385+ for (int event_id = 0 ; event_id < _PY_MONITORING_EVENTS; ++event_id) {
1386+ BorrowedRef<> entry = is->monitoring_callables [tool_id][event_id];
1387+ if (entry != nullptr && !Py_IsNone (entry)) {
1388+ return true ;
1389+ }
1390+ }
1391+ }
1392+ return false ;
1393+ }
1394+
1395+ // Patched version of sys.monitoring.register_callback().
1396+ // Intercepts callback registration/deregistration to disable/enable the JIT
1397+ // This is to handle debuggers/profilers attaching or detaching.
1398+ PyObject* patched_sys_monitoring_register_callback (
1399+ PyObject* /* self */ ,
1400+ PyObject* const * args,
1401+ Py_ssize_t nargs) {
1402+ auto mod_state = cinderx::getModuleState ();
1403+ BorrowedRef<> original =
1404+ mod_state->getOriginalSysMonitoringRegisterCallback ();
1405+ JIT_CHECK (
1406+ original != nullptr ,
1407+ " Expecting to have sys.monitoring.register_callback already saved" );
1408+
1409+ // Run the original function first
1410+ PyObject* result =
1411+ PyObject_Vectorcall (original, args, nargs, nullptr /* kwnames */ );
1412+ if (result == nullptr ) {
1413+ return nullptr ;
1414+ }
1415+
1416+ // Check if we need to enable or disable the JIT based on whether any
1417+ // monitoring callbacks are still registered.
1418+ if (hasRegisteredMonitoringCallbacks ()) {
1419+ disable_jit_impl (true /* deopt_all */ );
1420+ } else {
1421+ if (!enable_jit_impl ()) {
1422+ Py_DECREF (result);
1423+ return nullptr ;
1424+ }
1425+ }
1426+
1427+ return result;
1428+ }
1429+
1430+ #endif // PY_VERSION_HEX >= 0x030C0000
1431+
13561432void compile_after_n_calls_impl (uint32_t calls) {
13571433 getMutableConfig ().compile_after_n_calls = calls;
13581434
@@ -2395,6 +2471,70 @@ PyObject* after_fork_child(PyObject*, PyObject*) {
23952471 Py_RETURN_NONE;
23962472}
23972473
2474+ // Patch sys.monitoring.register_callback to intercept debugger/profiler
2475+ // attachment.
2476+ void patchSysMonitoringFunctions (PyObject* cinderjit_module) {
2477+ #if PY_VERSION_HEX >= 0x030C0000
2478+ BorrowedRef<> monitoring = PySys_GetObject (" monitoring" );
2479+ if (monitoring == nullptr ) {
2480+ JIT_DLOG (" sys.monitoring not found, skipping JIT monitoring integration" );
2481+ return ;
2482+ }
2483+
2484+ Ref<> original =
2485+ Ref<>::steal (PyObject_GetAttrString (monitoring, " register_callback" ));
2486+ if (original == nullptr ) {
2487+ PyErr_Clear ();
2488+ JIT_DLOG (
2489+ " sys.monitoring.register_callback not found, skipping JIT monitoring "
2490+ " integration" );
2491+ return ;
2492+ }
2493+
2494+ auto mod_state = cinderx::getModuleState ();
2495+ mod_state->setOriginalSysMonitoringRegisterCallback (original);
2496+
2497+ Ref<> patched_func = Ref<>::steal (PyObject_GetAttrString (
2498+ cinderjit_module, " patched_sys_monitoring_register_callback" ));
2499+ if (patched_func == nullptr ) {
2500+ JIT_LOG (
2501+ " Failed to get patched_sys_monitoring_register_callback from cinderjit "
2502+ " module" );
2503+ PyErr_Clear ();
2504+ return ;
2505+ }
2506+
2507+ if (PyObject_SetAttrString (monitoring, " register_callback" , patched_func) <
2508+ 0 ) {
2509+ JIT_LOG (" Failed to patch sys.monitoring.register_callback" );
2510+ PyErr_Clear ();
2511+ return ;
2512+ }
2513+
2514+ JIT_DLOG (" Successfully patched sys.monitoring.register_callback" );
2515+ #endif // PY_VERSION_HEX >= 0x030C0000
2516+ }
2517+
2518+ void restoreSysMonitoringRegisterCallback () {
2519+ #if PY_VERSION_HEX >= 0x030C0000
2520+ auto mod_state = cinderx::getModuleState ();
2521+ BorrowedRef<> original =
2522+ mod_state->getOriginalSysMonitoringRegisterCallback ();
2523+ if (original == nullptr ) {
2524+ return ;
2525+ }
2526+
2527+ BorrowedRef<> monitoring = PySys_GetObject (" monitoring" );
2528+ if (monitoring == nullptr ) {
2529+ return ;
2530+ }
2531+
2532+ if (PyObject_SetAttrString (monitoring, " register_callback" , original) < 0 ) {
2533+ PyErr_Clear ();
2534+ }
2535+ #endif // PY_VERSION_HEX >= 0x030C0000
2536+ }
2537+
23982538PyMethodDef jit_methods[] = {
23992539 {" disable" ,
24002540 reinterpret_cast <PyCFunction>(disable_jit),
@@ -2408,6 +2548,14 @@ PyMethodDef jit_methods[] = {
24082548 PyDoc_STR (
24092549 " Re-enable the JIT and re-attach compiled onto previously "
24102550 " JIT-compiled functions." )},
2551+ #if PY_VERSION_HEX >= 0x030C0000
2552+ {" patched_sys_monitoring_register_callback" ,
2553+ _PyCFunction_CAST (patched_sys_monitoring_register_callback),
2554+ METH_FASTCALL,
2555+ PyDoc_STR (
2556+ " Patched version of sys.monitoring.register_callback that "
2557+ " disables/enables the JIT when debuggers/profilers attach/detach." )},
2558+ #endif
24112559 {" auto" ,
24122560 auto_jit,
24132561 METH_NOARGS,
@@ -3078,6 +3226,12 @@ int initialize() {
30783226 return -1 ;
30793227 }
30803228
3229+ #if PY_VERSION_HEX >= 0x030C0000
3230+ if (getConfig ().support_monitoring ) {
3231+ patchSysMonitoringFunctions (mod);
3232+ }
3233+ #endif
3234+
30813235 getMutableConfig ().state = State::kRunning ;
30823236
30833237 mod_state->setJitList (std::move (jit_list));
@@ -3127,6 +3281,8 @@ void finalize() {
31273281
31283282 g_aot_ctx.destroy ();
31293283
3284+ restoreSysMonitoringRegisterCallback ();
3285+
31303286 getMutableConfig ().state = State::kNotInitialized ;
31313287}
31323288
0 commit comments