@@ -140,8 +140,89 @@ def get_opnames(ex):
140140
141141@requires_specialization
142142@unittest .skipIf (Py_GIL_DISABLED , "optimizer not yet supported in free-threaded builds" )
143- @unittest .skipUnless (hasattr (_testinternalcapi , "get_optimizer" ) and
144- hasattr (_testinternalcapi , "new_uop_optimizer" ),
143+ @unittest .skipUnless (hasattr (_testinternalcapi , "get_optimizer" ),
144+ "Requires optimizer infrastructure" )
145+ class TestExecutorInvalidation (unittest .TestCase ):
146+
147+ def setUp (self ):
148+ self .old = _testinternalcapi .get_optimizer ()
149+ self .opt = _testinternalcapi .new_counter_optimizer ()
150+ _testinternalcapi .set_optimizer (self .opt )
151+
152+ def tearDown (self ):
153+ _testinternalcapi .set_optimizer (self .old )
154+
155+ def test_invalidate_object (self ):
156+ # Generate a new set of functions at each call
157+ ns = {}
158+ func_src = "\n " .join (
159+ f"""
160+ def f{ n } ():
161+ for _ in range(1000):
162+ pass
163+ """ for n in range (5 )
164+ )
165+ exec (textwrap .dedent (func_src ), ns , ns )
166+ funcs = [ ns [f'f{ n } ' ] for n in range (5 )]
167+ objects = [object () for _ in range (5 )]
168+
169+ for f in funcs :
170+ f ()
171+ executors = [get_first_executor (f ) for f in funcs ]
172+ # Set things up so each executor depends on the objects
173+ # with an equal or lower index.
174+ for i , exe in enumerate (executors ):
175+ self .assertTrue (exe .is_valid ())
176+ for obj in objects [:i + 1 ]:
177+ _testinternalcapi .add_executor_dependency (exe , obj )
178+ self .assertTrue (exe .is_valid ())
179+ # Assert that the correct executors are invalidated
180+ # and check that nothing crashes when we invalidate
181+ # an executor multiple times.
182+ for i in (4 ,3 ,2 ,1 ,0 ):
183+ _testinternalcapi .invalidate_executors (objects [i ])
184+ for exe in executors [i :]:
185+ self .assertFalse (exe .is_valid ())
186+ for exe in executors [:i ]:
187+ self .assertTrue (exe .is_valid ())
188+
189+ def test_uop_optimizer_invalidation (self ):
190+ # Generate a new function at each call
191+ ns = {}
192+ exec (textwrap .dedent ("""
193+ def f():
194+ for i in range(1000):
195+ pass
196+ """ ), ns , ns )
197+ f = ns ['f' ]
198+ opt = _testinternalcapi .new_uop_optimizer ()
199+ with temporary_optimizer (opt ):
200+ f ()
201+ exe = get_first_executor (f )
202+ self .assertIsNotNone (exe )
203+ self .assertTrue (exe .is_valid ())
204+ _testinternalcapi .invalidate_executors (f .__code__ )
205+ self .assertFalse (exe .is_valid ())
206+
207+ def test_sys__clear_internal_caches (self ):
208+ def f ():
209+ for _ in range (1000 ):
210+ pass
211+ opt = _testinternalcapi .new_uop_optimizer ()
212+ with temporary_optimizer (opt ):
213+ f ()
214+ exe = get_first_executor (f )
215+ self .assertIsNotNone (exe )
216+ self .assertTrue (exe .is_valid ())
217+ sys ._clear_internal_caches ()
218+ self .assertFalse (exe .is_valid ())
219+ exe = get_first_executor (f )
220+ self .assertIsNone (exe )
221+
222+
223+ @requires_specialization
224+ @unittest .skipIf (Py_GIL_DISABLED , "optimizer not yet supported in free-threaded builds" )
225+ @unittest .skipUnless (hasattr (_testinternalcapi , "get_optimizer" ),
145226 "Requires optimizer infrastructure" )
146227class TestExecutorInvalidation (unittest .TestCase ):
147228
@@ -589,8 +670,7 @@ def testfunc(n):
589670
590671@requires_specialization
591672@unittest .skipIf (Py_GIL_DISABLED , "optimizer not yet supported in free-threaded builds" )
592- @unittest .skipUnless (hasattr (_testinternalcapi , "get_optimizer" ) and
593- hasattr (_testinternalcapi , "new_uop_optimizer" ),
673+ @unittest .skipUnless (hasattr (_testinternalcapi , "get_optimizer" ),
594674 "Requires optimizer infrastructure" )
595675@unittest .skipIf (os .getenv ("PYTHON_UOPS_OPTIMIZE" ) == "0" , "Needs uop optimizer to run." )
596676class TestUopsOptimization (unittest .TestCase ):
0 commit comments