Skip to content

Commit 4fedc1f

Browse files
authored
[mypyc] Borrow the callable environment link (#21957)
It's now final, so it can be borrowed more liberally. This made a nested function microbenchmark ~1.3x faster on 3.14 (free-threaded). I used coding agent assist.
1 parent 561f563 commit 4fedc1f

5 files changed

Lines changed: 42 additions & 36 deletions

File tree

mypyc/irbuild/env_class.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,11 @@ def load_env_registers(builder: IRBuilder, prefix: str = "") -> None:
143143

144144

145145
def load_outer_env(
146-
builder: IRBuilder, base: Value, outer_env: dict[SymbolNode, SymbolTarget]
146+
builder: IRBuilder,
147+
base: Value,
148+
outer_env: dict[SymbolNode, SymbolTarget],
149+
*,
150+
borrow: bool = False,
147151
) -> Value:
148152
"""Load the environment class for a given base into a register.
149153
@@ -156,7 +160,10 @@ def load_outer_env(
156160
157161
Returns the register where the environment class was loaded.
158162
"""
159-
env = builder.add(GetAttr(base, ENV_ATTR_NAME, builder.fn_info.fitem.line))
163+
if borrow:
164+
assert isinstance(base.type, RInstance)
165+
assert base.type.class_ir.is_final_attr(ENV_ATTR_NAME)
166+
env = builder.add(GetAttr(base, ENV_ATTR_NAME, builder.fn_info.fitem.line, borrow=borrow))
160167
assert isinstance(env.type, RInstance), f"{env} must be of type RInstance"
161168

162169
for symbol, target in outer_env.items():
@@ -182,7 +189,9 @@ def load_outer_envs(builder: IRBuilder, base: ImplicitClass) -> None:
182189
if isinstance(base, GeneratorClass):
183190
base.prev_env_reg = load_outer_env(builder, base.curr_env_reg, outer_env)
184191
else:
185-
base.prev_env_reg = load_outer_env(builder, base.self_reg, outer_env)
192+
# The callable stays alive throughout __call__, and its environment link is Final,
193+
# so the environment can be borrowed for the duration of the call.
194+
base.prev_env_reg = load_outer_env(builder, base.self_reg, outer_env, borrow=True)
186195
env_reg = base.prev_env_reg
187196
index -= 1
188197

mypyc/test-data/irbuild-basic.test

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2450,7 +2450,7 @@ def g_a_obj.__call__(__mypyc_self__):
24502450
r15 :: object_ptr
24512451
r16 :: object
24522452
L0:
2453-
r0 = __mypyc_self__.__mypyc_env__
2453+
r0 = borrow __mypyc_self__.__mypyc_env__
24542454
r1 = 'Entering'
24552455
r2 = builtins :: module
24562456
r3 = 'print'
@@ -2514,7 +2514,7 @@ def g_b_obj.__call__(__mypyc_self__):
25142514
r15 :: object_ptr
25152515
r16 :: object
25162516
L0:
2517-
r0 = __mypyc_self__.__mypyc_env__
2517+
r0 = borrow __mypyc_self__.__mypyc_env__
25182518
r1 = '---'
25192519
r2 = builtins :: module
25202520
r3 = 'print'
@@ -2571,7 +2571,7 @@ def d_c_obj.__call__(__mypyc_self__):
25712571
r6 :: object_ptr
25722572
r7 :: object
25732573
L0:
2574-
r0 = __mypyc_self__.__mypyc_env__
2574+
r0 = borrow __mypyc_self__.__mypyc_env__
25752575
r1 = 'd'
25762576
r2 = builtins :: module
25772577
r3 = 'print'
@@ -2747,7 +2747,7 @@ def g_a_obj.__call__(__mypyc_self__):
27472747
r15 :: object_ptr
27482748
r16 :: object
27492749
L0:
2750-
r0 = __mypyc_self__.__mypyc_env__
2750+
r0 = borrow __mypyc_self__.__mypyc_env__
27512751
r1 = 'Entering'
27522752
r2 = builtins :: module
27532753
r3 = 'print'
@@ -3575,7 +3575,7 @@ def wrapper_deco_obj.__call__(__mypyc_self__, args):
35753575
r0 :: __main__.deco_env
35763576
r1, r2 :: object
35773577
L0:
3578-
r0 = __mypyc_self__.__mypyc_env__
3578+
r0 = borrow __mypyc_self__.__mypyc_env__
35793579
r1 = r0.fn
35803580
r2 = PyObject_CallObject(r1, args)
35813581
return r2
@@ -3622,7 +3622,7 @@ def wrapper_deco_obj.__call__(__mypyc_self__, args):
36223622
r2 :: tuple
36233623
r3 :: object
36243624
L0:
3625-
r0 = __mypyc_self__.__mypyc_env__
3625+
r0 = borrow __mypyc_self__.__mypyc_env__
36263626
r1 = r0.fn
36273627
r2 = PyList_AsTuple(args)
36283628
r3 = PyObject_CallObject(r1, r2)
@@ -3672,7 +3672,7 @@ def wrapper_deco_obj.__call__(__mypyc_self__, lst, kwargs):
36723672
r3 :: dict
36733673
r4 :: object
36743674
L0:
3675-
r0 = __mypyc_self__.__mypyc_env__
3675+
r0 = borrow __mypyc_self__.__mypyc_env__
36763676
r1 = r0.fn
36773677
r2 = PyList_AsTuple(lst)
36783678
r3 = PyDict_Copy(kwargs)
@@ -3721,7 +3721,7 @@ def wrapper_deco_obj.__call__(__mypyc_self__, args):
37213721
r2 :: tuple
37223722
r3 :: object
37233723
L0:
3724-
r0 = __mypyc_self__.__mypyc_env__
3724+
r0 = borrow __mypyc_self__.__mypyc_env__
37253725
r1 = r0.fn
37263726
r2 = PySequence_Tuple(args)
37273727
r3 = PyObject_CallObject(r1, r2)
@@ -3771,7 +3771,7 @@ def wrapper_deco_obj.__call__(__mypyc_self__, args, kwargs):
37713771
r3 :: dict
37723772
r4 :: object
37733773
L0:
3774-
r0 = __mypyc_self__.__mypyc_env__
3774+
r0 = borrow __mypyc_self__.__mypyc_env__
37753775
r1 = r0.fn
37763776
r2 = PySequence_Tuple(args)
37773777
r3 = PyDict_Copy(kwargs)

mypyc/test-data/irbuild-generics.test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -701,7 +701,7 @@ def inner_deco_obj.__call__(__mypyc_self__, args, kwargs):
701701
r26 :: object
702702
r27 :: int
703703
L0:
704-
r0 = __mypyc_self__.__mypyc_env__
704+
r0 = borrow __mypyc_self__.__mypyc_env__
705705
r1 = var_object_size args
706706
r2 = PyList_New(r1)
707707
r3 = 0

mypyc/test-data/irbuild-nested.test

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ def inner_a_obj.__call__(__mypyc_self__):
5252
r0 :: __main__.a_env
5353
r1 :: object
5454
L0:
55-
r0 = __mypyc_self__.__mypyc_env__
55+
r0 = borrow __mypyc_self__.__mypyc_env__
5656
r1 = box(None, 1)
5757
return r1
5858
def a():
@@ -84,7 +84,7 @@ def second_b_first_obj.__call__(__mypyc_self__):
8484
r1 :: __main__.b_env
8585
r2 :: str
8686
L0:
87-
r0 = __mypyc_self__.__mypyc_env__
87+
r0 = borrow __mypyc_self__.__mypyc_env__
8888
r1 = r0.__mypyc_env__
8989
r2 = 'b.first.second: nested function'
9090
return r2
@@ -109,7 +109,7 @@ def first_b_obj.__call__(__mypyc_self__):
109109
r3 :: __main__.second_b_first_obj
110110
second :: object
111111
L0:
112-
r0 = __mypyc_self__.__mypyc_env__
112+
r0 = borrow __mypyc_self__.__mypyc_env__
113113
r1 = first_b_env()
114114
r1.__mypyc_env__ = r0; r2 = is_error
115115
r3 = second_b_first_obj()
@@ -145,7 +145,7 @@ def inner_c_obj.__call__(__mypyc_self__, s):
145145
r0 :: __main__.c_env
146146
r1, r2 :: str
147147
L0:
148-
r0 = __mypyc_self__.__mypyc_env__
148+
r0 = borrow __mypyc_self__.__mypyc_env__
149149
r1 = '!'
150150
r2 = PyUnicode_Concat(s, r1)
151151
return r2
@@ -179,7 +179,7 @@ def inner_d_obj.__call__(__mypyc_self__, s):
179179
r0 :: __main__.d_env
180180
r1, r2 :: str
181181
L0:
182-
r0 = __mypyc_self__.__mypyc_env__
182+
r0 = borrow __mypyc_self__.__mypyc_env__
183183
r1 = '?'
184184
r2 = PyUnicode_Concat(s, r1)
185185
return r2
@@ -278,7 +278,7 @@ def inner_a_obj.__call__(__mypyc_self__):
278278
r0 :: __main__.a_env
279279
r1 :: int
280280
L0:
281-
r0 = __mypyc_self__.__mypyc_env__
281+
r0 = borrow __mypyc_self__.__mypyc_env__
282282
r1 = r0.num
283283
return r1
284284
def a(num):
@@ -316,7 +316,7 @@ def inner_b_obj.__call__(__mypyc_self__):
316316
r1 :: bool
317317
foo, r2 :: int
318318
L0:
319-
r0 = __mypyc_self__.__mypyc_env__
319+
r0 = borrow __mypyc_self__.__mypyc_env__
320320
r0.num = 8; r1 = is_error
321321
foo = 12
322322
r2 = r0.num
@@ -356,7 +356,7 @@ def inner_c_obj.__call__(__mypyc_self__):
356356
r0 :: __main__.c_env
357357
r1 :: str
358358
L0:
359-
r0 = __mypyc_self__.__mypyc_env__
359+
r0 = borrow __mypyc_self__.__mypyc_env__
360360
r1 = 'f.inner: first definition'
361361
return r1
362362
def inner_c_obj_0.__get__(__mypyc_self__, instance, owner):
@@ -377,7 +377,7 @@ def inner_c_obj_0.__call__(__mypyc_self__):
377377
r0 :: __main__.c_env
378378
r1 :: str
379379
L0:
380-
r0 = __mypyc_self__.__mypyc_env__
380+
r0 = borrow __mypyc_self__.__mypyc_env__
381381
r1 = 'f.inner: second definition'
382382
return r1
383383
def c(flag):
@@ -435,7 +435,7 @@ def c_a_b_obj.__call__(__mypyc_self__):
435435
r1 :: __main__.a_env
436436
r2 :: int
437437
L0:
438-
r0 = __mypyc_self__.__mypyc_env__
438+
r0 = borrow __mypyc_self__.__mypyc_env__
439439
r1 = r0.__mypyc_env__
440440
r2 = r1.x
441441
return r2
@@ -463,7 +463,7 @@ def b_a_obj.__call__(__mypyc_self__):
463463
c, r7 :: object
464464
r8 :: int
465465
L0:
466-
r0 = __mypyc_self__.__mypyc_env__
466+
r0 = borrow __mypyc_self__.__mypyc_env__
467467
r1 = b_a_env()
468468
r1.__mypyc_env__ = r0; r2 = is_error
469469
r3 = r0.x
@@ -519,7 +519,7 @@ def inner_f_obj.__call__(__mypyc_self__):
519519
r0 :: __main__.f_env
520520
r1 :: str
521521
L0:
522-
r0 = __mypyc_self__.__mypyc_env__
522+
r0 = borrow __mypyc_self__.__mypyc_env__
523523
r1 = 'f.inner: first definition'
524524
return r1
525525
def inner_f_obj_0.__get__(__mypyc_self__, instance, owner):
@@ -540,7 +540,7 @@ def inner_f_obj_0.__call__(__mypyc_self__):
540540
r0 :: __main__.f_env
541541
r1 :: str
542542
L0:
543-
r0 = __mypyc_self__.__mypyc_env__
543+
r0 = borrow __mypyc_self__.__mypyc_env__
544544
r1 = 'f.inner: second definition'
545545
return r1
546546
def f(flag):
@@ -604,7 +604,7 @@ def foo_f_obj.__call__(__mypyc_self__):
604604
r0 :: __main__.f_env
605605
r1, r2 :: int
606606
L0:
607-
r0 = __mypyc_self__.__mypyc_env__
607+
r0 = borrow __mypyc_self__.__mypyc_env__
608608
r1 = r0.a
609609
r2 = CPyTagged_Add(r1, 2)
610610
return r2
@@ -627,7 +627,7 @@ def bar_f_obj.__call__(__mypyc_self__):
627627
r1, r2 :: object
628628
r3 :: int
629629
L0:
630-
r0 = __mypyc_self__.__mypyc_env__
630+
r0 = borrow __mypyc_self__.__mypyc_env__
631631
r1 = r0.foo
632632
r2 = PyObject_Vectorcall(r1, 0, 0, 0)
633633
r3 = unbox(int, r2)
@@ -657,7 +657,7 @@ def baz_f_obj.__call__(__mypyc_self__, n):
657657
r7 :: object
658658
r8, r9 :: int
659659
L0:
660-
r0 = __mypyc_self__.__mypyc_env__
660+
r0 = borrow __mypyc_self__.__mypyc_env__
661661
r1 = int_eq n, 0
662662
if r1 goto L1 else goto L2 :: bool
663663
L1:
@@ -742,7 +742,7 @@ def __mypyc_lambda__0_f_obj.__call__(__mypyc_self__, a, b):
742742
r0 :: __main__.f_env
743743
r1 :: object
744744
L0:
745-
r0 = __mypyc_self__.__mypyc_env__
745+
r0 = borrow __mypyc_self__.__mypyc_env__
746746
r1 = PyNumber_Add(a, b)
747747
return r1
748748
def __mypyc_lambda__1_f_obj.__get__(__mypyc_self__, instance, owner):
@@ -767,7 +767,7 @@ def __mypyc_lambda__1_f_obj.__call__(__mypyc_self__, a, b):
767767
r3 :: object_ptr
768768
r4 :: object
769769
L0:
770-
r0 = __mypyc_self__.__mypyc_env__
770+
r0 = borrow __mypyc_self__.__mypyc_env__
771771
r1 = r0.s
772772
r2 = [a, b]
773773
r3 = load_address r2

mypyc/test-data/run-functions.test

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1348,6 +1348,7 @@ def test_nested() -> None:
13481348

13491349
[case testNestedFunctionEnvironmentIsReadOnly]
13501350
from typing import Any, Callable
1351+
from testutil import assertRaises
13511352

13521353
def outer(value: str) -> Callable[[], str]:
13531354
def inner() -> str:
@@ -1357,12 +1358,8 @@ def outer(value: str) -> Callable[[], str]:
13571358
def test_environment_link_is_read_only() -> None:
13581359
fn: Any = outer("value")
13591360
environment = fn.__mypyc_env__
1360-
try:
1361+
with assertRaises(AttributeError):
13611362
fn.__mypyc_env__ = None
1362-
except AttributeError:
1363-
pass
1364-
else:
1365-
assert False
13661363
assert fn.__mypyc_env__ is environment
13671364
assert fn() == "value"
13681365

0 commit comments

Comments
 (0)