@@ -1333,6 +1333,153 @@ def test_modified_local_is_seen_by_optimized_code(self):
13331333 self .assertIs (type (s ), float )
13341334 self .assertEqual (s , 1024.0 )
13351335
1336+ def test_guard_type_version_removed (self ):
1337+ def thing (a ):
1338+ x = 0
1339+ for _ in range (100 ):
1340+ x += a .attr
1341+ x += a .attr
1342+ return x
1343+
1344+ class Foo :
1345+ attr = 1
1346+
1347+ res , ex = self ._run_with_optimizer (thing , Foo ())
1348+ opnames = list (iter_opnames (ex ))
1349+ self .assertIsNotNone (ex )
1350+ self .assertEqual (res , 200 )
1351+ guard_type_version_count = opnames .count ("_GUARD_TYPE_VERSION" )
1352+ self .assertEqual (guard_type_version_count , 1 )
1353+
1354+ def test_guard_type_version_removed_inlined (self ):
1355+ """
1356+ Verify that the guard type version if we have an inlined function
1357+ """
1358+
1359+ def fn ():
1360+ pass
1361+
1362+ def thing (a ):
1363+ x = 0
1364+ for _ in range (100 ):
1365+ x += a .attr
1366+ fn ()
1367+ x += a .attr
1368+ return x
1369+
1370+ class Foo :
1371+ attr = 1
1372+
1373+ res , ex = self ._run_with_optimizer (thing , Foo ())
1374+ opnames = list (iter_opnames (ex ))
1375+ self .assertIsNotNone (ex )
1376+ self .assertEqual (res , 200 )
1377+ guard_type_version_count = opnames .count ("_GUARD_TYPE_VERSION" )
1378+ self .assertEqual (guard_type_version_count , 1 )
1379+
1380+ def test_guard_type_version_not_removed (self ):
1381+ """
1382+ Verify that the guard type version is not removed if we modify the class
1383+ """
1384+
1385+ def thing (a ):
1386+ x = 0
1387+ for i in range (100 ):
1388+ x += a .attr
1389+ # for the first 90 iterations we set the attribute on this dummy function which shouldn't
1390+ # trigger the type watcher
1391+ # then after 90 it should trigger it and stop optimizing
1392+ # Note that the code needs to be in this weird form so it's optimized inline without any control flow
1393+ setattr ((Foo , Bar )[i < 90 ], "attr" , 2 )
1394+ x += a .attr
1395+ return x
1396+
1397+ class Foo :
1398+ attr = 1
1399+
1400+ class Bar :
1401+ pass
1402+
1403+ res , ex = self ._run_with_optimizer (thing , Foo ())
1404+ opnames = list (iter_opnames (ex ))
1405+
1406+ self .assertIsNotNone (ex )
1407+ self .assertEqual (res , 219 )
1408+ guard_type_version_count = opnames .count ("_GUARD_TYPE_VERSION" )
1409+ self .assertEqual (guard_type_version_count , 2 )
1410+
1411+
1412+ @unittest .expectedFailure
1413+ def test_guard_type_version_not_removed_escaping (self ):
1414+ """
1415+ Verify that the guard type version is not removed if have an escaping function
1416+ """
1417+
1418+ def thing (a ):
1419+ x = 0
1420+ for i in range (100 ):
1421+ x += a .attr
1422+ # eval should be escaping and so should cause optimization to stop and preserve both type versions
1423+ eval ("None" )
1424+ x += a .attr
1425+ return x
1426+
1427+ class Foo :
1428+ attr = 1
1429+ res , ex = self ._run_with_optimizer (thing , Foo ())
1430+ opnames = list (iter_opnames (ex ))
1431+ self .assertIsNotNone (ex )
1432+ self .assertEqual (res , 200 )
1433+ guard_type_version_count = opnames .count ("_GUARD_TYPE_VERSION" )
1434+ # Note: This will actually be 1 for noe
1435+ # https://github.com/python/cpython/pull/119365#discussion_r1626220129
1436+ self .assertEqual (guard_type_version_count , 2 )
1437+
1438+
1439+ def test_guard_type_version_executor_invalidated (self ):
1440+ """
1441+ Verify that the executor is invalided on a type change.
1442+ """
1443+
1444+ def thing (a ):
1445+ x = 0
1446+ for i in range (100 ):
1447+ x += a .attr
1448+ x += a .attr
1449+ return x
1450+
1451+ class Foo :
1452+ attr = 1
1453+
1454+ res , ex = self ._run_with_optimizer (thing , Foo ())
1455+ self .assertEqual (res , 200 )
1456+ self .assertIsNotNone (ex )
1457+ self .assertEqual (list (iter_opnames (ex )).count ("_GUARD_TYPE_VERSION" ), 1 )
1458+ self .assertTrue (ex .is_valid ())
1459+ Foo .attr = 0
1460+ self .assertFalse (ex .is_valid ())
1461+
1462+ def test_type_version_doesnt_segfault (self ):
1463+ """
1464+ Tests that setting a type version doesn't cause a segfault when later looking at the stack.
1465+ """
1466+
1467+ # Minimized from mdp.py benchmark
1468+
1469+ class A :
1470+ def __init__ (self ):
1471+ self .attr = {}
1472+
1473+ def method (self , arg ):
1474+ self .attr [arg ] = None
1475+
1476+ def fn (a ):
1477+ for _ in range (100 ):
1478+ (_ for _ in [])
1479+ (_ for _ in [a .method (None )])
1480+
1481+ fn (A ())
1482+
13361483
13371484if __name__ == "__main__" :
13381485 unittest .main ()
0 commit comments