Skip to content

Commit a54a177

Browse files
authored
[mypyc] Use native calls in singledispatch dispatch functions (#10888)
When generating calls to registered implementations in the dispatch function for a singledispatch function, use a native call instead of a non-native call to registered implementations to make dispatching faster. We still use non-native calls when the registered function has non-register decorators, as using a native call in that case would cause us to call the function without any modifications that the decorator might do.
1 parent 7808e82 commit a54a177

File tree

3 files changed

+67
-9
lines changed

3 files changed

+67
-9
lines changed

mypyc/irbuild/function.py

+19-9
Original file line numberDiff line numberDiff line change
@@ -833,21 +833,31 @@ def generate_singledispatch_dispatch_function(
833833
current_func_decl = builder.mapper.func_to_decl[fitem]
834834
arg_info = get_args(builder, current_func_decl.sig.args, line)
835835

836-
def gen_func_call_and_return(func_name: str, fullname: Optional[str] = None) -> None:
837-
func = load_func(builder, func_name, fullname, line)
838-
ret_val = builder.builder.py_call(
839-
func, arg_info.args, line, arg_info.arg_kinds, arg_info.arg_names
840-
)
836+
def gen_func_call_and_return(
837+
func_name: str,
838+
fdef: FuncDef,
839+
fullname: Optional[str] = None
840+
) -> None:
841+
if is_decorated(builder, fdef):
842+
func = load_func(builder, func_name, fullname, line)
843+
ret_val = builder.builder.py_call(
844+
func, arg_info.args, line, arg_info.arg_kinds, arg_info.arg_names
845+
)
846+
else:
847+
func_decl = builder.mapper.func_to_decl[fdef]
848+
ret_val = builder.builder.call(
849+
func_decl, arg_info.args, arg_info.arg_kinds, arg_info.arg_names, line
850+
)
841851
coerced = builder.coerce(ret_val, current_func_decl.sig.ret_type, line)
842852
builder.nonlocal_control[-1].gen_return(builder, coerced, line)
843853

844854
# Add all necessary imports of other modules that have registered functions in other modules
845855
# We're doing this in a separate pass over the implementations because that avoids the
846856
# complexity and code size implications of generating this import before every call to a
847857
# registered implementation that might need this imported
848-
# TODO: avoid adding imports if we use native calls for all of the registered implementations
849-
# in a module (once we add support for using native calls for registered implementations)
850858
for _, impl in impls:
859+
if not is_decorated(builder, impl):
860+
continue
851861
module_name = impl.fullname.rsplit('.')[0]
852862
if module_name not in builder.imports:
853863
# We need to generate an import here because the module needs to be imported before we
@@ -868,15 +878,15 @@ def gen_func_call_and_return(func_name: str, fullname: Optional[str] = None) ->
868878
# The shortname of a function is just '{class}.{func_name}', and we don't support
869879
# singledispatchmethod yet, so that is always the same as the function name
870880
name = short_id_from_name(impl.name, impl.name, impl.line)
871-
gen_func_call_and_return(name, fullname=impl.fullname)
881+
gen_func_call_and_return(name, impl, fullname=impl.fullname)
872882
builder.activate_block(next_impl)
873883

874884
# We don't pass fullname here because getting the fullname of the main generated singledispatch
875885
# function isn't easy, and we don't need it because the fullname is only needed for making sure
876886
# we load the function from another module instead of the globals dict if it's defined in
877887
# another module, which will never be true for the main singledispatch function (it's always
878888
# generated in the same module as the dispatch function)
879-
gen_func_call_and_return(main_singledispatch_function_name)
889+
gen_func_call_and_return(main_singledispatch_function_name, fitem)
880890

881891

882892
def gen_dispatch_func_ir(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
[case testNativeCallsUsedInDispatchFunction]
2+
from functools import singledispatch
3+
@singledispatch
4+
def f(arg) -> bool:
5+
return False
6+
7+
@f.register
8+
def g(arg: int) -> bool:
9+
return True
10+
[out]
11+
def __mypyc___mypyc_singledispatch_main_function_f___decorator_helper__(arg):
12+
arg :: object
13+
L0:
14+
return 0
15+
def __mypyc_f_decorator_helper__(arg):
16+
arg, r0 :: object
17+
r1 :: int32
18+
r2 :: bit
19+
r3 :: bool
20+
r4 :: int
21+
r5 :: bool
22+
r6 :: dict
23+
r7 :: str
24+
r8, r9 :: object
25+
r10 :: bool
26+
L0:
27+
r0 = load_address PyLong_Type
28+
r1 = PyObject_IsInstance(arg, r0)
29+
r2 = r1 >= 0 :: signed
30+
r3 = truncate r1: int32 to builtins.bool
31+
if r3 goto L1 else goto L2 :: bool
32+
L1:
33+
r4 = unbox(int, arg)
34+
r5 = g(r4)
35+
return r5
36+
L2:
37+
r6 = __main__.globals :: static
38+
r7 = '__mypyc___mypyc_singledispatch_main_function_f___decorator_helper__'
39+
r8 = CPyDict_GetItem(r6, r7)
40+
r9 = PyObject_CallFunctionObjArgs(r8, arg, 0)
41+
r10 = unbox(bool, r9)
42+
return r10
43+
def g(arg):
44+
arg :: int
45+
L0:
46+
return 1
47+

mypyc/test/test_irbuild.py

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
'irbuild-unreachable.test',
3535
'irbuild-isinstance.test',
3636
'irbuild-dunders.test',
37+
'irbuild-singledispatch.test',
3738
]
3839

3940

0 commit comments

Comments
 (0)