def test_SSA_to_SSI(): c = Variable('c') x = Variable('x') y = Variable('y') b1 = Block([c]) b2 = Block([x]) b3 = Block([]) graph = FunctionGraph('x', b1) b2.operations.append(SpaceOperation('add', [x, c], y)) b2.exitswitch = y b1.closeblock(Link([Constant(0)], b2)) b2.closeblock(Link([y], b2), Link([], b3)) b3.closeblock(Link([y, c], graph.exceptblock)) SSA_to_SSI(graph) assert len(b1.inputargs) == 1 assert len(b2.inputargs) == 2 assert len(b3.inputargs) == 2 assert b2.inputargs == b2.operations[0].args assert len(b1.exits[0].args) == 2 assert b1.exits[0].args[1] is c assert len(b2.exits[0].args) == 2 assert b2.exits[0].args == [y, b2.inputargs[1]] assert len(b2.exits[1].args) == 2 assert len(b3.exits[0].args) == 2 index = b3.inputargs.index(b3.exits[0].args[0]) assert b2.exits[1].args[index] is b2.operations[0].result index = b3.inputargs.index(b3.exits[0].args[1]) assert b2.exits[1].args[index] is b2.inputargs[1]
def test_regalloc_exitswitch_2(self): v1 = Variable(); v1.concretetype = rclass.CLASSTYPE v2 = Variable(); v2.concretetype = rclass.CLASSTYPE v3 = Variable(); v3.concretetype = rclass.CLASSTYPE v4 = Variable(); v4.concretetype = rclass.CLASSTYPE block = Block([]) block.operations = [ SpaceOperation('res_call', [], v1), SpaceOperation('-live-', [], None), ] graph = FunctionGraph('f', block, v4) exclink = Link([v2], graph.returnblock) exclink.llexitcase = 123 # normally an exception class exclink.last_exception = v2 exclink.last_exc_value = "unused" block.exitswitch = c_last_exception block.closeblock(Link([v1], graph.returnblock), exclink) # self.check_assembler(graph, """ res_call -> %i0 -live- catch_exception L1 int_return %i0 --- L1: goto_if_exception_mismatch $123, L2 last_exception -> %i0 int_return %i0 --- L2: reraise """)
def test_regalloc_lists(self): v1 = Variable() v1.concretetype = lltype.Signed v2 = Variable() v2.concretetype = lltype.Signed v3 = Variable() v3.concretetype = lltype.Signed v4 = Variable() v4.concretetype = lltype.Signed v5 = Variable() v5.concretetype = lltype.Signed block = Block([v1]) block.operations = [ SpaceOperation('int_add', [v1, Constant(1, lltype.Signed)], v2), SpaceOperation('rescall', [ListOfKind('int', [v1, v2])], v5), SpaceOperation('rescall', [ListOfKind('int', [v1, v2])], v3), ] graph = FunctionGraph('f', block, v4) block.closeblock(Link([v3], graph.returnblock)) # self.check_assembler( graph, """ int_add %i0, $1 -> %i1 rescall I[%i0, %i1] -> %i2 rescall I[%i0, %i1] -> %i0 int_return %i0 """)
def test_decode_builtin_call_method(): A = lltype.GcArray(lltype.Signed) def myfoobar(a, i, marker, c): assert marker == 'mymarker' return a[i] * ord(c) myfoobar.oopspec = 'spam.foobar(a, 2, c, i)' TYPE = lltype.FuncType( [lltype.Ptr(A), lltype.Signed, lltype.Void, lltype.Char], lltype.Signed) fnobj = lltype.functionptr(TYPE, 'foobar', _callable=myfoobar) vi = Variable('i') vi.concretetype = lltype.Signed vc = Variable('c') vc.concretetype = lltype.Char v_result = Variable('result') v_result.concretetype = lltype.Signed myarray = lltype.malloc(A, 10) myarray[5] = 42 op = SpaceOperation( 'direct_call', [newconst(fnobj), newconst(myarray), vi, voidconst('mymarker'), vc], v_result) oopspec, opargs = decode_builtin_call(op) assert oopspec == 'spam.foobar' assert opargs == [newconst(myarray), newconst(2), vc, vi]
def test_func_simple(): # -------------------- flowgraph building -------------------- # def f(x): # return x+1 x = Variable("x") x.concretetype = Signed result = Variable("result") result.concretetype = Signed one = Constant(1) one.concretetype = Signed op = SpaceOperation("int_add", [x, one], result) block = Block([x]) graph = FunctionGraph("f", block) block.operations.append(op) block.closeblock(Link([result], graph.returnblock)) graph.getreturnvar().concretetype = Signed # -------------------- end -------------------- F = FuncType([Signed], Signed) f = functionptr(F, "f", graph=graph) db = LowLevelDatabase() db.get(f) db.complete() dump_on_stdout(db) S = GcStruct('testing', ('fptr', Ptr(F))) s = malloc(S) s.fptr = f db = LowLevelDatabase() db.get(s) db.complete() dump_on_stdout(db)
def builder(translator, func): # build a hacked graph that doesn't take a *arg any more, but # individual extra arguments graph = translator.buildflowgraph(func) argnames, vararg, kwarg = graph.signature assert vararg, "graph should have a *arg at this point" assert not kwarg, "where does this **arg come from??" argscopy = [Variable(v) for v in graph.getargs()] starargs = [ Variable('stararg%d' % i) for i in range(nb_extra_args) ] newstartblock = Block(argscopy[:-1] + starargs) newtup = op.newtuple(*starargs) newtup.result = argscopy[-1] newstartblock.operations.append(newtup) newstartblock.closeblock(Link(argscopy, graph.startblock)) graph.startblock = newstartblock argnames = argnames + ['.star%d' % i for i in range(nb_extra_args)] graph.signature = Signature(argnames) # note that we can mostly ignore defaults: if nb_extra_args > 0, # then defaults aren't applied. if nb_extra_args == 0, then this # just removes the *arg and the defaults keep their meaning. if nb_extra_args > 0: graph.defaults = None # shouldn't be used in this case checkgraph(graph) return graph
def test_generalize_string_concat(annotator): hlop = op.add(Variable(), Variable()) s_str = SomeString(can_be_None=True) s_value, s_exc = annotate_op(annotator, hlop, [s_None, s_str]) s_value2, s_exc2 = annotate_op(annotator, hlop, [s_str, s_str]) assert contains_s(s_value2, s_value) assert contains_s(s_exc2, s_exc)
def test_generalize_getitem_string(annotator): hlop = op.getitem(Variable(), Variable()) s_int = SomeInteger() s_str = SomeString(can_be_None=True) s_value, s_exc = annotate_op(annotator, hlop, [s_None, s_int]) s_value2, s_exc2 = annotate_op(annotator, hlop, [s_str, s_int]) assert contains_s(s_value2, s_value) assert contains_s(s_exc2, s_exc)
def test_getitem_dict(annotator): bk = annotator.bookkeeper hlop = op.getitem(Variable(), Variable()) with bk.at_position(None): s_dict = bk.newdict() s_dict.dictdef.generalize_key(SomeString()) s_dict.dictdef.generalize_value(SomeInteger()) s_result, _ = annotate_op(annotator, hlop, [s_dict, SomeString()]) assert s_result == SomeInteger()
def test_generalize_getitem_list(annotator): bk = annotator.bookkeeper hlop = op.getitem(Variable(), Variable()) s_int = SomeInteger() with bk.at_position(None): s_empty_list = bk.newlist() s_value, s_exc = annotate_op(annotator, hlop, [s_None, s_int]) s_value2, s_exc2 = annotate_op(annotator, hlop, [s_empty_list, s_int]) assert contains_s(s_value2, s_value) assert contains_s(s_exc2, s_exc)
def test_funny_links(): from rpython.flowspace.model import Block, FunctionGraph, \ Variable, Constant, Link from rpython.flowspace.operation import op for i in range(2): v_i = Variable("i") block = Block([v_i]) g = FunctionGraph("is_one", block) op1 = op.eq(v_i, Constant(1)) block.operations.append(op1) block.exitswitch = op1.result tlink = Link([Constant(1)], g.returnblock, True) flink = Link([Constant(0)], g.returnblock, False) links = [tlink, flink] if i: links.reverse() block.closeblock(*links) t = TranslationContext() a = t.buildannotator() a.build_graph_types(g, [annmodel.SomeInteger()]) rtyper = t.buildrtyper() rtyper.specialize() interp = LLInterpreter(rtyper) assert interp.eval_graph(g, [1]) == 1 assert interp.eval_graph(g, [0]) == 0
def simplify_exceptions(graph): """The exception handling caused by non-implicit exceptions starts with an exitswitch on Exception, followed by a lengthy chain of is_/issubtype tests. We collapse them all into the block's single list of exits. """ renaming = {} for block in graph.iterblocks(): if not (block.canraise and block.exits[-1].exitcase is Exception): continue covered = [link.exitcase for link in block.exits[1:-1]] seen = [] preserve = list(block.exits[:-1]) exc = block.exits[-1] last_exception = exc.last_exception last_exc_value = exc.last_exc_value query = exc.target switches = [] # collect the targets while len(query.exits) == 2: newrenaming = {} for lprev, ltarg in zip(exc.args, query.inputargs): newrenaming[ltarg] = lprev.replace(renaming) op = query.operations[0] if not (op.opname in ("is_", "issubtype") and op.args[0].replace(newrenaming) == last_exception): break renaming.update(newrenaming) case = query.operations[0].args[-1].value assert issubclass(case, py.builtin.BaseException) lno, lyes = query.exits assert lno.exitcase == False and lyes.exitcase == True if case not in seen: is_covered = False for cov in covered: if issubclass(case, cov): is_covered = True break if not is_covered: switches.append((case, lyes)) seen.append(case) exc = lno query = exc.target if Exception not in seen: switches.append((Exception, exc)) # construct the block's new exits exits = [] for case, oldlink in switches: link = oldlink.replace(renaming) assert case is not None link.last_exception = last_exception link.last_exc_value = last_exc_value # make the above two variables unique renaming2 = {} for v in link.getextravars(): renaming2[v] = Variable(v) link = link.replace(renaming2) link.exitcase = case exits.append(link) block.recloseblock(*(preserve + exits))
def rewrite_can_enter_jit(self, jd, can_enter_jits): FUNCPTR = jd._PTR_JIT_ENTER_FUNCTYPE jit_enter_fnptr = self.helper_func(FUNCPTR, jd._maybe_enter_jit_fn) if len(can_enter_jits) == 0: # see test_warmspot.test_no_loop_at_all operations = jd.portal_graph.startblock.operations op1 = operations[0] assert (op1.opname == 'jit_marker' and op1.args[0].value == 'jit_merge_point') op0 = SpaceOperation('jit_marker', [Constant('can_enter_jit', lltype.Void)] + op1.args[1:], None) operations.insert(0, op0) can_enter_jits = [(jd.portal_graph, jd.portal_graph.startblock, 0)] for graph, block, index in can_enter_jits: if graph is jd._jit_merge_point_in: continue op = block.operations[index] greens_v, reds_v = support.decode_hp_hint_args(op) args_v = greens_v + reds_v vlist = [Constant(jit_enter_fnptr, FUNCPTR)] + args_v v_result = Variable() v_result.concretetype = lltype.Void newop = SpaceOperation('direct_call', vlist, v_result) block.operations[index] = newop
def instrument_inline_candidates(graphs, threshold): cache = {None: False} def candidate(graph): try: return cache[graph] except KeyError: res = static_instruction_count(graph) <= threshold cache[graph] = res return res n = 0 for parentgraph in graphs: for block in parentgraph.iterblocks(): ops = block.operations i = len(ops) - 1 while i >= 0: op = ops[i] i -= 1 if op.opname == "direct_call": funcobj = op.args[0].value._obj graph = getattr(funcobj, 'graph', None) if graph is not None: if getattr(getattr(funcobj, '_callable', None), '_dont_inline_', False): continue if candidate(graph): tag = Constant('inline', Void) label = Constant(n, Signed) dummy = Variable() dummy.concretetype = Void count = SpaceOperation('instrument_count', [tag, label], dummy) ops.insert(i + 1, count) n += 1 log.inlining("%d call sites instrumented" % n)
def materialize_object(obj_key, state, ops): """ Accepts a VirtualState object and creates the required operations, for its materialization/initialization. XXX: Edits ops in-place """ if obj_key not in state: return False # We're gonna delete the object from the state dict first (since it has # escaped) for correct recursion reasons in case of cyclic dependency. # this needs to be done with all the aliases of the object! vo = state[obj_key] # Thus, we'll make a copy first. assert obj_key in vo.aliases for key in vo.aliases: del state[key] # Starting assembling the operations. Creation and required castings: newvar = Variable() newvar.concretetype = vo.concretetype ops.append(SpaceOperation('malloc', vo.malloc_args, newvar)) # recreate the aliases for var in vo.aliases: if var.concretetype != vo.concretetype: ops.append(SpaceOperation('cast_pointer', [newvar], var)) else: ops.append(SpaceOperation('same_as', [newvar], var)) # Initialization for (key, concretetype), value in vo.vars.items(): if concretetype != vo.concretetype: # we need a cast_pointer v = Variable() v.concretetype = concretetype op = SpaceOperation('cast_pointer', [newvar], v) ops.append(op) target = v else: target = newvar # What if the assigned is a virtual object? Recursion: materialize_object(value, state, ops) m = Variable() m.concretetype = lltype.Void ops.append(SpaceOperation('setfield', [target, Constant(key, lltype.Void), value], m)) return True
def copy(self): "Make a copy of this state in which all Variables are fresh." newstate = [] for w in self.mergeable: if isinstance(w, Variable): w = Variable(w) newstate.append(w) return FrameState(newstate, self.blocklist, self.next_offset)
def _insert_reads(block, varnames): assert len(varnames) == len(block.inputargs) v_entry1 = Variable('entry') for i, name in enumerate(varnames): hlop = op.getattr(v_entry1, const(name)) hlop.result = block.inputargs[i] block.operations.insert(i, hlop) block.inputargs = [v_entry1]
def test_SSA_to_SSI_2(): x = Variable('x') y = Variable('y') z = Variable('z') b1 = Block([x]) b2 = Block([y]) b3 = Block([]) b3.operations.append(SpaceOperation('hello', [y], z)) b1.closeblock(Link([x], b2), Link([], b3)) graph = FunctionGraph('x', b1) SSA_to_SSI(graph) assert b1.inputargs == [x] assert b2.inputargs == [y] assert b3.inputargs == [b3.operations[0].args[0]] assert b1.exits[0].args == [x] assert b1.exits[1].args == [x]
def _copy(v): from rpython.flowspace.flowcontext import FlowSignal if isinstance(v, Variable): return Variable(v) elif isinstance(v, FlowSignal): vars = [_copy(var) for var in v.args] return v.rebuild(*vars) else: return v
def __init__(self, func, code): from rpython.flowspace.flowcontext import SpamBlock locals = [None] * code.co_nlocals for i in range(code.formalargcount): locals[i] = Variable(code.co_varnames[i]) state = FrameState(locals, [], None, [], 0) initialblock = SpamBlock(state) super(PyGraph, self).__init__(self._sanitize_funcname(func), initialblock) self.func = func self.signature = code.signature self.defaults = func.__defaults__ or ()
def transform_graph(self, graph): if graph in self.minimal_transform: if self.minimalgctransformer: self.minimalgctransformer.transform_graph(graph) self.minimal_transform.remove(graph) return if graph in self.seen_graphs: return self.seen_graphs.add(graph) self.links_to_split = {} # link -> vars to pop_alive across the link # for sanity, we need an empty block at the start of the graph inserted_empty_startblock = False if not starts_with_empty_block(graph): insert_empty_startblock(graph) inserted_empty_startblock = True is_borrowed = self.compute_borrowed_vars(graph) try: for block in graph.iterblocks(): self.transform_block(block, is_borrowed) except GCTransformError as e: e.args = ('[function %s]: %s' % (graph.name, e.message),) raise for link, livecounts in self.links_to_split.iteritems(): llops = LowLevelOpList() for var, livecount in livecounts.iteritems(): for i in range(livecount): self.pop_alive(var, llops) for i in range(-livecount): self.push_alive(var, llops) if llops: if link.prevblock.exitswitch is None: link.prevblock.operations.extend(llops) else: insert_empty_block(link, llops) # remove the empty block at the start of the graph, which should # still be empty (but let's check) if starts_with_empty_block(graph) and inserted_empty_startblock: old_startblock = graph.startblock graph.startblock = graph.startblock.exits[0].target checkgraph(graph) self.links_to_split = None v = Variable('vanishing_exc_value') v.concretetype = self.get_lltype_of_exception_value() llops = LowLevelOpList() self.pop_alive(v, llops) graph.exc_cleanup = (v, list(llops)) return is_borrowed # xxx for tests only
def guessexception(self, ctx, *cases): block = self.crnt_block links = [] for case in [None] + list(cases): if case is not None: if case is Exception: last_exc = Variable('last_exception') else: last_exc = Constant(case) last_exc_value = Variable('last_exc_value') vars = [last_exc, last_exc_value] vars2 = [Variable(), Variable()] else: vars = [] vars2 = [] egg = EggBlock(vars2, block, case) ctx.pendingblocks.append(egg) link = Link(vars, egg, case) if case is not None: link.extravars(last_exception=last_exc, last_exc_value=last_exc_value) egg.extravars(last_exception=last_exc)
def test_decode_builtin_call_nomethod(): def myfoobar(i, marker, c): assert marker == 'mymarker' return i * ord(c) myfoobar.oopspec = 'foobar(2, c, i)' TYPE = lltype.FuncType([lltype.Signed, lltype.Void, lltype.Char], lltype.Signed) fnobj = lltype.functionptr(TYPE, 'foobar', _callable=myfoobar) vi = Variable('i') vi.concretetype = lltype.Signed vc = Variable('c') vc.concretetype = lltype.Char v_result = Variable('result') v_result.concretetype = lltype.Signed op = SpaceOperation( 'direct_call', [newconst(fnobj), vi, voidconst('mymarker'), vc], v_result) oopspec, opargs = decode_builtin_call(op) assert oopspec == 'foobar' assert opargs == [newconst(2), vc, vi]
def _translate_arg(arg): if isinstance(arg, Variable): res = local_versions.get(arg, None) if res is None: res = Variable(arg) res.concretetype = arg.concretetype link.args.append(arg) block.inputargs.append(res) local_versions[arg] = res return res else: return arg
def flowin(self, block, count, vars, newvarsmap): # in this 'block', follow where the 'var' goes to and replace # it by a flattened-out family of variables. This family is given # by newvarsmap, whose keys are the 'flatnames'. def list_newvars(): return [newvarsmap[key] for key in self.flatnames] assert block.operations != () self.newops = [] for op in block.operations: for arg in op.args[1:]: # should be the first arg only assert arg not in vars if op.args and op.args[0] in vars: self.flowin_op(op, vars, newvarsmap) elif op.result in vars: assert op.opname == self.MALLOC_OP progress = True # drop the "malloc" operation newvarsmap = self.flatconstants.copy() # zero initial values # if there are substructures, they are now individually # malloc'ed in an exploded way. (They will typically be # removed again by the next malloc removal pass.) for key in self.needsubmallocs: v = Variable() v.concretetype = self.newvarstype[key] c = Constant(v.concretetype.TO, lltype.Void) if c.value == op.args[0].value: progress = False # replacing a malloc with # the same malloc! newop = self.recreate_malloc(c, v) self.newops.append(newop) newvarsmap[key] = v count[0] += progress else: self.newops.append(op) assert block.exitswitch not in vars for link in block.exits: appended = False newargs = [] for arg in link.args: if arg in vars: if not appended: newargs += list_newvars() appended = True else: newargs.append(arg) link.args[:] = newargs block.operations[:] = self.newops
def create_proxy_graph(self, op): """ creates a graph which calls the original function, checks for raised exceptions, fetches and then raises them again. If this graph is inlined, the correct exception matching blocks are produced.""" # XXX slightly annoying: construct a graph by hand # but better than the alternative result = op.result.copy() opargs = [] inputargs = [] callargs = [] ARGTYPES = [] for var in op.args: if isinstance(var, Variable): v = Variable() v.concretetype = var.concretetype inputargs.append(v) opargs.append(v) callargs.append(var) ARGTYPES.append(var.concretetype) else: opargs.append(var) newop = SpaceOperation(op.opname, opargs, result) startblock = Block(inputargs) startblock.operations.append(newop) newgraph = FunctionGraph("dummy_exc1", startblock) startblock.closeblock(Link([result], newgraph.returnblock)) newgraph.returnblock.inputargs[0].concretetype = op.result.concretetype self.gen_exc_check(startblock, newgraph.returnblock) excblock = Block([]) llops = rtyper.LowLevelOpList(None) var_value = self.gen_getfield('exc_value', llops) var_type = self.gen_getfield('exc_type', llops) # c_check1 = self.c_assertion_error_ll_exc_type c_check2 = self.c_n_i_error_ll_exc_type llops.genop('debug_catch_exception', [var_type, c_check1, c_check2]) # self.gen_setfield('exc_value', self.c_null_evalue, llops) self.gen_setfield('exc_type', self.c_null_etype, llops) excblock.operations[:] = llops newgraph.exceptblock.inputargs[ 0].concretetype = self.lltype_of_exception_type newgraph.exceptblock.inputargs[ 1].concretetype = self.lltype_of_exception_value excblock.closeblock(Link([var_type, var_value], newgraph.exceptblock)) startblock.exits[True].target = excblock startblock.exits[True].args = [] fptr = self.constant_func("dummy_exc1", ARGTYPES, op.result.concretetype, newgraph) return newgraph, SpaceOperation("direct_call", [fptr] + callargs, op.result)
def union(w1, w2): "Union of two variables or constants." if w1 == w2: return w1 if w1 is None or w2 is None: return None # if w1 or w2 is an undefined local, we "kill" the value # coming from the other path and return an undefined local if isinstance(w1, Variable) or isinstance(w2, Variable): return Variable() # new fresh Variable if isinstance(w1, Constant) and isinstance(w2, Constant): # FlowSignal represent stack unrollers in the stack. # They should not be merged because they will be unwrapped. # This is needed for try:except: and try:finally:, though # it makes the control flow a bit larger by duplicating the # handlers. dont_merge_w1 = w1 in UNPICKLE_TAGS or isinstance(w1.value, SpecTag) dont_merge_w2 = w2 in UNPICKLE_TAGS or isinstance(w2.value, SpecTag) if dont_merge_w1 or dont_merge_w2: raise UnionError else: return Variable() # generalize different constants raise TypeError('union of %r and %r' % (w1.__class__.__name__, w2.__class__.__name__))
def __init__(self, func, code): from rpython.flowspace.flowcontext import SpamBlock locals = [None] * code.co_nlocals for i in range(code.formalargcount): locals[i] = Variable(code.co_varnames[i]) state = FrameState(locals, [], None, [], 0) initialblock = SpamBlock(state) unsafe = False if func.func_doc and func.func_doc.lstrip().startswith('UNSAFE'): unsafe = True super(PyGraph, self).__init__(self._sanitize_funcname(func), initialblock, unsafe=unsafe) self.func = func self.signature = code.signature self.defaults = func.func_defaults or ()
def normalize_calltable_row_annotation(annotator, graphs): if len(graphs) <= 1: return False # nothing to do graph_bindings = {} for graph in graphs: graph_bindings[graph] = [annotator.binding(v) for v in graph.getargs()] iterbindings = graph_bindings.itervalues() nbargs = len(iterbindings.next()) for binding in iterbindings: assert len(binding) == nbargs generalizedargs = [] for i in range(nbargs): args_s = [] for graph, bindings in graph_bindings.items(): args_s.append(bindings[i]) s_value = annmodel.unionof(*args_s) generalizedargs.append(s_value) result_s = [ annotator.binding(graph.getreturnvar()) for graph in graph_bindings ] generalizedresult = annmodel.unionof(*result_s) conversion = False for graph in graphs: bindings = graph_bindings[graph] need_conversion = (generalizedargs != bindings) if need_conversion: conversion = True oldblock = graph.startblock inlist = [] for j, s_value in enumerate(generalizedargs): v = Variable(graph.getargs()[j]) annotator.setbinding(v, s_value) inlist.append(v) newblock = Block(inlist) # prepare the output args of newblock and link outlist = inlist[:] newblock.closeblock(Link(outlist, oldblock)) graph.startblock = newblock # finished checkgraph(graph) annotator.annotated[newblock] = annotator.annotated[oldblock] # convert the return value too if annotator.binding(graph.getreturnvar()) != generalizedresult: conversion = True annotator.setbinding(graph.getreturnvar(), generalizedresult) return conversion
def union(w1, w2): "Union of two variables or constants." from rpython.flowspace.flowcontext import FlowSignal if w1 == w2: return w1 if w1 is None or w2 is None: return None # if w1 or w2 is an undefined local, we "kill" the value # coming from the other path and return an undefined local if isinstance(w1, Variable) or isinstance(w2, Variable): return Variable() # new fresh Variable if isinstance(w1, Constant) and isinstance(w2, Constant): if isinstance(w1.value, SpecTag) or isinstance(w2.value, SpecTag): raise UnionError else: return Variable() # generalize different constants if isinstance(w1, FlowSignal) and isinstance(w2, FlowSignal): if type(w1) is not type(w2): raise UnionError vars = [union(v1, v2) for v1, v2 in zip(w1.args, w2.args)] return w1.rebuild(*vars) if isinstance(w1, FlowSignal) or isinstance(w2, FlowSignal): raise UnionError raise TypeError('union of %r and %r' % (w1.__class__.__name__, w2.__class__.__name__))