def codegen(self, e : Exp, concretization_functions : { str : Exp }, out : EVar) -> Stm: if isinstance(e, EMakeMinHeap) or isinstance(e, EMakeMaxHeap): out_raw = EVar(out.id).with_type(self.rep_type(e.type)) l = fresh_var(INT, "alloc_len") x = fresh_var(e.type.elem_type, "x") return seq([ SDecl(l.id, ELen(e.e)), SArrayAlloc(out_raw, l), SCall(out, "add_all", (ZERO, e.e))]) elif isinstance(e, EHeapElems): elem_type = e.type.t if isinstance(e.e, EMakeMinHeap) or isinstance(e.e, EMakeMaxHeap): x = fresh_var(elem_type, "x") return SForEach(x, e.e.e, SCall(out, "add", (x,))) i = fresh_var(INT, "i") return seq([ SDecl(i.id, ZERO), SWhile(ELt(i, EArrayLen(e.e).with_type(INT)), seq([ SCall(out, "add", (EArrayGet(e.e, i).with_type(elem_type),)), SAssign(i, EBinOp(i, "+", ONE).with_type(INT))]))]) elif isinstance(e, EHeapPeek): raise NotImplementedError() elif isinstance(e, EHeapPeek2): from cozy.evaluation import construct_value best = EArgMin if isinstance(e.e.type, TMinHeap) else EArgMax f = heap_func(e.e, concretization_functions) return SSwitch(e.n, ( (ZERO, SAssign(out, construct_value(e.type))), (ONE, SAssign(out, construct_value(e.type))), (TWO, SAssign(out, EArrayGet(e.e, ONE).with_type(e.type)))), SAssign(out, best(EBinOp(ESingleton(EArrayGet(e.e, ONE).with_type(e.type)).with_type(TBag(out.type)), "+", ESingleton(EArrayGet(e.e, TWO).with_type(e.type)).with_type(TBag(out.type))).with_type(TBag(out.type)), f).with_type(out.type))) else: raise NotImplementedError(e)
def optimized_distinct(xs, args): if isinstance(xs, EEmptyList) or isinstance(xs, ESingleton): yield xs return if isinstance(xs, EStateVar): yield EStateVar(EUnaryOp(UOp.Distinct, xs.e).with_type(xs.type)).with_type(xs.type) if isinstance(xs, EBinOp): if xs.op == "+": v = fresh_var(xs.type.elem_type) for a in optimized_distinct(xs.e1, args): for b in optimized_distinct(xs.e2, args): for b_prime in optimized_filter( b, ELambda(v, ENot(optimized_in(v, a))), args): yield EBinOp(a, "+", b_prime).with_type(xs.type) if xs.op == "-": v = fresh_var(xs.type.elem_type) for a in optimized_distinct(xs.e1, args): for b in optimized_distinct(xs.e2, args): yield from optimized_filter( a, ELambda(v, ENot(optimized_in(v, b))), args) if isinstance(xs, EFilter): for ee in optimized_distinct(xs.e, args): yield EFilter(ee, xs.predicate).with_type(xs.type) yield EUnaryOp(UOp.Distinct, xs).with_type(xs.type)
def uses_intrusive_data(e: target_syntax.Exp, handle: target_syntax.Exp) -> target_syntax.Exp: if isinstance(e, target_syntax.EMakeMap): if isinstance(e.e.type, target_syntax.TBag) and e.e.type.t == handle.type: k = e.key.apply_to(handle) kk = syntax_tools.fresh_var(k.type, "k") return uses_intrusive_data( e.value.apply_to( target_syntax.EFilter( e.e, target_syntax.ELambda(handle, syntax_tools.equal(k, kk)))), handle) return target_syntax.F elif isinstance(e, target_syntax.EMakeMap2): if e.e.type.t == handle.type: k = syntax_tools.fresh_var(e.type.k) return target_syntax.EImplies( target_syntax.EBinOp(k, target_syntax.BOp.In, e.e), uses_intrusive_data(e.value.apply_to(k), handle)) return target_syntax.F elif isinstance(e, target_syntax.EFilter): return target_syntax.EAll( [uses_intrusive_data(e.e, handle), e.p.apply_to(handle)]) elif isinstance(e, target_syntax.EEmptyList): return target_syntax.F elif isinstance(e, target_syntax.EMap): return uses_intrusive_data(e.e, handle) elif isinstance(e, target_syntax.EUnaryOp): return uses_intrusive_data(e.e, handle) elif isinstance(e, target_syntax.EBinOp): return uses_intrusive_data(e.e1, handle) or uses_intrusive_data( e.e2, handle) elif isinstance(e, target_syntax.ECond): return target_syntax.ECond(e.cond, uses_intrusive_data(e.then_branch, handle), uses_intrusive_data(e.else_branch, handle)).with_type( target_syntax.BOOL) elif isinstance(e, target_syntax.ESingleton): if e.type.t == handle.type: return target_syntax.EEq(e.e, handle) return target_syntax.F elif isinstance(e, target_syntax.ETuple): return target_syntax.EAny( uses_intrusive_data(ee, handle) for ee in e.es) elif isinstance(e, target_syntax.EVar): if isinstance(e.type, target_syntax.TBag) and e.type.t == handle.type: return target_syntax.EBinOp(handle, target_syntax.BOp.In, e).with_type(target_syntax.BOOL) return target_syntax.F elif type(e) in [ target_syntax.ENum, target_syntax.EBool, target_syntax.EEnumEntry ]: return target_syntax.F else: raise NotImplementedError(e)
def test_edeepin(self): ht = THandle("H", INT) hb = EVar("hb").with_type(TBag(ht)) h = fresh_var(ht, omit=free_vars(hb)) arg = fresh_var(ht, omit=free_vars(h)|free_vars(hb)) f1 = EDeepIn(h, hb) f2 = EUnaryOp(UOp.Any, EMap(hb, ELambda(arg, EBinOp(arg, "===", h).with_type(BOOL))).with_type(BOOL_BAG)).with_type(BOOL) self.assert_same(f1, f2)
def visit_EMakeMap2(self, e): res = fresh_var(e.type, "map") self.stms.append(SDecl(res, EEmptyMap().with_type(e.type))) k = e.value_function.arg v = fresh_var(e.type.v, "v") self.stms.append(simplify_and_optimize(SForEach(k, e.e, SMapUpdate(res, k, v, SAssign(v, e.value_function.body))))) return EMove(res).with_type(res.type)
def test_distinct_foreach(self): with io.StringIO() as f: codgen = CxxPrinter(out=f) bag = EFilter(EVar("v").with_type(TBag(INT)), mk_lambda(INT, lambda x: EBinOp(x, ">", ZERO))).with_type(TBag(INT)) x = fresh_var(INT) v = fresh_var(INT) stm = SForEach(x, EUnaryOp(UOp.Distinct, bag).with_type(TSet(INT)), SAssign(v, x)) codgen.visit(stm)
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 storage_size(e, freebies: [Exp] = []): h = extension_handler(type(e.type)) if h is not None: return h.storage_size(e, storage_size=storage_size) if e in freebies: return ZERO elif e.type == BOOL: return ONE elif is_numeric(e.type) or isinstance(e.type, THandle): return FOUR elif isinstance(e.type, TEnum): return TWO elif isinstance(e.type, TNative): return FOUR elif isinstance(e.type, TString): return TWENTY elif isinstance(e.type, TTuple): return ESum([ storage_size(ETupleGet(e, n).with_type(t)) for (n, t) in enumerate(e.type.ts) ]) elif isinstance(e.type, TRecord): return ESum([ storage_size(EGetField(e, f).with_type(t)) for (f, t) in e.type.fields ]) elif is_collection(e.type): v = fresh_var(e.type.t, omit=free_vars(e)) return ESum([ FOUR, EUnaryOp(UOp.Sum, EMap(e, ELambda( v, storage_size(v))).with_type(INT_BAG)).with_type(INT) ]) elif isinstance(e.type, TMap): k = fresh_var(e.type.k, omit=free_vars(e)) return ESum([ FOUR, EUnaryOp( UOp.Sum, EMap( EMapKeys(e).with_type(TBag(e.type.k)), ELambda( k, ESum([ storage_size(k), storage_size(EMapGet(e, k).with_type(e.type.v)) ]))).with_type(INT_BAG)).with_type(INT) ]) else: raise NotImplementedError(e.type)
def codegen(self, e : Exp, concretization_functions : { str : Exp }, out : EVar) -> Stm: """Return statements that write the result of `e` to `out`. The returned statements must declare the variable `out`; it will not be declared by the caller. This function also requires the `concretization_functions` that describe the invariants for variables in `e`. """ if isinstance(e, EMakeMinHeap) or isinstance(e, EMakeMaxHeap): assert out.type == self.rep_type(e.type) elem_type = e.type.elem_type extended_concretization_functions = dict(concretization_functions) extended_concretization_functions[out.id] = e dummy_out = EVar(out.id).with_type(e.type) a = fresh_var(TArray(elem_type), "heap_elems") return seq([ SArrayAlloc(a, ZERO), SDecl(out, ETuple((ZERO, a)).with_type(out.type)), self.implement_stmt(SCall(dummy_out, "add_all", (ZERO, e.e)), extended_concretization_functions)]) elif isinstance(e, EHeapElems): elem_type = e.type.elem_type if isinstance(e.e, EMakeMinHeap) or isinstance(e.e, EMakeMaxHeap): x = fresh_var(elem_type, "x") return seq([ SDecl(out, EEmptyList().with_type(out.type)), SForEach(x, e.e.e, SCall(out, "add", (x,)))]) i = fresh_var(INT, "i") # the array index return seq([ SDecl(out, EEmptyList().with_type(out.type)), SDecl(i, ZERO), SWhile(ELt(i, ETupleGet(e.e, 0).with_type(INT)), seq([ SCall(out, "add", (EArrayGet(ETupleGet(e.e, 1), i).with_type(elem_type),)), SAssign(i, EBinOp(i, "+", ONE).with_type(INT))]))]) elif isinstance(e, EHeapPeek): raise NotImplementedError() elif isinstance(e, EHeapPeek2): from cozy.evaluation import construct_value best = EArgMin if isinstance(e.e.type, TMinHeap) else EArgMax f = heap_func(e.e, concretization_functions) return seq([ SDecl(out, construct_value(out.type)), SSwitch(ETupleGet(e.e, 0), ( (ZERO, SAssign(out, construct_value(e.type))), (ONE, SAssign(out, construct_value(e.type))), (TWO, SAssign(out, EArrayGet(ETupleGet(e.e, 1), ONE).with_type(e.type)))), SAssign(out, best(EBinOp(ESingleton(EArrayGet(ETupleGet(e.e, 1), ONE).with_type(e.type)).with_type(TBag(out.type)), "+", ESingleton(EArrayGet(ETupleGet(e.e, 1), TWO).with_type(e.type)).with_type(TBag(out.type))).with_type(TBag(out.type)), f).with_type(out.type)))]) else: raise NotImplementedError(e)
def codegen(self, e : Exp, concretization_functions : { str : Exp }, out : EVar) -> Stm: """Return statements that write the result of `e` to `out`. The returned statements must declare the variable `out`; it will not be declared by the caller. This function also requires the `concretization_functions` that describe the invariants for variables in `e`. """ if isinstance(e, EMakeMinTreeMultiset) or isinstance(e, EMakeMaxTreeMultiset): assert out.type == self.rep_type(e.type) extended_concretization_functions = dict(concretization_functions) extended_concretization_functions[out.id] = e dummy_out = EVar(out.id).with_type(e.type) return seq([ SDecl(out, None), self.implement_stmt(SCall(dummy_out, "add_all", (e.e,)), extended_concretization_functions)]) elif isinstance(e, ETreeMultisetElems): elem_type = e.type.elem_type x = fresh_var(elem_type, "x") from cozy.syntax_tools import shallow_copy xs = shallow_copy(e.e).replace_type(e.type) return seq([ SDecl(out, EEmptyList().with_type(out.type)), SForEach(x, xs, SCall(out, "add", (x,)))]) elif isinstance(e, ETreeMultisetPeek): return SDecl(out, e) else: raise NotImplementedError(e)
def optimized_sum(xs, args): elem_type = xs.type.elem_type if isinstance(xs, EStateVar): yield EStateVar(sum_of(strip_EStateVar(xs))).with_type(elem_type) if isinstance(xs, EBinOp) and xs.op == "+": for a in optimized_sum(xs.e1, args=args): for b in optimized_sum(xs.e2, args=args): yield EBinOp(a, "+", b).with_type(elem_type) if isinstance(xs, EBinOp) and xs.op == "-": arg = fresh_var(elem_type) for a in optimized_sum(xs.e1, args=args): for e2 in _simple_filter(xs.e2, ELambda(arg, optimized_in(arg, xs.e1)), args): for b in optimized_sum(e2, args=args): yield EBinOp(a, "-", b).with_type(elem_type) x = excluded_element(xs, args) if x is not None: bag, x = x for s in optimized_sum(bag, args): yield EBinOp(s, "-", x).with_type(x.type) if isinstance(xs, ESingleton): yield xs.e if isinstance(xs, EFlatMap): f = xs.transform_function if isinstance(f.body, EBinOp) and f.body.op == "+": for e1 in optimized_flatmap(xs.e, ELambda(f.arg, f.body.e1), args): for e2 in optimized_flatmap(xs.e, ELambda(f.arg, f.body.e2), args): for e in optimized_sum(EBinOp(e1, "+", e2).with_type(e1.type), args): yield e yield sum_of(xs)
def mutate_in_place(self, lval, e, op, assumptions, invariants, make_subgoal): from cozy.state_maintenance import mutate old_value = e new_value = mutate(e, op) # added/removed elements t = TBag(lval.type.elem_type) old_elems = EHeapElems(old_value).with_type(t) new_elems = EHeapElems(new_value).with_type(t) initial_count = make_subgoal(ELen(old_elems)) to_add = make_subgoal(EBinOp(new_elems, "-", old_elems).with_type(t), docstring="additions to {}".format(pprint(lval))) to_del_spec = EBinOp(old_elems, "-", new_elems).with_type(t) removed_count = make_subgoal(ELen(to_del_spec)) to_del = make_subgoal(to_del_spec, docstring="deletions from {}".format(pprint(lval))) # modified elements f1 = heap_func(old_value) f2 = heap_func(new_value) v = fresh_var(t.elem_type) old_v_key = f1.apply_to(v) new_v_key = f2.apply_to(v) mod_spec = EFilter(old_elems, ELambda(v, EAll([EIn(v, new_elems), ENot(EEq(new_v_key, old_v_key))]))).with_type(new_elems.type) modified = make_subgoal(mod_spec) intermediate_count = make_subgoal(EBinOp(ELen(old_elems), "-", ELen(to_del_spec)).with_type(INT)) return seq([ SCall(lval, "remove_all", (initial_count, to_del)), SCall(lval, "add_all", (intermediate_count, to_add)), SForEach(v, modified, SCall(lval, "update", (v, make_subgoal(new_v_key, a=[EIn(v, mod_spec)]))))])
def reachable_handles_by_type(root : Exp) -> {THandle:Exp}: """ Compute a mapping from handle types to bags of all handle objects of that type reachable from the given root. Note that the bags may contain duplicate handles. This can happen in two ways: - there is a bag of handles reachable from the root that contains duplicate handles, or - the same handle is reachable from the root via two different paths """ if isinstance(root.type, THandle): return _merge( { root.type : ESingleton(root).with_type(TBag(root.type)) }, reachable_handles_by_type(EGetField(root, "val").with_type(root.type.value_type))) elif is_collection(root.type): v = fresh_var(root.type.elem_type) res = reachable_handles_by_type(v) for k, bag in list(res.items()): res[k] = EFlatMap(root, ELambda(v, bag)).with_type(bag.type) return res elif isinstance(root.type, TTuple): res = OrderedDict() for i, t in enumerate(root.type.ts): res = _merge(res, reachable_handles_by_type(ETupleGet(root, i).with_type(t))) return res elif isinstance(root.type, TRecord): res = OrderedDict() for f, t in root.type.fields: res = _merge(res, reachable_handles_by_type(EGetField(root, f).with_type(t))) return res elif isinstance(root.type, TMap): raise NotImplementedError() else: return OrderedDict()
def reachable_values_of_type(root: Exp, t: Type) -> Exp: """ Find all values of the given type reachable from the given root. """ if root.type == t: return ESingleton(root).with_type(TBag(t)) elif is_collection(root.type): v = fresh_var(root.type.t) res = reachable_values_of_type(v, t) return MkFlatMap(root, ELambda(v, res)) elif isinstance(root.type, THandle): return reachable_values_of_type( EGetField(root, "val").with_type(root.type.value_type), t) elif isinstance(root.type, TTuple): sub = [ reachable_values_of_type(ETupleGet(root, i).with_type(tt), t) for (i, tt) in enumerate(root.type.ts) ] return EUnion(sub, t) elif isinstance(root.type, TRecord): sub = [ reachable_values_of_type(EGetField(root, f).with_type(ft), t) for (f, ft) in root.type.fields ] return EUnion(sub, t) elif isinstance(root.type, TMap): raise NotImplementedError() else: return EEmptyList().with_type(TBag(t))
def mutate_in_place(self, lval, e, op, assumptions, make_subgoal): from cozy.state_maintenance import mutate old_value = e new_value = mutate(e, op) # added/removed elements t = TBag(lval.type.elem_type) old_elems = EHeapElems(old_value).with_type(t) new_elems = EHeapElems(new_value).with_type(t) initial_count = make_subgoal(ELen(old_elems)) to_add = make_subgoal(EBinOp(new_elems, "-", old_elems).with_type(t), docstring="additions to {}".format(pprint(lval))) to_del_spec = EBinOp(old_elems, "-", new_elems).with_type(t) removed_count = make_subgoal(ELen(to_del_spec)) to_del = make_subgoal(to_del_spec, docstring="deletions from {}".format(pprint(lval))) # modified elements f1 = heap_func(old_value) f2 = heap_func(new_value) v = fresh_var(t.t) old_v_key = f1.apply_to(v) new_v_key = f2.apply_to(v) mod_spec = EFilter(old_elems, ELambda(v, EAll([EIn(v, new_elems), ENot(EEq(new_v_key, old_v_key))]))).with_type(new_elems.type) modified = make_subgoal(mod_spec) return seq([ SCall(lval, "remove_all", (initial_count, to_del)), SCall(lval, "add_all", (EBinOp(initial_count, "-", removed_count).with_type(INT), to_add)), SForEach(v, modified, SCall(lval, "update", (v, make_subgoal(new_v_key, a=[EIn(v, mod_spec)]))))])
def histogram(e : Exp) -> (Stm, EVar): """Compute a histogram of the elements in the iterable `e`. Returns an unoptimized statement that declares and constructs a histogram map and the fresh variable that got declared. """ elem_type = e.type.elem_type h = fresh_var(TMap(elem_type, INT), "histogram") x = fresh_var(elem_type, "x") count = fresh_var(INT, "count") stm = seq([ SDecl(h, EEmptyMap().with_type(h.type)), SForEach(x, e, SMapUpdate(h, x, count, SAssign(count, EBinOp(count, "+", ONE).with_type(INT))))]) return (stm, h)
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 codegen(self, e: Exp, concretization_functions: {str: Exp}, out: EVar) -> Stm: """Return statements that write the result of `e` to `out`. The returned statements must declare the variable `out`; it will not be declared by the caller. This function also requires the `concretization_functions` that describe the invariants for variables in `e`. """ if isinstance(e, EMakeMinTreeMultiset) or isinstance( e, EMakeMaxTreeMultiset): assert out.type == self.rep_type(e.type) extended_concretization_functions = dict(concretization_functions) extended_concretization_functions[out.id] = e dummy_out = EVar(out.id).with_type(e.type) return seq([ SDecl(out, None), self.implement_stmt(SCall(dummy_out, "add_all", (e.e, )), extended_concretization_functions) ]) elif isinstance(e, ETreeMultisetElems): elem_type = e.type.elem_type x = fresh_var(elem_type, "x") from cozy.syntax_tools import shallow_copy xs = shallow_copy(e.e).replace_type(e.type) return seq([ SDecl(out, EEmptyList().with_type(out.type)), SForEach(x, xs, SCall(out, "add", (x, ))) ]) elif isinstance(e, ETreeMultisetPeek): return SDecl(out, e) else: raise NotImplementedError(e)
def reachable_handles_by_type(root : Exp) -> {THandle:Exp}: """ Compute a mapping from handle types to bags of all handle objects of that type reachable from the given root. Note that the bags may contain duplicate handles. This can happen in two ways: - there is a bag of handles reachable from the root that contains duplicate handles, or - the same handle is reachable from the root via two different paths """ if isinstance(root.type, THandle): return _merge( { root.type : ESingleton(root).with_type(TBag(root.type)) }, reachable_handles_by_type(EGetField(root, "val").with_type(root.type.value_type))) elif is_collection(root.type): v = fresh_var(root.type.t) res = reachable_handles_by_type(v) for k, bag in list(res.items()): res[k] = EFlatMap(root, ELambda(v, bag)).with_type(bag.type) return res elif isinstance(root.type, TTuple): res = OrderedDict() for i, t in enumerate(root.type.ts): res = _merge(res, reachable_handles_by_type(ETupleGet(root, i).with_type(t))) return res elif isinstance(root.type, TRecord): res = OrderedDict() for f, t in root.type.fields: res = _merge(res, reachable_handles_by_type(EGetField(root, f).with_type(t))) return res elif isinstance(root.type, TMap): raise NotImplementedError() else: return OrderedDict()
def EDeepIn(e1, e2): from cozy.syntax_tools import free_vars, fresh_var arg = fresh_var(e1.type, omit=free_vars(e1)) return EUnaryOp( UOp.Any, EMap(e2, ELambda(arg, EDeepEq(arg, e1))).with_type(BOOL_BAG)).with_type(BOOL)
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 _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 visit_Exp(self, e): h = extension_handler(type(e)) if h is not None: v = fresh_var(self.visit(e.type)) new_stm = h.codegen(e, self.concretization_functions, out=v) return EStm(new_stm, v).with_type(v.type) e = self.visit_ADT(e) if hasattr(e, "type"): e = shallow_copy(e).with_type(self.visit(e.type)) return e
def implement_stmt(self, s : Stm, concretization_functions : { str : Exp }) -> Stm: """Convert a call to a ordered function into simpler statements. This function also requires the `concretization_functions` that describe the invariants for variables in `e`. """ if isinstance(s, SCall): elem_type = s.target.type.elem_type target = EVar(s.target.id).with_type(self.rep_type(s.target.type)) if s.func == "add_all": x = fresh_var(elem_type, "x") return SForEach(x, s.args[0], SInsert(target, x)) elif s.func == "remove_all": x = fresh_var(elem_type, "x") return SForEach(x, s.args[0], SErase(target, x)) else: raise ValueError("ordereds do not support the function {}".format(s.func)) else: raise ValueError("the statement {} is not an update to a ordered variable".format(pprint(s)))
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 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
def ECountIn(e, collection): """Count the number of times e occurs in the collection""" from cozy.syntax_tools import free_vars, fresh_var assert e.type == collection.type.t arg = fresh_var(e.type, omit=free_vars(e)) return EUnaryOp( UOp.Length, EFilter(collection, ELambda(arg, EEq(arg, e))).with_type(collection.type)).with_type(INT)
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
def min_or_max(self, op, e, f): if isinstance(e, EBinOp) and e.op == "+" and isinstance(e.e1, ESingleton) and isinstance(e.e2, ESingleton): # argmin_f ([a] + [b]) ---> f(a) < f(b) ? a : b return self.visit(ECond( EBinOp(f.apply_to(e.e1.e), op, f.apply_to(e.e2.e)).with_type(BOOL), e.e1.e, e.e2.e).with_type(e.e1.e.type)) out = fresh_var(e.type.elem_type, "min" if op == "<" else "max") first = fresh_var(BOOL, "first") x = fresh_var(e.type.elem_type, "x") decl1 = SDecl(out, evaluation.construct_value(out.type)) decl2 = SDecl(first, ETRUE) find = SForEach(x, e, SIf(EBinOp( first, BOp.Or, EBinOp(f.apply_to(x), op, f.apply_to(out)).with_type(BOOL)).with_type(BOOL), seq([SAssign(first, EFALSE), SAssign(out, x)]), SNoOp())) self.stms.append(simplify_and_optimize(seq([decl1, decl2, find]))) return out
def compute_sharing(state_map: dict, true_types: dict) -> dict: """ Takes a dictionary mapping { state_var_id : state_exp } and a dictionary mapping { state_var_id : refined_type } and returns a dictionary { ht : groups } for each handle type ht. Each group is a list of implementation types whose intrusive data will never be used at the same time. """ types = set(t for e in state_map.values() for t in syntax_tools.all_types(e.type)) handle_types = set(t for t in types if isinstance(t, target_syntax.THandle)) out = {} # for (var, exp) in state_map.items(): # print(" --> {} = {}".format(var, syntax_tools.pprint(exp))) for ht in handle_types: groups = [] handle = syntax_tools.fresh_var(ht, "handle") # print(ht) # for (var, exp) in state_map.items(): # print(" --> {} iff {}".format(var, syntax_tools.pprint(uses_intrusive_data(exp, handle)))) type_uses_intrusive_data = {} for (var, exp) in state_map.items(): use = uses_intrusive_data(exp, handle) for t in syntax_tools.all_types(true_types[var]): # print(syntax_tools.pprint(t)) if hasattr(t, "intrusive_data"): type_uses_intrusive_data[t] = use # else: # print(" no intrusive data for " + syntax_tools.pprint(t)) # print(type_uses_intrusive_data) for t, cond in type_uses_intrusive_data.items(): found = False for g in groups: if all(not solver.satisfy( target_syntax.EAll([cond, type_uses_intrusive_data[t] ])) for t in g): found = True g.append(t) break if not found: groups.append([t]) # print(" --> {}".format(groups)) out[ht] = groups return out
def compare_to(self, other, assumptions : Exp = T, solver : IncrementalSolver = None): assert isinstance(other, SymbolicCost) if False: s = IncrementalSolver() v1, v2 = fresh_var(BOOL), fresh_var(BOOL) s.add_assumption(EAll([ self.order_cardinalities(other, assumptions, solver), EEq(v1, EBinOp(self.formula, "<=", other.formula).with_type(BOOL)), EEq(v2, EBinOp(other.formula, "<=", self.formula).with_type(BOOL))])) o1 = s.valid(v1) o2 = s.valid(v2) else: cards = self.order_cardinalities(other, assumptions, solver) o1 = self.always("<=", other, cards=cards) o2 = other.always("<=", self, cards=cards) if o1 and not o2: return Cost.BETTER elif o2 and not o1: return Cost.WORSE else: return Cost.UNORDERED
def compute_hash_1(self, hc: Exp, e : Exp) -> Stm: if is_scalar(e.type): return SAssign(hc, self.compute_hash_scalar(e)) elif isinstance(e.type, TArray): x = fresh_var(e.type.elem_type, "x") s = SSeq(SAssign(hc, ZERO.with_type(hc.type)), SForEach(x, e, SAssign(hc, EEscape("({hc} * 31) ^ ({h})", ("hc", "h"), (hc, self.compute_hash_scalar(x))).with_type(INT)))) return s else: raise NotImplementedError("can't compute hash for type {}".format(e.type))
def order_cardinalities(self, other, assumptions : Exp = T, solver : IncrementalSolver = None) -> Exp: if solver is None: solver = IncrementalSolver() if incremental: solver.push() solver.add_assumption(assumptions) cardinalities = OrderedDict() for m in (self.cardinalities, other.cardinalities): for k, v in m.items(): cardinalities[v] = k conds = [] res = [] for (v1, c1) in cardinalities.items(): res.append(EBinOp(v1, ">=", ZERO).with_type(BOOL)) for (v2, c2) in cardinalities.items(): if v1 == v2: continue if alpha_equivalent(c1, c2): res.append(EEq(v1, v2)) continue if incremental and use_indicators: conds.append((v1, v2, fresh_var(BOOL), cardinality_le(c1, c2, as_f=True))) else: if incremental: le = cardinality_le(c1, c2, solver=solver) else: # print("CMP {}: {} / {}".format("<-" if v1 < v2 else "->", pprint(c1), pprint(c2))) le = cardinality_le(c1, c2, assumptions=assumptions, solver=solver) if le: res.append(EBinOp(v1, "<=", v2).with_type(BOOL)) if incremental and use_indicators: solver.add_assumption(EAll( [EEq(indicator, f) for (v1, v2, indicator, f) in conds])) for (v1, v2, indicator, f) in conds: if solver.valid(indicator): res.append(EBinOp(v1, "<=", v2).with_type(BOOL)) if incremental: solver.pop() if assume_large_cardinalities.value: min_cardinality = ENum(assume_large_cardinalities.value).with_type(INT) for cvar, exp in cardinalities.items(): if isinstance(exp, EVar): res.append(EBinOp(cvar, ">", min_cardinality).with_type(BOOL)) # print("cards: {}".format(pprint(EAll(res)))) return EAll(res)
def implement_stmt(self, s : Stm, concretization_functions : { str : Exp }) -> Stm: 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))), 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))), 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)), 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 NotImplementedError() else: raise NotImplementedError(pprint(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 optimized_distinct(xs, args): if isinstance(xs, EEmptyList) or isinstance(xs, ESingleton): yield xs return if isinstance(xs, EStateVar): yield EStateVar(EUnaryOp(UOp.Distinct, xs.e).with_type(xs.type)).with_type(xs.type) if isinstance(xs, EBinOp): if xs.op == "+": v = fresh_var(xs.type.elem_type) for a in optimized_distinct(xs.e1, args): for b in optimized_distinct(xs.e2, args): for b_prime in optimized_filter(b, ELambda(v, ENot(optimized_in(v, a))), args): yield EBinOp(a, "+", b_prime).with_type(xs.type) if xs.op == "-": v = fresh_var(xs.type.elem_type) for a in optimized_distinct(xs.e1, args): for b in optimized_distinct(xs.e2, args): yield from optimized_filter(a, ELambda(v, ENot(optimized_in(v, b))), args) if isinstance(xs, EFilter): for ee in optimized_distinct(xs.e, args): yield EFilter(ee, xs.predicate).with_type(xs.type) yield EUnaryOp(UOp.Distinct, xs).with_type(xs.type)
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 impls(self, e: Exp, assumptions: Exp): ty = e.type if type(ty) is TMap: k = fresh_var(ty.k) for v in self.impls( EMapGet(e, k).with_type(e.type.v), assumptions): if is_enumerable(ty.k): yield TVectorMap(ty.k, v) else: yield TNativeMap(ty.k, v) elif type(ty) is TSet or (type(ty) is TBag and valid( EImplies(assumptions, EUnaryOp(UOp.AreUnique, e).with_type(BOOL)), model_callback=print)): if isinstance(ty.t, THandle): yield TIntrusiveLinkedList(ty.t) x = fresh_var(ty.t) for t in self.impls(x, EAll((assumptions, EIn(x, e)))): yield TNativeSet(t) elif type(ty) is TBag: x = fresh_var(ty.t) for t in self.impls(x, EAll((assumptions, EIn(x, e)))): yield TNativeList(t) elif type(ty) is TList: if isinstance(ty.t, THandle) and valid(EImplies( assumptions, EUnaryOp(UOp.AreUnique, e).with_type(BOOL)), model_callback=print): yield TIntrusiveLinkedList(ty.t) yield TNativeList(ty.t) elif type(ty) is TTuple: for refinements in cross_product([ self.impls( ETupleGet(e, i).with_type(ty.ts[i]), assumptions) for i in range(len(ty.ts)) ]): yield TTuple(refinements) else: yield ty
def heap_func(e : Exp, concretization_functions : { str : Exp } = None) -> ELambda: """ Assuming 'e' produces a heap, this returns the function used to sort its elements. """ if isinstance(e, EMakeMinHeap) or isinstance(e, EMakeMaxHeap): return e.key_function if isinstance(e, EVar) and concretization_functions: ee = concretization_functions.get(e.id) if ee is not None: return heap_func(ee) if isinstance(e, ECond): h1 = heap_func(e.then_branch) h2 = heap_func(e.else_branch) if alpha_equivalent(h1, h2): return h1 v = fresh_var(h1.arg.type) return ELambda(v, ECond(e.cond, h1.apply_to(v), h2.apply_to(v)).with_type(h1.body.type)) raise NotImplementedError(repr(e))
def fold_into_map(e, context): fvs = free_vars(e) state_vars = [v for v, p in context.vars() if p == STATE_POOL] for subexp, subcontext, subpool in all_subexpressions_with_context_information(e, context, RUNTIME_POOL): if isinstance(subexp, EMapGet) and isinstance(subexp.map, EStateVar): map = subexp.map.e key = subexp.key key_type = key.type value_type = subexp.type # e is of the form `... EStateVar(map)[key] ...` arg = fresh_var(subexp.type, omit=fvs) func = ELambda(arg, replace( e, context, RUNTIME_POOL, subexp, subcontext, subpool, arg)) if not all(v in state_vars for v in free_vars(func)): continue func = strip_EStateVar(func) new_map = map_values(map, func.apply_to) yield EMapGet(EStateVar(new_map).with_type(new_map.type), key).with_type(e.type)
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 optimized_sum(xs, args): elem_type = xs.type.elem_type if isinstance(xs, EStateVar): yield EStateVar(sum_of(xs)).with_type(elem_type) if isinstance(xs, EBinOp) and xs.op == "+": for a in optimized_sum(xs.e1, args=args): for b in optimized_sum(xs.e2, args=args): yield EBinOp(a, "+", b).with_type(elem_type) if isinstance(xs, EBinOp) and xs.op == "-": arg = fresh_var(elem_type) for a in optimized_sum(xs.e1, args=args): for e2 in _simple_filter(xs.e2, ELambda(arg, optimized_in(arg, xs.e1)), args): for b in optimized_sum(e2, args=args): yield EBinOp(a, "-", b).with_type(elem_type) x = excluded_element(xs, args) if x is not None: bag, x = x for s in optimized_sum(bag, args): yield EBinOp(s, "-", x).with_type(x.type) if isinstance(xs, ESingleton): yield xs.e yield sum_of(xs)
def maintenance_cost(self, old_value : Exp, new_value : Exp, op : Op, storage_size, maintenance_cost, freebies : [Exp] = []): assert type(e.type) in (TMinHeap, TMaxHeap) # added/removed elements t = TBag(e.type.elem_type) old_elems = EHeapElems(old_value).with_type(t) new_elems = EHeapElems(new_value).with_type(t) # Add these elems_added = storage_size( EBinOp(new_elems, "-", old_elems).with_type(t), freebies).with_type(INT) elems_rmved = storage_size( EBinOp(old_elems, "-", new_elems).with_type(t), freebies).with_type(INT) # modified elements f1 = heap_func(old_value) f2 = heap_func(new_value) v = fresh_var(t.elem_type) old_v_key = f1.apply_to(v) new_v_key = f2.apply_to(v) modified_elems = EFilter(old_elems, ELambda(v, EAll([EIn(v, new_elems), ENot(EEq(new_v_key, old_v_key))]))).with_type(new_elems.type) modified_cost = EUnaryOp( UOp.Sum, EMap( modified_elems, ELambda( v, maintenance_cost( new_v_key, op, freebies)).with_type(INT)).with_type(INT)).with_type(INT_BAG) return ESum([elems_added, elems_rmved, modified_cost])
def visit_ELambda(self, e): if e.arg in fvs: v = fresh_var(e.arg.type, omit=fvs) e = syntax.ELambda(v, e.apply_to(v)) return syntax.ELambda(e.arg, self.visit(e.body))
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 _simple_filter(xs : Exp, p : ELambda, args : {EVar}): """Assumes the body of p is already in negation normal form""" if p.body == ETRUE: yield xs return if p.body == EFALSE: yield EEmptyList().with_type(xs.type) return if isinstance(xs, EEmptyList): yield xs return yielded = False if isinstance(xs, ESingleton): yielded = True yield optimized_cond(p.apply_to(xs.e), xs, EEmptyList().with_type(xs.type)) if isinstance(p.body, EBinOp) and p.body.op == BOp.Or: for e1, e2 in itertools.permutations([p.body.e1, p.body.e2]): for r1 in _simple_filter(xs, ELambda(p.arg, e1), args): for r2 in _simple_filter(xs, ELambda(p.arg, EAll([e2, ENot(e1)])), args): yielded = True yield EBinOp(r1, "+", r2).with_type(xs.type) if isinstance(p.body, EBinOp) and p.body.op == BOp.And: for e1, e2 in itertools.permutations([p.body.e1, p.body.e2]): for r1 in _simple_filter(xs, ELambda(p.arg, e1), args): yielded = True yield from _simple_filter(r1, ELambda(p.arg, e2), args) if isinstance(xs, EStateVar) and not any(v in args for v in free_vars(p)): yielded = True yield EStateVar(EFilter(xs.e, strip_EStateVar(p)).with_type(xs.type)).with_type(xs.type) if isinstance(xs, EMapGet) and isinstance(xs.map, EStateVar) and not any(v in args for v in free_vars(p)): for m in map_values_multi(xs.map.e, lambda ys: _simple_filter(ys, p, args)): yielded = True yield EMapGet(EStateVar(m).with_type(m.type), xs.key).with_type(xs.type) if isinstance(xs, EBinOp) and xs.op in ("+", "-"): for e1 in _simple_filter(xs.e1, p, args): for e2 in _simple_filter(xs.e2, p, args): yielded = True yield EBinOp(e1, xs.op, e2).with_type(xs.type) if isinstance(p.body, EBinOp) and p.body.op == "==": e1 = p.body.e1 e2 = p.body.e2 fvs2 = free_vars(e2) fvs1 = free_vars(e1) for (e1, fvs1), (e2, fvs2) in itertools.permutations([(e1, fvs1), (e2, fvs2)]): if p.arg in fvs1 and not any(a in fvs1 for a in args) and p.arg not in fvs2 and isinstance(xs, EStateVar): if e1 == p.arg: yield optimized_cond( optimized_in(e2, xs), ESingleton(e2).with_type(xs.type), EEmptyList().with_type(xs.type)) k = fresh_var(e1.type) e = EMapGet( EStateVar( EMakeMap2( EMap(xs.e, ELambda(p.arg, e1)), ELambda(k, EFilter(xs.e, ELambda(p.arg, EEq(e1, k)))))), e2) res = retypecheck(e) assert res yielded = True yield e if not yielded: yield EFilter(xs, p).with_type(xs.type)
def visit_ECond(self, e): v = fresh_var(e.type, "conditional_result") self.stms.append(simplify_and_optimize(seq([ SDecl(v, evaluation.construct_value(e.type)), SIf(e.cond, SAssign(v, e.then_branch), SAssign(v, e.else_branch))]))) return v
def stream(iterable : Exp, loop_var : EVar, body : Stm) -> Stm: """Convert an iterable expression to a streaming operation. Input: iterable - an expression with an iterable type (Bag, Set, or List), not yet optimized loop_var - a variable to use as the loop variable body - a statement to run on that variable, not yet optimized Output: A statement equivalent to for (loop_var in iterable) { body } that eliminates as many intermediate collections and objects as possible. NOTE: The output of function will not be correct if the body modifies any free variable in the iterable expression or writes to any pointers that are read by the iterable expression. Generating code for the expression Map {func} (Filter {predicate} big_collection) might create two new collections as large as `big_collection`: one to hold the result of the filter and one to hold the result of the map. If all the code needs to do is to iterate over the result, then there is no reason to make the two new collections. This function is mutually recursive with `simplify_and_optimize`, so any transformations performed by that method are also applied to the output of this one. """ if isinstance(iterable, EEmptyList): return SNoOp() elif isinstance(iterable, ESingleton): setup, value = simplify_and_optimize_expression(iterable.e) # SScoped because if the iterable is e.g. [x] + [y], then the body # might be appear in the same block twice. If the body declares any # variables, that will cause problems in languages like Java or C++. return seq([setup, SScoped(re_use(value, loop_var, simplify_and_optimize(body)))]) elif isinstance(iterable, ECond): cond_setup, cond = simplify_and_optimize_expression(iterable.cond) return seq([ cond_setup, SIf(cond, stream(iterable.then_branch, loop_var, body), stream(iterable.else_branch, loop_var, body))]) elif isinstance(iterable, EUnaryOp) and iterable.op == UOp.Distinct: tmp = fresh_var(TSet(iterable.type.elem_type), "distinct_elems") return seq([ SDecl(tmp, EEmptyList().with_type(tmp.type)), stream(iterable.e, loop_var, SIf( ENot(EBinOp(loop_var, BOp.In, tmp).with_type(BOOL)), seq([body, SCall(tmp, "add", [loop_var])]), SNoOp()))]) elif isinstance(iterable, EBinOp) and iterable.op == "+": return seq([ stream(iterable.e1, loop_var, body), stream(iterable.e2, loop_var, body)]) elif isinstance(iterable, EBinOp) and iterable.op == "-": if is_hashable(iterable.type.elem_type): h_setup, h = histogram(iterable.e2) val_ref = fresh_var(INT, "count") return seq([ simplify_and_optimize(h_setup), stream( iterable.e1, loop_var, SIf(EGt(EMapGet(h, loop_var).with_type(INT), ZERO), SMapUpdate(h, loop_var, val_ref, SAssign(val_ref, EBinOp(val_ref, "-", ONE).with_type(INT))), body))]) else: rhs = fresh_var(iterable.e2.type, "bag_subtraction_right") return seq([ simplify_and_optimize(SDecl(rhs, iterable.e2)), stream( iterable.e1, loop_var, SIf(EIn(loop_var, rhs), SCall(rhs, "remove", (loop_var,)), body))]) elif isinstance(iterable, EFilter): return stream( EFlatMap(iterable.e, ELambda(iterable.predicate.arg, ECond(iterable.predicate.body, ESingleton(iterable.predicate.arg).with_type(iterable.type), EEmptyList().with_type(iterable.type)).with_type(iterable.type))).with_type(iterable.type), loop_var, body) elif isinstance(iterable, EMap): return stream( EFlatMap(iterable.e, ELambda(iterable.transform_function.arg, ESingleton(iterable.transform_function.body).with_type(iterable.type))).with_type(iterable.type), loop_var, body) elif isinstance(iterable, EFlatMap): inner_loop_var = fresh_var( iterable.transform_function.arg.type, iterable.transform_function.arg.id) return stream( iterable.e, inner_loop_var, stream(iterable.transform_function.apply_to(inner_loop_var), loop_var, body)) elif isinstance(iterable, EListSlice): elem_type = iterable.type.elem_type l = fresh_var(iterable.e.type, "list") s = fresh_var(INT, "start") e = fresh_var(INT, "end") return simplify_and_optimize(seq([ SDecl(l, iterable.e), SDecl(s, max_of(iterable.start, ZERO)), SDecl(e, min_of(iterable.end, ELen(l))), SWhile(ELt(s, e), seq([ SDecl(loop_var, EListGet(l, s).with_type(elem_type)), body, SAssign(s, EBinOp(s, "+", ONE).with_type(INT))]))])) elif isinstance(iterable, ELet): v = fresh_var( iterable.body_function.arg.type, iterable.body_function.arg.id) return seq([ simplify_and_optimize(SDecl(v, iterable.e)), stream(iterable.body_function.apply_to(v), loop_var, body)]) elif isinstance(iterable, EMove): return stream(iterable.e, loop_var, body) else: assert is_collection(iterable.type), repr(iterable) setup, e = simplify_and_optimize_expression(iterable) return seq([setup, SForEach(loop_var, e, simplify_and_optimize(body))])
def EDeepIn(e1, e2): from cozy.syntax_tools import free_vars, fresh_var arg = fresh_var(e1.type, omit=free_vars(e1)) return EUnaryOp(UOp.Any, EMap(e2, ELambda(arg, EDeepEq(arg, e1))).with_type(BOOL_BAG)).with_type(BOOL)
def ECountIn(e, collection): """Count the number of times e occurs in the collection""" from cozy.syntax_tools import free_vars, fresh_var assert e.type == collection.type.elem_type arg = fresh_var(e.type, omit=free_vars(e)) return EUnaryOp(UOp.Length, EFilter(collection, ELambda(arg, EEq(arg, e))).with_type(collection.type)).with_type(INT)
def visit_iterable(self, e): res = fresh_var(e.type) self.stms.append(SDecl(res, EEmptyList().with_type(e.type))) x = fresh_var(e.type.elem_type) self.stms.append(simplify_and_optimize(SForEach(x, e, SCall(res, "add", (x,))))) return EMove(res).with_type(res.type)