Skip to content

Commit cd013d1

Browse files
authored
[mypyc] Use native calls to singledispatch functions (#10981)
* Wrap singledispatch callable class in glue function mypyc doesn't have support for calling callable classes without going through the python API, so instead of having to special case the singledispatch callable class when we want to generate a native call to it, we create a glue function that loads the callable class from the globals dict and then makes a native call to that callable class's __call__ method. * Use native calls for singledispatch functions Use a native call to the glue function that we generate for singledispatch functions instead of calling those singledispatch functions through the python API. * Add irbuild test for native calls to singledispatch functions Add an irbuild test to make sure we use native calls to the glue functions for singledispatch functions, and that the glue functions make native calls to the __call__ methods on the callable classes. * Fix irbuild test
1 parent 4c90aa4 commit cd013d1

File tree

3 files changed

+197
-20
lines changed

3 files changed

+197
-20
lines changed

mypyc/irbuild/builder.py

+10
Original file line numberDiff line numberDiff line change
@@ -857,6 +857,16 @@ def call_refexpr_with_args(
857857
callee_node = callee.node
858858
if isinstance(callee_node, OverloadedFuncDef):
859859
callee_node = callee_node.impl
860+
# TODO: use native calls for any decorated functions which have all their decorators
861+
# removed, not just singledispatch functions (which we don't do now just in case those
862+
# decorated functions are callable classes or cannot be called without the python API for
863+
# some other reason)
864+
if (
865+
isinstance(callee_node, Decorator)
866+
and callee_node.func not in self.fdefs_to_decorators
867+
and callee_node.func in self.singledispatch_impls
868+
):
869+
callee_node = callee_node.func
860870
if (callee_node is not None
861871
and callee.fullname is not None
862872
and callee_node in self.mapper.func_to_decl

mypyc/irbuild/function.py

+27-1
Original file line numberDiff line numberDiff line change
@@ -947,14 +947,40 @@ def gen_dispatch_func_ir(
947947

948948
generate_singledispatch_dispatch_function(builder, main_func_name, fitem)
949949
args, _, blocks, _, fn_info = builder.leave()
950-
dispatch_func_ir = add_call_to_callable_class(builder, args, blocks, sig, fn_info)
950+
dispatch_callable_class = add_call_to_callable_class(builder, args, blocks, sig, fn_info)
951+
builder.functions.append(dispatch_callable_class)
951952
add_get_to_callable_class(builder, fn_info)
952953
add_register_method_to_callable_class(builder, fn_info)
953954
func_reg = instantiate_callable_class(builder, fn_info)
955+
dispatch_func_ir = generate_dispatch_glue_native_function(
956+
builder, fitem, dispatch_callable_class.decl, dispatch_name
957+
)
954958

955959
return dispatch_func_ir, func_reg
956960

957961

962+
def generate_dispatch_glue_native_function(
963+
builder: IRBuilder,
964+
fitem: FuncDef,
965+
callable_class_decl: FuncDecl,
966+
dispatch_name: str,
967+
) -> FuncIR:
968+
line = fitem.line
969+
builder.enter()
970+
# We store the callable class in the globals dict for this function
971+
callable_class = builder.load_global_str(dispatch_name, line)
972+
decl = builder.mapper.func_to_decl[fitem]
973+
arg_info = get_args(builder, decl.sig.args, line)
974+
args = [callable_class] + arg_info.args
975+
arg_kinds = [ArgKind.ARG_POS] + arg_info.arg_kinds
976+
arg_names = arg_info.arg_names
977+
arg_names.insert(0, 'self')
978+
ret_val = builder.builder.call(callable_class_decl, args, arg_kinds, arg_names, line)
979+
builder.add(Return(ret_val))
980+
arg_regs, _, blocks, _, fn_info = builder.leave()
981+
return FuncIR(decl, arg_regs, blocks)
982+
983+
958984
def generate_singledispatch_callable_class_ctor(builder: IRBuilder) -> None:
959985
"""Create an __init__ that sets registry and dispatch_cache to empty dicts"""
960986
line = -1

mypyc/test-data/irbuild-singledispatch.test

+160-19
Original file line numberDiff line numberDiff line change
@@ -28,25 +28,6 @@ L0:
2828
r4 = PyObject_SetAttr(__mypyc_self__, r3, r2)
2929
r5 = r4 >= 0 :: signed
3030
return 1
31-
def f_obj.__get__(__mypyc_self__, instance, owner):
32-
__mypyc_self__, instance, owner, r0 :: object
33-
r1 :: bit
34-
r2 :: object
35-
L0:
36-
r0 = load_address _Py_NoneStruct
37-
r1 = instance == r0
38-
if r1 goto L1 else goto L2 :: bool
39-
L1:
40-
return __mypyc_self__
41-
L2:
42-
r2 = PyMethod_New(__mypyc_self__, instance)
43-
return r2
44-
def f_obj.register(__mypyc_self__, cls, func):
45-
__mypyc_self__ :: __main__.f_obj
46-
cls, func, r0 :: object
47-
L0:
48-
r0 = CPySingledispatch_RegisterFunction(__mypyc_self__, cls, func)
49-
return r0
5031
def f_obj.__call__(__mypyc_self__, arg):
5132
__mypyc_self__ :: __main__.f_obj
5233
arg :: object
@@ -114,8 +95,168 @@ L7:
11495
r22 = PyObject_CallFunctionObjArgs(r6, arg, 0)
11596
r23 = unbox(bool, r22)
11697
return r23
98+
def f_obj.__get__(__mypyc_self__, instance, owner):
99+
__mypyc_self__, instance, owner, r0 :: object
100+
r1 :: bit
101+
r2 :: object
102+
L0:
103+
r0 = load_address _Py_NoneStruct
104+
r1 = instance == r0
105+
if r1 goto L1 else goto L2 :: bool
106+
L1:
107+
return __mypyc_self__
108+
L2:
109+
r2 = PyMethod_New(__mypyc_self__, instance)
110+
return r2
111+
def f_obj.register(__mypyc_self__, cls, func):
112+
__mypyc_self__ :: __main__.f_obj
113+
cls, func, r0 :: object
114+
L0:
115+
r0 = CPySingledispatch_RegisterFunction(__mypyc_self__, cls, func)
116+
return r0
117+
def f(arg):
118+
arg :: object
119+
r0 :: dict
120+
r1 :: str
121+
r2 :: object
122+
r3 :: bool
123+
L0:
124+
r0 = __main__.globals :: static
125+
r1 = 'f'
126+
r2 = CPyDict_GetItem(r0, r1)
127+
r3 = f_obj.__call__(r2, arg)
128+
return r3
117129
def g(arg):
118130
arg :: int
119131
L0:
120132
return 1
121133

134+
135+
[case testCallsToSingledispatchFunctionsAreNative]
136+
from functools import singledispatch
137+
138+
@singledispatch
139+
def f(x: object) -> None:
140+
pass
141+
142+
def test():
143+
f('a')
144+
[out]
145+
def __mypyc_singledispatch_main_function_f__(x):
146+
x :: object
147+
L0:
148+
return 1
149+
def f_obj.__init__(__mypyc_self__):
150+
__mypyc_self__ :: __main__.f_obj
151+
r0 :: dict
152+
r1 :: bool
153+
r2 :: dict
154+
r3 :: str
155+
r4 :: int32
156+
r5 :: bit
157+
L0:
158+
r0 = PyDict_New()
159+
__mypyc_self__.registry = r0; r1 = is_error
160+
r2 = PyDict_New()
161+
r3 = 'dispatch_cache'
162+
r4 = PyObject_SetAttr(__mypyc_self__, r3, r2)
163+
r5 = r4 >= 0 :: signed
164+
return 1
165+
def f_obj.__call__(__mypyc_self__, x):
166+
__mypyc_self__ :: __main__.f_obj
167+
x :: object
168+
r0 :: ptr
169+
r1 :: object
170+
r2 :: dict
171+
r3, r4 :: object
172+
r5 :: bit
173+
r6, r7 :: object
174+
r8 :: str
175+
r9 :: object
176+
r10 :: dict
177+
r11 :: object
178+
r12 :: int32
179+
r13 :: bit
180+
r14 :: object
181+
r15 :: ptr
182+
r16 :: object
183+
r17 :: bit
184+
r18 :: int
185+
r19 :: object
186+
r20 :: None
187+
L0:
188+
r0 = get_element_ptr x ob_type :: PyObject
189+
r1 = load_mem r0 :: builtins.object*
190+
keep_alive x
191+
r2 = __mypyc_self__.dispatch_cache
192+
r3 = CPyDict_GetWithNone(r2, r1)
193+
r4 = load_address _Py_NoneStruct
194+
r5 = r3 != r4
195+
if r5 goto L1 else goto L2 :: bool
196+
L1:
197+
r6 = r3
198+
goto L3
199+
L2:
200+
r7 = functools :: module
201+
r8 = '_find_impl'
202+
r9 = CPyObject_GetAttr(r7, r8)
203+
r10 = __mypyc_self__.registry
204+
r11 = PyObject_CallFunctionObjArgs(r9, r1, r10, 0)
205+
r12 = CPyDict_SetItem(r2, r1, r11)
206+
r13 = r12 >= 0 :: signed
207+
r6 = r11
208+
L3:
209+
r14 = load_address PyLong_Type
210+
r15 = get_element_ptr r6 ob_type :: PyObject
211+
r16 = load_mem r15 :: builtins.object*
212+
keep_alive r6
213+
r17 = r16 == r14
214+
if r17 goto L4 else goto L5 :: bool
215+
L4:
216+
r18 = unbox(int, r6)
217+
unreachable
218+
L5:
219+
r19 = PyObject_CallFunctionObjArgs(r6, x, 0)
220+
r20 = unbox(None, r19)
221+
return r20
222+
def f_obj.__get__(__mypyc_self__, instance, owner):
223+
__mypyc_self__, instance, owner, r0 :: object
224+
r1 :: bit
225+
r2 :: object
226+
L0:
227+
r0 = load_address _Py_NoneStruct
228+
r1 = instance == r0
229+
if r1 goto L1 else goto L2 :: bool
230+
L1:
231+
return __mypyc_self__
232+
L2:
233+
r2 = PyMethod_New(__mypyc_self__, instance)
234+
return r2
235+
def f_obj.register(__mypyc_self__, cls, func):
236+
__mypyc_self__ :: __main__.f_obj
237+
cls, func, r0 :: object
238+
L0:
239+
r0 = CPySingledispatch_RegisterFunction(__mypyc_self__, cls, func)
240+
return r0
241+
def f(x):
242+
x :: object
243+
r0 :: dict
244+
r1 :: str
245+
r2 :: object
246+
r3 :: None
247+
L0:
248+
r0 = __main__.globals :: static
249+
r1 = 'f'
250+
r2 = CPyDict_GetItem(r0, r1)
251+
r3 = f_obj.__call__(r2, x)
252+
return r3
253+
def test():
254+
r0 :: str
255+
r1 :: None
256+
r2 :: object
257+
L0:
258+
r0 = 'a'
259+
r1 = f(r0)
260+
r2 = box(None, 1)
261+
return r2
262+

0 commit comments

Comments
 (0)