def investigate_funcall_pair(rep, m, l_n_vc, r_n_vc, verbose=False, verbose_imp=False, simplify=True): l_nm = "%s @ %s" % (rep.p.nodes[l_n_vc[0]].fname, rep.node_count_name(l_n_vc)) r_nm = "%s @ %s" % (rep.p.nodes[r_n_vc[0]].fname, rep.node_count_name(r_n_vc)) print "Attempt match %s -> %s" % (l_nm, r_nm) imp = rep.get_func_assert(l_n_vc, r_n_vc) if verbose_imp: imp2 = logic.weaken_assert(imp) imp2 = solver.smt_expr(imp2, {}, rep.solv) if simplify: imp2 = simplify_sexp(imp2, rep, m) print imp2 assert imp.is_op("Implies"), imp [pred, concl] = imp.vals pred = solver.smt_expr(pred, {}, rep.solv) pred = solver.parse_s_expression(pred) bits = solver.split_hyp_sexpr(pred, []) xs = [eval_model_bool(m, v) for v in bits] print " %s" % xs for (v, bit) in zip(xs, bits): if v != True or verbose: print " %s: %s" % (v, bit) if bit[0] == "word32-eq": vs = [model_sx_word(m, x) for x in bit[1:]] print " (%s = %s)" % tuple(vs)
def setup_split_search (rep, head, restrs, hyps, i_opts, j_opts, unfold_limit = None, tags = None): p = rep.p if not tags: tags = p.pairing.tags if unfold_limit == None: unfold_limit = max ([start + (2 * step) + 1 for (start, step) in i_opts + j_opts]) trace ('Split search at %d, unfold limit %d.' % (head, unfold_limit)) l_tag, r_tag = tags loop_elts = [(n, start, step) for n in p.splittable_points (head) for (start, step) in i_opts] init_to_split = init_loops_to_split (p, restrs) r_to_split = [n for n in init_to_split if p.node_tags[n][0] == r_tag] cand_r_loop_elts = [(n2, start, step) for n in r_to_split for n2 in p.splittable_points (n) for (start, step) in j_opts] err_restrs = restr_others (p, tuple ([(sp, vc_upto (unfold_limit)) for sp in r_to_split]) + restrs, 1) nrerr_pc = mk_not (rep.get_pc (('Err', err_restrs), tag = r_tag)) def get_pc (n, k): head = p.loop_id (n) assert head in init_to_split if n != head: k += 1 restrs2 = restrs + ((head, vc_num (k)), ) return rep.get_pc ((n, restrs2)) for n in r_to_split: get_pc (n, unfold_limit) get_pc (head, unfold_limit) premise = foldr1 (mk_and, [nrerr_pc] + map (rep.interpret_hyp, hyps)) premise = logic.weaken_assert (premise) knowledge = SearchKnowledge (rep, 'search at %d (unfold limit %d)' % (head, unfold_limit), restrs, hyps, tags, (loop_elts, cand_r_loop_elts)) knowledge.premise = premise last_knowledge[0] = knowledge # make sure the representation is in sync rep.test_hyp_whyps (true_term, hyps) # make sure all mem eqs are being tracked mem_vs = [v for v in knowledge.v_ids if v[0].typ == builtinTs['Mem']] for (i, v) in enumerate (mem_vs): for v2 in mem_vs[:i]: for pred in expand_var_eqs (knowledge, (v, v2)): smt_expr (pred, {}, rep.solv) for v in knowledge.v_ids: for pred in expand_var_eqs (knowledge, (v, 'Const')): smt_expr (pred, {}, rep.solv) return knowledge
def try_interpret_hyp(rep, hyp): try: expr = rep.interpret_hyp(hyp) solver.smt_expr(expr, {}, rep.solv) return None except: return ('Broken Hyp', hyp)
def try_interpret_hyp(rep, hyp): try: expr = rep.interpret_hyp(hyp) solver.smt_expr(expr, {}, rep.solv) return None except: return ("Broken Hyp", hyp)
def investigate_funcall_pair(rep, m, l_n_vc, r_n_vc, verbose=False, verbose_imp=False, simplify=True): l_nm = "%s @ %s" % (rep.p.nodes[l_n_vc[0]].fname, rep.node_count_name(l_n_vc)) r_nm = "%s @ %s" % (rep.p.nodes[r_n_vc[0]].fname, rep.node_count_name(r_n_vc)) print 'Attempt match %s -> %s' % (l_nm, r_nm) imp = rep.get_func_assert(l_n_vc, r_n_vc) imp = logic.weaken_assert(imp) if verbose_imp: imp2 = solver.smt_expr(imp, {}, rep.solv) if simplify: imp2 = simplify_sexp(imp2, rep, m) print imp2 assert imp.is_op('Implies'), imp [pred, concl] = imp.vals pred = solver.smt_expr(pred, {}, rep.solv) pred = solver.parse_s_expression(pred) bits = solver.split_hyp_sexpr(pred, []) xs = [eval_model_bool(m, v) for v in bits] print ' %s' % xs for (v, bit) in zip(xs, bits): if v != True or verbose: print ' %s: %s' % (v, bit) if bit[0] == 'word32-eq': vs = [model_sx_word(m, x) for x in bit[1:]] print ' (%s = %s)' % tuple(vs)
def eval(expr, env): try: s = solver.smt_expr(expr, env, rep.solv) s_x = solver.parse_s_expression(s) ev = search.eval_model(m, s_x) return (s, solver.smt_expr(ev, {}, None)) except solver.EnvMiss, e: return None
def trace_mem(rep, tag, m, verbose=False, simplify=True, symbs=True, resolve_addrs=False): p = rep.p ns = walk_model(rep, tag, m) trace = [] for (n, vc) in ns: if (n, vc) not in rep.arc_pc_envs: # this n_vc has a pre-state, but has not been emitted. # no point trying to evaluate its expressions, the # solve won't have seen them yet. continue n_nm = rep.node_count_name((n, vc)) node = p.nodes[n] if node.kind == 'Call': exprs = list(node.args) elif node.kind == 'Basic': exprs = [expr for (_, expr) in node.upds] elif node.kind == 'Cond': exprs = [node.cond] env = rep.node_pc_envs[(tag, n, vc)][1] accs = list( set([acc for expr in exprs for acc in expr.get_mem_accesses()])) for (kind, addr, v, mem) in accs: addr_s = solver.smt_expr(addr, env, rep.solv) v_s = solver.smt_expr(v, env, rep.solv) addr = eval_str(addr, env, rep.solv, m) v = eval_str(v, env, rep.solv, m) m_nm = m_var_name(mem) print '%s: %s @ <%s> -- %s -- %s' % (kind, m_nm, addr, v, n_nm) if simplify: addr_s = simplify_sexp(addr_s, rep, m) v_s = simplify_sexp(v_s, rep, m) if verbose: print '\t %s -- %s' % (addr_s, v_s) if symbs: addr_n = str_to_num(addr) (hit_symbs, secs) = find_symbol(addr_n, output=False) ss = hit_symbs + secs if ss: print '\t [%s]' % ', '.join(ss) if resolve_addrs: accs = [(kind, solver.to_smt_expr(addr, env, rep.solv), solver.to_smt_expr(v, env, rep.solv), mem) for (kind, addr, v, mem) in accs] trace.extend([(kind, addr, v, mem, n, vc) for (kind, addr, v, mem) in accs]) if node.kind == 'Call': msg = '<function call to %s at %s>' % (node.fname, n_nm) print msg trace.append(msg) return trace
def fetch((n, vc)): if n in vds and [(nm, typ) for (nm, typ) in vs if (nm, typ) not in vds[n]]: return None try: (_, env) = rep.get_node_pc_env((n, vc), tag) s = solver.smt_expr(v, env, rep.solv) s_x = solver.parse_s_expression(s) ev = search.eval_model(m, s_x) return (s, solver.smt_expr(ev, {}, None)) except solver.EnvMiss, e: return None
def get_ptr_offsets(p, n_ptrs, bases, hyps=[], cache=None, fail_early=False): """detect which ptrs are guaranteed to be at constant offsets from some set of basis ptrs""" rep = rep_graph.mk_graph_slice(p, fast=True) if cache == None: cache = {} last_get_ptr_offsets[0] = (p, n_ptrs, bases, hyps) smt_bases = [] for (n, ptr, k) in bases: n_vc = default_n_vc(p, n) (_, env) = rep.get_node_pc_env(n_vc) smt = solver.smt_expr(ptr, env, rep.solv) smt_bases.append((smt, k)) ptr_typ = ptr.typ smt_ptrs = [] for (n, ptr) in n_ptrs: n_vc = default_n_vc(p, n) pc_env = rep.get_node_pc_env(n_vc) if not pc_env: continue smt = solver.smt_expr(ptr, pc_env[1], rep.solv) hyp = rep_graph.pc_true_hyp((n_vc, p.node_tags[n][0])) smt_ptrs.append(((n, ptr), smt, hyp)) hyps = hyps + mk_not_callable_hyps(p) for tag in set([p.node_tags[n][0] for (n, _) in n_ptrs]): hyps = hyps + init_correctness_hyps(p, tag) tags = set([p.node_tags[n][0] for (n, ptr) in n_ptrs]) ex_defs = {} for t in tags: ex_defs.update(get_extra_sp_defs(rep, t)) offs = [] for (v, ptr, hyp) in smt_ptrs: off = None for (ptr2, k) in smt_bases: off = offs_expr_const(ptr, ptr2, rep, [hyp] + hyps, cache=cache, extra_defs=ex_defs, typ=ptr_typ) if off != None: offs.append((v, off, k)) break if off == None: trace('get_ptr_offs fallthrough at %d: %s' % v) trace(str([hyp] + hyps)) assert not fail_early, (v, ptr) return offs
def trace_mem(rep, tag, m, verbose=False, simplify=True, symbs=True, resolve_addrs=False): p = rep.p ns = walk_model(rep, tag, m) trace = [] for (n, vc) in ns: if (n, vc) not in rep.arc_pc_envs: # this n_vc has a pre-state, but has not been emitted. # no point trying to evaluate its expressions, the # solve won't have seen them yet. continue n_nm = rep.node_count_name((n, vc)) node = p.nodes[n] if node.kind == "Call": msg = "<function call to %s at %s>" % (node.fname, n_nm) print msg trace.append(msg) if node.kind == "Basic": exprs = [expr for (_, expr) in node.upds] elif node.kind == "Cond": exprs = [node.cond] else: continue env = rep.node_pc_envs[(tag, n, vc)][1] accs = list(set([acc for expr in exprs for acc in expr.get_mem_accesses()])) for (kind, addr, v, mem) in accs: addr_s = solver.smt_expr(addr, env, rep.solv) v_s = solver.smt_expr(v, env, rep.solv) addr = eval_str(addr, env, rep.solv, m) v = eval_str(v, env, rep.solv, m) m_nm = m_var_name(mem) print "%s: %s @ <%s> -- %s -- %s" % (kind, m_nm, addr, v, n_nm) if simplify: addr_s = simplify_sexp(addr_s, rep, m) v_s = simplify_sexp(v_s, rep, m) if verbose: print "\t %s -- %s" % (addr_s, v_s) if symbs: (hit_symbs, secs) = find_symbol(addr, output=False) ss = hit_symbs + secs if ss: print "\t [%s]" % ", ".join(ss) if resolve_addrs: accs = [ (kind, solver.to_smt_expr(addr, env, rep.solv), solver.to_smt_expr(v, env, rep.solv), mem) for (kind, addr, v, mem) in accs ] trace.extend([(kind, addr, v, mem, n, vc) for (kind, addr, v, mem) in accs]) return trace
def smt_print(expr): env = {} while True: try: return solver.smt_expr(expr, env, None) except solver.EnvMiss, e: env[(e.name, e.typ)] = e.name
def test_hyp_whyps (self, hyp, hyps, cache = None, fast = False, model = None): self.avail_hyps = set (hyps) if not self.used_hyps <= self.avail_hyps: self.rebuild () last_test[0] = (hyp, hyps, list (self.pc_env_requests)) expr = self.interpret_hyp_imps (hyps, hyp) trace ('Testing hyp whyps', push = 1) trace ('requests = %s' % self.pc_env_requests) expr_s = smt_expr (expr, {}, self.solv) if cache and expr_s in cache: trace ('Cached: %s' % cache[expr_s]) return cache[expr_s] if fast: trace ('(not in cache)') return None self.solv.add_pvalid_dom_assertions () if model == None: (result, _) = self.solv.parallel_test_hyps ( [(None, expr)], {}) else: result = self.solv.test_hyp (expr, {}, model = model) trace ('Result: %s' % result, push = -1) if cache != None: cache[expr_s] = result if not result: last_failed_test[0] = last_test[0] return result
def get_extra_sp_defs (rep, tag): """all functions will keep the stack pointer equal, whether they have pairing partners or not. add these extra defs/equalities for the purposes of stack depth analysis.""" # FIXME how to parametrise this? sp = mk_var ('r13', syntax.word32T) defs = {} items = [(n_vc, x) for (n_vc, x) in rep.funcs.iteritems () if logic.is_int (n_vc[0])] for ((n, vc), (inputs, outputs, _)) in items: if rep.p.node_tags[n][0] == tag: inp_sp = solver.smt_expr (sp, inputs, rep.solv) inp_sp = solver.parse_s_expression (inp_sp) out_sp = solver.smt_expr (sp, outputs, rep.solv) out_sp = solver.parse_s_expression (out_sp) if inp_sp != out_sp: defs[out_sp] = inp_sp return defs
def eval_model_bool(m, x): if hasattr(x, 'typ'): x = solver.smt_expr(x, {}, None) x = solver.parse_s_expression(x) try: r = search.eval_model(m, x) assert r in [syntax.true_term, syntax.false_term], r return r == syntax.true_term except: return 'EXCEPT'
def eval_model_bool(m, x): if hasattr(x, "typ"): x = solver.smt_expr(x, {}, None) x = solver.parse_s_expression(x) try: r = search.eval_model(m, x) assert r in [syntax.true_term, syntax.false_term], r return r == syntax.true_term except: return "EXCEPT"
def get_ptr_offsets (p, n_ptrs, bases, hyps = []): """detect which ptrs are guaranteed to be at constant offsets from some set of basis ptrs""" rep = rep_graph.mk_graph_slice (p, fast = True) cache = {} last_get_ptr_offsets[0] = (p, n_ptrs, bases, hyps) smt_bases = [] for (n, ptr, k) in bases: n_vc = default_n_vc (p, n) (_, env) = rep.get_node_pc_env (n_vc) smt = solver.smt_expr (ptr, env, rep.solv) smt_bases.append ((smt, k)) smt_ptrs = [] for (n, ptr) in n_ptrs: n_vc = default_n_vc (p, n) pc_env = rep.get_node_pc_env (n_vc) if not pc_env: continue smt = solver.smt_expr (ptr, pc_env[1], rep.solv) hyp = rep_graph.pc_true_hyp ((n_vc, p.node_tags[n][0])) smt_ptrs.append (((n, ptr), smt, hyp)) hyps = hyps + mk_not_callable_hyps (p) tags = set ([p.node_tags[n][0] for (n, ptr) in n_ptrs]) ex_defs = {} for t in tags: ex_defs.update (get_extra_sp_defs (rep, t)) offs = [] for (v, ptr, hyp) in smt_ptrs: for (ptr2, k) in smt_bases: off = offs_expr_const (ptr, ptr2, rep, [hyp] + hyps, cache = cache, extra_defs = ex_defs) if off != None: offs.append ((v, off, k)) break trace ('get_ptr_offs fallthrough at %d: %s' % v) return offs
def get_extra_sp_defs(rep, tag): """add extra defs/equalities about stack pointer for the purposes of stack depth analysis.""" # FIXME how to parametrise this? sp = mk_var('r13', syntax.word32T) defs = {} fcalls = [ n_vc for n_vc in rep.funcs if logic.is_int(n_vc[0]) if rep.p.node_tags[n_vc[0]][0] == tag if preserves_sp(rep.p.nodes[n_vc[0]].fname) ] for (n, vc) in fcalls: (inputs, outputs, _) = rep.funcs[(n, vc)] if (sp.name, sp.typ) not in outputs: continue inp_sp = solver.smt_expr(sp, inputs, rep.solv) inp_sp = solver.parse_s_expression(inp_sp) out_sp = solver.smt_expr(sp, outputs, rep.solv) out_sp = solver.parse_s_expression(out_sp) if inp_sp != out_sp: defs[out_sp] = inp_sp return defs
def fetch_known_eqs (self, n_vc, tag): if not self.use_known_eqs: return None eqs = self.p.known_eqs.get ((n_vc, tag)) if eqs == None: return None avail = [] for (x, n_vc_y, tag_y, y, hyps) in eqs: if hyps <= self.avail_hyps: (_, env) = self.get_node_pc_env (n_vc_y, tag_y) avail.append ((x, smt_expr (y, env, self.solv))) self.used_hyps.update (hyps) if avail: return avail return None
def func_assert_premise_strength(rep, m, l_n_vc, r_n_vc): imp = rep.get_func_assert(l_n_vc, r_n_vc) assert imp.is_op('Implies'), imp [pred, concl] = imp.vals pred = solver.smt_expr(pred, {}, rep.solv) pred = solver.parse_s_expression(pred) bits = solver.split_hyp_sexpr(pred, []) assert bits, bits scores = [] for bit in bits: try: res = eval_model_bool(m, bit) if res: scores.append(1.0) else: scores.append(0.0) except solver.EnvMiss, e: scores.append(0.5) except AssertionError, e: scores.append(0.5)
def eval_model_expr (m, solv, v): s = solver.smt_expr (v, {}, solv) s_x = solver.parse_s_expression (s) return eval_model (m, s_x)
def find_split (rep, head, restrs, hyps, i_opts, j_opts, unfold_limit, tags = None): p = rep.p if tags: l_tag, r_tag = tags else: l_tag, r_tag = p.pairing.tags loop_elts = [(n, start, step) for n in p.loop_data[head][1] if p.loop_splittables[n] for (start, step) in i_opts] init_to_split = init_loops_to_split (p, restrs) r_to_split = [n for n in init_to_split if p.node_tags[n][0] == r_tag] cand_r_loop_elts = [(n2, start, step) for n in r_to_split for n2 in p.loop_data[n][1] if p.loop_splittables[n2] for (start, step) in j_opts] err_restrs = restr_others (p, tuple ([(sp, vc_upto (unfold_limit)) for sp in r_to_split]) + restrs, 1) nrerr_pc = mk_not (rep.get_pc (('Err', err_restrs), tag = r_tag)) def get_pc (n, k): head = p.loop_id (n) assert head in init_to_split if n != head: k += 1 restrs2 = restrs + ((head, vc_num (k)), ) return rep.get_pc ((n, restrs2)) for n in r_to_split: get_pc (n, unfold_limit) get_pc (head, unfold_limit) premise = foldr1 (mk_and, [nrerr_pc] + map (rep.interpret_hyp, hyps)) premise = logic.weaken_assert (premise) knowledge = (rep, (restrs, loop_elts, cand_r_loop_elts, premise), init_knowledge_pairs (rep, loop_elts, cand_r_loop_elts), set ()) last_knowledge[0] = knowledge # make sure the representation is in sync rep.test_hyp_whyps (true_term, hyps) # make sure all mem eqs are being tracked (_, _, (pairs, vs), _) = knowledge mem_vs = [v for v in vs if v[0].typ == builtinTs['Mem']] for (i, v) in enumerate (mem_vs): for v2 in mem_vs[:i]: for pred in expand_var_eqs (knowledge, (v, v2)): smt_expr (pred, {}, rep.solv) for v in vs: for pred in expand_var_eqs (knowledge, (v, 'Const')): smt_expr (pred, {}, rep.solv) # start the process with a model add_model (knowledge, [mk_not (get_pc (head, unfold_limit))]) num_eqs = 3 while True: trace ('Search at unfold limit %d' % unfold_limit) trace ('Computing live pairings') pair_eqs = [(pair, mk_pairing_v_eqs (knowledge, pair)) for pair in sorted (pairs) if pairs[pair][0] != 'Failed'] endorsed = [(pair, eqs) for (pair, eqs) in pair_eqs if eqs != None] trace (' ... %d live pairings, %d endorsed' % (len (pair_eqs), len (endorsed))) for (pair, eqs) in endorsed: split = v_eqs_to_split (p, pair, eqs, restrs, hyps, tags = tags) if split == None: pairs[pair] = ('Failed', 'SplitWeak', eqs) continue if check_split_induct (p, restrs, hyps, split, tags = tags): trace ('Tested v_eqs!') return ('Split', split) else: pairs[pair] = ('Failed', 'InductFailed', eqs) u_eqs = unknown_eqs (knowledge, num_eqs) if not u_eqs: trace (('Exhausted split candidates for loop at %d,' + ' unfold limit %d') % (head, unfold_limit)) fails = [it for it in pairs.items () if it[1][0] == 'Failed'] fails10 = fails[:10] trace (' %d of %d failed pairings:' % (len (fails10), len (fails))) last_failed_pairings.append (fails) del last_failed_pairings[:-10] for f in fails: trace (' %s' % (f,)) ind_fails = [it for it in fails if str (it[1][1]) == 'InductFailed'] if ind_fails: trace ( 'Inductive failures!') for f in ind_fails: trace (' %s' % (f,)) return (None, ind_fails) add_model_wrapper (knowledge, u_eqs) num_eqs = 4 - num_eqs # oscillate between 3, 1
(n, vcount))] pc_envs = [pc_env for pc_env in pc_envs if pc_env] if pc_envs == []: return None if n == 'Err': # we'll never care about variable values here # and there are sometimes a LOT of arcs to Err # so we save a lot of merge effort pc_envs = [(to_smt_expr (pc, env, self.solv), {}) for (pc, env) in pc_envs] (pc, env, large) = merge_envs_pcs (pc_envs, self.solv) if large or len (smt_expr (pc, env, self.solv)) > 80: name = self.path_cond_name ((n, vcount), tag) name = self.solv.add_def (name, pc, env) pc = mk_smt_expr (name, boolT) for (nm, typ) in env: if len (env[(nm, typ)]) > 80: env[(nm, typ)] = self.contract (nm, (n, vcount), env[(nm, typ)], typ) return (pc, env) def contract (self, name, n_vc, val, typ): if val in self.contractions: return self.contractions[val]
def emit_node (self, n): (pc, env) = self.get_node_pc_env (n, request = False) tag = self.p.node_tags[n[0]][0] app_eqs = self.apply_known_eqs_tm (n, tag) # node = logic.simplify_node_elementary (self.p.nodes[n[0]]) # whether to ignore unreachable Cond arcs seems to be a huge # dilemma. if we ignore them, some reachable sites become # unreachable and we can't interpret all hyps # if we don't ignore them, the variable set disagrees with # var_deps and so the abstracted loop pc/env may not be # sufficient and we get EnvMiss again. I don't really know # what to do about this corner case. node = self.p.nodes[n[0]] env = dict (env) if node.kind == 'Call': self.try_inline (n[0], pc, env) if pc == false_term: return [(c, false_term, {}) for c in node.get_conts()] elif node.kind == 'Cond' and node.left == node.right: return [(node.left, pc, env)] elif node.kind == 'Cond' and node.cond == true_term: return [(node.left, pc, env), (node.right, false_term, env)] elif node.kind == 'Basic': upds = [] for (lv, v) in node.upds: if v.kind == 'Var': upds.append ((lv, env[(v.name, v.typ)])) else: name = self.local_name (lv[0], n) v = app_eqs (v) vname = self.add_local_def (n, ('Var', lv), name, v, env) upds.append ((lv, vname)) for (lv, v) in upds: env[lv] = v return [(node.cont, pc, env)] elif node.kind == 'Cond': name = self.cond_name (n) cond = self.p.fresh_var (name, boolT) env[(cond.name, boolT)] = self.add_local_def (n, 'Cond', name, app_eqs (node.cond), env) lpc = mk_and (cond, pc) rpc = mk_and (mk_not (cond), pc) return [(node.left, lpc, env), (node.right, rpc, env)] elif node.kind == 'Call': nm = self.success_name (node.fname, n) success = self.solv.add_var (nm, boolT) success = mk_smt_expr (success, boolT) fun = functions[node.fname] ins = dict ([((x, typ), smt_expr (app_eqs (arg), env, self.solv)) for ((x, typ), arg) in azip (fun.inputs, node.args)]) mem_name = None for (x, typ) in reversed (fun.inputs): if typ == builtinTs['Mem']: mem_name = (node.fname, ins[(x, typ)]) outs = {} for ((x, typ), (y, typ2)) in azip (node.rets, fun.outputs): assert typ2 == typ if self.fast_const_ret (n[0], x, typ): outs[(y, typ2)] = env [(x, typ)] continue name = self.local_name (x, n) env[(x, typ)] = self.solv.add_var_restr (name, typ, mem_name = mem_name) outs[(y, typ2)] = env[(x, typ)] for ((x, typ), (y, _)) in azip (node.rets, fun.outputs): z = self.var_rep_request ((x, typ), 'Call', n, env) if z != None: env[(x, typ)] = z outs[(y, typ)] = z self.add_func (node.fname, ins, outs, success, n) return [(node.cont, pc, env)] else: assert not 'node kind understood'