%PDF- %PDF-
| Direktori : /lib/python3/dist-packages/pythran/analyses/ |
| Current File : //lib/python3/dist-packages/pythran/analyses/range_values.py |
""" Module Analysing code to extract positive subscripts from code. """
# TODO check bound of while and if for more accurate values.
import gast as ast
from collections import defaultdict
from contextlib import contextmanager
from functools import reduce
from pythran.analyses import Aliases, CFG
from pythran.intrinsic import Intrinsic
from pythran.passmanager import ModuleAnalysis
from pythran.interval import Interval, IntervalTuple, UNKNOWN_RANGE
from pythran.tables import MODULES, attributes
class UnsupportedExpression(NotImplementedError):
pass
class RangeValueTooCostly(RuntimeError):
pass
def combine(op, node0, node1):
key = '__{}__'.format(op.__class__.__name__.lower())
try:
return getattr(type(node0), key)(node0, node1)
except AttributeError:
return UNKNOWN_RANGE
def negate(node):
if isinstance(node, ast.Name):
# Not type info, could be anything :(
raise UnsupportedExpression()
if isinstance(node, ast.UnaryOp):
# !~x <> ~x == 0 <> x == ~0 <> x == -1
if isinstance(node.op, ast.Invert):
return ast.Compare(node.operand,
[ast.Eq()],
[ast.Constant(-1, None)])
# !!x <> x
if isinstance(node.op, ast.Not):
return node.operand
# !+x <> +x == 0 <> x == 0 <> !x
if isinstance(node.op, ast.UAdd):
return node.operand
# !-x <> -x == 0 <> x == 0 <> !x
if isinstance(node.op, ast.USub):
return node.operand
if isinstance(node, ast.BoolOp):
new_values = [ast.UnaryOp(ast.Not(), v) for v in node.values]
# !(x or y) <> !x and !y
if isinstance(node.op, ast.Or):
return ast.BoolOp(ast.And(), new_values)
# !(x and y) <> !x or !y
if isinstance(node.op, ast.And):
return ast.BoolOp(ast.Or(), new_values)
if isinstance(node, ast.Compare):
cmps = [ast.Compare(x, [negate(o)], [y])
for x, o, y
in zip([node.left] + node.comparators[:-1], node.ops,
node.comparators)]
if len(cmps) == 1:
return cmps[0]
return ast.BoolOp(ast.Or(), cmps)
if isinstance(node, ast.Eq):
return ast.NotEq()
if isinstance(node, ast.NotEq):
return ast.Eq()
if isinstance(node, ast.Gt):
return ast.LtE()
if isinstance(node, ast.GtE):
return ast.Lt()
if isinstance(node, ast.Lt):
return ast.GtE()
if isinstance(node, ast.LtE):
return ast.Gt()
if isinstance(node, ast.In):
return ast.NotIn()
if isinstance(node, ast.NotIn):
return ast.In()
if isinstance(node, ast.Attribute):
if node.attr == 'False':
return ast.Constant(True, None)
if node.attr == 'True':
return ast.Constant(False, None)
raise UnsupportedExpression()
def bound_range(mapping, aliases, node, modified=None):
"""
Bound the idenifier in `mapping' with the expression in `node'.
`aliases' is the result of aliasing analysis and `modified' is
updated with the set of identifiers possibly `bounded' as the result
of the call.
Returns `modified' or a fresh set of modified identifiers.
"""
if modified is None:
modified = set()
if isinstance(node, ast.Name):
# could be anything not just an integral
pass
elif isinstance(node, ast.UnaryOp):
try:
negated = negate(node.operand)
bound_range(mapping, aliases, negated, modified)
except UnsupportedExpression:
pass
elif isinstance(node, ast.BoolOp):
if isinstance(node.op, ast.And):
for value in node.values:
bound_range(mapping, aliases, value, modified)
elif isinstance(node.op, ast.Or):
mappings = [mapping.copy() for _ in node.values]
for value, mapping_cpy in zip(node.values, mappings):
bound_range(mapping_cpy, aliases, value, modified)
for k in modified:
mapping[k] = reduce(lambda x, y: x.union(y[k]),
mappings[1:],
mappings[0][k])
elif isinstance(node, ast.Compare):
left = node.left
if isinstance(node.left, ast.Name):
modified.add(node.left.id)
for op, right in zip(node.ops, node.comparators):
if isinstance(right, ast.Name):
modified.add(right.id)
if isinstance(left, ast.Name):
left_interval = mapping[left.id]
else:
left_interval = mapping[left]
if isinstance(right, ast.Name):
right_interval = mapping[right.id]
else:
right_interval = mapping[right]
l_l, l_h = left_interval.low, left_interval.high
r_l, r_h = right_interval.low, right_interval.high
r_i = l_i = None
if isinstance(op, ast.Eq):
low, high = max(l_l, r_l), min(l_h, r_h)
if low <= high:
l_i = r_i = Interval(max(l_l, r_l), min(l_h, r_h))
elif isinstance(op, ast.Lt):
# l < r => l.low < r.high & l.high < r.high
l_i = Interval(min(l_l, r_h - 1), min(l_h, r_h - 1))
# l < r => r.low < l.low & r.high < l.low
r_i = Interval(max(r_l, l_l + 1), max(r_h, l_l + 1))
elif isinstance(op, ast.LtE):
# l <= r => l.low <= r.high & l.high <= r.high
l_i = Interval(min(l_l, r_h), min(l_h, r_h))
# l <= r => r.low <= l.low & r.high <= l.low
r_i = Interval(max(r_l, l_l), max(r_h, l_l))
elif isinstance(op, ast.Gt):
# l > r => l.low > r.low & l.high > r.low
l_i = Interval(max(l_l, r_l + 1), max(l_h, r_l + 1))
# l > r => r.low > l.high & r.high > l.high
r_i = Interval(min(r_l, l_h - 1), min(r_h, l_h - 1))
elif isinstance(op, ast.GtE):
# l >= r => l.high >= r.low & l.low >= r.low
l_i = Interval(max(l_l, r_l), max(l_h, r_l))
# l >= r => r.low > l.high & r.high >= l.high
r_i = Interval(min(r_l, l_h), min(r_h, l_h))
elif isinstance(op, ast.In):
if isinstance(right, (ast.List, ast.Tuple, ast.Set)):
if right.elts:
low = min(mapping[elt].low for elt in right.elts)
high = max(mapping[elt].high for elt in right.elts)
l_i = Interval(low, high)
elif isinstance(right, ast.Call):
for alias in aliases[right.func]:
if not hasattr(alias, 'return_range_content'):
l_i = None
break
rrc = alias.return_range_content([mapping[arg] for arg
in right.args])
if l_i is None:
l_i = rrc
else:
l_i = l_i.union(alias.return_range(right))
if l_i is not None and isinstance(left, ast.Name):
mapping[left.id] = l_i
if r_i is not None and isinstance(right, ast.Name):
mapping[right.id] = r_i
left = right
class RangeValuesBase(ModuleAnalysis):
ResultHolder = object()
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)
self.parent = self
def add(self, variable, range_):
"""
Add a new low and high bound for a variable.
As it is flow insensitive, it compares it with old values and update it
if needed.
"""
if variable not in self.result:
self.result[variable] = range_
else:
self.result[variable] = self.result[variable].union(range_)
return self.result[variable]
def unionify(self, other):
for k, v in other.items():
if k in self.result:
self.result[k] = self.result[k].union(v)
else:
self.result[k] = v
def widen(self, curr, other):
self.result = curr
for k, v in other.items():
w = self.result.get(k, None)
if w is None:
self.result[k] = v
elif v is not w:
self.result[k] = w.widen(v)
def visit_BoolOp(self, node):
""" Merge right and left operands ranges.
TODO : We could exclude some operand with this range information...
>>> import gast as ast
>>> from pythran import passmanager, backend
>>> node = ast.parse('''
... def foo():
... a = 2
... c = 3
... d = a or c''')
>>> pm = passmanager.PassManager("test")
>>> res = pm.gather(RangeValues, node)
>>> res['d']
Interval(low=2, high=3)
"""
res = list(zip(*[self.visit(elt).bounds() for elt in node.values]))
return self.add(node, Interval(min(res[0]), max(res[1])))
def visit_BinOp(self, node):
""" Combine operands ranges for given operator.
>>> import gast as ast
>>> from pythran import passmanager, backend
>>> node = ast.parse('''
... def foo():
... a = 2
... c = 3
... d = a - c''')
>>> pm = passmanager.PassManager("test")
>>> res = pm.gather(RangeValues, node)
>>> res['d']
Interval(low=-1, high=-1)
"""
res = combine(node.op, self.visit(node.left), self.visit(node.right))
return self.add(node, res)
def visit_UnaryOp(self, node):
""" Update range with given unary operation.
>>> import gast as ast
>>> from pythran import passmanager, backend
>>> node = ast.parse('''
... def foo():
... a = 2
... c = -a
... d = ~a
... f = +a
... e = not a''')
>>> pm = passmanager.PassManager("test")
>>> res = pm.gather(RangeValues, node)
>>> res['f']
Interval(low=2, high=2)
>>> res['c']
Interval(low=-2, high=-2)
>>> res['d']
Interval(low=-3, high=-3)
>>> res['e']
Interval(low=0, high=1)
"""
res = self.visit(node.operand)
if isinstance(node.op, ast.Not):
res = Interval(0, 1)
elif(isinstance(node.op, ast.Invert) and
isinstance(res.high, int) and
isinstance(res.low, int)):
res = Interval(~res.high, ~res.low)
elif isinstance(node.op, ast.UAdd):
pass
elif isinstance(node.op, ast.USub):
res = Interval(-res.high, -res.low)
else:
res = UNKNOWN_RANGE
return self.add(node, res)
def visit_IfExp(self, node):
""" Use worst case for both possible values.
>>> import gast as ast
>>> from pythran import passmanager, backend
>>> node = ast.parse('''
... def foo():
... a = 2 or 3
... b = 4 or 5
... c = a if a else b''')
>>> pm = passmanager.PassManager("test")
>>> res = pm.gather(RangeValues, node)
>>> res['c']
Interval(low=2, high=5)
"""
self.visit(node.test)
body_res = self.visit(node.body)
orelse_res = self.visit(node.orelse)
return self.add(node, orelse_res.union(body_res))
def visit_Compare(self, node):
""" Boolean are possible index.
>>> import gast as ast
>>> from pythran import passmanager, backend
>>> node = ast.parse('''
... def foo():
... a = 2 or 3
... b = 4 or 5
... c = a < b
... d = b < 3
... e = b == 4''')
>>> pm = passmanager.PassManager("test")
>>> res = pm.gather(RangeValues, node)
>>> res['c']
Interval(low=1, high=1)
>>> res['d']
Interval(low=0, high=0)
>>> res['e']
Interval(low=0, high=1)
"""
if any(isinstance(op, (ast.In, ast.NotIn, ast.Is, ast.IsNot))
for op in node.ops):
self.generic_visit(node)
return self.add(node, Interval(0, 1))
curr = self.visit(node.left)
res = []
for op, comparator in zip(node.ops, node.comparators):
comparator = self.visit(comparator)
fake = ast.Compare(ast.Name('x', ast.Load(), None, None),
[op],
[ast.Name('y', ast.Load(), None, None)])
fake = ast.Expression(fake)
ast.fix_missing_locations(fake)
expr = compile(ast.gast_to_ast(fake), '<range_values>', 'eval')
res.append(eval(expr, {'x': curr, 'y': comparator}))
if all(res):
return self.add(node, Interval(1, 1))
elif any(r.low == r.high == 0 for r in res):
return self.add(node, Interval(0, 0))
else:
return self.add(node, Interval(0, 1))
def visit_Call(self, node):
""" Function calls are not handled for now.
>>> import gast as ast
>>> from pythran import passmanager, backend
>>> node = ast.parse('''
... def foo():
... a = builtins.range(10)''')
>>> pm = passmanager.PassManager("test")
>>> res = pm.gather(RangeValues, node)
>>> res['a']
Interval(low=-inf, high=inf)
"""
for alias in self.aliases[node.func]:
if alias is MODULES['builtins']['getattr']:
attr_name = node.args[-1].value
attribute = attributes[attr_name][-1]
self.add(node, attribute.return_range(None))
elif isinstance(alias, Intrinsic):
alias_range = alias.return_range(
[self.visit(n) for n in node.args])
self.add(node, alias_range)
elif isinstance(alias, ast.FunctionDef):
if alias not in self.result:
state = self.save_state()
self.parent.visit(alias)
self.restore_state(state)
self.add(node, self.result[alias])
else:
self.result.pop(node, None)
return self.generic_visit(node)
return self.result[node]
def visit_Constant(self, node):
""" Handle literals integers values. """
if isinstance(node.value, (bool, int)):
return self.add(node, Interval(node.value, node.value))
return UNKNOWN_RANGE
def visit_Name(self, node):
""" Get range for parameters for examples or false branching. """
return self.add(node, self.result[node.id])
def visit_Tuple(self, node):
return self.add(node,
IntervalTuple(self.visit(elt) for elt in node.elts))
def visit_Index(self, node):
return self.add(node, self.visit(node.value))
def visit_Subscript(self, node):
if isinstance(node.value, ast.Call):
for alias in self.aliases[node.value.func]:
if alias is MODULES['builtins']['getattr']:
attr_name = node.value.args[-1].value
attribute = attributes[attr_name][-1]
self.add(node, attribute.return_range_content(None))
elif isinstance(alias, Intrinsic):
self.add(node,
alias.return_range_content(
[self.visit(n) for n in node.value.args]))
else:
return self.generic_visit(node)
if not self.aliases[node.value.func]:
return self.generic_visit(node)
self.visit(node.slice)
return self.result[node]
else:
value = self.visit(node.value)
slice = self.visit(node.slice)
return self.add(node, value[slice])
def visit_FunctionDef(self, node):
""" Set default range value for globals and attributes.
>>> import gast as ast
>>> from pythran import passmanager, backend
>>> node = ast.parse("def foo(a, b): pass")
>>> pm = passmanager.PassManager("test")
>>> res = pm.gather(RangeValues, node)
>>> res['a']
Interval(low=-inf, high=inf)
"""
if node in self.result:
return
if self.use_omp:
return
self.result[node] = UNKNOWN_RANGE
# Set this prematurely to avoid infinite callgraph loop
prev_result = self.result.get(RangeValuesBase.ResultHolder, None)
self.function_visitor(node)
del self.result[node]
self.add(node, self.result[RangeValuesBase.ResultHolder])
if prev_result is not None:
self.result[RangeValuesBase.ResultHolder] = prev_result
else:
del self.result[RangeValuesBase.ResultHolder]
class RangeValuesSimple(RangeValuesBase):
"""
This analyse extract positive subscripts from code.
It is flow sensitive and aliasing is not taken into account as integer
doesn't create aliasing in Python.
>>> import gast as ast
>>> from pythran import passmanager, backend
>>> node = ast.parse('''
... def foo(a):
... for i in builtins.range(1, 10):
... c = i // 2''')
>>> pm = passmanager.PassManager("test")
>>> res = pm.gather(RangeValuesSimple, node)
>>> res['c'], res['i']
(Interval(low=0, high=5), Interval(low=1, high=10))
"""
def __init__(self, parent=None):
if parent is not None:
self.parent = parent
self.ctx = parent.ctx
self.deps = parent.deps
self.result = parent.result
self.aliases = parent.aliases
self.passmanager = parent.passmanager
else:
super(RangeValuesSimple, self).__init__()
def generic_visit(self, node):
""" Other nodes are not known and range value neither. """
super(RangeValuesSimple, self).generic_visit(node)
return self.add(node, UNKNOWN_RANGE)
def save_state(self):
return self.aliases,
def restore_state(self, state):
self.aliases, = state
def function_visitor(self, node):
for stmt in node.body:
self.visit(stmt)
def visit_Return(self, node):
if node.value:
return_range = self.visit(node.value)
return self.add(RangeValues.ResultHolder, return_range)
else:
return self.generic_visit(node)
def visit_Assert(self, node):
"""
Constraint the range of variables
>>> import gast as ast
>>> from pythran import passmanager, backend
>>> node = ast.parse("def foo(a): assert a >= 1; b = a + 1")
>>> pm = passmanager.PassManager("test")
>>> res = pm.gather(RangeValuesSimple, node)
>>> res['a']
Interval(low=1, high=inf)
>>> res['b']
Interval(low=2, high=inf)
"""
self.generic_visit(node)
bound_range(self.result, self.aliases, node.test)
def visit_Assign(self, node):
"""
Set range value for assigned variable.
We do not handle container values.
>>> import gast as ast
>>> from pythran import passmanager, backend
>>> node = ast.parse("def foo(): a = b = 2")
>>> pm = passmanager.PassManager("test")
>>> res = pm.gather(RangeValuesSimple, node)
>>> res['a']
Interval(low=2, high=2)
>>> res['b']
Interval(low=2, high=2)
"""
assigned_range = self.visit(node.value)
for target in node.targets:
if isinstance(target, ast.Name):
# Make sure all Interval doesn't alias for multiple variables.
self.add(target.id, assigned_range)
else:
self.visit(target)
def visit_AugAssign(self, node):
""" Update range value for augassigned variables.
>>> import gast as ast
>>> from pythran import passmanager, backend
>>> node = ast.parse("def foo(): a = 2; a -= 1")
>>> pm = passmanager.PassManager("test")
>>> res = pm.gather(RangeValuesSimple, node)
>>> res['a']
Interval(low=1, high=1)
"""
self.generic_visit(node)
if isinstance(node.target, ast.Name):
name = node.target.id
res = combine(node.op,
self.result[name],
self.result[node.value])
self.result[name] = res
def visit_For(self, node):
""" Handle iterate variable in for loops.
>>> import gast as ast
>>> from pythran import passmanager, backend
>>> node = ast.parse('''
... def foo():
... a = b = c = 2
... for i in builtins.range(1):
... a -= 1
... b += 1''')
>>> pm = passmanager.PassManager("test")
>>> res = pm.gather(RangeValuesSimple, node)
>>> res['a']
Interval(low=-inf, high=2)
>>> res['b']
Interval(low=2, high=inf)
>>> res['c']
Interval(low=2, high=2)
>>> node = ast.parse('''
... def foo():
... for i in (1, 2, 4):
... a = i''')
>>> pm = passmanager.PassManager("test")
>>> res = pm.gather(RangeValuesSimple, node)
>>> res['a']
Interval(low=1, high=4)
"""
assert isinstance(node.target, ast.Name), "For apply on variables."
self.visit(node.iter)
if isinstance(node.iter, ast.Call):
for alias in self.aliases[node.iter.func]:
if isinstance(alias, Intrinsic):
self.add(node.target.id,
alias.return_range_content(
[self.visit(n) for n in node.iter.args]))
self.visit_loop(node,
ast.Compare(node.target, [ast.In()], [node.iter]))
def visit_loop(self, node, cond=None):
""" Handle incremented variables in loop body.
>>> import gast as ast
>>> from pythran import passmanager, backend
>>> node = ast.parse('''
... def foo():
... a = b = c = 2
... while a > 0:
... a -= 1
... b += 1''')
>>> pm = passmanager.PassManager("test")
>>> res = pm.gather(RangeValuesSimple, node)
>>> res['a']
Interval(low=0, high=2)
>>> res['b']
Interval(low=2, high=inf)
>>> res['c']
Interval(low=2, high=2)
"""
if cond is not None:
init_range = self.result
self.result = self.result.copy()
bound_range(self.result, self.aliases, cond)
# visit once to gather newly declared vars
for stmt in node.body:
self.visit(stmt)
# freeze current state
old_range = self.result.copy()
# extra round
for stmt in node.body:
self.visit(stmt)
# widen any change
for expr, range_ in old_range.items():
self.result[expr] = self.result[expr].widen(range_)
# propagate the new informations again
if cond is not None:
bound_range(self.result, self.aliases, cond)
for stmt in node.body:
self.visit(stmt)
self.unionify(init_range)
self.visit(cond)
for stmt in node.orelse:
self.visit(stmt)
def visit_While(self, node):
self.visit(node.test)
return self.visit_loop(node, node.test)
def visit_If(self, node):
""" Handle iterate variable across branches
>>> import gast as ast
>>> from pythran import passmanager, backend
>>> pm = passmanager.PassManager("test")
>>> node = ast.parse('''
... def foo(a):
... if a > 1: b = 1
... else: b = 3''')
>>> res = pm.gather(RangeValuesSimple, node)
>>> res['b']
Interval(low=1, high=3)
>>> node = ast.parse('''
... def foo(a):
... if a > 1: b = a
... else: b = 3''')
>>> res = pm.gather(RangeValuesSimple, node)
>>> res['b']
Interval(low=2, high=inf)
>>> node = ast.parse('''
... def foo(a):
... if 0 < a < 4: b = a
... else: b = 3''')
>>> res = pm.gather(RangeValuesSimple, node)
>>> res['b']
Interval(low=1, high=3)
>>> node = ast.parse('''
... def foo(a):
... if (0 < a) and (a < 4): b = a
... else: b = 3''')
>>> res = pm.gather(RangeValuesSimple, node)
>>> res['b']
Interval(low=1, high=3)
>>> node = ast.parse('''
... def foo(a):
... if (a == 1) or (a == 2): b = a
... else: b = 3''')
>>> res = pm.gather(RangeValuesSimple, node)
>>> res['b']
Interval(low=1, high=3)
"""
self.visit(node.test)
old_range = self.result
self.result = old_range.copy()
bound_range(self.result, self.aliases, node.test)
for stmt in node.body:
self.visit(stmt)
body_range = self.result
self.result = old_range.copy()
for stmt in node.orelse:
self.visit(stmt)
orelse_range = self.result
self.result = body_range
self.unionify(orelse_range)
def visit_Try(self, node):
init_range = self.result
self.result = init_range.copy()
for stmt in node.body:
self.visit(stmt)
self.unionify(init_range)
init_range = self.result.copy()
for handler in node.handlers:
self.result, prev_state = init_range.copy(), self.result
for stmt in handler.body:
self.visit(stmt)
self.unionify(prev_state)
self.result, prev_state = init_range, self.result
for stmt in node.orelse:
self.visit(stmt)
self.unionify(prev_state)
for stmt in node.finalbody:
self.visit(stmt)
class RangeValues(RangeValuesBase):
"""
This analyse extract positive subscripts from code.
It is flow sensitive and aliasing is not taken into account as integer
doesn't create aliasing in Python.
>>> import gast as ast
>>> from pythran import passmanager, backend
>>> node = ast.parse('''
... def foo(a):
... for i in builtins.range(1, 10):
... c = i // 2
... return''')
>>> pm = passmanager.PassManager("test")
>>> res = pm.gather(RangeValues, node)
>>> res['c'], res['i']
(Interval(low=0, high=5), Interval(low=1, high=10))
"""
def __init__(self):
super(RangeValues, self).__init__()
def generic_visit(self, node):
""" Other nodes are not known and range value neither. """
super(RangeValues, self).generic_visit(node)
if isinstance(node, ast.stmt):
if node in self.cfg:
return self.cfg.successors(node)
else:
return self.add(node, UNKNOWN_RANGE)
def cfg_visit(self, node, skip=None):
successors = [node]
visited = set() if skip is None else skip.copy()
while successors:
successor = successors.pop()
if successor in visited:
continue
visited.add(successor)
nexts = self.visit(successor)
if nexts:
successors.extend((n for n in nexts if n is not CFG.NIL))
def save_state(self):
return (self.cfg, self.aliases, self.use_omp, self.no_backward,
self.no_if_split)
def restore_state(self, state):
(self.cfg, self.aliases, self.use_omp, self.no_backward,
self.no_if_split) = state
def function_visitor(self, node):
parent_result = self.result
self.result = defaultdict(lambda: UNKNOWN_RANGE)
for k, v in parent_result.items():
if isinstance(k, ast.FunctionDef):
self.result[k] = v
# try to visit the cfg, it's greedy but more accurate
try:
self.no_backward = 0
self.no_if_split = 0
self.cfg_visit(next(self.cfg.successors(node)))
for k, v in self.result.items():
parent_result[k] = v
self.result = parent_result
# too greedy? Never mind, we know how to be fast and simple :-)
except RangeValueTooCostly:
self.result = parent_result
rvs = RangeValuesSimple(self)
rvs.visit(node)
def visit_Return(self, node):
if node.value:
return_range = self.visit(node.value)
self.add(RangeValues.ResultHolder, return_range)
return self.cfg.successors(node)
def visit_Assert(self, node):
"""
Constraint the range of variables
>>> import gast as ast
>>> from pythran import passmanager, backend
>>> node = ast.parse("def foo(a): assert a >= 1; b = a + 1")
>>> pm = passmanager.PassManager("test")
>>> res = pm.gather(RangeValues, node)
>>> res['a']
Interval(low=1, high=inf)
>>> res['b']
Interval(low=2, high=inf)
"""
self.visit(node.test)
bound_range(self.result, self.aliases, node.test)
return self.cfg.successors(node)
def visit_Assign(self, node):
"""
Set range value for assigned variable.
We do not handle container values.
>>> import gast as ast
>>> from pythran import passmanager, backend
>>> node = ast.parse("def foo(): a = b = 2")
>>> pm = passmanager.PassManager("test")
>>> res = pm.gather(RangeValues, node)
>>> res['a']
Interval(low=2, high=2)
>>> res['b']
Interval(low=2, high=2)
"""
assigned_range = self.visit(node.value)
for target in node.targets:
if isinstance(target, ast.Name):
# Make sure all Interval doesn't alias for multiple variables.
self.result[target.id] = assigned_range
else:
self.visit(target)
return self.cfg.successors(node)
def visit_AugAssign(self, node):
""" Update range value for augassigned variables.
>>> import gast as ast
>>> from pythran import passmanager, backend
>>> node = ast.parse("def foo(): a = 2; a -= 1")
>>> pm = passmanager.PassManager("test")
>>> res = pm.gather(RangeValues, node)
>>> res['a']
Interval(low=1, high=1)
"""
self.generic_visit(node)
if isinstance(node.target, ast.Name):
name = node.target.id
res = combine(node.op,
self.result[name],
self.result[node.value])
self.result[name] = res
return self.cfg.successors(node)
def visit_loop_successor(self, node):
for successor in self.cfg.successors(node):
if successor is not node.body[0]:
if isinstance(node, ast.While):
bound_range(self.result, self.aliases,
ast.UnaryOp(ast.Not(), node.test))
return [successor]
def visit_For(self, node):
""" Handle iterate variable in for loops.
>>> import gast as ast
>>> from pythran import passmanager, backend
>>> node = ast.parse('''
... def foo():
... a = b = c = 2
... for i in builtins.range(1):
... a -= 1
... b += 1
... return''')
>>> pm = passmanager.PassManager("test")
>>> res = pm.gather(RangeValues, node)
>>> res['a']
Interval(low=-inf, high=2)
>>> res['b']
Interval(low=2, high=inf)
>>> res['c']
Interval(low=2, high=2)
>>> node = ast.parse('''
... def foo():
... for i in (1, 2, 4):
... a = i
... return''')
>>> pm = passmanager.PassManager("test")
>>> res = pm.gather(RangeValues, node)
>>> res['a']
Interval(low=1, high=4)
"""
assert isinstance(node.target, ast.Name), "For apply on variables."
self.visit(node.iter)
init_state = self.result.copy()
bound_range(self.result, self.aliases, ast.Compare(node.target,
[ast.In()],
[node.iter]))
# visit body
skip = {x for x in self.cfg.successors(node) if x is not node.body[0]}
skip.add(node)
next_ = self.cfg_visit(node.body[0], skip=skip)
if self.no_backward:
return self.visit_loop_successor(node)
else:
pass #self.no_backward += 1
prev_state = self.result
self.result = prev_state.copy()
self.cfg_visit(node.body[0], skip=skip)
self.widen(self.result, prev_state)
self.cfg_visit(node.body[0], skip=skip)
self.unionify(init_state)
pass #self.no_backward -= 1
return self.visit_loop_successor(node)
def visit_While(self, node):
""" Handle incremented variables in loop body.
>>> import gast as ast
>>> from pythran import passmanager, backend
>>> node = ast.parse('''
... def foo():
... a = b = c = 10
... while a > 0:
... a -= 1
... b += 1
... return''')
>>> pm = passmanager.PassManager("test")
>>> res = pm.gather(RangeValues, node)
>>> res['a']
Interval(low=-inf, high=0)
>>> res['b']
Interval(low=11, high=inf)
>>> res['c']
Interval(low=10, high=10)
"""
test_range = self.visit(node.test)
init_state = self.result.copy()
skip = {x for x in self.cfg.successors(node) if x is not node.body[0]}
skip.add(node)
# if the test may be false, visit the tail
if 0 in test_range:
for successor in list(self.cfg.successors(node)):
if successor is not node.body[0]:
self.cfg_visit(successor, skip=skip)
bound_range(self.result, self.aliases, node.test)
# visit body
self.cfg_visit(node.body[0], skip=skip)
if self.no_backward:
if 0 in test_range:
self.unionify(init_state)
return self.visit_loop_successor(node)
else:
pass #self.no_backward += 1
prev_state = self.result
self.result = prev_state.copy()
self.cfg_visit(node.body[0], skip=skip)
self.widen(self.result, prev_state)
# propagate the result of the widening
self.cfg_visit(node.body[0], skip=skip)
if 0 in test_range:
self.unionify(init_state)
else:
self.unionify(prev_state)
self.visit(node.test)
pass #self.no_backward -= 1
# exit from the while test
return self.visit_loop_successor(node)
def visit_If(self, node):
""" Handle iterate variable across branches
>>> import gast as ast
>>> from pythran import passmanager, backend
>>> pm = passmanager.PassManager("test")
>>> node = ast.parse('''
... def foo(a):
... if a > 1: b = 1
... else: b = 3
... pass''')
>>> res = pm.gather(RangeValues, node)
>>> res['b']
Interval(low=1, high=3)
>>> node = ast.parse('''
... def foo(a):
... if a > 1: b = a
... else: b = 3
... pass''')
>>> res = pm.gather(RangeValues, node)
>>> res['b']
Interval(low=2, high=inf)
>>> node = ast.parse('''
... def foo(a):
... if 0 < a < 4: b = a
... else: b = 3
... pass''')
>>> res = pm.gather(RangeValues, node)
>>> res['b']
Interval(low=1, high=3)
>>> node = ast.parse('''
... def foo(a):
... if (0 < a) and (a < 4): b = a
... else: b = 3
... pass''')
>>> res = pm.gather(RangeValues, node)
>>> res['b']
Interval(low=1, high=3)
>>> node = ast.parse('''
... def foo(a):
... if (a == 1) or (a == 2): b = a
... else: b = 3
... pass''')
>>> res = pm.gather(RangeValues, node)
>>> res['b']
Interval(low=1, high=3)
>>> node = ast.parse('''
... def foo(a):
... b = 5
... if a > 0: b = a
... pass''')
>>> res = pm.gather(RangeValues, node)
>>> res['a'], res['b']
(Interval(low=-inf, high=inf), Interval(low=1, high=inf))
>>> node = ast.parse('''
... def foo(a):
... if a > 3: b = 1
... else: b = 2
... if a > 1: b = 2
... pass''')
>>> res = pm.gather(RangeValues, node)
>>> res['b']
Interval(low=2, high=2)
"""
# handling each branch becomes too costly, opt for a simpler,
# less accurate algorithm.
if self.no_if_split == 4:
raise RangeValueTooCostly()
self.no_if_split += 1
test_range = self.visit(node.test)
init_state = self.result.copy()
if 1 in test_range:
bound_range(self.result, self.aliases, node.test)
self.cfg_visit(node.body[0])
visited_successors = {node.body[0]}
if node.orelse:
if 0 in test_range:
prev_state = self.result
self.result = init_state.copy()
bound_range(self.result, self.aliases,
ast.UnaryOp(ast.Not(), node.test))
self.cfg_visit(node.orelse[0])
self.unionify(prev_state)
visited_successors.add(node.orelse[0])
elif 0 in test_range:
successors = self.cfg.successors(node)
for successor in list(successors):
# no else branch
if successor not in visited_successors:
self.result, prev_state = init_state.copy(), self.result
bound_range(self.result, self.aliases,
ast.UnaryOp(ast.Not(), node.test))
self.cfg_visit(successor)
self.unionify(prev_state)
self.no_if_split -= 1
def visit_Try(self, node):
init_range = self.result
self.result = init_range.copy()
self.cfg_visit(node.body[0])
self.unionify(init_range)
init_range = self.result.copy()
for handler in node.handlers:
self.result, prev_state = init_range.copy(), self.result
self.cfg_visit(handler.body[0])
self.unionify(prev_state)
# Un comment the line below to test RangeValuesSimple
# RangeValues = RangeValuesSimple
# RangeValues.__name__ = 'RangeValues'