def sizeof(e, cardinalities): terms = [ONE] if is_collection(e.type): terms.append(cardinality(e, cardinalities)) elif isinstance(e.type, TMap): ks = EMapKeys(e).with_type(TBag(e.type.k)) terms.append(cardinality(ks, cardinalities)) if is_collection(e.type.v): vals = EFlatMap(ks, mk_lambda(e.type.k, lambda k: EMapGet(e, k).with_type(e.type.v))).with_type(e.type.v) terms.append(cardinality(vals, cardinalities)) return SymbolicCost(ESum(terms), cardinalities)
def fingerprint_is_subset(fp1, fp2): """Are all cases of fp1 a subset of fp2?""" assert is_collection(fp1[0]) assert is_collection(fp2[0]) x = EVar("x").with_type(fp1[0]) y = EVar("y").with_type(fp2[0]) is_subset = EIsSubset(x, y) return all( eval(is_subset, { "x": a, "y": b }) for (a, b) in zip(fp1[1:], fp2[1:]))
def visit_EBinOp(self, e): c1 = self.visit(e.e1) c2 = self.visit(e.e2) costs = [ONE, c1, c2] if e.op == BOp.In: costs.append(self.cardinality(e.e2)) elif e.op == "==" and is_collection(e.e1.type): costs.append(self.cardinality(e.e1)) costs.append(self.cardinality(e.e2)) elif e.op == "-" and is_collection(e.type): costs.append(self.cardinality(e.e1)) costs.append(self.cardinality(e.e2)) return self.combine(costs)
def visit(self, e): if hasattr(e, "_nosimpl"): return e if isinstance(e, Exp) and not isinstance(e, ELambda): t = e.type new = super().visit(e) if isinstance(e, Exp) and not isinstance(e, ELambda): assert new.type == e.type or (is_collection(new.type) and is_collection(e.type)), repr(e) if self.debug and isinstance(e, Exp) and not isinstance(e, ELambda): model = satisfy(ENot(EBinOp(e, "===", new).with_type(BOOL))) if model is not None: raise Exception( "bad simplification: {} ---> {} (under model {!r}, got {!r} and {!r})" .format(pprint(e), pprint(new), model, eval(e, model), eval(new, model))) return new
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 worst_case_cardinality(e: Exp) -> Polynomial: assert is_collection(e.type) while isinstance(e, EFilter) or isinstance(e, EMap) or isinstance( e, EFlatMap) or isinstance(e, EMakeMap2) or isinstance( e, EStateVar) or (isinstance(e, EUnaryOp) and e.op == UOp.Distinct) or isinstance( e, EListSlice): e = e.e if isinstance(e, EBinOp) and e.op == "-": return worst_case_cardinality(e.e1) if isinstance(e, EBinOp) and e.op == "+": return worst_case_cardinality(e.e1) + worst_case_cardinality(e.e2) if isinstance(e, EFlatMap): return worst_case_cardinality(e.e) * worst_case_cardinality( e.transform_function.body) if isinstance(e, ECond): return max(worst_case_cardinality(e.then_branch), worst_case_cardinality(e.else_branch)) if isinstance(e, EEmptyList): return Polynomial.ZERO if isinstance(e, ESingleton): return Polynomial.ONE if isinstance(e, EMapGet): try: return worst_case_cardinality(map_value_func(e.map).body) except NotImplementedError: print("WARNING: unable to peer inside map {}".format(pprint( e.map))) return Polynomial.N return Polynomial.N
def visit_Query(self, q): if q.visibility != Visibility.Public: return "" ret_exp = q.ret ret_type = ret_exp.type if is_collection(ret_type): x = self.fv(ret_type.elem_type, "x") if q.docstring: self.write(indent_lines(q.docstring, self.get_indent()), "\n") self.begin_statement() self.write("template <class F>") self.end_statement() self.begin_statement() self.write("inline void ", q.name, "(") self.visit_args(itertools.chain(q.args, [("_callback", TNative("const F&"))])) self.write(") ") with self.block(): self.visit(simplify_and_optimize(SForEach(x, ret_exp, SEscape("{indent}_callback({x});\n", ["x"], [x])))) self.end_statement() else: if q.docstring: self.write(indent_lines(q.docstring, self.get_indent()), "\n") self.begin_statement() self.write("inline ", self.visit(ret_type, ""), " ", q.name, "(") self.visit_args(q.args) self.write(") ") with self.block(): self.visit(simplify_and_optimize(SReturn(ret_exp))) self.end_statement()
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 should_consider_replacement( target : Exp, target_context : Context, subexp : Exp, subexp_context : Context, subexp_pool : Pool, subexp_fp : Fingerprint, replacement : Exp, replacement_fp : Fingerprint) -> bool: """Heuristic that controls "blind" replacements. Besides replacing subexpressions with improved versions, Cozy also attempts "blind" replacements where the subexpression and the replacement do not behave exactly the same. In some cases this can actually make a huge difference, for instance by replacing a collection with a singleton. However, not all blind replacements are worth trying. This function controls which ones Cozy actually attempts. Preconditions: - subexp and replacement are both legal in (subexp_context, subexp_pool) - subexp and replacement have the same type """ if not is_collection(subexp.type): return No("only collections matter") if not replacement_fp.subset_of(subexp_fp): return No("not a subset") return True
def _uneval(t, value): if is_numeric(t): return ENum(value).with_type(t) elif t == BOOL: return EBool(value).with_type(t) elif is_collection(t): e = EEmptyList().with_type(t) for x in value: e = EBinOp(e, "+", ESingleton(uneval(t.t, x)).with_type(t)).with_type(t) return e elif isinstance(t, TString): return EStr(value).with_type(t) elif isinstance(t, TTuple): return ETuple(tuple(uneval(tt, x) for (tt, x) in zip(t.ts, value))).with_type(t) elif isinstance(t, TRecord): return EMakeRecord( tuple((f, uneval(tt, value[f])) for (f, tt) in t.fields)).with_type(t) elif isinstance(t, TEnum): return EEnumEntry(value).with_type(t) elif isinstance(t, THandle): return EHandle( ENum(value.address).with_type(INT), uneval(t.value_type, value.value)).with_type(t) elif isinstance(t, TNative): return ENative(ENum(value[1]).with_type(INT)).with_type(t) else: raise NotImplementedError(pprint(t))
def visit_Query(self, q): if q.visibility != Visibility.Public: return "" ret_type = q.ret.type if is_collection(ret_type): x = EVar(self.fn("x")).with_type(ret_type.elem_type) def body(x): return SEscape("{indent}_callback.accept({x});\n", ["x"], [x]) if q.docstring: self.write(indent_lines(q.docstring, self.get_indent()), "\n") self.begin_statement() self.write("public ", self.visit(TNative("void"), q.name), "(") self.visit_args(itertools.chain(q.args, [("_callback", TNative("java.util.function.Consumer<{t}>".format(t=self.visit(ret_type.elem_type, ""))))])) self.write(") ") with self.block(): self.visit(simplify_and_optimize(SForEach(x, q.ret, SEscape("{indent}_callback.accept({x});\n", ["x"], [x])))) else: if q.docstring: self.write(indent_lines(q.docstring, self.get_indent()), "\n") self.begin_statement() self.write("public ", self.visit(ret_type, q.name), "(") self.visit_args(q.args) self.write(") ") with self.block(): self.visit(simplify_and_optimize(SReturn(q.ret))) self.end_statement()
def construct_value(t: Type) -> Exp: """ Construct an arbitrary expression e of the given type. eval(construct_value(t), {}) == mkval(t) """ if is_numeric(t): e = ENum(0) elif t == BOOL: e = F elif t == STRING: e = EStr("") elif is_collection(t): e = EEmptyList() elif isinstance(t, TTuple): e = ETuple(tuple(construct_value(tt) for tt in t.ts)) elif isinstance(t, TRecord): e = EMakeRecord(tuple( (f, construct_value(tt)) for (f, tt) in t.fields)) elif isinstance(t, TEnum): e = EEnumEntry(t.cases[0]) elif isinstance(t, THandle): e = EHandle(construct_value(INT), construct_value(t.value_type)) elif isinstance(t, TNative): e = ENative(construct_value(INT)) elif isinstance(t, TMap): e = EMakeMap2(EEmptyList().with_type(TBag(t.k)), ELambda(EVar("x").with_type(t.k), construct_value(t.v))) else: h = extension_handler(type(t)) if h is not None: return h.default_value(t, construct_value) raise NotImplementedError(pprint(t)) return e.with_type(t)
def should_consider_replacement(target: Exp, target_context: Context, subexp: Exp, subexp_context: Context, subexp_pool: Pool, subexp_fp: Fingerprint, replacement: Exp, replacement_fp: Fingerprint) -> bool: """Heuristic that controls "blind" replacements. Besides replacing subexpressions with improved versions, Cozy also attempts "blind" replacements where the subexpression and the replacement do not behave exactly the same. In some cases this can actually make a huge difference, for instance by replacing a collection with a singleton. However, not all blind replacements are worth trying. This function controls which ones Cozy actually attempts. Preconditions: - subexp and replacement are both legal in (subexp_context, subexp_pool) - subexp and replacement have the same type """ if not is_collection(subexp.type): return No("only collections matter") if not replacement_fp.subset_of(subexp_fp): return No("not a subset") return True
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 visit_Query(self, q): if q.visibility != Visibility.Public: return "" ret_exp = q.ret ret_type = ret_exp.type if is_collection(ret_type): x = self.fv(ret_type.elem_type, "x") if q.docstring: self.write(indent_lines(q.docstring, self.get_indent()), "\n") self.begin_statement() self.write("template <class F>") self.end_statement() self.begin_statement() self.write("inline void ", q.name, "(") self.visit_args( itertools.chain(q.args, [("_callback", TNative("const F&"))])) self.write(") ") with self.block(): self.visit( simplify_and_optimize( SForEach( x, ret_exp, SEscape("{indent}_callback({x});\n", ["x"], [x])))) self.end_statement() else: if q.docstring: self.write(indent_lines(q.docstring, self.get_indent()), "\n") self.begin_statement() self.write("inline ", self.visit(ret_type, ""), " ", q.name, "(") self.visit_args(q.args) self.write(") ") with self.block(): self.visit(simplify_and_optimize(SReturn(ret_exp))) self.end_statement()
def construct_value(t : Type) -> Exp: """ Construct an arbitrary expression e of the given type. eval(construct_value(t), {}) == mkval(t) """ if is_numeric(t): e = ENum(0) elif t == BOOL: e = EFALSE elif t == STRING: e = EStr("") elif is_collection(t): e = EEmptyList() elif isinstance(t, TTuple): e = ETuple(tuple(construct_value(tt) for tt in t.ts)) elif isinstance(t, TRecord): e = EMakeRecord(tuple((f, construct_value(tt)) for (f, tt) in t.fields)) elif isinstance(t, TEnum): e = EEnumEntry(t.cases[0]) elif isinstance(t, THandle): e = EHandle(construct_value(INT), construct_value(t.value_type)) elif isinstance(t, TNative): e = ENative(construct_value(INT)) elif isinstance(t, TMap): e = EMakeMap2( EEmptyList().with_type(TBag(t.k)), ELambda(EVar("x").with_type(t.k), construct_value(t.v))) else: h = extension_handler(type(t)) if h is not None: return h.default_value(t, construct_value) raise NotImplementedError(pprint(t)) return e.with_type(t)
def visit_Query(self, q, indent=""): if q.visibility != Visibility.Public: return "" ret_type = q.ret.type if is_collection(ret_type): x = EVar(self.fn("x")).with_type(ret_type.t) s = "{docstring}{indent}template <class F>\n".format( docstring=indent_lines(q.docstring, indent) + "\n" if q.docstring else "", indent=indent) s += "{indent}inline void {name} ({args}const F& _callback) const {{\n{body} }}\n\n".format( indent=indent, name=q.name, args="".join("{}, ".format(self.visit(t, name)) for name, t in q.args), body=self.visit(SForEach( x, q.ret, SEscape("{indent}_callback({x});\n", ["x"], [x])), indent=indent + INDENT)) return s else: body, out = self.visit(q.ret, indent + INDENT) return "{docstring}{indent}inline {type} {name} ({args}) const {{\n{body} return {out};\n }}\n\n".format( docstring=indent_lines(q.docstring, indent) + "\n" if q.docstring else "", indent=indent, type=self.visit(ret_type, ""), name=q.name, args=", ".join(self.visit(t, name) for name, t in q.args), out=out, body=body)
def debug_comparison(cm : CostModel, e1 : Exp, e2 : Exp, context : Context): """Print information about the cost relationship of two expressions. This procedure gives a lot of insight into the relationship between e1 and e2 under the given cost model. """ print("-" * 20) print("Comparing") print(" e1 = {}".format(pprint(e1))) print(" e2 = {}".format(pprint(e2))) print(" res = {}".format(cm.compare(e1, e2, context=context, pool=RUNTIME_POOL))) if is_collection(e1.type): print("worst_case_cardinality(e1) = {}".format(worst_case_cardinality(e1))) if is_collection(e2.type): print("worst_case_cardinality(e2) = {}".format(worst_case_cardinality(e2))) print("-" * 20 + " {} freebies...".format(len(cm.freebies))) for freebie in cm.freebies: print(" * {}".format(pprint(freebie))) print("-" * 20 + " {} ops...".format(len(cm.ops))) for o in cm.ops: print(pprint(o)) for ename, e in [("e1", e1), ("e2", e2)]: print("maintenance_cost({e}) = {res}".format(e=ename, res=pprint(maintenance_cost(e, o)))) print("-" * 20) for f in asymptotic_runtime, polynomial_runtime, max_storage_size, rt: for ename, e in [("e1", e1), ("e2", e2)]: res = f(e) print("{f}({e}) = {res}".format(f=f.__name__, e=ename, res=(pprint(res) if isinstance(res, Exp) else res))) print("-" * 20 + " {} examples...".format(len(cm.examples))) for x in cm.examples: print(x) for op in cm.ops: print(pprint(op)) print("maintcost(e1) = {}".format(eval_bulk(maintenance_cost(e1, op), [x], use_default_values_for_undefined_vars=True)[0])) print("maintcost(e2) = {}".format(eval_bulk(maintenance_cost(e2, op), [x], use_default_values_for_undefined_vars=True)[0])) print("storage(e1) = {}".format(eval_bulk(max_storage_size(e1), [x], use_default_values_for_undefined_vars=True)[0])) print("storage(e2) = {}".format(eval_bulk(max_storage_size(e2), [x], use_default_values_for_undefined_vars=True)[0])) print("runtime(e1) = {}".format(eval_bulk(rt(e1), [x], use_default_values_for_undefined_vars=True)[0])) print("runtime(e2) = {}".format(eval_bulk(rt(e2), [x], use_default_values_for_undefined_vars=True)[0])) print("-" * 20)
def polynomial_runtime(e: Exp) -> Polynomial: res = Polynomial.ZERO stk = [e] while stk: e = stk.pop() if isinstance(e, tuple) or isinstance(e, list): stk.extend(e) continue if not isinstance(e, Exp): continue if isinstance(e, ELambda): e = e.body if isinstance(e, EFilter): stk.append(e.e) res += worst_case_cardinality(e.e) * polynomial_runtime( e.predicate) continue if isinstance(e, EArgMin) or isinstance(e, EArgMax): stk.append(e.e) res += worst_case_cardinality(e.e) * polynomial_runtime( e.key_function) continue if isinstance(e, ESorted): stk.append(e.e) n = worst_case_cardinality(e.e) res += n * n continue if isinstance(e, EMap) or isinstance(e, EFlatMap): stk.append(e.e) res += worst_case_cardinality(e.e) * polynomial_runtime( e.transform_function) continue if isinstance(e, ELet): stk.append(e.e) stk.append(e.body_function.body) continue res += Polynomial.ONE if isinstance(e, EMakeMap2): res += worst_case_cardinality(e.e) * polynomial_runtime( e.value_function) if isinstance(e, EBinOp) and e.op == BOp.In: res += worst_case_cardinality(e.e2) if isinstance(e, EBinOp) and e.op == "-" and is_collection(e.type): res += worst_case_cardinality(e.e1) + worst_case_cardinality( e.e2) + worst_case_cardinality(e.e1) * worst_case_cardinality( e.e2) if isinstance(e, EUnaryOp) and e.op in LINEAR_TIME_UOPS: res += worst_case_cardinality(e.e) if isinstance(e, ECond): res += max(polynomial_runtime(e.then_branch), polynomial_runtime(e.else_branch)) stk.append(e.cond) continue if isinstance(e, EStateVar): continue stk.extend(e.children()) return res
def enumerate(self, context, size, pool, enumerate_subexps, enumerate_lambdas): from cozy.typecheck import is_collection if pool == STATE_POOL: for (sz1, sz2) in pick_to_sum(2, size-1): for e in enumerate_subexps(context, sz1, pool): if is_collection(e.type): elem_type = e.type.elem_type yield EMakeMaxTreeMultiset(e).with_type(TMaxTreeMultiset(elem_type)) yield EMakeMinTreeMultiset(e).with_type(TMinTreeMultiset(elem_type))
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 subset_of(self, other) -> bool: """Test for subset inclusion. If this returns True, then it could be the case that an expression with this fingerprint always returns a strict subset of the elements that would be returned by an expression with the other fingerprint. """ if not is_collection(self.type): raise ValueError( "this fingerprint is not for a collection-type expression") if not is_collection(other.type): raise ValueError( "other fingerprint is not for a collection-type expression") self._require_comparable_to(other) x = EVar("x").with_type(self.type) y = EVar("y").with_type(other.type) is_subset = EIsSubset(x, y) return all( eval_bulk(is_subset, [{ x.id: a, y.id: b } for (a, b) in zip(self.outputs, other.outputs)]))
def enumerate(self, context, size, pool, enumerate_subexps, enumerate_lambdas): from cozy.typecheck import is_collection if pool == STATE_POOL: for (sz1, sz2) in pick_to_sum(2, size - 1): for e in enumerate_subexps(context, sz1, pool): if is_collection(e.type): elem_type = e.type.elem_type yield EMakeMaxTreeMultiset(e).with_type( TMaxTreeMultiset(elem_type)) yield EMakeMinTreeMultiset(e).with_type( TMinTreeMultiset(elem_type))
def break_plus_minus(e): for (_, x, r, _) in enumerate_fragments(e): if isinstance(x, EBinOp) and x.op in ("+", "-"): # print("accel --> {}".format(pprint(r(x.e1)))) yield from break_plus_minus(r(x.e1)) # print("accel --> {}".format(pprint(r(x.e2)))) yield from break_plus_minus(r(x.e2)) if e.type == INT or is_collection(e.type): ee = EBinOp(r(x.e1), x.op, r(x.e2)).with_type(e.type) if e.type == INT and x.op == "-": ee.op = "+" ee.e2 = EUnaryOp("-", ee.e2).with_type(ee.e2.type) yield ee return yield 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 asymptotic_runtime(e: Exp) -> DominantTerm: res = DominantTerm.ZERO stk = [e] while stk: e = stk.pop() if isinstance(e, tuple) or isinstance(e, list): stk.extend(e) continue if not isinstance(e, Exp): continue if isinstance(e, ELambda): e = e.body if isinstance(e, EFilter): res += worst_case_cardinality(e.e) * asymptotic_runtime( e.p) + asymptotic_runtime(e.e) continue if isinstance(e, EMap) or isinstance(e, EFlatMap) or isinstance( e, EArgMin) or isinstance(e, EArgMax): res += worst_case_cardinality(e.e) * asymptotic_runtime( e.f) + asymptotic_runtime(e.e) continue res += DominantTerm.ONE if isinstance(e, EMakeMap2): res += worst_case_cardinality(e.e) * asymptotic_runtime(e.value) if isinstance(e, EBinOp) and e.op == BOp.In: res += worst_case_cardinality(e.e2) if isinstance(e, EBinOp) and e.op == "-" and is_collection(e.type): res += worst_case_cardinality(e.e1) + worst_case_cardinality( e.e2) + worst_case_cardinality(e.e1) * worst_case_cardinality( e.e2) if isinstance(e, EUnaryOp) and e.op in LINEAR_TIME_UOPS: res += worst_case_cardinality(e.e) if isinstance(e, ECond): res += max(asymptotic_runtime(e.then_branch), asymptotic_runtime(e.else_branch)) stk.append(e.cond) continue if isinstance(e, EStateVar): continue stk.extend(e.children()) if res.exponent == 0: return DominantTerm.ONE return res
def asymptotic_runtime(e: Exp) -> int: res = 0 stk = [e] while stk: e = stk.pop() if isinstance(e, tuple) or isinstance(e, list): stk.extend(e) continue if not isinstance(e, Exp): continue if isinstance(e, ELambda): e = e.body if isinstance(e, EFilter): res += max( wc_card(e.e) * asymptotic_runtime(e.p), asymptotic_runtime(e.e)) continue if isinstance(e, EMap) or isinstance(e, EFlatMap) or isinstance( e, EArgMin) or isinstance(e, EArgMax): res += max( wc_card(e.e) * asymptotic_runtime(e.f), asymptotic_runtime(e.e)) continue if isinstance(e, EMakeMap2): res += wc_card(e.e) * asymptotic_runtime(e.value) if isinstance(e, EBinOp) and e.op == BOp.In: res += wc_card(e.e2) if isinstance(e, EBinOp) and e.op == "-" and is_collection(e.type): res += wc_card(e.e1) + wc_card( e.e2) + wc_card(e.e1) * wc_card(e.e2) if isinstance(e, EUnaryOp) and e.op in LINEAR_TIME_UOPS: res += wc_card(e.e) if isinstance(e, ECond): res += max(asymptotic_runtime(e.then_branch), asymptotic_runtime(e.else_branch)) stk.append(e.cond) continue if isinstance(e, EStateVar): continue stk.extend(e.children()) return max(res, 1)
def wc_card(e): assert is_collection(e.type) while isinstance(e, EFilter) or isinstance(e, EMap) or isinstance( e, EFlatMap) or isinstance(e, EArgMin) or isinstance( e, EArgMax) or isinstance(e, EMakeMap2) or isinstance( e, EStateVar) or (isinstance(e, EUnaryOp) and e.op == UOp.Distinct): e = e.e if isinstance(e, EBinOp) and e.op == "-": return wc_card(e.e1) if isinstance(e, EBinOp) and e.op == "+": return max(wc_card(e.e1), wc_card(e.e2)) if isinstance(e, EFlatMap): return wc_card(e.e) * wc_card(e.f.body) if isinstance(e, ECond): return max(wc_card(e.then_branch), wc_card(e.else_branch)) if isinstance(e, EEmptyList): return 0 if isinstance(e, ESingleton): return 1 return EXTREME_COST
def break_bag(e): assert is_collection(e.type) if isinstance(e, EBinOp): if e.op == "+": yield from break_bag(e.e1) yield from break_bag(e.e2) else: assert e.op == "-" yield from break_bag(e.e1) for pos, x in break_bag(e.e2): yield (not pos, x) elif isinstance(e, EMap): for pos, x in break_bag(e.e): yield pos, EMap(x, e.f).with_type(e.type) elif isinstance(e, EFilter): for pos, x in break_bag(e.e): yield pos, EFilter(x, e.p).with_type(e.type) # elif isinstance(e, EStateVar): # yield from break_bag(e.e) else: yield True, e
def cardinality(e : Exp, cache : { Exp : EVar }, plus_one=False) -> Exp: assert is_collection(e.type) # if plus_one: # return ESum((self.cardinality(e, plus_one=False), ONE)) if isinstance(e, EEmptyList): return ZERO if isinstance(e, ESingleton): return ONE if isinstance(e, EBinOp) and e.op == "+": return ESum((cardinality(e.e1, cache), cardinality(e.e2, cache))) if isinstance(e, EMap): return cardinality(e.e, cache) if isinstance(e, EStateVar): return cardinality(e.e, cache) prev = cache.get(e) if prev is not None: return prev else: v = fresh_var(INT) cache[e] = v # if isinstance(e, EFilter): # cc = self.cardinality(e.e) # self.assumptions.append(EBinOp(v, "<=", cc).with_type(BOOL)) # # heuristic: (xs) large implies (filter_p xs) large # self.assumptions.append(EBinOp( # EBinOp(v, "*", ENum(5).with_type(INT)).with_type(INT), ">=", # EBinOp(cc, "*", ENum(4).with_type(INT)).with_type(INT)).with_type(BOOL)) # if isinstance(e, EUnaryOp) and e.op == UOp.Distinct: # cc = self.cardinality(e.e) # self.assumptions.append(EBinOp(v, "<=", cc).with_type(BOOL)) # # self.assumptions.append(EImplies(EGt(cc, ZERO), EGt(v, ZERO))) # # heuristic: (xs) large implies (distinct xs) large # self.assumptions.append(EBinOp( # EBinOp(v, "*", ENum(5).with_type(INT)).with_type(INT), ">=", # EBinOp(cc, "*", ENum(4).with_type(INT)).with_type(INT)).with_type(BOOL)) # if isinstance(e, EBinOp) and e.op == "-": # self.assumptions.append(EBinOp(v, "<=", self.cardinality(e.e1)).with_type(BOOL)) # if isinstance(e, ECond): # self.assumptions.append(EAny([EEq(v, self.cardinality(e.then_branch)), EEq(v, self.cardinality(e.else_branch))])) return v
def enumerate(self, context, size, pool, enumerate_subexps, enumerate_lambdas): from cozy.typecheck import is_ordered, is_collection if pool == STATE_POOL: for (sz1, sz2) in pick_to_sum(2, size-1): for e in enumerate_subexps(context, sz1, pool): if is_collection(e.type): elem_type = e.type.elem_type for keyfunc in enumerate_lambdas(e, pool, sz2): key_type = keyfunc.body.type if is_ordered(key_type): yield EMakeMinHeap(e, keyfunc).with_type(TMinHeap(elem_type, key_type)) yield EMakeMaxHeap(e, keyfunc).with_type(TMaxHeap(elem_type, key_type)) elif pool == RUNTIME_POOL: for e in enumerate_subexps(context.root(), size-1, STATE_POOL): t = e.type if isinstance(t, TMinHeap) or isinstance(t, TMaxHeap): elem_type = t.elem_type # yielding EHeapElems would be redundant yield EHeapPeek (EStateVar(e).with_type(e.type)).with_type(elem_type) yield EHeapPeek2(EStateVar(e).with_type(e.type)).with_type(elem_type)
def _uneval(t, value): if is_numeric(t): return ENum(value).with_type(t) elif t == BOOL: return EBool(value).with_type(t) elif is_collection(t): e = EEmptyList().with_type(t) for x in value: e = EBinOp(e, "+", ESingleton(uneval(t.elem_type, x)).with_type(t)).with_type(t) return e elif isinstance(t, TString): return EStr(value).with_type(t) elif isinstance(t, TTuple): return ETuple(tuple(uneval(tt, x) for (tt, x) in zip(t.ts, value))).with_type(t) elif isinstance(t, TRecord): return EMakeRecord(tuple((f, uneval(tt, value[f])) for (f, tt) in t.fields)).with_type(t) elif isinstance(t, TEnum): return EEnumEntry(value).with_type(t) elif isinstance(t, THandle): return EHandle(ENum(value.address).with_type(INT), uneval(t.value_type, value.value)).with_type(t) elif isinstance(t, TNative): return ENative(ENum(value[1]).with_type(INT)).with_type(t) else: raise NotImplementedError(pprint(t))
def worst_case_cardinality(e: Exp) -> DominantTerm: assert is_collection(e.type) while isinstance(e, EFilter) or isinstance(e, EMap) or isinstance( e, EFlatMap) or isinstance(e, EMakeMap2) or isinstance( e, EStateVar) or (isinstance(e, EUnaryOp) and e.op == UOp.Distinct) or isinstance( e, EListSlice): e = e.e if isinstance(e, EBinOp) and e.op == "-": return worst_case_cardinality(e.e1) if isinstance(e, EBinOp) and e.op == "+": return worst_case_cardinality(e.e1) + worst_case_cardinality(e.e2) if isinstance(e, EFlatMap): return worst_case_cardinality(e.e) * worst_case_cardinality(e.f.body) if isinstance(e, ECond): return max(worst_case_cardinality(e.then_branch), worst_case_cardinality(e.else_branch)) if isinstance(e, EEmptyList): return DominantTerm.ZERO if isinstance(e, ESingleton): return DominantTerm.ONE if isinstance(e, EMapGet): return worst_case_cardinality(map_value_func(e.map).body) return DominantTerm.N
def possibly_useful_nonrecursive(solver, e : Exp, context : Context, pool = RUNTIME_POOL, assumptions : Exp = ETRUE, ops : [Op] = ()) -> bool: """Heuristic filter to ignore expressions that are almost certainly useless.""" state_vars = OrderedSet(v for v, p in context.vars() if p == STATE_POOL) args = OrderedSet(v for v, p in context.vars() if p == RUNTIME_POOL) assumptions = EAll([assumptions, context.path_condition()]) at_runtime = pool == RUNTIME_POOL h = extension_handler(type(e)) if h is not None: res = h.possibly_useful(e, context, pool, assumptions, ops, solver) if not res: return res if isinstance(e, EStateVar) and not free_vars(e.e): return No("constant value in state position") if (isinstance(e, EDropFront) or isinstance(e, EDropBack)) and not at_runtime: return No("EDrop* in state position") if not allow_big_sets.value and isinstance(e, EFlatMap) and not at_runtime: return No("EFlatMap in state position") if not allow_int_arithmetic_state.value and not at_runtime and isinstance(e, EBinOp) and e.type == INT: return No("integer arithmetic in state position") if is_collection(e.type) and not is_scalar(e.type.elem_type): return No("collection of nonscalar: e {}\n elem_type: {}\n".format(e, e.type.elem_type)) if isinstance(e.type, TMap) and not is_scalar(e.type.k): return No("bad key type {}".format(pprint(e.type.k))) if isinstance(e.type, TMap) and isinstance(e.type.v, TMap): return No("map to map") # This check is probably a bad idea: whether `the` is legal may depend on # the contex that the expression is embedded within, so we can't skip it # during synthesis just because it looks invalid now. # if isinstance(e, EUnaryOp) and e.op == UOp.The: # len = EUnaryOp(UOp.Length, e.e).with_type(INT) # if not valid(EImplies(assumptions, EBinOp(len, "<=", ENum(1).with_type(INT)).with_type(BOOL))): # return No("illegal application of 'the': could have >1 elems") if not at_runtime and isinstance(e, EBinOp) and e.op == "-" and is_collection(e.type): return No("collection subtraction in state position") # if not at_runtime and isinstance(e, ESingleton): # return No("singleton in state position") if not allow_nonzero_state_constants.value and not at_runtime and isinstance(e, ENum) and e.val != 0: return No("nonzero integer constant in state position") if not allow_binop_state.value and at_runtime and isinstance(e, EStateVar) and isinstance(e.e, EBinOp) and is_scalar(e.e.e1.type) and is_scalar(e.e.e2.type): return No("constant-time binary operator {!r} in state position".format(e.e.op)) if not allow_conditional_state.value and not at_runtime and isinstance(e, ECond): return No("conditional in state position") if isinstance(e, EMakeMap2) and isinstance(e.e, EEmptyList): return No("trivially empty map") if isinstance(e, EMakeMap2) and isinstance(e.e, ESingleton): return No("really tiny map") if not at_runtime and (isinstance(e, EArgMin) or isinstance(e, EArgMax)): # Cozy has no way to efficiently implement mins/maxes when more than # one element may leave the collection. from cozy.state_maintenance import mutate for op in ops: elems = e.e elems_prime = mutate(elems, op.body) formula = EAll([assumptions] + list(op.assumptions) + [EGt(ELen(EBinOp(elems, "-", elems_prime).with_type(elems.type)), ONE)]) if solver.satisfiable(formula): return No("more than one element might be removed during {}".format(op.name)) if not allow_peels.value and not at_runtime and isinstance(e, EFilter): # catch "peels": removal of zero or one elements if solver.valid(EImplies(assumptions, ELe(ELen(EFilter(e.e, ELambda(e.predicate.arg, ENot(e.predicate.body))).with_type(e.type)), ONE))): return No("filter is a peel") if not allow_big_maps.value and not at_runtime and isinstance(e, EMakeMap2) and is_collection(e.type.v): all_collections = [sv for sv in state_vars if is_collection(sv.type)] total_size = ENum(0).with_type(INT) for c in all_collections: total_size = EBinOp(total_size, "+", EUnaryOp(UOp.Length, c).with_type(INT)).with_type(INT) my_size = EUnaryOp(UOp.Length, EFlatMap(EUnaryOp(UOp.Distinct, e.e).with_type(e.e.type), e.value_function).with_type(e.type.v)).with_type(INT) s = EImplies( assumptions, EBinOp(total_size, ">=", my_size).with_type(BOOL)) if not solver.valid(s): return No("non-polynomial-sized map") return True
def _try_optimize(e : Exp, context : Context, pool : Pool): if not accelerate.value: return if pool != RUNTIME_POOL: return state_vars = [v for v, p in context.vars() if p == STATE_POOL] args = [v for v, p in context.vars() if p == RUNTIME_POOL] # --------------------------------------------------------------------- # "Rewrite schemes": these trigger on many different AST shapes # They are listed first because they are more powerful than the # specific rewrite rules below. if not free_vars(e) and not free_funcs(e): try: yield _check(uneval(e.type, eval(e, {})), context, RUNTIME_POOL) except NotImplementedError: print("Unable to evaluate {!r}".format(e)) if all(v in state_vars for v in free_vars(e)): nsv = strip_EStateVar(e) sv = EStateVar(nsv).with_type(e.type) yield _check(sv, context, RUNTIME_POOL) for ee in fold_into_map(e, context): yield _check(ee, context, pool) # --------------------------------------------------------------------- # "Rewrites": these trigger on specific AST nodes if isinstance(e, EBinOp): if e.op == "-" and is_collection(e.type): ee = optimized_bag_difference(e.e1, e.e2) yield _check(ee, context, RUNTIME_POOL) if e.op == "===" and isinstance(e.e1.type, THandle): yield _check(EAll([ optimized_eq(optimized_addr(e.e1), optimized_addr(e.e2)), optimized_eq(optimized_val(e.e1), optimized_val(e.e2)).with_type(BOOL)]), context, RUNTIME_POOL) if e.op == BOp.In: ee = optimized_in(e.e1, e.e2) yield _check(ee, context, RUNTIME_POOL) if isinstance(e, ECond): yield _check(optimized_cond(e.cond, e.then_branch, e.else_branch), context, RUNTIME_POOL) if isinstance(e, EGetField): for ee in optimized_get_field(e.e, e.field_name, args): yield _check(ee, context, RUNTIME_POOL) if isinstance(e, EListGet) and e.index == ZERO: for res in optimized_the(e.e, args): yield _check(res, context, RUNTIME_POOL) if isinstance(e, EListGet) and isinstance(e.e, ECond): yield optimized_cond(e.e.cond, EListGet(e.e.then_branch, e.index).with_type(e.type), EListGet(e.e.else_branch, e.index).with_type(e.type)) from cozy.structures.treemultiset import ETreeMultisetElems, ETreeMultisetPeek if isinstance(e, EListGet) and isinstance(e.e, ETreeMultisetElems): yield ETreeMultisetPeek(e.e.e, e.index).with_type(e.type) if isinstance(e, EMapGet): ee = inline_mapget(e, context) yield _check(ee, context, RUNTIME_POOL) if isinstance(e, EUnaryOp): if e.op == UOp.Sum: for ee in optimized_sum(e.e, args): yield _check(ee, context, RUNTIME_POOL) if e.op == UOp.Length: ee = optimized_len(e.e) yield _check(ee, context, RUNTIME_POOL) if e.op == UOp.Empty: ee = optimized_empty(e.e) yield _check(ee, context, RUNTIME_POOL) if e.op == UOp.Exists: ee = optimized_exists(e.e) yield _check(ee, context, RUNTIME_POOL) if e.op == UOp.Distinct: for ee in optimized_distinct(e.e, args): yield _check(ee, context, RUNTIME_POOL) if e.op == UOp.The: for ee in optimized_the(e.e, args): yield _check(ee, context, RUNTIME_POOL) if isinstance(e, EArgMin) or isinstance(e, EArgMax): for ee in optimized_best(e.e, e.key_function, "<" if isinstance(e, EArgMin) else ">", args=args): yield _check(ee, context, RUNTIME_POOL) if isinstance(e, EFilter): for ee in optimized_filter(e.e, e.predicate, args=args): yield _check(ee, context, RUNTIME_POOL) if isinstance(e, EMap): for ee in optimized_map(e.e, e.transform_function, args=args): yield _check(ee, context, RUNTIME_POOL) from cozy.syntax import ESorted from cozy.structures.treemultiset import EMakeMaxTreeMultiset, TMaxTreeMultiset, EMakeMinTreeMultiset, TMinTreeMultiset, ETreeMultisetElems target = e if isinstance(target, ESorted) and isinstance(target.e, EStateVar): e_max = EMakeMaxTreeMultiset(target.e.e).with_type(TMaxTreeMultiset(target.e.e.type.elem_type)) e_min = EMakeMinTreeMultiset(target.e.e).with_type(TMinTreeMultiset(target.e.e.type.elem_type)) ee = optimized_cond(target.asc, ETreeMultisetElems(EStateVar(e_min).with_type(e_min.type)).with_type(target.type), ETreeMultisetElems(EStateVar(e_max).with_type(e_max.type)).with_type(target.type)) yield _check(ee, context, RUNTIME_POOL)
def _compile(e, env: {str: int}, out): if isinstance(e, EVar): i = env[e.id] if isinstance(i, int): def load_var(stk): stk.append(stk[i]) out.append(load_var) else: def load_bound(stk): stk.append(i()) out.append(load_bound) elif isinstance(e, EBool): out.append(push_true if e.val else push_false) elif isinstance(e, ENum): s = e.val def push_num(stk): stk.append(s) out.append(push_num) elif isinstance(e, EStr): s = e.val def push_str(stk): stk.append(s) out.append(push_str) elif isinstance(e, EEnumEntry): s = e.name def push_enum(stk): stk.append(s) out.append(push_enum) elif isinstance(e, EEmptyList): def push_empty_list(stk): stk.append(_EMPTY_BAG) out.append(push_empty_list) elif isinstance(e, ESingleton): _compile(e.e, env, out) if isinstance(e.type, TList): out.append(make_singleton_list) else: out.append(make_singleton_bag) elif isinstance(e, EHandle): _compile(e.addr, env, out) _compile(e.value, env, out) out.append(make_handle) elif isinstance(e, ENull): out.append(push_null) elif isinstance(e, ECond): _compile(e.cond, env, out) then_code = [] _compile(e.then_branch, env, then_code) else_code = [] _compile(e.else_branch, env, else_code) def ite(stk): return then_code if stk.pop() else else_code out.append(ite) elif isinstance(e, EMakeRecord): for (f, ee) in e.fields: _compile(ee, env, out) def make_record(stk): stk.append( FrozenDict((f, stk.pop()) for (f, _) in reversed(e.fields))) out.append(make_record) elif isinstance(e, EGetField): _compile(e.e, env, out) if isinstance(e.e.type, THandle): assert e.f == "val" out.append(get_handle_value) else: assert isinstance(e.e.type, TRecord) f = e.f def get_field(stk): stk.append(stk.pop()[f]) out.append(get_field) elif isinstance(e, ETuple): n = len(e.es) for ee in e.es: _compile(ee, env, out) def make_tuple(stk): entries = reversed([stk.pop() for i in range(n)]) stk.append(tuple(entries)) out.append(make_tuple) elif isinstance(e, ETupleGet): _compile(e.e, env, out) def tuple_get(stk): stk.append(stk.pop()[e.n]) out.append(tuple_get) elif isinstance(e, EStateVar): _compile(e.e, env, out) elif isinstance(e, ENative): _compile(e.e, env, out) def make_native(stk): stk.append((e.type.name, stk.pop())) out.append(make_native) elif isinstance(e, EUnaryOp): _compile(e.e, env, out) if e.op == UOp.Not: out.append(unaryop_not) elif e.op == UOp.Sum: out.append(unaryop_sum) elif e.op == UOp.Exists: out.append(unaryop_exists) elif e.op == UOp.Empty: out.append(unaryop_empty) elif e.op == UOp.All: out.append(unaryop_all) elif e.op == UOp.Any: out.append(unaryop_any) elif e.op == UOp.Length: out.append(unaryop_len) elif e.op == UOp.AreUnique: out.append(unaryop_areunique(e.e.type.t)) elif e.op == UOp.Distinct: out.append(unaryop_distinct(e.e.type.t)) elif e.op == UOp.The: out.append(unaryop_the(default=mkval(e.type))) elif e.op == UOp.Reversed: out.append(unaryop_reversed) elif e.op == "-": out.append(unaryop_neg) else: raise NotImplementedError(e.op) elif isinstance(e, EBinOp): if e.op == BOp.And: return _compile(ECond(e.e1, e.e2, F).with_type(BOOL), env, out) elif e.op == BOp.Or: return _compile(ECond(e.e1, T, e.e2).with_type(BOOL), env, out) elif e.op == "=>": return _compile(ECond(e.e1, e.e2, T).with_type(BOOL), env, out) _compile(e.e1, env, out) _compile(e.e2, env, out) e1type = e.e1.type if e.op == "+": if is_collection(e.type): out.append(binaryop_add_collections) else: out.append(binaryop_add_numbers) elif e.op == "*": out.append(binaryop_mul) elif e.op == "-": if isinstance(e.type, TBag) or isinstance(e.type, TSet): out.append(binaryop_sub_bags(e.type.t)) elif isinstance(e.type, TList): out.append(binaryop_sub_lists(e.type.t)) else: out.append(binaryop_sub) elif e.op == "==": out.append(binaryop_eq(e1type)) elif e.op == "===": out.append(binaryop_eq(e1type, deep=True)) elif e.op == "<": out.append(binaryop_lt(e1type)) elif e.op == ">": out.append(binaryop_gt(e1type)) elif e.op == "<=": out.append(binaryop_le(e1type)) elif e.op == ">=": out.append(binaryop_ge(e1type)) elif e.op == "!=": out.append(binaryop_ne(e1type)) elif e.op == BOp.In: out.append(binaryop_in(e1type)) else: raise NotImplementedError(e.op) elif isinstance(e, EListGet): _compile(e.e, env, out) _compile(e.index, env, out) out.append(list_index(mkval(e.type))) elif isinstance(e, EListSlice): _compile(e.e, env, out) _compile(e.start, env, out) _compile(e.end, env, out) out.append(list_slice) elif isinstance(e, EDropFront): _compile(e.e, env, out) out.append(drop_front) elif isinstance(e, EDropBack): _compile(e.e, env, out) out.append(drop_back) elif isinstance(e, EFilter): _compile(e.e, env, out) box = [None] body = [] with extend(env, e.p.arg.id, lambda: box[0]): _compile(e.p.body, env, body) def set_arg(v): def set_arg(stk): box[0] = v return set_arg def maybe_append_to_result(idx): return lambda stk: (stk[idx].append(box[0]) if stk.pop() else None) def do_filter(stk): bag = stk.pop() res_idx = len(stk) stk.append([]) ops = [] for (i, val) in enumerate(bag): ops.append(set_arg(val)) ops.extend(body) ops.append(maybe_append_to_result(res_idx)) return ops out.append(do_filter) out.append(iterable_to_bag) elif isinstance(e, EMap): _compile(e.e, env, out) box = [None] body = [] with extend(env, e.f.arg.id, lambda: box[0]): _compile(e.f.body, env, body) def set_arg(v): def set_arg(stk): box[0] = v return set_arg def append_to_result(idx): return lambda stk: stk[idx].append(stk.pop()) def do_map(stk): bag = stk.pop() res_idx = len(stk) stk.append([]) ops = [] for (i, val) in enumerate(bag): ops.append(set_arg(val)) ops.extend(body) ops.append(append_to_result(res_idx)) return ops out.append(do_map) out.append(iterable_to_bag) elif isinstance(e, EFlatMap): _compile(EMap(e.e, e.f).with_type(TBag(e.type)), env, out) out.append(do_concat) elif isinstance(e, EArgMin) or isinstance(e, EArgMax): # stack layout: # len | f(best) | best | elem_0 | ... | elem_len # body is a seq. of opcodes that has the effect of pushing # f(top_of_stack) onto the stack, leaving the old top underneath box = [None] def set_arg(stk): box[0] = stk[-1] body = [set_arg] with extend(env, e.f.arg.id, lambda: box[0]): _compile(e.f.body, env, body) keytype = e.f.body.type def initialize(stk): bag = stk.pop() if bag: stk.extend(reversed(bag)) else: stk.append(mkval(e.type)) return body + [push(len(bag) - 1)] do_cmp = binaryop_lt(keytype) if isinstance( e, EArgMin) else binaryop_gt(keytype) def loop(stk): len = stk.pop() key = stk.pop() if len > 0: best = stk.pop() return body + [ dup, push(key), do_cmp, if_then_else( [], [drop, drop, push(best), push(key)]), push(len - 1), loop ] _compile(e.e, env, out) out.append(initialize) out.append(loop) elif isinstance(e, EMakeMap2): _compile( EMap( e.e, ELambda( e.value.arg, ETuple((e.value.arg, e.value.body)).with_type( TTuple((e.value.arg.type, e.value.body.type))))).with_type( TBag( TTuple((e.value.arg.type, e.value.body.type)))), env, out) default = mkval(e.type.v) def make_map(stk): res = Map(e.type, default) for (k, v) in reversed(list(stk.pop())): res[k] = v stk.append(res) out.append(make_map) elif isinstance(e, EMapGet): _compile(e.map, env, out) _compile(e.key, env, out) out.append(read_map) elif isinstance(e, EHasKey): _compile(e.map, env, out) _compile(e.key, env, out) out.append(has_key(e.key.type)) elif isinstance(e, EMapKeys): _compile(e.e, env, out) out.append(read_map_keys) elif isinstance(e, ECall): _compile(EVar(e.func), env, out) for a in e.args: _compile(a, env, out) n = len(e.args) def call(stk): args = reversed([stk.pop() for i in range(n)]) f = stk.pop() stk.append(f(*args)) out.append(call) elif isinstance(e, ELet): _compile(e.e, env, out) box = [None] def set_arg(v): def set_arg(stk): box[0] = v return set_arg def do_bind(stk): return [set_arg(stk.pop())] out.append(do_bind) with extend(env, e.f.arg.id, lambda: box[0]): _compile(e.f.body, env, out) else: h = extension_handler(type(e)) if h is not None: _compile(h.encode(e), env, out) else: raise NotImplementedError(type(e)) if hasattr(e, "type") and isinstance(e.type, TList): out.append(iterable_to_list)
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 visit(self, e): if hasattr(e, "_nosimpl"): return e if isinstance(e, Exp) and not isinstance(e, ELambda): t = e.type new = super().visit(e) if isinstance(e, Exp) and not isinstance(e, ELambda): assert new.type == e.type or (is_collection(new.type) and is_collection(e.type)), repr(e) if self.debug and isinstance(e, Exp) and not isinstance(e, ELambda): model = satisfy(ENot(EBinOp(e, "===", new).with_type(BOOL))) if model is not None: raise Exception("bad simplification: {} ---> {} (under model {!r}, got {!r} and {!r})".format(pprint(e), pprint(new), model, eval(e, model), eval(new, model))) return new
def _compile(e, env : {str:int}, out): if isinstance(e, EVar): i = env[e.id] if isinstance(i, int): def load_var(stk): stk.append(stk[i]) out.append(load_var) else: def load_bound(stk): stk.append(i()) out.append(load_bound) elif isinstance(e, EBool): out.append(push_true if e.val else push_false) elif isinstance(e, ENum): s = e.val if e.type == FLOAT: s = Fraction(str(s)) def push_num(stk): stk.append(s) out.append(push_num) elif isinstance(e, EStr): s = e.val def push_str(stk): stk.append(s) out.append(push_str) elif isinstance(e, EEnumEntry): s = e.name def push_enum(stk): stk.append(s) out.append(push_enum) elif isinstance(e, EEmptyList): def push_empty_list(stk): stk.append(_EMPTY_BAG) out.append(push_empty_list) elif isinstance(e, ESingleton): _compile(e.e, env, out) if isinstance(e.type, TList): out.append(make_singleton_list) else: out.append(make_singleton_bag) elif isinstance(e, EHandle): _compile(e.addr, env, out) _compile(e.value, env, out) out.append(make_handle) elif isinstance(e, ENull): out.append(push_null) elif isinstance(e, ECond): _compile(e.cond, env, out) then_code = []; _compile(e.then_branch, env, then_code) else_code = []; _compile(e.else_branch, env, else_code) def ite(stk): return then_code if stk.pop() else else_code out.append(ite) elif isinstance(e, EMakeRecord): for (f, ee) in e.fields: _compile(ee, env, out) def make_record(stk): stk.append(FrozenDict((f, stk.pop()) for (f, _) in reversed(e.fields))) out.append(make_record) elif isinstance(e, EGetField): _compile(e.e, env, out) if isinstance(e.e.type, THandle): assert e.field_name == "val" out.append(get_handle_value) else: assert isinstance(e.e.type, TRecord) f = e.field_name def get_field(stk): stk.append(stk.pop()[f]) out.append(get_field) elif isinstance(e, ETuple): n = len(e.es) for ee in e.es: _compile(ee, env, out) def make_tuple(stk): entries = reversed([stk.pop() for i in range(n)]) stk.append(tuple(entries)) out.append(make_tuple) elif isinstance(e, ETupleGet): _compile(e.e, env, out) def tuple_get(stk): stk.append(stk.pop()[e.index]) out.append(tuple_get) elif isinstance(e, EStateVar): _compile(e.e, env, out) elif isinstance(e, ENative): _compile(e.e, env, out) def make_native(stk): stk.append((e.type.name, stk.pop())) out.append(make_native) elif isinstance(e, EUnaryOp): _compile(e.e, env, out) if e.op == UOp.Not: out.append(unaryop_not) elif e.op == UOp.Sum: out.append(unaryop_sum) elif e.op == UOp.Exists: out.append(unaryop_exists) elif e.op == UOp.Empty: out.append(unaryop_empty) elif e.op == UOp.All: out.append(unaryop_all) elif e.op == UOp.Any: out.append(unaryop_any) elif e.op == UOp.Length: out.append(unaryop_len) elif e.op == UOp.AreUnique: out.append(unaryop_areunique(e.e.type.elem_type)) elif e.op == UOp.Distinct: out.append(unaryop_distinct(e.e.type.elem_type)) elif e.op == UOp.The: out.append(unaryop_the(default=mkval(e.type))) elif e.op == UOp.Reversed: out.append(unaryop_reversed) elif e.op == "-": out.append(unaryop_neg) else: raise NotImplementedError(e.op) elif isinstance(e, EBinOp): if e.op == BOp.And: return _compile(ECond(e.e1, e.e2, EFALSE).with_type(BOOL), env, out) elif e.op == BOp.Or: return _compile(ECond(e.e1, ETRUE, e.e2).with_type(BOOL), env, out) elif e.op == "=>": return _compile(ECond(e.e1, e.e2, ETRUE).with_type(BOOL), env, out) _compile(e.e1, env, out) _compile(e.e2, env, out) e1type = e.e1.type if e.op == "+": if is_collection(e.type): out.append(binaryop_add_sets if isinstance(e.type, TSet) else binaryop_add_collections) else: out.append(binaryop_add_numbers) elif e.op == "*": out.append(binaryop_mul) elif e.op == "-": if isinstance(e.type, TBag) or isinstance(e.type, TSet): out.append(binaryop_sub_bags(e.type.elem_type)) elif isinstance(e.type, TList): out.append(binaryop_sub_lists(e.type.elem_type)) else: out.append(binaryop_sub) elif e.op == "==": out.append(binaryop_eq(e1type)) elif e.op == "===": out.append(binaryop_eq(e1type, deep=True)) elif e.op == "<": out.append(binaryop_lt(e1type)) elif e.op == ">": out.append(binaryop_gt(e1type)) elif e.op == "<=": out.append(binaryop_le(e1type)) elif e.op == ">=": out.append(binaryop_ge(e1type)) elif e.op == "!=": out.append(binaryop_ne(e1type)) elif e.op == BOp.In: out.append(binaryop_in(e1type)) else: raise NotImplementedError(e.op) elif isinstance(e, EListGet): _compile(e.e, env, out) _compile(e.index, env, out) out.append(list_index(mkval(e.type))) elif isinstance(e, EListSlice): _compile(e.e, env, out) _compile(e.start, env, out) _compile(e.end, env, out) out.append(list_slice) elif isinstance(e, EDropFront): _compile(e.e, env, out) out.append(drop_front) elif isinstance(e, EDropBack): _compile(e.e, env, out) out.append(drop_back) elif isinstance(e, ESorted): _compile(e.e, env, out) _compile(e.asc, env, out) def bag_sort(stk): asc = stk.pop() bag = stk.pop() bag = sorted(bag, reverse=not asc) assert isinstance(asc, bool) assert isinstance(bag, list) stk.append(bag) out.append(bag_sort) elif isinstance(e, EFilter): _compile(e.e, env, out) box = [None] body = [] with extend(env, e.predicate.arg.id, lambda: box[0]): _compile(e.predicate.body, env, body) def set_arg(v): def set_arg(stk): box[0] = v return set_arg def maybe_append_to_result(idx): return lambda stk: (stk[idx].append(box[0]) if stk.pop() else None) def do_filter(stk): bag = stk.pop() res_idx = len(stk) stk.append([]) ops = [] for (i, val) in enumerate(bag): ops.append(set_arg(val)) ops.extend(body) ops.append(maybe_append_to_result(res_idx)) return ops out.append(do_filter) out.append(iterable_to_bag) elif isinstance(e, EMap): _compile(e.e, env, out) box = [None] body = [] with extend(env, e.transform_function.arg.id, lambda: box[0]): _compile(e.transform_function.body, env, body) def set_arg(v): def set_arg(stk): box[0] = v return set_arg def append_to_result(idx): return lambda stk: stk[idx].append(stk.pop()) def do_map(stk): bag = stk.pop() res_idx = len(stk) stk.append([]) ops = [] for (i, val) in enumerate(bag): ops.append(set_arg(val)) ops.extend(body) ops.append(append_to_result(res_idx)) return ops out.append(do_map) out.append(iterable_to_bag) elif isinstance(e, EFlatMap): _compile(EMap(e.e, e.transform_function).with_type(TBag(e.type)), env, out) out.append(do_concat) elif isinstance(e, EArgMin) or isinstance(e, EArgMax): # stack layout: # len | f(best) | best | elem_0 | ... | elem_len # body is a seq. of opcodes that has the effect of pushing # f(top_of_stack) onto the stack, leaving the old top underneath box = [None] def set_arg(stk): box[0] = stk[-1] body = [set_arg] with extend(env, e.key_function.arg.id, lambda: box[0]): _compile(e.key_function.body, env, body) keytype = e.key_function.body.type def initialize(stk): bag = stk.pop() if bag: stk.extend(reversed(bag)) else: stk.append(mkval(e.type)) return body + [push(len(bag)-1)] do_cmp = binaryop_lt(keytype) if isinstance(e, EArgMin) else binaryop_gt(keytype) def loop(stk): len = stk.pop() key = stk.pop() if len > 0: best = stk.pop() return body + [dup, push(key), do_cmp, if_then_else( [], [drop, drop, push(best), push(key)]), push(len-1), loop] _compile(e.e, env, out) out.append(initialize) out.append(loop) elif isinstance(e, EMakeMap2): _compile(EMap(e.e, ELambda(e.value_function.arg, ETuple((e.value_function.arg, e.value_function.body)).with_type(TTuple((e.value_function.arg.type, e.value_function.body.type))))).with_type(TBag(TTuple((e.value_function.arg.type, e.value_function.body.type)))), env, out) default = mkval(e.type.v) def make_map(stk): res = Map(e.type, default) for (k, v) in list(stk.pop()): res[k] = v stk.append(res) out.append(make_map) elif isinstance(e, EMapGet): _compile(e.map, env, out) _compile(e.key, env, out) out.append(read_map) elif isinstance(e, EHasKey): _compile(e.map, env, out) _compile(e.key, env, out) out.append(has_key(e.key.type)) elif isinstance(e, EMapKeys): _compile(e.e, env, out) out.append(read_map_keys) elif isinstance(e, ECall): _compile(EVar(e.func), env, out) for a in e.args: _compile(a, env, out) n = len(e.args) def call(stk): args = reversed([stk.pop() for i in range(n)]) f = stk.pop() stk.append(f(*args)) out.append(call) elif isinstance(e, ELet): _compile(e.e, env, out) box = [None] def set_arg(v): def set_arg(stk): box[0] = v return set_arg def do_bind(stk): return [set_arg(stk.pop())] out.append(do_bind) with extend(env, e.body_function.arg.id, lambda: box[0]): _compile(e.body_function.body, env, out) else: from cozy.structures.treemultiset import ETreeMultisetElems h = extension_handler(type(e)) if h is not None: if isinstance(e, ETreeMultisetElems) and isinstance(e.e, EVar): _compile(e.e, env, out) # this is a no-op because its argument, the Treeset, will be sorted already def no_op(stk): pass out.append(no_op) else: _compile(h.encode(e), env, out) else: raise NotImplementedError(type(e)) if hasattr(e, "type") and isinstance(e.type, TList): out.append(iterable_to_list)
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)