[pypy-commit] pypy vecopt2: extended the instruction packing. it now finds adjacent memory references and packs them into pairs in the packset
plan_rich
noreply at buildbot.pypy.org
Tue May 5 09:45:35 CEST 2015
Author: Richard Plangger <rich at pasra.at>
Branch: vecopt2
Changeset: r77086:333f5c08c25c
Date: 2015-03-24 13:37 +0100
http://bitbucket.org/pypy/pypy/changeset/333f5c08c25c/
Log: extended the instruction packing. it now finds adjacent memory
references and packs them into pairs in the packset
diff --git a/rpython/jit/backend/llgraph/runner.py b/rpython/jit/backend/llgraph/runner.py
--- a/rpython/jit/backend/llgraph/runner.py
+++ b/rpython/jit/backend/llgraph/runner.py
@@ -157,6 +157,10 @@
def is_array_of_pointers(self):
return getkind(self.A.OF) == 'ref'
+ def getflag(self):
+ from rpython.jit.backend.llsupport.descr import get_type_flag
+ return get_type_flag(self.A.OF)
+
def is_array_of_floats(self):
return getkind(self.A.OF) == 'float'
diff --git a/rpython/jit/backend/llsupport/descr.py b/rpython/jit/backend/llsupport/descr.py
--- a/rpython/jit/backend/llsupport/descr.py
+++ b/rpython/jit/backend/llsupport/descr.py
@@ -211,6 +211,9 @@
def get_item_size_in_bytes(self):
return self.itemsize
+ def get_flag(self):
+ return self.flag
+
def is_array_of_structs(self):
return self.flag == FLAG_STRUCT
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
@@ -171,7 +171,6 @@
def _put_edge(self, idx_from, idx_to, arg):
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)
@@ -199,6 +198,32 @@
edges = self.adjacent_list[idx]
return edges
+ def independant(self, ai, bi):
+ """ An instruction depends on another if there is a dependency path from
+ A to B. It is not enough to check only if A depends on B, because
+ due to transitive relations.
+ """
+ if ai == bi:
+ return True
+ if ai > bi:
+ ai, bi = bi, ai
+ stmt_indices = [bi]
+ while len(stmt_indices) > 0:
+ idx = stmt_indices.pop()
+ for dep in self.instr_dependencies(idx):
+ if idx < dep.idx_to:
+ # this dependency points downwards (thus unrelevant)
+ continue
+ if ai > dep.idx_from:
+ # this points above ai (thus unrelevant)
+ continue
+
+ if dep.idx_from == ai:
+ # dependant. There is a path from ai to bi
+ return False
+ stmt_indices.append(dep.idx_from)
+ return True
+
def definition_dependencies(self, idx):
deps = []
for dep in self.adjacent_list[idx]:
@@ -211,6 +236,8 @@
Returns None if there is no dependency or the Dependency object in
any other case.
"""
+ if from_instr_idx > to_instr_idx:
+ to_instr_idx, from_instr_idx = from_instr_idx, to_instr_idx
for edge in self.instr_dependencies(from_instr_idx):
if edge.idx_to == to_instr_idx:
return edge
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
@@ -10,7 +10,10 @@
def build_dependency(self, ops):
loop = self.parse_loop(ops)
- return DependencyGraph(loop.operations)
+ self.last_graph = DependencyGraph(loop.operations)
+ for i in range(len(self.last_graph.adjacent_list)):
+ self.assert_independent(i,i)
+ return self.last_graph
def parse_loop(self, ops):
loop = self.parse(ops, postprocess=self.postprocess)
@@ -21,7 +24,7 @@
loop.operations[-1].setdescr(token)
return loop
- def assert_dependant(self, graph, edge_list):
+ def assert_edges(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.
@@ -53,6 +56,11 @@
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])
+
+ def assert_independent(self, a, b):
+ assert self.last_graph.independant(a,b), "{a} and {b} are dependant!".format(a=a,b=b)
+ def assert_dependent(self, a, b):
+ assert not self.last_graph.independant(a,b), "{a} and {b} are independant!".format(a=a,b=b)
class BaseTestDependencyGraph(DepTestHelper):
def test_dependency_empty(self):
@@ -61,7 +69,7 @@
jump()
"""
dep_graph = self.build_dependency(ops)
- self.assert_dependant(dep_graph, [ [], [], ])
+ self.assert_edges(dep_graph, [ [], [], ])
def test_dependency_of_constant_not_used(self):
ops = """
@@ -70,7 +78,7 @@
jump()
"""
dep_graph = self.build_dependency(ops)
- self.assert_dependant(dep_graph, [ [], [], [] ])
+ self.assert_edges(dep_graph, [ [], [], [] ])
def test_dependency_simple(self):
ops = """
@@ -80,9 +88,16 @@
guard_value(i2,3) []
jump()
"""
- dep_graph = self.build_dependency(ops)
- self.assert_dependant(dep_graph,
+ graph = self.build_dependency(ops)
+ self.assert_edges(graph,
[ [], [2], [1,3], [2], [], ])
+ for i in range(0,5):
+ self.assert_independent(0,i)
+ self.assert_dependent(1,2)
+ self.assert_dependent(2,3)
+ self.assert_dependent(1,3)
+ self.assert_independent(2,4)
+ self.assert_independent(3,4)
def test_def_use_jump_use_def(self):
ops = """
@@ -92,7 +107,7 @@
jump(i1)
"""
dep_graph = self.build_dependency(ops)
- self.assert_dependant(dep_graph,
+ self.assert_edges(dep_graph,
[ [1], [0,2,3], [1], [1] ])
def test_dependency_guard(self):
@@ -103,7 +118,7 @@
jump(i3)
"""
dep_graph = self.build_dependency(ops)
- self.assert_dependant(dep_graph,
+ self.assert_edges(dep_graph,
[ [2,3], [2], [1,0], [0] ])
def test_no_edge_duplication(self):
@@ -115,7 +130,7 @@
jump(i3)
"""
dep_graph = self.build_dependency(ops)
- self.assert_dependant(dep_graph,
+ self.assert_edges(dep_graph,
[ [1,2,3], [0,2], [1,0], [0,4], [3] ])
def test_no_edge_duplication_in_guard_failargs(self):
@@ -126,8 +141,11 @@
jump(i1)
"""
dep_graph = self.build_dependency(ops)
- self.assert_dependant(dep_graph,
+ self.assert_edges(dep_graph,
[ [1,2,3], [0,2], [1,0], [0] ])
+ self.assert_dependent(0,1)
+ self.assert_dependent(0,2)
+ self.assert_dependent(0,3)
def test_swap_dependencies(self):
ops = """
@@ -139,7 +157,7 @@
"""
dep_graph = self.build_dependency(ops)
dep_graph.swap_instructions(1,2)
- self.assert_dependant(dep_graph,
+ self.assert_edges(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))
@@ -171,10 +189,13 @@
jump(i12, i1, i14) # 11
"""
dep_graph = self.build_dependency(ops)
- self.assert_dependant(dep_graph,
+ self.assert_edges(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] ])
+ self.assert_independent(6, 2)
+ self.assert_independent(6, 1)
+ self.assert_dependent(6, 0)
def test_prevent_double_arg(self):
ops="""
@@ -184,7 +205,7 @@
jump(i0, i1, i2)
"""
dep_graph = self.build_dependency(ops)
- self.assert_dependant(dep_graph,
+ self.assert_edges(dep_graph,
[ [1,3], [0,2], [1], [0] ])
def test_ovf_dep(self):
@@ -195,7 +216,7 @@
jump(i0, i1, i2)
"""
dep_graph = self.build_dependency(ops)
- self.assert_dependant(dep_graph,
+ self.assert_edges(dep_graph,
[ [1,2,3], [0,2], [0,1], [0] ])
def test_exception_dep(self):
@@ -206,7 +227,7 @@
jump(p0, i1, i2)
"""
dep_graph = self.build_dependency(ops)
- self.assert_dependant(dep_graph,
+ self.assert_edges(dep_graph,
[ [1,3], [0,2], [1], [0] ])
def test_call_dependency_on_ptr_but_not_index_value(self):
@@ -219,7 +240,7 @@
jump(p2, p1, i3)
"""
dep_graph = self.build_dependency(ops)
- self.assert_dependant(dep_graph,
+ self.assert_edges(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):
@@ -232,7 +253,7 @@
jump(p2, p1, i3)
"""
dep_graph = self.build_dependency(ops)
- self.assert_dependant(dep_graph,
+ self.assert_edges(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):
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
@@ -9,7 +9,7 @@
import rpython.jit.metainterp.optimizeopt.virtualize as virtualize
from rpython.jit.metainterp.optimizeopt.dependency import DependencyGraph
from rpython.jit.metainterp.optimizeopt.unroll import Inliner
-from rpython.jit.metainterp.optimizeopt.vectorize import VectorizingOptimizer, MemoryRef
+from rpython.jit.metainterp.optimizeopt.vectorize import VectorizingOptimizer, MemoryRef, isomorphic
from rpython.jit.metainterp.optimize import InvalidLoop
from rpython.jit.metainterp.history import ConstInt, BoxInt, get_const_ptr_for_string
from rpython.jit.metainterp import executor, compile, resume
@@ -62,7 +62,6 @@
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, \
@@ -84,7 +83,7 @@
for i,op in enumerate(loop.operations):
print(i,op)
- def assert_dependant(self, graph, edge_list):
+ def assert_edges(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.
@@ -255,7 +254,7 @@
"""
vopt = self.vec_optimizer_unrolled(self.parse_loop(ops),1)
vopt.build_dependency_graph()
- self.assert_dependant(vopt.dependency_graph,
+ self.assert_edges(vopt.dependency_graph,
[ [1,2,3,5], [0], [0,3,4], [0,2], [2,5], [0,4] ])
vopt.find_adjacent_memory_refs()
@@ -397,11 +396,27 @@
i6 = int_add(i4,1)
jump(p0,i1,i6)
"""
+ ops2 = """
+ [p0,i0,i4]
+ i3 = raw_load(p0,i0,descr=chararraydescr)
+ i1 = int_add(i0,1)
+ i5 = raw_load(p0,i4,descr=chararraydescr)
+ i6 = int_add(i4,1)
+ i3 = raw_load(p0,i1,descr=chararraydescr)
+ i8 = int_add(i1,1)
+ i9 = raw_load(p0,i6,descr=chararraydescr)
+ i7 = int_add(i6,1)
+ jump(p0,i8,i7)
+ """
+
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),(0,2),(0,3),(0,4),(2,5)])
- self.assert_no_edge(vopt.dependency_graph, [(1,3),(2,4)])
+ self.assert_edges(vopt.dependency_graph,
+ [ [1,2,3,4,5,7,9],
+ [0], [0,5,6], [0], [0,7,8],
+ [0,2], [2,9], [0,4], [4,9],
+ [0,6,8],
+ ])
vopt.find_adjacent_memory_refs()
@@ -532,10 +547,33 @@
jump(p0,i1)
"""
loop = self.parse_loop(ops)
- vopt = self.init_pack_set(loop,2)
+ vopt = self.init_pack_set(loop,1)
+ assert vopt.dependency_graph.independant(1,5)
assert vopt.pack_set is not None
+ assert len(vopt.vec_info.memory_refs) == 2
+ assert len(vopt.pack_set.packs) == 1
-
+ def test_isomorphic_operations(self):
+ ops_src = """
+ [p1,p0,i0]
+ i3 = getarrayitem_gc(p0, i0, descr=chararraydescr)
+ i1 = int_add(i0, 1)
+ i2 = int_le(i1, 16)
+ i4 = getarrayitem_gc(p0, i1, descr=chararraydescr)
+ i5 = getarrayitem_gc(p1, i1, descr=floatarraydescr)
+ i6 = getarrayitem_gc(p0, i1, descr=floatarraydescr)
+ guard_true(i2) [p0, i0]
+ jump(p1,p0,i1)
+ """
+ loop = self.parse_loop(ops_src)
+ ops = loop.operations
+ assert isomorphic(ops[1], ops[4])
+ assert not isomorphic(ops[0], ops[1])
+ assert not isomorphic(ops[0], ops[5])
+ assert not isomorphic(ops[4], ops[5])
+ assert not isomorphic(ops[5], ops[6])
+ assert not isomorphic(ops[4], ops[6])
+ assert not isomorphic(ops[1], ops[6])
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
@@ -210,11 +210,11 @@
# 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 in dep.args and not dep.is_definition:
+ if memref.origin in dep.args:
# if is_definition is false the params is swapped
- # idx_to attributes points to definer
- def_op = operations[dep.idx_to]
- opidx = dep.idx_to
+ # idx_to attributes points to define
+ def_op = operations[dep.idx_from]
+ opidx = dep.idx_from
break
else:
# this is an error in the dependency graph
@@ -231,8 +231,18 @@
else:
break
- def init_pack_set(self):
- self.pack_set = PackSet()
+ self.pack_set = PackSet(self.dependency_graph, operations)
+ memory_refs = self.vec_info.memory_refs.items()
+ # initialize the pack set
+ for a_opidx,a_memref in memory_refs:
+ for b_opidx,b_memref in memory_refs:
+ # instead of compare every possible combination and
+ # exclue a_opidx == b_opidx only consider the ones
+ # that point forward:
+ if a_opidx < b_opidx:
+ if a_memref.is_adjacent_to(b_memref):
+ if self.pack_set.can_be_packed(a_memref, b_memref):
+ self.pack_set.packs.append(Pair(a_memref, b_memref))
def vectorize_trace(self, loop):
""" Implementation of the algorithm introduced by Larsen. Refer to
@@ -251,6 +261,116 @@
# was not able to vectorize
return False
+def isomorphic(l_op, r_op):
+ """ Described in the paper ``Instruction-Isomorphism in Program Execution''.
+ I think this definition is to strict. TODO -> find another reference
+ For now it must have the same instruction type, the array parameter must be equal,
+ and it must be of the same type (both size in bytes and type of array)
+ .
+ """
+ if l_op.getopnum() == r_op.getopnum() and \
+ l_op.getarg(0) == r_op.getarg(0):
+ l_d = l_op.getdescr()
+ r_d = r_op.getdescr()
+ if l_d is not None and r_d is not None:
+ if l_d.get_item_size_in_bytes() == r_d.get_item_size_in_bytes():
+ if l_d.getflag() == r_d.getflag():
+ return True
+
+ elif l_d is None and r_d is None:
+ return True
+
+ return False
+
+class PackSet(object):
+
+ def __init__(self, dependency_graph, operations):
+ self.packs = []
+ self.dependency_graph = dependency_graph
+ self.operations = operations
+
+ def can_be_packed(self, lh_ref, rh_ref):
+ l_op = self.operations[lh_ref.op_idx]
+ r_op = self.operations[lh_ref.op_idx]
+ if isomorphic(l_op, r_op):
+ if self.dependency_graph.independant(lh_ref.op_idx, rh_ref.op_idx):
+ for pack in self.packs:
+ if pack.left == lh_ref or pack.right == rh_ref:
+ return False
+ return True
+ return False
+
+
+class Pack(object):
+ """ A pack is a set of n statements that are:
+ * isomorphic
+ * independant
+ Statements are named operations in the code.
+ """
+ def __init__(self, ops):
+ self.operations = ops
+
+class Pair(Pack):
+ """ A special Pack object with only two statements. """
+ def __init__(self, left, right):
+ assert isinstance(left, MemoryRef)
+ assert isinstance(right, MemoryRef)
+ self.left = left
+ self.right = right
+ Pack.__init__(self, [left, right])
+
+ def __eq__(self, other):
+ if isinstance(other, Pair):
+ return self.left == other.left and \
+ self.right == other.right
+
+class MemoryRef(object):
+ def __init__(self, op_idx, array, origin, descr):
+ self.op_idx = op_idx
+ self.array = array
+ self.origin = origin
+ self.descr = descr
+ self.coefficient_mul = 1
+ self.coefficient_div = 1
+ self.constant = 0
+
+ def is_adjacent_to(self, other):
+ """ this is a symmetric relation """
+ match, off = self.calc_difference(other)
+ if match:
+ return off == 1 or off == -1
+ return False
+
+ def is_adjacent_after(self, other):
+ """ the asymetric relation to is_adjacent_to """
+ match, off = self.calc_difference(other)
+ if match:
+ return off == 1
+ return False
+
+ def __eq__(self, other):
+ match, off = self.calc_difference(other)
+ if match:
+ return off == 0
+ return False
+
+ def __ne__(self, other):
+ return not self.__eq__(other)
+
+
+ def calc_difference(self, other):
+ if self.array == other.array \
+ and self.origin == other.origin:
+ mycoeff = self.coefficient_mul // self.coefficient_div
+ othercoeff = other.coefficient_mul // other.coefficient_div
+ diff = other.constant - self.constant
+ return mycoeff == othercoeff, diff
+ return False, 0
+
+ def __repr__(self):
+ return 'MemoryRef(%s*(%s/%s)+%s)' % (self.origin, self.coefficient_mul,
+ self.coefficient_div, self.constant)
+
class IntegralMod(object):
""" Calculates integral modifications on an integer object.
The operations must be provided in backwards direction and of one
@@ -268,26 +388,9 @@
self.constant = 0
self.used_box = None
- def operation_INT_SUB(self, op):
- box_a0 = op.getarg(0)
- box_a1 = op.getarg(1)
- a0 = self.optimizer.getvalue(box_a0)
- a1 = self.optimizer.getvalue(box_a1)
- self.is_const_mod = True
- if a0.is_constant() and a1.is_constant():
- raise NotImplementedError()
- elif a0.is_constant():
- self.constant -= box_a0.getint() * self.coefficient_mul
- self.used_box = box_a1
- elif a1.is_constant():
- self.constant -= box_a1.getint() * self.coefficient_mul
- self.used_box = box_a0
- else:
- self.is_const_mod = False
-
def _update_additive(self, i):
return (i * self.coefficient_mul) / self.coefficient_div
-
+
additive_func_source = """
def operation_{name}(self, op):
box_a0 = op.getarg(0)
@@ -371,17 +474,23 @@
self.memory_refs = {}
self.track_memory_refs = False
- def operation_RAW_LOAD(self, op):
+ array_access_source = """
+ def operation_{name}(self, op):
descr = op.getdescr()
if self.track_memory_refs:
idx = len(self.optimizer._newoperations)-1
self.memory_refs[idx] = \
- MemoryRef(op.getarg(0), op.getarg(1), op.getdescr())
+ MemoryRef(idx, op.getarg(0), op.getarg(1), op.getdescr())
if not descr.is_array_of_pointers():
byte_count = descr.get_item_size_in_bytes()
if self.smallest_type_bytes == 0 \
or byte_count < self.smallest_type_bytes:
self.smallest_type_bytes = byte_count
+ """
+ exec py.code.Source(array_access_source.format(name='RAW_LOAD')).compile()
+ exec py.code.Source(array_access_source.format(name='GETARRAYITEM_GC')).compile()
+ exec py.code.Source(array_access_source.format(name='GETARRAYITEM_RAW')).compile()
+ del array_access_source
def default_operation(self, operation):
pass
@@ -389,70 +498,3 @@
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
- * independant
- Statements are named operations in the code.
- """
- def __init__(self, ops):
- self.operations = ops
-
-class Pair(Pack):
- """ A special Pack object with only two statements. """
- def __init__(self, left_op, right_op):
- assert isinstance(left_op, rop.ResOperation)
- assert isinstance(right_op, rop.ResOperation)
- self.left_op = left_op
- self.right_op = right_op
- Pack.__init__(self, [left_op, right_op])
-
-
-class MemoryRef(object):
- def __init__(self, array, origin, descr):
- self.array = array
- self.origin = origin
- self.descr = descr
- self.coefficient_mul = 1
- self.coefficient_div = 1
- self.constant = 0
-
- def is_adjacent_to(self, other):
- """ this is a symmetric relation """
- match, off = self.calc_difference(other)
- if match:
- return off == 1 or off == -1
- return False
-
- def is_adjacent_after(self, other):
- """ the asymetric relation to is_adjacent_to """
- match, off = self.calc_difference(other)
- if match:
- return off == 1
- return False
-
- def __eq__(self, other):
- match, off = self.calc_difference(other)
- if match:
- return off == 0
- return False
-
- def __ne__(self, other):
- return not self.__eq__(other)
-
-
- def calc_difference(self, other):
- if self.array == other.array \
- and self.origin == other.origin:
- mycoeff = self.coefficient_mul // self.coefficient_div
- othercoeff = other.coefficient_mul // other.coefficient_div
- diff = other.constant - self.constant
- return mycoeff == othercoeff, diff
- return False, 0
-
- def __repr__(self):
- return 'MemoryRef(%s*(%s/%s)+%s)' % (self.origin, self.coefficient_mul,
- self.coefficient_div, self.constant)
More information about the pypy-commit
mailing list