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