diff --git a/benchmarks/function_0.py b/benchmarks/function_0.py new file mode 100644 index 00000000..15cc2e0d --- /dev/null +++ b/benchmarks/function_0.py @@ -0,0 +1,8 @@ +def f(a, b, c): + pass + +for i in range(10000000): + f(1, 2, 3) + f(1, 2, 3) + f(1, 2, 3) + f(1, 2, 3) diff --git a/benchmarks/function_1.py b/benchmarks/function_1.py new file mode 100644 index 00000000..72b309b5 --- /dev/null +++ b/benchmarks/function_1.py @@ -0,0 +1,11 @@ +class A: + def f(self, a, b, c): + pass + +a = A() +for i in range(10000000): + a.f(1, 2, 3) + a.f(1, 2, 3) + a.f(1, 2, 3) + a.f(1, 2, 3) + diff --git a/include/pocketpy/codeobject.h b/include/pocketpy/codeobject.h index ab0b5baa..a34f7022 100644 --- a/include/pocketpy/codeobject.h +++ b/include/pocketpy/codeobject.h @@ -107,7 +107,9 @@ struct FuncDecl { Str signature; // signature of this function Str docstring; // docstring of this function - bool is_simple; + + bool is_simple; // whether this function is simple (no *arg, **kwarg, nested) + bool is_empty; // whether this function is empty (no code) NameDictInt kw_to_index; diff --git a/src/compiler.cpp b/src/compiler.cpp index 9b6bc251..aee296af 100644 --- a/src/compiler.cpp +++ b/src/compiler.cpp @@ -67,6 +67,14 @@ namespace pkpy{ if(func->kwargs.size() > 0) func->is_simple = false; if(func->starred_arg >= 0) func->is_simple = false; if(func->starred_kwarg >= 0) func->is_simple = false; + + func->is_empty = false; + if(func->code->codes.size() == 1){ + Bytecode bc = func->code->codes[0]; + if(bc.op == OP_RETURN_VALUE && bc.arg == 1){ + func->is_empty = true; + } + } } contexts.pop(); } diff --git a/src/vm.cpp b/src/vm.cpp index 68b2e387..dddbf233 100644 --- a/src/vm.cpp +++ b/src/vm.cpp @@ -862,7 +862,7 @@ PyObject* VM::vectorcall(int ARGC, int KWARGC, bool op_call){ PyObject* callable = p1[-(ARGC + 2)]; Type callable_t = _tp(callable); - bool method_call = p0[1] != PY_NULL; + int method_call = p0[1] != PY_NULL; // handle boundmethod, do a patch if(callable_t == tp_bound_method){ @@ -872,11 +872,11 @@ PyObject* VM::vectorcall(int ARGC, int KWARGC, bool op_call){ callable_t = _tp(callable); p1[-(ARGC + 2)] = bm.func; p1[-(ARGC + 1)] = bm.self; - method_call = true; + method_call = 1; // [unbound, self, args..., kwargs...] } - ArgsView args(p1 - ARGC - int(method_call), p1); + ArgsView args(p1 - ARGC - method_call, p1); ArgsView kwargs(p1, s_data._sp); PyObject** _base = args.begin(); @@ -884,11 +884,13 @@ PyObject* VM::vectorcall(int ARGC, int KWARGC, bool op_call){ if(callable_t == tp_function){ /*****************_py_call*****************/ + // check stack overflow if(s_data.is_overflow()) StackOverflowError(); const Function& fn = PK_OBJ_GET(Function, callable); const CodeObject* co = fn.decl->code.get(); int co_nlocals = co->varnames.size(); + if(fn.decl->is_simple){ if(args.size() != fn.decl->args.size()){ TypeError(_S( @@ -896,6 +898,13 @@ PyObject* VM::vectorcall(int ARGC, int KWARGC, bool op_call){ )); } if(!kwargs.empty()) TypeError(_S(co->name, "() takes no keyword arguments")); + + // fast path for empty function + if(fn.decl->is_empty){ + s_data.reset(p0); + return None; + } + // [callable, , args..., local_vars...] // ^p0 ^p1 ^_sp s_data.reset(_base + co_nlocals); diff --git a/tests/21_functions.py b/tests/21_functions.py index b7d2d621..3a732f89 100644 --- a/tests/21_functions.py +++ b/tests/21_functions.py @@ -140,3 +140,15 @@ try: exit(1) except TypeError: pass + +# empty function +def f(a, b, c): + pass + +assert f(1, 2, 3) == None + +class A: + def f(self, a, b, c): + pass + +assert A().f(1, 2, 3) == None