def construct_concrete(self, t: Type, e: Exp, out: Exp): """ Construct a value of type `t` from the expression `e` and store it in lvalue `out`. """ if hasattr(t, "construct_concrete"): return t.construct_concrete(e, out) elif isinstance(t, TBag) or isinstance(t, TList): assert out not in free_vars(e) x = self.fv(t.t, "x") return SSeq(self.initialize_native_list(out), SForEach(x, e, SCall(out, "add", [x]))) elif isinstance(t, TSet): if isinstance(e, EUnaryOp) and e.op == UOp.Distinct: return self.construct_concrete(t, e.e, out) x = self.fv(t.t, "x") return SSeq(self.initialize_native_set(out), SForEach(x, e, SCall(out, "add", [x]))) elif isinstance(t, TMap): return SSeq(self.initialize_native_map(out), self.construct_map(t, e, out)) elif isinstance(t, THandle): return SEscape("{indent}{lhs} = {rhs};\n", ["lhs", "rhs"], [out, e]) elif is_scalar(t): return SEscape("{indent}{lhs} = {rhs};\n", ["lhs", "rhs"], [out, e]) else: h = extension_handler(type(t)) if h is not None: return h.codegen(e, self.state_exps, out=out) raise NotImplementedError(t, e, out)
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 visit_SCall(self, call): h = extension_handler(type(call.target.type)) if h is not None: return self.visit(h.implement_stmt(call, self.state_exps)) target = self.visit(call.target) args = [self.visit(a) for a in call.args] self.begin_statement() if type(call.target.type) in (TBag, TList): if call.func == "add": self.write(target, ".push_back(", args[0], ");") elif call.func == "remove": v = self.fv(TNative("auto"), "it") self.write("auto ", v.id, "(::std::find(", target, ".begin(), ", target, ".end(), ", args[0], "));") self.end_statement() self.begin_statement() self.write("if (", v.id, " != ", target, ".end()) { ", target, ".erase(", v.id, "); }") else: raise NotImplementedError(call.func) elif isinstance(call.target.type, TSet): if call.func == "add": self.write(target, ".insert(", args[0], ");") elif call.func == "remove": self.write(target, ".erase(", target, ".find(", args[0], "));") else: raise NotImplementedError(call.func) else: raise NotImplementedError() 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 exp_wf_nonrecursive(solver, e : Exp, context : Context, pool = RUNTIME_POOL, assumptions : Exp = ETRUE): """Check the well-formedness of `e` but do not recurse into its children. Returns True or an instance of No explaining why `e` is not well-formed. See `exp_wf` for an explanation of well-formedness and the parameters that this function requires. """ if hasattr(e, "_wf"): return True 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) h = extension_handler(type(e)) if h is not None: assumptions = EAll([assumptions, context.path_condition()]) msg = h.check_wf(e, state_vars=state_vars, args=args, pool=pool, assumptions=assumptions, is_valid=solver.valid) if msg is not None: return No(msg) e._wf = True return True at_runtime = pool == RUNTIME_POOL if isinstance(e, EStateVar) and not at_runtime: return No("EStateVar in state pool position") if isinstance(e, EVar): if at_runtime and e in state_vars: return No("state var at runtime") elif not at_runtime and e in args: return No("arg in state exp") e._wf = True return True
def exp_wf_nonrecursive(solver, e: Exp, context: Context, pool=RUNTIME_POOL, assumptions: Exp = T): 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()]) h = extension_handler(type(e)) if h is not None: msg = h.check_wf(e, state_vars=state_vars, args=args, pool=pool, assumptions=assumptions, is_valid=solver.valid) if msg is not None: raise ExpIsNotWf(e, e, msg) return at_runtime = pool == RUNTIME_POOL if isinstance(e, EStateVar) and not at_runtime: raise ExpIsNotWf(e, e, "EStateVar in state pool position") if isinstance(e, EVar): if at_runtime and e in state_vars: raise ExpIsNotWf(e, e, "state var at runtime") elif not at_runtime and e in args: raise ExpIsNotWf(e, e, "arg in state exp")
def visit_Exp(self, e): h = extension_handler(type(e)) if h is not None: v = self.fv(e.type) self.declare(v) self.visit(h.codegen(e, self.state_exps, out=v)) return v.id else: raise NotImplementedError(e)
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 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 mutate_in_place(lval: syntax.Exp, e: syntax.Exp, op: syntax.Stm, abstract_state: [syntax.EVar], assumptions: [syntax.Exp] = None, invariants: [syntax.Exp] = None, subgoals_out: [syntax.Query] = None) -> syntax.Stm: """ Produce code to update `lval` that tracks derived value `e` when `op` is run. """ if assumptions is None: assumptions = [] if invariants is None: invariants = [] if subgoals_out is None: subgoals_out = [] def make_subgoal(e, a=[], docstring=None): if skip_stateless_synthesis.value and not any(v in abstract_state for v in free_vars(e)): return e query_name = fresh_name("query") query = syntax.Query(query_name, syntax.Visibility.Internal, [], assumptions + a, e, docstring) query_vars = [v for v in free_vars(query) if v not in abstract_state] query.args = [(arg.id, arg.type) for arg in query_vars] subgoals_out.append(query) return syntax.ECall(query_name, tuple(query_vars)).with_type(e.type) h = extension_handler(type(lval.type)) if h is not None: return h.mutate_in_place(lval=lval, e=e, op=op, assumptions=assumptions, invariants=invariants, make_subgoal=make_subgoal) # fallback: use an update sketch new_e = mutate(e, op) s, sgs = sketch_update(lval, e, new_e, ctx=abstract_state, assumptions=assumptions, invariants=invariants) subgoals_out.extend(sgs) return s
def exp_wf_nonrecursive(solver, e: Exp, context: Context, pool=RUNTIME_POOL, assumptions: Exp = ETRUE): """Check the well-formedness of `e` but do not recurse into its children. Returns True or an instance of No explaining why `e` is not well-formed. See `exp_wf` for an explanation of well-formedness and the parameters that this function requires. """ if hasattr(e, "_wf"): return True 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) h = extension_handler(type(e)) if h is not None: assumptions = EAll([assumptions, context.path_condition()]) msg = h.check_wf(e, state_vars=state_vars, args=args, pool=pool, assumptions=assumptions, is_valid=solver.valid) if msg is not None: return No(msg) e._wf = True return True at_runtime = pool == RUNTIME_POOL if isinstance(e, EStateVar) and not at_runtime: return No("EStateVar in state pool position") if isinstance(e, EVar): if at_runtime and e in state_vars: return No("state var at runtime") elif not at_runtime and e in args: return No("arg in state exp") e._wf = True return True
def visit_Exp(self, e): h = extension_handler(type(e)) if h is not None: h.typecheck(e, self.visit, self.report)
def visit_Exp(self, e): h = extension_handler(type(e)) if h is not None: h.typecheck(e, self.visit, self.report)
def _maintenance_cost(e: Exp, op: Op, freebies: [Exp] = []): """Determines the cost of maintaining the expression when there are freebies and ops being considered. The cost is the result of mutating the expression and getting the storage size of the difference between the mutated expression and the original. """ e_prime = mutate(e, op.body) if alpha_equivalent(e, e_prime): return ZERO h = extension_handler(type(e.type)) if h is not None: return h.maintenance_cost(old_value=e, new_value=e_prime, op=op, freebies=freebies, storage_size=storage_size, maintenance_cost=_maintenance_cost) if is_scalar(e.type): return storage_size(e, freebies) elif isinstance(e.type, TBag) or isinstance(e.type, TSet): things_added = storage_size( EBinOp(e_prime, "-", e).with_type(e.type), freebies).with_type(INT) things_remov = storage_size( EBinOp(e, "-", e_prime).with_type(e.type), freebies).with_type(INT) return ESum([things_added, things_remov]) elif isinstance(e.type, TList): return storage_size(e_prime, freebies) elif isinstance(e.type, TMap): keys = EMapKeys(e).with_type(TBag(e.type.k)) vals = EMap( keys, mk_lambda(e.type.k, lambda k: EMapGet(e, k).with_type(e.type.v))).with_type( TBag(e.type.v)) keys_prime = EMapKeys(e_prime).with_type(TBag(e_prime.type.k)) vals_prime = EMap( keys_prime, mk_lambda(e_prime.type.k, lambda k: EMapGet(e_prime, k).with_type( e_prime.type.v))).with_type(TBag(e_prime.type.v)) keys_added = storage_size( EBinOp(keys_prime, "-", keys).with_type(keys.type), freebies).with_type(INT) keys_rmved = storage_size( EBinOp(keys, "-", keys_prime).with_type(keys.type), freebies).with_type(INT) vals_added = storage_size( EBinOp(vals_prime, "-", vals).with_type(vals.type), freebies).with_type(INT) vals_rmved = storage_size( EBinOp(vals, "-", vals_prime).with_type(vals.type), freebies).with_type(INT) keys_difference = ESum([keys_added, keys_rmved]) vals_difference = ESum([vals_added, vals_rmved]) return EBinOp(keys_difference, "*", vals_difference).with_type(INT) else: raise NotImplementedError(repr(e.type))
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 exp_wf_nonrecursive(solver, e : Exp, context : Context, pool = RUNTIME_POOL, assumptions : Exp = T): 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()]) h = extension_handler(type(e)) if h is not None: msg = h.check_wf(e, state_vars=state_vars, args=args, pool=pool, assumptions=assumptions, is_valid=solver.valid) if msg is not None: raise ExpIsNotWf(e, e, msg) return at_runtime = pool == RUNTIME_POOL if isinstance(e, EStateVar) and not at_runtime: raise ExpIsNotWf(e, e, "EStateVar in state pool position") if isinstance(e, EStateVar): fvs = free_vars(e.e) if not fvs: raise ExpIsNotWf(e, e, "constant value in state position") bad = [v for v in fvs if v not in state_vars] if bad: raise ExpIsNotWf(e, e, "free non-statevars in state position: {}".format(", ".join(v.id for v in bad))) if (isinstance(e, EDropFront) or isinstance(e, EDropBack)) and not at_runtime: raise ExpIsNotWf(e, e, "EDrop* in state position") if isinstance(e, EFlatMap) and not at_runtime: raise ExpIsNotWf(e, e, "EFlatMap in state position") if not allow_int_arithmetic_state.value and not at_runtime and isinstance(e, EBinOp) and e.type == INT: raise ExpIsNotWf(e, e, "integer arithmetic in state position") # if isinstance(e, EUnaryOp) and e.op == UOp.Distinct and not at_runtime: # raise ExpIsNotWf(e, e, "'distinct' in state position") # if isinstance(e, EMapKeys) and not at_runtime: # raise ExpIsNotWf(e, e, "'mapkeys' in state position") if isinstance(e, EVar): if at_runtime and e in state_vars: raise ExpIsNotWf(e, e, "state var at runtime") elif not at_runtime and e in args: raise ExpIsNotWf(e, e, "arg in state exp") # if is_collection(e.type) and is_collection(e.type.t): # raise ExpIsNotWf(e, e, "collection of collection") if is_collection(e.type) and not is_scalar(e.type.t): raise ExpIsNotWf(e, e, "collection of nonscalar") if isinstance(e.type, TMap) and not is_scalar(e.type.k): raise ExpIsNotWf(e, e, "bad key type {}".format(pprint(e.type.k))) if isinstance(e.type, TMap) and isinstance(e.type.v, TMap): raise ExpIsNotWf(e, e, "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))): # raise ExpIsNotWf(e, e, "illegal application of 'the': could have >1 elems") if not at_runtime and isinstance(e, EBinOp) and e.op == "-" and is_collection(e.type): raise ExpIsNotWf(e, e, "collection subtraction in state position") if not at_runtime and isinstance(e, ESingleton): raise ExpIsNotWf(e, e, "singleton in state position") # if not at_runtime and isinstance(e, ENum) and e.val != 0 and e.type == INT: # raise ExpIsNotWf(e, e, "nonzero integer constant in state position") if not allow_conditional_state.value and not at_runtime and isinstance(e, ECond): raise ExpIsNotWf(e, e, "conditional in state position") if isinstance(e, EMakeMap2) and isinstance(e.e, EEmptyList): raise ExpIsNotWf(e, e, "trivially empty map") if do_expensive_checks.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.p.arg, ENot(e.p.body))).with_type(e.type)), ONE))): raise ExpIsNotWf(e, e, "filter is a peel") if do_expensive_checks.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).with_type(e.type.v)).with_type(INT) s = EImplies( assumptions, EBinOp(total_size, ">=", my_size).with_type(BOOL)) if not solver.valid(s): # from cozy.evaluation import eval # from cozy.solver import satisfy # model = satisfy(EAll([assumptions, EBinOp(total_size, "<", my_size).with_type(BOOL)]), collection_depth=3, validate_model=True) # assert model is not None # raise ExpIsNotWf(e, e, "non-polynomial-sized map ({}); total_size={}, this_size={}".format(model, eval(total_size, model), eval(my_size, model))) raise ExpIsNotWf(e, e, "non-polynomial-sized map")
def compare_values(t : Type, v1, v2, deep : bool = False) -> int: """Compare two Cozy values, returning LT, EQ, or GT. Parameters: t - the type of the values being compared v1 - the first value v2 - the second value deep - true to do a "deep" comparison that cares about iteration order on collections Because the LT, EQ, and GT constants are defined to be the integers -1, 0, and 1 respectively, this function can be used as an old-style comparator: values.sort(key=functools.cmp_to_key( lambda v1, v2: compare_values(t, v1, v2))) """ # For performance, this function uses a work-stack algorithm rather than # recursive calls to itself. The stack holds (type, value1, value2, deep) # tuples to compare. If the values on the head of the stack differ, then # this function can abort immediately with LT or GT. Otherwise it # continues to the next stack item. stk = [(t, v1, v2, deep)] while stk: (t, v1, v2, deep) = stk.pop() h = extension_handler(type(t)) if h is not None: stk.append((h.encoding_type(t), v1, v2, deep)) continue if isinstance(t, THandle): if deep: stk.append((t.value_type, v1.value, v2.value, deep)) stk.append((INT, v1.address, v2.address, deep)) elif isinstance(t, TEnum): i1 = t.cases.index(v1) i2 = t.cases.index(v2) if i1 == i2: continue if i1 < i2: return LT else: return GT elif isinstance(t, TBag) or isinstance(t, TSet): if deep: elems1 = list(v1) elems2 = list(v2) else: elems1 = list(sorted(v1)) elems2 = list(sorted(v2)) if len(elems1) < len(elems2): return LT if len(elems1) > len(elems2): return GT stk.extend(reversed([(t.t, x, y, deep) for (x, y) in zip(elems1, elems2)])) elif isinstance(t, TMap): keys1 = Bag(v1.keys()) keys2 = Bag(v2.keys()) stk.extend(reversed([(t.v, v1[k], v2[k], deep) for k in sorted(keys1)])) stk.append((TSet(t.k), keys1, keys2, False)) stk.append((t.v, v1.default, v2.default, deep)) elif isinstance(t, TTuple): stk.extend(reversed([(tt, vv1, vv2, deep) for (tt, vv1, vv2) in zip(t.ts, v1, v2)])) elif isinstance(t, TList): stk.extend(reversed([(t.t, vv1, vv2, deep) for (vv1, vv2) in zip(v1, v2)])) stk.append((INT, len(v1), len(v2), deep)) elif isinstance(t, TRecord): stk.extend(reversed([(ft, v1[f], v2[f], deep) for (f, ft) in t.fields])) else: if v1 == v2: continue elif v1 < v2: return LT else: return GT return EQ
def mutate_in_place( lval : syntax.Exp, e : syntax.Exp, op : syntax.Stm, abstract_state : [syntax.EVar], assumptions : [syntax.Exp] = None, invariants : [syntax.Exp] = None, subgoals_out : [syntax.Query] = None) -> syntax.Stm: """ Produce code to update `lval` that tracks derived value `e` when `op` is run. Parameters: - abstract_state: variables that are part of the abstract state of the data structure - assumptions: formulas that are known to be true before `op` runs - invariants: true formulas about the abstract state - subgoals_out [out parameter]: a list that will be filled with new query operations The queries written into `subgoals_out` will be used by the returned code. They will include `assumptions` as preconditions, but not `invariants`. This behavior allows them to be added to an implementation that already has the given invariants. Note that the returned subgoals are all in terms of the pre-state before `op` runs. """ if assumptions is None: assumptions = [] else: assumptions = list(assumptions) if invariants is None: invariants = [] if subgoals_out is None: subgoals_out = [] def make_subgoal(e, a=[], docstring=None): if skip_stateless_synthesis.value and not any(v in abstract_state for v in free_vars(e)): return e query_name = fresh_name("query") query = syntax.Query(query_name, syntax.Visibility.Internal, [], assumptions + a, e, docstring) query_vars = [v for v in free_vars(query) if v not in abstract_state] query.args = [(arg.id, arg.type) for arg in query_vars] subgoals_out.append(query) return syntax.ECall(query_name, tuple(query_vars)).with_type(e.type) h = extension_handler(type(lval.type)) if h is not None: ret = h.mutate_in_place( lval=lval, e=e, op=op, assumptions=assumptions, invariants=invariants, make_subgoal=make_subgoal) if ret is not None: return ret # fallback: use an update sketch new_e = mutate(e, op) s, sgs = sketch_update(lval, e, new_e, ctx=abstract_state, assumptions=assumptions, invariants=invariants) subgoals_out.extend(sgs) return s
def visit_SCall(self, s): h = extension_handler(type(s.target.type)) if h is not None: return self.visit(h.implement_stmt(s, self.concretization_functions)) return self.visit_ADT(s)
def visit_Type(self, ty): h = extension_handler(type(ty)) if h is not None: return self.visit(h.rep_type(ty)) return self.visit_ADT(ty)
def compare_values(t: Type, v1, v2, deep: bool = False) -> int: """Compare two Cozy values, returning LT, EQ, or GT. Parameters: t - the type of the values being compared v1 - the first value v2 - the second value deep - true to do a "deep" comparison that cares about iteration order on collections Because the LT, EQ, and GT constants are defined to be the integers -1, 0, and 1 respectively, this function can be used as an old-style comparator: values.sort(key=functools.cmp_to_key( lambda v1, v2: compare_values(t, v1, v2))) Notes regarding `deep`: There are two definitions of equality between Cozy values: == (aka "double-equals" or "normal equality") === (aka "triple-equals" or "deep equality") Normal equality is what a user means when they write "x == y" in an input specification: pointers are compared by address and two sets are the same if they have all the same elements. Deep equality is more useful internally: "x === y" means that every observable property of x is the same for y. In other words, an expression that uses x can be replaced by the same expression using y instead, and the output will always be the same. This is NOT true of double-equals expressions. A good example of this is the operator UOp.The, which gets the first element of a collection. Even if (x==y) for sets x and y, it can be true that (the x != the y), if the elements of x and y happen to be in different orders. In Python code, the == operator implements deep equality between Cozy values. This is very important so that Cozy values can be keys in maps, and two Cozy values that are not perfectly 100% the same do not overlap. The call `compare_values(type, v1, v2) == EQ` checks for normal equality. The call `compare_values(type, v1, v2, deep=True) == EQ` produces the same results as `v1 == v2`. However, performance might be slightly better if v1 and v2 are large collections. """ # For performance, this function uses a work-stack algorithm rather than # recursive calls to itself. The stack holds (type, value1, value2, deep) # tuples to compare. If the values on the head of the stack differ, then # this function can abort immediately with LT or GT. Otherwise it # continues to the next stack item. stk = [(t, v1, v2, deep)] while stk: # The code in this loop looks backwards since the "top" of the stack # is the last elment in the list. Thus, the next item to be compared # needs to be pushed LAST. As a result, we append sequences to the # stack in reversed order and we append more-important elements later. (t, v1, v2, deep) = stk.pop() h = extension_handler(type(t)) if h is not None: stk.append((h.encoding_type(t), v1, v2, deep)) continue if isinstance(t, THandle): if deep: stk.append((t.value_type, v1.value, v2.value, deep)) stk.append((INT, v1.address, v2.address, deep)) elif isinstance(t, TEnum): i1 = t.cases.index(v1) i2 = t.cases.index(v2) if i1 == i2: continue if i1 < i2: return LT else: return GT elif isinstance(t, TBag) or isinstance(t, TSet): if deep: elems1 = list(v1) elems2 = list(v2) else: elems1 = list(sorted(v1)) elems2 = list(sorted(v2)) if len(elems1) < len(elems2): return LT if len(elems1) > len(elems2): return GT stk.extend( reversed([(t.elem_type, x, y, deep) for (x, y) in zip(elems1, elems2)])) elif isinstance(t, TMap): keys1 = Bag(v1.keys()) keys2 = Bag(v2.keys()) stk.extend( reversed([(t.v, v1[k], v2[k], deep) for k in sorted(keys1)])) stk.append((TSet(t.k), keys1, keys2, False)) stk.append((t.v, v1.default, v2.default, deep)) elif isinstance(t, TTuple): stk.extend( reversed([(tt, vv1, vv2, deep) for (tt, vv1, vv2) in zip(t.ts, v1, v2)])) elif isinstance(t, TList): stk.extend( reversed([(t.elem_type, vv1, vv2, deep) for (vv1, vv2) in zip(v1, v2)])) stk.append((INT, len(v1), len(v2), deep)) elif isinstance(t, TRecord): stk.extend( reversed([(ft, v1[f], v2[f], deep) for (f, ft) in t.fields])) else: if v1 == v2: continue elif v1 < v2: return LT else: return GT return EQ
def compare_values(t : Type, v1, v2, deep : bool = False) -> int: """Compare two Cozy values, returning LT, EQ, or GT. Parameters: t - the type of the values being compared v1 - the first value v2 - the second value deep - true to do a "deep" comparison that cares about iteration order on collections Because the LT, EQ, and GT constants are defined to be the integers -1, 0, and 1 respectively, this function can be used as an old-style comparator: values.sort(key=functools.cmp_to_key( lambda v1, v2: compare_values(t, v1, v2))) Notes regarding `deep`: There are two definitions of equality between Cozy values: == (aka "double-equals" or "normal equality") === (aka "triple-equals" or "deep equality") Normal equality is what a user means when they write "x == y" in an input specification: pointers are compared by address and two sets are the same if they have all the same elements. Deep equality is more useful internally: "x === y" means that every observable property of x is the same for y. In other words, an expression that uses x can be replaced by the same expression using y instead, and the output will always be the same. This is NOT true of double-equals expressions. A good example of this is the operator UOp.The, which gets the first element of a collection. Even if (x==y) for sets x and y, it can be true that (the x != the y), if the elements of x and y happen to be in different orders. In Python code, the == operator implements deep equality between Cozy values. This is very important so that Cozy values can be keys in maps, and two Cozy values that are not perfectly 100% the same do not overlap. The call `compare_values(type, v1, v2) == EQ` checks for normal equality. The call `compare_values(type, v1, v2, deep=True) == EQ` produces the same results as `v1 == v2`. However, performance might be slightly better if v1 and v2 are large collections. """ # For performance, this function uses a work-stack algorithm rather than # recursive calls to itself. The stack holds (type, value1, value2, deep) # tuples to compare. If the values on the head of the stack differ, then # this function can abort immediately with LT or GT. Otherwise it # continues to the next stack item. stk = [(t, v1, v2, deep)] while stk: # The code in this loop looks backwards since the "top" of the stack # is the last elment in the list. Thus, the next item to be compared # needs to be pushed LAST. As a result, we append sequences to the # stack in reversed order and we append more-important elements later. (t, v1, v2, deep) = stk.pop() h = extension_handler(type(t)) if h is not None: stk.append((h.encoding_type(t), v1, v2, deep)) continue if isinstance(t, THandle): if deep: stk.append((t.value_type, v1.value, v2.value, deep)) stk.append((INT, v1.address, v2.address, deep)) elif isinstance(t, TEnum): i1 = t.cases.index(v1) i2 = t.cases.index(v2) if i1 == i2: continue if i1 < i2: return LT else: return GT elif isinstance(t, TBag) or isinstance(t, TSet): if deep: elems1 = list(v1) elems2 = list(v2) else: elems1 = list(sorted(v1)) elems2 = list(sorted(v2)) if len(elems1) < len(elems2): return LT if len(elems1) > len(elems2): return GT stk.extend(reversed([(t.elem_type, x, y, deep) for (x, y) in zip(elems1, elems2)])) elif isinstance(t, TMap): keys1 = Bag(v1.keys()) keys2 = Bag(v2.keys()) stk.extend(reversed([(t.v, v1[k], v2[k], deep) for k in sorted(keys1)])) stk.append((TSet(t.k), keys1, keys2, False)) stk.append((t.v, v1.default, v2.default, deep)) elif isinstance(t, TTuple): stk.extend(reversed([(tt, vv1, vv2, deep) for (tt, vv1, vv2) in zip(t.ts, v1, v2)])) elif isinstance(t, TList): stk.extend(reversed([(t.elem_type, vv1, vv2, deep) for (vv1, vv2) in zip(v1, v2)])) stk.append((INT, len(v1), len(v2), deep)) elif isinstance(t, TRecord): stk.extend(reversed([(ft, v1[f], v2[f], deep) for (f, ft) in t.fields])) else: if v1 == v2: continue elif v1 < v2: return LT else: return GT return EQ
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 visit_Type(self, t, name): h = extension_handler(type(t)) if h is not None: return self.visit(h.rep_type(t), name) raise NotImplementedError(t)
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 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