Exemple #1
0
 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)
Exemple #2
0
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)
Exemple #3
0
    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()
Exemple #4
0
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)
Exemple #5
0
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
Exemple #6
0
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")
Exemple #7
0
 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)
Exemple #8
0
 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
Exemple #9
0
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
Exemple #11
0
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
Exemple #12
0
 def visit_Exp(self, e):
     h = extension_handler(type(e))
     if h is not None:
         h.typecheck(e, self.visit, self.report)
Exemple #13
0
 def visit_Exp(self, e):
     h = extension_handler(type(e))
     if h is not None:
         h.typecheck(e, self.visit, self.report)
Exemple #14
0
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))
Exemple #15
0
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)
Exemple #16
0
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")
Exemple #17
0
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
Exemple #18
0
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
Exemple #19
0
 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)
Exemple #20
0
 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)
Exemple #21
0
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
Exemple #22
0
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
Exemple #23
0
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
Exemple #24
0
 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)
Exemple #25
0
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)
Exemple #26
0
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