def set_or_add_var(self, lval, rval, new_decl=False): if isinstance(lval, P4Slice): self.set_slice(self, lval, rval) return if isinstance(lval, P4Member): lval.set_value(self, rval) return # now that all the preprocessing is done we can assign the value log.debug("Setting %s(%s) to %s(%s) ", lval, type(lval), rval, type(rval)) ctx, lval_val = self.find_context(lval) if new_decl or not ctx: ctx = self # rvals could be a list, unroll the assignment if isinstance(rval, list) and lval_val is not None: if isinstance(lval_val, StructInstance): lval_val.set_list(rval) elif isinstance(lval, str): # TODO: Decide whether this makes sense log.warning("Skipping assignment %s to %s", rval, lval) else: raise TypeError(f"set_list {type(lval)} not supported!") return ctx.locals[lval] = rval
def resolve_expr(self, expr): # Resolves to z3 and z3p4 expressions # ints and lists are also okay # resolve potential string references first log.debug("Resolving %s", expr) if isinstance(expr, (str, P4Member)): expr = self.resolve_reference(expr) if isinstance(expr, P4Expression): # We got a P4 expression, recurse and resolve... expr = expr.eval(self) return self.resolve_expr(expr) if isinstance(expr, (z3.AstRef, int)): # These are z3 types and can be returned # Unfortunately int is part of it because z3 is very inconsistent # about var handling... return expr if isinstance( expr, (StaticType, P4ComplexInstance, P4Z3Class, types.MethodType)): # In a similar manner, just return any remaining class types # Methods can be class attributes and need to be returned as is return expr if isinstance(expr, list): # For lists, resolve each value individually and return a new list list_expr = [] for val_expr in expr: rval_expr = self.resolve_expr(val_expr) list_expr.append(rval_expr) return list_expr raise TypeError(f"Expression of type {type(expr)} cannot be resolved!")
def eval(self, p4_state): log.debug("Assigning %s to %s ", self.rval, self.lval) rval_expr = p4_state.resolve_expr(self.rval) # in assignments all complex types values are copied if isinstance(rval_expr, StructInstance): rval_expr = copy.copy(rval_expr) if isinstance(rval_expr, int): lval = p4_state.resolve_expr(self.lval) rval_expr = z3_cast(rval_expr, lval.sort()) p4_state.set_or_add_var(self.lval, rval_expr)
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 copy_in(self, ctx, merged_args): param_buffer = OrderedDict() var_buffer = OrderedDict() for param_name, arg in merged_args.items(): # We need to resolve array indices appropriately arg_expr, _ = resolve_index(ctx, arg.p4_val) try: param_val = ctx.resolve_reference(param_name) var_buffer[param_name] = (arg.mode, arg_expr, param_val) except RuntimeError: # if the variable name does not exist, set the value to None var_buffer[param_name] = (arg.mode, arg_expr, None) # Sometimes expressions are passed, resolve those first arg_expr = ctx.resolve_expr(arg_expr) # it can happen that we receive a list # infer the type, generate, and set try: p4_type = ctx.resolve_type(arg.p4_type) except KeyError: # this is a generic type, we need to bind for this scope # FIXME: Clean this up p4_type = arg_expr.sort() ctx.add_type(arg.p4_type, p4_type) if isinstance(arg_expr, list): # if the type is undefined, do nothing if isinstance(p4_type, P4ComplexType): arg_instance = ctx.gen_instance(UNDEF_LABEL, 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 = ctx.gen_instance(UNDEF_LABEL, 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, var_buffer
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(self, ctx): # An assignment, written with the = sign, first evaluates its left # sub-expression to an l-value, then evaluates its right sub-expression # to a value, and finally copies the value into the l-value. Derived # types (e.g. structs) are copied recursively, and all components of # headers are copied, including “validity” bits. Assignment is not # defined for extern values. log.debug("Assigning %s to %s ", self.rval, self.lval) lval, _ = resolve_index(ctx, self.lval) rval_expr = ctx.resolve_expr(self.rval) # in assignments all complex types values are copied if isinstance(rval_expr, StructInstance): rval_expr = copy.copy(rval_expr) if isinstance(rval_expr, int): rval_expr = z3_cast(rval_expr, ctx.resolve_expr(self.lval).sort()) ctx.set_or_add_var(lval, rval_expr)
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 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(self, p4_state): log.debug("Assigning %s to %s ", self.rval, self.lval) rval_expr = p4_state.resolve_expr(self.rval) # in assignments all complex types values are copied if isinstance(rval_expr, P4ComplexInstance): rval_expr = copy.copy(rval_expr) # make sure the assignment is aligned appropriately # this can happen because we also evaluate before the # BindTypeVariables pass # we can only align if tmp_val is a bitvector # example test: instance_overwrite.p4 lval = p4_state.resolve_expr(self.lval) if isinstance(rval_expr, int) and isinstance( lval, (z3.BitVecSortRef, z3.BitVecRef)): rval_expr = z3_cast(rval_expr, lval.sort()) p4_state.set_or_add_var(self.lval, rval_expr) p4z3_expr = p4_state.pop_next_expr() return p4z3_expr.eval(p4_state)
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 copy_out(self, ctx): # restore any variables that may have been overridden # with copy-out we copy from left to right # values on the right override values on the left # the var buffer is an ordered dict that maintains this order for par_name, (mode, par_ref, par_val) in self.var_buffer.items(): # we retrieve the current value val = self.resolve_reference(par_name) # we then reset the name in the scope to its original log.debug("Resetting %s to %s", par_name, type(par_val)) # value has not existed previously, ignore if par_val is not None: ctx.set_or_add_var(par_name, par_val) # if the param was copy-out, we copy the value we retrieved # back to the original input reference if mode in ("inout", "out"): log.debug("Copy-out: %s to %s", val, par_ref) # copy it back to the input reference # this assumes an lvalue as input ctx.set_or_add_var(par_ref, val)
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 restore_context(self, p4_state): # FIXME: This does not respect local context # local variables are overridden in functions and controls # restore any variables that may have been overridden for param_name, param in self.var_buffer.items(): is_ref = param[0] param_ref = param[1] param_val = param[2] if is_ref in ("inout", "out"): val = p4_state.resolve_reference(param_name) if param_val is None: # value has not existed previously, marked for deletion log.debug("Deleting %s", param_name) p4_state.del_var(param_name) else: log.debug("Resetting %s to %s", param_name, type(param_val)) p4_state.set_or_add_var(param_name, param_val) if is_ref in ("inout", "out"): # with copy-out we copy from left to right # values on the right override values on the left # the var buffer is an ordered dict that maintains this order log.debug("Copy-out: %s to %s", val, param_val) p4_state.set_or_add_var(param_ref, val)
def eval_default(self, ctx): act_idx, action_name, action_args = self.default_action log.debug("Evaluating default action...") return self.eval_action(ctx, action_name, action_args, act_idx)
def eval_default(self, p4_state): _, action_name, action_args = self.default_action log.debug("Evaluating default action...") return self.eval_action(p4_state, action_name, action_args)