Ejemplo n.º 1
0
    def set_or_add_var(self, lval, rval, new_decl=False):
        if isinstance(lval, P4Slice):
            self.set_slice(self, lval, rval)
            return
        if isinstance(lval, P4Member):
            lval.set_value(self, rval)
            return
        # now that all the preprocessing is done we can assign the value
        log.debug("Setting %s(%s) to %s(%s) ", lval, type(lval), rval,
                  type(rval))
        ctx, lval_val = self.find_context(lval)
        if new_decl or not ctx:
            ctx = self

        # rvals could be a list, unroll the assignment
        if isinstance(rval, list) and lval_val is not None:
            if isinstance(lval_val, StructInstance):
                lval_val.set_list(rval)
            elif isinstance(lval, str):
                # TODO: Decide whether this makes sense
                log.warning("Skipping assignment %s to %s", rval, lval)
            else:
                raise TypeError(f"set_list {type(lval)} not supported!")
            return
        ctx.locals[lval] = rval
Ejemplo n.º 2
0
    def resolve_expr(self, expr):
        # Resolves to z3 and z3p4 expressions
        # ints and lists  are also okay

        # resolve potential string references first
        log.debug("Resolving %s", expr)
        if isinstance(expr, (str, P4Member)):
            expr = self.resolve_reference(expr)
        if isinstance(expr, P4Expression):
            # We got a P4 expression, recurse and resolve...
            expr = expr.eval(self)
            return self.resolve_expr(expr)
        if isinstance(expr, (z3.AstRef, int)):
            # These are z3 types and can be returned
            # Unfortunately int is part of it because z3 is very inconsistent
            # about var handling...
            return expr
        if isinstance(
                expr,
            (StaticType, P4ComplexInstance, P4Z3Class, types.MethodType)):
            # In a similar manner, just return any remaining class types
            # Methods can be class attributes and need to be returned as is
            return expr
        if isinstance(expr, list):
            # For lists, resolve each value individually and return a new list
            list_expr = []
            for val_expr in expr:
                rval_expr = self.resolve_expr(val_expr)
                list_expr.append(rval_expr)
            return list_expr
        raise TypeError(f"Expression of type {type(expr)} cannot be resolved!")
Ejemplo n.º 3
0
 def eval(self, p4_state):
     log.debug("Assigning %s to %s ", self.rval, self.lval)
     rval_expr = p4_state.resolve_expr(self.rval)
     # in assignments all complex types values are copied
     if isinstance(rval_expr, StructInstance):
         rval_expr = copy.copy(rval_expr)
     if isinstance(rval_expr, int):
         lval = p4_state.resolve_expr(self.lval)
         rval_expr = z3_cast(rval_expr, lval.sort())
     p4_state.set_or_add_var(self.lval, rval_expr)
Ejemplo n.º 4
0
    def eval_table(self, p4_state):
        actions = self.actions
        const_entries = self.const_entries
        action_exprs = []
        # first evaluate all the constant entries
        for const_keys, action in reversed(const_entries):
            action_name = action[0]
            p4_action_args = action[1]
            matches = []
            # match the constant keys with the normal table keys
            # this generates the match expression for a specific constant entry
            for index, key in enumerate(self.keys):
                key_eval = p4_state.resolve_expr(key)
                const_key = const_keys[index]
                # default implies don't care, do not add
                # TODO: Verify that this assumption is right...
                if isinstance(const_key, DefaultExpression):
                    continue
                c_key_eval = p4_state.resolve_expr(const_keys[index])
                matches.append(key_eval == c_key_eval)
            action_match = z3.And(*matches)
            action_tuple = (action_name, p4_action_args)
            log.debug("Evaluating constant action %s...", action_name)
            # state forks here
            var_store, chain_copy = p4_state.checkpoint()
            action_expr = self.eval_action(p4_state, action_tuple)
            p4_state.restore(var_store, chain_copy)
            action_exprs.append((action_match, action_expr))

        # then append dynamic table entries to the constant entries
        for action in reversed(actions.values()):
            p4_action_id = action[0]
            action_name = action[1]
            p4_action_args = action[2]
            action_match = (self.tbl_action == z3.IntVal(p4_action_id))
            action_tuple = (action_name, p4_action_args)
            log.debug("Evaluating action %s...", action_name)
            # state forks here
            var_store, chain_copy = p4_state.checkpoint()
            action_expr = self.eval_action(p4_state, action_tuple)
            p4_state.restore(var_store, chain_copy)
            action_exprs.append((action_match, action_expr))

        # finally evaluate the default entry
        table_expr = self.eval_default(p4_state)
        default_expr = table_expr
        # generate a nested set of if expressions per available action
        for cond, action_expr in action_exprs:
            table_expr = z3.If(cond, action_expr, table_expr)
        # if we hit return the table expr
        # otherwise just return the default expr
        return z3.If(self.p4_attrs["hit"], table_expr, default_expr)
Ejemplo n.º 5
0
    def copy_in(self, ctx, merged_args):
        param_buffer = OrderedDict()
        var_buffer = OrderedDict()
        for param_name, arg in merged_args.items():
            # We need to resolve array indices appropriately
            arg_expr, _ = resolve_index(ctx, arg.p4_val)
            try:
                param_val = ctx.resolve_reference(param_name)
                var_buffer[param_name] = (arg.mode, arg_expr, param_val)
            except RuntimeError:
                # if the variable name does not exist, set the value to None
                var_buffer[param_name] = (arg.mode, arg_expr, None)
            # Sometimes expressions are passed, resolve those first
            arg_expr = ctx.resolve_expr(arg_expr)
            # it can happen that we receive a list
            # infer the type, generate, and set
            try:
                p4_type = ctx.resolve_type(arg.p4_type)
            except KeyError:
                # this is a generic type, we need to bind for this scope
                # FIXME: Clean this up
                p4_type = arg_expr.sort()
                ctx.add_type(arg.p4_type, p4_type)
            if isinstance(arg_expr, list):
                # if the type is undefined, do nothing
                if isinstance(p4_type, P4ComplexType):
                    arg_instance = ctx.gen_instance(UNDEF_LABEL, p4_type)
                    arg_instance.set_list(arg_expr)
                    arg_expr = arg_instance
            # it is possible to pass an int as value, we need to cast it
            elif isinstance(arg_expr, int):
                arg_expr = z3_cast(arg_expr, p4_type)
            # need to work with an independent copy
            # the purpose is to handle indirect assignments in an action
            if arg.mode in ("in", "inout") and isinstance(
                    arg_expr, StructInstance):
                arg_expr = copy.copy(arg_expr)
            if arg.mode == "out":
                # outs are left-values so the arg must be a string
                # infer the type value at runtime, param does not work yet
                # outs reset the input
                # In the case that the instance is a complex type make sure
                # to propagate the variable through all its members
                log.debug("Resetting %s to %s", arg_expr, param_name)
                arg_expr = ctx.gen_instance(UNDEF_LABEL, p4_type)

            log.debug("Copy-in: %s to %s", arg_expr, param_name)
            # buffer the value, do NOT set it yet
            param_buffer[param_name] = arg_expr
        return param_buffer, var_buffer
Ejemplo n.º 6
0
 def eval_table_entries(self, ctx, action_exprs, action_matches):
     forward_cond_copy = ctx.tmp_forward_cond
     for act_id, act_name, act_args in reversed(self.actions.values()):
         action_match = (self.tbl_action == z3.IntVal(act_id))
         log.debug("Evaluating action %s...", act_name)
         # state forks here
         var_store = ctx.checkpoint()
         cond = z3.And(self.locals["hit"], action_match)
         ctx.tmp_forward_cond = z3.And(forward_cond_copy, cond)
         self.eval_action(ctx, act_name, act_args, act_id)
         if not ctx.get_exited():
             action_exprs.append((cond, ctx.get_attrs()))
         ctx.set_exited(False)
         action_matches.append(action_match)
         ctx.restore(var_store)
Ejemplo n.º 7
0
 def eval(self, ctx):
     # An assignment, written with the = sign, first evaluates its left
     # sub-expression to an l-value, then evaluates its right sub-expression
     # to a value, and finally copies the value into the l-value. Derived
     # types (e.g. structs) are copied recursively, and all components of
     # headers are copied, including “validity” bits. Assignment is not
     # defined for extern values.
     log.debug("Assigning %s to %s ", self.rval, self.lval)
     lval, _ = resolve_index(ctx, self.lval)
     rval_expr = ctx.resolve_expr(self.rval)
     # in assignments all complex types values are copied
     if isinstance(rval_expr, StructInstance):
         rval_expr = copy.copy(rval_expr)
     if isinstance(rval_expr, int):
         rval_expr = z3_cast(rval_expr, ctx.resolve_expr(self.lval).sort())
     ctx.set_or_add_var(lval, rval_expr)
Ejemplo n.º 8
0
 def eval_table_entries(self, p4_state, action_exprs, action_matches):
     context = p4_state.current_context()
     forward_cond_copy = context.tmp_forward_cond
     for act_id, act_name, act_args in reversed(self.actions.values()):
         action_match = (self.tbl_action == z3.IntVal(act_id))
         log.debug("Evaluating action %s...", act_name)
         # state forks here
         var_store, contexts = p4_state.checkpoint()
         cond = z3.And(self.locals["hit"], action_match)
         context.tmp_forward_cond = z3.And(forward_cond_copy, cond)
         self.eval_action(p4_state, act_name, act_args)
         if not p4_state.has_exited:
             action_exprs.append((cond, p4_state.get_attrs()))
         p4_state.has_exited = False
         action_matches.append(action_match)
         p4_state.restore(var_store, contexts)
Ejemplo n.º 9
0
    def eval_const_entries(self, ctx, action_exprs, action_matches):
        forward_cond_copy = ctx.tmp_forward_cond
        for c_keys, (action_name, action_args) in reversed(self.const_entries):

            action_match = self.get_const_matches(ctx, c_keys)
            log.debug("Evaluating constant action %s...", action_name)
            # state forks here
            var_store = ctx.checkpoint()
            cond = z3.And(self.locals["hit"], action_match)
            ctx.tmp_forward_cond = z3.And(forward_cond_copy, cond)
            self.eval_action(ctx, action_name, action_args)
            if not ctx.get_exited():
                action_exprs.append((cond, ctx.get_attrs()))
            ctx.set_exited(False)
            action_matches.append(action_match)
            ctx.restore(var_store)
Ejemplo n.º 10
0
 def set_context(self, p4_state, merged_args, ref_criteria):
     # we have to subclass because of slight different behavior
     # inout and out parameters are not undefined they are set
     # with a new free constant
     param_buffer = OrderedDict()
     for param_name, arg in merged_args.items():
         # Sometimes expressions are passed, resolve those first
         arg_expr = p4_state.resolve_expr(arg.p4_val)
         # for now use the param name, not the arg_name constructed here
         # FIXME: there are some passes that rename causing issues
         arg_name = f"{self.name}_{param_name}"
         # it can happen that we receive a list
         # infer the type, generate, and set
         if isinstance(arg_expr, list):
             # if the type is undefined, do nothing
             if isinstance(arg.p4_type, P4ComplexType):
                 arg_instance = gen_instance(arg_name, arg.p4_type)
                 arg_instance.set_list(arg_expr)
                 arg_expr = arg_instance
         if arg.is_ref in ref_criteria:
             # outs are left-values so the arg must be a string
             # infer the type value at runtime, param does not work yet
             # outs reset the input
             # In the case that the instance is a complex type make sure
             # to propagate the variable through all its members
             log.debug("Resetting %s to %s", arg_expr, param_name)
             if isinstance(arg_expr, P4ComplexInstance):
                 # assume that for inout header validity is not touched
                 if arg.is_ref == "inout":
                     arg_expr.bind_to_name(arg_name)
                 else:
                     arg_expr = arg_expr.p4z3_type.instantiate(arg_name)
                 # we do not know whether the expression is valid afterwards
                 arg_expr.propagate_validity_bit()
             else:
                 arg_expr = z3.Const(f"{param_name}", arg_expr.sort())
         log.debug("Copy-in: %s to %s", arg_expr, param_name)
         # it is possible to pass an int as value, we need to cast it
         if isinstance(arg_expr, int) and isinstance(
                 arg.p4_type, (z3.BitVecSortRef, z3.BitVecRef)):
             arg_expr = z3_cast(arg_expr, arg.p4_type)
         # buffer the value, do NOT set it yet
         param_buffer[param_name] = arg_expr
     # now we can set the arguments without influencing subsequent variables
     for param_name, param_val in param_buffer.items():
         p4_state.set_or_add_var(param_name, param_val)
Ejemplo n.º 11
0
    def eval(self, p4_state):
        log.debug("Assigning %s to %s ", self.rval, self.lval)
        rval_expr = p4_state.resolve_expr(self.rval)
        # in assignments all complex types values are copied
        if isinstance(rval_expr, P4ComplexInstance):
            rval_expr = copy.copy(rval_expr)
        # make sure the assignment is aligned appropriately
        # this can happen because we also evaluate before the
        # BindTypeVariables pass
        # we can only align if tmp_val is a bitvector
        # example test: instance_overwrite.p4
        lval = p4_state.resolve_expr(self.lval)
        if isinstance(rval_expr, int) and isinstance(
                lval, (z3.BitVecSortRef, z3.BitVecRef)):
            rval_expr = z3_cast(rval_expr, lval.sort())
        p4_state.set_or_add_var(self.lval, rval_expr)

        p4z3_expr = p4_state.pop_next_expr()
        return p4z3_expr.eval(p4_state)
Ejemplo n.º 12
0
 def eval_const_entries(self, p4_state, action_exprs, action_matches):
     context = p4_state.current_context()
     forward_cond_copy = context.tmp_forward_cond
     for c_keys, (action_name, action_args) in reversed(self.const_entries):
         matches = []
         # match the constant keys with the normal table keys
         # this generates the match expression for a specific constant entry
         # this is a little inefficient, fix.
         # TODO: Figure out if key type matters here?
         for index, (key_expr, key_type) in enumerate(self.keys):
             c_key_expr = c_keys[index]
             # default implies don't care, do not add
             # TODO: Verify that this assumption is right...
             if isinstance(c_key_expr, DefaultExpression):
                 continue
             key_eval = p4_state.resolve_expr(key_expr)
             if isinstance(c_key_expr, P4Range):
                 x = c_key_expr.min
                 y = c_key_expr.max
                 c_key_eval = z3.And(z3.ULE(x, key_eval),
                                     z3.UGE(y, key_eval))
                 matches.append(c_key_eval)
             elif isinstance(c_key_expr, P4Mask):
                 val = p4_state.resolve_expr(c_key_expr.mask)
                 mask = c_key_expr.mask
                 c_key_eval = (val & mask) == (key_eval & mask)
                 matches.append(c_key_eval)
             else:
                 c_key_eval = p4_state.resolve_expr(c_key_expr)
                 matches.append(key_eval == c_key_eval)
         action_match = z3.And(*matches)
         log.debug("Evaluating constant action %s...", action_name)
         # state forks here
         var_store, contexts = p4_state.checkpoint()
         cond = z3.And(self.locals["hit"], action_match)
         context.tmp_forward_cond = z3.And(forward_cond_copy, cond)
         self.eval_action(p4_state, action_name, action_args)
         if not p4_state.has_exited:
             action_exprs.append((cond, p4_state.get_attrs()))
         p4_state.has_exited = False
         action_matches.append(action_match)
         p4_state.restore(var_store, contexts)
Ejemplo n.º 13
0
    def copy_out(self, ctx):
        # restore any variables that may have been overridden
        # with copy-out we copy from left to right
        # values on the right override values on the left
        # the var buffer is an ordered dict that maintains this order
        for par_name, (mode, par_ref, par_val) in self.var_buffer.items():
            # we retrieve the current value
            val = self.resolve_reference(par_name)

            # we then reset the name in the scope to its original
            log.debug("Resetting %s to %s", par_name, type(par_val))
            # value has not existed previously, ignore
            if par_val is not None:
                ctx.set_or_add_var(par_name, par_val)

            # if the param was copy-out, we copy the value we retrieved
            # back to the original input reference
            if mode in ("inout", "out"):
                log.debug("Copy-out: %s to %s", val, par_ref)
                # copy it back to the input reference
                # this assumes an lvalue as input
                ctx.set_or_add_var(par_ref, val)
Ejemplo n.º 14
0
    def copy_in(self, p4_state, merged_args):
        param_buffer = OrderedDict()
        for param_name, arg in merged_args.items():
            # Sometimes expressions are passed, resolve those first
            arg_expr = p4_state.resolve_expr(arg.p4_val)
            # for now use the param name, not the arg_name constructed here
            # FIXME: there are some passes that rename causing issues
            arg_name = "undefined"  # f"{self.name}_{param_name}"
            # it can happen that we receive a list
            # infer the type, generate, and set
            p4_type = resolve_type(p4_state, arg.p4_type)
            if isinstance(arg_expr, list):
                # if the type is undefined, do nothing
                if isinstance(p4_type, P4ComplexType):
                    arg_instance = gen_instance(p4_state, arg_name, p4_type)
                    arg_instance.set_list(arg_expr)
                    arg_expr = arg_instance
            # it is possible to pass an int as value, we need to cast it
            elif isinstance(arg_expr, int):
                arg_expr = z3_cast(arg_expr, p4_type)
            # need to work with an independent copy
            # the purpose is to handle indirect assignments in an action
            if arg.mode in ("in", "inout") and isinstance(
                    arg_expr, StructInstance):
                arg_expr = copy.copy(arg_expr)
            if arg.mode == "out":
                # outs are left-values so the arg must be a string
                # infer the type value at runtime, param does not work yet
                # outs reset the input
                # In the case that the instance is a complex type make sure
                # to propagate the variable through all its members
                log.debug("Resetting %s to %s", arg_expr, param_name)
                arg_expr = gen_instance(p4_state, arg_name, p4_type)

            log.debug("Copy-in: %s to %s", arg_expr, param_name)
            # buffer the value, do NOT set it yet
            param_buffer[param_name] = arg_expr
        return param_buffer
Ejemplo n.º 15
0
    def restore_context(self, p4_state):
        # FIXME: This does not respect local context
        # local variables are overridden in functions and controls
        # restore any variables that may have been overridden
        for param_name, param in self.var_buffer.items():
            is_ref = param[0]
            param_ref = param[1]
            param_val = param[2]
            if is_ref in ("inout", "out"):
                val = p4_state.resolve_reference(param_name)
            if param_val is None:
                # value has not existed previously, marked for deletion
                log.debug("Deleting %s", param_name)
                p4_state.del_var(param_name)
            else:
                log.debug("Resetting %s to %s", param_name, type(param_val))
                p4_state.set_or_add_var(param_name, param_val)

            if is_ref in ("inout", "out"):
                # with copy-out we copy from left to right
                # values on the right override values on the left
                # the var buffer is an ordered dict that maintains this order
                log.debug("Copy-out: %s to %s", val, param_val)
                p4_state.set_or_add_var(param_ref, val)
Ejemplo n.º 16
0
 def eval_default(self, ctx):
     act_idx, action_name, action_args = self.default_action
     log.debug("Evaluating default action...")
     return self.eval_action(ctx, action_name, action_args, act_idx)
Ejemplo n.º 17
0
 def eval_default(self, p4_state):
     _, action_name, action_args = self.default_action
     log.debug("Evaluating default action...")
     return self.eval_action(p4_state, action_name, action_args)