@@ -371,6 +371,75 @@ def testfunc(n):
371371 # look for indirect evidence: the += operator
372372 self .assertIn ("_BINARY_OP_ADD_INT" , uops )
373373
374+ def test_get_iter_list (self ):
375+ l = list (range (10 ))
376+ def testfunc (n ):
377+ total = 0
378+ while n :
379+ n -= 1
380+ total += n
381+ for i in l :
382+ break
383+ return total
384+
385+ total = testfunc (TIER2_THRESHOLD )
386+ self .assertEqual (total , sum (range (TIER2_THRESHOLD )))
387+ ex = get_first_executor (testfunc )
388+ self .assertIsNotNone (ex )
389+ uops = get_opnames (ex )
390+ self .assertIn ("_PUSH_TAGGED_ZERO" , uops )
391+ self .assertNotIn ("_GET_ITER" , uops )
392+ self .assertNotIn ("_GET_ITER_TRAD" , uops )
393+ self .assertNotIn ("_GET_ITER_VIRTUAL" , uops )
394+ self .assertNotIn ("_GET_ITER_SELF" , uops )
395+
396+ def test_get_iter_gen (self ):
397+ def gen ():
398+ while True :
399+ yield 1
400+
401+ def testfunc (n ):
402+ total = 0
403+ while n :
404+ n -= 1
405+ total += n
406+ for i in gen ():
407+ break
408+ return total
409+
410+ total = testfunc (TIER2_THRESHOLD )
411+ self .assertEqual (total , sum (range (TIER2_THRESHOLD )))
412+ ex = get_first_executor (testfunc )
413+ self .assertIsNotNone (ex )
414+ uops = get_opnames (ex )
415+ self .assertIn ("_PUSH_NULL" , uops )
416+ self .assertNotIn ("_GET_ITER" , uops )
417+ self .assertNotIn ("_GET_ITER_TRAD" , uops )
418+ self .assertNotIn ("_GET_ITER_VIRTUAL" , uops )
419+ self .assertNotIn ("_GET_ITER_SELF" , uops )
420+
421+ def test_get_iter_trad (self ):
422+ d = {v :v for v in range (10 )}
423+ def testfunc (n ):
424+ total = 0
425+ while n :
426+ n -= 1
427+ total += n
428+ for i in d :
429+ break
430+ return total
431+
432+ total = testfunc (TIER2_THRESHOLD )
433+ self .assertEqual (total , sum (range (TIER2_THRESHOLD )))
434+ ex = get_first_executor (testfunc )
435+ self .assertIsNotNone (ex )
436+ uops = get_opnames (ex )
437+ self .assertIn ("_GET_ITER_TRAD" , uops )
438+ self .assertNotIn ("_GET_ITER" , uops )
439+ self .assertNotIn ("_GET_ITER_VIRTUAL" , uops )
440+ self .assertNotIn ("_GET_ITER_SELF" , uops )
441+
442+
374443 def test_for_iter_range (self ):
375444 def testfunc (n ):
376445 total = 0
@@ -2646,6 +2715,9 @@ def testfunc(n):
26462715 uops = get_opnames (ex )
26472716 # When the result of type(...) is known, _CALL_TYPE_1 is decomposed.
26482717 self .assertNotIn ("_CALL_TYPE_1" , uops )
2718+ # _CALL_TYPE_1 produces 2 _POP_TOP_NOP (callable and null)
2719+ # type(42) is int produces 4 _POP_TOP_NOP
2720+ self .assertGreaterEqual (count_ops (ex , "_POP_TOP_NOP" ), 6 )
26492721
26502722 def test_call_type_1_result_is_const (self ):
26512723 def testfunc (n ):
@@ -3439,6 +3511,29 @@ def f(n):
34393511 self .assertNotIn ("_LOAD_ATTR_METHOD_NO_DICT" , uops )
34403512 self .assertIn ("_LOAD_CONST_INLINE_BORROW" , uops )
34413513
3514+ def test_cached_load_special (self ):
3515+ class CM :
3516+ def __enter__ (self ):
3517+ return self
3518+ def __exit__ (self , * args ):
3519+ pass
3520+ def f (n ):
3521+ cm = CM ()
3522+ x = 0
3523+ for _ in range (n ):
3524+ with cm :
3525+ x += 1
3526+ return x
3527+ res , ex = self ._run_with_optimizer (f , TIER2_THRESHOLD )
3528+ self .assertIsNotNone (ex )
3529+ self .assertEqual (res , TIER2_THRESHOLD )
3530+ uops = get_opnames (ex )
3531+ self .assertNotIn ("_LOAD_SPECIAL" , uops )
3532+ # __enter__/__exit__ produce 2 _POP_TOP_NOP
3533+ # x += 1 produces 2 _POP_TOP_NOP
3534+ # __exit__()'s None return produces 1 _POP_TOP_NOP
3535+ self .assertGreaterEqual (count_ops (ex , "_POP_TOP_NOP" ), 5 )
3536+
34423537 def test_store_fast_refcount_elimination (self ):
34433538 def foo (x ):
34443539 # Since x is known to be
@@ -4277,6 +4372,118 @@ def testfunc(n):
42774372 # propagates PyFloat_Type.
42784373 self .assertNotIn ("_GUARD_NOS_FLOAT" , uops )
42794374
4375+ def test_binary_op_extend_list_concat_type_propagation (self ):
4376+ # list + list is specialized via BINARY_OP_EXTEND. The tier 2 optimizer
4377+ # should learn that the result is a list and eliminate subsequent
4378+ # list-type guards.
4379+ def testfunc (n ):
4380+ a = [1 , 2 ]
4381+ b = [3 , 4 ]
4382+ x = True
4383+ for _ in range (n ):
4384+ c = a + b
4385+ if c [0 ]:
4386+ x = False
4387+ return x
4388+
4389+ res , ex = self ._run_with_optimizer (testfunc , TIER2_THRESHOLD )
4390+ self .assertEqual (res , False )
4391+ self .assertIsNotNone (ex )
4392+ uops = get_opnames (ex )
4393+ self .assertIn ("_BINARY_OP_EXTEND" , uops )
4394+ # The c[0] subscript emits _GUARD_NOS_LIST before _BINARY_OP_SUBSCR_LIST_INT;
4395+ # since _BINARY_OP_EXTEND now propagates PyList_Type, that guard is gone.
4396+ self .assertIn ("_BINARY_OP_SUBSCR_LIST_INT" , uops )
4397+ self .assertNotIn ("_GUARD_NOS_LIST" , uops )
4398+
4399+ def test_binary_op_extend_tuple_concat_type_propagation (self ):
4400+ # tuple + tuple is specialized via BINARY_OP_EXTEND. The tier 2 optimizer
4401+ # should learn the result is a tuple and eliminate subsequent tuple guards.
4402+ def testfunc (n ):
4403+ t1 = (1 , 2 )
4404+ t2 = (3 , 4 )
4405+ for _ in range (n ):
4406+ a , b , c , d = t1 + t2
4407+ return a + b + c + d
4408+
4409+ res , ex = self ._run_with_optimizer (testfunc , TIER2_THRESHOLD )
4410+ self .assertEqual (res , 10 )
4411+ self .assertIsNotNone (ex )
4412+ uops = get_opnames (ex )
4413+ self .assertIn ("_BINARY_OP_EXTEND" , uops )
4414+ self .assertIn ("_UNPACK_SEQUENCE_TUPLE" , uops )
4415+ self .assertNotIn ("_GUARD_TOS_TUPLE" , uops )
4416+
4417+ def test_binary_op_extend_guard_elimination (self ):
4418+ # When both operands have known types (e.g., from a prior
4419+ # _BINARY_OP_EXTEND result), the _GUARD_BINARY_OP_EXTEND
4420+ # should be eliminated.
4421+ def testfunc (n ):
4422+ a = [1 , 2 ]
4423+ b = [3 , 4 ]
4424+ total = 0
4425+ for _ in range (n ):
4426+ c = a + b # first: guard stays, result type = list
4427+ d = c + c # second: both operands are list -> guard eliminated
4428+ total += d [0 ]
4429+ return total
4430+
4431+ res , ex = self ._run_with_optimizer (testfunc , TIER2_THRESHOLD )
4432+ self .assertEqual (res , TIER2_THRESHOLD )
4433+ self .assertIsNotNone (ex )
4434+ uops = get_opnames (ex )
4435+ # Both list additions use _BINARY_OP_EXTEND
4436+ self .assertEqual (uops .count ("_BINARY_OP_EXTEND" ), 2 )
4437+ # But the second guard is eliminated because both operands
4438+ # are known to be lists from the first _BINARY_OP_EXTEND.
4439+ self .assertEqual (uops .count ("_GUARD_BINARY_OP_EXTEND" ), 1 )
4440+
4441+ def test_binary_op_extend_partial_guard_lhs_known (self ):
4442+ # When the lhs type is already known (from a prior _BINARY_OP_EXTEND
4443+ # result) but the rhs type is not, the optimizer should emit
4444+ # _GUARD_BINARY_OP_EXTEND_RHS (checking only the rhs) instead of
4445+ # the full _GUARD_BINARY_OP_EXTEND.
4446+ def testfunc (n ):
4447+ a = [1 , 2 ]
4448+ b = [3 , 4 ]
4449+ total = 0
4450+ for _ in range (n ):
4451+ c = a + b # result type is list (known)
4452+ d = c + b # lhs (c) is known list, rhs (b) is not -> _GUARD_BINARY_OP_EXTEND_RHS
4453+ total += d [0 ]
4454+ return total
4455+
4456+ res , ex = self ._run_with_optimizer (testfunc , TIER2_THRESHOLD )
4457+ self .assertEqual (res , TIER2_THRESHOLD )
4458+ self .assertIsNotNone (ex )
4459+ uops = get_opnames (ex )
4460+ self .assertIn ("_BINARY_OP_EXTEND" , uops )
4461+ self .assertIn ("_GUARD_BINARY_OP_EXTEND_RHS" , uops )
4462+ self .assertNotIn ("_GUARD_BINARY_OP_EXTEND_LHS" , uops )
4463+
4464+ def test_binary_op_extend_partial_guard_rhs_known (self ):
4465+ # When the rhs type is already known (from a prior _BINARY_OP_EXTEND
4466+ # result) but the lhs type is not, the optimizer should emit
4467+ # _GUARD_BINARY_OP_EXTEND_LHS (checking only the lhs) instead of
4468+ # the full _GUARD_BINARY_OP_EXTEND.
4469+ def testfunc (n ):
4470+ a = [1 , 2 ]
4471+ b = [3 , 4 ]
4472+ total = 0
4473+ for _ in range (n ):
4474+ c = a + b # result type is list (known)
4475+ d = b + c # rhs (c) is known list, lhs (b) is not -> _GUARD_BINARY_OP_EXTEND_LHS
4476+ total += d [2 ]
4477+ return total
4478+
4479+ res , ex = self ._run_with_optimizer (testfunc , TIER2_THRESHOLD )
4480+ self .assertEqual (res , TIER2_THRESHOLD )
4481+ self .assertIsNotNone (ex )
4482+ uops = get_opnames (ex )
4483+ self .assertIn ("_BINARY_OP_EXTEND" , uops )
4484+ self .assertIn ("_GUARD_BINARY_OP_EXTEND_LHS" , uops )
4485+ self .assertNotIn ("_GUARD_BINARY_OP_EXTEND_RHS" , uops )
4486+
42804487 def test_unary_invert_long_type (self ):
42814488 def testfunc (n ):
42824489 for _ in range (n ):
@@ -5008,7 +5215,7 @@ def testfunc(n):
50085215 self .assertEqual (res , TIER2_THRESHOLD )
50095216 self .assertIsNotNone (ex )
50105217 uops = get_opnames (ex )
5011- self .assertIn ( "_LOAD_CONST_INLINE_BORROW" , uops )
5218+ self .assertGreaterEqual ( count_ops ( ex , "_LOAD_CONST_INLINE_BORROW" ), 2 )
50125219 self .assertNotIn ("_BINARY_OP_SUBSCR_DICT" , uops )
50135220
50145221 def test_binary_subscr_frozendict_const_fold (self ):
@@ -5023,6 +5230,7 @@ def testfunc(n):
50235230 self .assertEqual (res , TIER2_THRESHOLD )
50245231 self .assertIsNotNone (ex )
50255232 uops = get_opnames (ex )
5233+ self .assertGreaterEqual (count_ops (ex , "_LOAD_CONST_INLINE_BORROW" ), 3 )
50265234 # lookup result is folded to constant 1, so comparison is optimized away
50275235 self .assertNotIn ("_COMPARE_OP_INT" , uops )
50285236
@@ -5038,8 +5246,39 @@ def testfunc(n):
50385246 self .assertEqual (res , TIER2_THRESHOLD )
50395247 self .assertIsNotNone (ex )
50405248 uops = get_opnames (ex )
5249+ self .assertGreaterEqual (count_ops (ex , "_LOAD_CONST_INLINE_BORROW" ), 3 )
50415250 self .assertNotIn ("_CONTAINS_OP_SET" , uops )
50425251
5252+ def test_contains_op_frozendict_const_fold (self ):
5253+ def testfunc (n ):
5254+ x = 0
5255+ for _ in range (n ):
5256+ if 'x' in FROZEN_DICT_CONST :
5257+ x += 1
5258+ return x
5259+
5260+ res , ex = self ._run_with_optimizer (testfunc , TIER2_THRESHOLD )
5261+ self .assertEqual (res , TIER2_THRESHOLD )
5262+ self .assertIsNotNone (ex )
5263+ uops = get_opnames (ex )
5264+ self .assertGreaterEqual (count_ops (ex , "_LOAD_CONST_INLINE_BORROW" ), 3 )
5265+ self .assertNotIn ("_CONTAINS_OP_DICT" , uops )
5266+
5267+ def test_not_contains_op_frozendict_const_fold (self ):
5268+ def testfunc (n ):
5269+ x = 0
5270+ for _ in range (n ):
5271+ if 'z' not in FROZEN_DICT_CONST :
5272+ x += 1
5273+ return x
5274+
5275+ res , ex = self ._run_with_optimizer (testfunc , TIER2_THRESHOLD )
5276+ self .assertEqual (res , TIER2_THRESHOLD )
5277+ self .assertIsNotNone (ex )
5278+ uops = get_opnames (ex )
5279+ self .assertGreaterEqual (count_ops (ex , "_LOAD_CONST_INLINE_BORROW" ), 3 )
5280+ self .assertNotIn ("_CONTAINS_OP_DICT" , uops )
5281+
50435282 def test_binary_subscr_list_slice (self ):
50445283 def testfunc (n ):
50455284 x = 0
@@ -5258,6 +5497,57 @@ def testfunc(*args):
52585497 # This is a sign the optimizer ran and didn't hit contradiction.
52595498 self .assertIn ("_LOAD_CONST_INLINE_BORROW" , uops )
52605499
5500+ def test_load_attr_getattribute_frame (self ):
5501+ class B :
5502+ def __getattribute__ (self , name ):
5503+ return len (name )
5504+
5505+ def testfunc (n ):
5506+ b = B ()
5507+ y = 0
5508+ for _ in range (n ):
5509+ y += b .x + b .y
5510+ return y
5511+
5512+ res , ex = self ._run_with_optimizer (testfunc , TIER2_THRESHOLD )
5513+ self .assertIsNotNone (ex )
5514+ self .assertEqual (res , 2 * TIER2_THRESHOLD )
5515+ uops = get_opnames (ex )
5516+ self .assertIn ("_LOAD_ATTR_GETATTRIBUTE_OVERRIDDEN_FRAME" , uops )
5517+ self .assertNotIn ("_LOAD_GLOBAL_BUILTINS" , uops )
5518+
5519+ def test_load_attr_property_frame_invalidates_on_code_change (self ):
5520+ class C :
5521+ @property
5522+ def val (self ):
5523+ return int (1 )
5524+
5525+ fget = C .val .fget
5526+
5527+ def testfunc (* args ):
5528+ n , c = args [0 ]
5529+ total = 0
5530+ for _ in range (n ):
5531+ total += c .val
5532+ return total
5533+
5534+ testfunc ((3 , C ()))
5535+ res , ex = self ._run_with_optimizer (testfunc , (TIER2_THRESHOLD , C ()))
5536+ self .assertEqual (res , TIER2_THRESHOLD )
5537+ self .assertIsNotNone (ex )
5538+ uops = get_opnames (ex )
5539+ self .assertIn ("_LOAD_ATTR_PROPERTY_FRAME" , uops )
5540+ # Check the optimizer traced through the property call.
5541+ self .assertNotIn ("_LOAD_GLOBAL_BUILTINS" , uops )
5542+ self .assertIn ("_CALL_BUILTIN_CLASS" , uops )
5543+
5544+ fget .__code__ = (lambda self : 2 ).__code__
5545+ _testinternalcapi .clear_executor_deletion_list ()
5546+ ex = get_first_executor (testfunc )
5547+ self .assertIsNone (ex )
5548+ res = testfunc ((TIER2_THRESHOLD , C ()))
5549+ self .assertEqual (res , TIER2_THRESHOLD * 2 )
5550+
52615551 def test_unary_negative (self ):
52625552 def testfunc (n ):
52635553 a = 3
0 commit comments