Esempio n. 1
0
 def __init__(self, name, type_params, params, const_params, body,
              local_decls):
     super(P4Control, self).__init__(name, params, body)
     self.local_decls = local_decls
     self.type_params = type_params
     self.const_params = const_params
     self.merged_consts = OrderedDict()
     self.locals["apply"] = self.apply
     self.type_context = {}
Esempio n. 2
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)
Esempio n. 3
0
def save_variables(p4_state, merged_args):
    var_buffer = OrderedDict()
    # save all the variables that may be overridden
    for param_name, arg in merged_args.items():
        try:
            param_val = p4_state.resolve_reference(param_name)
            var_buffer[param_name] = (arg.mode, arg.p4_val, param_val)
        except RuntimeError:
            # if the variable name does not exist, set the value to None
            var_buffer[param_name] = (arg.mode, arg.p4_val, None)
    return var_buffer
Esempio n. 4
0
    def __init__(self, name, **properties):
        super(P4Table, self).__init__(name, params={})
        self.keys = []
        self.const_entries = []
        self.actions = OrderedDict()
        self.default_action = None
        self.tbl_action = z3.Int(f"{self.name}_action")
        self.implementation = None
        self.locals["hit"] = z3.BoolVal(False)
        self.locals["miss"] = z3.BoolVal(True)
        self.locals["action_run"] = self
        self.locals["apply"] = self.apply

        # some custom logic to resolve properties
        self.add_keys(properties)
        self.add_default(properties)
        self.add_actions(properties)
        self.add_const_entries(properties)
        # set the rest
        self.properties = properties
Esempio n. 5
0
 def __init__(self, states):
     self.states = {}
     for state in states:
         self.states[state.name] = state
     self.states["accept"] = AcceptState()
     self.states["reject"] = RejectState()
     self.nodes = OrderedDict()
     self.max_loop = 1
     self.terminal_nodes = {}
     visited_states = set()
     self.start_node = self.get_parser_dag(visited_states,
                                           self.states["start"])
Esempio n. 6
0
    def __init__(self, name, **properties):
        super(P4Table, self).__init__(name, None, {})
        self.keys = []
        self.const_entries = []
        self.actions = OrderedDict()
        self.default_action = None
        self.tbl_action = z3.Int(f"{self.name}_action")
        self.p4_attrs["hit"] = z3.BoolVal(False)
        self.p4_attrs["miss"] = z3.BoolVal(True)
        self.p4_attrs["action_run"] = self
        self.p4_attrs["apply"] = self.apply

        self.add_keys(properties)
        self.add_default(properties)
        self.add_actions(properties)
        self.add_const_entries(properties)
Esempio n. 7
0
class P4Control(P4Callable):
    def __init__(self, name, z3_reg, params, const_params, body, local_decls):
        super(P4Control, self).__init__(name, z3_reg, params, body)
        self.locals = local_decls
        self.const_params = const_params
        self.merged_consts = OrderedDict()
        self.p4_attrs["apply"] = self.apply

    def init_type_params(self, *args, **kwargs):
        for idx, c_param in enumerate(self.const_params):
            c_param.p4_type = args[idx]
        return self

    def initialize(self, *args, **kwargs):
        self.merged_consts = merge_parameters(self.const_params, *args,
                                              **kwargs)
        return self

    def __call__(self, *args, **kwargs):
        return self.initialize(*args, **kwargs)

    def apply(self, p4_state, *args, **kwargs):
        return self.eval(p4_state, *args, **kwargs)

    def eval_callable(self, p4_state, merged_args, var_buffer):
        # initialize the local context of the function for execution
        p4_context = P4Context(var_buffer)

        merged_vars = save_variables(p4_state, self.merged_consts)
        p4_context.prepend_to_buffer(merged_vars)

        self.set_context(p4_state, merged_args, ("out"))

        for const_param_name, const_arg in self.merged_consts.items():
            const_val = const_arg.p4_val
            const_val = p4_state.resolve_expr(const_val)
            p4_state.set_or_add_var(const_param_name, const_val)
        # execute the action expression with the new environment
        p4_state.insert_exprs(p4_context)
        p4_state.insert_exprs(self.statements)
        p4_state.insert_exprs(self.locals)
        p4z3_expr = p4_state.pop_next_expr()
        return p4z3_expr.eval(p4_state)
Esempio n. 8
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
Esempio n. 9
0
class P4Table(P4Callable):
    def __init__(self, name, **properties):
        super(P4Table, self).__init__(name, params={})
        self.keys = []
        self.const_entries = []
        self.actions = OrderedDict()
        self.default_action = None
        self.tbl_action = z3.Int(f"{self.name}_action")
        self.implementation = None
        self.locals["hit"] = z3.BoolVal(False)
        self.locals["miss"] = z3.BoolVal(True)
        self.locals["action_run"] = self
        self.locals["apply"] = self.apply

        # some custom logic to resolve properties
        self.add_keys(properties)
        self.add_default(properties)
        self.add_actions(properties)
        self.add_const_entries(properties)
        # set the rest
        self.properties = properties

    def add_actions(self, properties):
        if "actions" not in properties:
            return
        for action_expr in properties["actions"]:
            action_name, action_args = resolve_action(action_expr)
            index = len(self.actions) + 1
            self.actions[action_name] = (index, action_name, action_args)

    def add_default(self, properties):
        if "default_action" not in properties:
            # In case there is no default action, the first action is default
            self.default_action = (0, "NoAction", ())
        else:
            def_action = properties["default_action"]
            action_name, action_args = resolve_action(def_action)
            self.default_action = (0, action_name, action_args)

    def add_keys(self, properties):
        if "key" not in properties:
            return
        for table_key in properties["key"]:
            self.keys.append(table_key)

    def add_const_entries(self, properties):
        if "entries" not in properties:
            return
        for entry in properties["entries"]:
            const_keys = entry[0]
            action_expr = entry[1]
            if len(self.keys) != len(const_keys):
                raise RuntimeError("Constant keys must match table keys!")
            action_name, action_args = resolve_action(action_expr)
            self.const_entries.append((const_keys, (action_name, action_args)))

    def apply(self, p4_state):
        self.eval(p4_state)
        return self

    def eval_keys(self, p4_state):
        key_pairs = []
        if not self.keys:
            # there is nothing to match with...
            return z3.BoolVal(False)
        for index, (key_expr, key_type) in enumerate(self.keys):
            key_eval = p4_state.resolve_expr(key_expr)
            key_sort = key_eval.sort()
            key_match = z3.Const(f"{self.name}_table_key_{index}", key_sort)
            if key_type == "exact":
                # Just a simple comparison, nothing special
                key_pairs.append(key_eval == key_match)
            elif key_type == "lpm":
                # I think this can be arbitrarily large...
                # If the shift exceeds the bit width, everything will be zero
                # but that does not matter
                # TODO: Test this?
                mask_var = z3.BitVec(f"{self.name}_table_mask_{index}",
                                     key_sort)
                lpm_mask = z3.BitVecVal(2**key_sort.size() - 1,
                                        key_sort) << mask_var
                match = (key_eval & lpm_mask) == (key_match & lpm_mask)
                key_pairs.append(match)
            elif key_type == "ternary":
                # Just apply a symbolic mask, any zero bit is a wildcard
                # TODO: Test this?
                mask = z3.Const(f"{self.name}_table_mask_{index}", key_sort)
                # this is dumb...
                if isinstance(key_sort, z3.BoolSortRef):
                    match = z3.And(key_eval, mask) == z3.And(key_match, mask)
                else:
                    match = (key_eval & mask) == (key_match & mask)
                key_pairs.append(match)
            elif key_type == "range":
                # Pick an arbitrary minimum and maximum within the bit range
                # the minimum must be strictly lesser than the max
                # I do not think a match is needed?
                # TODO: Test this?
                min_key = z3.Const(f"{self.name}_table_min_{index}", key_sort)
                max_key = z3.Const(f"{self.name}_table_max_{index}", key_sort)
                match = z3.And(z3.ULE(min_key, key_eval),
                               z3.UGE(max_key, key_eval))
                key_pairs.append(z3.And(match, z3.ULT(min_key, max_key)))
            elif key_type == "optional":
                # As far as I understand this is just a wildcard for control
                # plane purposes. Semantically, there is no point?
                # TODO: Test this?
                key_pairs.append(z3.BoolVal(True))
            elif key_type == "selector":
                # Selectors are a deep rabbit hole
                # This rabbit hole does not yet make sense to me
                # FIXME: Implement
                # will intentionally fail if no implementation is present
                # impl = self.properties["implementation"]
                # impl_extern = self.p4_state.resolve_reference(impl)
                key_pairs.append(z3.BoolVal(True))
            else:
                # weird key, might be some specific specification
                raise RuntimeError(f"Key type {key_type} not supported!")
        return z3.And(key_pairs)

    def eval_action(self, p4_state, action_name, action_args):
        p4_action = p4_state.resolve_reference(action_name)
        if not isinstance(p4_action, P4Action):
            raise TypeError(f"Expected a P4Action got {type(p4_action)}!")
        merged_action_args = []
        action_args_len = len(action_args) - 1
        for idx, param in enumerate(p4_action.params):
            if idx > action_args_len:
                # this is a ctrl argument, generate an input
                ctrl_arg = gen_instance(p4_state, f"{self.name}{param.name}",
                                        param.p4_type)
                merged_action_args.append(ctrl_arg)
            else:
                merged_action_args.append(action_args[idx])
        return p4_action(p4_state, *merged_action_args)

    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)

    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)

    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)

    def eval_table(self, p4_state):
        action_exprs = []
        action_matches = []
        context = p4_state.current_context()
        forward_cond_copy = context.tmp_forward_cond

        # only bother to evaluate if the table can actually hit
        if not self.locals["hit"] == z3.BoolVal(False):
            # note: the action lists are pass by reference here
            # first evaluate all the constant entries
            self.eval_const_entries(p4_state, action_exprs, action_matches)
            # then append dynamic table entries to the constant entries
            self.eval_table_entries(p4_state, action_exprs, action_matches)
        # finally start evaluating the default entry
        var_store, contexts = p4_state.checkpoint()
        # this hits when the table is either missed, or no action matches
        cond = z3.Or(self.locals["miss"], z3.Not(z3.Or(*action_matches)))
        context.tmp_forward_cond = z3.And(forward_cond_copy, cond)
        self.eval_default(p4_state)
        if p4_state.has_exited:
            p4_state.restore(var_store, contexts)
        p4_state.has_exited = False
        context.tmp_forward_cond = forward_cond_copy
        # generate a nested set of if expressions per available action
        for cond, then_vars in action_exprs:
            merge_attrs(p4_state, cond, then_vars)

    def eval_callable(self, p4_state, merged_args, var_buffer):
        # tables are a little bit special since they also have attributes
        # so what we do here is first initialize the key
        hit = self.eval_keys(p4_state)
        self.locals["hit"] = z3.simplify(hit)
        self.locals["miss"] = z3.Not(hit)
        # then execute the table as the next expression in the chain
        self.eval_table(p4_state)
Esempio n. 10
0
class P4Control(P4Callable):
    def __init__(self, name, type_params, params, const_params, body,
                 local_decls):
        super(P4Control, self).__init__(name, params, body)
        self.local_decls = local_decls
        self.type_params = type_params
        self.const_params = const_params
        self.merged_consts = OrderedDict()
        self.locals["apply"] = self.apply
        self.type_context = {}

    def bind_to_ctrl_type(self, context, ctrl_type):
        # TODO Figure out what to actually do here
        # FIXME: A hack to deal with lack of input params
        if len(ctrl_type.params) < len(self.type_params):
            return self
        init_ctrl = copy.copy(self)
        # the type params sometimes include the return type also
        # it is typically the first value, but is bound somewhere else
        for idx, t_param in enumerate(init_ctrl.type_params):
            sub_type = resolve_type(context, ctrl_type.params[idx].p4_type)
            init_ctrl.type_context[t_param] = sub_type
            for param_idx, param in enumerate(init_ctrl.params):
                if isinstance(param.p4_type, str) and param.p4_type == t_param:
                    init_ctrl.params[param_idx] = ctrl_type.params[idx]
        return init_ctrl

    def init_type_params(self, context, *args, **kwargs):
        # TODO Figure out what to actually do here
        init_ctrl = copy.copy(self)
        # the type params sometimes include the return type also
        # it is typically the first value, but is bound somewhere else
        for idx, t_param in enumerate(init_ctrl.type_params):
            arg = resolve_type(context, args[idx])
            init_ctrl.type_context[t_param] = arg
        return init_ctrl

    def initialize(self, context, *args, **kwargs):
        ctrl_copy = copy.copy(self)
        ctrl_copy.merged_consts = merge_parameters(ctrl_copy.const_params,
                                                   *args, **kwargs)
        # also bind types, because for reasons you can bind types everywhere...
        for idx, const_param in enumerate(ctrl_copy.const_params):
            # this means the type is generic
            p4_type = resolve_type(context, const_param.p4_type)
            if p4_type is None:
                # grab the type of the input arguments
                ctrl_copy.type_context[const_param.p4_type] = args[idx].sort()
        return ctrl_copy

    def apply(self, p4_state, *args, **kwargs):
        local_context = {}
        for type_name, p4_type in self.type_context.items():
            local_context[type_name] = resolve_type(p4_state, p4_type)
        p4_state.type_contexts.append(self.type_context)
        self.eval(p4_state, *args, **kwargs)
        p4_state.type_contexts.pop()

    def eval_callable(self, p4_state, merged_args, var_buffer):
        # initialize the local context of the function for execution
        context = p4_state.current_context()

        merged_vars = save_variables(p4_state, self.merged_consts)
        context.prepend_to_buffer(merged_vars)

        for const_param_name, const_arg in self.merged_consts.items():
            const_val = const_arg.p4_val
            const_val = p4_state.resolve_expr(const_val)
            p4_state.set_or_add_var(const_param_name, const_val)
        for expr in self.local_decls:
            expr.eval(p4_state)
            if p4_state.has_exited or context.has_returned:
                break
        self.statements.eval(p4_state)

    def __copy__(self):
        cls = self.__class__
        result = cls.__new__(cls)
        result.__dict__.update(self.__dict__)
        result.type_params = copy.copy(self.type_params)
        result.params = []
        for param in self.params:
            result.params.append(copy.copy(param))
        result.const_params = []
        for param in self.const_params:
            result.const_params.append(copy.copy(param))
        result.locals = {}
        result.locals["apply"] = result.apply
        return result
Esempio n. 11
0
 def __init__(self, z3_reg, name, params, type_params):
     super(P4Package, self).__init__(name, params)
     self.pipes = OrderedDict()
     self.z3_reg = z3_reg
     self.type_params = type_params
     self.type_context = {}
Esempio n. 12
0
 def __init__(self, name, z3_reg, params, const_params, body, local_decls):
     super(P4Control, self).__init__(name, z3_reg, params, body)
     self.locals = local_decls
     self.const_params = const_params
     self.merged_consts = OrderedDict()
     self.p4_attrs["apply"] = self.apply
Esempio n. 13
0
 def __init__(self, z3_reg, name, params):
     self.pipes = OrderedDict()
     self.params = params
     self.name = name
     self.z3_reg = z3_reg