diff --git a/loopy/check.py b/loopy/check.py index 146391bf2533e35e7bc2f2091c9968fb5b321b6f..7a4139b4e0c89212ffa107bae627d916cd0910d2 100644 --- a/loopy/check.py +++ b/loopy/check.py @@ -400,9 +400,18 @@ class IndirectDependencyEdgeFinder(object): cache_key = (depender_id, dependee_id) try: - return self.dep_edge_cache[cache_key] + result = self.dep_edge_cache[cache_key] except KeyError: pass + else: + if result is None: + from loopy.diagnostic import DependencyCycleFound + raise DependencyCycleFound("when " + "checking for dependency edge between " + "depender '%s' and dependee '%s'" + % (depender_id, dependee_id)) + else: + return result depender = self.kernel.id_to_insn[depender_id] @@ -410,6 +419,7 @@ class IndirectDependencyEdgeFinder(object): self.dep_edge_cache[cache_key] = True return True + self.dep_edge_cache[cache_key] = None for dep in depender.depends_on: if self(dep, dependee_id): self.dep_edge_cache[cache_key] = True diff --git a/loopy/diagnostic.py b/loopy/diagnostic.py index 410b89d78c55285cbf29787d7e13749089fc8ed0..561bbc7cc56a8338593a80b7d5890553af89c79b 100644 --- a/loopy/diagnostic.py +++ b/loopy/diagnostic.py @@ -123,6 +123,10 @@ class ExpressionToAffineConversionError(LoopyError): class VariableAccessNotOrdered(LoopyError): pass + +class DependencyCycleFound(LoopyError): + pass + # }}} diff --git a/loopy/kernel/instruction.py b/loopy/kernel/instruction.py index 067e3de13331f30f915efb34c0dd5780ba4616ba..5c238ec28b9d7e2d063045e366ed724fcad4d46c 100644 --- a/loopy/kernel/instruction.py +++ b/loopy/kernel/instruction.py @@ -872,7 +872,8 @@ class Assignment(MultiAssignmentBase): result += " {%s}" % (": ".join(options)) if self.predicates: - result += "\n" + 10*" " + "if (%s)" % " && ".join(self.predicates) + result += "\n" + 10*" " + "if (%s)" % " and ".join( + str(p) for p in self.predicates) return result # {{{ for interface uniformity with CallInstruction diff --git a/loopy/kernel/tools.py b/loopy/kernel/tools.py index ec26916f35c2ec67fb43185ed5cbc911de271869..371758b3c214bf707421ad706f9e28043d2fc097 100644 --- a/loopy/kernel/tools.py +++ b/loopy/kernel/tools.py @@ -1205,9 +1205,11 @@ def draw_dependencies_as_unicode_arrows( instances :arg fore: if given, will be used like a :mod:`colorama` ``Fore`` object to color-code dependencies. (E.g. red for downward edges) - :returns: A list of tuples (arrows, extender) with Unicode-drawn dependency - arrows, one per entry of *instructions*. *extender* can be used to - extend arrows below the line of an instruction. + :returns: A tuple ``(uniform_length, rows)``, where *rows* is a list of + tuples (arrows, extender) with Unicode-drawn dependency arrows, one per + entry of *instructions*. *extender* can be used to extend arrows below the + line of an instruction. *uniform_length* is the length of the *arrows* and + *extender* strings. """ reverse_deps = {} @@ -1221,6 +1223,8 @@ def draw_dependencies_as_unicode_arrows( # {{{ find column assignments # mapping from column indices to (end_insn_ids, pointed_at_insn_id) + # end_insn_ids is a set that gets modified in-place to remove 'ends' + # (arrow origins) as they are being passed. columns_in_use = {} n_columns = [0] @@ -1258,8 +1262,10 @@ def draw_dependencies_as_unicode_arrows( rdeps = reverse_deps.get(insn.id, set()).copy() - processed_ids assert insn.id not in rdeps - if insn.id in dep_to_column: - columns_in_use[insn.id][0].update(rdeps) + col = dep_to_column.get(insn.id) + if col is not None: + columns_in_use[col][0].update(rdeps) + del col # }}} @@ -1315,7 +1321,11 @@ def draw_dependencies_as_unicode_arrows( dep_key = dep if dep_key not in dep_to_column: col = dep_to_column[dep_key] = find_free_column() - columns_in_use[col] = (set([insn.id]), dep) + + # No need to add current instruction to end_insn_ids set, as + # we're currently handling it. + columns_in_use[col] = (set(), dep) + row[col] = do_flag_downward(u"┌", dep) # }}} @@ -1340,12 +1350,30 @@ def draw_dependencies_as_unicode_arrows( added_ellipsis = [False] + def len_without_color_escapes(s): + s = (s + .replace(fore.RED, "") + .replace(style.RESET_ALL, "")) + return len(s) + + def truncate_without_color_escapes(s, l): + # FIXME: This is a bit dumb--it removes color escapes when truncation + # is needed. + + s = (s + .replace(fore.RED, "") + .replace(style.RESET_ALL, "")) + + return s[:l] + u"…" + def conform_to_uniform_length(s): - if len(s) <= uniform_length: - return s + " "*(uniform_length-len(s)) + len_s = len_without_color_escapes(s) + + if len_s <= uniform_length: + return s + " "*(uniform_length-len_s) else: added_ellipsis[0] = True - return s[:uniform_length] + u"…" + return truncate_without_color_escapes(s, uniform_length) rows = [ (conform_to_uniform_length(row), @@ -1355,10 +1383,10 @@ def draw_dependencies_as_unicode_arrows( if added_ellipsis[0]: uniform_length += 1 - rows = [ - (conform_to_uniform_length(row), - conform_to_uniform_length(extender)) - for row, extender in rows] + rows = [ + (conform_to_uniform_length(row), + conform_to_uniform_length(extender)) + for row, extender in rows] return uniform_length, rows diff --git a/test/test_loopy.py b/test/test_loopy.py index 7a6b8c8a6b21bc30f5f0146b2c8b2cb78c57f5b4..07179ae14b3a8e92c31a7718af3e0f6d8f16f22a 100644 --- a/test/test_loopy.py +++ b/test/test_loopy.py @@ -2869,6 +2869,42 @@ def test_half_complex_conditional(ctx_factory): knl(queue) +def test_dep_cycle_printing_and_error(): + # https://gitlab.tiker.net/inducer/loopy/issues/140 + # This kernel has two dep cycles. + + knl = lp.make_kernel('{[i,j,k]: 0 <= i,j,k < 12}', + """ + for j + for i + <> nu = i - 4 + if nu > 0 + <> P_val = a[i, j] {id=pset0} + else + P_val = 0.1 * a[i, j] {id=pset1} + end + <> B_sum = 0 + for k + B_sum = B_sum + k * P_val {id=bset, dep=pset*} + end + # here, we are testing that Kc is properly promoted to a vector dtype + <> Kc = P_val * B_sum {id=kset, dep=bset} + a[i, j] = Kc {dep=kset} + end + end + """, + [lp.GlobalArg('a', shape=(12, 12), dtype=np.int32)]) + + knl = lp.split_iname(knl, 'j', 4, inner_tag='vec') + knl = lp.split_array_axis(knl, 'a', 1, 4) + knl = lp.tag_array_axes(knl, 'a', 'N1,N0,vec') + knl = lp.preprocess_kernel(knl) + + from loopy.diagnostic import DependencyCycleFound + with pytest.raises(DependencyCycleFound): + print(lp.generate_code(knl)[0]) + + if __name__ == "__main__": if len(sys.argv) > 1: exec(sys.argv[1])