diff --git a/src/c3py/bad_pattern.py b/src/c3py/bad_pattern.py new file mode 100644 index 0000000..ea96677 --- /dev/null +++ b/src/c3py/bad_pattern.py @@ -0,0 +1,237 @@ +from copy import deepcopy +from enum import Enum +from typing import Any, NamedTuple, Self + +import networkx as nx + +from c3py.history import History, Operation + + +class BadPattern(Enum): + CyclicCO = 1 + WriteCOInitRead = 2 + ThinAirRead = 3 + WriteCORead = 4 + CyclicCF = 5 + WriteHBInitRead = 6 + CyclicHB = 7 + + +class WRMemoryHistory(History): + def __init__(self, data): + super().__init__(data) + assert self.check_differentiated_h() + self.writes = dict[tuple[Any, Any], str]() + self.write_read_relations = dict[str, str]() + self.poset, self.bp = self.make_co() + + self.process_last_op = dict[str, str]() + for process, ops in data.items(): + self.process_last_op[process] = f"{process}.{ops[-1]}" + + self.hb: dict[list[tuple[str, str]]] = self.make_hb() + + def check_differentiated_h(self) -> bool: + wr = set[Operation.arg]() + for id, op in self.label.items(): + if op.method == "wr": + if op.arg in wr: + return False + wr.add(op.arg) + return True + + def make_co(self) -> tuple[Self, BadPattern]: + """ + :returns + tuple[self.poset, BadPattern] + + co = (po U wr)^+ + po is checked in History.__init__ + so just check wr here + """ + for id, op in self.label.items(): + if op.method == "wr": + self.writes[op.arg] = id + + cp = deepcopy(self.poset) + for id, op in self.label.items(): + if op.method == "rd" and op.ret is not None: + src = self.writes.get((op.arg, op.ret)) + if not src: + return cp, BadPattern.ThinAirRead + cp.link(src, id) + self.write_read_relations[src] = id + + if not nx.is_directed_acyclic_graph(cp.G): + return cp, BadPattern.CyclicCO + + # ensure transitivity of poset + tc = nx.transitive_closure(cp.G, reflexive=False) + missing_es = tc.edges() - cp.G.edges() + for e in missing_es: + cp.link(e[0], e[1]) + + return cp, None + + def make_hb(self) -> dict[list[tuple[str, str]]]: + hb = dict[list[tuple[str, str]]]() + for process, _ in self.process_last_op.items(): + hb[process] = [] + + for wr, rd in self.write_read_relations.items(): + rd_process = rd[0] + v = self.label[rd].arg + rd_ret = self.label[rd].ret + + r_anc = nx.ancestors(self.poset.G, rd) + for id in r_anc: + op = self.label[id] + if op.method == "wr" and op.arg[0] == v and op.arg[1] != rd_ret: + hb[rd_process].append((id, wr)) + + return hb + + def is_write_co_init_read(self) -> bool: + """ + This method has to be preceded by self.make_co + :returns True if WriteCoInitRead + """ + rd_init: list(tuple[str, Operation]) = [] # (id, Operation) + for id, op in self.label.items(): + if op.method == "rd" and op.ret is None: + rd_init.append((id, op)) + + for id1, op1 in rd_init: + anc = nx.ancestors(self.poset.G, id1) + for id2 in anc: + op2 = self.label[id2] + if op2.method == "wr" and op2.arg[0] == op1.arg: + return True + + return False + + def is_write_co_read(self) -> bool: + # ToDo do this in make_co + wr = dict[tuple[Any, Any], str]() + for id, op in self.label.items(): + if op.method == "wr": + wr[op.arg] = id + + wr_relations = list[ + tuple[str, str, str] + ]() # [(var, wr1, rd1), (var, wr2, rd2), ...] + for id, op in self.label.items(): + if op.method == "rd": + src = wr.get((op.arg, op.ret)) + if not src: + continue + wr_relations.append((op.arg, src, id)) + + for v, wr, rd in wr_relations: + wr_des = nx.descendants(self.poset.G, wr) + rd_anc = nx.ancestors(self.poset.G, rd) + intermediates = wr_des.intersection(rd_anc) + for i in intermediates: + if self.label[i].method == "wr" and self.label[i].arg[0] == v: + return True + + return False + + def is_cyclic_cf(self) -> bool: + cf = list[tuple[str, str]]() + for w, r in self.write_read_relations.items(): + v = self.label[r].arg + r_anc = nx.ancestors(self.poset.G, r) + for id in r_anc: + if id == w: + continue + op = self.label[id] + if op.method == "wr" and op.arg[0] == v: + cf.append((id, w)) + + cp = deepcopy(self.poset) + for s, d in cf: + cp.link(s, d) + + return not nx.is_directed_acyclic_graph(cp.G) + + def is_write_hb_init_read(self) -> bool: + init_rds = list[Operation]() + for id, op in self.label.items(): + if op.method == "rd" and not op.ret: + init_rds.append(op) + + for op in init_rds: + process = op.op_id[0] + cp = deepcopy(self.poset) + for s, d in self.hb[process]: + cp.link(s, d) + + anc_rd = nx.ancestors(cp.G, op.op_id) + for id in anc_rd: + op1 = self.label[id] + if op1.method == "wr" and op1.arg[0] == op.arg: + return True + + return False + + def is_cyclic_hb(self) -> bool: + for _, hbo in self.hb.items(): + cp = deepcopy(self.poset) + for tup in hbo: + cp.link(tup[0], tup[1]) + if not nx.is_directed_acyclic_graph(cp.G): + return True + + return False + + +class CCBPResult(NamedTuple): + is_CC: bool + bad_pattern: BadPattern + + +def find_cc_bad_pattern(h: WRMemoryHistory) -> CCBPResult: + if h.bp: + return CCBPResult(False, h.bp) + if h.is_write_co_init_read(): + return CCBPResult(False, BadPattern.WriteCOInitRead) + if h.is_write_co_read(): + return CCBPResult(False, BadPattern.WriteCORead) + return CCBPResult(True, None) + + +class CCvBPResult(NamedTuple): + is_CCv: bool + bad_pattern: BadPattern + + +def find_ccv_bad_pattern(h: WRMemoryHistory) -> CCvBPResult: + if h.bp: + return CCvBPResult(False, h.bp) + if h.is_write_co_init_read(): + return CCvBPResult(False, BadPattern.WriteCOInitRead) + if h.is_write_co_read(): + return CCvBPResult(False, BadPattern.WriteCORead) + if h.is_cyclic_cf(): + return CCvBPResult(False, BadPattern.CyclicCF) + return CCvBPResult(True, None) + + +class CMBPResult(NamedTuple): + is_CM: bool + bad_pattern: BadPattern + + +def find_cm_bad_pattern(h: WRMemoryHistory) -> CMBPResult: + if h.bp: + return CMBPResult(False, h.bp) + if h.is_write_co_init_read(): + return CMBPResult(False, BadPattern.WriteCOInitRead) + if h.is_write_co_read(): + return CMBPResult(False, BadPattern.WriteCORead) + if h.is_write_hb_init_read(): + return CMBPResult(False, BadPattern.WriteHBInitRead) + if h.is_cyclic_hb(): + return CMBPResult(False, BadPattern.CyclicHB) + return CMBPResult(True, None) diff --git a/src/c3py/bad_pattern_test.py b/src/c3py/bad_pattern_test.py new file mode 100644 index 0000000..c09e68b --- /dev/null +++ b/src/c3py/bad_pattern_test.py @@ -0,0 +1,255 @@ +from c3py.bad_pattern import ( + BadPattern, + WRMemoryHistory, + find_cc_bad_pattern, + find_ccv_bad_pattern, + find_cm_bad_pattern, +) +from c3py.history import Operation + + +class TestWRMemoryHistory: + def make_wrhistory_a(self): + h = WRMemoryHistory( + { + "a": [Operation("wr", ("x", 1)), Operation("rd", "x", 2)], + "b": [Operation("wr", ("x", 2)), Operation("rd", "x", 1)], + } + ) + return h + + def make_wrhistory_b(self): + h = WRMemoryHistory( + { + "a": [ + Operation("wr", ("z", 1)), + Operation("wr", ("x", 1)), + Operation("wr", ("y", 1)), + ], + "b": [ + Operation("wr", ("x", 2)), + Operation("rd", "z", None), # default value (0 in paper) + Operation("rd", "y", 1), + Operation("rd", "x", 2), + ], + } + ) + return h + + def make_wrhistory_c(self): + h = WRMemoryHistory( + { + "a": [Operation("wr", ("x", 1))], + "b": [ + Operation("wr", ("x", 2)), + Operation("rd", "x", 1), + Operation("rd", "x", 2), + ], + } + ) + return h + + def make_wrhistory_d(self): + h = WRMemoryHistory( + { + "a": [ + Operation("wr", ("x", 1)), + Operation("rd", "y", None), # default value + Operation("wr", ("y", 1)), + Operation("rd", "x", 1), + ], + "b": [ + Operation("wr", ("x", 2)), + Operation("rd", "y", None), # default value + Operation("wr", ("y", 2)), + Operation("rd", "x", 2), + ], + } + ) + return h + + def make_wrhistory_e(self): + h = WRMemoryHistory( + { + "a": [Operation("wr", ("x", 1)), Operation("wr", ("y", 1))], + "b": [Operation("rd", "y", 1), Operation("wr", ("x", 2))], + "c": [ + Operation("rd", "x", 2), + Operation("rd", "x", 1), + ], + } + ) + return h + + def test_check_differentiated_h(self): + h_b = self.make_wrhistory_b() + assert h_b.check_differentiated_h() + + # Todo + not_diff_h = WRMemoryHistory( + { + "a": [Operation("wr", ("x", 1)), Operation("rd", "x", 2)], + "b": [Operation("wr", ("x", 1)), Operation("rd", "x", 1)], + } + ) + assert not not_diff_h.check_differentiated_h() + + def test_make_co_a(self): + h = self.make_wrhistory_a() + assert not h.bp + assert h != BadPattern.CyclicCO + assert h != BadPattern.ThinAirRead + correct_co = set( + ( + ("a.1", "a.2"), + ("b.1", "b.2"), + ("a.1", "b.2"), + ("b.1", "a.2"), + ) + ) + assert h.poset.G.edges() == correct_co + + def test_make_co_b(self): + h = self.make_wrhistory_b() + assert not h.bp + assert h != BadPattern.CyclicCO + assert h != BadPattern.ThinAirRead + correct_co = set( + ( + ("a.1", "a.2"), + ("a.1", "a.3"), + ("a.1", "b.3"), + ("a.1", "b.4"), + ("a.2", "a.3"), + ("a.2", "b.3"), + ("a.2", "b.4"), + ("a.3", "b.3"), + ("a.3", "b.4"), + ("b.1", "b.2"), + ("b.1", "b.3"), + ("b.1", "b.4"), + ("b.2", "b.3"), + ("b.2", "b.4"), + ("b.3", "b.4"), + ) + ) + assert h.poset.G.edges() == correct_co + + def test_make_co_cyclic(self): + h = WRMemoryHistory( + { + "a": [Operation("rd", "x", 1), Operation("wr", ("x", 1))], + "b": [Operation("wr", ("x", 2)), Operation("rd", "x", 2)], + } + ) + assert h.bp == BadPattern.CyclicCO + + def test_make_co_thin_air_read(self): + h = WRMemoryHistory( + { + "a": [Operation("wr", ("x", 1)), Operation("rd", "x", 2)], + "b": [Operation("wr", ("x", 2)), Operation("rd", "y", 1)], + } + ) + assert h.bp == BadPattern.ThinAirRead + + def test_is_write_co_init_read_false(self): + h_b = self.make_wrhistory_b() + assert not h_b.is_write_co_init_read() + + def test_write_co_init_read(self): + h = WRMemoryHistory( + {"a": [Operation("wr", ("x", 1)), Operation("rd", "x", None)]} + ) + assert h.is_write_co_init_read() + + def test_is_write_co_read_true(self): + h = self.make_wrhistory_e() + assert h.is_write_co_read() + + def test_is_write_co_read_false(self): + h = self.make_wrhistory_a() + assert not h.is_write_co_read() + + def test_cc_bad_pattern_a(self): + h = self.make_wrhistory_a() + assert h.check_differentiated_h() + res = find_cc_bad_pattern(h) + assert res.is_CC + assert not res.bad_pattern + + def test_cc_bad_pattern_b(self): + h = self.make_wrhistory_b() + assert h.check_differentiated_h() + res = find_cc_bad_pattern(h) + assert res.is_CC + assert not res.bad_pattern + + def test_cc_bad_pattern_c(self): + h = self.make_wrhistory_c() + assert h.check_differentiated_h() + res = find_cc_bad_pattern(h) + assert res.is_CC + assert not res.bad_pattern + + def test_cc_bad_pattern_d(self): + h = self.make_wrhistory_d() + assert h.check_differentiated_h() + res = find_cc_bad_pattern(h) + assert res.is_CC + assert not res.bad_pattern + + def test_cc_bad_pattern_e(self): + h = self.make_wrhistory_e() + assert h.check_differentiated_h() + res = find_cc_bad_pattern(h) + assert not res.is_CC + assert res.bad_pattern == BadPattern.WriteCORead + + def test_ccv_bad_pattern_a(self): + h = self.make_wrhistory_a() + res = find_ccv_bad_pattern(h) + assert not res.is_CCv + assert res.bad_pattern == BadPattern.CyclicCF + + def test_ccv_bad_pattern_b(self): + h = self.make_wrhistory_b() + res = find_ccv_bad_pattern(h) + assert res.is_CCv + assert not res.bad_pattern + + def test_ccv_bad_pattern_c(self): + h = self.make_wrhistory_c() + res = find_ccv_bad_pattern(h) + assert not res.is_CCv + assert res.bad_pattern == BadPattern.CyclicCF + + def test_ccv_bad_pattern_d(self): + h = self.make_wrhistory_d() + res = find_ccv_bad_pattern(h) + assert res.is_CCv + assert not res.bad_pattern + + def test_cm_bad_pattern_a(self): + h = self.make_wrhistory_a() + res = find_cm_bad_pattern(h) + assert res.is_CM + assert not res.bad_pattern + + def test_cm_bad_pattern_b(self): + h = self.make_wrhistory_b() + res = find_cm_bad_pattern(h) + assert not res.is_CM + assert res.bad_pattern == BadPattern.WriteHBInitRead + + def test_cm_bad_pattern_c(self): + h = self.make_wrhistory_c() + res = find_cm_bad_pattern(h) + assert not res.is_CM + assert res.bad_pattern == BadPattern.CyclicHB + + def test_cm_bad_pattern_d(self): + h = self.make_wrhistory_d() + res = find_cm_bad_pattern(h) + assert res.is_CM + assert not res.bad_pattern