Skip to content

Commit

Permalink
Pass scalar by values for user functions
Browse files Browse the repository at this point in the history
Passing scalar by ref (through universal reference) leads to worst
code, so avoid the situation when we can.
  • Loading branch information
serge-sans-paille committed Apr 27, 2023
1 parent 7158247 commit c9d6b2c
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 9 deletions.
2 changes: 1 addition & 1 deletion docs/TUTORIAL.rst
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ subset of Python AST) into a C++ AST::
>>> from pythran import backend
>>> cxx = pm.dump(backend.Cxx, tree)
>>> str(cxx)
'#include <pythonic/include/operator_/add.hpp>\n#include <pythonic/include/operator_/lt.hpp>\n#include <pythonic/include/operator_/sub.hpp>\n#include <pythonic/operator_/add.hpp>\n#include <pythonic/operator_/lt.hpp>\n#include <pythonic/operator_/sub.hpp>\nnamespace \n{\n namespace __pythran_tutorial_module\n {\n struct fib\n {\n typedef void callable;\n typedef void pure;\n template <typename argument_type0 >\n struct type\n {\n typedef typename std::remove_cv<typename std::remove_reference<argument_type0>::type>::type __type0;\n typedef __type0 __type1;\n typedef typename pythonic::returnable<__type1>::type __type2;\n typedef __type2 result_type;\n } \n ;\n template <typename argument_type0 >\n inline\n typename type<argument_type0>::result_type operator()(argument_type0&& n) const\n ;\n } ;\n template <typename argument_type0 >\n inline\n typename fib::type<argument_type0>::result_type fib::operator()(argument_type0&& n) const\n {\n return (((bool)pythonic::operator_::lt(n, 2L)) ? typename __combined<decltype(n), decltype(pythonic::operator_::add(fib()(pythonic::operator_::sub(n, 1L)), fib()(pythonic::operator_::sub(n, 2L))))>::type(n) : typename __combined<decltype(n), decltype(pythonic::operator_::add(fib()(pythonic::operator_::sub(n, 1L)), fib()(pythonic::operator_::sub(n, 2L))))>::type(pythonic::operator_::add(fib()(pythonic::operator_::sub(n, 1L)), fib()(pythonic::operator_::sub(n, 2L)))));\n }\n }\n}'
'#include <pythonic/include/operator_/add.hpp>\n#include <pythonic/include/operator_/lt.hpp>\n#include <pythonic/include/operator_/sub.hpp>\n#include <pythonic/operator_/add.hpp>\n#include <pythonic/operator_/lt.hpp>\n#include <pythonic/operator_/sub.hpp>\nnamespace \n{\n namespace __pythran_tutorial_module\n {\n struct fib\n {\n typedef void callable;\n typedef void pure;\n template <typename argument_type0 >\n struct type\n {\n typedef typename std::remove_cv<typename std::remove_reference<argument_type0>::type>::type __type0;\n typedef __type0 __type1;\n typedef typename pythonic::returnable<__type1>::type __type2;\n typedef __type2 result_type;\n } \n ;\n template <typename argument_type0 >\n inline\n typename type<argument_type0>::result_type operator()(argument_type0 n) const\n ;\n } ;\n template <typename argument_type0 >\n inline\n typename fib::type<argument_type0>::result_type fib::operator()(argument_type0 n) const\n {\n return (((bool)pythonic::operator_::lt(n, 2L)) ? typename __combined<decltype(n), decltype(pythonic::operator_::add(pythonic::types::call(fib(), pythonic::operator_::sub(n, 1L)), pythonic::types::call(fib(), pythonic::operator_::sub(n, 2L))))>::type(n) : typename __combined<decltype(n), decltype(pythonic::operator_::add(pythonic::types::call(fib(), pythonic::operator_::sub(n, 1L)), pythonic::types::call(fib(), pythonic::operator_::sub(n, 2L))))>::type(pythonic::operator_::add(pythonic::types::call(fib(), pythonic::operator_::sub(n, 1L)), pythonic::types::call(fib(), pythonic::operator_::sub(n, 2L)))));\n }\n }\n}'

The above string is understandable by a C++11 compiler, but it quickly reaches the limit of our developer brain, so most of the time, we are more comfortable with the Python backend::

Expand Down
26 changes: 18 additions & 8 deletions pythran/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from pythran.analyses import LocalNodeDeclarations, GlobalDeclarations, Scope
from pythran.analyses import YieldPoints, IsAssigned, ASTMatcher, AST_any
from pythran.analyses import RangeValues, PureExpressions, Dependencies
from pythran.analyses import Immediates, Ancestors
from pythran.analyses import Immediates, Ancestors, StrictAliases
from pythran.config import cfg
from pythran.cxxgen import Template, Include, Namespace, CompilationUnit
from pythran.cxxgen import Statement, Block, AnnotatedStatement, Typedef, Label
Expand Down Expand Up @@ -156,12 +156,7 @@ def make_function_declaration(self, node, rtype, name, ftypes, fargs,
arguments = list()
first_default = len(node.args.args) - len(node.args.defaults)
for i, (t, a, d) in enumerate(zip(ftypes, fargs, defaults)):
# because universal reference and default don't get on well
if isinstance(self, CxxGenerator) or i >= first_default:
rvalue_ref = ""
else:
rvalue_ref = "&&"
argument = Value(t + rvalue_ref, "{0}{1}".format(a, make_default(d)))
argument = Value(t, "{0}{1}".format(a, make_default(d)))
arguments.append(argument)
return FunctionDeclaration(Value(rtype, name), arguments, *attributes)

Expand Down Expand Up @@ -713,6 +708,17 @@ def can_use_c_for(self, node):
def make_assign(self, local_iter_decl, local_iter, iterable):
return "{0} {1} = {2}".format(local_iter_decl, local_iter, iterable)

def is_user_function(self, func):
aliases = self.strict_aliases[func]
if not aliases:
return False
for alias in aliases:
if not isinstance(alias, ast.FunctionDef):
return False
if self.gather(YieldPoints, alias):
return False
return True

@cxx_loop
def visit_For(self, node):
"""
Expand Down Expand Up @@ -979,6 +985,10 @@ def visit_Call(self, node):
else:
arg = args[0]
result = fmt.format(attr, arg)
# Avoid passing scalars by ref as it prevents some C++ optimization.
# pythonic::types::call (tries to) handle that gracefully.
elif args and self.is_user_function(node.func):
result = "pythonic::types::call({})".format(", ".join([func] + args))
else:
result = "{}({})".format(func, ", ".join(args))

Expand Down Expand Up @@ -1370,7 +1380,7 @@ def __init__(self):
self.result = None
super(Cxx, self).__init__(Dependencies, GlobalDeclarations, Types,
Scope, RangeValues, PureExpressions,
Immediates, Ancestors)
Immediates, Ancestors, StrictAliases)

# mod
def visit_Module(self, node):
Expand Down
32 changes: 32 additions & 0 deletions pythran/pythonic/include/types/assignable.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#define PYTHONIC_INCLUDE_TYPES_ASSIGNABLE_HPP

#include <type_traits>
#include <utility>

PYTHONIC_NS_BEGIN

Expand All @@ -14,6 +15,37 @@ namespace types
return t;
}

// Pass all scalars by value when called through pythonic::types::call
template <class T, bool is_integral>
struct by_val {
using type = T;
};
template <class T>
struct by_val<T &, true> {
using type = T;
};
template <class T>
struct by_val<T &&, true> {
using type = T;
};
template <class T>
struct by_val<T const &, true> {
using type = T;
};

template <class T>
using by_val_t = typename by_val<
T, std::is_integral<typename std::decay<T>::type>::value>::type;

template <class F, class... Args>
static inline auto call(F &&f, Args &&...args)
-> decltype(std::forward<F>(f).template operator()<by_val_t<Args>...>(
std::forward<Args>(args)...))
{
return std::forward<F>(f).template operator()<by_val_t<Args>...>(
std::forward<Args>(args)...);
}

} // namespace types

struct dummy {
Expand Down

0 comments on commit c9d6b2c

Please sign in to comment.