@@ -1333,6 +1333,153 @@ def test_modified_local_is_seen_by_optimized_code(self):
1333
1333
self .assertIs (type (s ), float )
1334
1334
self .assertEqual (s , 1024.0 )
1335
1335
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
+
1336
1483
1337
1484
if __name__ == "__main__" :
1338
1485
unittest .main ()
0 commit comments