def pull_temps(s: Stm, decls_out: [SDecl], exp_is_bad) -> Stm: def pull(e: Exp) -> Exp: if exp_is_bad(e): v = fresh_var(e.type) decls_out.append(SDecl(v.id, e)) return v return e if isinstance(s, SNoOp): return s if isinstance(s, SSeq): s1 = pull_temps(s.s1, decls_out, exp_is_bad) s2 = pull_temps(s.s2, decls_out, exp_is_bad) return SSeq(s1, s2) if isinstance(s, SIf): cond = pull(s.cond) s1 = pull_temps(s.then_branch, decls_out, exp_is_bad) s2 = pull_temps(s.else_branch, decls_out, exp_is_bad) return SIf(cond, s1, s2) if isinstance(s, SForEach): bag = pull(s.iter) d_tmp = [] body = pull_temps(s.body, d_tmp, exp_is_bad) to_fix, ok = partition(d_tmp, lambda d: s.id in free_vars(d.val)) decls_out.extend(ok) for d in to_fix: v = EVar(d.id).with_type(d.val.type) mt = TMap(s.id.type, v.type) m = EMakeMap2(bag, ELambda(s.id, d.val)).with_type(mt) mv = fresh_var(m.type) md = SDecl(mv.id, m) decls_out.append(md) body = subst(body, {v.id: EMapGet(mv, s.id).with_type(v.type)}) return SForEach(s.id, bag, body) if isinstance(s, SAssign): return SAssign(s.lhs, pull(s.rhs)) if isinstance(s, SCall): return SCall(s.target, s.func, tuple(pull(arg) for arg in s.args)) if isinstance(s, SMapDel): return SMapDel(s.map, pull(s.key)) if isinstance(s, SMapPut): return SMapPut(s.map, pull(s.key), pull(s.value)) if isinstance(s, SMapUpdate): key = pull(s.key) d_tmp = [] change = pull_temps(s.change, d_tmp, exp_is_bad) for d in d_tmp: if s.val_var in free_vars(d.val): decls_out.append( SDecl( d.id, subst( d.val, { s.val_var.id: EMapGet(s.map, key).with_type(s.val_var.type) }))) else: decls_out.append(d) return SMapUpdate(s.map, key, s.val_var, change) raise NotImplementedError(s)
def can_elim_vars(spec : Exp, assumptions : Exp, vs : [EVar]): """Does any execution of `spec` actually depend on any of `vs`? It is possible for a variable to appear in an expression like `spec` without affecting its value. This function uses the solver to determine whether any of the given variables can affect the output of `spec`. """ spec = strip_EStateVar(spec) sub = { v.id : fresh_var(v.type) for v in vs } return valid(EImplies( EAll([assumptions, subst(assumptions, sub)]), EEq(spec, subst(spec, sub))))
def can_elim_vars(spec: Exp, assumptions: Exp, vs: [EVar]): """Does any execution of `spec` actually depend on any of `vs`? It is possible for a variable to appear in an expression like `spec` without affecting its value. This function uses the solver to determine whether any of the given variables can affect the output of `spec`. """ spec = strip_EStateVar(spec) sub = {v.id: fresh_var(v.type) for v in vs} return valid( EImplies(EAll([assumptions, subst(assumptions, sub)]), EEq(spec, subst(spec, sub))))
def always(self, op, other, cards : Exp, **kwargs) -> bool: """ Partial order on costs. """ if isinstance(self.formula, ENum) and isinstance(other.formula, ENum): return eval(EBinOp(self.formula, op, other.formula).with_type(BOOL), env={}) f = EImplies(cards, EBinOp(self.formula, op, other.formula).with_type(BOOL)) if integer_cardinalities.value: try: return valid(f, logic="QF_LIA", timeout=1, **kwargs) except SolverReportedUnknown: # If we accidentally made an unsolveable integer arithmetic formula, # then try again with real numbers. This will admit some models that # are not possible (since bags must have integer cardinalities), but # returning false is always a safe move here, so it's fine. print("Warning: not able to solve {}".format(pprint(f))) f = subst(f, { v.id : EVar(v.id).with_type(REAL) for v in free_vars(cards) }) # This timeout is dangerous! Sufficiently complex specifications # will cause this to timeout _every_time_, meaning we never make # progress. # However, this timeout helps ensure liveness: the Python process # never gets deadlocked waiting for Z3. In the Distant Future it # would be nice to move away from Z3Py and invoke Z3 as a subprocess # instead. That would allow the Python process to break out if it is # asked to stop while Z3 is running. It would also give us added # protection against Z3 segfaults, which have been observed in the # wild from time to time. timeout = 60 try: return valid(f, logic="QF_NRA", timeout=timeout, **kwargs) except SolverReportedUnknown: print("Giving up!") return False
def set_impl(self, q : Query, rep : [(EVar, Exp)], ret : Exp): with task("updating implementation", query=q.name): with task("finding duplicated state vars"): to_remove = set() for (v, e) in rep: aeq = find_one(vv for (vv, ee) in self.concrete_state if e.type == ee.type and self.state_solver.valid(EImplies(EAll(self.spec.assumptions), EEq(e, ee)))) # aeq = find_one(vv for (vv, ee) in self.concrete_state if e.type == ee.type and alpha_equivalent(e, ee)) if aeq is not None: event("state var {} is equivalent to {}".format(v.id, aeq.id)) ret = subst(ret, { v.id : aeq }) to_remove.add(v) rep = [ x for x in rep if x[0] not in to_remove ] self.concrete_state.extend(rep) self.query_impls[q.name] = rewrite_ret(q, lambda prev: ret, keep_assumptions=False) for op in self.op_specs: with task("incrementalizing query", query=q.name, op=op.name): for new_member, projection in rep: subqueries = [] state_update_stm = inc.mutate_in_place( new_member, projection, op.body, abstract_state=self.abstract_state, assumptions=op.assumptions, subgoals_out=subqueries) for sub_q in subqueries: sub_q.docstring = "[{}] {}".format(op.name, sub_q.docstring) state_update_stm = self._add_subquery(sub_q=sub_q, used_by=state_update_stm) self.updates[(new_member, op.name)] = state_update_stm
def adapt(self, e : Exp, ctx) -> Exp: if self == ctx: return e if self.alpha_equivalent(ctx): e = self._parent.adapt(e, ctx._parent) return subst(e, { ctx.var.id : self.var }) return self._parent.adapt(e, ctx)
def desugar(spec: Spec) -> Spec: # rewrite enums repl = { name: EEnumEntry(name).with_type(t) for t in all_types(spec) if isinstance(t, TEnum) for name in t.cases } spec = subst(spec, repl) # convert all collection types to bags spec = Spec(spec.name, list(spec.types), list(spec.extern_funcs), list(spec.statevars), list(spec.assumptions), list(spec.methods), spec.header, spec.footer, spec.docstring) for i in range(len(spec.statevars)): v, t = spec.statevars[i] if isinstance(t, TSet): # Sets become bags w/ implicit unique assumptions. t = TBag(t.t) spec.statevars[i] = (v, t) v = EVar(v).with_type(t) spec.assumptions.append(EUnaryOp(UOp.AreUnique, v).with_type(BOOL)) assert retypecheck(spec, env={}) # organize queries by name queries = {q.name: q for q in spec.methods if isinstance(q, Query)} class V(BottomUpRewriter): def visit_ECall(self, e): q = queries.get(e.func) if q is not None: return self.visit( subst( q.ret, { arg_name: arg for ((arg_name, ty), arg) in zip(q.args, e.args) })) else: return ECall(e.func, tuple(self.visit(a) for a in e.args)).with_type(e.type) spec = V().visit(spec) spec.methods = [ m for m in spec.methods if not (isinstance(m, Query) and m.visibility == Visibility.Private) ] class V(BottomUpRewriter): def visit_Exp(self, e): return desugar_list_comprehensions(e) spec = V().visit(spec) assert retypecheck(spec, env={}) return spec
def test_no_argument_conflict_lambda(self): x = EVar("x").with_type(TInt()) y = EVar("y").with_type(TInt()) f = ELambda(x, EBinOp(y, "+", ENum(1).with_type(INT))) assert retypecheck(f) g = subst(f, { y.id : x }) a = EVar("a").with_type(TInt()) b = EVar("b").with_type(TInt()) assert valid(EEq(g.apply_to(a), g.apply_to(b)))
def test_no_argument_conflict_lambda(self): x = EVar("x").with_type(TInt()) y = EVar("y").with_type(TInt()) f = ELambda(x, EBinOp(y, "+", ENum(1).with_type(INT))) assert retypecheck(f) g = subst(f, {y.id: x}) a = EVar("a").with_type(TInt()) b = EVar("b").with_type(TInt()) assert valid(equal(g.apply_to(a), g.apply_to(b)))
def _setup_handle_updates(self): """ This method creates update code for handle objects modified by each op. Must be called once after all user-specified queries have been added. """ for op in self.op_specs: handles = reachable_handles_at_method(self.spec, op) # print("-"*60) for t, bag in handles.items(): # print(" {} : {}".format(pprint(t), pprint(bag))) h = fresh_var(t) delta = inc.delta_form( self.spec.statevars + op.args + [(h.id, h.type)], op) lval = EGetField(h, "val").with_type(t.value_type) new_val = simplify(subst(lval, delta)) # get set of modified handles modified_handles = Query( fresh_name("modified_handles"), Visibility.Internal, [], op.assumptions, EFilter( EUnaryOp(UOp.Distinct, bag).with_type(bag.type), ELambda(h, ENot(EEq(lval, new_val)))).with_type(bag.type), "[{}] modified handles of type {}".format( op.name, pprint(t))) query_vars = [ v for v in free_vars(modified_handles) if v not in self.abstract_state ] modified_handles.args = [(arg.id, arg.type) for arg in query_vars] # modify each one (state_update_stm, subqueries) = inc.sketch_update( lval, lval, new_val, self.abstract_state, list(op.assumptions) + [EDeepIn(h, bag), EIn(h, modified_handles.ret)]) # print(" got {} subqueries".format(len(subqueries))) # print(" to update {} in {}, use\n{}".format(pprint(t), op.name, pprint(state_update_stm))) for sub_q in subqueries: sub_q.docstring = "[{}] {}".format(op.name, sub_q.docstring) state_update_stm = self._add_subquery( sub_q=sub_q, used_by=state_update_stm) if state_update_stm != SNoOp(): state_update_stm = SForEach( h, ECall(modified_handles.name, query_vars).with_type(bag.type), state_update_stm) state_update_stm = self._add_subquery( sub_q=modified_handles, used_by=state_update_stm) self.handle_updates[(t, op.name)] = state_update_stm
def visit_ECall(self, e): q = queries.get(e.func) if q is not None: return self.visit( subst( q.ret, { arg_name: arg for ((arg_name, ty), arg) in zip(q.args, e.args) })) else: return ECall(e.func, tuple(self.visit(a) for a in e.args)).with_type(e.type)
def adapt(self, e : Exp, ctx, e_fvs=None) -> Exp: if self == ctx: return e if e_fvs is None: e_fvs = free_vars(e) if isinstance(ctx, UnderBinder): if ctx.var not in e_fvs: return self.adapt(e, ctx.parent(), e_fvs=e_fvs) if alpha_equivalent(self.bag, self._parent.adapt(ctx.bag, ctx._parent)): e = self._parent.adapt(e, ctx._parent, e_fvs=e_fvs) return subst(e, { ctx.var.id : self.var }) return self._parent.adapt(e, ctx, e_fvs=e_fvs)
def mutate(e: syntax.Exp, op: syntax.Stm) -> syntax.Exp: """Return the new value of `e` after executing `op`.""" if isinstance(op, syntax.SNoOp): return e elif isinstance(op, syntax.SAssign): return _do_assignment(op.lhs, op.rhs, e) elif isinstance(op, syntax.SCall): if op.func == "add": return mutate( e, syntax.SCall(op.target, "add_all", (syntax.ESingleton( op.args[0]).with_type(op.target.type), ))) elif op.func == "add_all": return mutate( e, syntax.SAssign( op.target, syntax.EBinOp(op.target, "+", op.args[0]).with_type(op.target.type))) elif op.func == "remove": return mutate( e, syntax.SCall(op.target, "remove_all", (syntax.ESingleton( op.args[0]).with_type(op.target.type), ))) elif op.func == "remove_all": return mutate( e, syntax.SAssign( op.target, syntax.EBinOp(op.target, "-", op.args[0]).with_type(op.target.type))) else: raise Exception("Unknown func: {}".format(op.func)) elif isinstance(op, syntax.SIf): then_branch = mutate(e, op.then_branch) else_branch = mutate(e, op.else_branch) if alpha_equivalent(then_branch, else_branch): return then_branch return syntax.ECond(op.cond, then_branch, else_branch).with_type(e.type) elif isinstance(op, syntax.SSeq): if isinstance(op.s1, syntax.SSeq): return mutate(e, syntax.SSeq(op.s1.s1, syntax.SSeq(op.s1.s2, op.s2))) e2 = mutate(mutate(e, op.s2), op.s1) if isinstance(op.s1, syntax.SDecl): e2 = subst(e2, {op.s1.id: op.s1.val}) return e2 elif isinstance(op, syntax.SDecl): return e else: raise NotImplementedError(type(op))
def set_impl(self, q: Query, rep: [(EVar, Exp)], ret: Exp): to_remove = set() from cozy.solver import valid for (v, e) in rep: aeq = find_one( vv for (vv, ee) in self.concrete_state if e.type == ee.type and valid(EImplies(EAll(self.spec.assumptions), EEq(e, ee)))) # aeq = find_one(vv for (vv, ee) in self.concrete_state if e.type == ee.type and alpha_equivalent(e, ee)) if aeq is not None: print("########### state var {} is equivalent to {}".format( v.id, aeq.id)) ret = subst(ret, {v.id: aeq}) to_remove.add(v) rep = [x for x in rep if x[0] not in to_remove] self.concrete_state.extend(rep) self.query_impls[q.name] = rewrite_ret(q, lambda prev: ret, keep_assumptions=False) op_deltas = { op.name: inc.delta_form(self.spec.statevars, op) for op in self.op_specs } for op in self.op_specs: # print("###### INCREMENTALIZING: {}".format(op.name)) delta = op_deltas[op.name] for new_member, projection in rep: (state_update_stm, subqueries) = inc.sketch_update(new_member, projection, subst(projection, delta), self.abstract_state, list(op.assumptions)) for sub_q in subqueries: sub_q.docstring = "[{}] {}".format(op.name, sub_q.docstring) state_update_stm = self._add_subquery( sub_q=sub_q, used_by=state_update_stm) self.updates[(new_member, op.name)] = state_update_stm
def adapt(self, e: Exp, ctx, e_fvs=None) -> Exp: if self == ctx: return e if e_fvs is None: e_fvs = free_vars(e) if isinstance(ctx, UnderBinder): if ctx.var not in e_fvs: return self.adapt(e, ctx.parent(), e_fvs=e_fvs) if alpha_equivalent(self.bag, self._parent.adapt(ctx.bag, ctx._parent)): e = self._parent.adapt(e, ctx._parent, e_fvs=e_fvs) return subst(e, {ctx.var.id: self.var}) return self._parent.adapt(e, ctx, e_fvs=e_fvs)
def set_impl(self, q: Query, rep: [(EVar, Exp)], ret: Exp): """Update the implementation of a query. The query having the same name as `q` will have its implementation replaced by the given concrete representation and computation. This call may add additional "subqueries" to the implementation to maintain the new representation when each update operation is called. """ with task("updating implementation", query=q.name): with task("finding duplicated state vars"): to_remove = set() for (v, e) in rep: aeq = find_one(vv for (vv, ee) in self._concretization_functions if e.type == ee.type and self.state_solver.valid(EEq(e, ee))) # aeq = find_one(vv for (vv, ee) in self._concretization_functions if e.type == ee.type and alpha_equivalent(e, ee)) if aeq is not None: event("state var {} is equivalent to {}".format( v.id, aeq.id)) ret = subst(ret, {v.id: aeq}) to_remove.add(v) rep = [x for x in rep if x[0] not in to_remove] self._concretization_functions.extend(rep) self.query_impls[q.name] = rewrite_ret(q, lambda prev: ret, keep_assumptions=False) for op in self.op_specs: with task("incrementalizing query", query=q.name, op=op.name): for new_member, projection in rep: subqueries = [] state_update_stm = inc.mutate_in_place( new_member, projection, op.body, abstract_state=self.abstract_state, assumptions=op.assumptions, invariants=self.abstract_invariants, subgoals_out=subqueries) for sub_q in subqueries: sub_q.docstring = "[{}] {}".format( op.name, sub_q.docstring) state_update_stm = self._add_subquery( sub_q=sub_q, used_by=state_update_stm) self.updates[(new_member, op.name)] = state_update_stm
def check_calls_wf(spec : Spec): res = [] queries = { m.name : m for m in spec.methods if isinstance(m, Query) } for ctx in enumerate_fragments(spec): e = ctx.e if isinstance(e, ECall): q = queries.get(e.func) if q is None: continue print("Checking call {}...".format(pprint(e))) a = EAll(ctx.facts) for precond in q.assumptions: precond = mutate(subst(precond, { v : val for (v, t), val in zip(q.args, e.args) }), ctx.mutations) if not valid(inline_calls(spec, EImplies(a, precond))): res.append("at {}: call may not satisfy precondition {}".format(pprint(e), pprint(precond))) return res
def inline_enum_constants(syntax_tree: ADT) -> ADT: """Convert variables that refer to enum constants into EEnumEntry nodes. Enum types introduce both a type name and a set of constants. This function replaces variables that refer to those constants with a special kind of AST node representing the constant. Most other functions in Cozy assume that this transformation has taken place, and that variables are not names for enum constants. """ repl = { name: EEnumEntry(name).with_type(t) for t in all_types(syntax_tree) if isinstance(t, TEnum) for name in t.cases } return subst(syntax_tree, repl)
def check_ops_preserve_invariants(spec : Spec): if not invariant_preservation_check.value: return [] res = [] for m in spec.methods: if not isinstance(m, Op): continue remap = delta_form(spec.statevars, m) # print(m.name) # for id, e in remap.items(): # print(" {id} ---> {e}".format(id=id, e=pprint(e))) for a in spec.assumptions: a_post_delta = subst(a, remap) assumptions = list(m.assumptions) + list(spec.assumptions) if not valid(cse(EImplies(EAll(assumptions), a_post_delta))): res.append("{.name!r} may not preserve invariant {}".format(m, pprint(a))) return res
def _do_assignment(lval: syntax.Exp, new_value: syntax.Exp, e: syntax.Exp) -> syntax.Exp: """ Return the value of `e` after the assignment `lval = new_value`. """ if isinstance(lval, syntax.EVar): return subst(e, {lval.id: new_value}) elif isinstance(lval, syntax.EGetField): if isinstance(lval.e.type, syntax.THandle): assert lval.f == "val" # Because any two handles might alias, we need to rewrite all # reachable handles in `e`. return replace_get_value(e, lval.e, new_value) return _do_assignment(lval.e, _replace_field(lval.e, lval.f, new_value), e) else: raise Exception("not an lvalue: {}".format(pprint(lval)))
def visit_ELambda(self, e): if e.arg in binders_by_type[e.arg.type]: return super().visit_ADT(e) fvs = free_vars(e.body) legal_repls = [ b for b in binders_by_type[e.arg.type] if b not in fvs ] if not legal_repls: if allow_add: print("Adding aux binder {} and returning {}".format(e.arg, pprint(ELambda(e.arg, e.body))), file=sys.stderr) binders_to_use.append(e.arg) binders_by_type[e.arg.type].append(e.arg) return ELambda(e.arg, self.visit(e.body)) else: if throw: print("No legal binder to use for {}".format(pprint(e))) raise Exception(pprint(e)) else: return ELambda(e.arg, self.visit(e.body)) b = legal_repls[0] return ELambda(b, self.visit(subst(e.body, { e.arg.id : b })))
def visit_ECall(self, e): args = [self.visit(a) for a in e.args] if e.func in self.funcs: f = self.funcs[e.func] return "({})".format( f.body_string.format(**{ arg: "({})".format(val) for (arg, _), val in zip(f.args, args) })) elif e.func in self.queries: q = self.queries[e.func] body = subst( q.ret, { q.args[i][0]: EEscape(args[i], (), ()).with_type(q.args[i][1]) for i in range(len(q.args)) }) return self.visit(body) else: raise Exception("unknown function {}".format(repr(e.func)))
def set_impl(self, q : Query, rep : [(EVar, Exp)], ret : Exp): """Update the implementation of a query. The query having the same name as `q` will have its implementation replaced by the given concrete representation and computation. This call may add additional "subqueries" to the implementation to maintain the new representation when each update operation is called. """ with task("updating implementation", query=q.name): with task("finding duplicated state vars"): to_remove = set() for (v, e) in rep: aeq = find_one(vv for (vv, ee) in self._concretization_functions if e.type == ee.type and self.state_solver.valid(EEq(e, ee))) # aeq = find_one(vv for (vv, ee) in self._concretization_functions if e.type == ee.type and alpha_equivalent(e, ee)) if aeq is not None: event("state var {} is equivalent to {}".format(v.id, aeq.id)) ret = subst(ret, { v.id : aeq }) to_remove.add(v) rep = [ x for x in rep if x[0] not in to_remove ] self._concretization_functions.extend(rep) self.query_impls[q.name] = rewrite_ret(q, lambda prev: ret, keep_assumptions=False) for op in self.op_specs: with task("incrementalizing query", query=q.name, op=op.name): for new_member, projection in rep: subqueries = [] state_update_stm = inc.mutate_in_place( new_member, projection, op.body, abstract_state=self.abstract_state, assumptions=op.assumptions, invariants=self.abstract_invariants, subgoals_out=subqueries) for sub_q in subqueries: sub_q.docstring = "[{}] {}".format(op.name, sub_q.docstring) state_update_stm = self._add_subquery(sub_q=sub_q, used_by=state_update_stm) self.updates[(new_member, op.name)] = state_update_stm
def visit_ECall(self, e, indent=""): if e.args: setups, args = zip(*[self.visit(arg, indent) for arg in e.args]) else: setups, args = ([], []) if e.func in self.funcs: f = self.funcs[e.func] return ("".join(setups), "({})".format( f.body_string.format( **{arg: val for (arg, _), val in zip(f.args, args)}))) elif e.func in self.queries: q = self.queries[e.func] body = subst( q.ret, { q.args[i][0]: EEscape(args[i], (), ()).with_type(q.args[i][1]) for i in range(len(q.args)) }) setup, res = self.visit(body, indent=indent) return ("".join(setups) + setup, res) else: raise Exception("unknown function {}".format(repr(e.func)))
def visit_Spec(self, spec: Spec, state_exps: {str: Exp}, sharing, abstract_state=()): self.state_exps = state_exps self.funcs = {f.name: f for f in spec.extern_funcs} self.queries = { q.name: q for q in spec.methods if isinstance(q, Query) } self.vars = set(e.id for e in all_exps(spec) if isinstance(e, EVar)) s = "#pragma once\n" s += "#include <algorithm>\n" s += "#include <vector>\n" s += "#include <unordered_set>\n" s += "#include <string>\n" if self.use_qhash: s += "#include <QHash>\n" else: s += "#include <unordered_map>\n" if spec.header: s += "\n" + spec.header.strip() + "\n" s += "{}\nclass {} {{\n".format( ("\n" + spec.docstring) if spec.docstring else "", spec.name) s += "public:\n" self.setup_types(spec, state_exps, sharing) for t, name in self.types.items(): s += self.define_type(spec.name, t, name, INDENT, sharing) s += "protected:\n" for name, t in spec.statevars: self.statevar_name = name s += self.declare_field(name, t, indent=INDENT) s += "public:\n" # default constructor s += INDENT + "inline {name}() {{\n".format(name=spec.name) for name, t in spec.statevars: initial_value = state_exps[name] fvs = free_vars(initial_value) initial_value = subst( initial_value, {v.id: evaluation.construct_value(v.type) for v in fvs}) setup = self.construct_concrete(t, initial_value, EVar(name).with_type(t)) s += self.visit(setup, INDENT + INDENT) s += INDENT + "}\n" # explicit constructor if abstract_state: s += INDENT + "inline {name}({args}) {{\n".format( name=spec.name, args=", ".join(self.visit(t, v) for (v, t) in abstract_state)) for name, t in spec.statevars: initial_value = state_exps[name] setup = self.construct_concrete(t, initial_value, EVar(name).with_type(t)) s += self.visit(setup, INDENT + INDENT) s += INDENT + "}\n" # disable copy constructor (TODO: support this in the future?) s += INDENT + "{name}(const {name}& other) = delete;\n".format( name=spec.name) # generate methods for op in spec.methods: s += self.visit(op, INDENT) s += "};\n\n" s += spec.footer if not s.endswith("\n"): s += "\n" return s
def build(self, cache, size): for e in cache.find(pool=RUNTIME_POOL, size=size - 1, type=INT): if not is_root(e): continue e2 = simplify_sum(e) if e != e2: yield self.check(e2, RUNTIME_POOL) # for e in cache.find(pool=RUNTIME_POOL, size=size-1): # if isinstance(e, EMapGet) and isinstance(e.map, EMakeMap2): # x = e.map.value.apply_to(e.key) # x._tag = True # yield self.check(x, RUNTIME_POOL) # [x] - ys for e in cache.find_collections(pool=RUNTIME_POOL, size=size - 1): if not is_root(e): continue if isinstance(e, EBinOp) and e.op == "-" and isinstance( e.e1, ESingleton): x = e.e1.e y = e.e2 x = ECond( EBinOp(x, BOp.In, y).with_type(BOOL), EEmptyList().with_type(e.type), e.e1).with_type(e.type) yield self.check(x, RUNTIME_POOL) elif isinstance(e, EUnaryOp) and e.op == UOp.Distinct: e = strip_EStateVar(e) m = EMakeMap2(e.e, mk_lambda(e.type.t, lambda x: T)).with_type( TMap(e.type.t, BOOL)) yield self.check(m, STATE_POOL) m = EStateVar(m).with_type(m.type) yield self.check(m, RUNTIME_POOL) x = EMapKeys(m).with_type(e.type) # x._tag = True yield self.check(x, RUNTIME_POOL) # # x in ys ----> (count x in ys) > 0 # for e in cache.find(pool=RUNTIME_POOL, type=BOOL, size=size-1): # if isinstance(e, EBinOp) and e.op == BOp.In: # for b in self.binders: # if b.type != e.e1.type: # continue # x = EGt( # EUnaryOp(UOp.Length, EFilter(e.e2, ELambda(b, EEq(e.e1, b))).with_type(e.e2.type)).with_type(INT), # ZERO) # x._tag = True # yield self.check(x, RUNTIME_POOL) for e in cache.find(pool=RUNTIME_POOL, size=size - 1): if not is_root(e): continue if (isinstance(e, EArgMin) or isinstance( e, EArgMax)) and isinstance(e.e, EBinOp) and e.e.op == "+": l = e.e.e1 r = e.e.e2 op = e.e.op f = lambda x: type(e)(x, e.f).with_type(e.type) ll = EStateVar(f(l.e)).with_type(e.type) if isinstance( l, EStateVar) else f(l) rr = EStateVar(f(r.e)).with_type(e.type) if isinstance( r, EStateVar) else f(r) x = ECond( EUnaryOp(UOp.Exists, l).with_type(BOOL), ECond( EUnaryOp(UOp.Exists, r).with_type(BOOL), f( EBinOp( ESingleton(ll).with_type(e.e.type), op, ESingleton(rr).with_type(e.e.type)).with_type( e.e.type)), ll).with_type(e.type), rr).with_type(e.type) # from cozy.solver import valid # assert valid(EEq(e, x), model_callback=print) x._tag = True yield self.check(x, RUNTIME_POOL) # is-last(x, l) for (sz1, sz2) in pick_to_sum(2, size - 1): for e1 in cache.find(pool=RUNTIME_POOL, size=sz1): if not is_root(e1): continue for e2 in cache.find_collections(pool=STATE_POOL, size=sz2, of=e1.type): if not is_root(e2): continue for b in self.binders: if b.type != e1.type: continue m = EMakeMap2( e2, mk_lambda( e2.type.t, lambda x: EUnaryOp( UOp.Length, EFilter( e2, mk_lambda(e2.type.t, lambda y: EEq( x, y))).with_type(e2.type)). with_type(INT))).with_type(TMap( e2.type.t, INT)) # filt = EFilter(e2, ELambda(b, EEq(e1, b))).with_type(e2.type) # x = EEq( # EUnaryOp(UOp.Length, filt).with_type(INT), # ONE) x = EGt( EMapGet(EStateVar(m).with_type(m.type), e1).with_type(INT), ONE) # x._tag = True yield self.check(x, RUNTIME_POOL) # histogram # for e in cache.find_collections(pool=STATE_POOL, size=size-1): # m = EMakeMap2(e, # mk_lambda(e.type.t, lambda x: # EUnaryOp(UOp.Length, EFilter(e, # mk_lambda(e.type.t, lambda y: EEq(x, y))).with_type(e.type)).with_type(INT))).with_type(TMap(e.type.t, INT)) # m._tag = True # yield self.check(m, STATE_POOL) # Fixup EFilter(\x -> ECond...) for e in cache.find_collections(pool=RUNTIME_POOL, size=size - 1): if not is_root(e): continue if isinstance(e, EFilter): for (_, x, r, _) in enumerate_fragments(e.p.body): if isinstance(x, ECond): lhs = EFilter( e.e, ELambda(e.p.arg, EAll([x.cond, r(x.then_branch) ]))).with_type(e.type) rhs = EFilter( e.e, ELambda(e.p.arg, EAll([ENot(x.cond), r(x.else_branch) ]))).with_type(e.type) union = EBinOp(lhs, "+", rhs).with_type(e.type) # yield self.check(lhs.p.body, RUNTIME_POOL) # yield self.check(rhs.p.body, RUNTIME_POOL) yield self.check(lhs, RUNTIME_POOL) yield self.check(rhs, RUNTIME_POOL) yield self.check(union, RUNTIME_POOL) # Try instantiating bound expressions for pool in (STATE_POOL, RUNTIME_POOL): for (sz1, sz2) in pick_to_sum(2, size - 1): for e1 in cache.find(pool=pool, size=sz1): if not is_root(e1): continue for v in free_vars(e1): if pool == RUNTIME_POOL: e1 = subst( strip_EStateVar(e1), { sv.id: EStateVar(sv).with_type(sv.type) for sv in self.state_vars if sv != v }) for e2 in cache.find(pool=pool, type=v.type, size=sz2): yield self.check(subst(e1, {v.id: e2}), pool) for (sz1, sz2) in pick_to_sum(2, size - 1): for e in cache.find(pool=RUNTIME_POOL, size=sz1): if not is_root(e): continue for x, pool in map_accelerate(e, self.state_vars, self.binders, self.args, cache, sz2): yield self.check(x, pool) if isinstance(e, EFilter) and not any(v in self.binders for v in free_vars(e)): for x, pool in accelerate_filter(e.e, e.p, self.state_vars, self.binders, self.args, cache, sz2): yield self.check(x, pool) for bag in cache.find_collections(pool=RUNTIME_POOL, size=size - 1): if not is_root(bag): continue for a in self.args: for v in self.state_vars: if is_collection(v.type) and v.type == a.type: v = EStateVar(v).with_type(v.type) cond = EBinOp(a, BOp.In, v).with_type(BOOL) yield self.check( EFilter(bag, mk_lambda(bag.type.t, lambda _: cond)).with_type( bag.type), RUNTIME_POOL) yield self.check( EFilter( bag, mk_lambda(bag.type.t, lambda _: ENot(cond))).with_type( bag.type), RUNTIME_POOL) if isinstance(bag, EFilter): if any(v not in self.state_vars for v in free_vars(bag.e)): continue # separate filter conds if isinstance(bag.p.body, EBinOp) and bag.p.body.op == BOp.And: p1 = ELambda(bag.p.arg, bag.p.body.e1) p2 = ELambda(bag.p.arg, bag.p.body.e2) f1 = EFilter(bag.e, p1).with_type(bag.type) f2 = EFilter(bag.e, p2).with_type(bag.type) f3 = EFilter(f1, p2).with_type(bag.type) f4 = EFilter(f2, p1).with_type(bag.type) yield self.check(f1, RUNTIME_POOL) yield self.check(f2, RUNTIME_POOL) yield self.check(f3, RUNTIME_POOL) yield self.check(f4, RUNTIME_POOL) # construct map lookups binder = bag.p.arg inf = infer_map_lookup(bag.p.body, binder, set(self.state_vars)) if inf: key_proj, key_lookup, remaining_filter = inf bag_binder = find_one( self.binders, lambda b: b.type == key_proj.type and b != binder) if bag_binder: m = strip_EStateVar( EMakeMap2( EMap(bag.e, ELambda(binder, key_proj)).with_type( type(bag.type)(key_proj.type)), ELambda( bag_binder, EFilter( bag.e, ELambda(binder, EEq(key_proj, bag_binder))).with_type( bag.type))).with_type( TMap( key_proj.type, bag.type))) assert not any(v in self.args for v in free_vars(m)) yield self.check(m, STATE_POOL) m = EStateVar(m).with_type(m.type) mg = EMapGet(m, key_lookup).with_type(bag.type) yield self.check(mg, RUNTIME_POOL) yield self.check( EFilter(mg, ELambda( binder, remaining_filter)).with_type(mg.type), RUNTIME_POOL) # for e in cache.find(size=size-1): # # F(xs +/- ys) ---> F(xs), F(ys) # for z in break_plus_minus(e): # if z != e: # # print("broke {} --> {}".format(pprint(e), pprint(z))) # yield z # # try reordering operations # for (_, e1, f) in enumerate_fragments(e): # if e1.type == e.type and e1 != e: # for (_, e2, g) in enumerate_fragments(e1): # if e2.type == e.type and e2 != e1: # # e == f(g(e2)) # yield g(f(e2)) yield from self.wrapped.build(cache, size)
def visit_Spec(self, spec : Spec, state_exps : { str : Exp }, sharing, abstract_state=()): self.state_exps = state_exps self.funcs = { f.name: f for f in spec.extern_funcs } self.queries = { q.name: q for q in spec.methods if isinstance(q, Query) } self.vars = set(e.id for e in all_exps(spec) if isinstance(e, EVar)) self.write("#pragma once\n") self.write("#include <algorithm>\n") self.write("#include <set>\n") self.write("#include <functional>\n") self.write("#include <vector>\n") self.write("#include <unordered_set>\n") self.write("#include <string>\n") if self.use_qhash: self.write("#include <QHash>\n") else: self.write("#include <unordered_map>\n") if spec.header: self.write("\n" + spec.header.strip() + "\n") self.write("{}\nclass {} {{\n".format( ("\n" + spec.docstring) if spec.docstring else "", spec.name)) self.write("public:\n") print("Setting up auxiliary types...") self.setup_types(spec, state_exps, sharing) with self.indented(): for t, name in self.types.items(): self.define_type(spec.name, t, name, sharing) self.begin_statement() if isinstance(t, THandle): # No overridden hash code! We use pointers instead. continue self.write("struct _Hash", name, " ") with self.block(): self.write_stmt("typedef ", spec.name, "::", name, " argument_type;") self.write_stmt("typedef std::size_t result_type;") self.begin_statement() self.write("result_type operator()(const argument_type& x) const noexcept ") x = EVar("x").with_type(t) if isinstance(t, TEnum): fields = [EEnumToInt(x).with_type(INT)] elif isinstance(t, TRecord): fields = [EGetField(x, f).with_type(ft) for (f, ft) in t.fields] elif isinstance(t, TTuple): fields = [ETupleGet(x, n).with_type(tt) for (n, tt) in enumerate(t.ts)] else: raise NotImplementedError(t) with self.block(): self.visit(self.compute_hash(fields)) self.end_statement() self.write(";") self.end_statement() print("Setting up member variables...") self.write("protected:\n") with self.indented(): for name, t in spec.statevars: self.statevar_name = name self.declare_field(name, t) self.write("public:\n") with self.indented(): print("Generating constructors...") # default constructor self.begin_statement() self.write("inline ", spec.name, "() ") with self.block(): for name, t in spec.statevars: initial_value = state_exps[name] fvs = free_vars(initial_value) initial_value = subst(initial_value, {v.id : evaluation.construct_value(v.type) for v in fvs}) stm = simplify_and_optimize(SAssign(EVar(name).with_type(t), initial_value)) self.visit(stm) self.end_statement() # explicit constructor if abstract_state: self.begin_statement() self.write("explicit inline ", spec.name, "(") self.visit_args(abstract_state) self.write(") ") with self.block(): for name, t in spec.statevars: initial_value = state_exps[name] self.visit(simplify_and_optimize(SAssign(EVar(name).with_type(t), initial_value))) self.end_statement() # disable copy constructor (TODO: support this in the future?) self.begin_statement() self.write(spec.name, "(const ", spec.name, "& other) = delete;") self.end_statement() # generate methods for op in spec.methods: print("Generating method {}...".format(op.name)) self.visit(op) self.write("};\n") if spec.footer: self.write("\n", spec.footer) if not spec.footer.endswith("\n"): self.write("\n")
def visit_Spec(self, spec: Spec, state_exps: {str: Exp}, sharing, abstract_state=()): self.state_exps = state_exps self.funcs = {f.name: f for f in spec.extern_funcs} self.queries = { q.name: q for q in spec.methods if isinstance(q, Query) } self.vars = set(e.id for e in all_exps(spec) if isinstance(e, EVar)) self.write("#pragma once\n") self.write("#include <algorithm>\n") self.write("#include <set>\n") self.write("#include <functional>\n") self.write("#include <vector>\n") self.write("#include <unordered_set>\n") self.write("#include <string>\n") if self.use_qhash: self.write("#include <QHash>\n") else: self.write("#include <unordered_map>\n") if spec.header: self.write("\n" + spec.header.strip() + "\n") self.write("{}\nclass {} {{\n".format( ("\n" + spec.docstring) if spec.docstring else "", spec.name)) self.write("public:\n") print("Setting up auxiliary types...") self.setup_types(spec, state_exps, sharing) with self.indented(): for t, name in self.types.items(): self.define_type(spec.name, t, name, sharing) self.begin_statement() if isinstance(t, THandle): # No overridden hash code! We use pointers instead. continue self.write("struct _Hash", name, " ") with self.block(): self.write_stmt("typedef ", spec.name, "::", name, " argument_type;") self.write_stmt("typedef std::size_t result_type;") self.begin_statement() self.write( "result_type operator()(const argument_type& x) const noexcept " ) x = EVar("x").with_type(t) if isinstance(t, TEnum): fields = [EEnumToInt(x).with_type(INT)] elif isinstance(t, TRecord): fields = [ EGetField(x, f).with_type(ft) for (f, ft) in t.fields ] elif isinstance(t, TTuple): fields = [ ETupleGet(x, n).with_type(tt) for (n, tt) in enumerate(t.ts) ] else: raise NotImplementedError(t) with self.block(): self.visit(self.compute_hash(fields)) self.end_statement() self.write(";") self.end_statement() print("Setting up member variables...") self.write("protected:\n") with self.indented(): for name, t in spec.statevars: self.statevar_name = name self.declare_field(name, t) self.write("public:\n") with self.indented(): print("Generating constructors...") # default constructor self.begin_statement() self.write("inline ", spec.name, "() ") with self.block(): for name, t in spec.statevars: initial_value = state_exps[name] fvs = free_vars(initial_value) initial_value = subst(initial_value, { v.id: evaluation.construct_value(v.type) for v in fvs }) stm = simplify_and_optimize( SAssign(EVar(name).with_type(t), initial_value)) self.visit(stm) self.end_statement() # explicit constructor if abstract_state: self.begin_statement() self.write("explicit inline ", spec.name, "(") self.visit_args(abstract_state) self.write(") ") with self.block(): for name, t in spec.statevars: initial_value = state_exps[name] self.visit( simplify_and_optimize( SAssign( EVar(name).with_type(t), initial_value))) self.end_statement() # disable copy constructor (TODO: support this in the future?) self.begin_statement() self.write(spec.name, "(const ", spec.name, "& other) = delete;") self.end_statement() # generate methods for op in spec.methods: print("Generating method {}...".format(op.name)) self.visit(op) self.write("};\n") if spec.footer: self.write("\n", spec.footer) if not spec.footer.endswith("\n"): self.write("\n")
def visit_Spec(self, spec, state_exps, sharing, abstract_state=()): self.state_exps = state_exps self.funcs = { f.name: f for f in spec.extern_funcs } self.queries = { q.name: q for q in spec.methods if isinstance(q, Query) } self.vars = set(e.id for e in all_exps(spec) if isinstance(e, EVar)) self.setup_types(spec, state_exps, sharing) if guava.value: self.write("import com.google.common.collect.TreeMultiset;\n") self.write("import com.google.common.collect.Iterators;\n") if spec.header: self.write(spec.header.strip() + "\n\n") if spec.docstring: self.write(spec.docstring + "\n") self.write("public class {} implements java.io.Serializable ".format(spec.name)) with self.block(): for name, t in spec.types: self.types[t] = name # member variables for name, t in spec.statevars: self.write("{}protected {};\n".format(INDENT, self.visit(t, name))) # constructor self.write( "{indent}public {name}() {{\n{indent2}clear();\n{indent}}}\n\n" .format(indent=INDENT, indent2=INDENT+INDENT, name=spec.name)) # explicit constructor if abstract_state: self.begin_statement() self.write("public ", spec.name, "(") self.visit_args(abstract_state) self.write(") ") with self.block(): for name, t in spec.statevars: initial_value = state_exps[name] self.visit(simplify_and_optimize(SAssign(EVar(name).with_type(t), initial_value))) self.end_statement() # clear self.begin_statement() self.write("public void clear() ") with self.block(): for name, t in spec.statevars: initial_value = state_exps[name] fvs = free_vars(initial_value) initial_value = subst(initial_value, {v.id : evaluation.construct_value(v.type) for v in fvs}) setup = simplify_and_optimize(SAssign(EVar(name).with_type(t), initial_value)) self.visit(setup) self.end_statement() # methods for op in spec.methods: self.visit(op) # generate auxiliary types for t, name in self.types.items(): self.define_type(spec.name, t, name, sharing) self.write("\n") self.write(spec.footer) if not spec.footer.endswith("\n"): self.write("\n")
def re_use(value : Exp, v : EVar, s : Stm) -> Stm: if efficiently_reuseable(value) or count_occurrences_of_free_var(s, v) <= 1: return subst(s, {v.id : value}) return seq([SDecl(v, value), s])
def visit_ELet(self, e): value_exp = self.visit(e.e) fv = fresh_var(value_exp.type, e.body_function.arg.id) self.stms.append(SDecl(fv, value_exp)) return self.visit(subst(e.body_function.body, { e.body_function.arg.id : fv }))
def can_elim_vars(spec: Exp, assumptions: Exp, vs: [EVar]): spec = strip_EStateVar(spec) sub = {v.id: fresh_var(v.type) for v in vs} return valid( EImplies(EAll([assumptions, subst(assumptions, sub)]), EEq(spec, subst(spec, sub))))
def for_each(self, iterable: Exp, body) -> str: """Body is function: exp -> stm""" if isinstance(iterable, EEmptyList): return "" elif isinstance(iterable, ESingleton): return self.visit(SScoped(body(iterable.e))) elif isinstance(iterable, ECond): v = self.fv(iterable.type.t, "v") new_body = body(v) assert isinstance(new_body, Stm) return self.visit( SIf(iterable.cond, SForEach(v, iterable.then_branch, new_body), SForEach(v, iterable.else_branch, new_body))) elif isinstance(iterable, EMap): return self.for_each(iterable.e, lambda v: body(iterable.f.apply_to(v))) elif isinstance(iterable, EUnaryOp) and iterable.op == UOp.Distinct: tmp = self.fv(TSet(iterable.type.t), "tmp") self.declare(tmp) self.visit(self.initialize_native_set(tmp)) self.for_each( iterable.e, lambda x: SIf(ENot(EBinOp(x, BOp.In, tmp).with_type(BOOL)), seq([body(x), SCall(tmp, "add", [x])]), SNoOp())) elif isinstance(iterable, EFilter): return self.for_each( iterable.e, lambda x: SIf(iterable.p.apply_to(x), body(x), SNoOp())) elif isinstance(iterable, EBinOp) and iterable.op == "+": self.for_each(iterable.e1, body) self.for_each(iterable.e2, body) elif isinstance(iterable, EBinOp) and iterable.op == "-": t = TList(iterable.type.t) e = self.visit(EBinOp(iterable.e1, "-", iterable.e2).with_type(t)) return self.for_each(EEscape(e, (), ()).with_type(t), body) elif isinstance(iterable, EFlatMap): v = self.fv(iterable.type.t) new_body = body(v) assert isinstance(new_body, Stm) return self.for_each(iterable.e, body=lambda bag: SForEach( v, iterable.f.apply_to(bag), new_body)) elif isinstance(iterable, EListSlice): s = self.fv(INT, "start") e = self.fv(INT, "end") l = self.visit(iterable.e) self.declare(s, iterable.start) self.declare(e, iterable.end) return self.visit( SWhile( ELt(s, e), SSeq( body( EEscape("{l}[{i}]", ("l", "i"), (iterable.e, s)).with_type( iterable.type.t)), SAssign(s, EBinOp(s, "+", ONE).with_type(INT))))) elif isinstance(iterable, ECall) and iterable.func in self.queries: q = self.queries[iterable.func] return self.for_each( subst(q.ret, {a: v for ((a, t), v) in zip(q.args, iterable.args)}), body) elif isinstance(iterable, ELet): return self.for_each(iterable.f.apply_to(iterable.e), body) else: assert is_collection(iterable.type), repr(iterable) x = self.fv(iterable.type.t, "x") return self.for_each_native(x, iterable, body(x))
def apply_to(self, arg): from cozy.syntax_tools import subst return subst(self.body, {self.arg.id: arg})
def for_each(self, iterable: Exp, body, indent="") -> str: """Body is function: exp -> stm""" while isinstance(iterable, EDropFront) or isinstance( iterable, EDropBack): iterable = iterable.e if isinstance(iterable, EEmptyList): return "" elif isinstance(iterable, ESingleton): return self.visit(SScoped(body(iterable.e)), indent=indent) elif isinstance(iterable, ECond): v = self.fv(iterable.type.t, "v") new_body = body(v) assert isinstance(new_body, Stm) return self.visit(SIf(iterable.cond, SForEach(v, iterable.then_branch, new_body), SForEach(v, iterable.else_branch, new_body)), indent=indent) elif isinstance(iterable, EMap): return self.for_each(iterable.e, lambda v: body(iterable.f.apply_to(v)), indent=indent) elif isinstance(iterable, EUnaryOp) and iterable.op == UOp.Distinct: tmp = self.fv(library.TNativeSet(iterable.type.t), "tmp") return "".join(( "{indent}{decl};\n".format(indent=indent, decl=self.visit(tmp.type, tmp.id)), self.visit(self.initialize_native_set(tmp), indent), # TODO: could get better performance out of single "find or insert" primitive self.for_each( iterable.e, lambda x: SIf( ENot(EBinOp(x, BOp.In, tmp).with_type(BOOL)), seq([body(x), SCall(tmp, "add", [x])]), SNoOp()), indent))) elif isinstance(iterable, EFilter): return self.for_each( iterable.e, lambda x: SIf(iterable.p.apply_to(x), body(x), SNoOp()), indent=indent) elif isinstance(iterable, EBinOp) and iterable.op == "+": return self.for_each(iterable.e1, body, indent=indent) + self.for_each( iterable.e2, body, indent=indent) elif isinstance(iterable, EBinOp) and iterable.op == "-": t = library.TNativeList(iterable.type.t) setup, e = self.visit( EBinOp(iterable.e1, "-", iterable.e2).with_type(t), indent) return setup + self.for_each( EEscape(e, (), ()).with_type(t), body, indent) elif isinstance(iterable, EFlatMap): from cozy.syntax_tools import shallow_copy v = self.fv(iterable.type.t) new_body = body(v) assert isinstance(new_body, Stm) return self.for_each(iterable.e, indent=indent, body=lambda bag: SForEach( v, iterable.f.apply_to(bag), new_body)) elif isinstance(iterable, ECall) and iterable.func in self.queries: q = self.queries[iterable.func] return self.for_each(subst( q.ret, {a: v for ((a, t), v) in zip(q.args, iterable.args)}), body, indent=indent) elif isinstance(iterable, ELet): return self.for_each(iterable.f.apply_to(iterable.e), body, indent=indent) else: x = self.fv(iterable.type.t, "x") if type(iterable.type) in (TBag, library.TNativeList, TSet, library.TNativeSet, TList): return self.for_each_native(x, iterable, body(x), indent) return self.visit(iterable.type.for_each(x, iterable, body(x)), indent=indent)
def pull_temps(s : Stm, decls_out : [SDecl], exp_is_bad) -> Stm: """Remove "bad" expressions from `s`. This procedure returns a statement new_s that replaces every expression in `s` where `exp_is_bad` returns True with a fresh variable. After running, `decls_out` contains definitions for the fresh variables so that the whole statement decls_out; new_s should return the same result as `s`. """ def pull(e : Exp) -> Exp: """Pull an expression into a temporary. Creates a fresh variable for `e`, writes a declaration into `decls_out`, and returns the fresh variable. """ if exp_is_bad(e): v = fresh_var(e.type) decls_out.append(SDecl(v, e)) return v return e if isinstance(s, SNoOp): return s if isinstance(s, SSeq): s1 = pull_temps(s.s1, decls_out, exp_is_bad) s2 = pull_temps(s.s2, decls_out, exp_is_bad) return SSeq(s1, s2) if isinstance(s, SDecl): return SDecl(s.var, pull(s.val)) if isinstance(s, SIf): cond = pull(s.cond) s1 = pull_temps(s.then_branch, decls_out, exp_is_bad) s2 = pull_temps(s.else_branch, decls_out, exp_is_bad) return SIf(cond, s1, s2) if isinstance(s, SForEach): bag = pull(s.iter) d_tmp = [] body = pull_temps(s.body, d_tmp, exp_is_bad) to_fix, ok = partition(d_tmp, lambda d: s.loop_var in free_vars(d.val)) decls_out.extend(ok) for d in to_fix: v = d.var mt = TMap(s.loop_var.type, v.type) m = EMakeMap2(bag, ELambda(s.loop_var, d.val)).with_type(mt) mv = fresh_var(m.type) md = SDecl(mv, m) decls_out.append(md) body = subst(body, { v.id : EMapGet(mv, s.loop_var).with_type(v.type) }) return SForEach(s.loop_var, bag, body) if isinstance(s, SAssign): return SAssign(s.lhs, pull(s.rhs)) if isinstance(s, SCall): return SCall(s.target, s.func, tuple(pull(arg) for arg in s.args)) if isinstance(s, SMapDel): return SMapDel(s.map, pull(s.key)) if isinstance(s, SMapPut): return SMapPut(s.map, pull(s.key), pull(s.value)) if isinstance(s, SMapUpdate): key = pull(s.key) d_tmp = [] change = pull_temps(s.change, d_tmp, exp_is_bad) for d in d_tmp: if s.val_var in free_vars(d.val): decls_out.append(SDecl(d.var, subst(d.val, { s.val_var.id : EMapGet(s.map, key).with_type(s.val_var.type) }))) else: decls_out.append(d) return SMapUpdate(s.map, key, s.val_var, change) raise NotImplementedError(s)