[pypy-commit] pypy vecopt2: added new dependency changes
plan_rich
noreply at buildbot.pypy.org
Tue May 5 09:45:33 CEST 2015
Author: Richard Plangger <rich at pasra.at>
Branch: vecopt2
Changeset: r77085:4848cc630ada
Date: 2015-03-23 15:58 +0100
http://bitbucket.org/pypy/pypy/changeset/4848cc630ada/
Log: added new dependency changes
diff --git a/rpython/jit/metainterp/optimizeopt/dependency.py b/rpython/jit/metainterp/optimizeopt/dependency.py
--- a/rpython/jit/metainterp/optimizeopt/dependency.py
+++ b/rpython/jit/metainterp/optimizeopt/dependency.py
@@ -1,28 +1,56 @@
from rpython.jit.metainterp.resoperation import rop
+from rpython.jit.codewriter.effectinfo import EffectInfo
+from rpython.jit.metainterp.history import BoxPtr, ConstPtr, ConstInt, BoxInt
+from rpython.rtyper.lltypesystem import llmemory
+from rpython.rlib.unroll import unrolling_iterable
+
+MODIFY_COMPLEX_OBJ = [ (rop.SETARRAYITEM_GC, 0)
+ , (rop.SETARRAYITEM_RAW, 0)
+ , (rop.RAW_STORE, 0)
+ , (rop.SETINTERIORFIELD_GC, 0)
+ , (rop.SETINTERIORFIELD_RAW, 0)
+ , (rop.SETFIELD_GC, 0)
+ , (rop.SETFIELD_RAW, 0)
+ , (rop.ZERO_PTR_FIELD, 0)
+ , (rop.ZERO_PTR_FIELD, 0)
+ , (rop.ZERO_ARRAY, 0)
+ , (rop.STRSETITEM, 0)
+ , (rop.UNICODESETITEM, 0)
+ ]
class Dependency(object):
- def __init__(self, idx_from, idx_to, arg, is_definition):
- self.defined_arg = arg
+ def __init__(self, idx_from, idx_to, arg):
+ assert idx_from != idx_to
+ self.args = []
+ if arg is not None:
+ self.args.append(arg)
+
self.idx_from = idx_from
self.idx_to = idx_to
- self.is_definition = is_definition
+
+ def adjust_dep_after_swap(self, idx_old, idx_new):
+ if self.idx_from == idx_old:
+ self.idx_from = idx_new
+ elif self.idx_to == idx_old:
+ self.idx_to = idx_new
def __repr__(self):
- return 'Dep(trace[%d] -> trace[%d], arg: %s, def-use? %d)' \
- % (self.idx_from, self.idx_to, self.defined_arg, \
- self.is_definition)
+ return 'Dep(trace[%d] -> trace[%d], arg: %s)' \
+ % (self.idx_from, self.idx_to, self.args)
class DependencyGraph(object):
""" A graph that represents one of the following dependencies:
* True dependency
- * Anti dependency
- * Ouput dependency
+ * Anti dependency (not present in SSA traces)
+ * Ouput dependency (not present in SSA traces)
Representation is an adjacent list. The number of edges between the
vertices is expected to be small.
+ Note that adjacent lists order their dependencies. They are ordered
+ by the target instruction they point to if the instruction is
+ a dependency.
"""
- def __init__(self, trace):
- self.trace = trace
- self.operations = self.trace.operations
+ def __init__(self, operations):
+ self.operations = operations
self.adjacent_list = [ [] for i in range(len(self.operations)) ]
self.build_dependencies(self.operations)
@@ -65,32 +93,119 @@
idx = defining_indices[arg]
self._put_edge(idx, i, arg)
+ # a trace has store operations on complex operations
+ # (e.g. setarrayitem). in general only once cell is updated,
+ # and in theroy it could be tracked but for simplicity, the
+ # whole is marked as redefined, thus any later usage sees
+ # only this definition.
+ self._redefine_if_complex_obj_is_modified(op, i, defining_indices)
+ if op.is_guard() and i > 0:
+ self._guard_dependency(op, i, operations, defining_indices)
+
+ def _redefine_if_complex_obj_is_modified(self, op, index, defining_indices):
+ if not op.has_no_side_effect():
+ for arg in self._destroyed_arguments(op):
+ try:
+ # put an edge from the definition and all later uses until this
+ # instruction to this instruction
+ def_idx = defining_indices[arg]
+ for dep in self.instr_dependencies(def_idx):
+ if dep.idx_to >= index:
+ break
+ self._put_edge(dep.idx_to, index, arg)
+ self._put_edge(def_idx, index, arg)
+ except KeyError:
+ pass
+
+ def _destroyed_arguments(self, op):
+ # conservative, if an item in array p0 is modified or a call
+ # contains a boxptr parameter, it is assumed that this is a
+ # new definition.
+ args = []
+ if op.is_call() and op.getopnum() != rop.CALL_ASSEMBLER:
+ # free destroys an argument -> connect all uses & def with it
+ descr = op.getdescr()
+ extrainfo = descr.get_extra_info()
+ if extrainfo.oopspecindex == EffectInfo.OS_RAW_FREE:
+ args.append(op.getarg(1))
+ else:
+ for opnum, i in unrolling_iterable(MODIFY_COMPLEX_OBJ):
+ if op.getopnum() == opnum:
+ arg = op.getarg(i)
+ args.append(arg)
+ return args
+
+ def _guard_dependency(self, op, i, operations, defining_indices):
+ # respect a guard after a statement that can raise!
+ assert i > 0
+
+ j = i-1
+ while j > 0:
+ prev_op = operations[j]
+ if prev_op.is_guard():
+ j -= 1
+ else:
+ break
+ prev_op = operations[j]
+
+ if op.is_guard_exception() and prev_op.can_raise():
+ self._inhert_all_dependencies(operations, j, i)
+ # respect an overflow guard after an ovf statement!
+ if op.is_guard_overflow() and prev_op.is_ovf():
+ self._inhert_all_dependencies(operations, j, i)
+ if op.getopnum() == rop.GUARD_NOT_FORCED and prev_op.can_raise():
+ self._inhert_all_dependencies(operations, j, i)
+ if op.getopnum() == rop.GUARD_NOT_FORCED_2 and prev_op.can_raise():
+ self._inhert_all_dependencies(operations, j, i)
+
+ def _inhert_all_dependencies(self, operations, op_idx, from_idx):
+ assert op_idx < from_idx
+ for dep in self.instr_dependencies(from_idx):
+ for dep in self.instr_dependencies(dep.idx_from):
+ if dep.idx_to >= op_idx:
+ break
+ self._put_edge(dep.idx_to, op_idx, None)
+ if dep.idx_from < op_idx:
+ self._put_edge(dep.idx_from, op_idx, None)
+ self._put_edge(op_idx, from_idx, None)
+
def _put_edge(self, idx_from, idx_to, arg):
- if self._is_unique_dep(idx_from, idx_to, arg):
- self.adjacent_list[idx_from].append(Dependency(idx_from, idx_to, arg, True))
- self.adjacent_list[idx_to].append(Dependency(idx_to, idx_from, arg, False))
+ assert idx_from != idx_to
+ print("puttin", idx_from, idx_to)
+ dep = self.instr_dependency(idx_from, idx_to)
+ if dep is None:
+ dep = Dependency(idx_from, idx_to, arg)
+ self.adjacent_list[idx_from].append(dep)
+ self.adjacent_list[idx_to].append(dep)
+ else:
+ if arg not in dep.args:
+ dep.args.append(arg)
- def _is_unique_dep(self, idx_from, idx_to, arg):
- """ Dependencies must be unique. It is not allowed
- to have multiple dependencies.
- e.g. label(i1)
- i2 = int_add(i1,i1)
- ...
+ def get_uses(self, idx):
+ deps = []
+ for dep in self.adjacent_list[idx]:
+ if idx < dep.idx_to:
+ deps.append(dep)
+ return deps
- Only the label instr can only have one dep (0->1) even if it is
- used twice in int_add. The same is true for the reverse dependency
- (1<-0) at int_add.
- """
- for dep in self.adjacent_list[idx_from]:
- if dep.idx_from == idx_from and dep.idx_to == idx_to \
- and dep.defined_arg == arg:
- return False
- return True
+ def get_defs(self, idx):
+ deps = []
+ for dep in self.adjacent_list[idx]:
+ if idx > dep.idx_from:
+ deps.append(dep)
+ return deps
def instr_dependencies(self, idx):
edges = self.adjacent_list[idx]
return edges
+ def definition_dependencies(self, idx):
+ deps = []
+ for dep in self.adjacent_list[idx]:
+ for dep_def in self.adjacent_list[dep.idx_from]:
+ deps.append(dep_def)
+ return deps
+
def instr_dependency(self, from_instr_idx, to_instr_idx):
""" Does there exist a dependency from the instruction to another?
Returns None if there is no dependency or the Dependency object in
@@ -101,3 +216,25 @@
return edge
return None
+ def __repr__(self):
+ graph = "graph([\n"
+
+ for l in self.adjacent_list:
+ graph += " " + str([d.idx_to for d in l]) + "\n"
+
+ return graph + " ])"
+
+ def swap_instructions(self, ia, ib):
+ depa = self.adjacent_list[ia]
+ depb = self.adjacent_list[ib]
+
+ for d in depa:
+ d.adjust_dep_after_swap(ia, ib)
+
+ for d in depb:
+ d.adjust_dep_after_swap(ib, ia)
+
+ self.adjacent_list[ia] = depb
+ self.adjacent_list[ib] = depa
+
+
diff --git a/rpython/jit/metainterp/optimizeopt/test/test_dependency.py b/rpython/jit/metainterp/optimizeopt/test/test_dependency.py
--- a/rpython/jit/metainterp/optimizeopt/test/test_dependency.py
+++ b/rpython/jit/metainterp/optimizeopt/test/test_dependency.py
@@ -8,11 +8,9 @@
class DepTestHelper(BaseTest):
- enable_opts = "intbounds:rewrite:virtualize:string:earlyforce:pure:heap:unfold"
-
def build_dependency(self, ops):
loop = self.parse_loop(ops)
- return DependencyGraph(loop)
+ return DependencyGraph(loop.operations)
def parse_loop(self, ops):
loop = self.parse(ops, postprocess=self.postprocess)
@@ -23,27 +21,6 @@
loop.operations[-1].setdescr(token)
return loop
- def assert_no_edge(self, graph, f, t = -1):
- if type(f) == list:
- for _f,_t in f:
- self.assert_no_edge(graph, _f, _t)
- else:
- assert graph.instr_dependency(f, t) is None, \
- " it is expected that instruction at index" + \
- " %d DOES NOT depend on instr on index %d but it does" \
- % (f, t)
-
- def assert_def_use(self, graph, from_instr_index, to_instr_index = -1):
- if type(from_instr_index) == list:
- for f,t in from_instr_index:
- self.assert_def_use(graph, f, t)
- else:
- assert graph.instr_dependency(from_instr_index,
- to_instr_index) is not None, \
- " it is expected that instruction at index" + \
- " %d depends on instr on index %d but it is not" \
- % (from_instr_index, to_instr_index)
-
def assert_dependant(self, graph, edge_list):
""" Check if all dependencies are met. for complex cases
adding None instead of a list of integers skips the test.
@@ -53,17 +30,29 @@
for idx,edges in enumerate(edge_list):
if edges is None:
continue
- dependencies = graph.adjacent_list[idx]
+ dependencies = graph.adjacent_list[idx][:]
for edge in edges:
dependency = graph.instr_dependency(idx,edge)
+ if edge < idx:
+ dependency = graph.instr_dependency(edge, idx)
assert dependency is not None, \
" it is expected that instruction at index" + \
- " %d depends on instr on index %d but it is not" \
- % (idx, edge)
+ " %d depends on instr on index %d but it does not.\n%s" \
+ % (idx, edge, graph)
dependencies.remove(dependency)
assert dependencies == [], \
- "dependencies unexpected %s" \
- % dependencies
+ "dependencies unexpected %s.\n%s" \
+ % (dependencies,graph)
+ def assert_graph_equal(self, ga, gb):
+ assert len(ga.adjacent_list) == len(gb.adjacent_list)
+ for i in range(len(ga.adjacent_list)):
+ la = ga.adjacent_list[i]
+ lb = gb.adjacent_list[i]
+ assert len(la) == len(lb)
+ assert sorted([l.idx_to for l in la]) == \
+ sorted([l.idx_to for l in lb])
+ assert sorted([l.idx_from for l in la]) == \
+ sorted([l.idx_from for l in lb])
class BaseTestDependencyGraph(DepTestHelper):
def test_dependency_empty(self):
@@ -140,5 +129,111 @@
self.assert_dependant(dep_graph,
[ [1,2,3], [0,2], [1,0], [0] ])
+ def test_swap_dependencies(self):
+ ops = """
+ [i1,i4] # 0
+ i2 = int_lt(i1,0) # 1
+ i3 = int_lt(i4,0) # 2
+ guard_value(i2,0) [] # 3
+ jump(i1,i3) # 4
+ """
+ dep_graph = self.build_dependency(ops)
+ dep_graph.swap_instructions(1,2)
+ self.assert_dependant(dep_graph,
+ [ [1,2,4], [4,0], [3,0], [2], [0,1] ])
+ dep_graph.swap_instructions(1,2)
+ self.assert_graph_equal(dep_graph, self.build_dependency(ops))
+
+ dep_graph.swap_instructions(2,3)
+ ops2 = """
+ [i1,i4] # 0
+ i2 = int_lt(i1,0) # 1
+ guard_value(i2,0) [] # 2
+ i3 = int_lt(i4,0) # 3
+ jump(i1,i3) # 4
+ """
+ dep_graph_final = self.build_dependency(ops2)
+ self.assert_graph_equal(dep_graph, dep_graph_final)
+
+ def test_dependencies_1(self):
+ ops="""
+ [i0, i1, i2] # 0
+ i4 = int_gt(i1, 0) # 1
+ guard_true(i4) [] # 2
+ i6 = int_sub(i1, 1) # 3
+ i8 = int_gt(i6, 0) # 4
+ guard_false(i8) [] # 5
+ i10 = int_add(i2, 1) # 6
+ i12 = int_sub(i0, 1) # 7
+ i14 = int_add(i10, 1) # 8
+ i16 = int_gt(i12, 0) # 9
+ guard_true(i16) [] # 10
+ jump(i12, i1, i14) # 11
+ """
+ dep_graph = self.build_dependency(ops)
+ self.assert_dependant(dep_graph,
+ [ [1,3,6,7,11], [0,2], [1], [0,4], [3,5], [4],
+ # next entry is instr 6
+ [0,8], [0,9,11], [6,11], [7,10], [9], [7,0,8] ])
+
+ def test_prevent_double_arg(self):
+ ops="""
+ [i0, i1, i2]
+ i4 = int_gt(i1, i0)
+ guard_true(i4) []
+ jump(i0, i1, i2)
+ """
+ dep_graph = self.build_dependency(ops)
+ self.assert_dependant(dep_graph,
+ [ [1,3], [0,2], [1], [0] ])
+
+ def test_ovf_dep(self):
+ ops="""
+ [i0, i1, i2]
+ i4 = int_sub_ovf(1, 0)
+ guard_overflow() [i2]
+ jump(i0, i1, i2)
+ """
+ dep_graph = self.build_dependency(ops)
+ self.assert_dependant(dep_graph,
+ [ [1,2,3], [0,2], [0,1], [0] ])
+
+ def test_exception_dep(self):
+ ops="""
+ [p0, i1, i2]
+ i4 = call(p0, 1, descr=nonwritedescr)
+ guard_no_exception() []
+ jump(p0, i1, i2)
+ """
+ dep_graph = self.build_dependency(ops)
+ self.assert_dependant(dep_graph,
+ [ [1,3], [0,2], [1], [0] ])
+
+ def test_call_dependency_on_ptr_but_not_index_value(self):
+ ops="""
+ [p0, p1, i2]
+ i3 = int_add(i2,1)
+ i4 = call(p0, i3, descr=nonwritedescr)
+ guard_no_exception() [i2]
+ p2 = getarrayitem_gc(p1,i3)
+ jump(p2, p1, i3)
+ """
+ dep_graph = self.build_dependency(ops)
+ self.assert_dependant(dep_graph,
+ [ [1,2,3,4,5], [0,2,4,5], [0,1,3], [0,2], [0,1,5], [4,0,1] ])
+
+ def test_call_dependency(self):
+ ops="""
+ [p0, p1, i2, i5]
+ i3 = int_add(i2,1)
+ i4 = call(i5, i3, descr=nonwritedescr)
+ guard_no_exception() [i2]
+ p2 = getarrayitem_gc(p1,i3)
+ jump(p2, p1, i3)
+ """
+ dep_graph = self.build_dependency(ops)
+ self.assert_dependant(dep_graph,
+ [ [1,2,3,4,5], [0,2,4,5], [0,1,3], [0,2], [0,1,5], [4,0,1] ])
+
class TestLLtype(BaseTestDependencyGraph, LLtypeMixin):
pass
diff --git a/rpython/jit/metainterp/optimizeopt/test/test_vectorize.py b/rpython/jit/metainterp/optimizeopt/test/test_vectorize.py
--- a/rpython/jit/metainterp/optimizeopt/test/test_vectorize.py
+++ b/rpython/jit/metainterp/optimizeopt/test/test_vectorize.py
@@ -58,32 +58,18 @@
opt.loop.operations = opt.get_newoperations()
return opt
+ def init_pack_set(self, loop, unroll_factor = -1):
+ opt = self.vec_optimizer_unrolled(loop, unroll_factor)
+ opt.build_dependency_graph()
+ opt.find_adjacent_memory_refs()
+ opt.initialize_pack_set()
+ return opt
+
def assert_unroll_loop_equals(self, loop, expected_loop, \
unroll_factor = -1):
vec_optimizer = self.vec_optimizer_unrolled(loop, unroll_factor)
self.assert_equal(loop, expected_loop)
- def assert_no_edge(self, graph, f, t = -1):
- if type(f) == list:
- for _f,_t in f:
- self.assert_no_edge(graph, _f, _t)
- else:
- assert graph.instr_dependency(f, t) is None, \
- " it is expected that instruction at index" + \
- " %d DOES NOT depend on instr on index %d but it does" \
- % (f, t)
-
- def assert_def_use(self, graph, from_instr_index, to_instr_index = -1):
-
- if type(from_instr_index) == list:
- for f,t in from_instr_index:
- self.assert_def_use(graph, f, t)
- else:
- assert graph.instr_dependency(from_instr_index,
- to_instr_index) is not None, \
- " it is expected that instruction at index" + \
- " %d depends on instr on index %d but it is not" \
- % (from_instr_index, to_instr_index)
def assert_memory_ref_adjacent(self, m1, m2):
assert m1.is_adjacent_to(m2)
@@ -98,6 +84,29 @@
for i,op in enumerate(loop.operations):
print(i,op)
+ def assert_dependant(self, graph, edge_list):
+ """ Check if all dependencies are met. for complex cases
+ adding None instead of a list of integers skips the test.
+ This checks both if a dependency forward and backward exists.
+ """
+ assert len(edge_list) == len(graph.adjacent_list)
+ for idx,edges in enumerate(edge_list):
+ if edges is None:
+ continue
+ dependencies = graph.adjacent_list[idx][:]
+ for edge in edges:
+ dependency = graph.instr_dependency(idx,edge)
+ if edge < idx:
+ dependency = graph.instr_dependency(edge, idx)
+ assert dependency is not None, \
+ " it is expected that instruction at index" + \
+ " %d depends on instr on index %d but it does not.\n%s" \
+ % (idx, edge, graph)
+ dependencies.remove(dependency)
+ assert dependencies == [], \
+ "dependencies unexpected %s.\n%s" \
+ % (dependencies,graph)
+
class BaseTestVectorize(VecTestHelper):
def test_vectorize_skip_impossible_1(self):
@@ -246,9 +255,8 @@
"""
vopt = self.vec_optimizer_unrolled(self.parse_loop(ops),1)
vopt.build_dependency_graph()
- self.assert_no_edge(vopt.dependency_graph, [(i,i) for i in range(6)])
- self.assert_def_use(vopt.dependency_graph, [(0,1),(2,3),(4,5)])
- self.assert_no_edge(vopt.dependency_graph, [(0,4),(0,0)])
+ self.assert_dependant(vopt.dependency_graph,
+ [ [1,2,3,5], [0], [0,3,4], [0,2], [2,5], [0,4] ])
vopt.find_adjacent_memory_refs()
assert 1 in vopt.vec_info.memory_refs
@@ -514,6 +522,20 @@
vopt = self.vec_optimizer_unrolled(loop,1)
self.assert_equal(loop, self.parse_loop(ops))
+ def test_packset_init_simple(self):
+ ops = """
+ [p0,i0]
+ i3 = getarrayitem_gc(p0, i0, descr=chararraydescr)
+ i1 = int_add(i0, 1)
+ i2 = int_le(i1, 16)
+ guard_true(i2) [p0, i0]
+ jump(p0,i1)
+ """
+ loop = self.parse_loop(ops)
+ vopt = self.init_pack_set(loop,2)
+ assert vopt.pack_set is not None
+
+
class TestLLtype(BaseTestVectorize, LLtypeMixin):
pass
diff --git a/rpython/jit/metainterp/optimizeopt/vectorize.py b/rpython/jit/metainterp/optimizeopt/vectorize.py
--- a/rpython/jit/metainterp/optimizeopt/vectorize.py
+++ b/rpython/jit/metainterp/optimizeopt/vectorize.py
@@ -35,6 +35,7 @@
self.dependency_graph = None
self.first_debug_merge_point = False
self.last_debug_merge_point = None
+ self.pack_set = None
def emit_unrolled_operation(self, op):
if op.getopnum() == rop.DEBUG_MERGE_POINT:
@@ -189,13 +190,15 @@
self.find_adjacent_memory_refs()
def build_dependency_graph(self):
- self.dependency_graph = DependencyGraph(self.loop)
+ self.dependency_graph = DependencyGraph(self.loop.operations)
def find_adjacent_memory_refs(self):
""" the pre pass already builds a hash of memory references and the
- operations. Since it is in SSA form there is no array index. Indices
- are flattend. If there are two array accesses in the unrolled loop
- i0,i1 and i1 = int_add(i0,c), then i0 = i0 + 0, i1 = i0 + 1 """
+ operations. Since it is in SSA form there are no array indices.
+ If there are two array accesses in the unrolled loop
+ i0,i1 and i1 = int_add(i0,c), then i0 = i0 + 0, i1 = i0 + 1.
+ They are represented as a linear combination: i*c/d + e, i is a variable,
+ all others are integers that are calculated in reverse direction"""
loop = self.loop
operations = loop.operations
integral_mod = IntegralMod(self)
@@ -206,7 +209,8 @@
for dep in self.dependency_graph.instr_dependencies(opidx):
# this is a use, thus if dep is not a defintion
# it points back to the definition
- if memref.origin == dep.defined_arg and not dep.is_definition:
+ # if memref.origin == dep.defined_arg and not dep.is_definition:
+ if memref.origin in dep.args and not dep.is_definition:
# if is_definition is false the params is swapped
# idx_to attributes points to definer
def_op = operations[dep.idx_to]
@@ -227,6 +231,9 @@
else:
break
+ def init_pack_set(self):
+ self.pack_set = PackSet()
+
def vectorize_trace(self, loop):
""" Implementation of the algorithm introduced by Larsen. Refer to
'''Exploiting Superword Level Parallelism
@@ -382,6 +389,9 @@
default=LoopVectorizeInfo.default_operation)
LoopVectorizeInfo.inspect_operation = dispatch_opt
+class PackSet(object):
+ pass
+
class Pack(object):
""" A pack is a set of n statements that are:
* isomorphic
More information about the pypy-commit
mailing list