def EArgDistinct(bag, key): from cozy.syntax_tools import mk_lambda b = EVar(fresh_name()) distinct_keys = EUnaryOp(UOp.Distinct, EMap(b, key)) res = EMap(distinct_keys, mk_lambda(None, lambda x: EUnaryOp(UOp.The, EFilter(b, mk_lambda(None, lambda y: EEq(x, key.apply_to(y))))))) return ELet(bag, ELambda(b, res))
def make_subgoal(e, a=[], docstring=None): if skip_stateless_synthesis.value and not any(v in ctx for v in free_vars(e)): return e query_name = fresh_name("query") query = syntax.Query(query_name, syntax.Visibility.Internal, [], assumptions + a, e, docstring) query_vars = [v for v in free_vars(query) if v not in ctx] query.args = [(arg.id, arg.type) for arg in query_vars] subgoals.append(query) return syntax.ECall(query_name, tuple(query_vars)).with_type(e.type)
def find_one(self, iterable): v = fresh_var(iterable.type.elem_type, "v") label = fresh_name("label") x = fresh_var(iterable.type.elem_type, "x") decl = SDecl(v, evaluation.construct_value(v.type)) find = SEscapableBlock(label, SForEach(x, iterable, seq([ SAssign(v, x), SEscapeBlock(label)]))) self.stms.append(simplify_and_optimize(seq([decl, find]))) return v
def visit_EUnaryOp(self, e): op = e.op if op == UOp.Distinct: return self.visit_iterable(e) elif op == UOp.The: return self.find_one(e.e) elif op == UOp.Sum: sum_var = fresh_var(e.type, "sum") loop_var = fresh_var(e.e.type.elem_type, "x") self.stms.append(simplify_and_optimize(seq([ SDecl(sum_var, ENum(0).with_type(e.type)), SForEach(loop_var, e.e, SAssign(sum_var, EBinOp(sum_var, "+", loop_var).with_type(INT)))]))) return sum_var elif op == UOp.Length: arg = EVar("x").with_type(e.e.type.elem_type) return self.visit(EUnaryOp(UOp.Sum, EMap(e.e, ELambda(arg, ONE)).with_type(INT_BAG)).with_type(INT)) elif op == UOp.All: arg = EVar("x").with_type(e.e.type.elem_type) return self.visit(EUnaryOp(UOp.Empty, EFilter(e.e, ELambda(arg, ENot(arg))).with_type(INT_BAG)).with_type(INT)) elif op == UOp.Any: arg = EVar("x").with_type(e.e.type.elem_type) return self.visit(EUnaryOp(UOp.Exists, EFilter(e.e, ELambda(arg, arg)).with_type(INT_BAG)).with_type(INT)) elif op == UOp.Empty: iterable = e.e v = fresh_var(BOOL, "v") label = fresh_name("label") x = fresh_var(iterable.type.elem_type, "x") decl = SDecl(v, ETRUE) find = SEscapableBlock(label, SForEach(x, iterable, seq([ SAssign(v, EFALSE), SEscapeBlock(label)]))) self.stms.append(simplify_and_optimize(seq([decl, find]))) return v elif op == UOp.Exists: return self.visit(ENot(EUnaryOp(UOp.Empty, e.e).with_type(BOOL))) # elif op == UOp.AreUnique: # s = fresh_var(TSet(e.e.type.elem_type), "unique_elems") # u = fresh_var(BOOL, "is_unique") # x = fresh_var(e.e.type.elem_type) # label = fresh_name("label") # self.visit(seq([ # SDecl(s, EEmptyList().with_type(s.type)), # SDecl(u, ETRUE), # SEscapableBlock(label, # SForEach(x, e.e, # SIf(EEscape("{s}.find({x}) != {s}.end()", ("s", "x"), (s, x)).with_type(BOOL), # seq([SAssign(u, EFALSE), SEscapeBlock(label)]), # SEscape("{indent}{s}.insert({x});\n", ("s", "x"), (s, x)))))])) # return u.id return self.visit_Exp(e)
def visit_EBinOp(self, e): op = e.op if op == "+" and (isinstance(e.e1.type, TBag) or isinstance(e.e1.type, TSet)): raise NotImplementedError("adding collections: {}".format(e)) elif op == "==": return self._eq(e.e1, e.e2) elif op == "===": # rewrite deep-equality test into regular equality op = "==" elif op == "!=": return self.visit(ENot(EEq(e.e1, e.e2))) elif op == BOp.Or: return self.visit(ECond(e.e1, T, e.e2).with_type(BOOL)) elif op == BOp.And: return self.visit(ECond(e.e1, e.e2, F).with_type(BOOL)) elif op == "-" and is_collection(e.type): t = e.type v = self.fv(t, "v") x = self.fv(t.t, "x") self.declare(v, e.e1) self.visit(SForEach(x, e.e2, SCall(v, "remove", [x]))) return v.id elif op == BOp.In: if isinstance(e.e2.type, TSet): return self.test_set_containment_native(e.e2, e.e1) else: t = BOOL res = self.fv(t, "found") x = self.fv(e.e1.type, "x") label = fresh_name("label") self.visit( seq([ SDecl(res.id, F), SEscapableBlock( label, SForEach( x, e.e2, SIf( EBinOp(x, "==", e.e1).with_type(BOOL), seq([SAssign(res, T), SEscapeBlock(label)]), SNoOp()))) ])) return res.id return "({e1} {op} {e2})".format(e1=self.visit(e.e1), op=op, e2=self.visit(e.e2))
def visit_EBinOp(self, e): if e.op in ("+", "-") and is_collection(e.type): return self.visit_iterable(e) elif e.op == BOp.In and not isinstance(e.e2.type, TSet): t = BOOL res = fresh_var(t, "found") x = fresh_var(e.e1.type, "x") label = fresh_name("label") self.stms.append(simplify_and_optimize(seq([ SDecl(res, EFALSE), SEscapableBlock(label, SForEach(x, e.e2, SIf( EBinOp(x, "==", e.e1).with_type(BOOL), seq([SAssign(res, ETRUE), SEscapeBlock(label)]), SNoOp())))]))) return res return self.visit_Exp(e)
def array_resize_for_index(self, elem_type, a, i): """Resize the array until `i` is a legal index. When i < 0, it will do nothing instead. """ new_a = fresh_name(hint="new_array") if elem_type == BOOL: t = "long" else: t = self.strip_generics(self.visit(elem_type, name="")) len = EEscape("{a}.length", ["a"], [a]).with_type(INT) double_and_incr_size = SEscape( "{{indent}}{t}[] {new_a} = new {t}[({{len}} << 1) + 1];\n{{indent}}System.arraycopy({{a}}, 0, {new_a}, 0, {{len}});\n{{indent}}{{a}} = {new_a};\n".format(t=t, new_a=new_a), ["a", "len"], [a, len]) self.visit(SWhile( EAll([EBinOp(i, ">=", ZERO).with_type(BOOL), ENot(self.array_in_bounds(elem_type, a, i))]), double_and_incr_size))
def for_each(self, id, iter, body): assert iter.type == self # assert isinstance(iter, EVar), pprint(iter) iter = shallow_copy(iter).with_type(self.rep_type()) assert id.type == self.t next = fresh_name("next") return seq([ SDecl(id.id, iter), SWhile( ENot(equal(id, self.null)), seq([ SDecl(next, EGetField(id, self.next_ptr).with_type(id.type)), body, SAssign(id, EVar(next).with_type(id.type)) ])) ])
def multi(ldict, selfname, production, sep=None): """ Usage: multi(locals(), NAME, P[, sep=SEP]) where P and SEP are production names. This produces a production named NAME of the form NAME ::= empty | P (SEP P)* that produces tuples of whatever P produces. """ if sep is None: sep = "empty" f1name = fresh_name("multisep") def f1(p): if len(p) > 2 and p[3]: p[0] = (p[1], ) + p[3] else: p[0] = (p[1], ) f1.__doc__ = ("""{f1name} : {prod} | {prod} {sep} {f1name}""".format(f1name=f1name, prod=production, sep=sep)) f1.__name__ = "p_{}".format(f1name) ldict[f1.__name__] = f1 def f2(p): if p[1]: p[0] = p[1] else: p[0] = () f2.__doc__ = ("""{self} : empty | {f1name}""".format(self=selfname, f1name=f1name)) f2.__name__ = "p_{}".format(selfname) ldict[f2.__name__] = f2
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: print("Setting up handle updates for {}...".format(op.name)) 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) lval = EGetField(h, "val").with_type(t.value_type) new_val = inc.mutate(lval, op.body) # 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 subqueries = [] state_update_stm = inc.mutate_in_place( lval, lval, op.body, abstract_state=self.abstract_state, assumptions=list(op.assumptions) + [EDeepIn(h, bag), EIn(h, modified_handles.ret)], 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) 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 _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: print("Setting up handle updates for {}...".format(op.name)) 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) lval = EGetField(h, "val").with_type(t.value_type) new_val = inc.mutate(lval, op.body) # 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 subqueries = [] state_update_stm = inc.mutate_in_place( lval, lval, op.body, abstract_state=self.abstract_state, assumptions=list(op.assumptions) + [EDeepIn(h, bag), EIn(h, modified_handles.ret)], 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) 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 multi(ldict, selfname, production, sep=None): """ Usage: multi(locals(), NAME, P[, sep=SEP]) where P and SEP are production names. This produces a production named NAME of the form NAME ::= empty | P (SEP P)* that produces tuples of whatever P produces. """ if sep is None: sep = "empty" f1name = fresh_name("multisep") def f1(p): if len(p) > 2 and p[3]: p[0] = (p[1],) + p[3] else: p[0] = (p[1],) f1.__doc__ = ( """{f1name} : {prod} | {prod} {sep} {f1name}""".format(f1name=f1name, prod=production, sep=sep)) f1.__name__ = "p_{}".format(f1name) ldict[f1.__name__] = f1 def f2(p): if p[1]: p[0] = p[1] else: p[0] = () f2.__doc__ = ( """{self} : empty | {f1name}""".format(self=selfname, f1name=f1name)) f2.__name__ = "p_{}".format(selfname) ldict[f2.__name__] = f2
def visit_SForEach(self, for_each): x = for_each.loop_var iterable = for_each.iter body = for_each.body if not self.boxed and self.trovename(x.type) != "Object": iterable_src = self.visit(iterable) itname = fresh_name("iterator") self.write_stmt("gnu.trove.iterator.T{T}Iterator {it} = {iterable}.iterator();".format( it=itname, iterable=iterable_src, T=self.trovename(x.type))) self.begin_statement() self.write("while ({it}.hasNext()) ".format(it=itname)) with self.block(): self.declare(x, EEscape("{it}.next()".format(it=itname), (), ()).with_type(x.type)) self.visit(body) self.end_statement() return iterable = self.visit(iterable) self.begin_statement() self.write("for (", self.visit(x.type, x.id), " : ", iterable, ") ") with self.block(): self.visit(body) self.end_statement()
def visit_SEscapableBlock(self, s): l = fresh_name("label") with extend(self.labels, s.label, l): self.visit(SScoped(s.body)) self.write(l, ":\n")
def fn(self, hint="var"): n = common.fresh_name(hint, omit=self.vars) self.vars.add(n) return n
def update_key(self, m: Exp, k: Exp, v: EVar, change: Stm): idx = EVar(fresh_name("index")).with_type(TInt()) return seq([ SDecl(idx.id, self.to_index(k)), subst(change, {v.id: EVectorGet(m, idx)}) ])
def __init__(self, t): super().__init__(t) self.next_ptr = fresh_name("next_ptr") self.prev_ptr = fresh_name("prev_ptr") self.null = ENull().with_type(self.rep_type())
def implement_stmt(self, s: Stm, concretization_functions: {str: Exp}) -> Stm: """Convert a call to a heap function into simpler statements. This function also requires the `concretization_functions` that describe the invariants for variables in `e`. """ comparison_op = "<=" if isinstance(s.target.type, TMinHeap) else ">=" f = heap_func(s.target, concretization_functions) if isinstance(s, SCall): elem_type = s.target.type.elem_type target_raw = EVar(s.target.id).with_type( self.rep_type(s.target.type)) if s.func == "add_all": size = fresh_var(INT, "heap_size") i = fresh_var(INT, "i") x = fresh_var(elem_type, "x") return seq([ SDecl(size.id, s.args[0]), SEnsureCapacity( target_raw, EBinOp(size, "+", ELen(s.args[1])).with_type(INT)), SForEach( x, s.args[1], seq([ SAssign(EArrayGet(target_raw, size), x), SDecl(i.id, size), SWhile( EAll([ EBinOp(i, ">", ZERO).with_type(BOOL), ENot( EBinOp( f.apply_to( EArrayGet( target_raw, _parent(i))), comparison_op, f.apply_to(EArrayGet( target_raw, i))).with_type(BOOL)) ]), seq([ SSwap(EArrayGet(target_raw, _parent(i)), EArrayGet(target_raw, i)), SAssign(i, _parent(i)) ])), SAssign(size, EBinOp(size, "+", ONE).with_type(INT)) ])) ]) elif s.func == "remove_all": size = fresh_var(INT, "heap_size") size_minus_one = EBinOp(size, "-", ONE).with_type(INT) i = fresh_var(INT, "i") x = fresh_var(elem_type, "x") label = fresh_name("stop_bubble_down") child_index = fresh_var(INT, "child_index") return seq([ SDecl(size.id, s.args[0]), SForEach( x, s.args[1], seq([ # find the element to remove SDecl(i.id, EArrayIndexOf(target_raw, x).with_type(INT)), # swap with last element in heap SSwap(EArrayGet(target_raw, i), EArrayGet(target_raw, size_minus_one)), # bubble down SEscapableBlock( label, SWhile( _has_left_child(i, size_minus_one), seq([ SDecl(child_index.id, _left_child(i)), SIf( EAll([ _has_right_child( i, size_minus_one), ENot( EBinOp( f.apply_to( EArrayGet( target_raw, _left_child( i))), comparison_op, f.apply_to( EArrayGet( target_raw, _right_child( i))))) ]), SAssign(child_index, _right_child(i)), SNoOp()), SIf( ENot( EBinOp( f.apply_to( EArrayGet( target_raw, i)), comparison_op, f.apply_to( EArrayGet( target_raw, child_index)))), seq([ SSwap( EArrayGet(target_raw, i), EArrayGet( target_raw, child_index)), SAssign(i, child_index) ]), SEscapeBlock(label)) ]))), # dec. size SAssign(size, size_minus_one) ])) ]) else: raise ValueError("heaps do not support the function {}".format( s.func)) else: raise ValueError( "the statement {} is not an update to a heap variable".format( pprint(s)))
def implement_stmt(self, s : Stm, concretization_functions : { str : Exp }) -> Stm: """Convert a call to a heap function into simpler statements. This function also requires the `concretization_functions` that describe the invariants for variables in `e`. """ comparison_op = "<=" if isinstance(s.target.type, TMinHeap) else ">=" f = heap_func(s.target, concretization_functions) if isinstance(s, SCall): elem_type = s.target.type.elem_type target_raw = EVar(s.target.id).with_type(self.rep_type(s.target.type)) target_len = ETupleGet(target_raw, 0).with_type(INT) target_array = ETupleGet(target_raw, 1).with_type(TArray(elem_type)) if s.func == "add_all": size = fresh_var(INT, "heap_size") i = fresh_var(INT, "i") x = fresh_var(elem_type, "x") return seq([ SDecl(size, s.args[0]), SEnsureCapacity(target_array, EBinOp(size, "+", ELen(s.args[1])).with_type(INT)), SForEach(x, s.args[1], seq([ SAssign(target_len, EBinOp(target_len, "+", ONE).with_type(INT)), SAssign(EArrayGet(target_array, size).with_type(elem_type), x), SDecl(i, size), SWhile(EAll([ EBinOp(i, ">", ZERO).with_type(BOOL), ENot(EBinOp(f.apply_to(EArrayGet(target_array, _parent(i)).with_type(elem_type)), comparison_op, f.apply_to(EArrayGet(target_array, i).with_type(elem_type))).with_type(BOOL))]), seq([ SSwap(EArrayGet(target_array, _parent(i)).with_type(elem_type), EArrayGet(target_array, i).with_type(elem_type)), SAssign(i, _parent(i))])), SAssign(size, EBinOp(size, "+", ONE).with_type(INT))]))]) elif s.func == "remove_all": size = fresh_var(INT, "heap_size") size_minus_one = EBinOp(size, "-", ONE).with_type(INT) i = fresh_var(INT, "i") x = fresh_var(elem_type, "x") label = fresh_name("stop_bubble_down") child_index = fresh_var(INT, "child_index") return seq([ SDecl(size, s.args[0]), SForEach(x, s.args[1], seq([ SAssign(target_len, EBinOp(target_len, "-", ONE).with_type(INT)), # find the element to remove SDecl(i, EArrayIndexOf(target_array, x).with_type(INT)), # swap with last element in heap SSwap(EArrayGet(target_array, i).with_type(elem_type), EArrayGet(target_array, size_minus_one).with_type(elem_type)), # bubble down SEscapableBlock(label, SWhile(_has_left_child(i, size_minus_one), seq([ SDecl(child_index, _left_child(i)), SIf(EAll([_has_right_child(i, size_minus_one), ENot(EBinOp(f.apply_to(EArrayGet(target_array, _left_child(i)).with_type(elem_type)), comparison_op, f.apply_to(EArrayGet(target_array, _right_child(i)).with_type(elem_type))))]), SAssign(child_index, _right_child(i)), SNoOp()), SIf(ENot(EBinOp(f.apply_to(EArrayGet(target_array, i).with_type(elem_type)), comparison_op, f.apply_to(EArrayGet(target_array, child_index).with_type(elem_type)))), seq([ SSwap(EArrayGet(target_array, i).with_type(elem_type), EArrayGet(target_array, child_index).with_type(elem_type)), SAssign(i, child_index)]), SEscapeBlock(label))]))), # dec. size SAssign(size, size_minus_one)]))]) elif s.func == "update": # TODO: implement this return SNoOp() else: raise ValueError("heaps do not support the function {}".format(s.func)) else: raise ValueError("the statement {} is not an update to a heap variable".format(pprint(s)))
def visit_EBinOp(self, e, indent=""): op = e.op if op == "+" and (isinstance(e.e1.type, TBag) or isinstance(e.e1.type, TSet)): raise NotImplementedError("adding collections: {}".format(e)) elif op == "==": return self._eq(e.e1, e.e2, indent) elif op == BOp.In: if isinstance(e.e2.type, TSet): if type(e.e2.type) in (TSet, library.TNativeSet): return self.test_set_containment_native(e.e2, e.e1, indent) else: return self.visit(e.e2.type.contains(e.e1), indent) else: t = TBool() res = self.fv(t, "found") x = self.fv(e.e1.type, "x") label = fresh_name("label") setup = self.visit( seq([ SDecl(res.id, EBool(False).with_type(t)), SEscapableBlock( label, SForEach( x, e.e2, SIf( EBinOp(x, "==", e.e1).with_type(BOOL), seq([ SAssign(res, EBool(True).with_type(t)), SEscapeBlock(label) ]), SNoOp()))) ]), indent) return (setup, res.id) elif op == "-" and (isinstance(e.type, TBag) or isinstance( e.type, TList) or isinstance(e.type, TSet)): t = e.type if type(t) is TBag: t = library.TNativeList(t.t) v = self.fv(t, "v") x = self.fv(t.t, "x") stm = self.visit(SForEach(x, e.e2, SCall(v, "remove", [x])), indent) return ( "{}{};\n".format(indent, self.visit(v.type, v.id)) + self.visit(self.construct_concrete(v.type, e.e1, v), indent) + stm, v.id) elif op == BOp.Or: (s1, r1) = self.visit(e.e1, indent) (s2, r2) = self.visit(e.e2, indent) if s2: return self.visit( ECond(e.e1, EBool(True), e.e2).with_type(TBool()), indent) else: return (s1, "({} || {})".format(r1, r2)) elif op == BOp.And: (s1, r1) = self.visit(e.e1, indent) (s2, r2) = self.visit(e.e2, indent) if s2: return self.visit( ECond(e.e1, e.e2, EBool(False)).with_type(TBool()), indent) else: return (s1, "({} && {})".format(r1, r2)) ce1, e1 = self.visit(e.e1, indent) ce2, e2 = self.visit(e.e2, indent) return (ce1 + ce2, "({e1} {op} {e2})".format(e1=e1, op=op, e2=e2))
def visit_EUnaryOp(self, e, indent=""): op = e.op if op == UOp.The: return self.find_one(e.e, indent=indent) elif op == UOp.Sum: type = e.e.type.t res = self.fv(type, "sum") x = self.fv(type, "x") setup = self.visit( seq([ SDecl(res.id, ENum(0).with_type(type)), SForEach(x, e.e, SAssign(res, EBinOp(res, "+", x).with_type(type))) ]), indent) return (setup, res.id) elif op == UOp.Length: arg = EVar("x").with_type(e.e.type.t) return self.visit( EUnaryOp(UOp.Sum, EMap(e.e, ELambda(arg, ONE)).with_type(INT_BAG)).with_type(INT), indent) elif op == UOp.All: arg = EVar("x").with_type(e.e.type.t) return self.visit( EUnaryOp( UOp.Empty, EFilter(e.e, ELambda( arg, ENot(arg))).with_type(INT_BAG)).with_type(INT), indent) elif op == UOp.Any: arg = EVar("x").with_type(e.e.type.t) return self.visit( EUnaryOp(UOp.Exists, EFilter(e.e, ELambda( arg, arg)).with_type(INT_BAG)).with_type(INT), indent) elif op == UOp.Empty: iterable = e.e v = self.fv(BOOL, "v") label = fresh_name("label") x = self.fv(iterable.type.t, "x") decl = SDecl(v.id, T) find = SEscapableBlock( label, SForEach(x, iterable, seq([SAssign(v, F), SEscapeBlock(label)]))) return (self.visit(seq([decl, find]), indent), v.id) elif op == UOp.Exists: return self.visit(ENot(EUnaryOp(UOp.Empty, e.e).with_type(BOOL)), indent) elif op in ("-", UOp.Not): ce, ee = self.visit(e.e, indent) op_str = "!" if op == UOp.Not else str(op) return (ce, "({op}{ee})".format(op=op_str, ee=ee)) elif op == UOp.Distinct: v = self.fv(e.type, "v") stm = self.construct_concrete(e.type, e, v) return ("{}{};\n".format(indent, self.visit(e.type, v.id)) + self.visit(stm, indent), v.id) elif op == UOp.Reversed: v = self.fv(e.type, "v") stm = self.construct_concrete(e.type, e.e, v) stm = seq([stm, self.reverse_inplace(v)]) return ("{}{};\n".format(indent, self.visit(e.type, v.id)) + self.visit(stm, indent), v.id) else: raise NotImplementedError(op)
def visit_ENative(self, e, indent): assert e.e == ENum(0) v = fresh_name("tmp") decl = self.visit(e.type, v) return ("{}{};\n".format(indent, decl), v)
def visit_EUnaryOp(self, e): op = e.op if op == UOp.The: return self.find_one(e.e) elif op == UOp.Sum: type = e.e.type.t res = self.fv(type, "sum") x = self.fv(type, "x") self.visit( seq([ SDecl(res.id, ENum(0).with_type(type)), SForEach(x, e.e, SAssign(res, EBinOp(res, "+", x).with_type(type))) ])) return res.id elif op == UOp.Length: arg = EVar("x").with_type(e.e.type.t) return self.visit( EUnaryOp(UOp.Sum, EMap(e.e, ELambda(arg, ONE)).with_type(INT_BAG)).with_type(INT)) elif op == UOp.All: arg = EVar("x").with_type(e.e.type.t) return self.visit( EUnaryOp( UOp.Empty, EFilter(e.e, ELambda( arg, ENot(arg))).with_type(INT_BAG)).with_type(INT)) elif op == UOp.Any: arg = EVar("x").with_type(e.e.type.t) return self.visit( EUnaryOp(UOp.Exists, EFilter(e.e, ELambda( arg, arg)).with_type(INT_BAG)).with_type(INT)) elif op == UOp.Empty: iterable = e.e v = self.fv(BOOL, "v") label = fresh_name("label") x = self.fv(iterable.type.t, "x") decl = SDecl(v.id, T) find = SEscapableBlock( label, SForEach(x, iterable, seq([SAssign(v, F), SEscapeBlock(label)]))) self.visit(seq([decl, find])) return v.id elif op == UOp.Exists: return self.visit(ENot(EUnaryOp(UOp.Empty, e.e).with_type(BOOL))) elif op in ("-", UOp.Not): ee = self.visit(e.e) op_str = "!" if op == UOp.Not else op return "({op}({ee}))".format(op=op_str, ee=ee) elif op == UOp.Distinct: v = self.fv(e.type, "v") self.declare(v, e) return v.id elif op == UOp.AreUnique: s = self.fv(TSet(e.e.type.t), "unique_elems") u = self.fv(BOOL, "is_unique") x = self.fv(e.e.type.t) label = fresh_name("label") self.visit( seq([ SDecl(s.id, EEmptyList().with_type(s.type)), SDecl(u.id, T), SEscapableBlock( label, SForEach( x, e.e, SIf( EEscape("{s}.find({x}) != {s}.end()", ("s", "x"), (s, x)).with_type(BOOL), seq([SAssign(u, F), SEscapeBlock(label)]), SEscape("{indent}{s}.insert({x});\n", ("s", "x"), (s, x))))) ])) return u.id elif op == UOp.Reversed: v = self.fv(e.type, "v") self.declare(v, e.e) self.visit(self.reverse_inplace(v)) return v.id else: raise NotImplementedError(op)
def EIsSingleton(e): arg = EVar(fresh_name()).with_type(e.type.elem_type) return EBinOp(EUnaryOp(UOp.Sum, EMap(e, ELambda(arg, ONE)).with_type(TBag(INT))).with_type(INT), "<=", ONE).with_type(BOOL)
def code(self) -> Spec: state_read_by_query = { query_name: free_vars(query) for query_name, query in self.query_impls.items() } def queries_used_by(stm): for e in all_exps(stm): if isinstance(e, ECall) and e.func in [ q.name for q in self.query_specs ]: yield e.func # prevent read-after-write by lifting reads before writes. # list of SDecls temps = defaultdict(list) updates = dict(self.updates) for operator in self.op_specs: # Compute order constraints between statements: # v1 -> v2 means that the update code for v1 should (if possible) # appear before the update code for v2 # (i.e. the update code for v1 reads v2) g = igraph.Graph().as_directed() g.add_vertices(len(self.concrete_state)) for (i, (v1, _)) in enumerate(self.concrete_state): v1_update_code = self.updates[(v1, operator.name)] v1_queries = list(queries_used_by(v1_update_code)) for (j, (v2, _)) in enumerate(self.concrete_state): # if v1_update_code reads v2... if any(v2 in state_read_by_query[q] for q in v1_queries): # then v1->v2 g.add_edges([(i, j)]) # Find the minimum set of edges we need to break (see "feedback arc # set problem") edges_to_break = safe_feedback_arc_set(g, method="ip") g.delete_edges(edges_to_break) ordered_concrete_state = [ self.concrete_state[i] for i in g.topological_sorting(mode="OUT") ] # Lift auxiliary declarations as needed things_updated = [] for v, _ in ordered_concrete_state: things_updated.append(v) stm = updates[(v, operator.name)] for e in all_exps(stm): if isinstance(e, ECall) and e.func in [ q.name for q in self.query_specs ]: problems = set(things_updated) & state_read_by_query[ e.func] if problems: name = fresh_name() temps[operator.name].append(SDecl(name, e)) stm = replace(stm, e, EVar(name).with_type(e.type)) updates[(v, operator.name)] = stm # construct new op implementations new_ops = [] for op in self.op_specs: stms = [updates[(v, op.name)] for (v, _) in ordered_concrete_state] stms.extend(hup for ((t, op_name), hup) in self.handle_updates.items() if op.name == op_name) new_stms = seq(temps[op.name] + stms) new_ops.append(Op(op.name, op.args, [], new_stms, op.docstring)) # assemble final result return Spec(self.spec.name, self.spec.types, self.spec.extern_funcs, [(v.id, e.type) for (v, e) in self.concrete_state], [], list(self.query_impls.values()) + new_ops, self.spec.header, self.spec.footer, self.spec.docstring)