%PDF- %PDF-
| Direktori : /lib/python3/dist-packages/pythran/ |
| Current File : //lib/python3/dist-packages/pythran/passmanager.py |
"""
This module provides classes and functions for pass management.
There are two kinds of passes: transformations and analysis.
* ModuleAnalysis, FunctionAnalysis and NodeAnalysis are to be
subclassed by any pass that collects information about the AST.
* gather is used to gather (!) the result of an analyses on an AST node.
* Backend is to be sub-classed by any pass that dumps the AST content.
* dump is used to dump (!) the AST using the given backend.
* Transformation is to be sub-classed by any pass that updates the AST.
* apply is used to apply (sic) a transformation on an AST node.
"""
import gast as ast
import os
import re
def uncamel(name):
"""Transform CamelCase naming convention into C-ish convention."""
s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()
class AnalysisContext(object):
"""
Class that stores the hierarchy of node visited.
Contains:
* parent module
* parent function
"""
def __init__(self):
self.module = None
self.function = None
class ContextManager(object):
"""
Class to be inherited from to add automatic update of context.
The optional analysis dependencies are listed in `dependencies'.
"""
def __init__(self, *dependencies):
""" 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
self.ctx = ctx or AnalysisContext()
def verify_dependencies(self):
"""
Checks no analysis are called before a transformation,
as the transformation could invalidate the analysis.
"""
for i in range(1, len(self.deps)):
assert(not (isinstance(self.deps[i], Transformation) and
isinstance(self.deps[i - 1], Analysis))
), "invalid dep order for %s" % self
def visit(self, node):
if isinstance(node, ast.FunctionDef):
self.ctx.function = node
for D in self.deps:
if issubclass(D, FunctionAnalysis):
# this should have already been computed as part of the run
# method of function analysis triggered by prepare
result = self.passmanager._cache[node, D]
setattr(self, uncamel(D.__name__), result)
return super(ContextManager, self).visit(node)
def prepare(self, node):
'''Gather analysis result required by this analysis'''
if isinstance(node, ast.Module):
self.ctx.module = node
elif isinstance(node, ast.FunctionDef):
self.ctx.function = node
for D in self.deps:
d = D()
d.attach(self.passmanager, self.ctx)
result = d.run(node)
setattr(self, uncamel(D.__name__), result)
def run(self, node):
"""Override this to add special pre or post processing handlers."""
self.prepare(node)
return self.visit(node)
def gather(self, analysis, node):
a = analysis()
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")
self.update = False
ContextManager.__init__(self, *dependencies)
def run(self, node):
key = node, type(self)
if key in self.passmanager._cache:
self.result = self.passmanager._cache[key]
else:
super(Analysis, self).run(node)
self.passmanager._cache[key] = self.result
return self.result
def display(self, data):
print(data)
def apply(self, node):
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:
raise ValueError("{} called in an uninitialized context"
.format(type(self).__name__))
node = self.ctx.module
return super(ModuleAnalysis, self).run(node)
class FunctionAnalysis(Analysis):
"""An analysis that operates on a function."""
def run(self, node):
if isinstance(node, ast.Module):
self.ctx.module = node
last = None
for stmt in node.body:
if isinstance(stmt, ast.FunctionDef):
last = self.gather(type(self), stmt)
# last is None if there's no function to process
return self.result if last is None else last
elif not isinstance(node, ast.FunctionDef):
if self.ctx.function is None:
raise ValueError("{} called in an uninitialized context"
.format(type(self).__name__))
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."""
class Transformation(ContextManager, ast.NodeTransformer):
"""A pass that updates its content."""
def __init__(self, *args, **kwargs):
""" Initialize the update used to know if update happened. """
super(Transformation, self).__init__(*args, **kwargs)
self.update = False
def run(self, node):
""" Apply transformation and dependencies and fix new node location."""
n = super(Transformation, self).run(node)
# the transformation updated the AST, so analyse may need to be rerun
# we could use a finer-grain caching system, and provide a way to flag
# some analyses as `unmodified' by the transformation, as done in LLVM
# (and PIPS ;-)
if self.update:
ast.fix_missing_locations(n)
self.passmanager._cache.clear()
return n
def apply(self, node):
""" Apply transformation and return if an update happened. """
new_node = self.run(node)
return self.update, new_node
class PassManager(object):
'''
Front end to the pythran pass system.
'''
def __init__(self, module_name, module_dir=None):
self.module_name = module_name
self.module_dir = module_dir or os.getcwd()
self._cache = {}
def gather(self, analysis, node):
"High-level function to call an `analysis' on a `node'"
assert issubclass(analysis, Analysis)
a = analysis()
a.attach(self)
return a.run(node)
def dump(self, backend, node):
'''High-level function to call a `backend' on a `node' to generate
code for module `module_name'.'''
assert issubclass(backend, Backend)
b = backend()
b.attach(self)
return b.run(node)
def apply(self, transformation, node):
'''
High-level function to call a `transformation' on a `node'.
If the transformation is an analysis, the result of the analysis
is displayed.
'''
assert issubclass(transformation, (Transformation, Analysis))
a = transformation()
a.attach(self)
return a.apply(node)