def __call__(self, p4_state, *args, **kwargs): merged_args = merge_parameters(self.params, *args, **kwargs) # resolve the inputs *before* we bind types method_args = {} for param_name, arg in merged_args.items(): # we need to resolve "in" too because of side-effects if arg.p4_val is None: # sometimes we get optional parameters # FIXME: I do not actually know how to resolve them right now arg_expr = gen_instance(p4_state, "optional", arg.p4_type) else: arg_expr = p4_state.resolve_expr(arg.p4_val) method_args[param_name] = (arg.mode, arg.p4_val, arg_expr, arg.p4_type) # apply the local and parent extern type contexts local_context = {} for type_name, p4_type in self.extern_context.items(): local_context[type_name] = resolve_type(p4_state, p4_type) 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(local_context) # assign symbolic values to the inputs that are inout and out self.assign_values(p4_state, method_args) # execute the return expression within the new type environment expr = self.eval_callable(p4_state, merged_args, {}) # cleanup and return the value p4_state.type_contexts.pop() self.call_counter += 1 return expr
def initialize(self, context, *args, **kwargs): merged_args = merge_parameters(self.params, *args, **kwargs) for pipe_name, pipe_arg in merged_args.items(): if pipe_arg.p4_val is None: # for some reason, the argument is uninitialized. # FIXME: This should not happen. Why? continue log.info("Loading %s pipe...", pipe_name) pipe_val = context.resolve_expr(pipe_arg.p4_val) if isinstance(pipe_val, P4Control): # This boilerplate is all necessary to initialize state... # FIXME: Ideally, this should be handled by the control... context.type_contexts.append(self.type_context) ctrl_type = resolve_type(context, pipe_arg.p4_type) pipe_val = pipe_val.bind_to_ctrl_type(context, ctrl_type) context.type_contexts.append(ctrl_type.type_context) args = [] for idx, param in enumerate(pipe_val.params): ctrl_type_param_type = ctrl_type.params[idx].p4_type generic_type = resolve_type(context, ctrl_type_param_type) if generic_type is None: param_type = resolve_type(context, param.p4_type) self.type_context[ctrl_type_param_type] = param_type args.append(param.name) # create the z3 representation of this control state p4_state = self.z3_reg.set_p4_state(pipe_name, pipe_val.params) # dp not need the types for now context.type_contexts.pop() context.type_contexts.pop() # initialize the call with its own params we collected # this is essentially the input packet pipe_val.apply(p4_state, *args) # after executing the pipeline get its z3 representation state = p4_state.get_z3_repr() # and also merge back all the exit states we collected for exit_cond, exit_state in reversed(p4_state.exit_states): state = z3.If(exit_cond, exit_state, state) # all done, that is our P4 representation! self.pipes[pipe_name] = (state, p4_state.members, pipe_val) elif isinstance(pipe_val, P4Extern): var = z3.Const(f"{pipe_name}{pipe_val.name}", pipe_val.z3_type) self.pipes[pipe_name] = (var, [], pipe_val) elif isinstance(pipe_val, P4Package): # execute the package by calling its initializer pipe_val.initialize(context) # resolve all the sub_pipes for sub_pipe_name, sub_pipe_val in pipe_val.pipes.items(): sub_pipe_name = f"{pipe_name}_{sub_pipe_name}" self.pipes[sub_pipe_name] = sub_pipe_val elif isinstance(pipe_val, z3.ExprRef): # for some reason simple expressions are also possible. self.pipes[pipe_name] = (pipe_val, [], pipe_val) else: raise RuntimeError( f"Unsupported value {pipe_val}, type {type(pipe_val)}." " It does not make sense as a P4 pipeline.") return self
def assign_values(self, p4_state, method_args): for param_name, arg in method_args.items(): arg_mode, arg_ref, arg_expr, p4_src_type = arg # infer the type p4_type = resolve_type(p4_state, p4_src_type) # This is dynamic type inference based on arguments # FIXME Check this hack. if p4_type is None: if isinstance(arg_expr, list): # synthesize a list type from the input list # this mostly just a dummy # FIXME: Need to get the actual sub-types p4_type = ListType("tuple", p4_state, arg_expr) else: p4_type = arg_expr.sort() p4_state.type_contexts[-1][p4_src_type] = p4_type if arg_mode not in ("out", "inout"): # this value is read-only so we do not care # however, we still used it to potentially infer a type continue arg_name = f"{self.name}_{param_name}" arg_expr = gen_instance(p4_state, arg_name, p4_type) if isinstance(arg_expr, StructInstance): # # In the case that the instance is a complex type make sure # # to propagate the variable through all its members # bind_const = z3.Const(arg_name, arg_expr.z3_type) # arg_expr.bind(bind_const) # we do not know whether the expression is valid afterwards propagate_validity_bit(arg_expr) # (in)outs are left-values so the arg_ref must be a string p4_state.set_or_add_var(arg_ref, arg_expr)
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 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 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) # disable unrolling for now, we do not really need it for validation # and with it, tests take unpleasantly long # self.statements.max_loop = self.compute_loop_bound(p4_state) self.eval(p4_state, *args, **kwargs) p4_state.type_contexts.pop()
def compute_loop_bound(self, p4_state): sizes = [] for param in self.params: p4_type = resolve_type(p4_state, param.p4_type) self.collect_stack_sizes(p4_type, sizes) if sizes: max_size = max(sizes) else: max_size = 1 return max_size
def eval(self, p4_state): lval_expr = p4_state.resolve_expr(self.lval) rval_expr = resolve_type(p4_state, self.rval) # it can happen that we cast to a complex type... if isinstance(rval_expr, P4ComplexType): # we produce an initializer that takes care of the details initializer = P4Initializer(lval_expr, rval_expr) return initializer.eval(p4_state) return self.operator(lval_expr, rval_expr)
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 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 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
def init_type_params(self, context, *args, **kwargs): init_method = copy.copy(self) for idx, t_param in enumerate(init_method.type_params): arg = resolve_type(context, args[idx]) init_method.type_context[t_param] = arg return init_method
def eval(self, context): p4_method = resolve_type(context, self.p4_method) return p4_method.initialize(context, *self.args, **self.kwargs)