Ejemplo n.º 1
0
def should_consider_replacement(target: Exp, target_context: Context,
                                subexp: Exp, subexp_context: Context,
                                subexp_pool: Pool, subexp_fp: Fingerprint,
                                replacement: Exp,
                                replacement_fp: Fingerprint) -> bool:
    """Heuristic that controls "blind" replacements.

    Besides replacing subexpressions with improved versions, Cozy also attempts
    "blind" replacements where the subexpression and the replacement do not
    behave exactly the same.  In some cases this can actually make a huge
    difference, for instance by replacing a collection with a singleton.

    However, not all blind replacements are worth trying.  This function
    controls which ones Cozy actually attempts.

    Preconditions:
     - subexp and replacement are both legal in (subexp_context, subexp_pool)
     - subexp and replacement have the same type
    """

    if not is_collection(subexp.type):
        return No("only collections matter")

    if not replacement_fp.subset_of(subexp_fp):
        return No("not a subset")

    return True
Ejemplo n.º 2
0
 def check_wf(e, ctx, pool):
     with task("checking well-formedness", size=e.size()):
         try:
             exp_wf(e,
                    pool=pool,
                    context=ctx,
                    assumptions=self.assumptions,
                    solver=self.wf_solver)
         except ExpIsNotWf as exc:
             return No("at {}: {}".format(
                 pprint(exc.offending_subexpression), exc.reason))
         for (sub, sub_ctx, sub_pool) in shred(e, ctx, pool):
             res = good_idea(self.wf_solver,
                             sub,
                             sub_ctx,
                             sub_pool,
                             assumptions=self.assumptions)
             if not res:
                 return res
         if pool == RUNTIME_POOL and self.cost_model.compare(
                 e, self.targets[0], ctx, pool) == Order.GT:
             # from cozy.cost_model import debug_comparison
             # debug_comparison(self.cost_model, e, self.target, ctx)
             return No("too expensive")
         # if isinstance(e.type, TBag):
         #     c = self.cost_model.cardinality(e)
         #     if all(cc < c for cc in cards):
         #         # print("too big: {}".format(pprint(e)))
         #         return No("too big")
         return True
Ejemplo n.º 3
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
Ejemplo n.º 4
0
 def possibly_useful(self,
         e           : Exp,
         context,
         pool        : Pool,
         assumptions : Exp,
         ops         : [Op],
         solver) -> bool:
     if isinstance(e, EHeapPeek) or isinstance(e, EHeapPeek2):
         if pool != RUNTIME_POOL:
             return No("heap peek in state position")
     return True
Ejemplo n.º 5
0
 def check_wf(e, ctx, pool):
     with task("pruning", size=e.size()):
         is_wf = exp_wf(e, pool=pool, context=ctx, solver=wf_solver)
         if not is_wf:
             return is_wf
         res = possibly_useful(wf_solver, e, ctx, pool, ops=ops)
         if not res:
             return res
         if cost_pruning.value and pool == RUNTIME_POOL and cost_model.compare(
                 e, targets[0], ctx, pool) == Order.GT:
             return No("too expensive")
         return True
Ejemplo n.º 6
0
 def check_wf(e, ctx, pool):
     with task("checking well-formedness", size=e.size()):
         is_wf = exp_wf(e,
                        pool=pool,
                        context=ctx,
                        solver=self.wf_solver)
         if not is_wf:
             return is_wf
         res = good_idea_recursive(self.wf_solver,
                                   e,
                                   ctx,
                                   pool,
                                   ops=self.ops)
         if not res:
             return res
         if pool == RUNTIME_POOL and self.cost_model.compare(
                 e, self.targets[0], ctx, pool) == Order.GT:
             return No("too expensive")
         return True
Ejemplo n.º 7
0
 def possibly_useful(self, e: Exp, context, pool: Pool, assumptions: Exp,
                     ops: [Op], solver) -> bool:
     if isinstance(e, ETreeMultisetPeek):
         if pool != RUNTIME_POOL:
             return No("ordered peek in state position")
     return True
Ejemplo n.º 8
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
Ejemplo n.º 9
0
def good_idea(solver,
              e: Exp,
              context: Context,
              pool=RUNTIME_POOL,
              assumptions: Exp = T) -> 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

    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.t):
        return No("collection of nonscalar")
    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 at_runtime and isinstance(e, ENum) and e.val != 0 and e.type == INT:
    #     return No("nonzero integer constant in state position")
    if 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 in state position")
    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 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.p.arg, ENot(
                                e.p.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).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
            # return No("non-polynomial-sized map ({}); total_size={}, this_size={}".format(model, eval(total_size, model), eval(my_size, model)))
            return No("non-polynomial-sized map")

    return True