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 eval(self, p4_state): val = p4_state.resolve_expr(self.val) if self.instance_type is None: # no type defined, return just the value return val else: instance = gen_instance("None", self.instance_type) if isinstance(val, P4ComplexInstance): # copy the reference if we initialize with another complex type return copy.copy(val) if isinstance(instance, P4ComplexInstance): if isinstance(val, dict): instance.setValid() for name, val in val.items(): val_expr = p4_state.resolve_expr(val) instance.set_or_add_var(name, val_expr) elif isinstance(val, list): instance.set_list(val) else: raise RuntimeError( f"P4StructInitializer members {val} not supported!") return instance else: # cast the value we assign to the instance we create # TODO: I do not like this, there must be a better way to do this if isinstance(val, int) and isinstance( instance, (z3.BitVecSortRef, z3.BitVecRef)): val = z3_cast(val, instance.sort()) return val
def eval_callable(self, p4_state, merged_args, var_buffer): # initialize the local context of the function for execution if self.return_type is None: return None # methods can return values, we need to generate a new constant # we generate the name based on the input arguments # var_name = "" # for arg in merged_args.values(): # arg = p4_state.resolve_expr(arg.p4_val) # # fold runtime-known values # if isinstance(arg, z3.AstRef): # arg = z3.simplify(arg) # # elif isinstance(arg, list): # # for idx, member in enumerate(arg): # # arg[idx] = z3.simplify(member) # # Because we do not know what the extern is doing # # we initiate a new z3 const and # # just overwrite all reference types # # input arguments influence the output behavior # # add the input value to the return constant # var_name += str(arg) # If we return something, instantiate the type and return it # we merge the name # FIXME: We do not consider call order # and assume that externs are stateless return_instance = gen_instance(p4_state, self.name, self.return_type) # a returned header may or may not be valid if isinstance(return_instance, StructInstance): propagate_validity_bit(return_instance) return return_instance
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 eval_callable(self, p4_state, merged_args, var_buffer): # initialize the local context of the function for execution p4_context = P4Context(var_buffer) self.set_context(p4_state, merged_args, ("inout", "out")) p4_context.restore_context(p4_state) if self.return_type is not None: # methods can return values, we need to generate a new constant # we generate the name based on the input arguments var_name = "" for arg in merged_args.values(): arg = p4_state.resolve_expr(arg.p4_val) # Because we do not know what the extern is doing # we initiate a new z3 const and # just overwrite all reference types # input arguments influence the output behavior # add the input value to the return constant var_name += str(arg) # If we return something, instantiate the type and return it # we merge the name # FIXME: We do not consider call order # and assume that externs are stateless return_instance = gen_instance(f"{self.name}_{var_name}", self.return_type) if isinstance(return_instance, P4ComplexInstance): return_instance.propagate_validity_bit() return return_instance p4z3_expr = p4_state.pop_next_expr() return p4z3_expr.eval(p4_state)
def eval(self, p4_state): context = p4_state.current_context() if self.expr is None: expr = None else: # resolve the expr before restoring the state expr = p4_state.resolve_expr(self.expr) if isinstance(context.return_type, z3.BitVecSortRef): expr = z3_cast(expr, context.return_type) # we return a complex typed expression list, instantiate if isinstance(expr, list): instance = gen_instance(p4_state, "undefined", context.return_type) instance.set_list(expr) expr = instance cond = z3.simplify( z3.And(z3.Not(z3.Or(*context.forward_conds)), context.tmp_forward_cond)) if not cond == z3.BoolVal(False): context.return_states.append((cond, p4_state.copy_attrs())) if expr is not None: context.return_exprs.append((cond, expr)) context.has_returned = True context.forward_conds.append(context.tmp_forward_cond)
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 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 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)