From 07884f10eea6d4009115c4bf88bc2ddc23f8a894 Mon Sep 17 00:00:00 2001 From: William S Fulton Date: Tue, 1 Jan 2019 12:12:56 +0000 Subject: [PATCH] Python - remove duplicate proxy method definitions for global function wrappers. Global functions previously generated two definitions, eg: def foo(): return _example.foo() foo = _example.foo The first definition is replaced by the second definition and so the second definition is the one used when the method is actually called. Now just the first definition is generated by default and if the -fastproxy command line option is used, just the second definition is generated. The second definition is faster as it avoids the proxy Python method as it calls the low-level C wrapper directly. Using both -fastproxy and -olddefs command line options will restore the previously generated code as it will generate both method definitions. With this change, the wrappers for global C/C++ functions and C++ class methods now work in the same way wrt to generating just a proxy method by default and control via -fastproxy/-olddefs options. Closes #639. --- CHANGES.current | 21 +++++++++++++++++++++ Doc/Manual/Python.html | 12 +++++++++++- Source/Modules/python.cxx | 39 +++++++++++++++++++++++++-------------- 3 files changed, 57 insertions(+), 15 deletions(-) diff --git a/CHANGES.current b/CHANGES.current index 21cd23149..ef46ccd12 100644 --- a/CHANGES.current +++ b/CHANGES.current @@ -7,6 +7,27 @@ the issue number to the end of the URL: https://github.com/swig/swig/issues/ Version 4.0.0 (in progress) =========================== +2019-01-01: wsfulton + [Python] #639 remove duplicate proxy method definitions for global function wrappers. + + Global functions previously generated two definitions, eg: + + def foo(): + return _example.foo() + foo = _example.foo + + The first definition is replaced by the second definition and so the second definition + is the one used when the method is actually called. Now just the first definition is + generated by default and if the -fastproxy command line option is used, just the second + definition is generated. The second definition is faster as it avoids the proxy Python + method as it calls the low-level C wrapper directly. Using both -fastproxy and -olddefs + command line options will restore the previously generated code as it will generate both + method definitions. + + With this change, the wrappers for global C/C++ functions and C++ class methods now work + in the same way wrt to generating just a proxy method by default and control via + -fastproxy/-olddefs options. + 2018-12-20: hasinoff,wsfulton [Java] #1334 Set Java thread name to native thread name when using directors. diff --git a/Doc/Manual/Python.html b/Doc/Manual/Python.html index ddcd6407b..8d1678a7f 100644 --- a/Doc/Manual/Python.html +++ b/Doc/Manual/Python.html @@ -3913,6 +3913,7 @@ class Go(object):

+Each method in the Python class contains a Python proxy method which passes the arguments on to the underlying function in the low-level C/C++ module (_example in this case). The generated proxy class when using -fastproxy is:

@@ -3928,7 +3929,12 @@ class Go(object):

-where _swig_new_instance_method adds the method to the proxy class via C API calls. +where _swig_new_instance_method adds the method to the proxy class via C API calls for direct access to the underlying function in the low-level C/C++ module. +Note that for some methods it is not possible to generate the direct access call and so -fastproxy is ignored. +This happens, for example, when adding additional code to Python proxy methods, such as using %pythonprepend. +

+ +

The overhead calling into C/C++ from Python is reduced slightly using -fastproxy. Below are some timings in microseconds calling the 3 functions in the example above. Also included in the table for comparison is using the -builtin option covered in the @@ -3995,6 +4001,10 @@ The class defines each method in two different ways. The first definition is rep While this possibly provides the best of both worlds, the time to import the module will be slighly slower when the class is defined due to the additional method definitions.

+

+The command line options mentioned above also apply to wrapped C/C++ global functions, not just class methods. +

+

38.7 Tips and techniques

diff --git a/Source/Modules/python.cxx b/Source/Modules/python.cxx index a0f049449..83858d44c 100755 --- a/Source/Modules/python.cxx +++ b/Source/Modules/python.cxx @@ -2144,6 +2144,9 @@ public: if (nn) n = nn; + Parm *parms = Getattr(n, "parms"); + bool varargs = parms ? emit_isvarargs(parms) : 0; + /* We prefer to explicitly list all parameters of the C function in the generated Python code as this makes the function more convenient to use, however in some cases we must replace the real parameters list with just @@ -2153,8 +2156,9 @@ public: 2. We were explicitly asked to use the "compact" arguments form. 3. We were explicitly asked to use default args from C via the "python:cdefaultargs" feature. 4. One of the default argument values can't be represented in Python. + 5. Varargs that haven't been forced to use a fixed number of arguments with %varargs. */ - if (is_real_overloaded(n) || GetFlag(n, "feature:compactdefaultargs") || GetFlag(n, "feature:python:cdefaultargs") || !is_representable_as_pyargs(n)) { + if (is_real_overloaded(n) || GetFlag(n, "feature:compactdefaultargs") || GetFlag(n, "feature:python:cdefaultargs") || !is_representable_as_pyargs(n) || varargs) { String *parms = NewString(""); if (in_class) Printf(parms, "self, "); @@ -2295,21 +2299,28 @@ public: void emitFunctionShadowHelper(Node *n, File *f_dest, String *name, int kw) { String *parms = make_pyParmList(n, false, false, kw); String *callParms = make_pyParmList(n, false, true, kw); - /* Make a wrapper function to insert the code into */ - Printv(f_dest, "\ndef ", name, "(", parms, ")", returnTypeAnnotation(n), ":\n", NIL); - if (have_docstring(n)) - Printv(f_dest, tab4, docstring(n, AUTODOC_FUNC, tab4), "\n", NIL); - if (have_pythonprepend(n)) - Printv(f_dest, indent_pythoncode(pythonprepend(n), tab4, Getfile(n), Getline(n), "%pythonprepend or %feature(\"pythonprepend\")"), "\n", NIL); - if (have_pythonappend(n)) { - Printv(f_dest, tab4 "val = ", funcCall(name, callParms), "\n", NIL); - Printv(f_dest, indent_pythoncode(pythonappend(n), tab4, Getfile(n), Getline(n), "%pythonappend or %feature(\"pythonappend\")"), "\n", NIL); - Printv(f_dest, tab4 "return val\n", NIL); - } else { - Printv(f_dest, tab4 "return ", funcCall(name, callParms), "\n", NIL); + + // Callbacks need the C function in order to extract the pointer from the swig_ptr: string + bool fast = (fastproxy && !have_addtofunc(n)) || Getattr(n, "feature:callback"); + + if (!fast || olddefs) { + /* Make a wrapper function to insert the code into */ + Printv(f_dest, "\n", "def ", name, "(", parms, ")", returnTypeAnnotation(n), ":\n", NIL); + if (have_docstring(n)) + Printv(f_dest, tab4, docstring(n, AUTODOC_FUNC, tab4), "\n", NIL); + if (have_pythonprepend(n)) + Printv(f_dest, indent_pythoncode(pythonprepend(n), tab4, Getfile(n), Getline(n), "%pythonprepend or %feature(\"pythonprepend\")"), "\n", NIL); + if (have_pythonappend(n)) { + Printv(f_dest, tab4 "val = ", funcCall(name, callParms), "\n", NIL); + Printv(f_dest, indent_pythoncode(pythonappend(n), tab4, Getfile(n), Getline(n), "%pythonappend or %feature(\"pythonappend\")"), "\n", NIL); + Printv(f_dest, tab4 "return val\n", NIL); + } else { + Printv(f_dest, tab4 "return ", funcCall(name, callParms), "\n", NIL); + } } - if (!have_addtofunc(n)) { + // Below may result in a 2nd definition of the method when -olddefs is used. The Python interpreter will use the second definition as it overwrites the first. + if (fast) { /* If there is no addtofunc directive then just assign from the extension module (for speed up) */ Printv(f_dest, name, " = ", module, ".", name, "\n", NIL); }