| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -6,9 +6,27 @@ | |||
| 6 | 6 | extern "C" { | |
| 7 | 7 | #endif | |
| 8 | 8 | ||
| 9 | + typedef struct _PyExecutorLinkListNode { | ||
| 10 | + struct _PyExecutorObject *next; | ||
| 11 | + struct _PyExecutorObject *previous; | ||
| 12 | + } _PyExecutorLinkListNode; | ||
| 13 | + | ||
| 14 | + | ||
| 15 | + /* Bloom filter with m = 256 | ||
| 16 | + * https://en.wikipedia.org/wiki/Bloom_filter */ | ||
| 17 | + #define BLOOM_FILTER_WORDS 8 | ||
| 18 | + | ||
| 19 | + typedef struct _bloom_filter { | ||
| 20 | + uint32_t bits[BLOOM_FILTER_WORDS]; | ||
| 21 | + } _PyBloomFilter; | ||
| 22 | + | ||
| 9 | 23 | typedef struct { | |
| 10 | 24 | uint8_t opcode; | |
| 11 | 25 | uint8_t oparg; | |
| 26 | + uint8_t valid; | ||
| 27 | + uint8_t linked; | ||
| 28 | + _PyBloomFilter bloom; | ||
| 29 | + _PyExecutorLinkListNode links; | ||
| 12 | 30 | } _PyVMData; | |
| 13 | 31 | ||
| 14 | 32 | typedef struct _PyExecutorObject { | |
@@ -45,6 +63,14 @@ _PyOptimizer_BackEdge(struct _PyInterpreterFrame *frame, _Py_CODEUNIT *src, _Py_ | |||
| 45 | 63 | ||
| 46 | 64 | extern _PyOptimizerObject _PyOptimizer_Default; | |
| 47 | 65 | ||
| 66 | + void _Py_ExecutorInit(_PyExecutorObject *, _PyBloomFilter *); | ||
| 67 | + void _Py_ExecutorClear(_PyExecutorObject *); | ||
| 68 | + void _Py_BloomFilter_Init(_PyBloomFilter *); | ||
| 69 | + void _Py_BloomFilter_Add(_PyBloomFilter *bloom, void *obj); | ||
| 70 | + PyAPI_FUNC(void) _Py_Executor_DependsOn(_PyExecutorObject *executor, void *obj); | ||
| 71 | + PyAPI_FUNC(void) _Py_Executors_InvalidateDependency(PyInterpreterState *interp, void *obj); | ||
| 72 | + extern void _Py_Executors_InvalidateAll(PyInterpreterState *interp); | ||
| 73 | + | ||
| 48 | 74 | /* For testing */ | |
| 49 | 75 | PyAPI_FUNC(PyObject *)PyUnstable_Optimizer_NewCounter(void); | |
| 50 | 76 | PyAPI_FUNC(PyObject *)PyUnstable_Optimizer_NewUOpOptimizer(void); | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -215,6 +215,7 @@ struct _is { | |||
| 215 | 215 | struct types_state types; | |
| 216 | 216 | struct callable_cache callable_cache; | |
| 217 | 217 | _PyOptimizerObject *optimizer; | |
| 218 | + _PyExecutorObject *executor_list_head; | ||
| 218 | 219 | uint16_t optimizer_resume_threshold; | |
| 219 | 220 | uint16_t optimizer_backedge_threshold; | |
| 220 | 221 | uint32_t next_func_version; | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2489,6 +2489,67 @@ def get_first_executor(func): | |||
| 2489 | 2489 | return None | |
| 2490 | 2490 | ||
| 2491 | 2491 | ||
| 2492 | + class TestExecutorInvalidation(unittest.TestCase): | ||
| 2493 | + | ||
| 2494 | + def setUp(self): | ||
| 2495 | + self.old = _testinternalcapi.get_optimizer() | ||
| 2496 | + self.opt = _testinternalcapi.get_counter_optimizer() | ||
| 2497 | + _testinternalcapi.set_optimizer(self.opt) | ||
| 2498 | + | ||
| 2499 | + def tearDown(self): | ||
| 2500 | + _testinternalcapi.set_optimizer(self.old) | ||
| 2501 | + | ||
| 2502 | + def test_invalidate_object(self): | ||
| 2503 | + # Generate a new set of functions at each call | ||
| 2504 | + ns = {} | ||
| 2505 | + func_src = "\n".join( | ||
| 2506 | + f""" | ||
| 2507 | + def f{n}(): | ||
| 2508 | + for _ in range(1000): | ||
| 2509 | + pass | ||
| 2510 | + """ for n in range(5) | ||
| 2511 | + ) | ||
| 2512 | + exec(textwrap.dedent(func_src), ns, ns) | ||
| 2513 | + funcs = [ ns[f'f{n}'] for n in range(5)] | ||
| 2514 | + objects = [object() for _ in range(5)] | ||
| 2515 | + | ||
| 2516 | + for f in funcs: | ||
| 2517 | + f() | ||
| 2518 | + executors = [get_first_executor(f) for f in funcs] | ||
| 2519 | + # Set things up so each executor depends on the objects | ||
| 2520 | + # with an equal or lower index. | ||
| 2521 | + for i, exe in enumerate(executors): | ||
| 2522 | + self.assertTrue(exe.valid) | ||
| 2523 | + for obj in objects[:i+1]: | ||
| 2524 | + _testinternalcapi.add_executor_dependency(exe, obj) | ||
| 2525 | + self.assertTrue(exe.valid) | ||
| 2526 | + # Assert that the correct executors are invalidated | ||
| 2527 | + # and check that nothing crashes when we invalidate | ||
| 2528 | + # an executor mutliple times. | ||
| 2529 | + for i in (4,3,2,1,0): | ||
| 2530 | + _testinternalcapi.invalidate_executors(objects[i]) | ||
| 2531 | + for exe in executors[i:]: | ||
| 2532 | + self.assertFalse(exe.valid) | ||
| 2533 | + for exe in executors[:i]: | ||
| 2534 | + self.assertTrue(exe.valid) | ||
| 2535 | + | ||
| 2536 | + def test_uop_optimizer_invalidation(self): | ||
| 2537 | + # Generate a new function at each call | ||
| 2538 | + ns = {} | ||
| 2539 | + exec(textwrap.dedent(""" | ||
| 2540 | + def f(): | ||
| 2541 | + for i in range(1000): | ||
| 2542 | + pass | ||
| 2543 | + """), ns, ns) | ||
| 2544 | + f = ns['f'] | ||
| 2545 | + opt = _testinternalcapi.get_uop_optimizer() | ||
| 2546 | + with temporary_optimizer(opt): | ||
| 2547 | + f() | ||
| 2548 | + exe = get_first_executor(f) | ||
| 2549 | + self.assertTrue(exe.valid) | ||
| 2550 | + _testinternalcapi.invalidate_executors(f.__code__) | ||
| 2551 | + self.assertFalse(exe.valid) | ||
| 2552 | + | ||
| 2492 | 2553 | class TestUops(unittest.TestCase): | |
| 2493 | 2554 | ||
| 2494 | 2555 | def test_basic_loop(self): | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1002,6 +1002,32 @@ get_executor(PyObject *self, PyObject *const *args, Py_ssize_t nargs) | |||
| 1002 | 1002 | return (PyObject *)PyUnstable_GetExecutor((PyCodeObject *)code, ioffset); | |
| 1003 | 1003 | } | |
| 1004 | 1004 | ||
| 1005 | + static PyObject * | ||
| 1006 | + add_executor_dependency(PyObject *self, PyObject *args) | ||
| 1007 | + { | ||
| 1008 | + PyObject *exec; | ||
| 1009 | + PyObject *obj; | ||
| 1010 | + if (!PyArg_ParseTuple(args, "OO", &exec, &obj)) { | ||
| 1011 | + return NULL; | ||
| 1012 | + } | ||
| 1013 | + /* No way to tell in general if exec is an executor, so we only accept | ||
| 1014 | + * counting_executor */ | ||
| 1015 | + if (strcmp(Py_TYPE(exec)->tp_name, "counting_executor")) { | ||
| 1016 | + PyErr_SetString(PyExc_TypeError, "argument must be a counting_executor"); | ||
| 1017 | + return NULL; | ||
| 1018 | + } | ||
| 1019 | + _Py_Executor_DependsOn((_PyExecutorObject *)exec, obj); | ||
| 1020 | + Py_RETURN_NONE; | ||
| 1021 | + } | ||
| 1022 | + | ||
| 1023 | + static PyObject * | ||
| 1024 | + invalidate_executors(PyObject *self, PyObject *obj) | ||
| 1025 | + { | ||
| 1026 | + PyInterpreterState *interp = PyInterpreterState_Get(); | ||
| 1027 | + _Py_Executors_InvalidateDependency(interp, obj); | ||
| 1028 | + Py_RETURN_NONE; | ||
| 1029 | + } | ||
| 1030 | + | ||
| 1005 | 1031 | static int _pending_callback(void *arg) | |
| 1006 | 1032 | { | |
| 1007 | 1033 | /* we assume the argument is callable object to which we own a reference */ | |
@@ -1565,6 +1591,8 @@ static PyMethodDef module_functions[] = { | |||
| 1565 | 1591 | {"get_executor", _PyCFunction_CAST(get_executor), METH_FASTCALL, NULL}, | |
| 1566 | 1592 | {"get_counter_optimizer", get_counter_optimizer, METH_NOARGS, NULL}, | |
| 1567 | 1593 | {"get_uop_optimizer", get_uop_optimizer, METH_NOARGS, NULL}, | |
| 1594 | + {"add_executor_dependency", add_executor_dependency, METH_VARARGS, NULL}, | ||
| 1595 | + {"invalidate_executors", invalidate_executors, METH_O, NULL}, | ||
| 1568 | 1596 | {"pending_threadfunc", _PyCFunction_CAST(pending_threadfunc), | |
| 1569 | 1597 | METH_VARARGS | METH_KEYWORDS}, | |
| 1570 | 1598 | {"pending_identify", pending_identify, METH_VARARGS, NULL}, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1582,6 +1582,7 @@ _Py_Instrument(PyCodeObject *code, PyInterpreterState *interp) | |||
| 1582 | 1582 | if (code->co_executors != NULL) { | |
| 1583 | 1583 | _PyCode_Clear_Executors(code); | |
| 1584 | 1584 | } | |
| 1585 | + _Py_Executors_InvalidateDependency(interp, code); | ||
| 1585 | 1586 | int code_len = (int)Py_SIZE(code); | |
| 1586 | 1587 | /* code->_co_firsttraceable >= code_len indicates | |
| 1587 | 1588 | * that no instrumentation can be inserted. | |
@@ -1803,6 +1804,7 @@ _PyMonitoring_SetEvents(int tool_id, _PyMonitoringEventSet events) | |||
| 1803 | 1804 | return -1; | |
| 1804 | 1805 | } | |
| 1805 | 1806 | set_global_version(interp, new_version); | |
| 1807 | + _Py_Executors_InvalidateAll(interp); | ||
| 1806 | 1808 | return instrument_all_executing_code_objects(interp); | |
| 1807 | 1809 | } | |
| 1808 | 1810 | ||
@@ -1832,6 +1834,7 @@ _PyMonitoring_SetLocalEvents(PyCodeObject *code, int tool_id, _PyMonitoringEvent | |||
| 1832 | 1834 | /* Force instrumentation update */ | |
| 1833 | 1835 | code->_co_instrumentation_version -= MONITORING_VERSION_INCREMENT; | |
| 1834 | 1836 | } | |
| 1837 | + _Py_Executors_InvalidateDependency(interp, code); | ||
| 1835 | 1838 | if (_Py_Instrument(code, interp)) { | |
| 1836 | 1839 | return -1; | |
| 1837 | 1840 | } | |
| Back | FazBrowse Home | New Git URL |
0 commit comments