def get_const_matches(self, ctx, c_keys): 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, FIXME. for index, (key_expr, _) 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 # TODO: Unclear about the role of side-effects here key_eval = ctx.resolve_expr(key_expr) if isinstance(c_key_expr, P4Range): x = ctx.resolve_expr(c_key_expr.min) y = ctx.resolve_expr(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): # TODO: Unclear about the role of side-effects here val = ctx.resolve_expr(c_key_expr.value) mask = ctx.resolve_expr(c_key_expr.mask) c_key_eval = (val & mask) == (key_eval & mask) matches.append(c_key_eval) else: c_key_eval = ctx.resolve_expr(c_key_expr) matches.append(key_eval == c_key_eval) return z3.And(*matches)
def eval(self, ctx): cond = z3.simplify(ctx.resolve_expr(self.cond)) forward_cond_copy = ctx.tmp_forward_cond then_vars = None if not z3.is_false(cond): var_store = ctx.checkpoint() ctx.tmp_forward_cond = z3.And(forward_cond_copy, cond) try: self.then_block.eval(ctx) except ParserException: RejectState().eval(ctx) if not(ctx.has_returned or ctx.get_exited()): then_vars = ctx.get_attrs() ctx.set_exited(False) ctx.has_returned = False ctx.restore(var_store) if not z3.is_true(cond): var_store = ctx.checkpoint() ctx.tmp_forward_cond = z3.And(forward_cond_copy, z3.Not(cond)) try: self.else_block.eval(ctx) except ParserException: RejectState().eval(ctx) if ctx.get_exited() or ctx.has_returned: ctx.restore(var_store) ctx.set_exited(False) ctx.has_returned = False ctx.tmp_forward_cond = forward_cond_copy if then_vars: merge_attrs(ctx, cond, then_vars)
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 eval(self, p4_state): if self.is_terminal: context = p4_state.current_context() key = self.parser_state.name tmp_forward_conds = [] for context in reversed(p4_state.contexts): tmp_forward_conds.append(context.tmp_forward_cond) cond = z3.And(*tmp_forward_conds) attrs = p4_state.get_attrs() # add the if key in self.parser_tree.terminal_nodes: orig_cond = self.parser_tree.terminal_nodes[key][0] orig_dict = self.parser_tree.terminal_nodes[key][1] merge_dicts(orig_dict, cond, attrs) cond = z3.And(orig_cond, cond) attrs = orig_dict self.parser_tree.terminal_nodes[key] = (cond, attrs) return parser_state = self.parser_state try: parser_state.eval(p4_state) if isinstance(self.child, list): # there is a switch case try to untangle it. self.handle_select(p4_state) elif isinstance(self.child, ParserNode): # direct descendant, continue the evaluation self.child.eval(p4_state) except ParserException: RejectState().eval(p4_state)
def handle_select(self, ctx): switches = [] select_conds = [] forward_cond_copy = ctx.tmp_forward_cond match_list = ctx.resolve_expr(self.match) for parser_cond, parser_node in self.child: case_expr = ctx.resolve_expr(parser_cond) cond = build_select_cond(ctx, case_expr, match_list) # state forks here var_store = ctx.checkpoint() ctx.tmp_forward_cond = z3.And(forward_cond_copy, cond) parser_node.eval(ctx) select_conds.append(cond) if not (ctx.get_exited() or z3.is_false(cond)): switches.append((ctx.tmp_forward_cond, ctx.get_attrs())) ctx.set_exited(False) ctx.has_returned = False ctx.restore(var_store) # this hits when no select matches cond = z3.Not(z3.Or(*select_conds)) ctx.tmp_forward_cond = z3.And(forward_cond_copy, cond) self.default.eval(ctx) ctx.set_exited(False) ctx.has_returned = False ctx.tmp_forward_cond = forward_cond_copy for cond, then_vars in reversed(switches): merge_attrs(ctx, cond, then_vars)
def handle_select(self, p4_state): switches = [] select_conds = [] context = p4_state.current_context() forward_cond_copy = context.tmp_forward_cond match_list = p4_state.resolve_expr(self.match) for parser_cond, parser_node in reversed(self.child): case_expr = p4_state.resolve_expr(parser_cond) cond = build_select_cond(p4_state, case_expr, match_list) # state forks here var_store, contexts = p4_state.checkpoint() context.tmp_forward_cond = z3.And(forward_cond_copy, cond) parser_node.eval(p4_state) select_conds.append(cond) if not p4_state.has_exited: switches.append( (context.tmp_forward_cond, p4_state.get_attrs())) p4_state.has_exited = False context.has_returned = False p4_state.restore(var_store, contexts) # this hits when the table is either missed, or no action matches cond = z3.Not(z3.Or(*select_conds)) context.tmp_forward_cond = z3.And(forward_cond_copy, cond) self.default.eval(p4_state) p4_state.has_exited = False context.has_returned = False context.tmp_forward_cond = forward_cond_copy for cond, then_vars in switches: merge_attrs(p4_state, cond, then_vars)
def build_select_cond(p4_state, case_expr, match_list): select_cond = [] # these casts are kind of silly but simplify the code a lot if isinstance(case_expr, StructInstance): case_expr = case_expr.flatten() elif not isinstance(case_expr, list): case_expr = [case_expr] 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): select_cond.append(z3.BoolVal(True)) elif isinstance(case_match, P4Range): x = case_match.min y = case_match.max match_key = z3.And(z3.ULE(x, match_list[idx]), z3.UGE(y, match_list[idx])) select_cond.append(match_key) elif isinstance(case_match, P4Mask): val = p4_state.resolve_expr(case_match.value) mask = case_match.mask match_key = (val | ~mask) == (match_list[idx] | ~mask) select_cond.append(match_key) else: select_cond.append(case_match == match_list[idx]) if not select_cond: return z3.BoolVal(False) return z3.And(*select_cond)
def eval(self, p4_state): context = p4_state.current_context() cond = z3.simplify(p4_state.resolve_expr(self.cond)) forward_cond_copy = context.tmp_forward_cond then_vars = None if not cond == z3.BoolVal(False): var_store, contexts = p4_state.checkpoint() context.tmp_forward_cond = z3.And(forward_cond_copy, cond) try: self.then_block.eval(p4_state) except ParserException: RejectState().eval(p4_state) if not (context.has_returned or p4_state.has_exited): then_vars = p4_state.get_attrs() p4_state.has_exited = False context.has_returned = False p4_state.restore(var_store, contexts) if not cond == z3.BoolVal(True): var_store, contexts = p4_state.checkpoint() context.tmp_forward_cond = z3.And(forward_cond_copy, z3.Not(cond)) try: self.else_block.eval(p4_state) except ParserException: RejectState().eval(p4_state) if p4_state.has_exited or context.has_returned: p4_state.restore(var_store, contexts) p4_state.has_exited = False context.has_returned = False context.tmp_forward_cond = forward_cond_copy if then_vars: merge_attrs(p4_state, cond, then_vars)
def eval_table_entries(self, ctx, action_exprs, action_matches): forward_cond_copy = ctx.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 = ctx.checkpoint() cond = z3.And(self.locals["hit"], action_match) ctx.tmp_forward_cond = z3.And(forward_cond_copy, cond) self.eval_action(ctx, act_name, act_args, act_id) if not ctx.get_exited(): action_exprs.append((cond, ctx.get_attrs())) ctx.set_exited(False) action_matches.append(action_match) ctx.restore(var_store)
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_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_const_entries(self, ctx, action_exprs, action_matches): forward_cond_copy = ctx.tmp_forward_cond for c_keys, (action_name, action_args) in reversed(self.const_entries): action_match = self.get_const_matches(ctx, c_keys) log.debug("Evaluating constant action %s...", action_name) # state forks here var_store = ctx.checkpoint() cond = z3.And(self.locals["hit"], action_match) ctx.tmp_forward_cond = z3.And(forward_cond_copy, cond) self.eval_action(ctx, action_name, action_args) if not ctx.get_exited(): action_exprs.append((cond, ctx.get_attrs())) ctx.set_exited(False) action_matches.append(action_match) ctx.restore(var_store)
def eval_cases(self, p4_state, cases): case_exprs = [] case_matches = [] context = p4_state.current_context() forward_cond_copy = context.tmp_forward_cond for case in reversed(cases.values()): var_store, contexts = p4_state.checkpoint() context.tmp_forward_cond = z3.And(forward_cond_copy, case["match"]) case["case_block"].eval(p4_state) if not (context.has_returned or p4_state.has_exited): then_vars = p4_state.get_attrs() case_exprs.append((case["match"], then_vars)) context.has_returned = False p4_state.has_exited = False p4_state.restore(var_store, contexts) case_matches.append(case["match"]) var_store, contexts = p4_state.checkpoint() cond = z3.Not(z3.Or(*case_matches)) context.tmp_forward_cond = z3.And(forward_cond_copy, cond) self.default_case.eval(p4_state) if context.has_returned or p4_state.has_exited: p4_state.restore(var_store, contexts) context.has_returned = False p4_state.has_exited = False context.tmp_forward_cond = forward_cond_copy for cond, then_vars in case_exprs: merge_attrs(p4_state, cond, then_vars)
def eval_table(self, ctx): action_exprs = [] action_matches = [] forward_cond_copy = ctx.tmp_forward_cond # only bother to evaluate if the table can actually hit if not z3.is_false(self.locals["hit"]): # note: the action lists are pass by reference here # first evaluate all the constant entries self.eval_const_entries(ctx, action_exprs, action_matches) # then append dynamic table entries to the constant entries # only do this if the table can actually be manipulated if not self.immutable: self.eval_table_entries(ctx, action_exprs, action_matches) # finally start evaluating the default entry var_store = ctx.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))) ctx.tmp_forward_cond = z3.And(forward_cond_copy, cond) self.eval_default(ctx) if ctx.get_exited(): ctx.restore(var_store) ctx.set_exited(False) ctx.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(ctx, cond, then_vars)
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 eval(self, ctx): parser_state = self.parser_state try: parser_state.eval(ctx) except ParserException: RejectState().eval(ctx) return if self.is_terminal: key = self.parser_state.name tmp_forward_conds = [] sub_ctx = ctx while not isinstance(sub_ctx, StaticContext): tmp_forward_conds.append(sub_ctx.tmp_forward_cond) sub_ctx = sub_ctx.parent_ctx cond = z3.And(*tmp_forward_conds) attrs = ctx.get_attrs() # add the if key in self.parser_tree.terminal_nodes: orig_cond = self.parser_tree.terminal_nodes[key][0] orig_dict = self.parser_tree.terminal_nodes[key][1] merge_dicts(orig_dict, cond, attrs) cond = z3.Or(orig_cond, cond) attrs = orig_dict self.parser_tree.terminal_nodes[key] = (cond, attrs) return if isinstance(self.child, list): # there is a switch case try to untangle it. self.handle_select(ctx) elif isinstance(self.child, ParserNode): # direct descendant, continue the evaluation self.child.eval(ctx)
def eval(self, ctx): cond = z3.simplify( z3.And(z3.Not(z3.Or(*ctx.forward_conds)), ctx.tmp_forward_cond)) if not z3.is_false(cond): ctx.return_states.append((cond, ctx.copy_attrs())) ctx.has_returned = True ctx.forward_conds.append(ctx.tmp_forward_cond)
def eval(self, p4_state): # FIXME: This checkpointing should not be necessary # Figure out what is going on var_store, contexts = p4_state.checkpoint() forward_conds = [] tmp_forward_conds = [] for context in reversed(p4_state.contexts): context.copy_out(p4_state) forward_conds.extend(context.forward_conds) tmp_forward_conds.append(context.tmp_forward_cond) context = p4_state.current_context() cond = z3.simplify( z3.And(z3.Not(z3.Or(*forward_conds)), z3.And(*tmp_forward_conds))) if not cond == z3.BoolVal(False): p4_state.exit_states.append((cond, p4_state.get_z3_repr())) p4_state.has_exited = True p4_state.restore(var_store, contexts) context.forward_conds.append(context.tmp_forward_cond)
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_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 eval(self, ctx): # FIXME: This checkpointing should not be necessary # Figure out what is going on var_store = ctx.checkpoint() forward_conds = [] tmp_forward_conds = [] sub_ctx = ctx while not isinstance(sub_ctx, StaticContext): sub_ctx.copy_out(ctx) forward_conds.extend(sub_ctx.forward_conds) tmp_forward_conds.append(sub_ctx.tmp_forward_cond) sub_ctx = sub_ctx.parent_ctx cond = z3.simplify( z3.And(z3.Not(z3.Or(*forward_conds)), z3.And(*tmp_forward_conds))) if not z3.is_false(cond): ctx.add_exit_state(cond, ctx.get_p4_state().get_members(ctx)) ctx.set_exited(True) ctx.restore(var_store) ctx.forward_conds.append(ctx.tmp_forward_cond)
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 eval(self, p4_state): # FIXME: This checkpointing should not be necessary # Figure out what is going on forward_conds = [] tmp_forward_conds = [] for context in reversed(p4_state.contexts): forward_conds.extend(context.forward_conds) tmp_forward_conds.append(context.tmp_forward_cond) context = p4_state.current_context() cond = z3.And(z3.Not(z3.Or(*forward_conds)), z3.And(*tmp_forward_conds)) var_store, contexts = p4_state.checkpoint() for member_name, _ in p4_state.members: member_val = p4_state.resolve_reference(member_name) if isinstance(member_val, StructInstance): member_val.deactivate() p4_state.exit_states.append((cond, p4_state.get_z3_repr())) p4_state.restore(var_store, contexts) p4_state.has_exited = True context.forward_conds.append(context.tmp_forward_cond)
def eval(self, ctx): # FIXME: This checkpointing should not be necessary # Figure out what is going on forward_conds = [] tmp_forward_conds = [] sub_ctx = ctx while not isinstance(sub_ctx, StaticContext): forward_conds.extend(sub_ctx.forward_conds) tmp_forward_conds.append(sub_ctx.tmp_forward_cond) sub_ctx = sub_ctx.parent_ctx cond = z3.And(z3.Not(z3.Or(*forward_conds)), z3.And(*tmp_forward_conds)) var_store = ctx.checkpoint() for member_name, _ in ctx.get_p4_state().members: member_val = ctx.resolve_reference(member_name) if isinstance(member_val, StructInstance): member_val.deactivate() ctx.add_exit_state(cond, ctx.get_p4_state().get_members(ctx)) ctx.restore(var_store) ctx.set_exited(True) ctx.forward_conds.append(ctx.tmp_forward_cond)
def eval_cases(self, ctx, cases): case_exprs = [] case_matches = [] forward_cond_copy = ctx.tmp_forward_cond fall_through_matches = [] for case_match, case_block in cases.values(): # there is no block for the switch # this expressions falls through to the next switch case if not case_block: fall_through_matches.append(case_match) continue # matches the condition OR all the other fall-through switches case_match = z3.Or(case_match, *fall_through_matches) fall_through_matches.clear() var_store = ctx.checkpoint() ctx.tmp_forward_cond = z3.And( forward_cond_copy, case_match) case_block.eval(ctx) if not (ctx.has_returned or ctx.get_exited()): then_vars = ctx.get_attrs() case_exprs.append((case_match, then_vars)) ctx.has_returned = False ctx.set_exited(False) ctx.restore(var_store) case_matches.append(case_match) var_store = ctx.checkpoint() # process the default expression cond = z3.Not(z3.Or(*case_matches)) if not z3.is_false(cond): ctx.tmp_forward_cond = z3.And(forward_cond_copy, cond) self.default_case.eval(ctx) if ctx.has_returned or ctx.get_exited(): ctx.restore(var_store) ctx.has_returned = False ctx.set_exited(False) ctx.tmp_forward_cond = forward_cond_copy # merge all the expressions in reverse order for cond, then_vars in reversed(case_exprs): merge_attrs(ctx, cond, then_vars)
def eval(self, p4_state): # boolean expressions can short-circuit # so we save the result of the right-hand expression and merge lval_expr = p4_state.resolve_expr(self.lval) var_store, chain_copy = p4_state.checkpoint() context = p4_state.current_context() forward_cond_copy = context.tmp_forward_cond context.tmp_forward_cond = z3.And(forward_cond_copy, lval_expr) rval_expr = p4_state.resolve_expr(self.rval) else_vars = p4_state.get_attrs() p4_state.restore(var_store, chain_copy) context.tmp_forward_cond = forward_cond_copy merge_attrs(p4_state, lval_expr, else_vars) return self.operator(lval_expr, rval_expr)
def eval_switch_table_matches(self, ctx, table): cases = OrderedDict() if table.immutable: # if the table is immutable we can only match on const entries for c_keys, (action_name, _) in table.const_entries: const_matches = [] # check if the action of the entry is even present if action_name not in self.case_blocks: continue # compute the match key # FIXME: Deal with side effects here # Maybe just checkpoint and restore? Ugh. So expensive... match_cond = table.get_const_matches(ctx, c_keys) action = table.actions[action_name][0] if action_name in cases: prev_match, _ = cases[action_name] match_cond = z3.Or(match_cond, prev_match) const_matches.append(match_cond) cases[action_name] = ( match_cond, self.case_blocks[action_name]) # we also need to process the default action separately # this basically hits only if no other case matches _, action_name, _ = table.default_action match_cond = z3.Not(z3.Or(*const_matches)) if action_name in cases: prev_match, _ = cases[action_name] match_cond = z3.Or(match_cond, prev_match) const_matches.append(match_cond) cases[action_name] = (match_cond, self.case_blocks[action_name]) else: # otherwise we are dealing with a normal table # just insert the match entries combined with the hit expression add_default = None for case_name, case_block in self.case_blocks.items(): match_var = table.tbl_action action = table.actions[case_name][0] match_cond = z3.And(table.locals["hit"], (action == match_var)) cases[case_name] = (match_cond, case_block) # we need to check if the default is in the cases # this implies that the "default" case can never be executed if case_name == table.default_action[1]: add_default = (case_name, (z3.BoolVal(True), case_block)) if add_default and len(table.actions) == len(cases): cases[add_default[0]] = add_default[1] return cases
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 eval(self, p4_state): cond = z3.simplify(p4_state.resolve_expr(self.cond)) # handle side effects for function and table calls if cond == z3.BoolVal(False): return p4_state.resolve_expr(self.else_val) if cond == z3.BoolVal(True): return p4_state.resolve_expr(self.then_val) var_store, chain_copy = p4_state.checkpoint() context = p4_state.current_context() forward_cond_copy = context.tmp_forward_cond context.tmp_forward_cond = z3.And(forward_cond_copy, cond) then_expr = p4_state.resolve_expr(self.then_val) then_vars = p4_state.get_attrs() p4_state.restore(var_store, chain_copy) context.tmp_forward_cond = forward_cond_copy else_expr = p4_state.resolve_expr(self.else_val) merge_attrs(p4_state, cond, then_vars) return handle_mux(cond, then_expr, else_expr)
def eval(self, ctx): if self.expr is None: expr = None else: # resolve the expr before restoring the state expr = ctx.resolve_expr(self.expr) if isinstance(ctx.return_type, z3.BitVecSortRef): expr = z3_cast(expr, ctx.return_type) # we return a complex typed expression list, instantiate if isinstance(expr, list): # name is meaningless here so keep it empty instance = ctx.gen_instance("", ctx.return_type) instance.set_list(expr) expr = instance cond = z3.simplify(z3.And(z3.Not(z3.Or(*ctx.forward_conds)), ctx.tmp_forward_cond)) if not z3.is_false(cond): ctx.return_states.append((cond, ctx.copy_attrs())) if expr is not None: ctx.return_exprs.append((cond, expr)) ctx.has_returned = True ctx.forward_conds.append(ctx.tmp_forward_cond)