diff options
author | robot-piglet <[email protected]> | 2025-06-07 08:40:18 +0300 |
---|---|---|
committer | robot-piglet <[email protected]> | 2025-06-07 08:51:32 +0300 |
commit | 0d8efcced9baa2783ed347bd277d427f4856f2cd (patch) | |
tree | 4c34c507a4f9799b69cb173b614ec557c786cf6c /contrib/python/pythran | |
parent | 4090cb78bac6232cb38fb8dde597d315070fca46 (diff) |
Intermediate changes
commit_hash:929b636025dc9f709f1ff57bfa609d3504adba19
Diffstat (limited to 'contrib/python/pythran')
116 files changed, 1668 insertions, 1202 deletions
diff --git a/contrib/python/pythran/.dist-info/METADATA b/contrib/python/pythran/.dist-info/METADATA index f68434e090c..977fa455ed0 100644 --- a/contrib/python/pythran/.dist-info/METADATA +++ b/contrib/python/pythran/.dist-info/METADATA @@ -1,6 +1,6 @@ -Metadata-Version: 2.1 +Metadata-Version: 2.4 Name: pythran -Version: 0.17.0 +Version: 0.18.0 Summary: Ahead of Time compiler for numeric kernels Author-email: Serge Guelton <[email protected]> License: Copyright (c) 2012, HPC Project and Serge Guelton @@ -52,24 +52,25 @@ Requires-Python: >=3.7 Description-Content-Type: text/x-rst License-File: LICENSE License-File: AUTHORS -Requires-Dist: ply >=3.4 +Requires-Dist: ply>=3.4 Requires-Dist: setuptools -Requires-Dist: gast ~=0.6.0 +Requires-Dist: gast~=0.6.0 Requires-Dist: numpy -Requires-Dist: beniget ~=0.4.0 +Requires-Dist: beniget~=0.4.0 Provides-Extra: doc -Requires-Dist: numpy ; extra == 'doc' -Requires-Dist: nbsphinx ; extra == 'doc' -Requires-Dist: scipy ; extra == 'doc' -Requires-Dist: guzzle-sphinx-theme ; extra == 'doc' +Requires-Dist: numpy; extra == "doc" +Requires-Dist: nbsphinx; extra == "doc" +Requires-Dist: scipy; extra == "doc" +Requires-Dist: guzzle_sphinx_theme; extra == "doc" Provides-Extra: test -Requires-Dist: ipython ; extra == 'test' -Requires-Dist: nbval ; extra == 'test' -Requires-Dist: cython ; extra == 'test' -Requires-Dist: wheel ; extra == 'test' -Requires-Dist: packaging ; extra == 'test' -Requires-Dist: ninja ; extra == 'test' -Requires-Dist: meson ; extra == 'test' +Requires-Dist: ipython; extra == "test" +Requires-Dist: nbval; extra == "test" +Requires-Dist: cython; extra == "test" +Requires-Dist: wheel; extra == "test" +Requires-Dist: packaging; extra == "test" +Requires-Dist: ninja; extra == "test" +Requires-Dist: meson; extra == "test" +Dynamic: license-file Pythran ####### @@ -119,7 +120,7 @@ Using ``pip`` Using ``mamba`` or ``conda`` **************************** -1. Using ``mamba`` (https://github.com/conda-forge/miniforge#mambaforge) or ``conda`` (https://github.com/conda-forge/miniforge) +1. Using ``mamba`` (https://github.com/conda-forge/miniforge) or ``conda`` (https://github.com/conda-forge/miniforge) 2. Run:: diff --git a/contrib/python/pythran/README.rst b/contrib/python/pythran/README.rst index a8ac9706212..eeb9384b2e9 100644 --- a/contrib/python/pythran/README.rst +++ b/contrib/python/pythran/README.rst @@ -46,7 +46,7 @@ Using ``pip`` Using ``mamba`` or ``conda`` **************************** -1. Using ``mamba`` (https://github.com/conda-forge/miniforge#mambaforge) or ``conda`` (https://github.com/conda-forge/miniforge) +1. Using ``mamba`` (https://github.com/conda-forge/miniforge) or ``conda`` (https://github.com/conda-forge/miniforge) 2. Run:: diff --git a/contrib/python/pythran/pythran/analyses/__init__.py b/contrib/python/pythran/pythran/analyses/__init__.py index 3c149f9b0bd..9948c9260c5 100644 --- a/contrib/python/pythran/pythran/analyses/__init__.py +++ b/contrib/python/pythran/pythran/analyses/__init__.py @@ -19,7 +19,7 @@ from .constant_expressions import ConstantExpressions from .dependencies import Dependencies from .extended_syntax_check import ExtendedSyntaxCheck from .fixed_size_list import FixedSizeList -from .global_declarations import GlobalDeclarations +from .global_declarations import GlobalDeclarations, NonlocalDeclarations from .global_effects import GlobalEffects from .globals_analysis import Globals from .has_return import HasReturn, HasBreak, HasContinue diff --git a/contrib/python/pythran/pythran/analyses/aliases.py b/contrib/python/pythran/pythran/analyses/aliases.py index 79f050e7111..ff4f2a937fa 100644 --- a/contrib/python/pythran/pythran/analyses/aliases.py +++ b/contrib/python/pythran/pythran/analyses/aliases.py @@ -71,7 +71,7 @@ for module in MODULES.values(): save_intrinsic_alias(module) -class Aliases(ModuleAnalysis): +class Aliases(ModuleAnalysis[GlobalDeclarations]): ''' Gather aliasing informations across nodes @@ -81,10 +81,11 @@ class Aliases(ModuleAnalysis): RetId = '@' + ResultType = dict + def __init__(self): - self.result = dict() + super().__init__() self.aliases = None - super(Aliases, self).__init__(GlobalDeclarations) @staticmethod def dump(result, filter=None): @@ -420,6 +421,21 @@ class Aliases(ModuleAnalysis): >>> Aliases.dump(result, filter=ast.Subscript) [a, b][c] => ['a', 'b'] + Also work in case of a dict: + + >>> module = ast.parse('def foo(a, b, c): return {a:b}[c]') + >>> result = pm.gather(Aliases, module) + >>> Aliases.dump(result, filter=ast.Subscript) + {a: b}[c] => ['b'] + + Even when built in several statements: + >>> module = ast.parse('def foo(a, b, c): d = {} ; d[a] = b; return d[c]') + >>> result = pm.gather(Aliases, module) + >>> Aliases.dump(result, filter=ast.Subscript) + d[a] => ['b'] + d[c] => ['b'] + + Moreover, in case of a tuple indexed by a constant value, we can further refine the aliasing information: @@ -473,7 +489,7 @@ class Aliases(ModuleAnalysis): def visit_Name(self, node): if node.id not in self.aliases: - raise UnboundIdentifierError + raise UnboundIdentifierError(node.id) return self.add(node, self.aliases[node.id]) def visit_Tuple(self, node): @@ -661,10 +677,28 @@ class Aliases(ModuleAnalysis): a_id = alias.id self.aliases[a_id] = self.aliases[a_id].union((t,)) self.add(t, self.aliases[t.id]) + elif isinstance(t, ast.Subscript): + def wrap(t, aliases): + if isinstance(t, ast.Subscript): + alias, wrapped = wrap(t.value, aliases) + return alias, {ContainerOf(wrapped)} + elif isinstance(t, ast.Name): + return t, aliases + else: + raise NotImplementedError + try: + alias, wrapped = wrap(t, value_aliases) + self.aliases[alias.id] = self.aliases[alias.id].union(wrapped) + except NotImplementedError: + ... + self.visit(t) + self.add(t, value_aliases) else: self.visit(t) - visit_AnnAssign = visit_Assign + def visit_AnnAssign(self, node): + self.visit_Assign(node) + self.visit(node.annotation) def visit_For(self, node): ''' diff --git a/contrib/python/pythran/pythran/analyses/ancestors.py b/contrib/python/pythran/pythran/analyses/ancestors.py index 6956378bb34..7364b6b134e 100644 --- a/contrib/python/pythran/pythran/analyses/ancestors.py +++ b/contrib/python/pythran/pythran/analyses/ancestors.py @@ -13,10 +13,11 @@ class Ancestors(ModuleAnalysis): and list of nodes as values. ''' + ResultType = dict + def __init__(self): - self.result = dict() + super().__init__() self.current = tuple() - super(Ancestors, self).__init__() def generic_visit(self, node): self.result[node] = current = self.current @@ -29,6 +30,7 @@ class Ancestors(ModuleAnalysis): class AncestorsWithBody(Ancestors): + # Overload the visit method set from Ancestors visit = ModuleAnalysis.visit def visit_metadata(self, node): diff --git a/contrib/python/pythran/pythran/analyses/argument_effects.py b/contrib/python/pythran/pythran/analyses/argument_effects.py index 808b86349c5..8b436c98418 100644 --- a/contrib/python/pythran/pythran/analyses/argument_effects.py +++ b/contrib/python/pythran/pythran/analyses/argument_effects.py @@ -48,17 +48,17 @@ for module in MODULES.values(): save_function_effect(module) -class ArgumentEffects(ModuleAnalysis): +class ArgumentEffectsHelper(ModuleAnalysis[Aliases, GlobalDeclarations, Intrinsics]): """Gathers inter-procedural effects on function arguments.""" + ResultType = DiGraph + def __init__(self): # There's an edge between src and dest if a parameter of dest is # modified by src - self.result = DiGraph() + super().__init__() self.node_to_functioneffect = {} - super(ArgumentEffects, self).__init__(Aliases, GlobalDeclarations, - Intrinsics) def prepare(self, node): """ @@ -67,7 +67,7 @@ class ArgumentEffects(ModuleAnalysis): Initialisation done for Pythonic functions and default value set for user defined functions. """ - super(ArgumentEffects, self).prepare(node) + super().prepare(node) for i in self.intrinsics: fe = IntrinsicArgumentEffects[i] self.node_to_functioneffect[i] = fe @@ -78,29 +78,6 @@ class ArgumentEffects(ModuleAnalysis): self.node_to_functioneffect[n] = fe self.result.add_node(fe) - def run(self, node): - result = super(ArgumentEffects, self).run(node) - candidates = set(result) - while candidates: - function = candidates.pop() - for ue in enumerate(function.update_effects): - update_effect_idx, update_effect = ue - if not update_effect: - continue - for pred in result.successors(function): - edge = result.edges[function, pred] - for fp in enumerate(edge["formal_parameters"]): - i, formal_parameter_idx = fp - # propagate the impurity backward if needed. - # Afterward we may need another graph iteration - ith_effectiv = edge["effective_parameters"][i] - if(formal_parameter_idx == update_effect_idx and - not pred.update_effects[ith_effectiv]): - pred.update_effects[ith_effectiv] = True - candidates.add(pred) - self.result = {f.func: f.update_effects for f in result} - return self.result - def argument_index(self, node): while isinstance(node, ast.Subscript): node = node.value @@ -199,3 +176,31 @@ class ArgumentEffects(ModuleAnalysis): edge["effective_parameters"].append(n) edge["formal_parameters"].append(i) self.generic_visit(node) + +class ArgumentEffects(ModuleAnalysis[ArgumentEffectsHelper]): + + """Gathers inter-procedural effects on function arguments.""" + + ResultType = dict + + def visit_Module(self, node): + result = self.argument_effects_helper + candidates = set(result) + while candidates: + function = candidates.pop() + for ue in enumerate(function.update_effects): + update_effect_idx, update_effect = ue + if not update_effect: + continue + for pred in result.successors(function): + edge = result.edges[function, pred] + for fp in enumerate(edge["formal_parameters"]): + i, formal_parameter_idx = fp + # propagate the impurity backward if needed. + # Afterward we may need another graph iteration + ith_effectiv = edge["effective_parameters"][i] + if(formal_parameter_idx == update_effect_idx and + not pred.update_effects[ith_effectiv]): + pred.update_effects[ith_effectiv] = True + candidates.add(pred) + self.result = {f.func: f.update_effects for f in result} diff --git a/contrib/python/pythran/pythran/analyses/argument_read_once.py b/contrib/python/pythran/pythran/analyses/argument_read_once.py index 8210fc70d74..676ce77d98c 100644 --- a/contrib/python/pythran/pythran/analyses/argument_read_once.py +++ b/contrib/python/pythran/pythran/analyses/argument_read_once.py @@ -4,24 +4,32 @@ from pythran.analyses.aliases import Aliases from pythran.analyses.global_declarations import GlobalDeclarations from pythran.passmanager import ModuleAnalysis from pythran.tables import MODULES +from pythran.intrinsic import defaultlist import pythran.intrinsic as intrinsic import gast as ast from functools import reduce -class ArgumentReadOnce(ModuleAnalysis): - - """ - Counts the usages of each argument of each function. +def recursive_weight(function, index, predecessors): + # TODO : Find out why it happens in some cases + if len(function.read_effects) <= index: + return 0 + if function.read_effects[index] == -1: + # In case of recursive/cyclic calls + cycle = function in predecessors + predecessors.add(function) + if cycle: + function.read_effects[index] = 2 * function.dependencies( + ArgumentReadOnceHelper.Context(function, index, + predecessors, False)) + else: + function.read_effects[index] = function.dependencies( + ArgumentReadOnceHelper.Context(function, index, + predecessors, True)) + return function.read_effects[index] - Attributes - ---------- - result : {FunctionEffects} - Number of use for each argument of each function. - node_to_functioneffect : {???: ???} - FunctionDef ast node to function effect binding. - """ +class ArgumentReadOnceHelper(ModuleAnalysis[Aliases, GlobalDeclarations]): class FunctionEffects(object): def __init__(self, node): @@ -30,9 +38,15 @@ class ArgumentReadOnce(ModuleAnalysis): if isinstance(node, ast.FunctionDef): self.read_effects = [-1] * len(node.args.args) elif isinstance(node, intrinsic.Intrinsic): - self.read_effects = [ - 1 if isinstance(x, intrinsic.ReadOnceEffect) - else 2 for x in node.argument_effects] + def tocode(x): + return 1 if isinstance(x, intrinsic.ReadOnceEffect) else 2 + + isdefaultlist = isinstance(node.argument_effects, defaultlist) + container = node.argument_effects.content if isdefaultlist else node.argument_effects + self.read_effects = [tocode(x) for x in container] + if isdefaultlist: + default = lambda: tocode(node.argument_effects.default()) + self.read_effects = defaultlist(self.read_effects, default=default) elif isinstance(node, ast.alias): self.read_effects = [] else: @@ -51,11 +65,12 @@ class ArgumentReadOnce(ModuleAnalysis): self.path = path self.global_dependencies = global_dependencies + ResultType = set + def __init__(self): """ Basic initialiser for class attributes. """ - self.result = set() + super().__init__() self.node_to_functioneffect = dict() - super(ArgumentReadOnce, self).__init__(Aliases, GlobalDeclarations) def prepare(self, node): """ @@ -64,10 +79,10 @@ class ArgumentReadOnce(ModuleAnalysis): Initialisation done for Pythonic functions and default values set for user defined functions. """ - super(ArgumentReadOnce, self).prepare(node) + super(ArgumentReadOnceHelper, self).prepare(node) # global functions init for n in self.global_declarations.values(): - fe = ArgumentReadOnce.FunctionEffects(n) + fe = ArgumentReadOnceHelper.FunctionEffects(n) self.node_to_functioneffect[n] = fe self.result.add(fe) @@ -78,7 +93,7 @@ class ArgumentReadOnce(ModuleAnalysis): if isinstance(intr, dict): # Submodule case save_effect(intr) else: - fe = ArgumentReadOnce.FunctionEffects(intr) + fe = ArgumentReadOnceHelper.FunctionEffects(intr) self.node_to_functioneffect[intr] = fe self.result.add(fe) if isinstance(intr, intrinsic.Class): # Class case @@ -87,32 +102,6 @@ class ArgumentReadOnce(ModuleAnalysis): for module in MODULES.values(): save_effect(module) - def run(self, node): - result = super(ArgumentReadOnce, self).run(node) - for fun in result: - for i in range(len(fun.read_effects)): - self.recursive_weight(fun, i, set()) - self.result = {f.func: f.read_effects for f in result} - return self.result - - def recursive_weight(self, function, index, predecessors): - # TODO : Find out why it happens in some cases - if len(function.read_effects) <= index: - return 0 - if function.read_effects[index] == -1: - # In case of recursive/cyclic calls - cycle = function in predecessors - predecessors.add(function) - if cycle: - function.read_effects[index] = 2 * function.dependencies( - ArgumentReadOnce.Context(function, index, - predecessors, False)) - else: - function.read_effects[index] = function.dependencies( - ArgumentReadOnce.Context(function, index, - predecessors, True)) - return function.read_effects[index] - def argument_index(self, node): while isinstance(node, ast.Subscript): node = node.value @@ -219,8 +208,7 @@ class ArgumentReadOnce(ModuleAnalysis): def merger(ctx): base = l0(ctx) if (ctx.index in index_corres) and ctx.global_dependencies: - rec = self.recursive_weight(func, index_corres[ctx.index], - ctx.path) + rec = recursive_weight(func, index_corres[ctx.index], ctx.path) else: rec = 0 return base + rec @@ -236,3 +224,26 @@ class ArgumentReadOnce(ModuleAnalysis): dep = self.generic_visit(node) local = self.local_effect(node.iter, 1) return lambda ctx: dep(ctx) + local(ctx) + +class ArgumentReadOnce(ModuleAnalysis[ArgumentReadOnceHelper]): + + """ + Counts the usages of each argument of each function. + + Attributes + ---------- + result : {FunctionEffects} + Number of use for each argument of each function. + node_to_functioneffect : {???: ???} + FunctionDef ast node to function effect binding. + """ + + ResultType = set + + + def visit_Module(self, node): + result = self.argument_read_once_helper + for fun in result: + for i in range(len(fun.read_effects)): + recursive_weight(fun, i, set()) + self.result = {f.func: f.read_effects for f in result} diff --git a/contrib/python/pythran/pythran/analyses/cfg.py b/contrib/python/pythran/pythran/analyses/cfg.py index b2e9de78f47..59c14b8a723 100644 --- a/contrib/python/pythran/pythran/analyses/cfg.py +++ b/contrib/python/pythran/pythran/analyses/cfg.py @@ -35,9 +35,7 @@ class CFG(FunctionAnalysis): #: control flow without a return statement. NIL = object() - def __init__(self): - self.result = DiGraph() - super(CFG, self).__init__() + ResultType = DiGraph def visit_FunctionDef(self, node): """OUT = node, RAISES = ()""" @@ -64,6 +62,7 @@ class CFG(FunctionAnalysis): visit_Assign = visit_AnnAssign = visit_AugAssign = visit_Import = visit_Pass visit_Expr = visit_Print = visit_ImportFrom = visit_Pass visit_Yield = visit_Delete = visit_Pass + visit_Nonlocal = visit_Pass def visit_Return(self, node): """OUT = (), RAISES = ()""" diff --git a/contrib/python/pythran/pythran/analyses/constant_expressions.py b/contrib/python/pythran/pythran/analyses/constant_expressions.py index 7c6e3938f78..4c05d4024fa 100644 --- a/contrib/python/pythran/pythran/analyses/constant_expressions.py +++ b/contrib/python/pythran/pythran/analyses/constant_expressions.py @@ -11,14 +11,11 @@ import gast as ast -class ConstantExpressions(NodeAnalysis): +class ConstantExpressions(NodeAnalysis[Globals, Locals, Aliases, PureFunctions]): """Identify constant expressions.""" - def __init__(self): - self.result = set() - super(ConstantExpressions, self).__init__(Globals, Locals, Aliases, - PureFunctions) + ResultType = set def add(self, node): self.result.add(node) diff --git a/contrib/python/pythran/pythran/analyses/dependencies.py b/contrib/python/pythran/pythran/analyses/dependencies.py index 93efecf0486..f153558f8c1 100644 --- a/contrib/python/pythran/pythran/analyses/dependencies.py +++ b/contrib/python/pythran/pythran/analyses/dependencies.py @@ -59,9 +59,7 @@ class Dependencies(ModuleAnalysis): ast.FloorDiv: ('operator', 'ifloordiv'), } - def __init__(self): - self.result = set() - super(Dependencies, self).__init__() + ResultType = set def visit_List(self, node): self.result.add(('builtins', 'list')) diff --git a/contrib/python/pythran/pythran/analyses/extended_syntax_check.py b/contrib/python/pythran/pythran/analyses/extended_syntax_check.py index e0673dc75f1..3520ebc14a3 100644 --- a/contrib/python/pythran/pythran/analyses/extended_syntax_check.py +++ b/contrib/python/pythran/pythran/analyses/extended_syntax_check.py @@ -26,7 +26,7 @@ def is_global(node): is_global_constant(node)) -class ExtendedSyntaxCheck(ModuleAnalysis): +class ExtendedSyntaxCheck(ModuleAnalysis[StrictAliases, ArgumentEffects]): """ Perform advanced syntax checking, based on strict aliases analysis: - is there a function redefinition? @@ -34,12 +34,11 @@ class ExtendedSyntaxCheck(ModuleAnalysis): - is there an operation that updates a global variable? """ + ResultType = type(None) def __init__(self): - self.result = None - self.update = False + super().__init__() self.inassert = False self.functions = set() - ModuleAnalysis.__init__(self, StrictAliases, ArgumentEffects) def check_global_with_side_effect(self, node, arg): if not isinstance(arg, ast.Call): diff --git a/contrib/python/pythran/pythran/analyses/fixed_size_list.py b/contrib/python/pythran/pythran/analyses/fixed_size_list.py index 1d05d2dc421..dc6593ab9cb 100644 --- a/contrib/python/pythran/pythran/analyses/fixed_size_list.py +++ b/contrib/python/pythran/pythran/analyses/fixed_size_list.py @@ -5,18 +5,16 @@ This could be a type information, but it seems easier to implement it that way """ from pythran.passmanager import FunctionAnalysis from pythran.tables import MODULES +from pythran.analyses import Aliases, Ancestors +from pythran.analyses.use_def_chain import DefUseChains +from pythran.analyses import ArgumentEffects import gast as ast -class FixedSizeList(FunctionAnalysis): +class FixedSizeList(FunctionAnalysis[Aliases, DefUseChains, Ancestors, ArgumentEffects]): - def __init__(self): - self.result = set() - from pythran.analyses import Aliases, DefUseChains, Ancestors - from pythran.analyses import ArgumentEffects - super(FixedSizeList, self).__init__(Aliases, DefUseChains, Ancestors, - ArgumentEffects) + ResultType = set def is_fixed_size_list_def(self, node): if isinstance(node, ast.List): diff --git a/contrib/python/pythran/pythran/analyses/global_declarations.py b/contrib/python/pythran/pythran/analyses/global_declarations.py index aca22d4632b..3a27a6af22a 100644 --- a/contrib/python/pythran/pythran/analyses/global_declarations.py +++ b/contrib/python/pythran/pythran/analyses/global_declarations.py @@ -23,10 +23,7 @@ class GlobalDeclarations(ModuleAnalysis): """ - def __init__(self): - """ Result is an identifier with matching definition. """ - self.result = dict() - super(GlobalDeclarations, self).__init__() + ResultType = dict def visit_FunctionDef(self, node): """ Import module define a new variable name. """ @@ -43,3 +40,59 @@ class GlobalDeclarations(ModuleAnalysis): def visit_Name(self, node): if isinstance(node.ctx, ast.Store): self.result[node.id] = node + + +class NonlocalDeclarations(ModuleAnalysis): + + """ Transitively gather nonlocal declarations, per function + + >>> import gast as ast + >>> from pythran import passmanager + >>> from pythran.analyses import NonlocalDeclarations + >>> node = ast.parse(''' + ... def foo(a): + ... def t(): + ... def bar(): + ... nonlocal a + ... a = 1 + ... bar() + ... t()''') + >>> pm = passmanager.PassManager("test") + >>> [(n.name, l) for n, l in pm.gather(NonlocalDeclarations, node).items()] + [('foo', set()), ('t', {'a'}), ('bar', {'a'})] + + """ + + ResultType = dict + + def __init__(self): + super().__init__() + self.context = [] + self.locals = [] + self.nested = [set()] + + def visit_FunctionDef(self, node): + self.nested[-1].add(node) + + self.nested.append(set()) + self.locals.append(set()) + + self.context.append(node) + + self.result[node] = set() + self.generic_visit(node) + + self.context.pop() + + locals_ = self.locals.pop() + nested = self.nested.pop() + + for f in nested: + self.result[node].update(self.result[f] - locals_) + + def visit_Name(self, node): + if isinstance(node.ctx, (ast.Store, ast.Param)): + self.locals[-1].add(node.id) + + def visit_Nonlocal(self, node): + self.result[self.context[-1]].update(node.names) diff --git a/contrib/python/pythran/pythran/analyses/global_effects.py b/contrib/python/pythran/pythran/analyses/global_effects.py index 0f8c3ad8108..dd82a0958e5 100644 --- a/contrib/python/pythran/pythran/analyses/global_effects.py +++ b/contrib/python/pythran/pythran/analyses/global_effects.py @@ -46,15 +46,14 @@ def save_global_effects(module): for module in MODULES.values(): save_global_effects(module) -class GlobalEffects(ModuleAnalysis): +class GlobalEffectsHelper(ModuleAnalysis[Aliases, GlobalDeclarations, Intrinsics]): """Add a flag on each function that updates a global variable.""" + ResultType = DiGraph def __init__(self): - self.result = DiGraph() + super().__init__() self.node_to_functioneffect = dict() - super(GlobalEffects, self).__init__(Aliases, GlobalDeclarations, - Intrinsics) def prepare(self, node): """ @@ -63,7 +62,7 @@ class GlobalEffects(ModuleAnalysis): Initialisation done for Pythonic functions and default value set for user defined functions. """ - super(GlobalEffects, self).prepare(node) + super().prepare(node) for i in self.intrinsics: fe = IntrinsicGlobalEffects[i] @@ -78,19 +77,6 @@ class GlobalEffects(ModuleAnalysis): self.node_to_functioneffect[intrinsic.UnboundValue] = \ FunctionEffect(intrinsic.UnboundValue) - def run(self, node): - result = super(GlobalEffects, self).run(node) - keep_going = True - while keep_going: - keep_going = False - for function in result: - if function.global_effect: - for pred in self.result.predecessors(function): - if not pred.global_effect: - keep_going = pred.global_effect = True - self.result = {f.func for f in result if f.global_effect} - return self.result - def visit_FunctionDef(self, node): self.current_function = self.node_to_functioneffect[node] assert self.current_function in self.result @@ -125,3 +111,22 @@ class GlobalEffects(ModuleAnalysis): func_alias = self.node_to_functioneffect[func_alias] self.result.add_edge(self.current_function, func_alias) self.generic_visit(node) + + +class GlobalEffects(ModuleAnalysis[GlobalEffectsHelper]): + + """Add a flag on each function that updates a global variable.""" + + ResultType = dict + + def visit_Module(self, node): + result = self.global_effects_helper + keep_going = True + while keep_going: + keep_going = False + for function in result: + if function.global_effect: + for pred in result.predecessors(function): + if not pred.global_effect: + keep_going = pred.global_effect = True + self.result = {f.func for f in result if f.global_effect} diff --git a/contrib/python/pythran/pythran/analyses/globals_analysis.py b/contrib/python/pythran/pythran/analyses/globals_analysis.py index 18cadb757c1..e883ade87eb 100644 --- a/contrib/python/pythran/pythran/analyses/globals_analysis.py +++ b/contrib/python/pythran/pythran/analyses/globals_analysis.py @@ -4,10 +4,9 @@ from pythran.analyses.global_declarations import GlobalDeclarations from pythran.passmanager import ModuleAnalysis -class Globals(ModuleAnalysis): - def __init__(self): - self.result = set() - super(Globals, self).__init__(GlobalDeclarations) +class Globals(ModuleAnalysis[GlobalDeclarations]): + + ResultType = set def visit_Module(self, node): self.result = {'builtins', diff --git a/contrib/python/pythran/pythran/analyses/has_return.py b/contrib/python/pythran/pythran/analyses/has_return.py index 7f9a27b50f8..604c68f591f 100644 --- a/contrib/python/pythran/pythran/analyses/has_return.py +++ b/contrib/python/pythran/pythran/analyses/has_return.py @@ -9,9 +9,7 @@ from pythran.passmanager import NodeAnalysis class HasReturn(NodeAnalysis): - def __init__(self): - self.result = False - super(HasReturn, self).__init__() + ResultType = bool def visit_Return(self, _): self.result = True @@ -22,9 +20,7 @@ class HasReturn(NodeAnalysis): class HasBreak(NodeAnalysis): - def __init__(self): - self.result = False - super(HasBreak, self).__init__() + ResultType = bool def visit_For(self, _): return @@ -37,9 +33,7 @@ class HasBreak(NodeAnalysis): class HasContinue(NodeAnalysis): - def __init__(self): - self.result = False - super(HasContinue, self).__init__() + ResultType = bool def visit_For(self, _): return diff --git a/contrib/python/pythran/pythran/analyses/identifiers.py b/contrib/python/pythran/pythran/analyses/identifiers.py index 5dc0e6047b1..b909fa17b98 100644 --- a/contrib/python/pythran/pythran/analyses/identifiers.py +++ b/contrib/python/pythran/pythran/analyses/identifiers.py @@ -7,9 +7,7 @@ from pythran.passmanager import NodeAnalysis class Identifiers(NodeAnalysis): """Gather all identifiers used throughout a node.""" - def __init__(self): - self.result = set() - super(Identifiers, self).__init__() + ResultType = set def visit_Name(self, node): self.result.add(node.id) diff --git a/contrib/python/pythran/pythran/analyses/immediates.py b/contrib/python/pythran/pythran/analyses/immediates.py index 08c58d59d85..61a0897bfe5 100644 --- a/contrib/python/pythran/pythran/analyses/immediates.py +++ b/contrib/python/pythran/pythran/analyses/immediates.py @@ -11,10 +11,8 @@ from pythran.utils import pythran_builtin, isnum, ispowi _make_shape = pythran_builtin('make_shape') -class Immediates(NodeAnalysis): - def __init__(self): - self.result = set() - super(Immediates, self).__init__(Aliases) +class Immediates(NodeAnalysis[Aliases]): + ResultType = set def visit_BinOp(self, node): self.generic_visit(node) diff --git a/contrib/python/pythran/pythran/analyses/imported_ids.py b/contrib/python/pythran/pythran/analyses/imported_ids.py index 4df642bceed..41e59e35a75 100644 --- a/contrib/python/pythran/pythran/analyses/imported_ids.py +++ b/contrib/python/pythran/pythran/analyses/imported_ids.py @@ -8,24 +8,45 @@ import pythran.metadata as md import gast as ast -class ImportedIds(NodeAnalysis): - - """Gather ids referenced by a node and not declared locally.""" +class ImportedIds(NodeAnalysis[Globals, Locals]): + + """ + Gather ids referenced by a node and not declared locally. + + >>> import gast as ast + >>> from pythran import passmanager + >>> from pythran.analyses import ImportedIds + >>> node = ast.parse(''' + ... def foo(): + ... def t(): + ... nonlocal g + ... g = k + ... t()''') + >>> pm = passmanager.PassManager("test") + >>> sorted(pm.gather(ImportedIds, node)) + ['g', 'k'] + """ + + ResultType = set def __init__(self): - self.result = set() + super().__init__() self.current_locals = set() - self.is_list = False + self.current_nonlocals = set() self.in_augassign = False - super(ImportedIds, self).__init__(Globals, Locals) def visit_Name(self, node): - if isinstance(node.ctx, ast.Store) and not self.in_augassign: + if node.id in self.current_nonlocals: + self.result.add(node.id) + elif isinstance(node.ctx, ast.Store) and not self.in_augassign: self.current_locals.add(node.id) elif (node.id not in self.visible_globals and node.id not in self.current_locals): self.result.add(node.id) + def visit_Nonlocal(self, node): + self.current_nonlocals.update(node.names) + def visit_FunctionDef(self, node): self.current_locals.add(node.name) current_locals = self.current_locals.copy() @@ -98,12 +119,4 @@ class ImportedIds(NodeAnalysis): def prepare(self, node): super(ImportedIds, self).prepare(node) - if self.is_list: # so that this pass can be called on list - node = node.body[0] self.visible_globals = set(self.globals) - self.locals[node] - - def run(self, node): - if isinstance(node, list): # so that this pass can be called on list - self.is_list = True - node = ast.If(ast.Constant(1, None), node, []) - return super(ImportedIds, self).run(node) diff --git a/contrib/python/pythran/pythran/analyses/inlinable.py b/contrib/python/pythran/pythran/analyses/inlinable.py index 7de49627904..16c0f4edc63 100644 --- a/contrib/python/pythran/pythran/analyses/inlinable.py +++ b/contrib/python/pythran/pythran/analyses/inlinable.py @@ -9,7 +9,7 @@ import gast as ast import copy -class Inlinable(ModuleAnalysis): +class Inlinable(ModuleAnalysis[PureExpressions]): """ Determine set of inlinable function. @@ -17,9 +17,7 @@ class Inlinable(ModuleAnalysis): recurse on itself. """ - def __init__(self): - self.result = dict() - super(Inlinable, self).__init__(PureExpressions) + ResultType = dict def visit_FunctionDef(self, node): """ Determine this function definition can be inlined. """ @@ -40,7 +38,7 @@ class Inlinable(ModuleAnalysis): return ids = self.gather(Identifiers, sbody) - # FIXME : It mark "not inlinable" def foo(foo): return foo + # FIXME : It marks "not inlinable" def foo(foo): return foo if node.name not in ids: self.result[node.name] = copy.deepcopy(node) self.result[node.name].body = [self.result[node.name].body[sindex]] diff --git a/contrib/python/pythran/pythran/analyses/intrinsics.py b/contrib/python/pythran/pythran/analyses/intrinsics.py index 8b558e6d535..87c6f2beece 100644 --- a/contrib/python/pythran/pythran/analyses/intrinsics.py +++ b/contrib/python/pythran/pythran/analyses/intrinsics.py @@ -10,10 +10,8 @@ class Intrinsics(ModuleAnalysis): """ Gather all intrinsics used in the module """ - def __init__(self): - """ Result is a set of intrinsic values. """ - self.result = set() - super(Intrinsics, self).__init__() + """ Result is a set of intrinsic values. """ + ResultType = set def visit_Attribute(self, node): obj, _ = attr_to_path(node) diff --git a/contrib/python/pythran/pythran/analyses/is_assigned.py b/contrib/python/pythran/pythran/analyses/is_assigned.py index 64d9db5f13e..b349297d1b8 100644 --- a/contrib/python/pythran/pythran/analyses/is_assigned.py +++ b/contrib/python/pythran/pythran/analyses/is_assigned.py @@ -14,10 +14,7 @@ class IsAssigned(NodeAnalysis): arguments effects as it is use by value. """ - def __init__(self): - """ Basic initialiser. """ - self.result = list() - super(IsAssigned, self).__init__() + ResultType = list def visit_Name(self, node): """ Stored variable have new value. """ diff --git a/contrib/python/pythran/pythran/analyses/lazyness_analysis.py b/contrib/python/pythran/pythran/analyses/lazyness_analysis.py index 5589231080b..398cba7c2c8 100644 --- a/contrib/python/pythran/pythran/analyses/lazyness_analysis.py +++ b/contrib/python/pythran/pythran/analyses/lazyness_analysis.py @@ -14,7 +14,7 @@ import gast as ast import sys -class LazynessAnalysis(FunctionAnalysis): +class LazynessAnalysis(FunctionAnalysis[ArgumentEffects, Aliases, PureExpressions]): """ Returns number of time a name is used. @@ -88,9 +88,11 @@ class LazynessAnalysis(FunctionAnalysis): INF = float('inf') MANY = sys.maxsize + # map variable with maximum count of use in the programm + ResultType = dict + def __init__(self): - # map variable with maximum count of use in the programm - self.result = dict() + super().__init__() # map variable with current count of use self.name_count = dict() # map variable to variables needed to compute it @@ -104,8 +106,6 @@ class LazynessAnalysis(FunctionAnalysis): # prevent any form of Forward Substitution at omp frontier self.in_omp = set() self.name_to_nodes = dict() - super(LazynessAnalysis, self).__init__(ArgumentEffects, Aliases, - PureExpressions) def modify(self, name): # if we modify a variable, all variables that needed it @@ -157,6 +157,11 @@ class LazynessAnalysis(FunctionAnalysis): self.ids = self.gather(Identifiers, node) self.generic_visit(node) + # update result with last name_count values + for name, val in self.name_count.items(): + old_val = self.result.get(name, 0) + self.result[name] = max(old_val, val) + def visit_Assign(self, node): md.visit(self, node) if node.value: @@ -371,13 +376,3 @@ class LazynessAnalysis(FunctionAnalysis): self.visit(arg) self.func_args_lazyness(node.func, node.args, node) self.visit(node.func) - - def run(self, node): - result = super(LazynessAnalysis, self).run(node) - - # update result with last name_count values - for name, val in self.name_count.items(): - old_val = result.get(name, 0) - result[name] = max(old_val, val) - self.result = result - return self.result diff --git a/contrib/python/pythran/pythran/analyses/literals.py b/contrib/python/pythran/pythran/analyses/literals.py index 929e8c4e6a7..bcf152ff98b 100644 --- a/contrib/python/pythran/pythran/analyses/literals.py +++ b/contrib/python/pythran/pythran/analyses/literals.py @@ -11,9 +11,7 @@ class Literals(FunctionAnalysis): """ Store variable that save only Literals (with no construction cost) """ - def __init__(self): - self.result = set() - super(Literals, self).__init__() + ResultType = set def visit_Assign(self, node): # list, dict, set and other are not considered as Literals as they have diff --git a/contrib/python/pythran/pythran/analyses/local_declarations.py b/contrib/python/pythran/pythran/analyses/local_declarations.py index e3ac7548832..9a538911866 100644 --- a/contrib/python/pythran/pythran/analyses/local_declarations.py +++ b/contrib/python/pythran/pythran/analyses/local_declarations.py @@ -32,10 +32,7 @@ class LocalNodeDeclarations(NodeAnalysis): ['b', 'c'] """ - def __init__(self): - """ Initialize empty set as the result. """ - self.result = set() - super(LocalNodeDeclarations, self).__init__() + ResultType = set def visit_Name(self, node): """ Any node with Store context is a new declaration. """ @@ -58,10 +55,8 @@ class LocalNameDeclarations(NodeAnalysis): ['a', 'b', 'foo'] """ - def __init__(self): - """ Initialize empty set as the result. """ - self.result = set() - super(LocalNameDeclarations, self).__init__() + """ Initialize empty set as the result. """ + ResultType = set def visit_Name(self, node): """ Any node with Store or Param context is a new identifier. """ diff --git a/contrib/python/pythran/pythran/analyses/locals_analysis.py b/contrib/python/pythran/pythran/analyses/locals_analysis.py index 0d76584521a..1f4fb3054ae 100644 --- a/contrib/python/pythran/pythran/analyses/locals_analysis.py +++ b/contrib/python/pythran/pythran/analyses/locals_analysis.py @@ -33,11 +33,12 @@ class Locals(ModuleAnalysis): ['b', 'm', 'n'] """ + ResultType = dict + def __init__(self): - self.result = dict() + super().__init__() self.locals = set() self.nesting = 0 - super(Locals, self).__init__() def generic_visit(self, node): super(Locals, self).generic_visit(node) diff --git a/contrib/python/pythran/pythran/analyses/node_count.py b/contrib/python/pythran/pythran/analyses/node_count.py index 02a6455b7b3..37615cca1f4 100644 --- a/contrib/python/pythran/pythran/analyses/node_count.py +++ b/contrib/python/pythran/pythran/analyses/node_count.py @@ -20,9 +20,7 @@ class NodeCount(NodeAnalysis): 5 """ - def __init__(self): - self.result = 0 - super(NodeCount, self).__init__() + ResultType = int def generic_visit(self, node): self.result += 1 diff --git a/contrib/python/pythran/pythran/analyses/optimizable_comprehension.py b/contrib/python/pythran/pythran/analyses/optimizable_comprehension.py index 1635fc40005..4803d8f6cf9 100644 --- a/contrib/python/pythran/pythran/analyses/optimizable_comprehension.py +++ b/contrib/python/pythran/pythran/analyses/optimizable_comprehension.py @@ -6,11 +6,9 @@ from pythran.analyses.identifiers import Identifiers from pythran.passmanager import NodeAnalysis -class OptimizableComprehension(NodeAnalysis): +class OptimizableComprehension(NodeAnalysis[Identifiers]): """Find whether a comprehension can be optimized.""" - def __init__(self): - self.result = set() - super(OptimizableComprehension, self).__init__(Identifiers) + ResultType = set def check_comprehension(self, iters): targets = {gen.target.id for gen in iters} diff --git a/contrib/python/pythran/pythran/analyses/ordered_global_declarations.py b/contrib/python/pythran/pythran/analyses/ordered_global_declarations.py index 1c4f30c0484..2e3dcd3dd74 100644 --- a/contrib/python/pythran/pythran/analyses/ordered_global_declarations.py +++ b/contrib/python/pythran/pythran/analyses/ordered_global_declarations.py @@ -7,12 +7,9 @@ from pythran.passmanager import ModuleAnalysis import gast as ast -class OrderedGlobalDeclarations(ModuleAnalysis): +class OrderedGlobalDeclarationsHelper(ModuleAnalysis[StrictAliases, GlobalDeclarations]): '''Order all global functions according to their callgraph depth''' - def __init__(self): - self.result = dict() - super(OrderedGlobalDeclarations, self).__init__( - StrictAliases, GlobalDeclarations) + ResultType = dict def visit_FunctionDef(self, node): self.curr = node @@ -29,10 +26,15 @@ class OrderedGlobalDeclarations(ModuleAnalysis): if alias in self.global_declarations: self.result[self.curr].add(alias) - def run(self, node): + +class OrderedGlobalDeclarations(ModuleAnalysis[OrderedGlobalDeclarationsHelper]): + '''Order all global functions according to their callgraph depth''' + ResultType = list + + def visit_Module(self, node): # compute the weight of each function # the weight of a function is the number functions it references - result = super(OrderedGlobalDeclarations, self).run(node) + result = self.ordered_global_declarations_helper old_count = -1 new_count = 0 # iteratively propagate weights @@ -42,6 +44,6 @@ class OrderedGlobalDeclarations(ModuleAnalysis): old_count = new_count new_count = sum(len(value) for value in result.values()) # return functions, the one with the greatest weight first - self.result = sorted(self.result.keys(), reverse=True, - key=lambda s: len(self.result[s])) + self.result = sorted(result.keys(), reverse=True, + key=lambda s: len(result[s])) return self.result diff --git a/contrib/python/pythran/pythran/analyses/parallel_maps.py b/contrib/python/pythran/pythran/analyses/parallel_maps.py index 42a2761f10d..449630dc8d3 100644 --- a/contrib/python/pythran/pythran/analyses/parallel_maps.py +++ b/contrib/python/pythran/pythran/analyses/parallel_maps.py @@ -6,13 +6,11 @@ from pythran.passmanager import ModuleAnalysis from pythran.tables import MODULES -class ParallelMaps(ModuleAnalysis): +class ParallelMaps(ModuleAnalysis[PureExpressions, Aliases]): """Yields the est of maps that could be parallel.""" - def __init__(self): - self.result = set() - super(ParallelMaps, self).__init__(PureExpressions, Aliases) + ResultType = set def visit_Call(self, node): if all(alias == MODULES['builtins']['map'] diff --git a/contrib/python/pythran/pythran/analyses/potential_iterator.py b/contrib/python/pythran/pythran/analyses/potential_iterator.py index 23c6b3ad30f..9cf9dd2ccaf 100644 --- a/contrib/python/pythran/pythran/analyses/potential_iterator.py +++ b/contrib/python/pythran/pythran/analyses/potential_iterator.py @@ -9,11 +9,9 @@ from pythran.passmanager import NodeAnalysis import gast as ast -class PotentialIterator(NodeAnalysis): +class PotentialIterator(NodeAnalysis[Aliases, ArgumentReadOnce]): """Find whether an expression can be replaced with an iterator.""" - def __init__(self): - self.result = set() - NodeAnalysis.__init__(self, Aliases, ArgumentReadOnce) + ResultType = set def visit_For(self, node): self.result.add(node.iter) diff --git a/contrib/python/pythran/pythran/analyses/pure_expressions.py b/contrib/python/pythran/pythran/analyses/pure_expressions.py index 71381f4e843..51bd051ec38 100644 --- a/contrib/python/pythran/pythran/analyses/pure_expressions.py +++ b/contrib/python/pythran/pythran/analyses/pure_expressions.py @@ -12,13 +12,10 @@ from pythran.intrinsic import Intrinsic import gast as ast -class PureExpressions(ModuleAnalysis): +class PureExpressions(ModuleAnalysis[ArgumentEffects, GlobalEffects, Aliases, PureFunctions]): '''Yields the set of pure expressions''' - def __init__(self): - self.result = set() - super(PureExpressions, self).__init__(ArgumentEffects, GlobalEffects, - Aliases, PureFunctions) + ResultType = set def visit_FunctionDef(self, node): if node in self.pure_functions: diff --git a/contrib/python/pythran/pythran/analyses/pure_functions.py b/contrib/python/pythran/pythran/analyses/pure_functions.py index f8feba86711..9bc27d18103 100644 --- a/contrib/python/pythran/pythran/analyses/pure_functions.py +++ b/contrib/python/pythran/pythran/analyses/pure_functions.py @@ -7,13 +7,10 @@ from pythran.analyses.global_effects import GlobalEffects from pythran.passmanager import ModuleAnalysis -class PureFunctions(ModuleAnalysis): +class PureFunctions(ModuleAnalysis[ArgumentEffects, GlobalEffects]): '''Yields the set of pure functions''' - def __init__(self): - self.result = set() - super(PureFunctions, self).__init__(ArgumentEffects, GlobalEffects) - + ResultType = set def prepare(self, node): super(PureFunctions, self).prepare(node) diff --git a/contrib/python/pythran/pythran/analyses/range_values.py b/contrib/python/pythran/pythran/analyses/range_values.py index bc11d37b47f..ab1cbe0de8e 100644 --- a/contrib/python/pythran/pythran/analyses/range_values.py +++ b/contrib/python/pythran/pythran/analyses/range_values.py @@ -6,6 +6,7 @@ from collections import defaultdict from functools import reduce from pythran.analyses import Aliases, CFG +from pythran.analyses.use_omp import UseOMP from pythran.intrinsic import Intrinsic from pythran.passmanager import ModuleAnalysis from pythran.interval import Interval, IntervalTuple, UNKNOWN_RANGE @@ -205,15 +206,14 @@ def bound_range(mapping, aliases, node, modified=None): left = right -class RangeValuesBase(ModuleAnalysis): +class RangeValuesBase(ModuleAnalysis[Aliases, CFG, UseOMP]): ResultHolder = object() + ResultType = lambda: defaultdict(lambda: UNKNOWN_RANGE) def __init__(self): """Initialize instance variable and gather globals name information.""" - self.result = defaultdict(lambda: UNKNOWN_RANGE) - from pythran.analyses import UseOMP - super(RangeValuesBase, self).__init__(Aliases, CFG, UseOMP) + super().__init__() self.parent = self def add(self, variable, range_): diff --git a/contrib/python/pythran/pythran/analyses/scope.py b/contrib/python/pythran/pythran/analyses/scope.py index f96783a09b9..56a4532533c 100644 --- a/contrib/python/pythran/pythran/analyses/scope.py +++ b/contrib/python/pythran/pythran/analyses/scope.py @@ -10,7 +10,24 @@ from collections import defaultdict import gast as ast -class Scope(FunctionAnalysis): +def all_users(d): + """ + Gather users of a definition, including users of an augmented assign. + """ + visited = set() + dname = d.name() + def all_users_impl(d): + if d in visited: + return + visited.add(d) + for u in d.users(): + if u.name() == dname: + yield u + yield from all_users_impl(u) + return all_users_impl(d) + + +class Scope(FunctionAnalysis[AncestorsWithBody, DefUseChains]): ''' Associate each variable declaration with the node that defines it @@ -20,12 +37,12 @@ class Scope(FunctionAnalysis): The result is a dictionary with nodes as key and set of names as values ''' + ResultType = lambda: defaultdict(set) def __init__(self): - self.result = defaultdict(set) + super().__init__() self.decl_holders = (ast.FunctionDef, ast.For, ast.excepthandler, ast.While, ast.If, tuple) - super(Scope, self).__init__(AncestorsWithBody, DefUseChains) def visit_OMPDirective(self, node): for dep in node.deps: @@ -50,7 +67,7 @@ class Scope(FunctionAnalysis): for name, defs in name_to_defs.items(): # get all refs to that name refs = [d.node for d in defs] + [u.node - for d in defs for u in d.users()] + for d in defs for u in all_users(d)] # add OpenMP refs (well, the parent of the holding stmt) refs.extend(self.ancestors[d][-3] # -3 to get the right parent for d in self.openmp_deps.get(name, [])) diff --git a/contrib/python/pythran/pythran/analyses/static_expressions.py b/contrib/python/pythran/pythran/analyses/static_expressions.py index c6c7fa52989..4a56c8b8df9 100644 --- a/contrib/python/pythran/pythran/analyses/static_expressions.py +++ b/contrib/python/pythran/pythran/analyses/static_expressions.py @@ -6,7 +6,6 @@ from pythran.passmanager import NodeAnalysis class HasStaticExpression(NodeAnalysis): def __init__(self): self.result = False - super(HasStaticExpression, self).__init__() def visit_Attribute(self, node): self.generic_visit(node) @@ -17,10 +16,10 @@ class StaticExpressions(NodeAnalysis): """Identify constant expressions.""" + ResultType = set def __init__(self): - self.result = set() + super().__init__() self.constant_expressions = set() - super(StaticExpressions, self).__init__() def add(self, node): self.result.add(node) diff --git a/contrib/python/pythran/pythran/analyses/use_def_chain.py b/contrib/python/pythran/pythran/analyses/use_def_chain.py index b88ce258ad5..beb9a9c82c2 100644 --- a/contrib/python/pythran/pythran/analyses/use_def_chain.py +++ b/contrib/python/pythran/pythran/analyses/use_def_chain.py @@ -17,33 +17,30 @@ class ExtendedDefUseChains(beniget.DefUseChains): md.visit(self, node) return super(ExtendedDefUseChains, self).visit(node) - -class UseDefChains(ModuleAnalysis): +class DefUseChains(ModuleAnalysis): """ - Build use-define chains analysis for each variable. + Build define-use-define chains analysis for each variable. """ - def __init__(self): - self.result = None - super(UseDefChains, self).__init__(DefUseChains) + ResultType = type(None) def visit_Module(self, node): - udc = beniget.UseDefChains(self.def_use_chains) - self.result = udc.chains + duc = ExtendedDefUseChains() + duc.visit(node) + self.result = duc -class DefUseChains(ModuleAnalysis): +class UseDefChains(ModuleAnalysis[DefUseChains]): """ - Build define-use-define chains analysis for each variable. + Build use-define chains analysis for each variable. """ - def __init__(self): - self.result = None - super(DefUseChains, self).__init__() + ResultType = type(None) def visit_Module(self, node): - duc = ExtendedDefUseChains() - duc.visit(node) - self.result = duc + udc = beniget.UseDefChains(self.def_use_chains) + self.result = udc.chains + + diff --git a/contrib/python/pythran/pythran/analyses/use_omp.py b/contrib/python/pythran/pythran/analyses/use_omp.py index 68da74307f8..b2d4dafba09 100644 --- a/contrib/python/pythran/pythran/analyses/use_omp.py +++ b/contrib/python/pythran/pythran/analyses/use_omp.py @@ -7,9 +7,7 @@ from pythran.passmanager import FunctionAnalysis class UseOMP(FunctionAnalysis): """Detects if a function use openMP""" - def __init__(self): - self.result = False - super(UseOMP, self).__init__() + ResultType = bool def visit_OMPDirective(self, _): self.result = True diff --git a/contrib/python/pythran/pythran/analyses/yield_points.py b/contrib/python/pythran/pythran/analyses/yield_points.py index a1069df3137..0f0f5f32f1b 100644 --- a/contrib/python/pythran/pythran/analyses/yield_points.py +++ b/contrib/python/pythran/pythran/analyses/yield_points.py @@ -7,9 +7,7 @@ from pythran.passmanager import FunctionAnalysis class YieldPoints(FunctionAnalysis): '''Gathers all yield points of a generator, if any.''' - def __init__(self): - self.result = list() - super(YieldPoints, self).__init__() + ResultType = list def visit_Yield(self, node): self.result.append(node) diff --git a/contrib/python/pythran/pythran/backend.py b/contrib/python/pythran/pythran/backend.py index cb0f77c1ccd..e5728ab3e0e 100644 --- a/contrib/python/pythran/pythran/backend.py +++ b/contrib/python/pythran/pythran/backend.py @@ -42,9 +42,7 @@ class Python(Backend): print('hello world') ''' - def __init__(self): - self.result = '' - super(Python, self).__init__() + ResultType = str def visit(self, node): output = io.StringIO() @@ -98,7 +96,7 @@ def cxx_loop(visit): res = visit(self, node) return res - break_handler = "__no_breaking{0}".format(id(node)) + break_handler = "__no_breaking{0}".format(len(self.break_handlers)) with pushpop(self.break_handlers, break_handler): res = visit(self, node) @@ -108,7 +106,9 @@ def cxx_loop(visit): orelse_label = [Label(break_handler)] else: orelse_label = [] - return Block([res] + orelse + orelse_label) + skip = [node.target.id] if isinstance(node, ast.For) else [] + return self.process_locals(node, Block([res] + orelse + orelse_label), + *skip) return loop_visitor @@ -116,25 +116,31 @@ class CachedTypeVisitor: def __init__(self, other=None): if other is None: - self.rcache = dict() self.mapping = dict() + self.typeid = dict() + self.combined = dict() else: - self.rcache = other.rcache.copy() self.mapping = other.mapping.copy() + self.typeid = other.typeid.copy() + self.combined = other.combined.copy() def __call__(self, node): if node not in self.mapping: t = node.generate(self) if node not in self.mapping: - if t in self.rcache: - self.mapping[node] = self.rcache[t] + # Always re-evaluate LType as their evaluation depends on the + # callers (due to the recursion clause) + if type(node).__name__ == 'LType': + return t else: - self.rcache[t] = len(self.mapping) - self.mapping[node] = len(self.mapping) - return "__type{0}".format(self.mapping[node]) + if t not in self.typeid: + self.typeid[t] = len(self.typeid) + self.mapping[node] = (t, self.typeid[t]) + + return "__type{0}".format(self.mapping[node][1]) def typedefs(self): - kv = sorted(self.rcache.items(), key=lambda x: x[1]) + kv = sorted(set(self.mapping.values()), key=lambda x: x[1]) L = list() for k, v in kv: typename = "__type" + str(v) @@ -187,6 +193,7 @@ class CxxFunction(ast.NodeVisitor): self.used_break = set() self.ldecls = None self.openmp_deps = set() + self.unique_counter = 0 if not (cfg.getboolean('backend', 'annotate') and self.passmanager.code): self.add_line_info = self.skip_line_info @@ -196,6 +203,10 @@ class CxxFunction(ast.NodeVisitor): def __getattr__(self, attr): return getattr(self.parent, attr) + def unique(self): + self.unique_counter += 1 + return self.unique_counter + # local declaration processing def process_locals(self, node, node_visited, *skipped): """ @@ -209,7 +220,7 @@ class CxxFunction(ast.NodeVisitor): return node_visited # no processing locals_visited = [] - for varname in local_vars: + for varname in sorted(local_vars): vartype = self.typeof(varname) decl = Statement("{} {}".format(vartype, varname)) locals_visited.append(decl) @@ -336,13 +347,13 @@ class CxxFunction(ast.NodeVisitor): "typename {0}result_type".format(ffscope), "{0}::operator()".format(self.fname), formal_types, formal_args) - ctx = CachedTypeVisitor(self.lctx) + operator_local_declarations = ( [Statement("{0} {1}".format( - ctx(self.types[self.local_names[k]]), cxxid(k))) - for k in self.ldecls] + self.lctx(self.types[self.local_names[k]]), cxxid(k))) + for k in sorted(self.ldecls)] ) - dependent_typedefs = ctx.typedefs() + dependent_typedefs = self.lctx.typedefs() operator_definition = FunctionBody( templatize(operator_signature, formal_types), Block(dependent_typedefs + @@ -439,7 +450,9 @@ class CxxFunction(ast.NodeVisitor): alltargets) else: assert isinstance(self.types[targets[0]], - self.types.builder.Lazy) + (self.types.builder.Lazy, + self.types.builder.NamedType, + self.types.builder.ListType)) alltargets = '{} {}'.format( self.types.builder.Lazy( self.types.builder.NamedType( @@ -502,7 +515,7 @@ class CxxFunction(ast.NodeVisitor): is removed for iterator in case of yields statement in function. """ # Choose target variable for iterator (which is iterator type) - local_target = "__target{0}".format(id(node)) + local_target = "__target{0}".format(self.unique()) local_target_decl = self.typeof( self.types.builder.IteratorOfType(local_iter_decl)) @@ -592,7 +605,7 @@ class CxxFunction(ast.NodeVisitor): if self.is_in_collapse(node, args[upper_arg]): upper_bound = upper_value # compatible with collapse else: - upper_bound = "__target{0}".format(id(node)) + upper_bound = "__target{0}".format(self.unique()) islocal = (node.target.id not in self.openmp_deps and node.target.id in self.scope[node] and @@ -764,7 +777,8 @@ class CxxFunction(ast.NodeVisitor): loop_body = Block([self.visit(stmt) for stmt in node.body]) # Declare local variables at the top of the loop body - loop_body = self.process_locals(node, loop_body, node.target.id) + if not node.orelse: + loop_body = self.process_locals(node, loop_body, node.target.id) iterable = self.visit(node.iter) if self.can_use_c_for(node): @@ -778,7 +792,7 @@ class CxxFunction(ast.NodeVisitor): loop = [self.process_omp_attachements(node, autofor)] else: # Iterator declaration - local_iter = "__iter{0}".format(id(node)) + local_iter = "__iter{0}".format(self.unique()) local_iter_decl = self.types.builder.Assignable( self.types[node.iter]) @@ -921,19 +935,12 @@ class CxxFunction(ast.NodeVisitor): if not node.elts: # empty list return '{}(pythonic::types::empty_list())'.format(self.typeof(node)) else: - + node_type = self.types.builder.Assignable(self.types[node]) elts = [self.visit(n) for n in node.elts] - node_type = self.types[node] - - # constructor disambiguation, clang++ workaround - if len(elts) == 1: - return "{0}({1}, pythonic::types::single_value())".format( - self.typeof(self.types.builder.Assignable(node_type)), - elts[0]) - else: - return "{0}({{{1}}})".format( - self.typeof(self.types.builder.Assignable(node_type)), - ", ".join(elts)) + return "{0}({{{1}}})".format( + self.typeof(node_type), + ", ".join("static_cast<typename {}::value_type>({})" + .format(self.typeof(node_type), elt) for elt in elts)) def visit_Set(self, node): if not node.elts: # empty set @@ -941,17 +948,10 @@ class CxxFunction(ast.NodeVisitor): else: elts = [self.visit(n) for n in node.elts] node_type = self.types.builder.Assignable(self.types[node]) - - # constructor disambiguation, clang++ workaround - if len(elts) == 1: - return "{0}({1}, pythonic::types::single_value())".format( - self.typeof(node_type), - elts[0]) - else: - return "{0}{{{{{1}}}}}".format( - self.typeof(node_type), - ", ".join("static_cast<typename {}::value_type>({})" - .format(self.typeof(node_type), elt) for elt in elts)) + return "{0}({{{1}}})".format( + self.typeof(node_type), + ", ".join("static_cast<typename {}::value_type>({})" + .format(self.typeof(node_type), elt) for elt in elts)) def visit_Dict(self, node): if not node.keys: # empty dict @@ -1249,7 +1249,7 @@ class CxxGenerator(CxxFunction): [Statement("{0} {1}".format( ctx(self.types[self.local_names[k]]), k)) - for k in self.ldecls] + + for k in sorted(self.ldecls)] + [Statement("{0} {1}".format(v, k)) for k, v in self.extra_declarations] + [Statement( @@ -1354,7 +1354,8 @@ class CxxGenerator(CxxFunction): return super(CxxGenerator, self).make_assign("", local_iter, iterable) -class Cxx(Backend): +class Cxx(Backend[Dependencies, GlobalDeclarations, Types, Scope, RangeValues, + PureExpressions, Immediates, Ancestors, StrictAliases]): """ Produces a C++ representation of the AST. @@ -1392,13 +1393,7 @@ class Cxx(Backend): } } """ - - def __init__(self): - """ Basic initialiser gathering analysis informations. """ - self.result = None - super(Cxx, self).__init__(Dependencies, GlobalDeclarations, Types, - Scope, RangeValues, PureExpressions, - Immediates, Ancestors, StrictAliases) + ResultType = type(None) # mod def visit_Module(self, node): diff --git a/contrib/python/pythran/pythran/config.py b/contrib/python/pythran/pythran/config.py index 053c26d3d81..d24cc1fa486 100644 --- a/contrib/python/pythran/pythran/config.py +++ b/contrib/python/pythran/pythran/config.py @@ -200,8 +200,6 @@ def make_extension(python, **extra): if python: extension['define_macros'].append('ENABLE_PYTHON_MODULE') - extension['define_macros'].append( - '__PYTHRAN__={}'.format(sys.version_info.major)) pythonic_dir = get_include() @@ -313,10 +311,7 @@ def run(): Dump on stdout the config flags required to compile pythran-generated code. ''' import argparse - import distutils.ccompiler - import distutils.sysconfig import pythran - import numpy parser = argparse.ArgumentParser( prog='pythran-config', @@ -327,8 +322,26 @@ def run(): parser.add_argument('--compiler', action='store_true', help='print default compiler') - parser.add_argument('--cflags', action='store_true', - help='print compilation flags') + group = parser.add_mutually_exclusive_group() + group.add_argument('--cflags', action='store_true', + help='print compilation flags to compile extension ' + 'modules directly') + + # The with-BLAS variant could be added if there's user demand. Unclear + # if there are packages that need this, and also integrate with a build + # system for Python and NumPy flags. SciPy and scikit-image need the + # no-BLAS variant. + group.add_argument('--cflags-pythran-only', action='store_true', + help='print compilation flags for usage by a build ' + 'system (doesn\'t include Python, NumPy or BLAS ' + 'flags).' + ) + + parser.add_argument('--include-dir', action='store_true', + help=( + 'print Pythran include directory ' + '(matches `pythran.get_include()`).') + ) parser.add_argument('--libs', action='store_true', help='print linker flags') @@ -349,6 +362,21 @@ def run(): args = parser.parse_args(sys.argv[1:]) + # This should not rely on distutils/setuptools, or anything else not in the + # stdlib. Please don't add imports higher up. + if args.cflags_pythran_only: + compile_flags = [ + "-DENABLE_PYTHON_MODULE", + "-DPYTHRAN_BLAS_NONE", + ] + print(" ".join(compile_flags), f"-I{get_include()}") + return None + + + import distutils.ccompiler + import distutils.sysconfig + import numpy + args.python = not args.no_python output = [] @@ -416,6 +444,9 @@ def run(): if args.libs: output.extend(ldflags) + if args.include_dir: + output.append(get_include()) + if output: print(' '.join(output)) diff --git a/contrib/python/pythran/pythran/cxxgen.py b/contrib/python/pythran/pythran/cxxgen.py index 504a9636cfe..83b9432c405 100644 --- a/contrib/python/pythran/pythran/cxxgen.py +++ b/contrib/python/pythran/pythran/cxxgen.py @@ -28,6 +28,7 @@ Generator for C/C++. # from textwrap import dedent +from pythran.config import cfg from pythran.tables import pythran_ward from pythran.spec import signatures_to_string from pythran.utils import quote_cxxstring @@ -580,9 +581,7 @@ class PythonModule(object): self.functions.setdefault(name, []).append(func_descriptor) def add_global_var(self, name, init): - self.global_vars.append(name) - self.python_implems.append(Assign('static PyObject* ' + name, - 'to_python({})'.format(init))) + self.global_vars.append((name, 'to_python({})'.format(init))) def __str__(self): """Generate (i.e. yield) the source code of the @@ -591,9 +590,9 @@ class PythonModule(object): themethods = [] theextraobjects = [] theoverloads = [] - for vname in self.global_vars: + for vname, vinit in self.global_vars: theextraobjects.append( - 'PyModule_AddObject(theModule, "{0}", {0});'.format(vname)) + 'PyModule_AddObject(theModule, "{0}", {1});'.format(vname, vinit)) for fname, overloads in self.functions.items(): tryall = [] @@ -695,26 +694,19 @@ class PythonModule(object): '''.format(methods="".join(m + "," for m in themethods))) module = dedent(''' - #if PY_MAJOR_VERSION >= 3 - static struct PyModuleDef moduledef = {{ - PyModuleDef_HEAD_INIT, - "{name}", /* m_name */ - {moduledoc}, /* m_doc */ - -1, /* m_size */ - Methods, /* m_methods */ - NULL, /* m_reload */ - NULL, /* m_traverse */ - NULL, /* m_clear */ - NULL, /* m_free */ - }}; - #define PYTHRAN_RETURN return theModule - #define PYTHRAN_MODULE_INIT(s) PyInit_##s - #else - #define PYTHRAN_RETURN return - #define PYTHRAN_MODULE_INIT(s) init##s - #endif + static struct PyModuleDef moduledef = {{ + PyModuleDef_HEAD_INIT, + "{name}", /* m_name */ + {moduledoc}, /* m_doc */ + -1, /* m_size */ + Methods, /* m_methods */ + NULL, /* m_reload */ + NULL, /* m_traverse */ + NULL, /* m_clear */ + NULL, /* m_free */ + }}; PyMODINIT_FUNC - PYTHRAN_MODULE_INIT({name})(void) + PyInit_{name}(void) #ifndef _WIN32 __attribute__ ((visibility("default"))) #if defined(GNUC) && !defined(__clang__) @@ -723,34 +715,32 @@ class PythonModule(object): #endif ; PyMODINIT_FUNC - PYTHRAN_MODULE_INIT({name})(void) {{ + PyInit_{name}(void) {{ import_array(); {import_umath} - #if PY_MAJOR_VERSION >= 3 PyObject* theModule = PyModule_Create(&moduledef); - #else - PyObject* theModule = Py_InitModule3("{name}", - Methods, - {moduledoc} - ); - #endif if(! theModule) - PYTHRAN_RETURN; + return theModule; + {freethreading} PyObject * theDoc = Py_BuildValue("(ss)", "{version}", "{hash}"); if(! theDoc) - PYTHRAN_RETURN; + return theModule; PyModule_AddObject(theModule, "__pythran__", theDoc); {extraobjects} - PYTHRAN_RETURN; + return theModule; }} '''.format(name=self.name, import_umath="import_umath();" if self.ufuncs else "", extraobjects='\n'.join(theextraobjects), + freethreading=""" + #ifdef Py_GIL_DISABLED + PyUnstable_Module_SetGIL(theModule, Py_MOD_GIL_NOT_USED); + #endif""" if cfg.getboolean("backend", "freethreading_compatible") else "", **self.metadata)) body = (self.preamble + diff --git a/contrib/python/pythran/pythran/cxxtypes.py b/contrib/python/pythran/pythran/cxxtypes.py index e0d86e47f50..c30cd884dc1 100644 --- a/contrib/python/pythran/pythran/cxxtypes.py +++ b/contrib/python/pythran/pythran/cxxtypes.py @@ -89,6 +89,9 @@ std::declval<str>()))>::type>::type >>> builder.ListType(builder.NamedType('int')) pythonic::types::list<typename std::remove_reference<int>::type> + >>> builder.NDArrayType(builder.NamedType('int'), 1) + pythonic::types::ndarray<int, pythonic::types::pshape<long>> + >>> builder.SetType(builder.NamedType('int')) pythonic::types::set<int> @@ -117,7 +120,7 @@ std::declval<bool>())) """ A generic type object to be sub-classed - The keyword arguments are used to built the internal representation + The keyword arguments are used to build the internal representation one attribute per key with the associated value """ @@ -172,14 +175,11 @@ std::declval<bool>())) """ prefix = "__ptype{0}" - count = 0 - def __init__(self, fun, ptype): + def __init__(self, fun, ptype, index): super(PType, self).__init__(fun=fun, type=ptype, - name=PType.prefix.format( - PType.count)) - PType.count += 1 + name=PType.prefix.format(index)) def generate(self, ctx): return ctx(self.type) @@ -202,7 +202,9 @@ std::declval<bool>())) return ctx(self.orig) else: self.isrec = True - return ctx(self.final_type) + res = ctx(self.final_type) + self.isrec = False + return res class InstantiatedType(Type): """ @@ -473,6 +475,20 @@ std::declval<bool>())) return 'pythonic::types::dict<{},{}>'.format(ctx(self.of_key), ctx(self.of_val)) + class NDArrayType(DependentType): + ''' + Type holding a numpy array without view + ''' + def __init__(self, dtype, nbdims): + super(DependentType, self).__init__(of=dtype, nbdims=nbdims) + + def generate(self, ctx): + return 'pythonic::types::ndarray<{}, pythonic::types::pshape<{}>>'.format( + ctx(self.of), + ", ".join((['long'] * self.nbdims)) + ) + + class ContainerType(DependentType): ''' Type of any container of stuff of the same type diff --git a/contrib/python/pythran/pythran/dist.py b/contrib/python/pythran/pythran/dist.py index b254dee7b97..45cf299ca0c 100644 --- a/contrib/python/pythran/pythran/dist.py +++ b/contrib/python/pythran/pythran/dist.py @@ -60,6 +60,7 @@ class PythranBuildExtMixIn(object): 'preprocessor': None, 'compiler_cxx': None, 'compiler_so': None, + 'compiler_so_cxx': None, 'compiler': None, 'linker_exe': None, 'linker_so': None, @@ -72,7 +73,6 @@ class PythranBuildExtMixIn(object): prev[key] = get_value(self.compiler, key) else: del prev[key] - # try hard to modify the compiler if getattr(ext, 'cxx', None) is not None: for comp in prev: @@ -92,7 +92,7 @@ class PythranBuildExtMixIn(object): return find_exe(exe, *args, **kwargs) msvc._find_exe = _find_exe - except ImportError: + except (AttributeError, ImportError): pass # In general, distutils uses -Wstrict-prototypes, but this option @@ -175,7 +175,6 @@ class PythranExtension(Extension): # Inserting at head so that user-specified config in CLI takes # precedence. kwargs['config'] = ['compiler.blas=none'] + kwargs.get('config', []) - cfg_ext = cfg.make_extension(python=True, **kwargs) self.cxx = cfg_ext.pop('cxx', None) self.cc = cfg_ext.pop('cc', None) diff --git a/contrib/python/pythran/pythran/errors.py b/contrib/python/pythran/pythran/errors.py index 02e978da4e7..c3416aa6b1e 100644 --- a/contrib/python/pythran/pythran/errors.py +++ b/contrib/python/pythran/pythran/errors.py @@ -16,8 +16,8 @@ class PythranSyntaxError(SyntaxError): SyntaxError.__init__(self, msg) if node: self.filename = getattr(node, 'filename', None) - self.lineno = node.lineno - self.offset = node.col_offset + self.lineno = getattr(node, 'lineno', None) + self.offset = getattr(node, 'col_offset', None) def __str__(self): loc_info = self.lineno is not None and self.offset is not None diff --git a/contrib/python/pythran/pythran/graph.py b/contrib/python/pythran/pythran/graph.py index 7a29337e27a..9d152c80529 100644 --- a/contrib/python/pythran/pythran/graph.py +++ b/contrib/python/pythran/pythran/graph.py @@ -18,11 +18,11 @@ class DiGraph(object): return (k for k, v in self._adjacency.items() if node in v) def add_node(self, node): - self._adjacency.setdefault(node, set()) + self._adjacency.setdefault(node, {}) def add_edge(self, src, dest, **props): self.add_node(dest) - self._adjacency.setdefault(src, set()).add(dest) + self._adjacency.setdefault(src, {}).setdefault(dest, None) self._edges[(src, dest)] = props @property @@ -33,7 +33,7 @@ class DiGraph(object): return dest in self._adjacency[src] def remove_edge(self, src, dest): - self._adjacency[src].remove(dest) + self._adjacency[src].pop(dest) del self._edges[(src, dest)] def __len__(self): diff --git a/contrib/python/pythran/pythran/intrinsic.py b/contrib/python/pythran/pythran/intrinsic.py index c214bb419af..f7566e4de3d 100644 --- a/contrib/python/pythran/pythran/intrinsic.py +++ b/contrib/python/pythran/pythran/intrinsic.py @@ -5,6 +5,7 @@ from pythran.interval import UNKNOWN_RANGE, bool_values from pythran.types.signature import extract_combiner from pythran.typing import Any, Union, Fun, Generator +from itertools import repeat, chain import gast as ast @@ -16,8 +17,21 @@ class UnboundValueType(object): UnboundValue = UnboundValueType() -# FIXME: we should find a better way to implement default behavior -DefaultArgNum = 20 +class defaultlist: + DefaultLen = 20 + def __init__(self, content=None, *, default): + self.content = [] if content is None else content + self.default = default + + def __iter__(self): + return chain(self.content, repeat(self.default(), len(self) - + len(self.content))) + + def __getitem__(self, i): + return self.content[i] if i < len(self.content) else self.default() + + def __len__(self): + return max(len(self.content), defaultlist.DefaultLen) class UpdateEffect(object): @@ -51,7 +65,7 @@ class Intrinsic(object): def __init__(self, **kwargs): self.argument_effects = kwargs.get('argument_effects', - (UpdateEffect(),) * DefaultArgNum) + defaultlist(default=UpdateEffect)) self.global_effects = kwargs.get('global_effects', False) self.return_alias = kwargs.get('return_alias', lambda x: {UnboundValue}) @@ -151,27 +165,27 @@ class UserFunction(FunctionIntr): class ConstFunctionIntr(FunctionIntr): def __init__(self, **kwargs): kwargs.setdefault('argument_effects', - (ReadEffect(),) * DefaultArgNum) + defaultlist(default=ReadEffect)) super(ConstFunctionIntr, self).__init__(**kwargs) class ConstExceptionIntr(ConstFunctionIntr): def __init__(self, **kwargs): kwargs.setdefault('argument_effects', - (ReadEffect(),) * DefaultArgNum) + defaultlist(default=ReadEffect)) super(ConstExceptionIntr, self).__init__(**kwargs) class ReadOnceFunctionIntr(ConstFunctionIntr): def __init__(self, **kwargs): super(ReadOnceFunctionIntr, self).__init__( - argument_effects=(ReadOnceEffect(),) * DefaultArgNum, **kwargs) + argument_effects=defaultlist(default=ReadOnceEffect), **kwargs) class MethodIntr(FunctionIntr): def __init__(self, *combiners, **kwargs): kwargs.setdefault('argument_effects', - (UpdateEffect(),) + (ReadEffect(),) * DefaultArgNum) + defaultlist((UpdateEffect(),), default=ReadEffect)) kwargs['combiners'] = combiners super(MethodIntr, self).__init__(**kwargs) @@ -184,14 +198,14 @@ class MethodIntr(FunctionIntr): class ConstMethodIntr(MethodIntr): def __init__(self, *combiners, **kwargs): - kwargs.setdefault('argument_effects', (ReadEffect(),) * DefaultArgNum) + kwargs.setdefault('argument_effects', defaultlist(default=ReadEffect)) super(ConstMethodIntr, self).__init__(*combiners, **kwargs) class ReadOnceMethodIntr(ConstMethodIntr): def __init__(self, **kwargs): super(ReadOnceMethodIntr, self).__init__( - argument_effects=(ReadOnceEffect(),) * DefaultArgNum, **kwargs) + argument_effects=defaultlist(default=ReadOnceEffect), **kwargs) class AttributeIntr(Intrinsic): diff --git a/contrib/python/pythran/pythran/optimizations/comprehension_patterns.py b/contrib/python/pythran/pythran/optimizations/comprehension_patterns.py index 1381e6a7ccf..2d85a1d1ddc 100644 --- a/contrib/python/pythran/pythran/optimizations/comprehension_patterns.py +++ b/contrib/python/pythran/pythran/optimizations/comprehension_patterns.py @@ -10,7 +10,7 @@ from pythran.utils import attr_to_path, path_to_attr import gast as ast -class ComprehensionPatterns(Transformation): +class ComprehensionPatterns(Transformation[OptimizableComprehension]): ''' Transforms list comprehension into intrinsics. >>> import gast as ast @@ -28,9 +28,6 @@ class ComprehensionPatterns(Transformation): return ([0] * builtins.len(builtins.range(y))) ''' - def __init__(self): - Transformation.__init__(self, OptimizableComprehension) - def visit_Module(self, node): self.use_itertools = False self.generic_visit(node) diff --git a/contrib/python/pythran/pythran/optimizations/constant_folding.py b/contrib/python/pythran/pythran/optimizations/constant_folding.py index 5bf1f3e0233..b89b578b777 100644 --- a/contrib/python/pythran/pythran/optimizations/constant_folding.py +++ b/contrib/python/pythran/pythran/optimizations/constant_folding.py @@ -13,6 +13,7 @@ import builtins import gast as ast from copy import deepcopy import logging +import numpy import sys logger = logging.getLogger('pythran') @@ -57,6 +58,22 @@ class PythranBuiltins(object): return true_br if cond else false_br @staticmethod + def StaticIfReturn(val): + return (None, val, None) + + @staticmethod + def StaticIfNoReturn(val): + return (None, val, None) + + @staticmethod + def StaticIfCont(val): + return (None, val, None) + + @staticmethod + def StaticIfBreak(val): + return (None, val, None) + + @staticmethod def is_none(val): return val is None @@ -64,6 +81,10 @@ class PythranBuiltins(object): def make_shape(*args): return args + @staticmethod + def restrict_assign(expr, value): + numpy.copyto(expr, value) + class BreakLoop(Exception): pass @@ -86,7 +107,8 @@ class ConstEval(ast.NodeVisitor): # stmt def visit_Return(self, node): - self.locals['@'] = node.value and self.visit(node.value) + if '@' not in self.locals: + self.locals['@'] = node.value and self.visit(node.value) def visit_Delete(self, node): if isinstance(node, ast.Name): @@ -317,15 +339,17 @@ class ConstEval(ast.NodeVisitor): elif type(op) is ast.LtE: cond = curr <= right elif type(op) is ast.Gt: - cond = curr <= right + cond = curr > right elif type(op) is ast.GtE: - cond = curr <= right + cond = curr >= right elif type(op) is ast.Is: - cond = curr <= right + cond = curr is right elif type(op) is ast.IsNot: - cond = curr <= right + cond = curr is not right elif type(op) is ast.In: - cond = curr <= right + cond = curr in right + elif type(op) is ast.NotIn: + cond = curr not in right else: raise ValueError("invalid compare op") if not cond: @@ -387,7 +411,7 @@ class ConstEval(ast.NodeVisitor): assert not self.locals -class ConstantFolding(Transformation): +class ConstantFolding(Transformation[ConstantExpressions]): """ Replace constant expression by their evaluation. @@ -402,9 +426,6 @@ class ConstantFolding(Transformation): return 4 """ - def __init__(self): - Transformation.__init__(self, ConstantExpressions) - def prepare(self, node): assert isinstance(node, ast.Module) self.env = {'builtins': builtins} @@ -487,7 +508,7 @@ class ConstantFolding(Transformation): return Transformation.generic_visit(self, node) -class PartialConstantFolding(Transformation): +class PartialConstantFolding(Transformation[ConstantExpressions]): """ Replace partially constant expression by their evaluation. @@ -521,9 +542,6 @@ class PartialConstantFolding(Transformation): return (n, m, n) """ - def __init__(self): - Transformation.__init__(self, ConstantExpressions) - def fold_mult_left(self, node): if not isinstance(node.left, (ast.List, ast.Tuple)): return False diff --git a/contrib/python/pythran/pythran/optimizations/dead_code_elimination.py b/contrib/python/pythran/pythran/optimizations/dead_code_elimination.py index fd1a6b91dc3..e6d0ff984c0 100644 --- a/contrib/python/pythran/pythran/optimizations/dead_code_elimination.py +++ b/contrib/python/pythran/pythran/optimizations/dead_code_elimination.py @@ -20,7 +20,7 @@ class ClumsyOpenMPDependencyHandler(ast.NodeVisitor): return node -class DeadCodeElimination(Transformation): +class DeadCodeElimination(Transformation[PureExpressions, DefUseChains, Ancestors]): """ Remove useless statement like: - assignment to unused variables @@ -57,9 +57,7 @@ class DeadCodeElimination(Transformation): return 1 """ def __init__(self): - super(DeadCodeElimination, self).__init__(PureExpressions, - DefUseChains, - Ancestors) + super().__init__() self.blacklist = set() def used_target(self, node): diff --git a/contrib/python/pythran/pythran/optimizations/fast_gexpr.py b/contrib/python/pythran/pythran/optimizations/fast_gexpr.py index 8fe23b33d08..58b437de5c3 100644 --- a/contrib/python/pythran/pythran/optimizations/fast_gexpr.py +++ b/contrib/python/pythran/pythran/optimizations/fast_gexpr.py @@ -8,10 +8,6 @@ import gast as ast class FastGExpr(Transformation): - def __init__(self): - self.update = False - super(FastGExpr, self).__init__(InterproceduralAliases) - def as_gexpr(self, node): if not isinstance(node, ast.Subscript): return None @@ -39,10 +35,20 @@ class FastGExpr(Transformation): if isinstance(value, ast.Subscript): if not isinstance(value.value, ast.Name): return True + # Lazy loading of interprocedural_aliases as it's a costly analysis + if not self.interprocedural_aliases: + self.interprocedural_aliases = self.passmanager.gather(InterproceduralAliases, self.current_module) return not self.interprocedural_aliases[gexpr[0]].isdisjoint(self.interprocedural_aliases[value.value]) return True + def visit_Module(self, node): + self.current_module = node + self.interprocedural_aliases = None + new_node = self.generic_visit(node) + self.interprocedural_aliases = None + self.current_module = None + return new_node def visit_Assign(self, node): targets = node.targets if isinstance(node, ast.Assign) else (node.target,) diff --git a/contrib/python/pythran/pythran/optimizations/forward_substitution.py b/contrib/python/pythran/pythran/optimizations/forward_substitution.py index 4d34f8c05e5..79d0c8006de 100644 --- a/contrib/python/pythran/pythran/optimizations/forward_substitution.py +++ b/contrib/python/pythran/pythran/optimizations/forward_substitution.py @@ -9,6 +9,7 @@ from pythran.passmanager import Transformation import pythran.graph as graph from collections import defaultdict +from copy import deepcopy import gast as ast try: @@ -45,7 +46,8 @@ class Remover(ast.NodeTransformer): return node -class ForwardSubstitution(Transformation): +class ForwardSubstitution(Transformation[LazynessAnalysis, UseDefChains, DefUseChains, + Ancestors, CFG, Literals]): """ Replace variable that can be computed later. @@ -80,12 +82,7 @@ class ForwardSubstitution(Transformation): def __init__(self): """ Satisfy dependencies on others analyses. """ - super(ForwardSubstitution, self).__init__(LazynessAnalysis, - UseDefChains, - DefUseChains, - Ancestors, - CFG, - Literals) + super().__init__() self.to_remove = None def visit_FunctionDef(self, node): @@ -98,6 +95,31 @@ class ForwardSubstitution(Transformation): Remover(self.to_remove).visit(node) return node + def visit_AugAssign(self, node): + # Separate case for augassign, where we only handle situation where the + # def is a literal (any kind) with a single use (this AugAssign). + self.generic_visit(node) + if not isinstance(node.target, ast.Name): + return node + + defs = self.use_def_chains[node.target] + name_defs = [dnode for dnode in defs if isinstance(dnode.node, ast.Name)] + if len(name_defs) != 1: + return node + name_def, = name_defs + dnode = name_def.node + parent = self.ancestors[dnode][-1] + if not isinstance(parent, ast.Assign): + return node + + try: + ast.literal_eval(parent.value) + except ValueError: + return node + + self.update = True + return ast.Assign([node.target], ast.BinOp(deepcopy(parent.value), node.op, node.value)) + def visit_Name(self, node): if not isinstance(node.ctx, ast.Load): return node diff --git a/contrib/python/pythran/pythran/optimizations/inline_builtins.py b/contrib/python/pythran/pythran/optimizations/inline_builtins.py index b8cd26f7a1a..4bddbf480d8 100644 --- a/contrib/python/pythran/pythran/optimizations/inline_builtins.py +++ b/contrib/python/pythran/pythran/optimizations/inline_builtins.py @@ -11,7 +11,7 @@ from copy import deepcopy import gast as ast -class InlineBuiltins(Transformation): +class InlineBuiltins(Transformation[Aliases]): """ Replace some builtins by their bodies. @@ -34,9 +34,6 @@ class InlineBuiltins(Transformation): return [bar(1), bar(2)] """ - def __init__(self): - Transformation.__init__(self, Aliases) - def inlineBuiltinsXMap(self, node): self.update = True diff --git a/contrib/python/pythran/pythran/optimizations/inlining.py b/contrib/python/pythran/pythran/optimizations/inlining.py index dac8dc6b2f4..803066028c7 100644 --- a/contrib/python/pythran/pythran/optimizations/inlining.py +++ b/contrib/python/pythran/pythran/optimizations/inlining.py @@ -7,7 +7,7 @@ import gast as ast import copy -class Inlining(Transformation): +class Inlining(Transformation[Inlinable, Aliases]): """ Inline one line functions. @@ -36,10 +36,10 @@ __pythran_inlinefooa0)) * (__pythran_inlinefoob1 + \ def __init__(self): """ fun : Function {name :body} for inlinable functions. """ + super().__init__() self.update = False self.defs = list() self.call_count = 0 - super(Inlining, self).__init__(Inlinable, Aliases) def visit_Stmt(self, node): """ Add new variable definition before the Statement. """ @@ -54,12 +54,21 @@ __pythran_inlinefooa0)) * (__pythran_inlinefoob1 + \ visit_AugAssign = visit_Stmt visit_Print = visit_Stmt visit_For = visit_Stmt - visit_While = visit_Stmt visit_If = visit_Stmt visit_With = visit_Stmt visit_Assert = visit_Stmt visit_Expr = visit_Stmt + def visit_While(self, node): + # FIXME: we're only preventing inlining within test because it's + # difficult to compute predecessors of the test while also handling + # exceptions. We could use the cfg analysis for this but I'm a bit lazy + # and it's not a critical optimization. + test, node.test = node.test, None + self.generic_visit(node) + node.test = test + return node + def visit_Call(self, node): """ Replace function call by inlined function's body. @@ -86,11 +95,12 @@ __pythran_inlinefooa0)) * (__pythran_inlinefoob1 + \ annotation=None, type_comment=None) self.defs.append(ast.Assign(targets=[new_var], value=arg_call, - type_comment=None)) + type_comment=None)) arg_to_value[arg_fun.id] = ast.Name(id=v_name, ctx=ast.Load(), annotation=None, type_comment=None) + self.call_count += 1 return Inliner(arg_to_value).visit(to_inline.body[0]) return node diff --git a/contrib/python/pythran/pythran/optimizations/iter_transformation.py b/contrib/python/pythran/pythran/optimizations/iter_transformation.py index 9af88c32cac..42b03f346ca 100644 --- a/contrib/python/pythran/pythran/optimizations/iter_transformation.py +++ b/contrib/python/pythran/pythran/optimizations/iter_transformation.py @@ -14,7 +14,7 @@ EQUIVALENT_ITERATORS = { } -class IterTransformation(Transformation): +class IterTransformation(Transformation[PotentialIterator, Aliases]): """ Replaces expressions by iterators when possible. @@ -36,10 +36,6 @@ class IterTransformation(Transformation): return foo(n) """ - def __init__(self): - """Gather required information.""" - Transformation.__init__(self, PotentialIterator, Aliases) - def find_matching_builtin(self, node): """ Return matched keyword. diff --git a/contrib/python/pythran/pythran/optimizations/list_comp_to_genexp.py b/contrib/python/pythran/pythran/optimizations/list_comp_to_genexp.py index 0fd7587f223..346aa0046f3 100644 --- a/contrib/python/pythran/pythran/optimizations/list_comp_to_genexp.py +++ b/contrib/python/pythran/pythran/optimizations/list_comp_to_genexp.py @@ -6,7 +6,7 @@ from pythran.passmanager import Transformation import gast as ast -class ListCompToGenexp(Transformation): +class ListCompToGenexp(Transformation[PotentialIterator]): ''' Transforms list comprehension into genexp >>> import gast as ast @@ -25,8 +25,6 @@ def bar(n): \\n\ def bar(n): return foo((x for x in builtins.range(n))) ''' - def __init__(self): - Transformation.__init__(self, PotentialIterator) def visit_ListComp(self, node): self.generic_visit(node) diff --git a/contrib/python/pythran/pythran/optimizations/list_to_tuple.py b/contrib/python/pythran/pythran/optimizations/list_to_tuple.py index 87261ab7330..7c49b65b98a 100644 --- a/contrib/python/pythran/pythran/optimizations/list_to_tuple.py +++ b/contrib/python/pythran/pythran/optimizations/list_to_tuple.py @@ -24,7 +24,7 @@ def totuple(node): return ast.Tuple(node.elts, node.ctx) -class ListToTuple(Transformation): +class ListToTuple(Transformation[Aliases, FixedSizeList]): """ Replace list nodes by tuple nodes when possible @@ -39,9 +39,6 @@ class ListToTuple(Transformation): import numpy return numpy.ones((n, n)) """ - def __init__(self): - self.update = False - super(ListToTuple, self).__init__(Aliases, FixedSizeList) def visit_AugAssign(self, node): if not islist(node.value): diff --git a/contrib/python/pythran/pythran/optimizations/modindex.py b/contrib/python/pythran/pythran/optimizations/modindex.py index 2a046ea10fe..99917db50ae 100644 --- a/contrib/python/pythran/pythran/optimizations/modindex.py +++ b/contrib/python/pythran/pythran/optimizations/modindex.py @@ -9,7 +9,8 @@ import gast as ast from copy import deepcopy -class ModIndex(Transformation): +class ModIndex(Transformation[UseDefChains, Ancestors, Aliases, RangeValues, + Identifiers]): ''' Simplify modulo on loop index @@ -33,8 +34,7 @@ class ModIndex(Transformation): ''' def __init__(self): - Transformation.__init__(self, UseDefChains, Ancestors, Aliases, - RangeValues, Identifiers) + super().__init__() self.loops_mod = dict() def single_def(self, node): diff --git a/contrib/python/pythran/pythran/optimizations/pattern_transform.py b/contrib/python/pythran/pythran/optimizations/pattern_transform.py index 1638bbd9da3..e18d49c3af7 100644 --- a/contrib/python/pythran/pythran/optimizations/pattern_transform.py +++ b/contrib/python/pythran/pythran/optimizations/pattern_transform.py @@ -334,8 +334,8 @@ class PlaceholderReplace(Transformation): def __init__(self, placeholders): """ Store placeholders value collected. """ + super().__init__() self.placeholders = placeholders - super(PlaceholderReplace, self).__init__() def visit(self, node): """ Replace the placeholder if it is one or continue. """ @@ -352,10 +352,6 @@ class PatternTransform(Transformation): Based on BaseMatcher to search correct pattern. """ - def __init__(self): - """ Initialize the Basematcher to search for placeholders. """ - super(PatternTransform, self).__init__() - def visit_Module(self, node): self.extra_imports = [] self.generic_visit(node) diff --git a/contrib/python/pythran/pythran/optimizations/range_based_simplify.py b/contrib/python/pythran/pythran/optimizations/range_based_simplify.py index 3add95211ea..f1995698eef 100644 --- a/contrib/python/pythran/pythran/optimizations/range_based_simplify.py +++ b/contrib/python/pythran/pythran/optimizations/range_based_simplify.py @@ -8,7 +8,7 @@ from math import isinf from copy import deepcopy -class RangeBasedSimplify(Transformation): +class RangeBasedSimplify(Transformation[RangeValues]): ''' Simplify expressions based on range analysis @@ -40,9 +40,6 @@ class RangeBasedSimplify(Transformation): return (2, 1) ''' - def __init__(self): - Transformation.__init__(self, RangeValues) - def visit_OMPDirective(self, node): return node diff --git a/contrib/python/pythran/pythran/optimizations/remove_dead_functions.py b/contrib/python/pythran/pythran/optimizations/remove_dead_functions.py index 5aff9a28ddb..55df57af0de 100644 --- a/contrib/python/pythran/pythran/optimizations/remove_dead_functions.py +++ b/contrib/python/pythran/pythran/optimizations/remove_dead_functions.py @@ -5,7 +5,7 @@ from pythran.passmanager import Transformation import pythran.metadata as metadata -class RemoveDeadFunctions(Transformation): +class RemoveDeadFunctions(Transformation[DefUseChains]): """ Remove useless local functions @@ -24,9 +24,6 @@ class RemoveDeadFunctions(Transformation): <BLANKLINE> """ - def __init__(self): - super(RemoveDeadFunctions, self).__init__(DefUseChains) - def visit_FunctionDef(self, node): if metadata.get(node, metadata.Local): if not self.def_use_chains.chains[node].users(): diff --git a/contrib/python/pythran/pythran/optimizations/square.py b/contrib/python/pythran/pythran/optimizations/square.py index 85d8cc2836f..062045edc20 100644 --- a/contrib/python/pythran/pythran/optimizations/square.py +++ b/contrib/python/pythran/pythran/optimizations/square.py @@ -38,9 +38,6 @@ class Square(Transformation): [AST_any(), ast.Constant(2, None)], []) - def __init__(self): - Transformation.__init__(self) - def replace(self, value): self.update = self.need_import = True module_name = ast.Name(mangle('numpy'), ast.Load(), None, None) diff --git a/contrib/python/pythran/pythran/optimizations/tuple_to_shape.py b/contrib/python/pythran/pythran/optimizations/tuple_to_shape.py index 7a3e5f92590..797c3400b7c 100644 --- a/contrib/python/pythran/pythran/optimizations/tuple_to_shape.py +++ b/contrib/python/pythran/pythran/optimizations/tuple_to_shape.py @@ -24,7 +24,7 @@ def toshape(node): return ast.Call(b, node.elts, []) -class TupleToShape(Transformation): +class TupleToShape(Transformation[Aliases]): """ Replace tuple nodes by shape when relevant @@ -39,10 +39,6 @@ class TupleToShape(Transformation): import numpy return numpy.ones(builtins.pythran.make_shape(n, 4)) """ - def __init__(self): - self.update = False - super(TupleToShape, self).__init__(Aliases) - def visit_Call(self, node): func_aliases = self.aliases.get(node.func, None) if func_aliases is not None: diff --git a/contrib/python/pythran/pythran/passmanager.py b/contrib/python/pythran/pythran/passmanager.py index e4ea253888f..a4c1191a0d9 100644 --- a/contrib/python/pythran/pythran/passmanager.py +++ b/contrib/python/pythran/pythran/passmanager.py @@ -14,6 +14,7 @@ There are two kinds of passes: transformations and analysis. import gast as ast import os import re +from collections.abc import Iterable from time import time @@ -23,7 +24,7 @@ def uncamel(name): return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower() -class AnalysisContext(object): +class AnalysisContext: """ Class that stores the hierarchy of node visited. @@ -38,7 +39,16 @@ class AnalysisContext(object): self.function = None -class ContextManager(object): +class ContextManagerMeta(type): + def __getitem__(cls, Dependencies): + class CustomContextManager(cls): + if isinstance(Dependencies, Iterable): + deps = Dependencies + else: + deps = Dependencies, + return CustomContextManager + +class ContextManager(metaclass=ContextManagerMeta): """ Class to be inherited from to add automatic update of context. @@ -46,11 +56,11 @@ class ContextManager(object): The optional analysis dependencies are listed in `dependencies'. """ - def __init__(self, *dependencies): + deps = () + + def __init__(self): """ Create default context and save all dependencies. """ - self.deps = dependencies self.verify_dependencies() - super(ContextManager, self).__init__() def attach(self, pm, ctx=None): self.passmanager = pm @@ -101,19 +111,14 @@ class ContextManager(object): a.attach(self.passmanager, self.ctx) return a.run(node) - class Analysis(ContextManager, ast.NodeVisitor): """ A pass that does not change its content but gathers informations about it. """ - def __init__(self, *dependencies): - '''`dependencies' holds the type of all analysis required by this - analysis. `self.result' must be set prior to calling this - constructor.''' - assert hasattr(self, "result"), ( - "An analysis must have a result attribute when initialized") + def __init__(self): + super().__init__() + self.result = type(self).ResultType() self.update = False - ContextManager.__init__(self, *dependencies) def run(self, node): key = node, type(self) @@ -131,10 +136,9 @@ class Analysis(ContextManager, ast.NodeVisitor): self.display(self.run(node)) return False, node - class ModuleAnalysis(Analysis): - """An analysis that operates on a whole module.""" + def run(self, node): if not isinstance(node, ast.Module): if self.ctx.module is None: @@ -143,9 +147,7 @@ class ModuleAnalysis(Analysis): node = self.ctx.module return super(ModuleAnalysis, self).run(node) - class FunctionAnalysis(Analysis): - """An analysis that operates on a function.""" def run(self, node): @@ -164,14 +166,10 @@ class FunctionAnalysis(Analysis): node = self.ctx.function return super(FunctionAnalysis, self).run(node) - class NodeAnalysis(Analysis): - """An analysis that operates on any node.""" - class Backend(ModuleAnalysis): - """A pass that produces code from an AST.""" @@ -179,9 +177,9 @@ class Transformation(ContextManager, ast.NodeTransformer): """A pass that updates its content.""" - def __init__(self, *args, **kwargs): + def __init__(self): """ Initialize the update used to know if update happened. """ - super(Transformation, self).__init__(*args, **kwargs) + super(Transformation, self).__init__() self.update = False def run(self, node): @@ -201,7 +199,7 @@ class Transformation(ContextManager, ast.NodeTransformer): return self.update, new_node -class PassManager(object): +class PassManager: ''' Front end to the pythran pass system. ''' diff --git a/contrib/python/pythran/pythran/pythonic/builtins/oct.hpp b/contrib/python/pythran/pythran/pythonic/builtins/oct.hpp index 9150c790633..791d2811563 100644 --- a/contrib/python/pythran/pythran/pythonic/builtins/oct.hpp +++ b/contrib/python/pythran/pythran/pythonic/builtins/oct.hpp @@ -17,13 +17,7 @@ namespace builtins types::str oct(T const &v) { std::ostringstream oss; - oss << -#if defined(__PYTHRAN__) && __PYTHRAN__ == 3 - "0o" -#else - '0' -#endif - << std::oct << v; + oss << "0o" << std::oct << v; return oss.str(); } } // namespace builtins diff --git a/contrib/python/pythran/pythran/pythonic/builtins/str/splitlines.hpp b/contrib/python/pythran/pythran/pythonic/builtins/str/splitlines.hpp new file mode 100644 index 00000000000..91fa344ada5 --- /dev/null +++ b/contrib/python/pythran/pythran/pythonic/builtins/str/splitlines.hpp @@ -0,0 +1,35 @@ +#ifndef PYTHONIC_BUILTIN_STR_SPLITLINES_HPP +#define PYTHONIC_BUILTIN_STR_SPLITLINES_HPP + +#include "pythonic/include/builtins/str/splitlines.hpp" + +#include "pythonic/types/list.hpp" +#include "pythonic/types/str.hpp" +#include "pythonic/utils/functor.hpp" + +PYTHONIC_NS_BEGIN + +namespace builtins +{ + + namespace str + { + + inline types::list<types::str> splitlines(types::str const &in, bool keepends) + { + auto const& in_ = in.chars(); + types::list<types::str> res(0); + size_t current = 0; + const ssize_t adjust = keepends ? 1 : 0; + while (current < (size_t)in.size()) { + size_t next = in_.find("\n", current); + res.push_back(in_.substr(current, next - current + adjust)); + current = (next == types::str::npos) ? next : (next + 1); + } + return res; + } + + } // namespace str +} // namespace builtins +PYTHONIC_NS_END +#endif diff --git a/contrib/python/pythran/pythran/pythonic/include/builtins/str/splitlines.hpp b/contrib/python/pythran/pythran/pythonic/include/builtins/str/splitlines.hpp new file mode 100644 index 00000000000..8bc7a3c45c0 --- /dev/null +++ b/contrib/python/pythran/pythran/pythonic/include/builtins/str/splitlines.hpp @@ -0,0 +1,22 @@ +#ifndef PYTHONIC_INCLUDE_BUILTIN_STR_SPLITLINES_HPP +#define PYTHONIC_INCLUDE_BUILTIN_STR_SPLITLINES_HPP + +#include "pythonic/include/types/list.hpp" +#include "pythonic/include/types/str.hpp" +#include "pythonic/include/utils/functor.hpp" + +PYTHONIC_NS_BEGIN + +namespace builtins +{ + + namespace str + { + + types::list<types::str> splitlines(types::str const &in, bool keepends = false); + + DEFINE_FUNCTOR(pythonic::builtins::str, splitlines); + } +} +PYTHONIC_NS_END +#endif diff --git a/contrib/python/pythran/pythran/pythonic/include/numpy/array.hpp b/contrib/python/pythran/pythran/pythonic/include/numpy/array.hpp index fbc6081c7d6..d64ec19f4cc 100644 --- a/contrib/python/pythran/pythran/pythonic/include/numpy/array.hpp +++ b/contrib/python/pythran/pythran/pythonic/include/numpy/array.hpp @@ -53,6 +53,13 @@ namespace numpy typename types::array_base<T, N, V>::shape_t> array(types::array_base<T, N, V> &&, dtype d = dtype()); + template <class... Ts> + auto array(std::tuple<Ts...> t) + -> decltype(array(types::to_array<typename __combined<Ts...>::type>(t))) + { + return array(types::to_array<typename __combined<Ts...>::type>(t)); + } + DEFINE_FUNCTOR(pythonic::numpy, array); } // namespace numpy PYTHONIC_NS_END diff --git a/contrib/python/pythran/pythran/pythonic/include/numpy/dot.hpp b/contrib/python/pythran/pythran/pythonic/include/numpy/dot.hpp index 1d7b2a2576a..41aab6226c4 100644 --- a/contrib/python/pythran/pythran/pythonic/include/numpy/dot.hpp +++ b/contrib/python/pythran/pythran/pythonic/include/numpy/dot.hpp @@ -29,15 +29,15 @@ struct is_strided { template <class E> struct is_blas_array { static constexpr bool value = - pythonic::types::is_array<E>::value && + pythonic::types::has_buffer<E>::value && is_blas_type<typename pythonic::types::dtype_of<E>::type>::value && !is_strided<E>::value; }; template <class E> -struct is_blas_expr { +struct is_blas_view { static constexpr bool value = - pythonic::types::is_array<E>::value && + pythonic::types::has_buffer<E>::value && is_blas_type<typename pythonic::types::dtype_of<E>::type>::value; }; @@ -56,7 +56,7 @@ namespace numpy typename std::enable_if< types::is_numexpr_arg<E>::value && types::is_numexpr_arg<F>::value && E::value == 1 && F::value == 1 && - (!is_blas_expr<E>::value || !is_blas_expr<F>::value || + (!is_blas_view<E>::value || !is_blas_view<F>::value || !std::is_same<typename E::dtype, typename F::dtype>::value), typename __combined<typename E::dtype, typename F::dtype>::type>::type dot(E const &e, F const &f); @@ -102,7 +102,7 @@ namespace numpy E::value == 1 && F::value == 1 && std::is_same<typename E::dtype, float>::value && std::is_same<typename F::dtype, float>::value && - (is_blas_expr<E>::value && is_blas_expr<F>::value && + (is_blas_view<E>::value && is_blas_view<F>::value && !(is_blas_array<E>::value && is_blas_array<F>::value)), float>::type dot(E const &e, F const &f); @@ -112,7 +112,7 @@ namespace numpy E::value == 1 && F::value == 1 && std::is_same<typename E::dtype, double>::value && std::is_same<typename F::dtype, double>::value && - (is_blas_expr<E>::value && is_blas_expr<F>::value && + (is_blas_view<E>::value && is_blas_view<F>::value && !(is_blas_array<E>::value && is_blas_array<F>::value)), double>::type dot(E const &e, F const &f); @@ -122,7 +122,7 @@ namespace numpy E::value == 1 && F::value == 1 && std::is_same<typename E::dtype, std::complex<float>>::value && std::is_same<typename F::dtype, std::complex<float>>::value && - (is_blas_expr<E>::value && is_blas_expr<F>::value && + (is_blas_view<E>::value && is_blas_view<F>::value && !(is_blas_array<E>::value && is_blas_array<F>::value)), std::complex<float>>::type dot(E const &e, F const &f); @@ -132,7 +132,7 @@ namespace numpy E::value == 1 && F::value == 1 && std::is_same<typename E::dtype, std::complex<double>>::value && std::is_same<typename F::dtype, std::complex<double>>::value && - (is_blas_expr<E>::value && is_blas_expr<F>::value && + (is_blas_view<E>::value && is_blas_view<F>::value && !(is_blas_array<E>::value && is_blas_array<F>::value)), std::complex<double>>::type dot(E const &e, F const &f); diff --git a/contrib/python/pythran/pythran/pythonic/include/numpy/float128.hpp b/contrib/python/pythran/pythran/pythonic/include/numpy/float128.hpp index a0051f56bc4..8f4819e2b10 100644 --- a/contrib/python/pythran/pythran/pythonic/include/numpy/float128.hpp +++ b/contrib/python/pythran/pythran/pythonic/include/numpy/float128.hpp @@ -12,7 +12,11 @@ namespace numpy namespace details { - long double float128(); + inline long double float128() + { + return {}; + } + template <class V> long double float128(V v); } // namespace details diff --git a/contrib/python/pythran/pythran/pythonic/include/types/NoneType.hpp b/contrib/python/pythran/pythran/pythonic/include/types/NoneType.hpp index b60dab4d715..9dfdbd77bc9 100644 --- a/contrib/python/pythran/pythran/pythonic/include/types/NoneType.hpp +++ b/contrib/python/pythran/pythran/pythonic/include/types/NoneType.hpp @@ -15,6 +15,14 @@ namespace types struct none_type { none_type(); intptr_t id() const; + bool operator==(none_type) const + { + return true; + } + explicit operator bool() const + { + return false; + } }; inline std::ostream &operator<<(std::ostream &os, none_type const &) @@ -25,7 +33,7 @@ namespace types template <class T, bool is_fundamental = std::is_fundamental<T>::value> struct none; - /* Type adapator to simulate an option type + /* Type adaptor to simulate an option type * * see http://en.wikipedia.org/wiki/Option_type */ @@ -60,10 +68,13 @@ namespace types explicit operator bool() const; - intptr_t id() const; + explicit operator T const &() const + { + assert(!is_none); + return *static_cast<T const *>(this); + } - template <class T0> - friend std::ostream &operator<<(std::ostream &os, none<T0, false> const &); + intptr_t id() const; }; /* specialization of none for integral types we cannot derive from @@ -75,7 +86,7 @@ namespace types return !static_cast<P const *>(this)->is_none && static_cast<P const *>(this)->data; } - operator T() const + operator T const &() const { return static_cast<P const *>(this)->data; } @@ -91,64 +102,6 @@ namespace types template <class T> struct none<T, true> : none_data<none<T, true>, T> { T data; - template <class T1> - friend std::ostream &operator<<(std::ostream &, none<T1, true> const &); - template <class T1> - friend T1 operator+(none<T1, true> const &t0, T1 const &t1); - template <class T1> - friend T1 operator+(T1 const &t0, none<T1, true> const &t1); - template <class T1> - friend none<T1, true> operator+(none<T1, true> const &t0, - none<T1, true> const &t1); - template <class T1> - friend bool operator>(none<T1, true> const &t0, T1 const &t1); - template <class T1> - friend bool operator>(T1 const &t0, none<T1, true> const &t1); - template <class T1> - friend none<bool> operator>(none<T1, true> const &t0, - none<T1, true> const &t1); - template <class T1> - friend bool operator>=(none<T1, true> const &t0, T1 const &t1); - template <class T1> - friend bool operator>=(T1 const &t0, none<T1, true> const &t1); - template <class T1> - friend none<bool> operator>=(none<T1, true> const &t0, - none<T1, true> const &t1); - template <class T1> - friend bool operator<(none<T1, true> const &t0, T1 const &t1); - template <class T1> - friend bool operator<(T1 const &t0, none<T1, true> const &t1); - template <class T1> - friend none<bool> operator<(none<T1, true> const &t0, - none<T1, true> const &t1); - template <class T1> - friend bool operator<=(none<T1, true> const &t0, T1 const &t1); - template <class T1> - friend bool operator<=(T1 const &t0, none<T1, true> const &t1); - template <class T1> - friend none<bool> operator<=(none<T1, true> const &t0, - none<T1, true> const &t1); - template <class T1> - friend T1 operator-(none<T1, true> const &t0, T1 const &t1); - template <class T1> - friend T1 operator-(T1 const &t0, none<T1, true> const &t1); - template <class T1> - friend none<T1, true> operator-(none<T1, true> const &t0, - none<T1, true> const &t1); - template <class T1> - friend T1 operator*(none<T1, true> const &t0, T1 const &t1); - template <class T1> - friend T1 operator*(T1 const &t0, none<T1, true> const &t1); - template <class T1> - friend none<T1, true> operator*(none<T1, true> const &t0, - none<T1, true> const &t1); - template <class T1> - friend T1 operator/(none<T1, true> const &t0, T1 const &t1); - template <class T1> - friend T1 operator/(T1 const &t0, none<T1, true> const &t1); - template <class T1> - friend none<T1, true> operator/(none<T1, true> const &t0, - none<T1, true> const &t1); template <class T1> none &operator+=(T1 other); @@ -159,7 +112,6 @@ namespace types template <class T1> none &operator/=(T1 other); - public: bool is_none; none(); none(none_type const &); @@ -181,65 +133,64 @@ namespace types return {static_cast<T1>(data)}; } }; - template <class T> - T operator+(none<T, true> const &t0, T const &t1); - template <class T> - T operator+(T const &t0, none<T, true> const &t1); - template <class T> - none<T, true> operator+(none<T, true> const &t0, none<T, true> const &t1); - template <class T> - bool operator>(none<T, true> const &t0, T const &t1); - template <class T> - bool operator>(T const &t0, none<T, true> const &t1); - template <class T> - none<bool> operator>(none<T, true> const &t0, none<T, true> const &t1); - template <class T> - bool operator>=(none<T, true> const &t0, T const &t1); - template <class T> - bool operator>=(T const &t0, none<T, true> const &t1); - template <class T> - none<bool> operator>=(none<T, true> const &t0, none<T, true> const &t1); - template <class T> - bool operator<(none<T, true> const &t0, T const &t1); - template <class T> - bool operator<(T const &t0, none<T, true> const &t1); - template <class T> - none<bool> operator<(none<T, true> const &t0, none<T, true> const &t1); - template <class T> - bool operator<=(none<T, true> const &t0, T const &t1); - template <class T> - bool operator<=(T const &t0, none<T, true> const &t1); - template <class T> - none<bool> operator<=(none<T, true> const &t0, none<T, true> const &t1); - template <class T> - T operator-(none<T, true> const &t0, T const &t1); - template <class T> - T operator-(T const &t0, none<T, true> const &t1); - template <class T> - none<T, true> operator-(none<T, true> const &t0, none<T, true> const &t1); - template <class T> - T operator*(none<T, true> const &t0, T const &t1); - template <class T> - T operator*(T const &t0, none<T, true> const &t1); - template <class T> - none<T, true> operator*(none<T, true> const &t0, none<T, true> const &t1); - template <class T> - T operator/(none<T, true> const &t0, T const &t1); - template <class T> - T operator/(T const &t0, none<T, true> const &t1); - template <class T> - none<T, true> operator/(none<T, true> const &t0, none<T, true> const &t1); + +#define NONE_OPERATOR_OVERLOAD(op) \ + template <class T> \ + auto operator op(none<T> const &t0, T const &t1) \ + ->decltype(static_cast<T const &>(t0) op t1) \ + { \ + return static_cast<T const &>(t0) op t1; \ + } \ + \ + template <class T> \ + auto operator op(T const &t0, none<T> const &t1) \ + ->decltype(t0 op static_cast<T const &>(t1)) \ + { \ + return t0 op static_cast<T const &>(t1); \ + } \ + \ + template <class T> \ + auto operator op(none<T> const &t0, none<T> const &t1) \ + ->none<decltype(static_cast<T const &>(t0) \ + op static_cast<T const &>(t1))> \ + { \ + if (t0.is_none && t1.is_none) \ + return none_type{}; \ + else { \ + return {static_cast<T const &>(t0) op static_cast<T const &>(t1)}; \ + } \ + } + + NONE_OPERATOR_OVERLOAD(+) + NONE_OPERATOR_OVERLOAD(-) + NONE_OPERATOR_OVERLOAD(*) + NONE_OPERATOR_OVERLOAD(/) + + NONE_OPERATOR_OVERLOAD(>) + NONE_OPERATOR_OVERLOAD(>=) + NONE_OPERATOR_OVERLOAD(<) + NONE_OPERATOR_OVERLOAD(<=) + template <class T0, class T1> decltype(operator_::mod(std::declval<T0>(), std::declval<T1>())) - operator%(none<T0, true> const &t0, T1 const &t1); + operator%(none<T0> const &t0, T1 const &t1); + template <class T0, class T1> decltype(operator_::mod(std::declval<T0>(), std::declval<T1>())) - operator%(T0 const &t0, none<T1, true> const &t1); + operator%(T0 const &t0, none<T1> const &t1); + template <class T0, class T1> - none<decltype(operator_::mod(std::declval<T0>(), std::declval<T1>())), true> - operator%(none<T0, true> const &t0, none<T1, true> const &t1); - template <class T> - std::ostream &operator<<(std::ostream &os, none<T, true> const &v); + none<decltype(operator_::mod(std::declval<T0>(), std::declval<T1>()))> + operator%(none<T0> const &t0, none<T1> const &t1); + + template <class T, bool F> + std::ostream &operator<<(std::ostream &os, none<T, F> const &v) + { + if (v.is_none) + return os << none_type(); + else + return os << v.data; + } template <class T> struct is_none { @@ -269,6 +220,14 @@ namespace std struct tuple_element<I, pythonic::types::none<T0>> { using type = typename std::tuple_element<I, T0>::type; }; + + template <> + struct hash<pythonic::types::none_type> { + size_t operator()(const pythonic::types::none_type &x) const + { + return 0; + } + }; } // namespace std /* type inference stuff { */ diff --git a/contrib/python/pythran/pythran/pythonic/include/types/combined.hpp b/contrib/python/pythran/pythran/pythonic/include/types/combined.hpp index 6f531e9fb98..4e2179f4848 100644 --- a/contrib/python/pythran/pythran/pythonic/include/types/combined.hpp +++ b/contrib/python/pythran/pythran/pythonic/include/types/combined.hpp @@ -45,9 +45,9 @@ struct __combined<T0, T1> { // by our clumsy type inference scheme // so we sometime endup with __combined<indexable_container<...>, int> which // only makes sense when broadcasting - // fortunately, broadcasting is only supported by ndarray, && we already + // fortunately, broadcasting is only supported by ndarray, and we already // ignore __combined for ndarray - // so the only thing to do in such situations is « ! throw an error » + // so the only thing to do in such situations is « not throw an error » template <class F0, class F1> static F0 get(...); @@ -60,116 +60,91 @@ struct __combined<T0, T1> { }; template <class T0, class T1> -struct __combined<const T0, T1> { - using type = typename std::add_const<typename __combined<T0, T1>::type>::type; +struct __combined<const T0, T1> : std::add_const<typename __combined<T0, T1>::type> { }; template <class T0, class T1> -struct __combined<T0, const T1> { - using type = typename std::add_const<typename __combined<T0, T1>::type>::type; +struct __combined<T0, const T1> : std::add_const<typename __combined<T0, T1>::type> { }; template <class T0, class T1> -struct __combined<T0 &, T1> { - using type = typename __combined<T0, T1>::type; +struct __combined<T0 &, T1> : __combined<T0, T1> { }; template <class T0, class T1> -struct __combined<T0 &&, T1> { - using type = typename __combined<T0, T1>::type; +struct __combined<T0 &&, T1> : __combined<T0, T1> { }; template <class T0, class T1> -struct __combined<T0 const &, T1> { - using type = typename __combined<T0, T1>::type; +struct __combined<T0 const &, T1> : __combined<T0, T1> { }; template <class T0, class T1> -struct __combined<T0, T1 &> { - using type = typename __combined<T0, T1>::type; +struct __combined<T0, T1 &> : __combined<T0, T1> { }; template <class T0, class T1> -struct __combined<T0, T1 &&> { - using type = typename __combined<T0, T1>::type; +struct __combined<T0, T1 &&> : __combined<T0, T1> { }; template <class T0, class T1> -struct __combined<T0, T1 const &> { - using type = typename __combined<T0, T1>::type; +struct __combined<T0, T1 const &> : __combined<T0, T1> { }; template <class T0, class T1> -struct __combined<const T0, T1 const &> { - using type = typename __combined<T0, T1>::type; +struct __combined<const T0, T1 const &> : __combined<T0, T1> { }; template <class T0, class T1> -struct __combined<const T0, T1 &> { - using type = typename __combined<T0, T1>::type; +struct __combined<const T0, T1 &> : __combined<T0, T1> { }; template <class T0, class T1> -struct __combined<const T0, T1 &&> { - using type = typename __combined<T0, T1>::type; +struct __combined<const T0, T1 &&> : __combined<T0, T1> { }; template <class T0, class T1> -struct __combined<T0 &, T1 const> { - using type = typename __combined<T0, T1>::type; +struct __combined<T0 &, T1 const> : __combined<T0, T1> { }; template <class T0, class T1> -struct __combined<T0 &&, T1 const> { - using type = typename __combined<T0, T1>::type; +struct __combined<T0 &&, T1 const> : __combined<T0, T1> { }; template <class T0, class T1> -struct __combined<T0 const &, T1 const> { - using type = typename __combined<T0, T1>::type; +struct __combined<T0 const &, T1 const> : __combined<T0, T1> { }; template <class T0, class T1> -struct __combined<T0 &, T1 const &> { - using type = typename __combined<T0, T1>::type; +struct __combined<T0 &, T1 const &> : __combined<T0, T1> { }; template <class T0, class T1> -struct __combined<T0 &&, T1 const &> { - using type = typename __combined<T0, T1>::type; +struct __combined<T0 &&, T1 const &> : __combined<T0, T1> { }; template <class T0, class T1> -struct __combined<T0 const &, T1 &> { - using type = typename __combined<T0, T1>::type; +struct __combined<T0 const &, T1 &> : __combined<T0, T1> { }; template <class T0, class T1> -struct __combined<T0 const &, T1 &&> { - using type = typename __combined<T0, T1>::type; +struct __combined<T0 const &, T1 &&> : __combined<T0, T1> { }; template <class T0, class T1> -struct __combined<T0 &, T1 &> { - using type = typename std::add_lvalue_reference< - typename __combined<T0, T1>::type>::type; +struct __combined<T0 &, T1 &> : std::add_lvalue_reference<typename __combined<T0, T1>::type> { }; template <class T0, class T1> -struct __combined<T0 &&, T1 &&> { - using type = typename std::add_rvalue_reference< - typename __combined<T0, T1>::type>::type; +struct __combined<T0 &&, T1 &&> : std::add_rvalue_reference<typename __combined<T0, T1>::type> { }; template <class T0, class T1> -struct __combined<const T0, const T1> { - using type = typename std::add_const<typename __combined<T0, T1>::type>::type; +struct __combined<const T0, const T1> : std::add_const<typename __combined<T0, T1>::type> { }; template <class T0, class T1> -struct __combined<const T0 &, const T1 &> { - using type = typename std::add_lvalue_reference< - typename std::add_const<typename __combined<T0, T1>::type>::type>::type; +struct __combined<const T0 &, const T1 &> : std::add_lvalue_reference<typename std::add_const<typename __combined<T0, T1>::type>::type> { }; template <class T> @@ -183,6 +158,13 @@ private: container(); }; +namespace std { + template <size_t I, class T> + struct tuple_element<I, container<T>> { + using type = typename container<T>::value_type; + }; +} + template <class K, class V> class indexable_container { @@ -196,6 +178,13 @@ private: indexable_container(); }; +namespace std { + template <size_t I, class K, class V> + struct tuple_element<I, indexable_container<K, V>> { + using type = typename indexable_container<K, V>::value_type; + }; +} + template <class T> class dict_container { @@ -207,6 +196,13 @@ private: dict_container(); }; +namespace std { + template <size_t I, class T> + struct tuple_element<I, dict_container<T>> { + using type = typename dict_container<T>::value_type; + }; +} + template <class T> class indexable { diff --git a/contrib/python/pythran/pythran/pythonic/include/types/dict.hpp b/contrib/python/pythran/pythran/pythonic/include/types/dict.hpp index 5f6b826bd94..7772a2641e5 100644 --- a/contrib/python/pythran/pythran/pythonic/include/types/dict.hpp +++ b/contrib/python/pythran/pythran/pythonic/include/types/dict.hpp @@ -28,35 +28,56 @@ namespace types struct empty_dict; template <class I> - struct item_iterator_adaptator : public I { + struct item_iterator_adaptator { + I base; + using difference_type = typename std::iterator_traits<I>::difference_type; using value_type = make_tuple_t< - typename std::remove_cv<typename I::value_type::first_type>::type, - typename I::value_type::second_type>; + typename std::remove_cv<typename std::iterator_traits<I>::value_type::first_type>::type, + typename std::iterator_traits<I>::value_type::second_type>; using pointer = value_type *; using reference = value_type &; + using iterator_category = typename std::iterator_traits<I>::iterator_category; item_iterator_adaptator() = default; item_iterator_adaptator(I const &i); value_type operator*() const; + bool operator==(item_iterator_adaptator const& other) const { return base == other.base;} + bool operator!=(item_iterator_adaptator const& other) const { return base != other.base;} + item_iterator_adaptator& operator++() { ++base; return *this;} + auto operator->() ->decltype( &*base) { return &*base;} }; template <class I> - struct key_iterator_adaptator : public I { - using value_type = typename I::value_type::first_type; - using pointer = typename I::value_type::first_type *; - using reference = typename I::value_type::first_type &; - key_iterator_adaptator(); + struct key_iterator_adaptator { + I base; + using difference_type = typename std::iterator_traits<I>::difference_type; + using value_type = typename std::iterator_traits<I>::value_type::first_type; + using pointer = typename std::iterator_traits<I>::value_type::first_type *; + using reference = typename std::iterator_traits<I>::value_type::first_type &; + using iterator_category = typename std::iterator_traits<I>::iterator_category; + key_iterator_adaptator() = default; key_iterator_adaptator(I const &i); value_type operator*() const; + bool operator==(key_iterator_adaptator const& other) const { return base == other.base;} + bool operator!=(key_iterator_adaptator const& other) const { return base != other.base;} + key_iterator_adaptator& operator++() { ++base; return *this;} + auto operator->() ->decltype( &*base) { return &*base;} }; template <class I> - struct value_iterator_adaptator : public I { - using value_type = typename I::value_type::second_type; - using pointer = typename I::value_type::second_type *; - using reference = typename I::value_type::second_type &; - value_iterator_adaptator(); + struct value_iterator_adaptator { + I base; + using difference_type = typename std::iterator_traits<I>::difference_type; + using value_type = typename std::iterator_traits<I>::value_type::second_type; + using pointer = typename std::iterator_traits<I>::value_type::second_type *; + using reference = typename std::iterator_traits<I>::value_type::second_type &; + using iterator_category = typename std::iterator_traits<I>::iterator_category; + value_iterator_adaptator() = default; value_iterator_adaptator(I const &i); value_type operator*() const; + bool operator==(value_iterator_adaptator const& other) const { return base == other.base;} + bool operator!=(value_iterator_adaptator const& other) const { return base != other.base;} + value_iterator_adaptator& operator++() { ++base; return *this;} + auto operator->() ->decltype( &*base) { return &*base;} }; template <class D> @@ -95,6 +116,55 @@ namespace types long size() const; }; + // specialization of a map whose key are none_type + template <class V> + struct none_type_map { + using key_type = none_type; + using mapped_type = V; + using value_type = std::pair<const key_type, mapped_type>; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using reference = value_type &; + using const_reference = const value_type &; + using pointer = value_type *; + using const_pointer = const value_type *; + using iterator = value_type *; + using const_iterator = const value_type *; + + value_type data_[1]; + bool empty_; + + none_type_map(size_type) : data_(), empty_(true) {} + template <class B, class E> + none_type_map(B begin, E end) : data_{begin == end ? value_type() : *begin}, empty_(begin == end) { + } + + mapped_type& operator[](none_type) { + empty_ = false; + return data_[0].second; + } + + iterator find(none_type) { + return data_ + empty_; + } + + const_iterator find(none_type) const { + return data_ + empty_; + } + + iterator begin() { return data_ + empty_; } + const_iterator begin() const { return data_ + empty_; } + iterator end() { return data_ + 1; } + const_iterator end() const { return data_ + 1; } + + bool empty() const { return empty_;} + void clear() { empty_ = true; data_[0].second = {}; } + const_iterator erase(const_iterator pos) { clear(); return end();} + bool erase(none_type) { bool res = empty_; clear(); return !res;} + + size_t size() const { return empty_?0:1;} + }; + template <class K, class V> class dict { @@ -104,9 +174,11 @@ namespace types typename std::remove_cv<typename std::remove_reference<K>::type>::type; using _value_type = typename std::remove_cv<typename std::remove_reference<V>::type>::type; - using container_type = std::unordered_map< + using container_type = typename std::conditional<std::is_same<K, none_type>::value, + none_type_map<_value_type>, + std::unordered_map< _key_type, _value_type, std::hash<_key_type>, std::equal_to<_key_type>, - utils::allocator<std::pair<const _key_type, _value_type>>>; + utils::allocator<std::pair<const _key_type, _value_type>>>>::type; utils::shared_ref<container_type> data; template <class Kp, class Vp> @@ -135,7 +207,6 @@ namespace types using size_type = typename container_type::size_type; using difference_type = typename container_type::difference_type; using value_type = typename container_type::value_type; - using allocator_type = typename container_type::allocator_type; using pointer = typename container_type::pointer; using const_pointer = typename container_type::const_pointer; @@ -168,7 +239,7 @@ namespace types value_const_iterator value_end() const; // dict interface - operator bool(); + operator bool() const; V &operator[](K const &key) &; template <class OtherKey> diff --git a/contrib/python/pythran/pythran/pythonic/include/types/list.hpp b/contrib/python/pythran/pythran/pythonic/include/types/list.hpp index be008c7bcca..5d5d939aaab 100644 --- a/contrib/python/pythran/pythran/pythonic/include/types/list.hpp +++ b/contrib/python/pythran/pythran/pythonic/include/types/list.hpp @@ -37,10 +37,6 @@ namespace types template <class... Tys> struct pshape; - /* for type disambiguification */ - struct single_value { - }; - /* list view */ template <class T, class S = slice> class sliced_list @@ -219,7 +215,6 @@ namespace types list(InputIterator start, InputIterator stop); list(empty_list const &); list(size_type sz); - list(T const &value, single_value, size_type sz = 1); list(std::initializer_list<T> l); list(list<T> &&other); list(list<T> const &other); diff --git a/contrib/python/pythran/pythran/pythonic/include/types/ndarray.hpp b/contrib/python/pythran/pythran/pythonic/include/types/ndarray.hpp index 49c82549874..fff23b7522d 100644 --- a/contrib/python/pythran/pythran/pythonic/include/types/ndarray.hpp +++ b/contrib/python/pythran/pythran/pythonic/include/types/ndarray.hpp @@ -17,10 +17,12 @@ #include "pythonic/include/types/tuple.hpp" #include "pythonic/include/numpy/bool_.hpp" +#include "pythonic/include/numpy/complex256.hpp" #include "pythonic/include/numpy/complex128.hpp" #include "pythonic/include/numpy/complex64.hpp" #include "pythonic/include/numpy/float32.hpp" #include "pythonic/include/numpy/float64.hpp" +#include "pythonic/include/numpy/float128.hpp" #include "pythonic/include/numpy/int16.hpp" #include "pythonic/include/numpy/int32.hpp" #include "pythonic/include/numpy/int64.hpp" @@ -800,6 +802,10 @@ namespace types using type = pythonic::numpy::functor::float64; }; template <> + struct dtype_helper<long double> { + using type = pythonic::numpy::functor::float128; + }; + template <> struct dtype_helper<std::complex<float>> { using type = pythonic::numpy::functor::complex64; }; diff --git a/contrib/python/pythran/pythran/pythonic/include/types/numpy_gexpr.hpp b/contrib/python/pythran/pythran/pythonic/include/types/numpy_gexpr.hpp index 9057e4da1ff..b56aeba3bc1 100644 --- a/contrib/python/pythran/pythran/pythonic/include/types/numpy_gexpr.hpp +++ b/contrib/python/pythran/pythran/pythonic/include/types/numpy_gexpr.hpp @@ -1026,6 +1026,12 @@ template <class Arg, class... S, class O> struct __combined<O, pythonic::types::numpy_gexpr<Arg, S...>> { using type = pythonic::types::numpy_gexpr<Arg, S...>; }; + +template <class Arg, class... S, class O> +struct __combined<O &, pythonic::types::numpy_gexpr<Arg, S...>> { + using type = pythonic::types::numpy_gexpr<Arg, S...>; +}; + template <class Arg, class... S> struct __combined<pythonic::types::none_type, pythonic::types::numpy_gexpr<Arg, S...>> { diff --git a/contrib/python/pythran/pythran/pythonic/include/types/numpy_iexpr.hpp b/contrib/python/pythran/pythran/pythonic/include/types/numpy_iexpr.hpp index d0a65262313..99f19d12bdf 100644 --- a/contrib/python/pythran/pythran/pythonic/include/types/numpy_iexpr.hpp +++ b/contrib/python/pythran/pythran/pythonic/include/types/numpy_iexpr.hpp @@ -72,23 +72,23 @@ namespace types long size() const; - template <class E> - struct is_almost_same : std::false_type { + template <class E0, class E1> + struct is_almost_same : std::is_same<typename std::decay<E0>::type, typename std::decay<E1>::type> { }; - template <class Argp> - struct is_almost_same<numpy_iexpr<Argp>> - : std::integral_constant< - bool, !std::is_same<Arg, Argp>::value && - std::is_same<typename std::decay<Arg>::type, - typename std::decay<Argp>::type>::value> { + + template <class A0, class A1> + struct is_almost_same<numpy_iexpr<A0>, numpy_iexpr<A1>> : is_almost_same<A0, A1> { + }; + template <class T, class S0, class S1> + struct is_almost_same<ndarray<T, S0>, ndarray<T, S1>> : std::integral_constant<bool, (std::tuple_size<S0>::value == std::tuple_size<S1>::value)> { }; template <class E, class Requires = typename std::enable_if< - !is_almost_same<E>::value, void>::type> + !is_almost_same<numpy_iexpr, E>::value, void>::type> numpy_iexpr &operator=(E const &expr); template <class Argp, class Requires = typename std::enable_if< - is_almost_same<numpy_iexpr<Argp>>::value, void>::type> + is_almost_same<Arg, Argp>::value, void>::type> numpy_iexpr &operator=(numpy_iexpr<Argp> const &expr); numpy_iexpr &operator=(numpy_iexpr const &expr); @@ -461,6 +461,11 @@ template <class E, class K> struct __combined<pythonic::types::numpy_iexpr<E>, container<K>> { using type = pythonic::types::numpy_iexpr<E>; }; + +template <class E, class Arg, class...S> +struct __combined<pythonic::types::numpy_iexpr<E>, pythonic::types::numpy_gexpr<Arg, S...>> { + using type = pythonic::types::numpy_iexpr<E>; +}; template <class E0, class E1> struct __combined<pythonic::types::numpy_iexpr<E0>, pythonic::types::numpy_iexpr<E1>> { diff --git a/contrib/python/pythran/pythran/pythonic/include/types/set.hpp b/contrib/python/pythran/pythran/pythonic/include/types/set.hpp index a15c901f0f1..cb07955def3 100644 --- a/contrib/python/pythran/pythran/pythonic/include/types/set.hpp +++ b/contrib/python/pythran/pythran/pythonic/include/types/set.hpp @@ -29,6 +29,14 @@ namespace types } // namespace types PYTHONIC_NS_END +namespace std +{ + template <size_t I, class T> + struct tuple_element<I, pythonic::types::set<T>> { + typedef typename pythonic::types::set<T>::value_type type; + }; +} // namespace std + /* type inference stuff {*/ #include "pythonic/include/types/combined.hpp" template <class A, class B> @@ -157,7 +165,6 @@ namespace types template <class InputIterator> set(InputIterator start, InputIterator stop); set(empty_set const &); - set(T const &value, single_value); set(std::initializer_list<value_type> l); set(set<T> const &other); template <class F> diff --git a/contrib/python/pythran/pythran/pythonic/include/types/tuple.hpp b/contrib/python/pythran/pythran/pythonic/include/types/tuple.hpp index a5eed3475e1..a8f072cafba 100644 --- a/contrib/python/pythran/pythran/pythonic/include/types/tuple.hpp +++ b/contrib/python/pythran/pythran/pythonic/include/types/tuple.hpp @@ -674,6 +674,13 @@ namespace std } template <size_t I, class T, size_t N, class V> + typename pythonic::types::array_base<T, N, V>::value_type + get(pythonic::types::array_base<T, N, V> &&t) + { + return std::move(t)[I]; + } + + template <size_t I, class T, size_t N, class V> struct tuple_element<I, pythonic::types::array_base<T, N, V>> { using type = typename pythonic::types::array_base<T, N, V>::value_type; }; diff --git a/contrib/python/pythran/pythran/pythonic/include/utils/numpy_traits.hpp b/contrib/python/pythran/pythran/pythonic/include/utils/numpy_traits.hpp index 112ff7a9491..2ab8e72f66c 100644 --- a/contrib/python/pythran/pythran/pythonic/include/utils/numpy_traits.hpp +++ b/contrib/python/pythran/pythran/pythonic/include/utils/numpy_traits.hpp @@ -185,6 +185,25 @@ namespace types static T get(...); using type = decltype(get<E>(nullptr)); }; + + template <class T> + struct has_buffer { + static constexpr bool value = false; + }; + + template <class T, class pS> + struct has_buffer<ndarray<T, pS>> { + static constexpr bool value = true; + }; + + template <class A> + struct has_buffer<numpy_iexpr<A>> : has_buffer<A>{ + }; + + template <class A, class... S> + struct has_buffer<numpy_gexpr<A, S...>> : has_buffer<A> { + }; + } // namespace types PYTHONIC_NS_END diff --git a/contrib/python/pythran/pythran/pythonic/include/utils/seq.hpp b/contrib/python/pythran/pythran/pythonic/include/utils/seq.hpp index abb47fe97be..576141cb7ef 100644 --- a/contrib/python/pythran/pythran/pythonic/include/utils/seq.hpp +++ b/contrib/python/pythran/pythran/pythonic/include/utils/seq.hpp @@ -17,15 +17,28 @@ namespace utils namespace details { + template <class Left, class Right> + struct make_integer_sequence_join; + + template <class T, T... Left, T... Right> + struct make_integer_sequence_join<integer_sequence<T, Left...>, + integer_sequence<T, Right...>> { + using type = integer_sequence<T, Left..., (sizeof...(Left) + Right)...>; + }; template <class T, std::size_t N, T... S> struct make_integer_sequence - : make_integer_sequence<T, N - 1, static_cast<T>(N - 1), S...> { + : make_integer_sequence_join< + typename make_integer_sequence<T, N / 2>::type, + typename make_integer_sequence<T, N - N / 2>::type> { }; - - template <class T, T... S> - struct make_integer_sequence<T, 0, S...> { - using type = integer_sequence<T, S...>; + template <class T> + struct make_integer_sequence<T, 0> { + using type = integer_sequence<T>; + }; + template <class T> + struct make_integer_sequence<T, 1> { + using type = integer_sequence<T, 0>; }; } // namespace details diff --git a/contrib/python/pythran/pythran/pythonic/numpy/dot.hpp b/contrib/python/pythran/pythran/pythonic/numpy/dot.hpp index 2c1f65b3d73..5beadb4489a 100644 --- a/contrib/python/pythran/pythran/pythonic/numpy/dot.hpp +++ b/contrib/python/pythran/pythran/pythonic/numpy/dot.hpp @@ -1354,7 +1354,7 @@ namespace numpy types::is_numexpr_arg<E>::value && types::is_numexpr_arg<F>::value // Arguments are array_like && E::value == 1 && F::value == 1 // It is a two vectors. - && (!is_blas_expr<E>::value || !is_blas_expr<F>::value || + && (!is_blas_view<E>::value || !is_blas_view<F>::value || !std::is_same<typename E::dtype, typename F::dtype>::value), typename __combined<typename E::dtype, typename F::dtype>::type>::type dot(E const &e, F const &f) @@ -1423,7 +1423,7 @@ namespace numpy E::value == 1 && F::value == 1 && std::is_same<typename E::dtype, float>::value && std::is_same<typename F::dtype, float>::value && - (is_blas_expr<E>::value && is_blas_expr<F>::value && + (is_blas_view<E>::value && is_blas_view<F>::value && !(is_blas_array<E>::value && is_blas_array<F>::value)), float>::type dot(E const &e, F const &f) @@ -1442,7 +1442,7 @@ namespace numpy E::value == 1 && F::value == 1 && std::is_same<typename E::dtype, double>::value && std::is_same<typename F::dtype, double>::value && - (is_blas_expr<E>::value && is_blas_expr<F>::value && + (is_blas_view<E>::value && is_blas_view<F>::value && !(is_blas_array<E>::value && is_blas_array<F>::value)), double>::type dot(E const &e, F const &f) @@ -1461,7 +1461,7 @@ namespace numpy E::value == 1 && F::value == 1 && std::is_same<typename E::dtype, std::complex<float>>::value && std::is_same<typename F::dtype, std::complex<float>>::value && - (is_blas_expr<E>::value && is_blas_expr<F>::value && + (is_blas_view<E>::value && is_blas_view<F>::value && !(is_blas_array<E>::value && is_blas_array<F>::value)), std::complex<float>>::type dot(E const &e, F const &f) @@ -1482,7 +1482,7 @@ namespace numpy E::value == 1 && F::value == 1 && std::is_same<typename E::dtype, std::complex<double>>::value && std::is_same<typename F::dtype, std::complex<double>>::value && - (is_blas_expr<E>::value && is_blas_expr<F>::value && + (is_blas_view<E>::value && is_blas_view<F>::value && !(is_blas_array<E>::value && is_blas_array<F>::value)), std::complex<double>>::type dot(E const &e, F const &f) diff --git a/contrib/python/pythran/pythran/pythonic/numpy/float128.hpp b/contrib/python/pythran/pythran/pythonic/numpy/float128.hpp index d8e678d3fcd..e5c2ea5b552 100644 --- a/contrib/python/pythran/pythran/pythonic/numpy/float128.hpp +++ b/contrib/python/pythran/pythran/pythonic/numpy/float128.hpp @@ -15,11 +15,6 @@ namespace numpy namespace details { - inline long double float128() - { - return {}; - } - template <class V> long double float128(V v) { diff --git a/contrib/python/pythran/pythran/pythonic/types/NoneType.hpp b/contrib/python/pythran/pythran/pythonic/types/NoneType.hpp index 201d91ce72b..3952b8092a9 100644 --- a/contrib/python/pythran/pythran/pythonic/types/NoneType.hpp +++ b/contrib/python/pythran/pythran/pythonic/types/NoneType.hpp @@ -135,191 +135,23 @@ namespace types return is_none ? NONE_ID : reinterpret_cast<intptr_t>(&data); } - template <class T> - T operator+(none<T, true> const &t0, T const &t1) - { - return t0.data + t1; - } - - template <class T> - T operator+(T const &t0, none<T, true> const &t1) - { - return t0 + t1.data; - } - - template <class T> - none<T, true> operator+(none<T, true> const &t0, none<T, true> const &t1) - { - if (t0.is_none && t1.is_none) - return none_type{}; - else - return {t0.data + t1.data}; - } - - template <class T> - bool operator>(none<T, true> const &t0, T const &t1) - { - return t0.data > t1; - } - - template <class T> - bool operator>(T const &t0, none<T, true> const &t1) - { - return t0 > t1.data; - } - - template <class T> - none<bool> operator>(none<T, true> const &t0, none<T, true> const &t1) - { - if (t0.is_none && t1.is_none) - return none_type{}; - else - return {t0.data > t1.data}; - } - - template <class T> - bool operator>=(none<T, true> const &t0, T const &t1) - { - return t0.data >= t1; - } - - template <class T> - bool operator>=(T const &t0, none<T, true> const &t1) - { - return t0 >= t1.data; - } - - template <class T> - none<bool> operator>=(none<T, true> const &t0, none<T, true> const &t1) - { - if (t0.is_none && t1.is_none) - return none_type{}; - else - return {t0.data >= t1.data}; - } - - template <class T> - bool operator<(none<T, true> const &t0, T const &t1) - { - return t0.data < t1; - } - - template <class T> - bool operator<(T const &t0, none<T, true> const &t1) - { - return t0 < t1.data; - } - - template <class T> - none<bool> operator<(none<T, true> const &t0, none<T, true> const &t1) - { - if (t0.is_none && t1.is_none) - return none_type{}; - else - return {t0.data < t1.data}; - } - - template <class T> - bool operator<=(none<T, true> const &t0, T const &t1) - { - return t0.data <= t1; - } - - template <class T> - bool operator<=(T const &t0, none<T, true> const &t1) - { - return t0 <= t1.data; - } - - template <class T> - none<bool> operator<=(none<T, true> const &t0, none<T, true> const &t1) - { - if (t0.is_none && t1.is_none) - return none_type{}; - else - return {t0.data <= t1.data}; - } - - template <class T> - T operator-(none<T, true> const &t0, T const &t1) - { - return t0.data - t1; - } - - template <class T> - T operator-(T const &t0, none<T, true> const &t1) - { - return t0 - t1.data; - } - - template <class T> - none<T, true> operator-(none<T, true> const &t0, none<T, true> const &t1) - { - if (t0.is_none && t1.is_none) - return none_type{}; - else - return {t0.data - t1.data}; - } - - template <class T> - T operator*(none<T, true> const &t0, T const &t1) - { - return t0.data * t1; - } - - template <class T> - T operator*(T const &t0, none<T, true> const &t1) - { - return t0 * t1.data; - } - - template <class T> - none<T, true> operator*(none<T, true> const &t0, none<T, true> const &t1) - { - if (t0.is_none && t1.is_none) - return none_type{}; - else - return {t0.data * t1.data}; - } - - template <class T> - T operator/(none<T, true> const &t0, T const &t1) - { - return t0.data / t1; - } - - template <class T> - T operator/(T const &t0, none<T, true> const &t1) - { - return t0 / t1.data; - } - - template <class T> - none<T, true> operator/(none<T, true> const &t0, none<T, true> const &t1) - { - if (t0.is_none && t1.is_none) - return none_type{}; - else - return {t0.data / t1.data}; - } - template <class T0, class T1> decltype(operator_::mod(std::declval<T0>(), std::declval<T1>())) - operator%(none<T0, true> const &t0, T1 const &t1) + operator%(none<T0> const &t0, T1 const &t1) { return operator_::mod(t0.data, t1); } template <class T0, class T1> decltype(operator_::mod(std::declval<T0>(), std::declval<T1>())) - operator%(T0 const &t0, none<T1, true> const &t1) + operator%(T0 const &t0, none<T1> const &t1) { return operator_::mod(t0, t1.data); } template <class T0, class T1> none<decltype(operator_::mod(std::declval<T0>(), std::declval<T1>())), true> - operator%(none<T0, true> const &t0, none<T1, true> const &t1) + operator%(none<T0> const &t0, none<T1> const &t1) { if (t0.is_none && t1.is_none) return none_type{}; @@ -363,14 +195,6 @@ namespace types return *this; } - template <class T> - std::ostream &operator<<(std::ostream &os, none<T, true> const &v) - { - if (v.is_none) - return os << none_type(); - else - return os << v.data; - } } // namespace types PYTHONIC_NS_END diff --git a/contrib/python/pythran/pythran/pythonic/types/array.hpp b/contrib/python/pythran/pythran/pythonic/types/array.hpp index e7b3966793d..b10d9405e01 100644 --- a/contrib/python/pythran/pythran/pythonic/types/array.hpp +++ b/contrib/python/pythran/pythran/pythonic/types/array.hpp @@ -593,7 +593,9 @@ namespace types array<T> array<T>::operator*(long n) const { if (size() == 1) { - return array<T>(fast(0), single_value{}, n); + array<T> r(n); + std::fill(begin(), end(), fast(0)); + return r; } else { array<T> r(size() * n); auto start = r.begin(); diff --git a/contrib/python/pythran/pythran/pythonic/types/dict.hpp b/contrib/python/pythran/pythran/pythonic/types/dict.hpp index c6065e87ac6..5bc7cbc89fb 100644 --- a/contrib/python/pythran/pythran/pythonic/types/dict.hpp +++ b/contrib/python/pythran/pythran/pythonic/types/dict.hpp @@ -21,28 +21,21 @@ PYTHONIC_NS_BEGIN namespace types { /// item implementation - template <class I> - item_iterator_adaptator<I>::item_iterator_adaptator(I const &i) : I(i) + item_iterator_adaptator<I>::item_iterator_adaptator(I const &i) : base(i) { } - template <class I> typename item_iterator_adaptator<I>::value_type item_iterator_adaptator<I>::operator*() const { - auto &&tmp = I::operator*(); + auto &&tmp = *base;; return pythonic::types::make_tuple(tmp.first, tmp.second); } /// key_iterator_adaptator implementation template <class I> - key_iterator_adaptator<I>::key_iterator_adaptator() : I() - { - } - - template <class I> - key_iterator_adaptator<I>::key_iterator_adaptator(I const &i) : I(i) + key_iterator_adaptator<I>::key_iterator_adaptator(I const &i) : base(i) { } @@ -50,17 +43,12 @@ namespace types typename key_iterator_adaptator<I>::value_type key_iterator_adaptator<I>::operator*() const { - return (*this)->first; + return base->first; } /// value_iterator_adaptator implementation template <class I> - value_iterator_adaptator<I>::value_iterator_adaptator() : I() - { - } - - template <class I> - value_iterator_adaptator<I>::value_iterator_adaptator(I const &i) : I(i) + value_iterator_adaptator<I>::value_iterator_adaptator(I const &i) : base(i) { } @@ -68,7 +56,7 @@ namespace types typename value_iterator_adaptator<I>::value_type value_iterator_adaptator<I>::operator*() const { - return (*this)->second; + return base->second; } template <class D> @@ -302,7 +290,7 @@ namespace types // dict interface template <class K, class V> - dict<K, V>::operator bool() + dict<K, V>::operator bool() const { return !data->empty(); } diff --git a/contrib/python/pythran/pythran/pythonic/types/list.hpp b/contrib/python/pythran/pythran/pythonic/types/list.hpp index 9c346b07059..076b5e701de 100644 --- a/contrib/python/pythran/pythran/pythonic/types/list.hpp +++ b/contrib/python/pythran/pythran/pythonic/types/list.hpp @@ -273,10 +273,6 @@ namespace types { } template <class T> - list<T>::list(T const &value, single_value, size_type sz) : _data(sz, value) - { - } - template <class T> list<T>::list(std::initializer_list<T> l) : _data(std::move(l)) { } @@ -642,7 +638,9 @@ namespace types list<T> list<T>::operator*(long n) const { if (size() == 1) { - return list<T>(fast(0), single_value{}, n); + list<T> r(size() * n); + std::fill(r.begin(), r.end(), fast(0)); + return r; } else { list<T> r(size() * n); auto start = r.begin(); diff --git a/contrib/python/pythran/pythran/pythonic/types/numpy_iexpr.hpp b/contrib/python/pythran/pythran/pythonic/types/numpy_iexpr.hpp index d4faef64896..5f44a4ae1bc 100644 --- a/contrib/python/pythran/pythran/pythonic/types/numpy_iexpr.hpp +++ b/contrib/python/pythran/pythran/pythonic/types/numpy_iexpr.hpp @@ -83,7 +83,6 @@ namespace types return *new (this) numpy_iexpr<Arg>(expr); } - assert(buffer); return utils::broadcast_copy < numpy_iexpr &, numpy_iexpr const &, value, value - utils::dim_of<numpy_iexpr>::value, is_vectorizable && numpy_iexpr<Arg>::is_vectorizable && diff --git a/contrib/python/pythran/pythran/pythonic/types/set.hpp b/contrib/python/pythran/pythran/pythonic/types/set.hpp index 7bf74bf0fc8..7c31ada0ec0 100644 --- a/contrib/python/pythran/pythran/pythonic/types/set.hpp +++ b/contrib/python/pythran/pythran/pythonic/types/set.hpp @@ -44,12 +44,6 @@ namespace types } template <class T> - set<T>::set(T const &value, single_value) : data() - { - data->insert(value); - } - - template <class T> set<T>::set(std::initializer_list<value_type> l) : data(std::move(l)) { } diff --git a/contrib/python/pythran/pythran/pythran.cfg b/contrib/python/pythran/pythran/pythran.cfg index 677677657a6..3665a6e8d56 100644 --- a/contrib/python/pythran/pythran/pythran.cfg +++ b/contrib/python/pythran/pythran/pythran.cfg @@ -31,10 +31,15 @@ ignore_fold_error = False [typing] -# maximum number of combiner per user function +# maximum number of inter-procedural combiner per user function # increasing this value inreases typing accuracy # but slows down compilation time, to the point of making g++ crash -max_combiner = 2 +max_interprocedural_combiner = 2 + +# maximum number of intra-procedural combiner per user function +# increasing this value inreases typing accuracy +# but slows down compilation time, to the point of making g++ crash +max_combiner = 8 # above this number of overloads, pythran specifications are considered invalid # as it generates ultra-large binaries @@ -54,3 +59,7 @@ annotate = false # set to 'lineno' if you want to generate line number instead of python extract annotation_kind = 'comment' + +# make generated module compatible with freethreading, see +# https://py-free-threading.github.io/ +freethreading_compatible = true diff --git a/contrib/python/pythran/pythran/spec.py b/contrib/python/pythran/pythran/spec.py index 694094083cb..838f99ccd0e 100644 --- a/contrib/python/pythran/pythran/spec.py +++ b/contrib/python/pythran/pythran/spec.py @@ -454,12 +454,23 @@ class SpecParser(object): def p_error(self, p): if p.type == 'IDENTIFIER': + alt = {'double': 'float64', + 'void': 'None', + 'char': 'int8', + 'short': 'int16', + 'long': 'int64', + }.get(p.value) + if alt: + hint = " Did you mean `{}`?".format(alt) + else: + hint = '' raise self.PythranSpecError( - "Unexpected identifier `{}` at that point".format(p.value), + "Unsupported identifier `{}` at that point.{}".format(p.value, + hint), p.lexpos) else: raise self.PythranSpecError( - "Unexpected token `{}` at that point".format(p.value), + "Unexpected token `{}` at that point.".format(p.value), p.lexpos) def __init__(self): diff --git a/contrib/python/pythran/pythran/syntax.py b/contrib/python/pythran/pythran/syntax.py index 2137d6ba33e..13f4d7fa634 100644 --- a/contrib/python/pythran/pythran/syntax.py +++ b/contrib/python/pythran/pythran/syntax.py @@ -47,6 +47,7 @@ class SyntaxChecker(ast.NodeVisitor): def __init__(self): """ Gather attributes from MODULES content. """ self.attributes = set() + self.functions = [] def save_attribute(module): """ Recursively save Pythonic keywords as possible attributes. """ @@ -131,7 +132,9 @@ class SyntaxChecker(ast.NodeVisitor): if node.args.kwarg: raise PythranSyntaxError("Keyword arguments not supported", node) + self.functions.append(node) self.generic_visit(node) + self.functions.pop() def visit_Raise(self, node): self.generic_visit(node) @@ -207,6 +210,12 @@ class SyntaxChecker(ast.NodeVisitor): def visit_Global(self, node): raise PythranSyntaxError("'global' statements are not supported", node) + def visit_Nonlocal(self, node): + if len(self.functions) < 2: + raise PythranSyntaxError( + "nonlocal keyword is only valid on nested functions", + node) + def check_syntax(node): '''Does nothing but raising PythranSyntaxError when needed''' diff --git a/contrib/python/pythran/pythran/tables.py b/contrib/python/pythran/pythran/tables.py index d62abe11c05..a61709d198b 100644 --- a/contrib/python/pythran/pythran/tables.py +++ b/contrib/python/pythran/pythran/tables.py @@ -4589,6 +4589,17 @@ try: except ImportError: pass +def save_path(module_name, elements): + """ Recursively sets path. """ + for elem, signature in elements.items(): + if isinstance(signature, dict): # Submodule case + save_path(module_name + (elem,), signature) + else: + signature.path = module_name + (elem,) + +save_path((), CLASSES) +save_path((), MODULES) + def looks_like_a_forward_function(spec): return not spec.args and spec.varargs == 'args' and spec.varkw == 'kwargs' diff --git a/contrib/python/pythran/pythran/toolchain.py b/contrib/python/pythran/pythran/toolchain.py index ba6a9aafe91..42dcc072ad2 100644 --- a/contrib/python/pythran/pythran/toolchain.py +++ b/contrib/python/pythran/pythran/toolchain.py @@ -52,8 +52,8 @@ def _extract_specs_dependencies(specs): # for each argument for t in signature: deps.update(pytype_to_deps(t)) - # Keep "include" first - return sorted(deps, key=lambda x: "include" not in x) + + return sorted(deps) def _parse_optimization(optimization): diff --git a/contrib/python/pythran/pythran/transformations/expand_builtins.py b/contrib/python/pythran/pythran/transformations/expand_builtins.py index 8ce5c63c154..4d815c2b8d3 100644 --- a/contrib/python/pythran/pythran/transformations/expand_builtins.py +++ b/contrib/python/pythran/pythran/transformations/expand_builtins.py @@ -9,7 +9,7 @@ import builtins import gast as ast -class ExpandBuiltins(Transformation): +class ExpandBuiltins(Transformation[Locals, Globals]): """ Expands all builtins into full paths. @@ -24,9 +24,6 @@ class ExpandBuiltins(Transformation): return builtins.list() """ - def __init__(self): - Transformation.__init__(self, Locals, Globals) - def visit_Name(self, node): s = node.id if s in ('None', 'True', 'False'): diff --git a/contrib/python/pythran/pythran/transformations/expand_globals.py b/contrib/python/pythran/pythran/transformations/expand_globals.py index 605b786039f..0cb6ef39887 100644 --- a/contrib/python/pythran/pythran/transformations/expand_globals.py +++ b/contrib/python/pythran/pythran/transformations/expand_globals.py @@ -51,9 +51,9 @@ class ExpandGlobals(Transformation): def __init__(self): """ Initialize local declaration and constant name to expand. """ + super().__init__() self.local_decl = set() self.to_expand = set() - super(ExpandGlobals, self).__init__() def visit_Module(self, node): """Turn globals assignment to functionDef and visit function defs. """ diff --git a/contrib/python/pythran/pythran/transformations/expand_imports.py b/contrib/python/pythran/pythran/transformations/expand_imports.py index 718feadbff8..43da1c892d1 100644 --- a/contrib/python/pythran/pythran/transformations/expand_imports.py +++ b/contrib/python/pythran/pythran/transformations/expand_imports.py @@ -9,7 +9,7 @@ from pythran.analyses import Ancestors import gast as ast -class ExpandImports(Transformation): +class ExpandImports(Transformation[Ancestors]): """ Expands all imports into full paths. @@ -39,7 +39,7 @@ class ExpandImports(Transformation): """ def __init__(self): - super(ExpandImports, self).__init__(Ancestors) + super().__init__() self.imports = set() self.symbols = dict() diff --git a/contrib/python/pythran/pythran/transformations/false_polymorphism.py b/contrib/python/pythran/pythran/transformations/false_polymorphism.py index 165517b98aa..c1c8a35685e 100644 --- a/contrib/python/pythran/pythran/transformations/false_polymorphism.py +++ b/contrib/python/pythran/pythran/transformations/false_polymorphism.py @@ -4,9 +4,10 @@ from pythran.passmanager import Transformation from pythran.analyses import DefUseChains, UseDefChains, Identifiers import gast as ast +import re -class FalsePolymorphism(Transformation): +class FalsePolymorphism(Transformation[DefUseChains, UseDefChains]): """ Rename variable when possible to avoid false polymorphism. @@ -22,16 +23,18 @@ class FalsePolymorphism(Transformation): a_ = 'babar' """ - def __init__(self): - super(FalsePolymorphism, self).__init__(DefUseChains, UseDefChains) - def visit_FunctionDef(self, node): # reset available identifier names # removing local identifiers from the list so that first occurrence can # actually use the slot identifiers = self.gather(Identifiers, node) + captured_identifiers = set() + captured_identifiers_pattern = re.compile('^__pythran_boxed_(?:args_)?(.*)$') for def_ in self.def_use_chains.locals[node]: + match = captured_identifiers_pattern.match(def_.name()) + if match: + captured_identifiers.add(match.group(1)) try: identifiers.remove(def_.name()) except KeyError: @@ -41,6 +44,8 @@ class FalsePolymorphism(Transformation): # that should have the same name visited_defs = set() for def_ in self.def_use_chains.locals[node]: + if def_.name() in captured_identifiers: + continue if def_ in visited_defs: continue diff --git a/contrib/python/pythran/pythran/transformations/normalize_ifelse.py b/contrib/python/pythran/pythran/transformations/normalize_ifelse.py index be75ff495a9..d30bc8eb8fb 100644 --- a/contrib/python/pythran/pythran/transformations/normalize_ifelse.py +++ b/contrib/python/pythran/pythran/transformations/normalize_ifelse.py @@ -6,7 +6,7 @@ from pythran.passmanager import Transformation import gast as ast -class NormalizeIfElse(Transformation): +class NormalizeIfElse(Transformation[Ancestors]): ''' >>> import gast as ast @@ -47,9 +47,6 @@ class NormalizeIfElse(Transformation): return 2 ''' - def __init__(self): - super(NormalizeIfElse, self).__init__(Ancestors) - def check_lasts(self, node): if isinstance(node, (ast.Return, ast.Break, ast.Return)): return True diff --git a/contrib/python/pythran/pythran/transformations/normalize_is_none.py b/contrib/python/pythran/pythran/transformations/normalize_is_none.py index 17a31e0e91d..57cecae3210 100644 --- a/contrib/python/pythran/pythran/transformations/normalize_is_none.py +++ b/contrib/python/pythran/pythran/transformations/normalize_is_none.py @@ -9,15 +9,7 @@ import gast as ast def is_none(expr): - # py3 - if isinstance(expr, ast.Constant) and expr.value is None: - return True - - # py2 - if not isinstance(expr, ast.Attribute): - return False - - return expr.attr == "None" + return isinstance(expr, ast.Constant) and expr.value is None def is_is_none(expr): @@ -64,13 +56,10 @@ def is_is_not_none(expr): return None -class NormalizeIsNone(Transformation): +class NormalizeIsNone(Transformation[Ancestors]): table = {ast.And: ast.BitAnd, ast.Or: ast.BitOr} - def __init__(self): - super(NormalizeIsNone, self).__init__(Ancestors) - @staticmethod def match_is_none(node): noned_var = is_is_none(node) diff --git a/contrib/python/pythran/pythran/transformations/normalize_method_calls.py b/contrib/python/pythran/pythran/transformations/normalize_method_calls.py index 9aea96e39f6..8cc06b00a2c 100644 --- a/contrib/python/pythran/pythran/transformations/normalize_method_calls.py +++ b/contrib/python/pythran/pythran/transformations/normalize_method_calls.py @@ -12,7 +12,7 @@ import gast as ast from functools import reduce -class NormalizeMethodCalls(Transformation): +class NormalizeMethodCalls(Transformation[Globals, Ancestors]): ''' Turns built in method calls into function calls. @@ -27,7 +27,7 @@ class NormalizeMethodCalls(Transformation): ''' def __init__(self): - Transformation.__init__(self, Globals, Ancestors) + super().__init__() self.imports = {'builtins': 'builtins', mangle('__dispatch__'): '__dispatch__'} self.to_import = set() diff --git a/contrib/python/pythran/pythran/transformations/normalize_return.py b/contrib/python/pythran/pythran/transformations/normalize_return.py index 5c948ec232b..cb4096780c1 100644 --- a/contrib/python/pythran/pythran/transformations/normalize_return.py +++ b/contrib/python/pythran/pythran/transformations/normalize_return.py @@ -6,7 +6,7 @@ from pythran.passmanager import Transformation import gast as ast -class NormalizeReturn(Transformation): +class NormalizeReturn(Transformation[CFG]): ''' Adds Return statement when they are implicit, and adds the None return value when not set @@ -19,12 +19,9 @@ class NormalizeReturn(Transformation): >>> print(pm.dump(backend.Python, node)) def foo(y): print(y) - return builtins.None + return None ''' - def __init__(self): - super(NormalizeReturn, self).__init__(CFG) - def visit_FunctionDef(self, node): self.yield_points = self.gather(YieldPoints, node) for stmt in node.body: @@ -38,19 +35,13 @@ class NormalizeReturn(Transformation): if self.yield_points: node.body.append(ast.Return(None)) else: - none = ast.Attribute( - ast.Name("builtins", ast.Load(), None, None), - 'None', - ast.Load()) - node.body.append(ast.Return(none)) + node.body.append(ast.Return(ast.Constant(None, None))) break return node def visit_Return(self, node): if not node.value and not self.yield_points: - none = ast.Attribute(ast.Name("builtins", ast.Load(), None, None), - 'None', ast.Load()) - node.value = none + node.value = ast.Constant(None, None) self.update = True return node diff --git a/contrib/python/pythran/pythran/transformations/normalize_static_if.py b/contrib/python/pythran/pythran/transformations/normalize_static_if.py index 0b29d157a10..f21b7bad97c 100644 --- a/contrib/python/pythran/pythran/transformations/normalize_static_if.py +++ b/contrib/python/pythran/pythran/transformations/normalize_static_if.py @@ -47,7 +47,7 @@ def outline(name, formal_parameters, out_parameters, stmts, ) if has_return: pr = PatchReturn(stmts[-1], has_break or has_cont) - pr.visit(fdef) + pr.generic_visit(fdef) if has_break or has_cont: if not has_return: @@ -55,7 +55,7 @@ def outline(name, formal_parameters, out_parameters, stmts, stmts[-1].value], ast.Load()) pbc = PatchBreakContinue(stmts[-1]) - pbc.visit(fdef) + pbc.generic_visit(fdef) return fdef @@ -66,6 +66,9 @@ class PatchReturn(ast.NodeTransformer): self.guard = guard self.has_break_or_cont = has_break_or_cont + def visit_FunctionDef(self, node): + return node + def visit_Return(self, node): if node is self.guard: holder = "StaticIfNoReturn" @@ -92,11 +95,14 @@ class PatchBreakContinue(ast.NodeTransformer): def __init__(self, guard): self.guard = guard - def visit_For(self, _): - pass + def visit_FunctionDef(self, node): + return node + + def visit_For(self, node): + return node - def visit_While(self, _): - pass + def visit_While(self, node): + return node def patch_Control(self, node, flag): new_node = deepcopy(self.guard) @@ -117,11 +123,7 @@ class PatchBreakContinue(ast.NodeTransformer): return self.patch_Control(node, LOOP_CONT) -class NormalizeStaticIf(Transformation): - - def __init__(self): - super(NormalizeStaticIf, self).__init__(StaticExpressions, Ancestors, - DefUseChains) +class NormalizeStaticIf(Transformation[StaticExpressions, Ancestors, DefUseChains]): def visit_Module(self, node): self.new_functions = [] @@ -329,10 +331,7 @@ class NormalizeStaticIf(Transformation): return ast.Expr(actual_call) -class SplitStaticExpression(Transformation): - - def __init__(self): - super(SplitStaticExpression, self).__init__(StaticExpressions) +class SplitStaticExpression(Transformation[StaticExpressions]): def visit_Cond(self, node): ''' diff --git a/contrib/python/pythran/pythran/transformations/normalize_tuples.py b/contrib/python/pythran/pythran/transformations/normalize_tuples.py index 0cd5ba5c809..3c71b3a6f92 100644 --- a/contrib/python/pythran/pythran/transformations/normalize_tuples.py +++ b/contrib/python/pythran/pythran/transformations/normalize_tuples.py @@ -46,9 +46,6 @@ class NormalizeTuples(Transformation): """ tuple_name = "__tuple" - def __init__(self): - Transformation.__init__(self) - def get_new_id(self): i = 0 while 1: diff --git a/contrib/python/pythran/pythran/transformations/remove_comprehension.py b/contrib/python/pythran/pythran/transformations/remove_comprehension.py index be5b979d76e..0f19643e898 100644 --- a/contrib/python/pythran/pythran/transformations/remove_comprehension.py +++ b/contrib/python/pythran/pythran/transformations/remove_comprehension.py @@ -29,8 +29,8 @@ class RemoveComprehension(Transformation): """ def __init__(self): + super().__init__() self.count = 0 - Transformation.__init__(self) def visit_Module(self, node): self.has_dist_comp = False diff --git a/contrib/python/pythran/pythran/transformations/remove_lambdas.py b/contrib/python/pythran/pythran/transformations/remove_lambdas.py index db832c39897..4b00c83d89b 100644 --- a/contrib/python/pythran/pythran/transformations/remove_lambdas.py +++ b/contrib/python/pythran/pythran/transformations/remove_lambdas.py @@ -141,7 +141,7 @@ class _LambdaRemover(ast.NodeTransformer): return proxy_call -class RemoveLambdas(Transformation): +class RemoveLambdas(Transformation[GlobalDeclarations]): """ Turns lambda into top-level functions. @@ -159,9 +159,6 @@ class RemoveLambdas(Transformation): return (y + x) """ - def __init__(self): - super(RemoveLambdas, self).__init__(GlobalDeclarations) - def visit_Module(self, node): self.lambda_functions = list() self.patterns = {} diff --git a/contrib/python/pythran/pythran/transformations/remove_named_arguments.py b/contrib/python/pythran/pythran/transformations/remove_named_arguments.py index b63ff70b6ce..b70c278e141 100644 --- a/contrib/python/pythran/pythran/transformations/remove_named_arguments.py +++ b/contrib/python/pythran/pythran/transformations/remove_named_arguments.py @@ -15,7 +15,7 @@ def handle_special_calls(func_alias, node): node.args.insert(0, ast.Constant(0, None)) -class RemoveNamedArguments(Transformation): +class RemoveNamedArguments(Transformation[Aliases]): ''' Replace call with named arguments to regular calls @@ -32,9 +32,6 @@ class RemoveNamedArguments(Transformation): return foo(0, z) ''' - def __init__(self): - super(RemoveNamedArguments, self).__init__(Aliases) - def handle_keywords(self, func, node, offset=0): ''' Gather keywords to positional argument information diff --git a/contrib/python/pythran/pythran/transformations/remove_nested_functions.py b/contrib/python/pythran/pythran/transformations/remove_nested_functions.py index cbb82989978..1958179f85b 100644 --- a/contrib/python/pythran/pythran/transformations/remove_nested_functions.py +++ b/contrib/python/pythran/pythran/transformations/remove_nested_functions.py @@ -1,6 +1,6 @@ """ RemoveNestedFunctions turns nested function into top-level functions. """ -from pythran.analyses import GlobalDeclarations, ImportedIds +from pythran.analyses import GlobalDeclarations, NonlocalDeclarations, ImportedIds from pythran.passmanager import Transformation from pythran.tables import MODULES from pythran.conversion import mangle @@ -15,10 +15,15 @@ class _NestedFunctionRemover(ast.NodeTransformer): ast.NodeTransformer.__init__(self) self.parent = parent self.identifiers = set(self.global_declarations.keys()) + self.boxes = {} + self.nonlocal_boxes = {} def __getattr__(self, attr): return getattr(self.parent, attr) + def visit_Nonlocal(self, node): + return ast.Pass() + def visit_FunctionDef(self, node): self.update = True if MODULES['functools'] not in self.global_declarations.values(): @@ -40,12 +45,35 @@ class _NestedFunctionRemover(ast.NodeTransformer): self.identifiers.add(new_name) ii = self.gather(ImportedIds, node) - binded_args = [ast.Name(iin, ast.Load(), None, None) - for iin in sorted(ii)] + sii = sorted(ii) + binded_args = [ast.Name('__pythran_boxed_' + iin, ast.Load(), None, + None) + for iin in sii] node.args.args = ([ast.Name(iin, ast.Param(), None, None) - for iin in sorted(ii)] + + for iin in sii] + node.args.args) + unboxing = [] + nonlocal_boxes = {} + for iin in sii: + if iin in self.nonlocal_declarations[node]: + nonlocal_boxes.setdefault(iin, None) + self.nonlocal_boxes.setdefault(iin, None) + else: + unboxing.append(ast.Assign([ast.Name(iin, ast.Store(), None, None)], + ast.Subscript(ast.Name('__pythran_boxed_args_' + iin, ast.Load(), None, + None), + ast.Constant(None, None), + ast.Load()))) + self.boxes.setdefault(iin, None) + BoxArgsInserter(nonlocal_boxes).visit(node) + + for arg in node.args.args: + if arg.id in ii: + arg.id = '__pythran_boxed_args_' + arg.id + + node.body = unboxing + node.body + metadata.add(node, metadata.Local()) class Renamer(ast.NodeTransformer): @@ -55,11 +83,12 @@ class _NestedFunctionRemover(ast.NodeTransformer): node.func.id == former_name): node.func.id = new_name node.args = ( - [ast.Name(iin, ast.Load(), None, None) - for iin in sorted(ii)] + + [ast.Name('__pythran_boxed_args_' + iin, ast.Load(), None, None) + for iin in sii] + node.args ) return node + Renamer().visit(node) node.name = new_name @@ -79,11 +108,101 @@ class _NestedFunctionRemover(ast.NodeTransformer): ), None) - self.generic_visit(node) - return new_node + nfr = _NestedFunctionRemover(self) + nfr.remove_nested(node) + return new_node -class RemoveNestedFunctions(Transformation): + def remove_nested(self, node): + node.body = [self.visit(stmt) for stmt in node.body] + if self.update: + boxes = [] + arg_ids = {arg.id for arg in node.args.args} + all_boxes = list(self.boxes) + all_boxes.extend(b for b in self.nonlocal_boxes if b not in + self.boxes) + for i in all_boxes: + if i in arg_ids: + box_value = ast.Dict([ast.Constant(None, None)], [ast.Name(i, + ast.Load(), + None, + None)]) + else: + box_value = ast.Dict([], []) + box = ast.Assign([ast.Name('__pythran_boxed_' + i, ast.Store(), + None, None)], box_value) + boxes.append(box) + + pre_boxes = self.boxes.copy() + for k in self.nonlocal_boxes: + pre_boxes.pop(k, None) + + BoxPreInserter(pre_boxes).visit(node) + BoxInserter(self.nonlocal_boxes).visit(node) + node.body = boxes + node.body + return self.update + +class BoxPreInserter(ast.NodeTransformer): + + def __init__(self, insertion_points): + self.insertion_points = insertion_points + + def insert_target(self, target): + if getattr(target, 'id', None) not in self.insertion_points: + return None + return ast.Assign( + [ast.Subscript( + ast.Name('__pythran_boxed_' + target.id, ast.Load(), None, None), + ast.Constant(None, None), + ast.Store())], + ast.Name(target.id, ast.Load(), None, None)) + + + def visit_Assign(self, node): + extras = [] + for t in node.targets: + extra = self.insert_target(t) + if extra: + extras.append(extra) + if extras: + return [node] + extras + else: + return node + + def visit_AugAssign(self, node): + extra = self.insert_target(node.target) + if extra: + return [node, extra] + else: + return node + + +class BoxInserter(ast.NodeTransformer): + + def __init__(self, insertion_points): + self.insertion_points = insertion_points + self.prefix = '__pythran_boxed_' + + def visit_Name(self, node): + if node.id not in self.insertion_points: + return node + if isinstance(node.ctx, ast.Param): + return node + return ast.Subscript( + ast.Name(self.prefix + node.id, + ast.Load(), None, None), + ast.Constant(None, None), + node.ctx) + +class BoxArgsInserter(BoxInserter): + + def __init__(self, insertion_points): + super().__init__(insertion_points) + self.prefix += 'args_' + + +class RemoveNestedFunctions(Transformation[GlobalDeclarations, + NonlocalDeclarations]): """ Replace nested function by top-level functions. @@ -99,13 +218,13 @@ class RemoveNestedFunctions(Transformation): >>> print(pm.dump(backend.Python, node)) import functools as __pythran_import_functools def foo(x): - bar = __pythran_import_functools.partial(pythran_bar0, x) + __pythran_boxed_x = {None: x} + bar = __pythran_import_functools.partial(pythran_bar0, __pythran_boxed_x) bar(12) - def pythran_bar0(x, y): + def pythran_bar0(__pythran_boxed_args_x, y): + x = __pythran_boxed_args_x[None] return (x + y) """ - def __init__(self): - super(RemoveNestedFunctions, self).__init__(GlobalDeclarations) def visit_Module(self, node): # keep original node as it's updated by _NestedFunctionRemover @@ -115,6 +234,5 @@ class RemoveNestedFunctions(Transformation): def visit_FunctionDef(self, node): nfr = _NestedFunctionRemover(self) - node.body = [nfr.visit(stmt) for stmt in node.body] - self.update |= nfr.update + self.update |= nfr.remove_nested(node) return node diff --git a/contrib/python/pythran/pythran/transformations/unshadow_parameters.py b/contrib/python/pythran/pythran/transformations/unshadow_parameters.py index e61e90c695b..ee582c603be 100644 --- a/contrib/python/pythran/pythran/transformations/unshadow_parameters.py +++ b/contrib/python/pythran/pythran/transformations/unshadow_parameters.py @@ -8,7 +8,7 @@ from pythran.passmanager import Transformation import gast as ast -class UnshadowParameters(Transformation): +class UnshadowParameters(Transformation[Identifiers]): ''' Prevents parameter shadowing by creating new variable. @@ -23,9 +23,6 @@ class UnshadowParameters(Transformation): a_ = 1 ''' - def __init__(self): - Transformation.__init__(self, Identifiers) - def visit_FunctionDef(self, node): self.argsid = {arg.id for arg in node.args.args} self.renaming = {} diff --git a/contrib/python/pythran/pythran/types/reorder.py b/contrib/python/pythran/pythran/types/reorder.py index c027ec0cc9f..b3835804150 100644 --- a/contrib/python/pythran/pythran/types/reorder.py +++ b/contrib/python/pythran/pythran/types/reorder.py @@ -44,18 +44,13 @@ def topological_sort(G, nbunch): return list(reversed(order)) -class Reorder(Transformation): +class Reorder(Transformation[TypeDependencies, OrderedGlobalDeclarations]): """ Reorder top-level functions to prevent circular type dependencies. """ - def __init__(self): - """ Trigger others analysis informations. """ - super(Reorder, self).__init__(TypeDependencies, - OrderedGlobalDeclarations) - def prepare(self, node): """ Format type dependencies information to use if for reordering. """ - super(Reorder, self).prepare(node) + super().prepare(node) candidates = self.type_dependencies.successors( TypeDependencies.NoDeps) # We first select function which may have a result without calling any diff --git a/contrib/python/pythran/pythran/types/tog.py b/contrib/python/pythran/pythran/types/tog.py index f3a2b0f461c..e115bcf8526 100644 --- a/contrib/python/pythran/pythran/types/tog.py +++ b/contrib/python/pythran/pythran/types/tog.py @@ -5,6 +5,7 @@ import gast as ast from copy import deepcopy +import numpy from numpy import floating, integer, complexfloating from pythran.tables import MODULES, attributes @@ -410,7 +411,7 @@ def tr(t): elif isinstance(t, NoneType_): return NoneType - elif t is bool: + elif t in (bool, getattr(numpy, 'bool', bool)): return Bool() elif issubclass(t, slice): @@ -953,6 +954,18 @@ def analyse(node, env, non_generic=None): defn_type), node) return env + elif isinstance(node, ast.AnnAssign): + defn_type = analyse(node.value, env, non_generic) + target_type = analyse(node.target, env, non_generic) + try: + unify(target_type, defn_type) + except InferenceError: + raise PythranTypeError( + "Invalid assignment from type `{}` to type `{}`".format( + target_type, + defn_type), + node) + return env elif isinstance(node, ast.AugAssign): # FIMXE: not optimal: evaluates type of node.value twice fake_target = deepcopy(node.target) diff --git a/contrib/python/pythran/pythran/types/type_dependencies.py b/contrib/python/pythran/pythran/types/type_dependencies.py index a7fe47a9c8d..15674ceecec 100644 --- a/contrib/python/pythran/pythran/types/type_dependencies.py +++ b/contrib/python/pythran/pythran/types/type_dependencies.py @@ -51,7 +51,7 @@ def pytype_to_deps(t): return res -class TypeDependencies(ModuleAnalysis): +class TypeDependencies(ModuleAnalysis[GlobalDeclarations]): """ Gathers the callees of each function required for type inference. @@ -224,14 +224,15 @@ class TypeDependencies(ModuleAnalysis): NoDeps = "None" + ResultType = DiGraph + def __init__(self): """ Create empty result graph and gather global declarations. """ - self.result = DiGraph() + super().__init__() self.current_function = None self.naming = dict() # variable to dependencies for current function. # variable to dependencies for current conditional statement self.in_cond = dict() - ModuleAnalysis.__init__(self, GlobalDeclarations) def prepare(self, node): """ @@ -334,15 +335,20 @@ class TypeDependencies(ModuleAnalysis): It is valid for subscript, `a[i] = foo()` means `a` type depend on `foo` return type. """ - if not node.value: - return value_deps = self.visit(node.value) - targets = node.targets if isinstance(node, ast.Assign) else (node.target,) - for target in targets: + for target in node.targets: name = get_variable(target) if isinstance(name, ast.Name): self.naming[name.id] = value_deps - visit_AnnAssign = visit_Assign + + def visit_AnnAssign(self, node): + deps = [] + if node.value: + deps.extend(self.visit(node.value)) + deps.extend(self.visit(node.annotation)) + name = get_variable(node.target) + if isinstance(name, ast.Name): + self.naming[name.id] = deps def visit_AugAssign(self, node): """ diff --git a/contrib/python/pythran/pythran/types/types.py b/contrib/python/pythran/pythran/types/types.py index e2774616e09..40cfcdf0308 100644 --- a/contrib/python/pythran/pythran/types/types.py +++ b/contrib/python/pythran/pythran/types/types.py @@ -7,12 +7,15 @@ This module performs the return type inference, according to symbolic types, from pythran.analyses import LazynessAnalysis, StrictAliases, YieldPoints from pythran.analyses import LocalNodeDeclarations, Immediates, RangeValues from pythran.analyses import Ancestors +from pythran.analyses.aliases import ContainerOf from pythran.config import cfg from pythran.cxxtypes import TypeBuilder, ordered_set from pythran.intrinsic import UserFunction, Class from pythran.passmanager import ModuleAnalysis +from pythran.errors import PythranSyntaxError from pythran.tables import operator_to_lambda, MODULES -from pythran.types.conversion import pytype_to_ctype +from pythran.typing import List, Dict, Set, Tuple, NDArray, Union +from pythran.types.conversion import pytype_to_ctype, PYTYPE_TO_CTYPE_TABLE from pythran.types.reorder import Reorder from pythran.utils import attr_to_path, cxxid, isnum, isextslice @@ -21,40 +24,201 @@ from functools import reduce import gast as ast from itertools import islice from copy import deepcopy +import numpy as np + +alias_to_type = { + MODULES['builtins']['int'] : int, + MODULES['builtins']['bool'] : bool, + MODULES['builtins']['float'] : float, + MODULES['builtins']['str'] : str, + MODULES['builtins']['complex'] : complex, + MODULES['builtins']['dict'] : Dict, + MODULES['builtins']['list'] : List, + MODULES['builtins']['set'] : Set, + MODULES['builtins']['tuple'] : Tuple, + MODULES['builtins']['type'] : type, + MODULES['builtins']['None'] : type(None), + MODULES['numpy']['intc']: np.intc, + MODULES['numpy']['intp']: np.intp, + MODULES['numpy']['int64']: np.int64, + MODULES['numpy']['int32']: np.int32, + MODULES['numpy']['int16']: np.int16, + MODULES['numpy']['int8']: np.int8, + MODULES['numpy']['uintc']: np.uintc, + MODULES['numpy']['uintp']: np.uintp, + MODULES['numpy']['uint64']: np.uint64, + MODULES['numpy']['uint32']: np.uint32, + MODULES['numpy']['uint16']: np.uint16, + MODULES['numpy']['uint8']: np.uint8, + MODULES['numpy']['float32']: np.float32, + MODULES['numpy']['float64']: np.float64, + MODULES['numpy']['complex64']: np.complex64, + MODULES['numpy']['complex128']: np.complex128, + MODULES['numpy']['ndarray']: NDArray, +} +try: + alias_to_type[MODULES['numpy']['float128']] = np.float128 + alias_to_type[MODULES['numpy']['complex256']] = np.complex256 +except AttributeError: + pass + +def alias_key(a): + if hasattr(a, 'path'): + return a.path + if hasattr(a, 'name'): + return a.name, + if hasattr(a, 'id'): + return a.id, + if isinstance(a, ast.Subscript): + return ('subscript:',) + alias_key(a.value) + alias_key(a.slice) + if isinstance(a, ast.Attribute): + return ('attr:', a.attr) + alias_key(a.value) + if isinstance(a, ast.Call): + return ('call:',) + alias_key(a.func) + if isinstance(a, ContainerOf): + return sum((alias_key(c) for c in sorted(a.containees, key=alias_key)), ()) + if isinstance(a, ast.Constant): + return ('cst:', str(a.value)) + # FIXME: how could we order those? + return str(id(a)), + + + +class TypeAnnotationParser(ast.NodeVisitor): + + class TypeOf: + def __init__(self, val): + self.val = val + + class UnionOf: + def __init__(self, lhs, rhs): + self.lhs = lhs + self.rhs = rhs + + def __init__(self, type_visitor): + self.type_visitor = type_visitor + self.aliases = self.type_visitor.strict_aliases + + def extract(self, node): + node_aliases = self.aliases[node] + if len(node_aliases) > 1: + raise PythranSyntaxError("Ambiguous identifier in type annotation", + node) + if not node_aliases: + raise PythranSyntaxError("Unbound identifier in type annotation", + node) + node_alias, = node_aliases + if node_alias not in alias_to_type: + raise PythranSyntaxError("Unsupported identifier in type annotation", + node) + + return alias_to_type[node_alias] + + + def visit_Attribute(self, node): + return self.extract(node) + + def visit_Name(self, node): + return self.extract(node) + + def visit_Constant(self, node): + return node.value + + def visit_Call(self, node): + func = self.visit(node.func) + if func is not type: + raise PythranSyntaxError("Expecting a type or a call to `type(...)`", + node) + if len(node.args) != 1: + raise PythranSyntaxError("`type` only supports a single argument", + node) + self.type_visitor.visit(node.args[0]) + return self.TypeOf(self.type_visitor.result[node.args[0]]) + + def visit_Tuple(self, node): + return tuple([self.visit(elt) for elt in node.elts]) + + def visit_BinOp(self, node): + if not isinstance(node.op, ast.BitOr): + raise PythranSyntaxError("Unsupported operation between type operands", + node) + left = self.visit(node.left) + right = self.visit(node.right) + return self.UnionOf(left, right) + + def visit_Subscript(self, node): + value = self.visit(node.value) + slice_ = self.visit(node.slice) + if issubclass(value, NDArray): + dtype, ndims = slice_ + return value[tuple([dtype, *([slice(0)] * ndims)])] + else: + return value[slice_] + + +def build_type(builder, t): + """ Python -> pythonic type binding. """ + if t is None: + return builder.NamedType(pytype_to_ctype(type(None))) + elif isinstance(t, List): + return builder.ListType(build_type(builder, t.__args__[0])) + elif isinstance(t, Set): + return builder.SetType(build_type(builder, t.__args__[0])) + elif isinstance(t, Dict): + tkey, tvalue = t.__args__ + return builder.DictType(build_type(builder, tkey), build_type(builder, + tvalue)) + elif isinstance(t, Tuple): + return builder.TupleType(*[build_type(builder, p) for p in t.__args__]) + elif isinstance(t, NDArray): + return builder.NDArrayType(build_type(builder, t.__args__[0]), len(t.__args__) - 1) + elif isinstance(t, TypeAnnotationParser.TypeOf): + return t.val + elif isinstance(t, TypeAnnotationParser.UnionOf): + return builder.CombinedTypes(build_type(builder, t.lhs), + build_type(builder, t.rhs)) + elif t in PYTYPE_TO_CTYPE_TABLE: + return builder.NamedType(PYTYPE_TO_CTYPE_TABLE[t]) + else: + raise NotImplementedError("build_type on {}".format(type(t))) + + +def parse_type_annotation(type_visitor, ann): + tap = TypeAnnotationParser(type_visitor) + typ = tap.visit(ann) + return build_type(type_visitor.builder, typ) class UnboundableRValue(Exception): pass -class Types(ModuleAnalysis): +class Types(ModuleAnalysis[Reorder, StrictAliases, LazynessAnalysis, + Immediates, RangeValues, Ancestors]): """ Infer symbolic type for all AST node. """ + class ResultType(dict): + def __init__(self): + self.builder = TypeBuilder() + + def copy(self): + other = TypeResult() + other.update(self.items()) + other.builder = self.builder + return other - def __init__(self): + def __init__(self): + super().__init__() self.max_seq_size = cfg.getint('typing', 'max_heterogeneous_sequence_size') - - class TypeResult(dict): - def __init__(self): - self.builder = TypeBuilder() - - def copy(self): - other = TypeResult() - other.update(self.items()) - other.builder = self.builder - return other - - self.result = TypeResult() self.builder = self.result.builder self.result["bool"] = self.builder.NamedType("bool") self.combiners = defaultdict(UserFunction) self.current_global_declarations = dict() self.max_recompute = 1 # max number of use to be lazy - ModuleAnalysis.__init__(self, Reorder, StrictAliases, LazynessAnalysis, - Immediates, RangeValues, Ancestors) self.curr_locals_declaration = None + self.ptype_count = 0 def combined(self, *types): all_types = ordered_set() @@ -69,6 +233,16 @@ class Types(ModuleAnalysis): elif len(all_types) == 1: return next(iter(all_types)) else: + all_types = all_types[:cfg.getint('typing', 'max_combiner')] + if {type(ty) for ty in all_types} == {self.builder.ListType}: + return self.builder.ListType(self.combined(*[ty.of for ty in all_types])) + if {type(ty) for ty in all_types} == {self.builder.SetType}: + return self.builder.SetType(self.combined(*[ty.of for ty in all_types])) + if {type(ty) for ty in all_types} == {self.builder.Assignable}: + return self.builder.Assignable(self.combined(*[ty.of for ty in all_types])) + if {type(ty) for ty in all_types} == {self.builder.Lazy}: + return self.builder.Lazy(self.combined(*[ty.of for ty in all_types])) + return self.builder.CombinedTypes(*all_types) @@ -97,37 +271,41 @@ class Types(ModuleAnalysis): register(mname, module) super(Types, self).prepare(node) - def run(self, node): - super(Types, self).run(node) + def visit_Module(self, node): + self.generic_visit(node) for head in self.current_global_declarations.values(): if head not in self.result: self.result[head] = "pythonic::types::none_type" - return self.result def register(self, fname, nid, ptype): """register ptype as a local typedef""" # Too many of them leads to memory burst - if len(self.typedefs[fname, nid]) < cfg.getint('typing', 'max_combiner'): + if len(self.typedefs[fname, nid]) < cfg.getint('typing', + 'max_interprocedural_combiner'): self.typedefs[fname, nid].append(ptype) return True return False def node_to_id(self, n, depth=()): + name, depth = self.node_to_name(n, depth) + return name.id, depth + + def node_to_name(self, n, depth=()): if isinstance(n, ast.Name): - return (n.id, depth) + return (n, depth) elif isinstance(n, ast.Subscript): if isinstance(n.slice, ast.Slice): - return self.node_to_id(n.value, depth) + return self.node_to_name(n.value, depth) else: index = n.slice.value if isnum(n.slice) else None - return self.node_to_id(n.value, depth + (index,)) + return self.node_to_name(n.value, depth + (index,)) # use alias information if any elif isinstance(n, ast.Call): - for alias in self.strict_aliases[n]: + for alias in self.sorted_strict_aliases(n): if alias is n: # no specific alias info continue try: - return self.node_to_id(alias, depth) + return self.node_to_name(alias, depth) except UnboundableRValue: continue raise UnboundableRValue() @@ -156,98 +334,130 @@ class Types(ModuleAnalysis): op = lambda x: x node_aliases = ordered_set([node]) - node_aliases.extend(self.strict_aliases.get(node, ())) + if node in self.strict_aliases: + node_aliases.extend(self.sorted_strict_aliases(node)) for a in node_aliases: self.combine_(a, op, othernode) def combine_(self, node, op, othernode): + # This comes from an assignment,so we must check where the value is + # assigned + name = None try: - # This comes from an assignment,so we must check where the value is - # assigned - try: - node_id, depth = self.node_to_id(node) - if depth: - node = ast.Name(node_id, ast.Load(), None, None) - former_op = op - - # update the type to reflect container nesting - def merge_container_type(ty, index): - # integral index make it possible to correctly - # update tuple type - if isinstance(index, int): - kty = self.builder.NamedType( - 'std::integral_constant<long,{}>' - .format(index)) - return self.builder.IndexableContainerType(kty, - ty) - elif isinstance(index, float): - kty = self.builder.NamedType('double') - return self.builder.IndexableContainerType(kty, - ty) + name, depth = self.node_to_name(node) + if depth: + former_op = op + + # update the type to reflect container nesting + def merge_container_type(ty, index): + # integral index make it possible to correctly + # update tuple type + if isinstance(index, int): + kty = self.builder.NamedType( + 'std::integral_constant<long,{}>' + .format(index)) + return self.builder.IndexableContainerType(kty, + ty) + elif isinstance(index, float): + kty = self.builder.NamedType('double') + return self.builder.IndexableContainerType(kty, + ty) + else: + # FIXME: what about other key types? + return self.builder.ContainerType(ty) + + for node_alias in self.sorted_strict_aliases(name, + extra=[name]): + def traverse_alias(alias, l): + if isinstance(alias, ContainerOf): + for containee in sorted(alias.containees, + key=alias_key): + traverse_alias(containee, l + 1) else: - # FIXME: what about other key types? - return self.builder.ContainerType(ty) - - def op(*args): - return reduce(merge_container_type, depth, - former_op(*args)) - - self.name_to_nodes[node_id].append(node) - except UnboundableRValue: - pass - - # perform inter procedural combination - if self.isargument(node): - node_id, _ = self.node_to_id(node) - if node not in self.result: - self.result[node] = op(self.result[othernode]) - assert self.result[node], "found an alias with a type" - - parametric_type = self.builder.PType(self.current, - self.result[othernode]) - - if self.register(self.current, node_id, parametric_type): - - current_function = self.combiners[self.current] - - def translator_generator(args, op): - ''' capture args for translator generation''' - def interprocedural_type_translator(s, n): - translated_othernode = ast.Name( - '__fake__', ast.Load(), None, None) - s.result[translated_othernode] = ( - parametric_type.instanciate( - s.current, - [s.result[arg] for arg in n.args])) - - # look for modified argument - for p, effective_arg in enumerate(n.args): - formal_arg = args[p] - if formal_arg.id == node_id: - translated_node = effective_arg - break - try: - s.combine(translated_node, - op, - translated_othernode) - except NotImplementedError: - pass - # this may fail when the effective - # parameter is an expression - except UnboundLocalError: - pass - # this may fail when translated_node - # is a default parameter - return interprocedural_type_translator - - translator = translator_generator(self.current.args.args, op) - current_function.add_combiner(translator) - else: - self.update_type(node, op, self.result[othernode]) - + def local_op(*args): + return reduce(merge_container_type, + depth[:-l] if l else depth, + former_op(*args)) + if len(depth) > l: + self.combine_(alias, local_op, othernode) + + traverse_alias(node_alias, 0) + return except UnboundableRValue: pass + if isinstance(node, ContainerOf): + def containeeop(*args): + container_type = op(*args) + if isinstance(container_type, self.builder.IndexableType): + raise NotImplementedError + if isinstance(container_type, (self.builder.ListType, + self.builder.SetType)): + return container_type.of + return self.builder.ElementType( + 0 if np.isnan(node.index) else node.index, + container_type) + + for containee in sorted(node.containees, key=alias_key): + try: + self.combine(containee, containeeop, othernode) + except NotImplementedError: + pass + + # perform inter procedural combination + if self.isargument(node): + node_id, _ = self.node_to_id(node) + if node not in self.result: + self.result[node] = op(self.result[othernode]) + self.name_to_nodes[name.id].append(node) + assert self.result[node], "found an alias with a type" + + parametric_type = self.builder.PType(self.current, + self.result[othernode], + self.ptype_count) + self.ptype_count += 1 + + if self.register(self.current, node_id, parametric_type): + + current_function = self.combiners[self.current] + + def translator_generator(args, op): + ''' capture args for translator generation''' + def interprocedural_type_translator(s, n): + translated_othernode = ast.Name( + '__fake__', ast.Load(), None, None) + s.result[translated_othernode] = ( + parametric_type.instanciate( + s.current, + [s.result[arg] for arg in n.args])) + + # look for modified argument + for p, effective_arg in enumerate(n.args): + formal_arg = args[p] + if formal_arg.id == node_id: + translated_node = effective_arg + break + try: + s.combine(translated_node, + op, + translated_othernode) + except NotImplementedError: + pass + # this may fail when the effective + # parameter is an expression + except UnboundLocalError: + pass + # this may fail when translated_node + # is a default parameter + return interprocedural_type_translator + + translator = translator_generator(self.current.args.args, op) + current_function.add_combiner(translator) + else: + self.update_type(node, op, self.result[othernode]) + if name is not None: + self.name_to_nodes[name.id].append(node) + def update_type(self, node, ty_builder, *args): if ty_builder is None: ty, = args @@ -280,10 +490,14 @@ class Types(ModuleAnalysis): for delayed_node in self.delayed_nodes: delayed_type = self.result[delayed_node] + if not isinstance(delayed_type, self.builder.LType): + continue all_types = ordered_set(self.result[ty] for ty in self.name_to_nodes[delayed_node.id]) final_type = self.combined(*all_types) delayed_type.final_type = final_type + if final_type is delayed_type.orig: + self.result[delayed_node] = delayed_type.orig # propagate type information through all aliases for name, nodes in self.name_to_nodes.items(): @@ -305,11 +519,25 @@ class Types(ModuleAnalysis): for k in self.curr_locals_declaration: self.result[k] = self.get_qualifier(k)(self.result[k]) + def assignable(self, ty): + if isinstance(ty, (self.builder.Assignable, self.builder.ListType, + self.builder.NamedType)): + return ty + else: + return self.builder.Assignable(ty) + + def lazy(self, ty): + if isinstance(ty, (self.builder.Lazy, self.builder.ListType, + self.builder.NamedType)): + return ty + else: + return self.builder.Lazy(ty) + def get_qualifier(self, node): lazy_res = self.lazyness_analysis[node.id] - return (self.builder.Lazy + return (self.lazy if lazy_res <= self.max_recompute - else self.builder.Assignable) + else self.assignable) def visit_Return(self, node): """ Compute return type and merges with others possible return type.""" @@ -331,38 +559,38 @@ class Types(ModuleAnalysis): if t in self.curr_locals_declaration: self.result[t] = self.get_qualifier(t)(self.result[t]) if isinstance(t, ast.Subscript): - if self.visit_AssignedSubscript(t): - for alias in self.strict_aliases[t.value]: - fake = ast.Subscript(alias, t.slice, ast.Store()) - self.combine(fake, None, node.value) + self.visit_AssignedSubscript(t) def visit_AnnAssign(self, node): + node_type = parse_type_annotation(self, node.annotation) + t = node.target + if node_type: + self.result[t] = node_type if not node.value: - # FIXME: replace this by actually setting the node type from the - # annotation - self.curr_locals_declaration.remove(node.target) + self.curr_locals_declaration.remove(t) return self.visit(node.value) - t = node.target - self.combine(t, None, node.value) + if node_type: + # A bit odd, isn't it? :-) + self.combine(t, None, t) + else: + self.combine(t, None, node.value) if t in self.curr_locals_declaration: self.result[t] = self.get_qualifier(t)(self.result[t]) + if isinstance(t, ast.Subscript): - if self.visit_AssignedSubscript(t): - for alias in self.strict_aliases[t.value]: - fake = ast.Subscript(alias, t.slice, ast.Store()) - self.combine(fake, None, node.value) + self.visit_AssignedSubscript(t) def visit_AugAssign(self, node): - self.visit(node.value) - + # No visit_AssignedSubscript as the container should already have been + # populated. if isinstance(node.target, ast.Subscript): - if self.visit_AssignedSubscript(node.target): - for alias in self.strict_aliases[node.target.value]: - fake = ast.Subscript(alias, node.target.slice, ast.Store()) - self.combine(fake, None, node.value) + self.visit(node.target) + self.visit(node.value) else: - self.combine(node.target, None, node.value) + tmp = ast.BinOp(deepcopy(node.target), node.op, node.value) + self.visit(tmp) + self.combine(node.target, None, tmp) def visit_For(self, node): @@ -417,18 +645,21 @@ class Types(ModuleAnalysis): self.result[left], self.result[right]) + def sorted_strict_aliases(self, func, extra=[]): + return sorted(self.strict_aliases[func] | set(extra), key=alias_key) + def visit_Call(self, node): self.generic_visit(node) func = node.func - for alias in self.strict_aliases[func]: + for alias in self.sorted_strict_aliases(func): # this comes from a bind if isinstance(alias, ast.Call): a0 = alias.args[0] # by construction of the bind construct assert len(self.strict_aliases[a0]) == 1 - bounded_function = next(iter(self.strict_aliases[a0])) + bounded_function = next(iter(self.sorted_strict_aliases(a0))) fake_name = deepcopy(a0) fake_node = ast.Call(fake_name, alias.args[1:] + node.args, []) @@ -444,7 +675,7 @@ class Types(ModuleAnalysis): def last_chance(): # maybe we can get saved if we have a hint about # the called function return type - for alias in self.strict_aliases[func]: + for alias in self.sorted_strict_aliases(func): if alias is self.current and alias in self.result: # great we have a (partial) type information self.result[node] = self.result[alias] @@ -563,13 +794,12 @@ class Types(ModuleAnalysis): def visit_AssignedSubscript(self, node): if isinstance(node.slice, ast.Slice): - return False + return elif isextslice(node.slice): - return False + return else: self.visit(node.slice) self.combine(node.value, self.builder.IndexableType, node.slice) - return True def delayed(self, node): fallback_type = self.combined(*[self.result[n] for n in diff --git a/contrib/python/pythran/pythran/unparse.py b/contrib/python/pythran/pythran/unparse.py index 058aa004ac7..f08fc0630aa 100644 --- a/contrib/python/pythran/pythran/unparse.py +++ b/contrib/python/pythran/pythran/unparse.py @@ -218,6 +218,10 @@ class Unparser: self.fill("global ") interleave(lambda: self.write(", "), self.write, t.names) + def _Nonlocal(self, t): + self.fill("nonlocal ") + interleave(lambda: self.write(", "), self.write, t.names) + def _Yield(self, t): self.write("(") self.write("yield") diff --git a/contrib/python/pythran/pythran/version.py b/contrib/python/pythran/pythran/version.py index 7aca4f8ffed..f083a1bd668 100644 --- a/contrib/python/pythran/pythran/version.py +++ b/contrib/python/pythran/pythran/version.py @@ -1,2 +1,2 @@ -__version__ = '0.17.0' +__version__ = '0.18.0' __descr__ = 'Ahead of Time compiler for numeric kernels' diff --git a/contrib/python/pythran/ya.make b/contrib/python/pythran/ya.make index 96bb3996205..ace834763b2 100644 --- a/contrib/python/pythran/ya.make +++ b/contrib/python/pythran/ya.make @@ -2,7 +2,7 @@ PY3_LIBRARY() -VERSION(0.17.0) +VERSION(0.18.0) LICENSE(BSD-3-Clause) |