def __init__(self, annotator): self.annotator = annotator self.policy = annotator.policy self.descs = {} # map Python objects to their XxxDesc wrappers self.methoddescs = {} # map (funcdesc, classdef) to the MethodDesc self.classdefs = [] # list of all ClassDefs self.pbctypes = {} self.seen_mutable = {} self.listdefs = {} # map position_keys to ListDefs self.dictdefs = {} # map position_keys to DictDefs self.immutable_cache = {} self.classpbc_attr_families = { } # {'attr': UnionFind(ClassAttrFamily)} self.frozenpbc_attr_families = UnionFind(description.FrozenAttrFamily) self.pbc_maximal_call_families = UnionFind(description.CallFamily) self.emulated_pbc_calls = {} self.all_specializations = {} # {FuncDesc: specialization-info} self.pending_specializations = [] # list of callbacks self.external_class_cache = {} # cache of ExternalType classes self.needs_generic_instantiate = {} self.stats = Stats(self) # used in SomeObject.__new__ for keeping debugging info self._isomeobject_coming_from = identity_dict() delayed_imports()
def memo(funcdesc, arglist_s): from pypy.annotation.model import SomePBC, SomeImpossibleValue, SomeBool from pypy.annotation.model import unionof # call the function now, and collect possible results argvalues = [] for s in arglist_s: if s.is_constant(): values = [s.const] elif isinstance(s, SomePBC): values = [] assert not s.can_be_None, "memo call: cannot mix None and PBCs" for desc in s.descriptions: if desc.pyobj is None: raise Exception( "memo call with a class or PBC that has no " "corresponding Python object (%r)" % (desc, )) values.append(desc.pyobj) elif isinstance(s, SomeImpossibleValue): return s # we will probably get more possible args later elif isinstance(s, SomeBool): values = [False, True] else: raise Exception("memo call: argument must be a class or a frozen " "PBC, got %r" % (s, )) argvalues.append(values) # the list of all possible tuples of arguments to give to the memo function possiblevalues = cartesian_product(argvalues) # a MemoTable factory -- one MemoTable per family of arguments that can # be called together, merged via a UnionFind. bookkeeper = funcdesc.bookkeeper try: memotables = bookkeeper.all_specializations[funcdesc] except KeyError: func = funcdesc.pyobj if func is None: raise Exception("memo call: no Python function object to call " "(%r)" % (funcdesc, )) def compute_one_result(args): value = func(*args) memotable = MemoTable(funcdesc, args, value) memotable.register_finish() return memotable memotables = UnionFind(compute_one_result) bookkeeper.all_specializations[funcdesc] = memotables # merge the MemoTables for the individual argument combinations firstvalues = possiblevalues.next() _, _, memotable = memotables.find(firstvalues) for values in possiblevalues: _, _, memotable = memotables.union(firstvalues, values) if memotable.graph is not None: return memotable.graph # if already computed else: # otherwise, for now, return the union of each possible result return unionof( *[bookkeeper.immutablevalue(v) for v in memotable.table.values()])
def memo(funcdesc, arglist_s): from pypy.annotation.model import SomePBC, SomeImpossibleValue, SomeBool from pypy.annotation.model import unionof # call the function now, and collect possible results argvalues = [] for s in arglist_s: if s.is_constant(): values = [s.const] elif isinstance(s, SomePBC): values = [] assert not s.can_be_None, "memo call: cannot mix None and PBCs" for desc in s.descriptions: if desc.pyobj is None: raise Exception("memo call with a class or PBC that has no " "corresponding Python object (%r)" % (desc,)) values.append(desc.pyobj) elif isinstance(s, SomeImpossibleValue): return s # we will probably get more possible args later elif isinstance(s, SomeBool): values = [False, True] else: raise Exception("memo call: argument must be a class or a frozen " "PBC, got %r" % (s,)) argvalues.append(values) # the list of all possible tuples of arguments to give to the memo function possiblevalues = cartesian_product(argvalues) # a MemoTable factory -- one MemoTable per family of arguments that can # be called together, merged via a UnionFind. bookkeeper = funcdesc.bookkeeper try: memotables = bookkeeper.all_specializations[funcdesc] except KeyError: func = funcdesc.pyobj if func is None: raise Exception("memo call: no Python function object to call " "(%r)" % (funcdesc,)) def compute_one_result(args): value = func(*args) memotable = MemoTable(funcdesc, args, value) memotable.register_finish() return memotable memotables = UnionFind(compute_one_result) bookkeeper.all_specializations[funcdesc] = memotables # merge the MemoTables for the individual argument combinations firstvalues = possiblevalues.next() _, _, memotable = memotables.find(firstvalues) for values in possiblevalues: _, _, memotable = memotables.union(firstvalues, values) if memotable.graph is not None: return memotable.graph # if already computed else: # otherwise, for now, return the union of each possible result return unionof(*[bookkeeper.immutablevalue(v) for v in memotable.table.values()])
def __init__(self, hannotator): self.pending_specializations = [] self.originflags = {} self.virtual_containers = {} self.descs = {} self.tsgraph_maximal_call_families = UnionFind(TsGraphCallFamily) self.annotator = hannotator self.tsgraphsigs = {} self.nonstuboriggraphcount = 0 self.stuboriggraphcount = 0 if hannotator is not None: # for tests t = hannotator.base_translator self.impurity_analyzer = ImpurityAnalyzer(t) # circular imports hack global hintmodel from pypy.jit.hintannotator import model as hintmodel
def coalesce_variables(self): self._unionfind = UnionFind() pendingblocks = list(self.graph.iterblocks()) while pendingblocks: block = pendingblocks.pop() # Aggressively try to coalesce each source variable with its # target. We start from the end of the graph instead of # from the beginning. This is a bit arbitrary, but the idea # is that the end of the graph runs typically more often # than the start, given that we resume execution from the # middle during blackholing. for link in block.exits: if link.last_exception is not None: self._depgraph.add_node(link.last_exception) if link.last_exc_value is not None: self._depgraph.add_node(link.last_exc_value) for i, v in enumerate(link.args): self._try_coalesce(v, link.target.inputargs[i])
def get_classpbc_attr_families(self, attrname): """Return the UnionFind for the ClassAttrFamilies corresponding to attributes of the given name. """ map = self.classpbc_attr_families try: access_sets = map[attrname] except KeyError: access_sets = map[attrname] = UnionFind(description.ClassAttrFamily) return access_sets
def test_cleanup(): state = [] class ReferencedByExternalState(object): def __init__(self, obj): state.append(self) self.obj = obj def absorb(self, other): state.remove(other) uf = UnionFind(ReferencedByExternalState) uf.find(1) for i in xrange(1, 10, 2): uf.union(i, 1) uf.find(2) for i in xrange(2, 20, 2): uf.union(i, 2) assert len(state) == 2 # we have exactly 2 partitions
def __init__(self, graph): # Build a list of "unification opportunities": for each block and each # 'n', an "opportunity" groups the block's nth input variable with # the nth output variable from each of the incoming links, in a list: # [Block, blockvar, linkvar, linkvar, linkvar...] opportunities = [] opportunities_with_const = [] for block, links in mkinsideentrymap(graph).items(): assert links for n, inputvar in enumerate(block.inputargs): vars = [block, inputvar] put_in = opportunities for link in links: var = link.args[n] if not isinstance(var, Variable): put_in = opportunities_with_const vars.append(var) # if any link provides a Constant, record this in # the opportunities_with_const list instead put_in.append(vars) self.opportunities = opportunities self.opportunities_with_const = opportunities_with_const self.variable_families = UnionFind()
def compute_lifetimes(self, graph): """Compute the static data flow of the graph: returns a list of LifeTime instances, each of which corresponds to a set of Variables from the graph. The variables are grouped in the same LifeTime if a value can pass from one to the other by following the links. Each LifeTime also records all places where a Variable in the set is used (read) or build (created). """ lifetimes = UnionFind(LifeTime) def set_creation_point(block, var, *cp): _, _, info = lifetimes.find((block, var)) info.creationpoints[cp] = True def set_use_point(block, var, *up): _, _, info = lifetimes.find((block, var)) info.usepoints[up] = True def union(block1, var1, block2, var2): if isinstance(var1, Variable): lifetimes.union((block1, var1), (block2, var2)) elif isinstance(var1, Constant): set_creation_point(block2, var2, "constant", var1) else: raise TypeError(var1) for var in graph.startblock.inputargs: set_creation_point(graph.startblock, var, "inputargs") set_use_point(graph.returnblock, graph.returnblock.inputargs[0], "return") set_use_point(graph.exceptblock, graph.exceptblock.inputargs[0], "except") set_use_point(graph.exceptblock, graph.exceptblock.inputargs[1], "except") def visit(node): if isinstance(node, Block): for op in node.operations: if op.opname in self.IDENTITY_OPS: # special-case these operations to identify their input # and output variables union(node, op.args[0], node, op.result) continue if op.opname in self.SUBSTRUCT_OPS: if self.visit_substruct_op(node, union, op): continue for i in range(len(op.args)): if isinstance(op.args[i], Variable): set_use_point(node, op.args[i], "op", node, op, i) set_creation_point(node, op.result, "op", node, op) if isinstance(node.exitswitch, Variable): set_use_point(node, node.exitswitch, "exitswitch", node) if isinstance(node, Link): if isinstance(node.last_exception, Variable): set_creation_point(node.prevblock, node.last_exception, "last_exception") if isinstance(node.last_exc_value, Variable): set_creation_point(node.prevblock, node.last_exc_value, "last_exc_value") d = {} for i, arg in enumerate(node.args): union(node.prevblock, arg, node.target, node.target.inputargs[i]) if isinstance(arg, Variable): if arg in d: # same variable present several times in link.args # consider it as a 'use' of the variable, which # will disable malloc optimization (aliasing problems) set_use_point(node.prevblock, arg, "dup", node, i) else: d[arg] = True traverse(visit, graph) return lifetimes.infos()
class RegAllocator(object): DEBUG_REGALLOC = False def __init__(self, graph, kind): self.graph = graph self.kind = kind def make_dependencies(self): dg = DependencyGraph() for block in self.graph.iterblocks(): # Compute die_at = {Variable: index_of_operation_with_last_usage} die_at = dict.fromkeys(block.inputargs, 0) for i, op in enumerate(block.operations): for v in op.args: if isinstance(v, Variable): die_at[v] = i elif isinstance(v, ListOfKind): for v1 in v: if isinstance(v1, Variable): die_at[v1] = i if op.result is not None: die_at[op.result] = i + 1 if isinstance(block.exitswitch, tuple): for x in block.exitswitch: die_at.pop(x, None) else: die_at.pop(block.exitswitch, None) for link in block.exits: for v in link.args: die_at.pop(v, None) die_at = [(value, key) for (key, value) in die_at.items()] die_at.sort() die_at.append((sys.maxint,)) # Done. XXX the code above this line runs 3 times # (for kind in KINDS) to produce the same result... livevars = [v for v in block.inputargs if getkind(v.concretetype) == self.kind] # Add the variables of this block to the dependency graph for i, v in enumerate(livevars): dg.add_node(v) for j in range(i): dg.add_edge(livevars[j], v) livevars = set(livevars) die_index = 0 for i, op in enumerate(block.operations): while die_at[die_index][0] == i: try: livevars.remove(die_at[die_index][1]) except KeyError: pass die_index += 1 if (op.result is not None and getkind(op.result.concretetype) == self.kind): dg.add_node(op.result) for v in livevars: if getkind(v.concretetype) == self.kind: dg.add_edge(v, op.result) livevars.add(op.result) self._depgraph = dg def coalesce_variables(self): self._unionfind = UnionFind() pendingblocks = list(self.graph.iterblocks()) while pendingblocks: block = pendingblocks.pop() # Aggressively try to coalesce each source variable with its # target. We start from the end of the graph instead of # from the beginning. This is a bit arbitrary, but the idea # is that the end of the graph runs typically more often # than the start, given that we resume execution from the # middle during blackholing. for link in block.exits: if link.last_exception is not None: self._depgraph.add_node(link.last_exception) if link.last_exc_value is not None: self._depgraph.add_node(link.last_exc_value) for i, v in enumerate(link.args): self._try_coalesce(v, link.target.inputargs[i]) def _try_coalesce(self, v, w): if isinstance(v, Variable) and getkind(v.concretetype) == self.kind: dg = self._depgraph uf = self._unionfind v0 = uf.find_rep(v) w0 = uf.find_rep(w) if v0 is not w0 and v0 not in dg.neighbours[w0]: _, rep, _ = uf.union(v0, w0) assert uf.find_rep(v0) is uf.find_rep(w0) is rep if rep is v0: dg.coalesce(w0, v0) else: assert rep is w0 dg.coalesce(v0, w0) def find_node_coloring(self): self._coloring = self._depgraph.find_node_coloring() if self.DEBUG_REGALLOC: for block in self.graph.iterblocks(): print block for v in block.getvariables(): print '\t', v, '\t', self.getcolor(v) def getcolor(self, v): return self._coloring[self._unionfind.find_rep(v)] def swapcolors(self, col1, col2): for key, value in self._coloring.items(): if value == col1: self._coloring[key] = col2 elif value == col2: self._coloring[key] = col1
class HintBookkeeper(object): def __init__(self, hannotator): self.pending_specializations = [] self.originflags = {} self.virtual_containers = {} self.descs = {} self.tsgraph_maximal_call_families = UnionFind(TsGraphCallFamily) self.annotator = hannotator self.tsgraphsigs = {} self.nonstuboriggraphcount = 0 self.stuboriggraphcount = 0 if hannotator is not None: # for tests t = hannotator.base_translator self.impurity_analyzer = ImpurityAnalyzer(t) # circular imports hack global hintmodel from pypy.jit.hintannotator import model as hintmodel def getdesc(self, graph): try: return self.descs[graph] except KeyError: self.descs[graph] = desc = GraphDesc(self, graph) return desc def enter(self, position_key): """Start of an operation. The operation is uniquely identified by the given key.""" res = getattr(self, 'position_key', None) self.position_key = position_key TLS.bookkeeper = self return res def leave(self, old=None): """End of an operation.""" if old is None: del TLS.bookkeeper del self.position_key else: self.position_key = old def myinputargorigin(self, graph, i): try: origin = self.originflags[graph, i] except KeyError: origin = hintmodel.InputArgOriginFlags(self, graph, i) self.originflags[graph, i] = origin return origin def myorigin(self): try: origin = self.originflags[self.position_key] except KeyError: assert len(self.position_key) == 3 graph, block, i = self.position_key spaceop = block.operations[i] spaceop = SpaceOperation(spaceop.opname, list(spaceop.args), spaceop.result) origin = hintmodel.OriginFlags(self, spaceop) self.originflags[self.position_key] = origin return origin def compute_at_fixpoint(self): binding = self.annotator.binding # for the entry point, we need to remove the 'myorigin' of # the input arguments (otherwise they will always be green, # as there is no call to the entry point to make them red) tsgraph = self.annotator.translator.graphs[0] for v in tsgraph.getargs(): hs_arg = binding(v) if isinstance(hs_arg, hintmodel.SomeLLAbstractConstant): hs_arg.myorigin = None # for convenience, force the return var to be red too, as # the timeshifter doesn't support anything else if self.annotator.policy.entrypoint_returns_red: v = tsgraph.getreturnvar() hs_red = hintmodel.variableoftype(v.concretetype) self.annotator.setbinding(v, hs_red) # propagate the green/red constraints log.event("Computing maximal green set...") greenorigindependencies = {} callreturndependencies = {} for origin in self.originflags.values(): origin.greenargs = True origin.record_dependencies(greenorigindependencies, callreturndependencies) while True: progress = False # check all calls to see if they are green calls or not for origin, graphs in callreturndependencies.items(): if self.is_green_call(origin.spaceop): pass # green call => don't force spaceop.result to red else: # non-green calls: replace the dependency with a regular # dependency from graph.getreturnvar() to spaceop.result del callreturndependencies[origin] retdeps = greenorigindependencies.setdefault(origin, []) for graph in graphs: retdeps.append(graph.getreturnvar()) # propagate normal dependencies for origin, deps in greenorigindependencies.items(): for v in deps: if not binding(v).is_green(): # not green => force the origin to be red too origin.greenargs = False del greenorigindependencies[origin] progress = True break if not progress: break for callfamily in self.tsgraph_maximal_call_families.infos(): if len(callfamily.tsgraphs) > 1: # if at least one graph in the family returns a red, # we force a red as the return of all of them returns_red = False for graph in callfamily.tsgraphs: if not binding(graph.getreturnvar()).is_green(): returns_red = True if returns_red: for graph in callfamily.tsgraphs: v = graph.getreturnvar() hs_red = hintmodel.variableoftype(v.concretetype) self.annotator.setbinding(v, hs_red) # compute and cache the signature of the graphs before they are # modified by further code ha = self.annotator for tsgraph in ha.translator.graphs: sig_hs = ([ha.binding(v) for v in tsgraph.getargs()], ha.binding(tsgraph.getreturnvar())) self.tsgraphsigs[tsgraph] = sig_hs def is_pure_graph(self, graph): impure = self.impurity_analyzer.analyze_direct_call(graph) return not impure def is_green_call(self, callop): "Is the given call operation completely computable at compile-time?" for v in callop.args: hs_arg = self.annotator.binding(v) if not hs_arg.is_green(): return False # all-green arguments. Note that we can return True even if the # result appears to be red; it's not a real red result then. impure = self.impurity_analyzer.analyze(callop) return not impure def immutableconstant(self, const): res = hintmodel.SomeLLAbstractConstant(const.concretetype, {}) res.const = const.value # we want null pointers to be deepfrozen! if isinstance(const.concretetype, (lltype.Ptr, ootype.Instance, ootype.BuiltinType, ootype.StaticMethod)): if not const.value: res.deepfrozen = True return res def immutablevalue(self, value): return self.immutableconstant(Constant(value, lltype.typeOf(value))) def current_op_concretetype(self): _, block, i = self.position_key op = block.operations[i] return op.result.concretetype def current_op_binding(self): _, block, i = self.position_key op = block.operations[i] hs_res = self.annotator.binding(op.result, annmodel.s_ImpossibleValue) return hs_res def getvirtualcontainerdef(self, TYPE, constructor=None): try: res = self.virtual_containers[self.position_key] assert res.T == TYPE except KeyError: if constructor is None: from pypy.jit.hintannotator.container import virtualcontainerdef constructor = virtualcontainerdef res = constructor(self, TYPE) self.virtual_containers[self.position_key] = res return res def warning(self, msg): return self.annotator.warning(msg) def specialization_key(self, fixed, args_hs): if fixed: return 'fixed' else: key = [] specialize = False for i, arg_hs in enumerate(args_hs): if isinstance(arg_hs, hintmodel.SomeLLAbstractVariable): key.append('v') specialize = True continue if (isinstance(arg_hs, hintmodel.SomeLLAbstractConstant) and arg_hs.eager_concrete): key.append('E') specialize = True else: key.append('x') if (isinstance(arg_hs, hintmodel.SomeLLAbstractConstant) and arg_hs.deepfrozen): key.append('D') specialize = True else: key.append('x') if specialize: return ''.join(key) else: return None def get_graph_by_key(self, graph, specialization_key): desc = self.getdesc(graph) return desc._cache[specialization_key] def get_graph_for_call(self, graph, fixed, args_hs): # this can modify args_hs in-place! key = self.specialization_key(fixed, args_hs) if key is None: alt_name = None else: alt_name = graph.name + '_H'+key desc = self.getdesc(graph) graph = desc.specialize(args_hs, key=key, alt_name=alt_name) return graph def graph_call(self, graph, fixed, args_hs, tsgraph_accum=None, hs_callable=None): input_args_hs = list(args_hs) graph = self.get_graph_for_call(graph, fixed, input_args_hs) if tsgraph_accum is not None: tsgraph_accum.append(graph) # save this if the caller cares # propagate fixing of arguments in the function to the caller for inp_arg_hs, arg_hs in zip(input_args_hs, args_hs): if isinstance(arg_hs, hintmodel.SomeLLAbstractConstant): assert len(inp_arg_hs.origins) == 1 [o] = inp_arg_hs.origins.keys() if o.read_fixed(): for o in arg_hs.origins: o.set_fixed() hs_res = self.annotator.recursivecall(graph, self.position_key, input_args_hs) # look on which input args the hs_res result depends on if isinstance(hs_res, hintmodel.SomeLLAbstractConstant): if (hs_callable is not None and not isinstance(hs_callable, hintmodel.SomeLLAbstractConstant)): hs_res = hintmodel.variableoftype(hs_res.concretetype, hs_res.deepfrozen) else: deps_hs = [] for hs_inputarg, hs_arg in zip(input_args_hs, args_hs): if isinstance(hs_inputarg, hintmodel.SomeLLAbstractConstant): assert len(hs_inputarg.origins) == 1 [o] = hs_inputarg.origins.keys() if o in hs_res.origins: deps_hs.append(hs_arg) if fixed: deps_hs.append(hs_res) hs_res = hintmodel.reorigin(hs_res, hs_callable, *deps_hs) return hs_res def graph_family_call(self, graph_list, fixed, args_hs, tsgraphs_accum=None, hs_callable=None): if tsgraphs_accum is None: tsgraphs = [] else: tsgraphs = tsgraphs_accum results_hs = [] for graph in graph_list: results_hs.append(self.graph_call(graph, fixed, args_hs, tsgraphs, hs_callable)) # put the tsgraphs in the same call family call_families = self.tsgraph_maximal_call_families _, rep, callfamily = call_families.find(tsgraphs[0]) for tsgraph in tsgraphs[1:]: _, rep, callfamily = call_families.union(rep, tsgraph) return annmodel.unionof(*results_hs)
class RegAllocator(object): DEBUG_REGALLOC = False def __init__(self, graph, consider_var, ListOfKind): self.graph = graph self.consider_var = consider_var self.ListOfKind = ListOfKind def make_dependencies(self): dg = DependencyGraph() for block in self.graph.iterblocks(): # Compute die_at = {Variable: index_of_operation_with_last_usage} die_at = dict.fromkeys(block.inputargs, 0) for i, op in enumerate(block.operations): for v in op.args: if isinstance(v, Variable): die_at[v] = i elif isinstance(v, self.ListOfKind): for v1 in v: if isinstance(v1, Variable): die_at[v1] = i if op.result is not None: die_at[op.result] = i + 1 if isinstance(block.exitswitch, tuple): for x in block.exitswitch: die_at.pop(x, None) else: die_at.pop(block.exitswitch, None) for link in block.exits: for v in link.args: die_at.pop(v, None) die_at = [(value, key) for (key, value) in die_at.items()] die_at.sort() die_at.append((sys.maxint, )) # Done. XXX the code above this line runs 3 times # (for kind in KINDS) to produce the same result... livevars = [v for v in block.inputargs if self.consider_var(v)] # Add the variables of this block to the dependency graph for i, v in enumerate(livevars): dg.add_node(v) for j in range(i): dg.add_edge(livevars[j], v) livevars = set(livevars) die_index = 0 for i, op in enumerate(block.operations): while die_at[die_index][0] == i: try: livevars.remove(die_at[die_index][1]) except KeyError: pass die_index += 1 if (op.result is not None and self.consider_var(op.result)): dg.add_node(op.result) for v in livevars: if self.consider_var(v): dg.add_edge(v, op.result) livevars.add(op.result) self._depgraph = dg def coalesce_variables(self): self._unionfind = UnionFind() pendingblocks = list(self.graph.iterblocks()) while pendingblocks: block = pendingblocks.pop() # Aggressively try to coalesce each source variable with its # target. We start from the end of the graph instead of # from the beginning. This is a bit arbitrary, but the idea # is that the end of the graph runs typically more often # than the start, given that we resume execution from the # middle during blackholing. for link in block.exits: if link.last_exception is not None: self._depgraph.add_node(link.last_exception) if link.last_exc_value is not None: self._depgraph.add_node(link.last_exc_value) for i, v in enumerate(link.args): self._try_coalesce(v, link.target.inputargs[i]) def _try_coalesce(self, v, w): if isinstance(v, Variable) and self.consider_var(v): assert self.consider_var(w) dg = self._depgraph uf = self._unionfind v0 = uf.find_rep(v) w0 = uf.find_rep(w) if v0 is not w0 and v0 not in dg.neighbours[w0]: _, rep, _ = uf.union(v0, w0) assert uf.find_rep(v0) is uf.find_rep(w0) is rep if rep is v0: dg.coalesce(w0, v0) else: assert rep is w0 dg.coalesce(v0, w0) def find_node_coloring(self): self._coloring = self._depgraph.find_node_coloring() if self.DEBUG_REGALLOC: for block in self.graph.iterblocks(): print block for v in block.getvariables(): print '\t', v, '\t', self.getcolor(v) def getcolor(self, v): return self._coloring[self._unionfind.find_rep(v)] def swapcolors(self, col1, col2): for key, value in self._coloring.items(): if value == col1: self._coloring[key] = col2 elif value == col2: self._coloring[key] = col1