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)
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(self, p4_state): cond = p4_state.resolve_expr(self.cond) # handle side effects for function calls var_store, chain_copy = p4_state.checkpoint() then_val = p4_state.resolve_expr(self.then_val) then_vars = copy_attrs(p4_state.locals) p4_state.restore(var_store, chain_copy) else_val = p4_state.resolve_expr(self.else_val) p4_state.merge_attrs(cond, then_vars) then_expr = then_val else_expr = else_val # this is a really nasty hack, do not try this at home kids # because we have to be able to access the sub values again # we have to resolve the if condition in the case of complex types # we do this by splitting the if statement into a list # lists can easily be assigned to a target structure if isinstance(then_expr, P4ComplexInstance): then_expr = then_expr.flatten() if isinstance(else_expr, P4ComplexInstance): else_expr = else_expr.flatten() if isinstance(then_expr, list) and isinstance(else_expr, list): sub_cond = [] # handle nested complex types then_expr = self.unravel_datatype(then_val, then_expr) else_expr = self.unravel_datatype(else_val, else_expr) for idx, member in enumerate(then_expr): if_expr = z3.If(cond, member, else_expr[idx]) sub_cond.append(if_expr) return sub_cond then_is_const = isinstance(then_expr, (z3.BitVecRef, int)) else_is_const = isinstance(else_expr, (z3.BitVecRef, int)) if then_is_const and else_is_const: # align the bitvectors to allow operations, we cast ints downwards if else_expr.size() > then_expr.size(): else_expr = z3_cast(else_expr, then_expr.size()) if else_expr.size() < then_expr.size(): then_expr = z3_cast(then_expr, else_expr.size()) return z3.If(cond, then_expr, else_expr)
def eval_callable(self, ctx, merged_args, var_buffer): # execute the action expression with the new environment ctx.return_type = self.return_type self.statements.eval(ctx) return_expr = None if len(ctx.return_exprs) == 1: _, return_expr = ctx.return_exprs.pop() elif len(ctx.return_exprs) > 1: # the first condition is not needed since it is the default _, return_expr = ctx.return_exprs.pop() if isinstance(return_expr, StructInstance): while ctx.return_exprs: then_cond, then_expr = ctx.return_exprs.pop() return_expr = handle_mux(then_cond, then_expr, return_expr) else: while ctx.return_exprs: then_cond, then_expr = ctx.return_exprs.pop() return_expr = z3.If(then_cond, then_expr, return_expr) return return_expr
def eval(self, p4_state): switches = [] for case_val, case_name in reversed(self.cases): case_expr = p4_state.resolve_expr(case_val) select_cond = [] if isinstance(case_expr, P4ComplexInstance): case_expr = case_expr.flatten() if isinstance(case_expr, list): for idx, case_match in enumerate(case_expr): # default implies don't care, do not add # TODO: Verify that this assumption is right... if isinstance(case_match, DefaultExpression): continue match = self.match[idx] match_expr = p4_state.resolve_expr(match) cond = match_expr == case_match select_cond.append(cond) else: # default implies don't care, do not add # TODO: Verify that this assumption is right... if isinstance(case_expr, DefaultExpression): continue for match in self.match: match_expr = p4_state.resolve_expr(match) cond = case_expr == match_expr select_cond.append(cond) if not select_cond: select_cond = [z3.BoolVal(False)] var_store, chain_copy = p4_state.checkpoint() parser_state = self.state_list[case_name] state_expr = parser_state.eval(p4_state) p4_state.restore(var_store, chain_copy) switches.append((z3.And(*select_cond), state_expr)) default_parser_state = self.state_list[self.default] expr = default_parser_state.eval(p4_state) for cond, state_expr in switches: expr = z3.If(cond, state_expr, expr) return expr
def operator(x, y): no_overflow = z3.BVSubNoOverflow(x, y) no_underflow = z3.BVSubNoUnderflow(x, y, False) zero_return = 0 return z3.If(z3.And(no_overflow, no_underflow), x - y, zero_return)
def operator(x, y): no_overflow = z3.BVAddNoOverflow(x, y, False) no_underflow = z3.BVAddNoUnderflow(x, y) max_return = 2**x.size() - 1 return z3.If(z3.And(no_overflow, no_underflow), x + y, max_return)