def eval_keys(self, ctx): 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 = ctx.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.prog_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 extract_hdr(self, ctx, merged_args): hdr = merged_args[self.hdr_param_name].p4_val # apply the local and parent extern type ctxs for type_name, p4_type in self.extern_ctx.items(): ctx.add_type(type_name, ctx.resolve_type(p4_type)) for type_name, p4_type in self.type_ctx.items(): ctx.add_type(type_name, ctx.resolve_type(p4_type)) # advance the header index if a next field has been accessed hdr_stack = detect_hdr_stack_next(ctx, hdr) if hdr_stack: compare = hdr_stack.locals[ "nextIndex"] >= hdr_stack.locals["size"] if z3.simplify(compare) == z3.BoolVal(True): raise ParserException("Index out of bounds!") # grab the hdr value hdr_expr = ctx.resolve_expr(hdr) hdr_expr.activate() bind_const = z3.Const(f"{self.name}_{self.hdr_param_name}", hdr_expr.z3_type) hdr_expr.bind(bind_const) # advance the stack, if it exists if hdr_stack: hdr_stack.locals["lastIndex"] = hdr_stack.locals[ "nextIndex"] hdr_stack.locals["nextIndex"] += 1 self.call_counter += 1
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 eval_keys(self, p4_state): key_pairs = [] if not self.keys: # there is nothing to match with... return z3.BoolVal(False) for index, key in enumerate(self.keys): key_eval = p4_state.resolve_expr(key) key_sort = key_eval.sort() key_match = z3.Const(f"{self.name}_table_key_{index}", key_sort) key_pairs.append(key_eval == key_match) return z3.And(key_pairs)
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)
def eval_action(self, p4_state, action_tuple): p4_action = action_tuple[0] p4_action_args = action_tuple[1] p4_action = p4_state.resolve_reference(p4_action) if not isinstance(p4_action, P4Action): raise TypeError(f"Expected a P4Action got {type(p4_action)}!") action_args = [] p4_action_args_len = len(p4_action_args) - 1 for idx, param in enumerate(p4_action.params): if idx > p4_action_args_len: if isinstance(param.p4_type, z3.SortRef): action_args.append( z3.Const(f"{self.name}{param.name}", param.p4_type)) else: action_args.append(param.p4_type) else: action_args.append(p4_action_args[idx]) return p4_action(p4_state, *action_args)
def initialize(self, ctx, *args, **kwargs): merged_args = merge_parameters(self.params, *args, **kwargs) for pipe_name, pipe_arg in merged_args.items(): log.info("Loading %s pipe...", pipe_name) pipe_val = ctx.resolve_expr(pipe_arg.p4_val) if isinstance(pipe_val, P4Control): # create the z3 representation of this control state state_ctx, p4_state = self.build_state_ctx( ctx, pipe_name, pipe_arg, pipe_val) state_ctx.set_p4_state(p4_state) # initialize the call with its own params we collected # this is essentially the input packet args = [] for param in pipe_val.params: args.append(param.name) pipe_val.apply(state_ctx, *args) # after executing the pipeline get its z3 representation z3_function = p4_state.create_z3_representation(state_ctx) # all done, that is our P4 representation! self.pipes[pipe_name] = (z3_function, p4_state, 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, None, pipe_val) elif isinstance(pipe_val, P4Package): # execute the package by calling its initializer # pipe_val.initialize(ctx) # 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, None, 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