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 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_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_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 operator(x, y): # if x and y are ints we might deal with a signed value # we need to use the normal operator in this case if isinstance(x, int) and isinstance(y, int): return op.ge(x, y) return z3.UGE(x, y)