@@ -1601,6 +1601,125 @@ def test_modified_local_is_seen_by_optimized_code(self):
16011601 self .assertIs (type (s ), float )
16021602 self .assertEqual (s , 1024.0 )
16031603
1604+ def test_guard_type_version_removed (self ):
1605+ def thing (a ):
1606+ x = 0
1607+ for _ in range (TIER2_THRESHOLD ):
1608+ x += a .attr
1609+ x += a .attr
1610+ return x
1611+
1612+ class Foo :
1613+ attr = 1
1614+
1615+ res , ex = self ._run_with_optimizer (thing , Foo ())
1616+ opnames = list (iter_opnames (ex ))
1617+ self .assertIsNotNone (ex )
1618+ self .assertEqual (res , TIER2_THRESHOLD * 2 )
1619+ guard_type_version_count = opnames .count ("_GUARD_TYPE_VERSION" )
1620+ self .assertEqual (guard_type_version_count , 1 )
1621+
1622+ def test_guard_type_version_removed_inlined (self ):
1623+ """
1624+ Verify that the guard type version if we have an inlined function
1625+ """
1626+
1627+ def fn ():
1628+ pass
1629+
1630+ def thing (a ):
1631+ x = 0
1632+ for _ in range (TIER2_THRESHOLD ):
1633+ x += a .attr
1634+ fn ()
1635+ x += a .attr
1636+ return x
1637+
1638+ class Foo :
1639+ attr = 1
1640+
1641+ res , ex = self ._run_with_optimizer (thing , Foo ())
1642+ opnames = list (iter_opnames (ex ))
1643+ self .assertIsNotNone (ex )
1644+ self .assertEqual (res , TIER2_THRESHOLD * 2 )
1645+ guard_type_version_count = opnames .count ("_GUARD_TYPE_VERSION" )
1646+ self .assertEqual (guard_type_version_count , 1 )
1647+
1648+ def test_guard_type_version_removed_invalidation (self ):
1649+
1650+ def thing (a ):
1651+ x = 0
1652+ for i in range (TIER2_THRESHOLD + 1 ):
1653+ x += a .attr
1654+ # The first TIER2_THRESHOLD iterations we set the attribute on
1655+ # this dummy class, which shouldn't trigger the type watcher.
1656+ # Note that the code needs to be in this weird form so it's
1657+ # optimized inline without any control flow:
1658+ setattr ((Bar , Foo )[i == TIER2_THRESHOLD + 1 ], "attr" , 2 )
1659+ x += a .attr
1660+ return x
1661+
1662+ class Foo :
1663+ attr = 1
1664+
1665+ class Bar :
1666+ pass
1667+
1668+ res , ex = self ._run_with_optimizer (thing , Foo ())
1669+ opnames = list (iter_opnames (ex ))
1670+ self .assertEqual (res , TIER2_THRESHOLD * 2 + 2 )
1671+ call = opnames .index ("_CALL_BUILTIN_FAST" )
1672+ load_attr_top = opnames .index ("_LOAD_CONST_INLINE_BORROW" , 0 , call )
1673+ load_attr_bottom = opnames .index ("_LOAD_CONST_INLINE_BORROW" , call )
1674+ self .assertEqual (opnames [:load_attr_top ].count ("_GUARD_TYPE_VERSION" ), 1 )
1675+ self .assertEqual (opnames [call :load_attr_bottom ].count ("_CHECK_VALIDITY" ), 2 )
1676+
1677+ def test_guard_type_version_removed_escaping (self ):
1678+
1679+ def thing (a ):
1680+ x = 0
1681+ for i in range (TIER2_THRESHOLD ):
1682+ x += a .attr
1683+ # eval should be escaping
1684+ eval ("None" )
1685+ x += a .attr
1686+ return x
1687+
1688+ class Foo :
1689+ attr = 1
1690+ res , ex = self ._run_with_optimizer (thing , Foo ())
1691+ opnames = list (iter_opnames (ex ))
1692+ self .assertIsNotNone (ex )
1693+ self .assertEqual (res , TIER2_THRESHOLD * 2 )
1694+ call = opnames .index ("_CALL_BUILTIN_FAST_WITH_KEYWORDS" )
1695+ load_attr_top = opnames .index ("_LOAD_CONST_INLINE_BORROW" , 0 , call )
1696+ load_attr_bottom = opnames .index ("_LOAD_CONST_INLINE_BORROW" , call )
1697+ self .assertEqual (opnames [:load_attr_top ].count ("_GUARD_TYPE_VERSION" ), 1 )
1698+ self .assertEqual (opnames [call :load_attr_bottom ].count ("_CHECK_VALIDITY" ), 2 )
1699+
1700+ def test_guard_type_version_executor_invalidated (self ):
1701+ """
1702+ Verify that the executor is invalided on a type change.
1703+ """
1704+
1705+ def thing (a ):
1706+ x = 0
1707+ for i in range (TIER2_THRESHOLD ):
1708+ x += a .attr
1709+ x += a .attr
1710+ return x
1711+
1712+ class Foo :
1713+ attr = 1
1714+
1715+ res , ex = self ._run_with_optimizer (thing , Foo ())
1716+ self .assertEqual (res , TIER2_THRESHOLD * 2 )
1717+ self .assertIsNotNone (ex )
1718+ self .assertEqual (list (iter_opnames (ex )).count ("_GUARD_TYPE_VERSION" ), 1 )
1719+ self .assertTrue (ex .is_valid ())
1720+ Foo .attr = 0
1721+ self .assertFalse (ex .is_valid ())
1722+
16041723 def test_guard_type_version_locked_removed (self ):
16051724 """
16061725 Verify that redundant _GUARD_TYPE_VERSION_LOCKED guards are
@@ -1736,9 +1855,42 @@ def testfunc(n):
17361855 uops = get_opnames (ex )
17371856 # Both methods should be traced through
17381857 self .assertEqual (uops .count ("_PUSH_FRAME" ), 2 )
1858+ # Type version propagation: one guard covers both method lookups
1859+ self .assertEqual (uops .count ("_GUARD_TYPE_VERSION" ), 1 )
17391860 # Function checks cannot be eliminated for safety reasons.
17401861 self .assertIn ("_CHECK_FUNCTION_VERSION" , uops )
17411862
1863+ def test_method_chain_guard_elimination (self ):
1864+ """
1865+ Calling two methods on the same object should share the outer
1866+ type guard — only one _GUARD_TYPE_VERSION for the two lookups.
1867+ """
1868+ class Calc :
1869+ def __init__ (self , val ):
1870+ self .val = val
1871+
1872+ def add (self , x ):
1873+ self .val += x
1874+ return self
1875+
1876+ def testfunc (n ):
1877+ c = Calc (0 )
1878+ for _ in range (n ):
1879+ c .add (1 ).add (2 )
1880+ return c .val
1881+
1882+ res , ex = self ._run_with_optimizer (testfunc , TIER2_THRESHOLD )
1883+ self .assertEqual (res , TIER2_THRESHOLD * 3 )
1884+ self .assertIsNotNone (ex )
1885+ uops = get_opnames (ex )
1886+ # Both add() calls should be inlined
1887+ push_count = uops .count ("_PUSH_FRAME" )
1888+ self .assertEqual (push_count , 2 )
1889+ # Only one outer type version guard for the two method lookups
1890+ # on the same object c (the second lookup reuses type info)
1891+ guard_version_count = uops .count ("_GUARD_TYPE_VERSION" )
1892+ self .assertEqual (guard_version_count , 1 )
1893+
17421894 def test_func_guards_removed_or_reduced (self ):
17431895 def testfunc (n ):
17441896 for i in range (n ):
@@ -3485,6 +3637,7 @@ def testfunc(n):
34853637 self .assertEqual (res , 2 * TIER2_THRESHOLD )
34863638 self .assertIsNotNone (ex )
34873639 uops = get_opnames (ex )
3640+ self .assertIn ("_GUARD_TYPE_VERSION" , uops )
34883641 self .assertNotIn ("_CHECK_ATTR_CLASS" , uops )
34893642
34903643 def test_load_common_constant (self ):
0 commit comments