diff --git a/docs/JSON_format.md b/docs/JSON_format.md index 5de96d19c..bf6c79761 100644 --- a/docs/JSON_format.md +++ b/docs/JSON_format.md @@ -2,16 +2,15 @@ All bmv2 target switches take as input a JSON file, whose format is essentially target independent. The format is very simple and several examples can be found -in this repository, including [here] -(../targets/simple_router/simple_router.json). +in this repository, including +[here](../targets/simple_router/simple_router.json). This documents attempt to describe the expected JSON schema and the constraints -on each attribute. There is some ongoing work to write a formal JSON schema as -per [this specification] (http://json-schema.org/). +on each attribute. ## Current bmv2 JSON format version -The version described in this document is *2.9*. +The version described in this document is *2.10*. The major version number will be increased by the compiler only when backward-compatibility of the JSON format is broken. After a major version @@ -28,7 +27,7 @@ though it is recommended for all consummers of the JSON. Tentative support for signed fields (with a 2 complement representation) has been added to bmv2, although they are not supported in P4 1.0 or by the [p4c-bm -compiler] (https://github.com/p4lang/p4c-bm). However, signed constants (in +compiler](https://github.com/p4lang/p4c-bm). However, signed constants (in expressions, or as primitive arguments) are always supported. Arithmetic is done with infinite precision, but when a value is copied into a field, it is truncated based on the field's bitwidth. @@ -46,8 +45,9 @@ header instance name and the second is the field member name. endian order); it can be prefixed with a negative sign, for negative values. - if `type` is `bool`, `value` is either `true` or `false`. - if `type` is a named P4 type (`header`, `header_stack`, `calculation`, -`register_array`, `meter_array`, `counter_array`), `value` is a string -corresponding to the name of the designated object. +`register_array`, `meter_array`, `counter_array`, `header_union`, +`header_union_stack`), `value` is a string corresponding to the name of the +designated object. - if `type` is `string`, `value` is a sequence of characters. - if `type` is `lookahead` (parser only), `value` is a JSON 2-tuple, where the first item is the bit offset for the lookahead and the second item is the @@ -58,9 +58,14 @@ index. - if `type` is `stack_field`, `value` is a JSON 2-tuple, where the first item is the header stack name and the second is the field member name. This is used to access a field in the last valid header instance in the stack. +- if `type` is `union_stack_field`, `value` is a JSON 3-tuple, where the first item is +the header union stack name, the second is the union member name and the third +is the field member name. This can be used exclusively in the `transition_key` +of a parser to access a field in the last valid union instance in the stack. - if `type` is `expression`, `value` is a JSON object with 3 attributes: - `op`: the operation performed (`+`, `-`, `*`, `<<`, `>>`, `==`, `!=`, `>`, - `>=`, `<`, `<=`, `and`, `or`, `not`, `&`, `|`, `^`, `~`, `valid`) + `>=`, `<`, `<=`, `and`, `or`, `not`, `&`, `|`, `^`, `~`, `valid`, + `valid_union`) - `left`: the left side of the operation, or `null` if unary operation - `right`: the right side of the operation @@ -86,7 +91,7 @@ object has a fourth attribute, `cond` (condition), which is itself an expression. For example, in `(hA.f1 == 9) ? 3 : 4`, `cond` would be the JSON representation of `(hA.f1 == 9)`, `left` would be the JSON representation of `3` and `right` would be the JSON representation of `4`. - - stack header access (`op` is `dereference_stack`): `left` is a + - stack header access (`op` is `dereference_header_stack`): `left` is a `header_stack` and `right` needs to evaluate to a valid index inside the stack; the expression produces a `header`. - last valid index in a stack (`op` is `last_stack_index`): unary operation @@ -101,6 +106,15 @@ representing a valid field offset for that header; the expression returns the header field at the given offset. The interest of this operation is that the `header` needs not be known at compile time, it can be a stack member resolved at runtime. + - stack union access (`op` is `dereference_union_stack`): `left` is a +`header_union_stack` and `right` needs to evaluate to a valid index inside the +stack; the expression produces a `header`. + - access to the header union member at a given offset (`op` is +`access_union_header`): `left` needs to evaluate to a `header_union` and `right` +is a JSON integer representing a valid member offset for that union; the +expression returns the header at the given offset. The interest of this +operation is that the `header_union` needs not be known at compile time, it can +be a union stack entry resolved at runtime. For field references, some special values are allowed. They are called "hidden fields". For now, we only support one kind of hidden fields: `
action); void add_action_to_table(const std::string &table_name, @@ -304,10 +315,12 @@ class P4Objects { void init_header_types(const Json::Value &root); void init_headers(const Json::Value &root); void init_header_stacks(const Json::Value &root); + void init_header_unions(const Json::Value &root, InitState *); + void init_header_union_stacks(const Json::Value &root, InitState *); void init_extern_instances(const Json::Value &root); void init_parse_vsets(const Json::Value &root); void init_errors(const Json::Value &root); - void init_parsers(const Json::Value &root); + void init_parsers(const Json::Value &root, InitState *); void init_deparsers(const Json::Value &root); void init_calculations(const Json::Value &root); void init_counter_arrays(const Json::Value &root); @@ -334,6 +347,9 @@ class P4Objects { std::unordered_map header_ids_map{}; std::unordered_map header_stack_ids_map{}; + std::unordered_map header_union_ids_map{}; + std::unordered_map + header_union_stack_ids_map{}; std::unordered_map header_to_type_map{}; std::unordered_map header_stack_to_type_map{}; @@ -425,6 +441,13 @@ class P4Objects { // used for initialization only std::unordered_map header_id_to_stack_id{}; + struct HeaderUnionPos { + header_union_id_t union_id; + size_t offset; // the offset of the header in the union + }; + std::unordered_map header_id_to_union_pos{}; + std::unordered_map + union_id_to_union_stack_id{}; ConfigOptionMap config_options{}; diff --git a/include/bm/bm_sim/actions.h b/include/bm/bm_sim/actions.h index 77b247e09..102dd1339 100644 --- a/include/bm/bm_sim/actions.h +++ b/include/bm/bm_sim/actions.h @@ -56,6 +56,10 @@ //! - `[const] MeterArray &` //! - `[const] CounterArray &` //! - `[const] RegisterArray &` +//! - `[const] HeaderUnion &` +//! - `[const] StackIface &` for arbitrary P4 stacks +//! - `[const] HeaderStack &` for P4 header stacks +//! - `[const] HeaderUnionStack &` for P4 header union stacks //! - `const std::string &` or `const char *` for strings //! //! You can declare and register primitives anywhere in your switch target C++ @@ -180,7 +184,8 @@ struct ActionParam { METER_ARRAY, COUNTER_ARRAY, REGISTER_ARRAY, EXPRESSION, EXTERN_INSTANCE, - STRING} tag; + STRING, + HEADER_UNION, HEADER_UNION_STACK} tag; union { unsigned int const_offset; @@ -241,6 +246,10 @@ struct ActionParam { // I use a pointer here to avoid complications with the union; the string // memory is owned by ActionFn (just like for ArithExpression above) const std::string *str; + + header_union_stack_id_t header_union_stack; + + header_union_id_t header_union; }; // convert to the correct type when calling a primitive @@ -346,6 +355,49 @@ const HeaderStack &ActionParam::to( return ActionParam::to(state); } +template <> inline +StackIface &ActionParam::to(ActionEngineState *state) const { + switch (tag) { + case HEADER_STACK: + return state->phv.get_header_stack(header_stack); + case HEADER_UNION_STACK: + return state->phv.get_header_union_stack(header_union_stack); + default: + assert(0); + } +} + +template <> inline +const StackIface &ActionParam::to( + ActionEngineState *state) const { + return ActionParam::to(state); +} + +template <> inline +HeaderUnion &ActionParam::to(ActionEngineState *state) const { + assert(tag == ActionParam::HEADER_UNION); + return state->phv.get_header_union(header_union); +} + +template <> inline +const HeaderUnion &ActionParam::to( + ActionEngineState *state) const { + return ActionParam::to(state); +} + +template <> inline +HeaderUnionStack &ActionParam::to( + ActionEngineState *state) const { + assert(tag == ActionParam::HEADER_UNION_STACK); + return state->phv.get_header_union_stack(header_union_stack); +} + +template <> inline +const HeaderUnionStack &ActionParam::to( + ActionEngineState *state) const { + return ActionParam::to(state); +} + template <> inline const NamedCalculation &ActionParam::to( ActionEngineState *state) const { @@ -562,6 +614,9 @@ class ActionFn : public NamedP4Object { void parameter_push_back_header_stack(header_stack_id_t header_stack); void parameter_push_back_last_header_stack_field( header_stack_id_t header_stack, int field_offset); + void parameter_push_back_header_union(header_union_id_t header_union); + void parameter_push_back_header_union_stack( + header_union_stack_id_t header_union_stack); void parameter_push_back_const(const Data &data); void parameter_push_back_action_data(int action_data_offset); void parameter_push_back_register_ref(RegisterArray *register_array, diff --git a/include/bm/bm_sim/core/primitives.h b/include/bm/bm_sim/core/primitives.h index d9049a8e9..8358dbea9 100644 --- a/include/bm/bm_sim/core/primitives.h +++ b/include/bm/bm_sim/core/primitives.h @@ -43,6 +43,19 @@ struct assign_header : public ActionPrimitive
{ void operator ()(Header &dst, const Header &src); }; +struct assign_union + : public ActionPrimitive { + void operator ()(HeaderUnion &dst, const HeaderUnion &src); +}; + +struct push : public ActionPrimitive { + void operator ()(StackIface &stack, const Data &num); +}; + +struct pop : public ActionPrimitive { + void operator ()(StackIface &stack, const Data &num); +}; + } // namespace core } // namespace bm diff --git a/include/bm/bm_sim/expressions.h b/include/bm/bm_sim/expressions.h index 6302b5aab..14fbf1ec5 100644 --- a/include/bm/bm_sim/expressions.h +++ b/include/bm/bm_sim/expressions.h @@ -35,20 +35,24 @@ class RegisterSync; enum class ExprOpcode { LOAD_FIELD, LOAD_HEADER, LOAD_HEADER_STACK, LOAD_LAST_HEADER_STACK_FIELD, - LOAD_BOOL, LOAD_CONST, LOAD_LOCAL, + LOAD_UNION, LOAD_UNION_STACK, LOAD_BOOL, LOAD_CONST, LOAD_LOCAL, LOAD_REGISTER_REF, LOAD_REGISTER_GEN, ADD, SUB, MOD, DIV, MUL, SHIFT_LEFT, SHIFT_RIGHT, EQ_DATA, NEQ_DATA, GT_DATA, LT_DATA, GET_DATA, LET_DATA, EQ_HEADER, NEQ_HEADER, + EQ_UNION, NEQ_UNION, EQ_BOOL, NEQ_BOOL, AND, OR, NOT, BIT_AND, BIT_OR, BIT_XOR, BIT_NEG, - VALID_HEADER, + VALID_HEADER, VALID_UNION, TERNARY_OP, SKIP, TWO_COMP_MOD, DATA_TO_BOOL, BOOL_TO_DATA, - DEREFERENCE_STACK, LAST_STACK_INDEX, SIZE_STACK, + DEREFERENCE_HEADER_STACK, + DEREFERENCE_UNION_STACK, + LAST_STACK_INDEX, SIZE_STACK, ACCESS_FIELD, + ACCESS_UNION_HEADER, }; class ExprOpcodesMap { @@ -84,6 +88,10 @@ struct Op { int field_offset; } stack_field; + header_union_id_t header_union; + + header_union_stack_id_t header_union_stack; + bool bool_value; int const_offset; @@ -92,6 +100,8 @@ struct Op { int field_offset; + int header_offset; + // In theory, if registers cannot be resized, I could directly store a // pointer to the correct register cell, i.e. &(*array)[idx]. However, this // gives me more flexibility in case I want to be able to resize the @@ -117,6 +127,9 @@ class Expression { void push_back_load_header_stack(header_stack_id_t header_stack); void push_back_load_last_header_stack_field(header_stack_id_t header_stack, int field_offset); + void push_back_load_header_union(header_union_id_t header_union); + void push_back_load_header_union_stack( + header_union_stack_id_t header_union_stack); void push_back_load_const(const Data &data); void push_back_load_local(const int offset); void push_back_load_register_ref(RegisterArray *register_array, @@ -125,6 +138,7 @@ class Expression { void push_back_op(ExprOpcode opcode); void push_back_ternary_op(const Expression &e1, const Expression &e2); void push_back_access_field(int field_offset); + void push_back_access_union_header(int header_offset); void build(); diff --git a/include/bm/bm_sim/header_stacks.h b/include/bm/bm_sim/header_stacks.h deleted file mode 100644 index f1a8b6020..000000000 --- a/include/bm/bm_sim/header_stacks.h +++ /dev/null @@ -1,209 +0,0 @@ -/* Copyright 2013-present Barefoot Networks, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* - * Antonin Bas (antonin@barefootnetworks.com) - * - */ - -//! @file header_stacks.h - -#ifndef BM_BM_SIM_HEADER_STACKS_H_ -#define BM_BM_SIM_HEADER_STACKS_H_ - -#include -#include -#include -#include - -#include "named_p4object.h" -#include "headers.h" - -namespace bm { - -using header_stack_id_t = p4object_id_t; - -//! HeaderStack is used to reprsent header stacks in P4. The HeaderStack class -//! does not store any header / field data itself, but stores references to -//! the Header instances which constitute the stack, as well as the stack -//! internal state (e.g. number of valid headers in the stack). -//! -//! A HeaderStack reference can be used in an action primitive. For example: -//! @code -//! class pop : public ActionPrimitive { -//! void operator ()(HeaderStack &stack, const Data &num) { -//! stack.pop_front(num.get_uint()); -//! } -//! }; -//! @endcode -class HeaderStack : public NamedP4Object { - public: - friend class PHV; - - public: - HeaderStack(const std::string &name, p4object_id_t id, - const HeaderType &header_type) - : NamedP4Object(name, id), - header_type(header_type) { } - - //! Removes the first element of the stack. Returns the number of elements - //! removed, which is `0` if the stack is empty and `1` otherwise. The second - //! element of the stack becomes the first element, and so on... - size_t pop_front() { - if (next == 0) return 0u; - next--; - for (size_t i = 0; i < next; i++) { - headers[i].get().swap_values(&headers[i + 1].get()); - } - headers[next].get().mark_invalid(); - return 1u; - } - - //! Removes the first \p num element of the stack. Returns the number of - //! elements removed, which is `0` if the stack is empty. Calling this - //! function is more efficient than calling pop_front() multiple times. - size_t pop_front(size_t num) { - if (num == 0) return 0; - size_t popped = std::min(next, num); - next -= popped; - for (size_t i = 0; i < next; i++) { - headers[i].get().swap_values(&headers[i + num].get()); - } - for (size_t i = next; i < next + popped; i++) { - headers[i].get().mark_invalid(); - } - return popped; - } - - //! Pushes an element to the front of the stack. If the stack is already full, - //! the last header of the stack will be discarded. This function returns the - //! number of elements pushed, which is guaranteed to be always 1. The first - //! header of the stack is marked valid (note that it was already valid, if - //! the stack was not empty). - size_t push_front() { - if (next < headers.size()) next++; - for (size_t i = next - 1; i > 0; i--) { - headers[i].get().swap_values(&headers[i - 1].get()); - } - // TODO(antonin): do I want to reset the header as well? - // this may be complicated given the design - headers[0].get().mark_valid(); - return 1u; - } - - //! Pushes \p num elements to the front of the stack. If the stack becomes - //! full, the extra headers at the bottom of the stack will be discarded. This - //! function returns the number of elements pushed, which is guaranteed to be - //! min(get_depth(), \p num)`. All inserted headers are guaranteed to be - //! marked valid. Calling this function is more efficient that calling - //! push_front() multiple times. - size_t push_front(size_t num) { - if (num == 0) return 0; - next = std::min(headers.size(), next + num); - for (size_t i = next - 1; i > num - 1; i--) { - headers[i].get().swap_values(&headers[i - num].get()); - } - size_t pushed = std::min(headers.size(), num); - for (size_t i = 0; i < pushed; i++) { - headers[i].get().mark_valid(); - } - return pushed; - } - - // TODO(antonin): push_front(Header &h) where h is copied ? - - size_t pop_back() { - if (next == 0) return 0u; - next--; - headers[next].get().mark_invalid(); - return 1u; - } - - // basically, meant to be called by the parser - size_t push_back() { - if (next == headers.size()) return 0u; - headers[next].get().mark_valid(); - next++; - return 1u; - } - - //! Returns the maximum capacity of the stack - size_t get_depth() const { return headers.size(); } - - //! Returns the current occupancy of the stack - size_t get_count() const { return next; } - - // TODO(antonin): do we really need those, or will the compiler do loop - // unrolling, in which case we will just have an increase number of parse - // states - - Header &get_last() { - assert(next > 0 && "header stack empty"); - return headers[next - 1]; - } - - const Header &get_last() const { - assert(next > 0 && "header stack empty"); - return headers[next - 1]; - } - - Header &get_next() { - assert(next < headers.size() && "header stack full"); - return headers[next]; - } - - const Header &get_next() const { - assert(next < headers.size() && "header stack full"); - return headers[next]; - } - - //! Returns true if the header stack is full - bool is_full() const { - return (next >= headers.size()); - } - - void reset() { - next = 0; - } - - Header &at(size_t idx) { - return headers.at(idx); - } - - const Header &at(size_t idx) const { - return headers.at(idx); - } - - private: - // To be called by PHV class - // This is a special case, as I want to store a reference - // NOLINTNEXTLINE - void set_next_header(Header &h) { - headers.emplace_back(h); - } - - private: - using HeaderRef = std::reference_wrapper
; - - private: - const HeaderType &header_type; - std::vector headers{}; - // first empty index; if next == headers.size(), stack is full - size_t next{0}; -}; - -} // namespace bm - -#endif // BM_BM_SIM_HEADER_STACKS_H_ diff --git a/include/bm/bm_sim/header_unions.h b/include/bm/bm_sim/header_unions.h new file mode 100644 index 000000000..9c1681685 --- /dev/null +++ b/include/bm/bm_sim/header_unions.h @@ -0,0 +1,123 @@ +/* Copyright 2013-present Barefoot Networks, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Antonin Bas (antonin@barefootnetworks.com) + * + */ + +//! @file header_unions.h + +#ifndef BM_BM_SIM_HEADER_UNIONS_H_ +#define BM_BM_SIM_HEADER_UNIONS_H_ + +#include // std::swap +#include +#include +#include // std::reference_wrapper + +#include "named_p4object.h" +#include "headers.h" + +namespace bm { + +using header_union_id_t = p4object_id_t; + +//! Used to represent P4 header union instances. It stores references to the +//! Header instances included in the union and ensures that at most one of these +//! instances is valid at any given time. +//! +//! A HeaderUnion reference can be used in an action primitive. For example: +//! @code +//! class my_primitive : public ActionPrimitive { +//! void operator ()(HeaderUnion &header_union) { +//! // ... +//! } +//! }; +//! @endcode +class HeaderUnion : public NamedP4Object { + public: + friend class PHV; + + HeaderUnion(const std::string &name, p4object_id_t id) + : NamedP4Object(name, id) { } + + void make_header_valid(size_t new_valid_idx) { + if (valid && valid_header_idx != new_valid_idx) + headers[valid_header_idx].get().mark_invalid(); + valid_header_idx = new_valid_idx; + valid = true; + // the caller is responsible for making the new header valid + } + + void make_header_invalid(size_t idx) { + if (valid && valid_header_idx == idx) valid = false; + } + + // used for stacks of header unions + void swap_values(HeaderUnion *other) { + assert(headers.size() == other->headers.size()); + std::swap(valid, other->valid); + std::swap(valid_header_idx, other->valid_header_idx); + // this is probably too conservative, I think we only need to swap valid + // headers, but this is a good place to start + for (size_t i = 0; i < headers.size(); i++) + headers[i].get().swap_values(&other->headers[i].get()); + } + + //! Returns a pointer to the valid Header instance in the union, or nullptr if + //! no header is valid. + Header *get_valid_header() const { + return valid ? &headers[valid_header_idx].get() : nullptr; + } + + //! Returns the number of headers included in the union. + size_t get_num_headers() const { return headers.size(); } + + //! Access the Header at index \p idx in the union. If the index is invalid, + //! an std::out_of_range exception will be thrown. + Header &at(size_t idx) { return headers.at(idx); } + //! @copydoc at(size_t idx) + const Header &at(size_t idx) const { return headers.at(idx); } + + // compare to another union instance; returns true iff both unions are valid, + // and the valid headers are the same in both unions. + bool cmp(const HeaderUnion &other) const; + + //! Returns true if one of the headers in the union is valid. + bool is_valid() const { return valid; } + + // no-ops on purpose + void mark_invalid() { } + void mark_valid() { } + + private: + // To be called by PHV class + // This is a special case, as I want to store a reference + // NOLINTNEXTLINE + void set_next_header(Header &h) { + headers.emplace_back(h); + } + + using HeaderRef = std::reference_wrapper
; + + std::vector headers{}; + bool valid{false}; + size_t valid_header_idx{0}; +}; + +} // namespace bm + +#endif // BM_BM_SIM_HEADER_UNIONS_H_ diff --git a/include/bm/bm_sim/headers.h b/include/bm/bm_sim/headers.h index 712eb8f31..dfbbf6aa2 100644 --- a/include/bm/bm_sim/headers.h +++ b/include/bm/bm_sim/headers.h @@ -38,6 +38,8 @@ using header_type_id_t = p4object_id_t; class VLHeaderExpression; class ArithExpression; +class HeaderUnion; + class HeaderType : public NamedP4Object { public: // do not specify custome values for enum entries, the value is used directly @@ -285,7 +287,20 @@ class Header : public NamedP4Object { template void extract_VL_common(const char *data, const Fn &VL_fn); + // called by the PHV class + void set_union_membership(HeaderUnion *header_union, size_t idx); + private: + struct UnionMembership { + UnionMembership(HeaderUnion *header_union, size_t idx); + + void make_valid(); + void make_invalid(); + + HeaderUnion *header_union; + size_t idx; + }; + const HeaderType &header_type; std::vector fields{}; bool valid{false}; @@ -294,6 +309,7 @@ class Header : public NamedP4Object { bool metadata{false}; int nbytes_packet{0}; std::unique_ptr VL_expr; + std::unique_ptr union_membership{nullptr}; #ifdef BMDEBUG_ON const Debugger::PacketId *packet_id{&Debugger::dummy_PacketId}; #endif diff --git a/include/bm/bm_sim/parser.h b/include/bm/bm_sim/parser.h index e0a8e3cff..85a09ec18 100644 --- a/include/bm/bm_sim/parser.h +++ b/include/bm/bm_sim/parser.h @@ -94,6 +94,10 @@ class ParseSwitchKeyBuilder { void push_back_stack_field(header_stack_id_t header_stack, int field_offset, int bitwidth); + void push_back_union_stack_field(header_union_stack_id_t header_union_stack, + size_t header_offset, int field_offset, + int bitwidth); + void push_back_lookahead(int offset, int bitwidth); std::vector get_bitwidths() const; @@ -102,11 +106,16 @@ class ParseSwitchKeyBuilder { private: struct Entry { - enum {FIELD, LOOKAHEAD, STACK_FIELD} tag{}; + enum {FIELD, LOOKAHEAD, STACK_FIELD, UNION_STACK_FIELD} tag{}; // I made sure ParserLookAhead was POD data so that it is easy to use in the // union union { field_t field; + struct { + header_union_stack_id_t header_union_stack; + size_t header_offset; + int offset; + } union_stack_field; ParserLookAhead lookahead; }; @@ -114,6 +123,10 @@ class ParseSwitchKeyBuilder { static Entry make_stack_field(header_stack_id_t header_stack, int offset); + static Entry make_union_stack_field( + header_union_stack_id_t header_union_stack, size_t header_offset, + int offset); + static Entry make_lookahead(int offset, int bitwidth); }; @@ -215,6 +228,8 @@ class ParseState : public NamedP4Object { const ArithExpression &field_length_expr, size_t max_header_bytes); void add_extract_to_stack(header_stack_id_t header_stack); + void add_extract_to_union_stack(header_union_stack_id_t header_union_stack, + size_t header_offset); void add_set_from_field(header_id_t dst_header, int dst_offset, header_id_t src_header, int src_offset); diff --git a/include/bm/bm_sim/phv.h b/include/bm/bm_sim/phv.h index 89fd32205..6ca12d035 100644 --- a/include/bm/bm_sim/phv.h +++ b/include/bm/bm_sim/phv.h @@ -36,7 +36,9 @@ #include "fields.h" #include "headers.h" -#include "header_stacks.h" +#include "header_unions.h" +// #include "header_stacks.h" +#include "stacks.h" #include "named_p4object.h" #include "expressions.h" @@ -89,7 +91,8 @@ class PHV { public: PHV() {} - PHV(size_t num_headers, size_t num_header_stacks); + PHV(size_t num_headers, size_t num_header_stacks, + size_t num_header_unions, size_t num_header_union_stacks); //! Access the Header with id \p header_index, with no bound checking. Header &get_header(header_id_t header_index) { @@ -163,6 +166,32 @@ class PHV { return header_stacks[header_stack_index]; } + //! Access the HeaderUnion with id \p header_union_index, with no bound + //! checking. + HeaderUnion &get_header_union(header_union_id_t header_union_index) { + return header_unions[header_union_index]; + } + + //! @copydoc get_header_union(header_union_id_t header_union_index) + const HeaderUnion &get_header_union( + header_union_id_t header_union_index) const { + return header_unions[header_union_index]; + } + + //! Access the HeaderUnionStack with id \p header_union_stack_index, with no + //! bound checking. + HeaderUnionStack &get_header_union_stack( + header_union_stack_id_t header_union_stack_index) { + return header_union_stacks[header_union_stack_index]; + } + + //! @copydoc get_header_union_stack(header_union_stack_id_t + //! header_union_stack_index) + const HeaderUnionStack &get_header_union_stack( + header_union_stack_id_t header_union_stack_index) const { + return header_union_stacks[header_union_stack_index]; + } + //! Mark all Header instances in the PHV as invalid. void reset(); @@ -282,6 +311,15 @@ class PHV { const HeaderType &header_type, const std::vector &header_ids); + void push_back_header_union(const std::string &header_union_name, + header_union_id_t header_union_index, + const std::vector &header_ids); + + void push_back_header_union_stack( + const std::string &header_union_stack_name, + header_union_stack_id_t header_union_stack_index, + const std::vector &header_union_ids); + // get_field(from) will be equivalent to get_field(to) // 'to' needs to be a valid field name (or a previously inserted alias) // 'from' (the alias) does not need to adhere to the "hdr.f" naming convention @@ -290,10 +328,14 @@ class PHV { private: std::vector
headers{}; std::vector header_stacks{}; + std::vector header_unions{}; + std::vector header_union_stacks{}; HeaderNamesMap headers_map{}; FieldNamesMap fields_map{}; size_t capacity{0}; size_t capacity_stacks{0}; + size_t capacity_unions{0}; + size_t capacity_union_stacks{0}; Debugger::PacketId packet_id; }; @@ -308,7 +350,8 @@ class PHVFactory { HeaderDesc(const std::string &name, const header_id_t index, const HeaderType &header_type, const bool metadata) - : name(name), index(index), header_type(header_type), metadata(metadata) { + : name(name), index(index), header_type(header_type), + metadata(metadata) { for (int offset = 0; offset < header_type.get_num_fields(); offset++) { arith_offsets.insert(offset); } @@ -324,7 +367,29 @@ class PHVFactory { HeaderStackDesc(const std::string &name, const header_stack_id_t index, const HeaderType &header_type, const std::vector &headers) - : name(name), index(index), header_type(header_type), headers(headers) { } + : name(name), index(index), header_type(header_type), + headers(headers) { } + }; + + struct HeaderUnionDesc { + const std::string name; + header_union_id_t index; + std::vector headers; + + HeaderUnionDesc(const std::string &name, const header_union_id_t index, + const std::vector &headers) + : name(name), index(index), headers(headers) { } + }; + + struct HeaderUnionStackDesc { + const std::string name; + header_union_stack_id_t index; + std::vector header_unions; + + HeaderUnionStackDesc(const std::string &name, + const header_union_stack_id_t index, + const std::vector &header_unions) + : name(name), index(index), header_unions(header_unions) { } }; public: @@ -338,6 +403,15 @@ class PHVFactory { const HeaderType &header_type, const std::vector &headers); + void push_back_header_union(const std::string &header_union_name, + const header_stack_id_t header_union_index, + const std::vector &headers); + + void push_back_header_union_stack( + const std::string &header_union_stack_name, + const header_union_stack_id_t header_union_stack_index, + const std::vector &header_unions); + void add_field_alias(const std::string &from, const std::string &to); const HeaderType &get_header_type(header_id_t header_id) const { @@ -362,6 +436,16 @@ class PHVFactory { void enable_all_stack_field_arith(header_stack_id_t header_stack_id); + void enable_union_stack_field_arith( + header_union_stack_id_t header_union_stack_id, size_t header_offset, + int field_offset); + + void enable_all_union_stack_field_arith( + header_union_stack_id_t header_union_stack_id, size_t header_offset); + + void enable_all_union_stack_field_arith( + header_union_stack_id_t header_union_stack_id); + void enable_all_arith(); std::unique_ptr create() const; @@ -369,6 +453,9 @@ class PHVFactory { private: std::map header_descs{}; // sorted by header id std::map header_stack_descs{}; + std::map header_union_descs{}; + std::map + header_union_stack_descs{}; std::map field_aliases{}; // order does not matter std::unordered_set field_names{}; // just for debugging }; diff --git a/include/bm/bm_sim/phv_forward.h b/include/bm/bm_sim/phv_forward.h index 107af2fe4..302d8bba0 100644 --- a/include/bm/bm_sim/phv_forward.h +++ b/include/bm/bm_sim/phv_forward.h @@ -25,6 +25,8 @@ namespace bm { using header_id_t = int; using header_stack_id_t = int; +using header_union_id_t = int; +using header_union_stack_id_t = int; class PHV; class PHVFactory; diff --git a/include/bm/bm_sim/stacks.h b/include/bm/bm_sim/stacks.h new file mode 100644 index 000000000..6ea929bbb --- /dev/null +++ b/include/bm/bm_sim/stacks.h @@ -0,0 +1,167 @@ +/* Copyright 2013-present Barefoot Networks, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Antonin Bas (antonin@barefootnetworks.com) + * + */ + +//! @file stacks.h + +#ifndef BM_BM_SIM_STACKS_H_ +#define BM_BM_SIM_STACKS_H_ + +#include +#include + +#include "headers.h" +#include "header_unions.h" +#include "named_p4object.h" + +namespace bm { + +using header_stack_id_t = p4object_id_t; + +//! Interface to a P4 stack (of headers or header unions). +//! A StackIface reference can be used in an action primitive. For example: +//! @code +//! struct pop : public ActionPrimitive { +//! void operator ()(StackIface &stack, const Data &num) { +//! stack.pop_front(num.get()); +//! }; +//! @endcode +class StackIface { + public: + virtual ~StackIface() { } + + //! Removes the first element of the stack. Returns the number of elements + //! removed, which is `0` if the stack is empty and `1` otherwise. The second + //! element of the stack becomes the first element, and so on... + virtual size_t pop_front() = 0; + + //! Removes the first \p num element of the stack. Returns the number of + //! elements removed, which is `0` if the stack is empty. Calling this + //! function is more efficient than calling pop_front() multiple times. + virtual size_t pop_front(size_t num) = 0; + + //! Pushes an element to the front of the stack. If the stack is already full, + //! the last element of the stack will be discarded. This function returns the + //! number of elements pushed, which is guaranteed to be always 1. The first + //! element of the stack is marked valid (note that it was already valid if + //! the stack was not empty). + virtual size_t push_front() = 0; + + //! Pushes \p num elements to the front of the stack. If the stack becomes + //! full, the extra elements at the bottom of the stack will be + //! discarded. This function returns the number of elements pushed, which is + //! guaranteed to be min(get_depth(), \p num)`. All inserted elements are + //! guaranteed to be marked valid. Calling this function is more efficient + //! that calling push_front() multiple times. + virtual size_t push_front(size_t num) = 0; + + virtual size_t pop_back() = 0; + + // basically, meant to be called by the parser + virtual size_t push_back() = 0; + + //! Returns the maximum capacity of the stack + virtual size_t get_depth() const = 0; + + //! Returns the current occupancy of the stack + virtual size_t get_count() const = 0; + + //! Returns true if the header stack is full + virtual bool is_full() const = 0; + + virtual void reset() = 0; +}; + +//! Stack is used to represent header and union stacks in P4. The Stack class +//! itself does not store any union / header / field data itself, but stores +//! references to the HeaderUnion / Header instances which constitute the stack, +//! as well as the stack internal state (e.g. number of valid headers in the +//! stack). +template +class Stack : public StackIface, public NamedP4Object { + public: + friend class PHV; + + Stack(const std::string &name, p4object_id_t id); + + size_t pop_front() override; + size_t pop_front(size_t num) override; + + size_t push_front() override; + size_t push_front(size_t num) override; + + size_t pop_back() override; + + size_t push_back() override; + + size_t get_depth() const override; + size_t get_count() const override; + bool is_full() const override; + + void reset() override; + + T &get_last(); + const T &get_last() const; + + T &get_next(); + const T &get_next() const; + + T &at(size_t idx); + const T &at(size_t idx) const; + + private: + using TRef = std::reference_wrapper; + + // To be called by PHV class + // This is a special case, as I want to store a reference + // NOLINTNEXTLINE + void set_next_element(T &e); + + std::vector elements{}; + // first empty index; if next == headers.size(), stack is full + size_t next{0}; +}; + +using header_stack_id_t = p4object_id_t; +using header_union_stack_id_t = p4object_id_t; + +//! Convenience alias for stacks of headers +//! A HeaderStack reference can be used in an action primitive. For example: +//! @code +//! struct my_primitive : public ActionPrimitive { +//! void operator ()(HeaderStack &header_stack, const Data &num) { +//! // ... +//! }; +//! @endcode +using HeaderStack = Stack
; +//! Convenience alias for stacks of header unions +//! A HeaderUnionStack reference can be used in an action primitive. For +//! example: +//! @code +//! struct my_primitive +//! : public ActionPrimitive { +//! void operator ()(HeaderUnionStack &header_union_stack, const Data &num) { +//! // ... +//! }; +//! @endcode +using HeaderUnionStack = Stack; + +} // namespace bm + +#endif // BM_BM_SIM_STACKS_H_ diff --git a/src/bm_sim/Makefile.am b/src/bm_sim/Makefile.am index 9d7891b06..9874d6189 100644 --- a/src/bm_sim/Makefile.am +++ b/src/bm_sim/Makefile.am @@ -36,6 +36,7 @@ extern.cpp \ extract.h \ fields.cpp \ headers.cpp \ +header_unions.cpp \ learning.cpp \ lookup_structures.cpp \ logger.cpp \ @@ -59,6 +60,7 @@ stateful.cpp \ switch.cpp \ simple_pre.cpp \ simple_pre_lag.cpp \ +stacks.cpp \ tables.cpp \ target_parser.cpp \ transport.cpp \ diff --git a/src/bm_sim/P4Objects.cpp b/src/bm_sim/P4Objects.cpp index 76b328891..630306daa 100644 --- a/src/bm_sim/P4Objects.cpp +++ b/src/bm_sim/P4Objects.cpp @@ -104,7 +104,9 @@ using EFormat = ExceptionFormatter; } // namespace -enum class P4Objects::ExprType { UNKNOWN, DATA, HEADER, HEADER_STACK, BOOL }; +enum class P4Objects::ExprType { + UNKNOWN, DATA, HEADER, HEADER_STACK, BOOL, UNION, UNION_STACK +}; void P4Objects::build_expression(const Json::Value &json_expression, @@ -125,6 +127,8 @@ ExprOpcode get_eq_opcode(ExprType expr_type) { return ExprOpcode::EQ_HEADER; case ExprType::BOOL: return ExprOpcode::EQ_BOOL; + case ExprType::UNION: + return ExprOpcode::EQ_UNION; default: break; } @@ -140,6 +144,8 @@ ExprOpcode get_neq_opcode(ExprType expr_type) { return ExprOpcode::NEQ_HEADER; case ExprType::BOOL: return ExprOpcode::NEQ_BOOL; + case ExprType::UNION: + return ExprOpcode::NEQ_UNION; default: break; } @@ -182,19 +188,28 @@ ExprType get_opcode_type(ExprOpcode opcode) { case ExprOpcode::LET_DATA: case ExprOpcode::EQ_HEADER: case ExprOpcode::NEQ_HEADER: + case ExprOpcode::EQ_UNION: + case ExprOpcode::NEQ_UNION: case ExprOpcode::EQ_BOOL: case ExprOpcode::NEQ_BOOL: case ExprOpcode::AND: case ExprOpcode::OR: case ExprOpcode::NOT: case ExprOpcode::VALID_HEADER: + case ExprOpcode::VALID_UNION: case ExprOpcode::DATA_TO_BOOL: return ExprType::BOOL; case ExprOpcode::LOAD_HEADER: - case ExprOpcode::DEREFERENCE_STACK: + case ExprOpcode::DEREFERENCE_HEADER_STACK: + case ExprOpcode::ACCESS_UNION_HEADER: return ExprType::HEADER; case ExprOpcode::LOAD_HEADER_STACK: return ExprType::HEADER_STACK; + case ExprOpcode::LOAD_UNION: + case ExprOpcode::DEREFERENCE_UNION_STACK: + return ExprType::UNION; + case ExprOpcode::LOAD_UNION_STACK: + return ExprType::UNION_STACK; case ExprOpcode::TERNARY_OP: return ExprType::UNKNOWN; case ExprOpcode::SKIP: @@ -309,6 +324,17 @@ P4Objects::build_expression(const Json::Value &json_expression, *expr_type = ExprType::DATA; phv_factory.enable_stack_field_arith(header_stack_id, field_offset); + } else if (type == "header_union") { + auto header_union_id = get_header_union_id(json_value.asString()); + expr->push_back_load_header_union(header_union_id); + *expr_type = ExprType::UNION; + } else if (type == "header_union_stack") { + auto header_union_stack_id = get_header_union_stack_id( + json_value.asString()); + expr->push_back_load_header_union_stack(header_union_stack_id); + *expr_type = ExprType::UNION_STACK; + + phv_factory.enable_all_union_stack_field_arith(header_union_stack_id); } else { throw json_exception( EFormat() << "Invalid 'type' in expression: '" << type << "'", @@ -471,10 +497,45 @@ struct DirectMeterArray { int offset; }; +class HeaderUnionType : public NamedP4Object { + public: + HeaderUnionType(const std::string &name, p4object_id_t id) + : NamedP4Object(name, id) { } + + struct EntryInfo { + std::string name; + HeaderType *header_type; + }; + + size_t get_header_offset(const std::string &name) { + for (size_t i = 0; i < entries.size(); i++) { + if (name == entries.at(i).name) return i; + } + throw json_exception(EFormat() << "Unknown field name '" << name + << "' in union type '" << get_name() << "'"); + } + + HeaderType *get_header_type(const std::string &header_name) { + auto header_offset = get_header_offset(header_name); + return entries.at(header_offset).header_type; + } + + void push_back(const std::string &name, HeaderType *header_type) { + entries.push_back({name, header_type}); + } + + private: + std::vector entries{}; +}; + } // namespace struct P4Objects::InitState { std::unordered_map direct_meters{}; + std::unordered_map header_union_types_map{}; + std::unordered_map header_union_to_type_map{}; + std::unordered_map + header_union_stack_to_type_map{}; }; void @@ -579,6 +640,84 @@ P4Objects::init_header_stacks(const Json::Value &cfg_root) { } } +void +P4Objects::init_header_unions(const Json::Value &cfg_root, + InitState *init_state) { + auto &header_union_types_map = init_state->header_union_types_map; + auto &header_union_to_type_map = init_state->header_union_to_type_map; + + const auto &cfg_header_union_types = cfg_root["header_union_types"]; + + for (const auto &cfg_header_union_type : cfg_header_union_types) { + auto header_union_type_name = cfg_header_union_type["name"].asString(); + p4object_id_t header_union_type_id = cfg_header_union_type["id"].asInt(); + HeaderUnionType header_union_type(header_union_type_name, + header_union_type_id); + + for (const auto &cfg_header : cfg_header_union_type["headers"]) { + auto name = cfg_header[0].asString(); + auto type_name = cfg_header[1].asString(); + header_union_type.push_back(name, get_header_type(type_name)); + } + header_union_types_map.emplace(header_union_type_name, header_union_type); + } + + const auto &cfg_header_unions = cfg_root["header_unions"]; + + for (const auto &cfg_header_union : cfg_header_unions) { + auto header_union_name = cfg_header_union["name"].asString(); + header_union_id_t header_union_id = cfg_header_union["id"].asInt(); + auto header_union_type_name = cfg_header_union["union_type"].asString(); + auto &header_union_type = header_union_types_map.at(header_union_type_name); + + std::vector header_ids; + for (const auto &cfg_header_id : cfg_header_union["header_ids"]) { + header_id_to_union_pos.emplace( + cfg_header_id.asInt(), + HeaderUnionPos({header_union_id, header_ids.size()})); + header_ids.push_back(cfg_header_id.asInt()); + } + + phv_factory.push_back_header_union(header_union_name, header_union_id, + header_ids); + add_header_union_id(header_union_name, header_union_id); + header_union_to_type_map.emplace(header_union_name, &header_union_type); + } +} + +void +P4Objects::init_header_union_stacks(const Json::Value &cfg_root, + InitState *init_state) { + auto &header_union_types_map = init_state->header_union_types_map; + auto &header_union_stack_to_type_map = + init_state->header_union_stack_to_type_map; + + const auto &cfg_header_union_stacks = cfg_root["header_union_stacks"]; + + for (const auto &cfg_header_union_stack : cfg_header_union_stacks) { + auto header_union_stack_name = cfg_header_union_stack["name"].asString(); + header_union_stack_id_t header_union_stack_id = + cfg_header_union_stack["id"].asInt(); + auto header_union_type_name = + cfg_header_union_stack["union_type"].asString(); + auto &header_union_type = header_union_types_map.at(header_union_type_name); + + std::vector header_union_ids; + auto &cfg_header_unions = cfg_header_union_stack["header_union_ids"]; + for (const auto &cfg_header_union_id : cfg_header_unions) { + header_union_ids.push_back(cfg_header_union_id.asInt()); + union_id_to_union_stack_id[cfg_header_union_id.asInt()] = + header_union_stack_id; + } + + phv_factory.push_back_header_union_stack( + header_union_stack_name, header_union_stack_id, header_union_ids); + add_header_union_stack_id(header_union_stack_name, header_union_stack_id); + header_union_stack_to_type_map.emplace(header_union_stack_name, + &header_union_type); + } +} + void P4Objects::init_extern_instances(const Json::Value &cfg_root) { const Json::Value &cfg_extern_instances = cfg_root["extern_instances"]; @@ -672,8 +811,11 @@ P4Objects::init_errors(const Json::Value &cfg_root) { } void -P4Objects::init_parsers(const Json::Value &cfg_root) { - const Json::Value &cfg_parsers = cfg_root["parsers"]; +P4Objects::init_parsers(const Json::Value &cfg_root, InitState *init_state) { + auto &header_union_stack_to_type_map = + init_state->header_union_stack_to_type_map; + + const auto &cfg_parsers = cfg_root["parsers"]; for (const auto &cfg_parser : cfg_parsers) { const string parser_name = cfg_parser["name"].asString(); p4object_id_t parser_id = cfg_parser["id"].asInt(); @@ -701,8 +843,8 @@ P4Objects::init_parsers(const Json::Value &cfg_root) { assert(cfg_parameters.size() == 1); const Json::Value &cfg_extract = cfg_parameters[0]; const string extract_type = cfg_extract["type"].asString(); - const string extract_header = cfg_extract["value"].asString(); if (extract_type == "regular") { + const string extract_header = cfg_extract["value"].asString(); header_id_t header_id = get_header_id(extract_header); const auto &header_type = phv_factory.get_header_type(header_id); if (header_type.is_VL_header() && !header_type.has_VL_expr()) { @@ -714,9 +856,22 @@ P4Objects::init_parsers(const Json::Value &cfg_root) { } parse_state->add_extract(header_id); } else if (extract_type == "stack") { + const string extract_header = cfg_extract["value"].asString(); header_stack_id_t header_stack_id = get_header_stack_id(extract_header); parse_state->add_extract_to_stack(header_stack_id); + } else if (extract_type == "union_stack") { + const auto &cfg_union_stack = cfg_extract["value"]; + assert(cfg_union_stack.size() == 2); + auto extract_union_stack = cfg_union_stack[0].asString(); + auto header_union_stack_id = get_header_union_stack_id( + extract_union_stack); + auto *union_type = header_union_stack_to_type_map.at( + extract_union_stack); + auto header_offset = union_type->get_header_offset( + cfg_union_stack[1].asString()); + parse_state->add_extract_to_union_stack( + header_union_stack_id, header_offset); } else { throw json_exception( EFormat() << "Extract type '" << extract_type @@ -834,12 +989,28 @@ P4Objects::init_parsers(const Json::Value &cfg_root) { int bitwidth = header_type->get_bit_width(field_offset); key_builder.push_back_stack_field(header_stack_id, field_offset, bitwidth); + } else if (type == "union_stack_field") { + auto union_stack_name = cfg_value[0].asString(); + auto header_union_stack_id = get_header_union_stack_id( + union_stack_name); + auto *union_type = header_union_stack_to_type_map.at( + union_stack_name); + auto header_offset = union_type->get_header_offset( + cfg_value[1].asString()); + auto header_type = union_type->get_header_type( + cfg_value[1].asString()); + auto field_offset = header_type->get_field_offset( + cfg_value[2].asString()); + auto bitwidth = header_type->get_bit_width(field_offset); + key_builder.push_back_union_stack_field( + header_union_stack_id, header_offset, field_offset, bitwidth); } else if (type == "lookahead") { int offset = cfg_value[0].asInt(); int bitwidth = cfg_value[1].asInt(); key_builder.push_back_lookahead(offset, bitwidth); } else { - assert(0 && "invalid entry in parse state key"); + throw json_exception("Invalid entry in parse state key", + cfg_key_elem); } } @@ -1669,6 +1840,10 @@ P4Objects::init_objects(std::istream *is, init_header_stacks(cfg_root); + init_header_unions(cfg_root, &init_state); + + init_header_union_stacks(cfg_root, &init_state); + if (cfg_root.isMember("field_aliases")) { const auto &cfg_field_aliases = cfg_root["field_aliases"]; @@ -1691,7 +1866,7 @@ P4Objects::init_objects(std::istream *is, init_errors(cfg_root); // parser errors - init_parsers(cfg_root); + init_parsers(cfg_root, &init_state); init_deparsers(cfg_root); @@ -1929,22 +2104,50 @@ P4Objects::get_primitive(const std::string &name) { void P4Objects::enable_arith(header_id_t header_id, int field_offset) { - auto it = header_id_to_stack_id.find(header_id); - if (it == header_id_to_stack_id.end()) { - phv_factory.enable_field_arith(header_id, field_offset); - } else { - phv_factory.enable_stack_field_arith(it->second, field_offset); + { + auto it = header_id_to_union_pos.find(header_id); + if (it != header_id_to_union_pos.end()) { + const auto &union_pos = it->second; + auto it2 = union_id_to_union_stack_id.find(union_pos.union_id); + if (it2 != union_id_to_union_stack_id.end()) { + phv_factory.enable_union_stack_field_arith( + it2->second, union_pos.offset, field_offset); + } + return; + } } + { + auto it = header_id_to_stack_id.find(header_id); + if (it != header_id_to_stack_id.end()) { + phv_factory.enable_stack_field_arith(it->second, field_offset); + return; + } + } + phv_factory.enable_field_arith(header_id, field_offset); } void P4Objects::enable_arith(header_id_t header_id) { - auto it = header_id_to_stack_id.find(header_id); - if (it == header_id_to_stack_id.end()) { - phv_factory.enable_all_field_arith(header_id); - } else { - phv_factory.enable_all_stack_field_arith(it->second); + { + auto it = header_id_to_union_pos.find(header_id); + if (it != header_id_to_union_pos.end()) { + const auto &union_pos = it->second; + auto it2 = union_id_to_union_stack_id.find(union_pos.union_id); + if (it2 != union_id_to_union_stack_id.end()) { + phv_factory.enable_all_union_stack_field_arith( + it2->second, union_pos.offset); + } + return; + } + } + { + auto it = header_id_to_stack_id.find(header_id); + if (it != header_id_to_stack_id.end()) { + phv_factory.enable_all_stack_field_arith(it->second); + return; + } } + phv_factory.enable_all_field_arith(header_id); } ActionFn * @@ -2050,6 +2253,18 @@ P4Objects::add_header_stack_id(const std::string &name, header_stack_ids_map[name] = header_stack_id; } +void +P4Objects::add_header_union_id(const std::string &name, + header_union_id_t header_union_id) { + header_union_ids_map[name] = header_union_id; +} + +void +P4Objects::add_header_union_stack_id( + const std::string &name, header_union_stack_id_t header_union_stack_id) { + header_union_stack_ids_map[name] = header_union_stack_id; +} + header_id_t P4Objects::get_header_id(const std::string &name) const { return header_ids_map.at(name); @@ -2060,6 +2275,16 @@ P4Objects::get_header_stack_id(const std::string &name) const { return header_stack_ids_map.at(name); } +header_union_id_t +P4Objects::get_header_union_id(const std::string &name) const { + return header_union_ids_map.at(name); +} + +header_union_stack_id_t +P4Objects::get_header_union_stack_id(const std::string &name) const { + return header_union_stack_ids_map.at(name); +} + void P4Objects::add_action(p4object_id_t id, std::unique_ptr action) { actions_map[id] = std::move(action); diff --git a/src/bm_sim/actions.cpp b/src/bm_sim/actions.cpp index 93cbfcaa3..a6adf7dc7 100644 --- a/src/bm_sim/actions.cpp +++ b/src/bm_sim/actions.cpp @@ -75,6 +75,23 @@ ActionFn::parameter_push_back_last_header_stack_field( params.push_back(param); } +void +ActionFn::parameter_push_back_header_union(header_union_id_t header_union) { + ActionParam param; + param.tag = ActionParam::HEADER_UNION; + param.header_union = header_union; + params.push_back(param); +} + +void +ActionFn::parameter_push_back_header_union_stack( + header_union_stack_id_t header_union_stack) { + ActionParam param; + param.tag = ActionParam::HEADER_UNION_STACK; + param.header_union_stack = header_union_stack; + params.push_back(param); +} + void ActionFn::parameter_push_back_const(const Data &data) { const_values.push_back(data); diff --git a/src/bm_sim/core/primitives.cpp b/src/bm_sim/core/primitives.cpp index e0a12c77e..77c78ece3 100644 --- a/src/bm_sim/core/primitives.cpp +++ b/src/bm_sim/core/primitives.cpp @@ -30,6 +30,12 @@ REGISTER_PRIMITIVE(assign_VL); REGISTER_PRIMITIVE(assign_header); +REGISTER_PRIMITIVE(assign_union); + +REGISTER_PRIMITIVE(push); + +REGISTER_PRIMITIVE(pop); + void assign_header::operator ()(Header &dst, const Header &src) { if (!src.is_valid()) { @@ -46,6 +52,24 @@ assign_header::operator ()(Header &dst, const Header &src) { } } +void +assign_union::operator ()(HeaderUnion &dst, const HeaderUnion &src) { + assert(dst.get_num_headers() == src.get_num_headers()); + // naive implementation which iterates over all headers in the union + for (size_t i = 0; i < dst.get_num_headers(); i++) + assign_header()(dst.at(i), src.at(i)); +} + +void +push::operator ()(StackIface &stack, const Data &num) { + stack.push_front(num.get()); +} + +void +pop::operator ()(StackIface &stack, const Data &num) { + stack.pop_front(num.get()); +} + // dummy method to prevent the removal of global variables (generated by // REGISTER_PRIMITIVE) by the linker; we call it in the ActionOpcodesMap // constructor in actions.cpp diff --git a/src/bm_sim/expressions.cpp b/src/bm_sim/expressions.cpp index ebeace931..0eebac81e 100644 --- a/src/bm_sim/expressions.cpp +++ b/src/bm_sim/expressions.cpp @@ -19,7 +19,7 @@ */ #include -#include +#include #include #include @@ -38,6 +38,8 @@ ExprOpcodesMap::ExprOpcodesMap() { {"load_header", ExprOpcode::LOAD_HEADER}, {"load_header_stack", ExprOpcode::LOAD_HEADER_STACK}, {"load_last_header_stack_field", ExprOpcode::LOAD_LAST_HEADER_STACK_FIELD}, + {"load_union", ExprOpcode::LOAD_UNION}, + {"load_union_stack", ExprOpcode::LOAD_UNION_STACK}, {"load_bool", ExprOpcode::LOAD_BOOL}, {"load_const", ExprOpcode::LOAD_CONST}, {"load_local", ExprOpcode::LOAD_LOCAL}, @@ -54,6 +56,8 @@ ExprOpcodesMap::ExprOpcodesMap() { {"!=", ExprOpcode::NEQ_DATA}, {"==h", ExprOpcode::EQ_HEADER}, {"!=h", ExprOpcode::NEQ_HEADER}, + {"==u", ExprOpcode::EQ_UNION}, + {"!=u", ExprOpcode::NEQ_UNION}, {"==b", ExprOpcode::EQ_BOOL}, {"!=b", ExprOpcode::NEQ_BOOL}, {">", ExprOpcode::GT_DATA}, @@ -68,14 +72,20 @@ ExprOpcodesMap::ExprOpcodesMap() { {"^", ExprOpcode::BIT_XOR}, {"~", ExprOpcode::BIT_NEG}, {"valid", ExprOpcode::VALID_HEADER}, + {"valid_union", ExprOpcode::VALID_UNION}, {"?", ExprOpcode::TERNARY_OP}, {"two_comp_mod", ExprOpcode::TWO_COMP_MOD}, {"d2b", ExprOpcode::DATA_TO_BOOL}, {"b2d", ExprOpcode::BOOL_TO_DATA}, - {"dereference_stack", ExprOpcode::DEREFERENCE_STACK}, + // backward-compatibility + // dereference_stack and dereference_header_stack are equivalent + {"dereference_stack", ExprOpcode::DEREFERENCE_HEADER_STACK}, + {"dereference_header_stack", ExprOpcode::DEREFERENCE_HEADER_STACK}, + {"dereference_union_stack", ExprOpcode::DEREFERENCE_UNION_STACK}, {"last_stack_index", ExprOpcode::LAST_STACK_INDEX}, {"size_stack", ExprOpcode::SIZE_STACK}, {"access_field", ExprOpcode::ACCESS_FIELD}, + {"access_union_header", ExprOpcode::ACCESS_UNION_HEADER}, }; } @@ -141,6 +151,23 @@ Expression::push_back_load_last_header_stack_field( ops.push_back(op); } +void +Expression::push_back_load_header_union(header_union_id_t header_union) { + Op op; + op.opcode = ExprOpcode::LOAD_UNION; + op.header_union = header_union; + ops.push_back(op); +} + +void +Expression::push_back_load_header_union_stack( + header_union_stack_id_t header_union_stack) { + Op op; + op.opcode = ExprOpcode::LOAD_UNION_STACK; + op.header_union_stack = header_union_stack; + ops.push_back(op); +} + void Expression::push_back_load_const(const Data &data) { const_values.push_back(data); @@ -234,6 +261,14 @@ Expression::push_back_access_field(int field_offset) { ops.push_back(op); } +void +Expression::push_back_access_union_header(int header_offset) { + Op op; + op.opcode = ExprOpcode::ACCESS_UNION_HEADER; + op.header_offset = header_offset; + ops.push_back(op); +} + void Expression::build() { data_registers_cnt = assign_dest_registers(); @@ -305,13 +340,20 @@ Expression::eval_(const PHV &phv, ExprType expr_type, header_temps_stack.clear(); // header_temps_stack.reserve(4); - static thread_local std::vector hs_temps_stack; - hs_temps_stack.clear(); - // hs_temps_stack.reserve(2); + static thread_local std::vector stack_temps_stack; + stack_temps_stack.clear(); + // stack_temps_stack.reserve(2); + + static thread_local std::vector union_temps_stack; + union_temps_stack.clear(); + // union_temps_stack.reserve(2); bool lb, rb; const Data *ld, *rd; const Header *lh, *rh; + const HeaderUnion *lu, *ru; + const HeaderStack *hs; + const HeaderUnionStack *hus; for (size_t i = 0; i < ops.size(); i++) { const auto &op = ops[i]; @@ -326,7 +368,7 @@ Expression::eval_(const PHV &phv, ExprType expr_type, break; case ExprOpcode::LOAD_HEADER_STACK: - hs_temps_stack.push_back(&(phv.get_header_stack(op.header_stack))); + stack_temps_stack.push_back(&(phv.get_header_stack(op.header_stack))); break; case ExprOpcode::LOAD_LAST_HEADER_STACK_FIELD: @@ -335,6 +377,15 @@ Expression::eval_(const PHV &phv, ExprType expr_type, .get_field(op.stack_field.field_offset))); break; + case ExprOpcode::LOAD_UNION: + union_temps_stack.push_back(&(phv.get_header_union(op.header_union))); + break; + + case ExprOpcode::LOAD_UNION_STACK: + stack_temps_stack.push_back( + &(phv.get_header_union_stack(op.header_union_stack))); + break; + case ExprOpcode::LOAD_BOOL: bool_temps_stack.push_back(op.bool_value); break; @@ -363,6 +414,11 @@ Expression::eval_(const PHV &phv, ExprType expr_type, header_temps_stack.pop_back(); break; + case ExprOpcode::ACCESS_UNION_HEADER: + ru = union_temps_stack.back(); union_temps_stack.pop_back(); + header_temps_stack.push_back(&ru->at(op.header_offset)); + break; + case ExprOpcode::ADD: rd = data_temps_stack.back(); data_temps_stack.pop_back(); ld = data_temps_stack.back(); data_temps_stack.pop_back(); @@ -460,6 +516,18 @@ Expression::eval_(const PHV &phv, ExprType expr_type, bool_temps_stack.push_back(!lh->cmp(*rh)); break; + case ExprOpcode::EQ_UNION: + ru = union_temps_stack.back(); union_temps_stack.pop_back(); + lu = union_temps_stack.back(); union_temps_stack.pop_back(); + bool_temps_stack.push_back(lu->cmp(*ru)); + break; + + case ExprOpcode::NEQ_UNION: + ru = union_temps_stack.back(); union_temps_stack.pop_back(); + lu = union_temps_stack.back(); union_temps_stack.pop_back(); + bool_temps_stack.push_back(!lu->cmp(*ru)); + break; + case ExprOpcode::EQ_BOOL: rb = bool_temps_stack.back(); bool_temps_stack.pop_back(); lb = bool_temps_stack.back(); bool_temps_stack.pop_back(); @@ -521,6 +589,11 @@ Expression::eval_(const PHV &phv, ExprType expr_type, header_temps_stack.pop_back(); break; + case ExprOpcode::VALID_UNION: + bool_temps_stack.push_back(union_temps_stack.back()->is_valid()); + union_temps_stack.pop_back(); + break; + case ExprOpcode::TERNARY_OP: if (bool_temps_stack.back()) i += 1; @@ -549,28 +622,36 @@ Expression::eval_(const PHV &phv, ExprType expr_type, data_temps_stack.push_back(&data_temps[op.data_dest_index]); break; - case ExprOpcode::DEREFERENCE_STACK: + case ExprOpcode::DEREFERENCE_HEADER_STACK: rd = data_temps_stack.back(); data_temps_stack.pop_back(); - header_temps_stack.push_back( - &hs_temps_stack.back()->at(rd->get())); - hs_temps_stack.pop_back(); + hs = static_cast(stack_temps_stack.back()); + header_temps_stack.push_back(&hs->at(rd->get())); + stack_temps_stack.pop_back(); break; // LAST_STACK_INDEX seems a little redundant given SIZE_STACK, but I don't // exclude in the future to do some sanity checking for LAST_STACK_INDEX case ExprOpcode::LAST_STACK_INDEX: data_temps[op.data_dest_index].set( - hs_temps_stack.back()->get_count() - 1); - hs_temps_stack.pop_back(); + stack_temps_stack.back()->get_count() - 1); + stack_temps_stack.pop_back(); data_temps_stack.push_back(&data_temps[op.data_dest_index]); break; case ExprOpcode::SIZE_STACK: - data_temps[op.data_dest_index].set(hs_temps_stack.back()->get_count()); - hs_temps_stack.pop_back(); + data_temps[op.data_dest_index].set( + stack_temps_stack.back()->get_count()); + stack_temps_stack.pop_back(); data_temps_stack.push_back(&data_temps[op.data_dest_index]); break; + case ExprOpcode::DEREFERENCE_UNION_STACK: + rd = data_temps_stack.back(); data_temps_stack.pop_back(); + hus = static_cast(stack_temps_stack.back()); + union_temps_stack.push_back(&hus->at(rd->get())); + stack_temps_stack.pop_back(); + break; + default: assert(0 && "invalid operand"); break; @@ -671,7 +752,8 @@ Expression::assign_dest_registers() { break; case ExprOpcode::DATA_TO_BOOL: - case ExprOpcode::DEREFERENCE_STACK: + case ExprOpcode::DEREFERENCE_HEADER_STACK: + case ExprOpcode::DEREFERENCE_UNION_STACK: registers_curr -= new_registers.top(); new_registers.pop(); break; diff --git a/src/bm_sim/header_unions.cpp b/src/bm_sim/header_unions.cpp new file mode 100644 index 000000000..ee3d2e915 --- /dev/null +++ b/src/bm_sim/header_unions.cpp @@ -0,0 +1,33 @@ +/* Copyright 2013-present Barefoot Networks, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Antonin Bas (antonin@barefootnetworks.com) + * + */ + +#include +#include + +namespace bm { + +bool +HeaderUnion::cmp(const HeaderUnion &other) const { + return valid && other.valid && + (valid_header_idx == other.valid_header_idx) && + headers[valid_header_idx].get().cmp(other.headers[valid_header_idx]); +} + +} // namespace bm diff --git a/src/bm_sim/headers.cpp b/src/bm_sim/headers.cpp index 1eeecc77a..3578fcbcc 100644 --- a/src/bm_sim/headers.cpp +++ b/src/bm_sim/headers.cpp @@ -19,6 +19,7 @@ */ #include +#include #include #include @@ -133,7 +134,7 @@ Header::Header(const std::string &name, p4object_id_t id, const HeaderType &header_type, const std::set &arith_offsets, const bool metadata) - : NamedP4Object(name, id), header_type(header_type), metadata(metadata) { + : NamedP4Object(name, id), header_type(header_type), metadata(metadata) { // header_type_id = header_type.get_type_id(); for (int i = 0; i < header_type.get_num_fields(); i++) { const auto &finfo = header_type.get_finfo(i); @@ -180,12 +181,14 @@ void Header::mark_valid() { valid = true; valid_field->set(1); + if (union_membership) union_membership->make_valid(); } void Header::mark_invalid() { valid = false; valid_field->set(0); + if (union_membership) union_membership->make_invalid(); } void @@ -317,4 +320,22 @@ Header::cmp(const Header &other) const { (fields == other.fields); } +Header::UnionMembership::UnionMembership(HeaderUnion *header_union, size_t idx) + : header_union(header_union), idx(idx) { } + +void +Header::UnionMembership::make_valid() { + header_union->make_header_valid(idx); +} + +void +Header::UnionMembership::make_invalid() { + header_union->make_header_invalid(idx); +} + +void +Header::set_union_membership(HeaderUnion *header_union, size_t idx) { + union_membership.reset(new UnionMembership(header_union, idx)); +} + } // namespace bm diff --git a/src/bm_sim/parser.cpp b/src/bm_sim/parser.cpp index 4f0e68d4f..769bd4e3f 100644 --- a/src/bm_sim/parser.cpp +++ b/src/bm_sim/parser.cpp @@ -150,6 +150,18 @@ ParseSwitchKeyBuilder::Entry::make_stack_field(header_stack_id_t header_stack, return e; } +ParseSwitchKeyBuilder::Entry +ParseSwitchKeyBuilder::Entry::make_union_stack_field( + header_union_stack_id_t header_union_stack, size_t header_offset, + int offset) { + Entry e; + e.tag = UNION_STACK_FIELD; + e.union_stack_field.header_union_stack = header_union_stack; + e.union_stack_field.header_offset = header_offset; + e.union_stack_field.offset = offset; + return e; +} + ParseSwitchKeyBuilder::Entry ParseSwitchKeyBuilder::Entry::make_lookahead(int offset, int bitwidth) { Entry e; @@ -172,6 +184,15 @@ ParseSwitchKeyBuilder::push_back_stack_field(header_stack_id_t header_stack, bitwidths.push_back(bitwidth); } +void +ParseSwitchKeyBuilder::push_back_union_stack_field( + header_union_stack_id_t header_union_stack, size_t header_offset, + int field_offset, int bitwidth) { + entries.push_back(Entry::make_union_stack_field( + header_union_stack, header_offset, field_offset)); + bitwidths.push_back(bitwidth); +} + void ParseSwitchKeyBuilder::push_back_lookahead(int offset, int bitwidth) { entries.push_back(Entry::make_lookahead(offset, bitwidth)); @@ -198,6 +219,13 @@ ParseSwitchKeyBuilder::operator()(const PHV &phv, const char *data, case Entry::LOOKAHEAD: e.lookahead.peek(data, key); break; + case Entry::UNION_STACK_FIELD: + key->append( + phv.get_header_union_stack( + e.union_stack_field.header_union_stack).get_last() + .at(e.union_stack_field.header_offset) + .get_field(e.union_stack_field.offset).get_bytes()); + break; } } } @@ -282,7 +310,7 @@ struct ParserOpExtractStack : ParserOp { header_stack_id_t header_stack; explicit ParserOpExtractStack(header_stack_id_t header_stack) - : header_stack(header_stack) {} + : header_stack(header_stack) { } void operator()(Packet *pkt, const char *data, size_t *bytes_parsed) const override { @@ -302,6 +330,33 @@ struct ParserOpExtractStack : ParserOp { } }; +struct ParserOpExtractUnionStack : ParserOp { + header_union_stack_id_t header_union_stack; + size_t header_offset; + + explicit ParserOpExtractUnionStack(header_union_stack_id_t header_union_stack, + size_t header_offset) + : header_union_stack(header_union_stack), header_offset(header_offset) { } + + void operator()(Packet *pkt, const char *data, + size_t *bytes_parsed) const override { + auto phv = pkt->get_phv(); + auto &union_stack = phv->get_header_union_stack(header_union_stack); + if (union_stack.is_full()) + throw parser_exception_core(ErrorCodeMap::Core::StackOutOfBounds); + auto &next_union = union_stack.get_next(); + auto &next_hdr = next_union.at(header_offset); + BMELOG(parser_extract, *pkt, next_hdr.get_id()); + BMLOG_DEBUG_PKT(*pkt, + "Extracting to header union stack {}, next header is {}", + header_union_stack, next_hdr.get_id()); + check_enough_data_for_extract(*pkt, *bytes_parsed, next_hdr); + next_hdr.extract(data, *phv); + *bytes_parsed += next_hdr.get_nbytes_packet(); + union_stack.push_back(); + } +}; + struct ParserOpVerify : ParserOp { BoolExpression condition; ArithExpression error_expr; @@ -703,6 +758,13 @@ ParseState::add_extract_to_stack(header_stack_id_t header_stack) { parser_ops.emplace_back(new ParserOpExtractStack(header_stack)); } +void +ParseState::add_extract_to_union_stack( + header_union_stack_id_t header_union_stack, size_t header_offset) { + parser_ops.emplace_back(new ParserOpExtractUnionStack( + header_union_stack, header_offset)); +} + void ParseState::add_set_from_field(header_id_t dst_header, int dst_offset, header_id_t src_header, int src_offset) { diff --git a/src/bm_sim/phv.cpp b/src/bm_sim/phv.cpp index 5db0f451c..e2dcc1777 100644 --- a/src/bm_sim/phv.cpp +++ b/src/bm_sim/phv.cpp @@ -27,11 +27,16 @@ namespace bm { -PHV::PHV(size_t num_headers, size_t num_header_stacks) - : capacity(num_headers), capacity_stacks(num_header_stacks) { +PHV::PHV(size_t num_headers, size_t num_header_stacks, + size_t num_header_unions, size_t num_header_union_stacks) + : capacity(num_headers), capacity_stacks(num_header_stacks), + capacity_unions(num_header_unions), + capacity_union_stacks(num_header_union_stacks) { // this is needed, otherwise our references will not be valid anymore headers.reserve(num_headers); header_stacks.reserve(num_header_stacks); + header_unions.reserve(num_header_unions); + header_union_stacks.reserve(num_header_union_stacks); } void @@ -46,6 +51,8 @@ void PHV::reset_header_stacks() { for (auto &hs : header_stacks) hs.reset(); + for (auto &hus : header_union_stacks) + hus.reset(); } // so slow I want to die, but optional for a target... @@ -108,15 +115,50 @@ PHV::push_back_header_stack(const std::string &header_stack_name, header_stack_id_t header_stack_index, const HeaderType &header_type, const std::vector &header_ids) { + (void) header_type; // not used anymore assert(header_stack_index < static_cast(capacity_stacks)); assert(header_stack_index == static_cast(header_stacks.size())); - HeaderStack header_stack(header_stack_name, header_stack_index, header_type); + HeaderStack header_stack(header_stack_name, header_stack_index); for (header_id_t header_id : header_ids) { - header_stack.set_next_header(get_header(header_id)); + header_stack.set_next_element(get_header(header_id)); } header_stacks.push_back(std::move(header_stack)); } +void +PHV::push_back_header_union(const std::string &header_union_name, + header_union_id_t header_union_index, + const std::vector &header_ids) { + assert(header_union_index < static_cast(capacity_unions)); + assert(header_union_index == static_cast(header_unions.size())); + HeaderUnion header_union(header_union_name, header_union_index); + for (header_id_t header_id : header_ids) { + header_union.set_next_header(get_header(header_id)); + } + header_unions.push_back(std::move(header_union)); + size_t idx = 0; + for (header_id_t header_id : header_ids) { + auto &header = get_header(header_id); + header.set_union_membership(&header_unions.back(), idx++); + } +} + +void +PHV::push_back_header_union_stack( + const std::string &header_union_stack_name, + header_union_stack_id_t header_union_stack_index, + const std::vector &header_union_ids) { + assert(header_union_stack_index < static_cast(capacity_union_stacks)); + assert( + header_union_stack_index == static_cast(header_union_stacks.size())); + HeaderUnionStack header_union_stack( + header_union_stack_name, header_union_stack_index); + for (header_id_t header_union_id : header_union_ids) { + header_union_stack.set_next_element(get_header_union(header_union_id)); + } + header_union_stacks.push_back(std::move(header_union_stack)); +} + void PHV::add_field_alias(const std::string &from, const std::string &to) { // if an alias has the same name as an actual field, we give priority to the @@ -138,8 +180,7 @@ PHVFactory::push_back_header(const std::string &header_name, const header_id_t header_index, const HeaderType &header_type, const bool metadata) { - HeaderDesc desc = HeaderDesc(header_name, header_index, - header_type, metadata); + HeaderDesc desc(header_name, header_index, header_type, metadata); // cannot use operator[] because it requires default constructibility header_descs.insert(std::make_pair(header_index, desc)); for (int i = 0; i < header_type.get_num_fields(); i++) { @@ -152,12 +193,33 @@ PHVFactory::push_back_header_stack(const std::string &header_stack_name, const header_stack_id_t header_stack_index, const HeaderType &header_type, const std::vector &headers) { - HeaderStackDesc desc = HeaderStackDesc( + HeaderStackDesc desc( header_stack_name, header_stack_index, header_type, headers); // cannot use operator[] because it requires default constructibility header_stack_descs.insert(std::make_pair(header_stack_index, desc)); } +void +PHVFactory::push_back_header_union(const std::string &header_union_name, + const header_union_id_t header_union_index, + const std::vector &headers) { + HeaderUnionDesc desc(header_union_name, header_union_index, headers); + // cannot use operator[] because it requires default constructibility + header_union_descs.insert(std::make_pair(header_union_index, desc)); +} + +void +PHVFactory::push_back_header_union_stack( + const std::string &header_union_stack_name, + const header_union_stack_id_t header_union_stack_index, + const std::vector &header_unions) { + HeaderUnionStackDesc desc( + header_union_stack_name, header_union_stack_index, header_unions); + // cannot use operator[] because it requires default constructibility + header_union_stack_descs.insert( + std::make_pair(header_union_stack_index, desc)); +} + void PHVFactory::add_field_alias(const std::string &from, const std::string &to) { auto it = field_names.find(from); @@ -170,13 +232,13 @@ PHVFactory::add_field_alias(const std::string &from, const std::string &to) { void PHVFactory::enable_field_arith(header_id_t header_id, int field_offset) { - HeaderDesc &desc = header_descs.at(header_id); + auto &desc = header_descs.at(header_id); desc.arith_offsets.insert(field_offset); } void PHVFactory::enable_all_field_arith(header_id_t header_id) { - HeaderDesc &desc = header_descs.at(header_id); + auto &desc = header_descs.at(header_id); for (int offset = 0; offset < desc.header_type.get_num_fields(); offset++) { desc.arith_offsets.insert(offset); } @@ -184,31 +246,63 @@ PHVFactory::enable_all_field_arith(header_id_t header_id) { void PHVFactory::disable_field_arith(header_id_t header_id, int field_offset) { - HeaderDesc &desc = header_descs.at(header_id); + auto &desc = header_descs.at(header_id); desc.arith_offsets.erase(field_offset); } void PHVFactory::disable_all_field_arith(header_id_t header_id) { - HeaderDesc &desc = header_descs.at(header_id); + auto &desc = header_descs.at(header_id); desc.arith_offsets.clear(); } void PHVFactory::enable_stack_field_arith(header_stack_id_t header_stack_id, int field_offset) { - HeaderStackDesc &desc = header_stack_descs.at(header_stack_id); + const auto &desc = header_stack_descs.at(header_stack_id); for (header_id_t header_id : desc.headers) enable_field_arith(header_id, field_offset); } void PHVFactory::enable_all_stack_field_arith(header_stack_id_t header_stack_id) { - HeaderStackDesc &desc = header_stack_descs.at(header_stack_id); + const auto &desc = header_stack_descs.at(header_stack_id); for (header_id_t header_id : desc.headers) enable_all_field_arith(header_id); } +void +PHVFactory::enable_union_stack_field_arith( + header_union_stack_id_t header_union_stack_id, size_t header_offset, + int field_offset) { + const auto &stack_desc = header_union_stack_descs.at(header_union_stack_id); + for (header_union_id_t union_id : stack_desc.header_unions) { + const auto &union_desc = header_union_descs.at(union_id); + enable_field_arith(union_desc.headers.at(header_offset), field_offset); + } +} + +void +PHVFactory::enable_all_union_stack_field_arith( + header_union_stack_id_t header_union_stack_id, size_t header_offset) { + const auto &stack_desc = header_union_stack_descs.at(header_union_stack_id); + for (header_union_id_t union_id : stack_desc.header_unions) { + const auto &union_desc = header_union_descs.at(union_id); + enable_all_field_arith(union_desc.headers.at(header_offset)); + } +} + +void +PHVFactory::enable_all_union_stack_field_arith( + header_union_stack_id_t header_union_stack_id) { + const auto &stack_desc = header_union_stack_descs.at(header_union_stack_id); + for (header_union_id_t union_id : stack_desc.header_unions) { + const auto &union_desc = header_union_descs.at(union_id); + for (header_id_t header_id : union_desc.headers) + enable_all_field_arith(header_id); + } +} + void PHVFactory::enable_all_arith() { for (auto it : header_descs) @@ -217,22 +311,34 @@ PHVFactory::enable_all_arith() { std::unique_ptr PHVFactory::create() const { - std::unique_ptr phv(new PHV(header_descs.size(), - header_stack_descs.size())); + std::unique_ptr phv(new PHV( + header_descs.size(), header_stack_descs.size(), + header_union_descs.size(), header_union_stack_descs.size())); for (const auto &e : header_descs) { - const HeaderDesc &desc = e.second; + const auto &desc = e.second; phv->push_back_header(desc.name, desc.index, desc.header_type, desc.arith_offsets, desc.metadata); } for (const auto &e : header_stack_descs) { - const HeaderStackDesc &desc = e.second; + const auto &desc = e.second; phv->push_back_header_stack(desc.name, desc.index, desc.header_type, desc.headers); } + for (const auto &e : header_union_descs) { + const auto &desc = e.second; + phv->push_back_header_union(desc.name, desc.index, desc.headers); + } + + for (const auto &e : header_union_stack_descs) { + const auto &desc = e.second; + phv->push_back_header_union_stack(desc.name, desc.index, + desc.header_unions); + } + for (const auto &e : field_aliases) phv->add_field_alias(e.first, e.second); diff --git a/src/bm_sim/stacks.cpp b/src/bm_sim/stacks.cpp new file mode 100644 index 000000000..eb84fcba6 --- /dev/null +++ b/src/bm_sim/stacks.cpp @@ -0,0 +1,181 @@ +/* Copyright 2013-present Barefoot Networks, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Antonin Bas (antonin@barefootnetworks.com) + * + */ + +#include +#include +#include + +#include // std::min +#include + +namespace bm { + +template +Stack::Stack(const std::string &name, p4object_id_t id) + : StackIface(), NamedP4Object(name, id) { } + +template +size_t +Stack::pop_front() { + if (next == 0) return 0u; + next--; + for (size_t i = 0; i < next; i++) { + elements[i].get().swap_values(&elements[i + 1].get()); + } + elements[next].get().mark_invalid(); + return 1u; +} + +template +size_t +Stack::pop_front(size_t num) { + if (num == 0) return 0; + size_t popped = std::min(next, num); + next -= popped; + for (size_t i = 0; i < next; i++) { + elements[i].get().swap_values(&elements[i + num].get()); + } + for (size_t i = next; i < next + popped; i++) { + elements[i].get().mark_invalid(); + } + return popped; +} + +template +size_t +Stack::push_front() { + if (next < elements.size()) next++; + for (size_t i = next - 1; i > 0; i--) { + elements[i].get().swap_values(&elements[i - 1].get()); + } + // TODO(antonin): do I want to reset the element as well? + // this may be complicated given the design + elements[0].get().mark_valid(); + return 1u; +} + +template +size_t +Stack::push_front(size_t num) { + if (num == 0) return 0; + next = std::min(elements.size(), next + num); + for (size_t i = next - 1; i > num - 1; i--) { + elements[i].get().swap_values(&elements[i - num].get()); + } + size_t pushed = std::min(elements.size(), num); + for (size_t i = 0; i < pushed; i++) { + elements[i].get().mark_valid(); + } + return pushed; +} + +template +size_t +Stack::pop_back() { + if (next == 0) return 0u; + next--; + elements[next].get().mark_invalid(); + return 1u; +} + +template +size_t +Stack::push_back() { + if (next == elements.size()) return 0u; + elements[next].get().mark_valid(); + next++; + return 1u; +} + +template +size_t +Stack::get_depth() const { + return elements.size(); +} + +template +size_t +Stack::get_count() const { + return next; +} + +template +bool +Stack::is_full() const { + return (next >= elements.size()); +} + +template +void +Stack::reset() { + next = 0; +} + +template +T & +Stack::get_last() { + assert(next > 0 && "stack empty"); + return elements[next - 1]; +} + +template +const T & +Stack::get_last() const { + assert(next > 0 && "stack empty"); + return elements[next - 1]; +} + +template +T & +Stack::get_next() { + assert(next < elements.size() && "stack full"); + return elements[next]; +} + +template +const T & +Stack::get_next() const { + assert(next < elements.size() && "stack full"); + return elements[next]; +} + +template +T & +Stack::at(size_t idx) { + return elements.at(idx); +} + +template +const T & +Stack::at(size_t idx) const { + return elements.at(idx); +} + +template +void +Stack::set_next_element(T &e) { // NOLINT(runtime/references) + elements.emplace_back(e); +} + +// explicit instantiation +template class Stack
; +template class Stack; + +} // namespace bm diff --git a/targets/simple_switch/primitives.cpp b/targets/simple_switch/primitives.cpp index 63280332a..027e56b03 100644 --- a/targets/simple_switch/primitives.cpp +++ b/targets/simple_switch/primitives.cpp @@ -314,22 +314,6 @@ class register_write REGISTER_PRIMITIVE(register_write); -class push : public ActionPrimitive { - void operator ()(HeaderStack &stack, const Data &num) { - stack.push_front(num.get_uint()); - } -}; - -REGISTER_PRIMITIVE(push); - -class pop : public ActionPrimitive { - void operator ()(HeaderStack &stack, const Data &num) { - stack.pop_front(num.get_uint()); - } -}; - -REGISTER_PRIMITIVE(pop); - // I cannot name this "truncate" and register it with the usual // REGISTER_PRIMITIVE macro, because of a name conflict: // diff --git a/tests/Makefile.am b/tests/Makefile.am index d50ea5ff5..28aad9192 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -12,7 +12,7 @@ libtestutils_la_LIBADD = $(top_builddir)/src/bm_sim/libbmsim.la AM_CPPFLAGS += \ -isystem $(top_srcdir)/third_party/gtest/include \ -isystem $(top_srcdir)/third_party/jsoncpp/include \ --DTESTDATADIR=\"$(srcdir)/testdata\" +-DTESTDATADIR=\"$(abs_srcdir)/testdata\" if SKIP_UNDETERMINISTIC_TESTS AM_CPPFLAGS += -DSKIP_UNDETERMINISTIC_TESTS @@ -49,6 +49,7 @@ test_pre \ test_calculations \ test_headers \ test_header_stacks \ +test_header_unions \ test_meters \ test_ageing \ test_counters \ @@ -85,6 +86,7 @@ test_pre_SOURCES = $(common_source) test_pre.cpp test_calculations_SOURCES = $(common_source) test_calculations.cpp test_headers_SOURCES = $(common_source) test_headers.cpp test_header_stacks_SOURCES = $(common_source) test_header_stacks.cpp +test_header_unions_SOURCES = $(common_source) test_header_unions.cpp test_meters_SOURCES = $(common_source) test_meters.cpp test_ageing_SOURCES = $(common_source) test_ageing.cpp test_counters_SOURCES = $(common_source) test_counters.cpp @@ -119,6 +121,7 @@ test_pre.cpp \ test_calculations.cpp \ test_headers.cpp \ test_header_stacks.cpp \ +test_header_unions.cpp \ test_meters.cpp \ test_ageing.cpp \ test_counters.cpp \ diff --git a/tests/primitives.cpp b/tests/primitives.cpp index f52104447..9d082a4ef 100644 --- a/tests/primitives.cpp +++ b/tests/primitives.cpp @@ -88,14 +88,6 @@ class register_write REGISTER_PRIMITIVE(register_write); -class pop : public ActionPrimitive { - void operator ()(HeaderStack &stack, const Data &num) { - stack.pop_front(num.get_uint()); - } -}; - -REGISTER_PRIMITIVE(pop); - class ignore_string : public ActionPrimitive { void operator ()(const std::string &s) { (void)s; diff --git a/tests/test_actions.cpp b/tests/test_actions.cpp index 03fe2a7ca..3ae4ba203 100644 --- a/tests/test_actions.cpp +++ b/tests/test_actions.cpp @@ -173,6 +173,19 @@ class SaveCString REGISTER_PRIMITIVE(SaveCString); +class HeaderUnionAsParameter : public ActionPrimitive { + void operator ()(const HeaderUnion &hu) override { + union_name = hu.get_name(); + } + + std::string union_name; + + public: + std::string get_union_name() const { return union_name; } +}; + +REGISTER_PRIMITIVE(HeaderUnionAsParameter); + // Google Test fixture for actions tests class ActionsTest : public ::testing::Test { protected: @@ -182,9 +195,12 @@ class ActionsTest : public ::testing::Test { HeaderType testHeaderType; header_id_t testHeader1{0}, testHeader2{1}; header_id_t testHeaderS0{2}, testHeaderS1{3}; + header_id_t testHeaderU0{4}, testHeaderU1{5}; header_stack_id_t testHeaderStack{0}; + header_union_id_t testHeaderUnion{0}; + ActionFn testActionFn; ActionFnEntry testActionFnEntry; @@ -213,6 +229,11 @@ class ActionsTest : public ::testing::Test { phv_factory.push_back_header_stack("test_stack", testHeaderStack, testHeaderType, {testHeaderS0, testHeaderS1}); + + phv_factory.push_back_header("testU0", testHeaderU0, testHeaderType); + phv_factory.push_back_header("testU1", testHeaderU1, testHeaderType); + phv_factory.push_back_header_union("test_union", testHeaderUnion, + {testHeaderU0, testHeaderU1}); } virtual void SetUp() { @@ -601,6 +622,16 @@ TEST_F(ActionsTest, WritePacketRegister) { ASSERT_EQ(v, pkt->get_register(idx)); } +TEST_F(ActionsTest, HeaderUnion) { + HeaderUnionAsParameter primitive; + testActionFn.push_back_primitive(&primitive); + testActionFn.parameter_push_back_header_union(testHeaderUnion); + + testActionFnEntry(pkt.get()); + + EXPECT_EQ("test_union", primitive.get_union_name()); +} + template class ActionsStringParamTest : public ActionsTest { protected: diff --git a/tests/test_conditionals.cpp b/tests/test_conditionals.cpp index dadbd58ea..f3f89bec5 100644 --- a/tests/test_conditionals.cpp +++ b/tests/test_conditionals.cpp @@ -24,6 +24,7 @@ #include #include +#include // This is where expressions are tested (essentially the same thing as // conditionals) @@ -644,7 +645,7 @@ TEST_F(ConditionalsTest, Stacks) { size_t index) { auto c = base_condition(name); c.push_back_load_const(Data(index)); - c.push_back_op(ExprOpcode::DEREFERENCE_STACK); + c.push_back_op(ExprOpcode::DEREFERENCE_HEADER_STACK); c.push_back_op(ExprOpcode::VALID_HEADER); c.build(); return c; @@ -750,3 +751,144 @@ TEST_F(ConditionalsTest, Stress) { } } } + + +class ConditionalsUnionTest : public ConditionalsTest { + protected: + header_id_t testHeader3{2}, testHeader4{3}; + + header_union_id_t testHeaderUnion0{0}; + header_union_id_t testHeaderUnion1{1}; + + header_union_stack_id_t testHeaderUnionStack{0}; + size_t union_stack_depth{2}; + + ConditionalsUnionTest() { + phv_factory.push_back_header("test3", testHeader3, testHeaderType); + phv_factory.push_back_header("test4", testHeader4, testHeaderType); + + const std::vector headers0({testHeader1, testHeader2}); + phv_factory.push_back_header_union( + "test_union0", testHeaderUnion0, headers0); + const std::vector headers1({testHeader3, testHeader4}); + phv_factory.push_back_header_union( + "test_union1", testHeaderUnion1, headers1); + + const std::vector unions( + {testHeaderUnion0, testHeaderUnion1}); + phv_factory.push_back_header_union_stack( + "test_union_stack", testHeaderUnionStack, unions); + } +}; + +TEST_F(ConditionalsUnionTest, EqUnion) { + Conditional c("ctest", 0); + c.push_back_load_header_union(testHeaderUnion0); + c.push_back_load_header_union(testHeaderUnion1); + c.push_back_op(ExprOpcode::EQ_UNION); + c.build(); + + auto &hdr1 = phv->get_header(testHeader1); + auto &hdr2 = phv->get_header(testHeader2); + auto &hdr3 = phv->get_header(testHeader3); + hdr1.mark_valid(); + hdr3.mark_valid(); + + ASSERT_TRUE(c.eval(*phv)); + + hdr2.mark_valid(); + + ASSERT_FALSE(c.eval(*phv)); +} + +TEST_F(ConditionalsUnionTest, NeqUnion) { + Conditional c("ctest", 0); + c.push_back_load_header_union(testHeaderUnion0); + c.push_back_load_header_union(testHeaderUnion1); + c.push_back_op(ExprOpcode::NEQ_UNION); + c.build(); + + auto &hdr1 = phv->get_header(testHeader1); + auto &hdr2 = phv->get_header(testHeader2); + auto &hdr3 = phv->get_header(testHeader3); + hdr2.mark_valid(); + hdr3.mark_valid(); + + ASSERT_TRUE(c.eval(*phv)); + + hdr1.mark_valid(); + + ASSERT_FALSE(c.eval(*phv)); +} + +TEST_F(ConditionalsUnionTest, AccessUnionHeader) { + // valid(test_union0.test2) ? + Conditional c("ctest", 0); + c.push_back_load_header_union(testHeaderUnion0); + c.push_back_access_union_header(1); // access header at offset 1 in union + c.push_back_op(ExprOpcode::VALID_HEADER); + c.build(); + + auto &hdr1 = phv->get_header(testHeader1); + auto &hdr2 = phv->get_header(testHeader2); + hdr2.mark_valid(); + + ASSERT_TRUE(c.eval(*phv)); + + hdr1.mark_valid(); + + ASSERT_FALSE(c.eval(*phv)); +} + +TEST_F(ConditionalsUnionTest, ValidUnion) { + // valid(test_union0) ? + Conditional c("ctest", 0); + c.push_back_load_header_union(testHeaderUnion0); + c.push_back_op(ExprOpcode::VALID_UNION); + c.build(); + + auto &hdr1 = phv->get_header(testHeader1); + + ASSERT_FALSE(c.eval(*phv)); + + hdr1.mark_valid(); + + ASSERT_TRUE(c.eval(*phv)); +} + +TEST_F(ConditionalsUnionTest, DereferenceUnionStack) { + Conditional c("ctest", 0); + c.push_back_load_header_union_stack(testHeaderUnionStack); + c.push_back_load_const(Data(0)); + c.push_back_op(ExprOpcode::DEREFERENCE_UNION_STACK); + c.push_back_op(ExprOpcode::VALID_UNION); + c.build(); + + auto &union_stack = phv->get_header_union_stack(testHeaderUnionStack); + union_stack.push_front(); + auto &hdr1 = phv->get_header(testHeader1); + + ASSERT_FALSE(c.eval(*phv)); + + hdr1.mark_valid(); + + ASSERT_TRUE(c.eval(*phv)); +} + +TEST_F(ConditionalsUnionTest, UnionStackSize) { + // size(test_union) == 1 ? + Conditional c("ctest", 0); + c.push_back_load_header_union_stack(testHeaderUnionStack); + c.push_back_op(ExprOpcode::SIZE_STACK); + c.push_back_load_const(Data(1)); + c.push_back_op(ExprOpcode::EQ_DATA); + c.build(); + + auto &union_stack = phv->get_header_union_stack(testHeaderUnionStack); + + ASSERT_FALSE(c.eval(*phv)); + + union_stack.push_front(); + + ASSERT_TRUE(c.eval(*phv)); +} diff --git a/tests/test_core_primitives.cpp b/tests/test_core_primitives.cpp index 76b7a50cd..2011dcf6b 100644 --- a/tests/test_core_primitives.cpp +++ b/tests/test_core_primitives.cpp @@ -125,3 +125,5 @@ TEST_F(AssignPrimitivesTest, AssignHeader) { EXPECT_TRUE(hdr21.cmp(hdr20)); } } + +// assign_union is tested in test_header_unions diff --git a/tests/test_header_stacks.cpp b/tests/test_header_stacks.cpp index 37d0e9fee..13d74e1cf 100644 --- a/tests/test_header_stacks.cpp +++ b/tests/test_header_stacks.cpp @@ -41,7 +41,7 @@ class HeaderStackTest : public ::testing::Test { size_t stack_depth{3}; HeaderStackTest() - : testHeaderType("test_t", 0) { + : testHeaderType("test_t", 0) { testHeaderType.push_back_field("f16", 16); testHeaderType.push_back_field("f48", 48); phv_factory.push_back_header("test_0", testHeader_0, testHeaderType); diff --git a/tests/test_header_unions.cpp b/tests/test_header_unions.cpp new file mode 100644 index 000000000..af4afc090 --- /dev/null +++ b/tests/test_header_unions.cpp @@ -0,0 +1,246 @@ +/* Copyright 2013-present Barefoot Networks, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + * Antonin Bas (antonin@barefootnetworks.com) + * + */ + +#include + +#include +#include + +#include +#include + +using namespace bm; + +// Google Test fixture for header union tests +class HeaderUnionTest : public ::testing::Test { + protected: + PHVFactory phv_factory; + std::unique_ptr phv; + + HeaderType testHeaderType0, testHeaderType1; + header_id_t testHeader00{0}, testHeader01{1}; + header_id_t testHeader10{2}, testHeader11{3}; + + header_union_id_t testHeaderUnion0{0}; + header_union_id_t testHeaderUnion1{1}; + + HeaderUnionTest() + : testHeaderType0("test0_t", 0), testHeaderType1("test1_t", 1) { + testHeaderType0.push_back_field("f16", 16); + testHeaderType0.push_back_field("f48", 48); + testHeaderType1.push_back_field("f32", 32); + + phv_factory.push_back_header("test00", testHeader00, testHeaderType0); + phv_factory.push_back_header("test01", testHeader01, testHeaderType1); + phv_factory.push_back_header("test10", testHeader10, testHeaderType0); + phv_factory.push_back_header("test11", testHeader11, testHeaderType1); + + const std::vector headers0({testHeader00, testHeader01}); + phv_factory.push_back_header_union( + "test_union0", testHeaderUnion0, headers0); + const std::vector headers1({testHeader10, testHeader11}); + phv_factory.push_back_header_union( + "test_union1", testHeaderUnion1, headers1); + } + + virtual void SetUp() { + phv = phv_factory.create(); + } + + // virtual void TearDown() {} +}; + +TEST_F(HeaderUnionTest, Validity) { + auto &header_union = phv->get_header_union(testHeaderUnion0); + auto &header0 = phv->get_header(testHeader00); + auto &header1 = phv->get_header(testHeader01); + EXPECT_EQ(nullptr, header_union.get_valid_header()); + EXPECT_FALSE(header_union.is_valid()); + + header_union.make_header_valid(1); + EXPECT_EQ(&header1, header_union.get_valid_header()); + EXPECT_TRUE(header_union.is_valid()); + + header_union.make_header_invalid(0); + EXPECT_EQ(&header1, header_union.get_valid_header()); + + header_union.make_header_valid(0); + EXPECT_EQ(&header0, header_union.get_valid_header()); + + header_union.make_header_invalid(0); + EXPECT_EQ(nullptr, header_union.get_valid_header()); +} + +TEST_F(HeaderUnionTest, ValidityThruHeader) { + auto &header_union = phv->get_header_union(testHeaderUnion0); + auto &header0 = phv->get_header(testHeader00); + auto &header1 = phv->get_header(testHeader01); + EXPECT_EQ(nullptr, header_union.get_valid_header()); + EXPECT_FALSE(header0.is_valid()); + EXPECT_FALSE(header1.is_valid()); + + header1.mark_valid(); + EXPECT_FALSE(header0.is_valid()); + EXPECT_TRUE(header1.is_valid()); + EXPECT_EQ(&header1, header_union.get_valid_header()); + + // make the same header valid again, nothing should change + header1.mark_valid(); + EXPECT_FALSE(header0.is_valid()); + EXPECT_TRUE(header1.is_valid()); + EXPECT_EQ(&header1, header_union.get_valid_header()); + + header0.mark_invalid(); + EXPECT_FALSE(header0.is_valid()); + EXPECT_TRUE(header1.is_valid()); + EXPECT_EQ(&header1, header_union.get_valid_header()); + + header0.mark_valid(); + EXPECT_TRUE(header0.is_valid()); + EXPECT_FALSE(header1.is_valid()); + EXPECT_EQ(&header0, header_union.get_valid_header()); + + header0.mark_invalid(); + EXPECT_FALSE(header0.is_valid()); + EXPECT_FALSE(header1.is_valid()); + EXPECT_EQ(nullptr, header_union.get_valid_header()); +} + +TEST_F(HeaderUnionTest, SwapValues) { + auto &header_union0 = phv->get_header_union(testHeaderUnion0); + auto &header00 = phv->get_header(testHeader00); + auto &header01 = phv->get_header(testHeader01); + auto &header_union1 = phv->get_header_union(testHeaderUnion1); + auto &header10 = phv->get_header(testHeader10); + auto &header11 = phv->get_header(testHeader11); + unsigned int v(0xaba); + auto &f01_32 = header01.get_field(0); + auto &f11_32 = header11.get_field(0); + f01_32.set(v); + + header01.mark_valid(); + header10.mark_valid(); + EXPECT_FALSE(header00.is_valid()); + EXPECT_TRUE(header01.is_valid()); + EXPECT_EQ(&header01, header_union0.get_valid_header()); + EXPECT_TRUE(header10.is_valid()); + EXPECT_FALSE(header11.is_valid()); + EXPECT_EQ(&header10, header_union1.get_valid_header()); + + header_union0.swap_values(&header_union1); + EXPECT_TRUE(header00.is_valid()); + EXPECT_FALSE(header01.is_valid()); + EXPECT_EQ(&header00, header_union0.get_valid_header()); + EXPECT_FALSE(header10.is_valid()); + EXPECT_TRUE(header11.is_valid()); + EXPECT_EQ(&header11, header_union1.get_valid_header()); + EXPECT_EQ(v, f11_32.get()); +} + +TEST_F(HeaderUnionTest, AssignUnion) { + auto primitive = ActionOpcodesMap::get_instance()->get_primitive( + "assign_union"); + ASSERT_NE(nullptr, primitive); + auto assign_union = dynamic_cast(primitive.get()); + ASSERT_NE(nullptr, assign_union); + + auto &header_union0 = phv->get_header_union(testHeaderUnion0); + auto &header00 = phv->get_header(testHeader00); + auto &header01 = phv->get_header(testHeader01); + auto &header_union1 = phv->get_header_union(testHeaderUnion1); + auto &header10 = phv->get_header(testHeader10); + auto &header11 = phv->get_header(testHeader11); + const std::string data00(8, '\xab'); + const std::string data01(4, '\xcd'); + header00.extract(data00.data(), *phv); + EXPECT_TRUE(header00.is_valid()); + // extracting to h01 makes h00 invalid because both headers are in a union + header01.extract(data01.data(), *phv); + EXPECT_FALSE(header00.is_valid()); + EXPECT_TRUE(header01.is_valid()); + + (*assign_union)(header_union1, header_union0); + EXPECT_FALSE(header10.is_valid()); + EXPECT_TRUE(header11.is_valid()); + EXPECT_FALSE(header10.cmp(header00)); + EXPECT_TRUE(header11.cmp(header01)); + + header10.mark_valid(); + (*assign_union)(header_union0, header_union1); + EXPECT_TRUE(header00.is_valid()); + EXPECT_FALSE(header01.is_valid()); + EXPECT_TRUE(header00.cmp(header10)); + EXPECT_FALSE(header01.cmp(header11)); +} + + +// most of the stack code is common for header stacks and union stacks (see +// Stack), so we do not have a lot of unit tests specific to union stacks. +class HeaderUnionStackTest : public HeaderUnionTest { + protected: + header_union_stack_id_t testHeaderUnionStack{0}; + size_t stack_depth{2}; + + HeaderUnionStackTest() { + const std::vector unions( + {testHeaderUnion0, testHeaderUnion1}); + phv_factory.push_back_header_union_stack( + "test_union_stack", testHeaderUnionStack, unions); + } +}; + +TEST_F(HeaderUnionStackTest, PushAndPop) { + auto &union_stack = phv->get_header_union_stack(testHeaderUnionStack); + auto &header_union0 = phv->get_header_union(testHeaderUnion0); + auto &header00 = phv->get_header(testHeader00); + auto &header01 = phv->get_header(testHeader01); + auto &header_union1 = phv->get_header_union(testHeaderUnion1); + auto &header10 = phv->get_header(testHeader10); + auto &header11 = phv->get_header(testHeader11); + + EXPECT_EQ(stack_depth, union_stack.get_depth()); + EXPECT_EQ(0u, union_stack.get_count()); + + union_stack.push_front(); + EXPECT_EQ(1u, union_stack.get_count()); + header01.mark_valid(); + EXPECT_EQ(&header01, header_union0.get_valid_header()); + EXPECT_EQ(nullptr, header_union1.get_valid_header()); + + union_stack.push_front(); + EXPECT_EQ(2u, union_stack.get_count()); + EXPECT_TRUE(header11.is_valid()); + EXPECT_FALSE(header01.is_valid()); + EXPECT_EQ(nullptr, header_union0.get_valid_header()); + EXPECT_EQ(&header11, header_union1.get_valid_header()); + + header10.mark_valid(); // swap second union + EXPECT_EQ(&header10, header_union1.get_valid_header()); + EXPECT_FALSE(header11.is_valid()); + + union_stack.pop_front(); + EXPECT_EQ(1u, union_stack.get_count()); + EXPECT_TRUE(header00.is_valid()); + EXPECT_FALSE(header01.is_valid()); + EXPECT_FALSE(header10.is_valid()); + EXPECT_FALSE(header11.is_valid()); + EXPECT_EQ(&header00, header_union0.get_valid_header()); + EXPECT_EQ(nullptr, header_union1.get_valid_header()); +} diff --git a/tests/test_parser.cpp b/tests/test_parser.cpp index eed3c3319..33d9b8d10 100644 --- a/tests/test_parser.cpp +++ b/tests/test_parser.cpp @@ -20,14 +20,18 @@ #include +#include + #include #include #include #include #include #include +#include #include +#include #include #include #include @@ -1921,3 +1925,210 @@ TEST_F(ParserExtractVLTest, HeaderTooShort) { auto packet = get_pkt(packet_data); parse_and_check_error(&packet, ErrorCodeMap::Core::HeaderTooShort); } + + +// Google Test fixture for header union stacks +class HeaderUnionStackParserTest : public ParserTestGeneric { + protected: + HeaderType optionAHeaderType, optionBHeaderType; + ParseState optionsParseState, optionAParseState, optionBParseState; + header_id_t optionA_0{0}, optionB_0{1}, optionA_1{2}, optionB_1{3}; + header_union_id_t union_0{0}, union_1{1}; + header_union_stack_id_t options{0}; + + Deparser deparser; + + HeaderUnionStackParserTest() + : optionAHeaderType("optionA_t", 0), optionBHeaderType("optionB_t", 1), + optionsParseState("options_parse", 0), + optionAParseState("optionA_parse", 0), + optionBParseState("optionB_parse", 0), + deparser("test_deparser", 0) { + optionAHeaderType.push_back_field("type", 8); + optionAHeaderType.push_back_field("bos", 8); + optionBHeaderType.push_back_field("type", 8); + optionBHeaderType.push_back_field("bos", 8); + optionBHeaderType.push_back_field("f8", 8); + + phv_factory.push_back_header("optionA_0", optionA_0, optionAHeaderType); + phv_factory.push_back_header("optionA_1", optionA_1, optionAHeaderType); + phv_factory.push_back_header("optionB_0", optionB_0, optionBHeaderType); + phv_factory.push_back_header("optionB_1", optionB_1, optionBHeaderType); + + const std::vector headers_0({optionA_0, optionB_0}); + phv_factory.push_back_header_union("option_0", union_0, headers_0); + const std::vector headers_1({optionA_1, optionB_1}); + phv_factory.push_back_header_union("option_1", union_1, headers_1); + + const std::vector unions({union_0, union_1}); + phv_factory.push_back_header_union_stack("options", options, unions); + } + + virtual void SetUp() { + phv_source->set_phv_factory(0, &phv_factory); + + // we assume at least one option for the parse graph definition + + { + ParseSwitchKeyBuilder optionsKeyBuilder; + optionsKeyBuilder.push_back_lookahead(0, 8); // type + optionsParseState.set_key_builder(optionsKeyBuilder); + char keyA[] = {'\x0a'}; + optionsParseState.add_switch_case(sizeof(keyA), keyA, &optionAParseState); + char keyB[] = {'\x0b'}; + optionsParseState.add_switch_case(sizeof(keyB), keyB, &optionBParseState); + } + { + ParseSwitchKeyBuilder optionAKeyBuilder; + // options[last].optionA.bos + optionAKeyBuilder.push_back_union_stack_field(options, 0, 1, 8); + optionAParseState.set_key_builder(optionAKeyBuilder); + char key[] = {'\x00'}; + optionAParseState.add_switch_case(sizeof(key), key, &optionsParseState); + } + { + ParseSwitchKeyBuilder optionBKeyBuilder; + // options[last].optionA.bos + optionBKeyBuilder.push_back_union_stack_field(options, 1, 1, 8); + optionBParseState.set_key_builder(optionBKeyBuilder); + char key[] = {'\x00'}; + optionBParseState.add_switch_case(sizeof(key), key, &optionsParseState); + } + + // optionA has offset 0 in union, optionB has offset 1 in union + optionAParseState.add_extract_to_union_stack(options, 0); + optionBParseState.add_extract_to_union_stack(options, 1); + + parser.set_init_state(&optionsParseState); + + // TODO(antonin): ideally we would like a push_back_header_union method here + deparser.push_back_header(optionA_0); + deparser.push_back_header(optionB_0); + deparser.push_back_header(optionA_1); + deparser.push_back_header(optionB_1); + } + + Packet get_pkt(const std::string &data) { + assert(data.size() <= 128); + return Packet::make_new(data.size(), + PacketBuffer(128, data.data(), data.size()), + phv_source.get()); + } + + // virtual void TearDown() { } +}; + +TEST_F(HeaderUnionStackParserTest, ParseAndDeparse2Options) { + // optionB followed by optionA + const std::string data("\x0b\x00\xab\x0a\x01", 5); + auto packet = get_pkt(data); + auto phv = packet.get_phv(); + parse_and_check_no_error(&packet); + + const auto &optionA_0_hdr = phv->get_header(optionA_0); + const auto &optionA_1_hdr = phv->get_header(optionA_1); + const auto &optionB_0_hdr = phv->get_header(optionB_0); + const auto &optionB_1_hdr = phv->get_header(optionB_1); + const auto &options_union_0 = phv->get_header_union(union_0); + const auto &options_union_1 = phv->get_header_union(union_1); + const auto &options_union_stack = phv->get_header_union_stack(options); + + EXPECT_FALSE(optionA_0_hdr.is_valid()); + EXPECT_TRUE(optionA_1_hdr.is_valid()); + EXPECT_TRUE(optionB_0_hdr.is_valid()); + EXPECT_FALSE(optionB_1_hdr.is_valid()); + EXPECT_EQ(&optionB_0_hdr, options_union_0.get_valid_header()); + EXPECT_EQ(&optionA_1_hdr, options_union_1.get_valid_header()); + EXPECT_EQ(2u, options_union_stack.get_count()); + EXPECT_EQ(0xab, optionB_0_hdr.get_field(2).get()); + + deparser.deparse(&packet); + + ASSERT_EQ(data.size(), packet.get_data_size()); + ASSERT_EQ(0, memcmp(data.data(), packet.data(), data.size())); +} + + +namespace fs = boost::filesystem; + +// unsure whether these tests really belong here, or in test_p4objects... + +class HeaderUnionStackE2ETest : public ::testing::Test { + protected: + P4Objects objects; + LookupStructureFactory factory; + Parser *parser; + Deparser *deparser; + + std::unique_ptr phv_source{nullptr}; + + static constexpr size_t num_packets = 10u; + + HeaderUnionStackE2ETest() + : phv_source(PHVSourceIface::make_phv_source()) { } + + void init_objects(const fs::path &json_path) { + std::ifstream is(json_path.string()); + EXPECT_EQ(0, objects.init_objects(&is, &factory)); + parser = get_parser(); + ASSERT_TRUE(parser != nullptr); + deparser = get_deparser(); + ASSERT_TRUE(deparser != nullptr); + } + + void parse_and_deparse(const std::string &data) { + for (size_t i = 0; i < num_packets; i++) { + auto packet = get_pkt(data); + parse_and_check_no_error(&packet); + deparser->deparse(&packet); + ASSERT_EQ(data.size(), packet.get_data_size()); + ASSERT_EQ(0, memcmp(data.data(), packet.data(), data.size())); + } + } + + private: + Parser *get_parser() const { + return objects.get_parser_rt("parser"); + } + + Deparser *get_deparser() const { + return objects.get_deparser_rt("deparser"); + } + + void parse_and_check_no_error(Packet *packet) { + auto error_codes = objects.get_error_codes(); + ErrorCode no_error(error_codes.from_core(ErrorCodeMap::Core::NoError)); + parser->parse(packet); + ASSERT_EQ(no_error, packet->get_error_code()); + } + + Packet get_pkt(const std::string &data) { + phv_source->set_phv_factory(0, &objects.get_phv_factory()); + assert(data.size() <= 128); + return Packet::make_new(data.size(), + PacketBuffer(128, data.data(), data.size()), + phv_source.get()); + } +}; + +constexpr size_t HeaderUnionStackE2ETest::num_packets; + +TEST_F(HeaderUnionStackE2ETest, OptionsE2eCount) { + fs::path json_path = + fs::path(TESTDATADIR) / fs::path("unions_e2e_options_count.json"); + init_objects(json_path); + + // the last byte is a payload byte, to avoid an invalid memory read because of + // the parser lookahead + std::string data("\x02\x0a\x0b\x66\xab", 5); + parse_and_deparse(data); +} + +TEST_F(HeaderUnionStackE2ETest, OptionsE2eBos) { + fs::path json_path = + fs::path(TESTDATADIR) / fs::path("unions_e2e_options_bos.json"); + init_objects(json_path); + + std::string data("\x0b\x00\x77\x0a\x01", 5); + parse_and_deparse(data); +} diff --git a/tests/testdata/unions_e2e_options_bos.json b/tests/testdata/unions_e2e_options_bos.json new file mode 100644 index 000000000..be331cf78 --- /dev/null +++ b/tests/testdata/unions_e2e_options_bos.json @@ -0,0 +1,316 @@ +{ + "__meta__": { + "version": [ + 2, + 5 + ], + "compiler": "HAND_WRITTEN" + }, + "header_types": [ + { + "name": "standard_metadata_t", + "id": 0, + "fields": [ + [ + "ingress_port", + 9 + ], + [ + "packet_length", + 32 + ], + [ + "egress_spec", + 9 + ], + [ + "egress_port", + 9 + ], + [ + "egress_instance", + 32 + ], + [ + "instance_type", + 32 + ], + [ + "clone_spec", + 32 + ], + [ + "_padding", + 5 + ] + ], + "length_exp": null, + "max_length": null + }, + { + "name": "optionA_t", + "id": 1, + "fields": [ + [ + "type", + 8 + ], + [ + "bos", + 8 + ] + ], + "length_exp": null, + "max_length": null + }, + { + "name": "optionB_t", + "id": 2, + "fields": [ + [ + "type", + 8 + ], + [ + "bos", + 8 + ], + [ + "f8", + 8 + ] + ], + "length_exp": null, + "max_length": null + } + ], + "headers": [ + { + "name": "standard_metadata", + "id": 0, + "header_type": "standard_metadata_t", + "metadata": true + }, + { + "name": "optionA_0", + "id": 1, + "header_type": "optionA_t", + "metadata": false + }, + { + "name": "optionA_1", + "id": 2, + "header_type": "optionA_t", + "metadata": false + }, + { + "name": "optionB_0", + "id": 3, + "header_type": "optionB_t", + "metadata": false + }, + { + "name": "optionB_1", + "id": 4, + "header_type": "optionB_t", + "metadata": false + } + ], + "header_stacks": [], + "header_union_types": [ + { + "name": "options", + "id": 0, + "headers": [ + [ + "optionA", + "optionA_t" + ], + [ + "optionB", + "optionB_t" + ] + ] + } + ], + "header_unions": [ + { + "name": "options_0", + "id": 0, + "union_type": "options", + "header_ids": [ + 1, + 3 + ] + }, + { + "name": "options_1", + "id": 1, + "union_type": "options", + "header_ids": [ + 2, + 4 + ] + } + ], + "header_union_stacks": [ + { + "name": "options", + "id": 0, + "union_type": "options", + "header_union_ids": [ + 0, + 1 + ] + } + ], + "parsers": [ + { + "name": "parser", + "id": 0, + "init_state": "start", + "parse_states": [ + { + "name": "start", + "id": 0, + "parser_ops": [], + "transition_key": [ + { + "type": "lookahead", + "value": [ + 0, + 8 + ] + } + ], + "transitions": [ + { + "type": "hexstr", + "value": "0x0a", + "mask": null, + "next_state": "parse_optionA" + }, + { + "type": "hexstr", + "value": "0x0b", + "mask": null, + "next_state": "parse_optionB" + } + ] + }, + { + "name": "parse_optionA", + "id": 1, + "parser_ops": [ + { + "op": "extract", + "parameters": [ + { + "type": "union_stack", + "value": [ + "options", + "optionA" + ] + } + ] + } + ], + "transition_key": [ + { + "type": "union_stack_field", + "value": [ + "options", + "optionA", + "bos" + ] + } + ], + "transitions": [ + { + "type": "hexstr", + "value": "0x00", + "mask": null, + "next_state": "start" + } + ] + }, + { + "name": "parse_optionB", + "id": 2, + "parser_ops": [ + { + "op": "extract", + "parameters": [ + { + "type": "union_stack", + "value": [ + "options", + "optionB" + ] + } + ] + } + ], + "transition_key": [ + { + "type": "union_stack_field", + "value": [ + "options", + "optionB", + "bos" + ] + } + ], + "transitions": [ + { + "type": "hexstr", + "value": "0x00", + "mask": null, + "next_state": "start" + } + ] + } + ] + } + ], + "parse_vsets": [], + "deparsers": [ + { + "name": "deparser", + "id": 0, + "order": [ + "optionA_0", + "optionB_0", + "optionA_1", + "optionB_1" + ] + } + ], + "meter_arrays": [], + "actions": [], + "pipelines": [ + { + "name": "ingress", + "id": 0, + "init_table": null, + "tables": [], + "action_profiles": [], + "conditionals": [] + }, + { + "name": "egress", + "id": 1, + "init_table": null, + "tables": [], + "action_profiles": [], + "conditionals": [] + } + ], + "calculations": [], + "checksums": [], + "learn_lists": [], + "field_lists": [], + "counter_arrays": [], + "register_arrays": [], + "force_arith": [] +} diff --git a/tests/testdata/unions_e2e_options_count.json b/tests/testdata/unions_e2e_options_count.json new file mode 100644 index 000000000..e6267c1b7 --- /dev/null +++ b/tests/testdata/unions_e2e_options_count.json @@ -0,0 +1,447 @@ +{ + "__meta__": { + "version": [ + 2, + 5 + ], + "compiler": "HAND_WRITTEN" + }, + "header_types": [ + { + "name": "standard_metadata_t", + "id": 0, + "fields": [ + [ + "ingress_port", + 9 + ], + [ + "packet_length", + 32 + ], + [ + "egress_spec", + 9 + ], + [ + "egress_port", + 9 + ], + [ + "egress_instance", + 32 + ], + [ + "instance_type", + 32 + ], + [ + "clone_spec", + 32 + ], + [ + "_padding", + 5 + ] + ], + "length_exp": null, + "max_length": null + }, + { + "name": "h_t", + "id": 1, + "fields": [ + [ + "num_options", + 8 + ] + ], + "length_exp": null, + "max_length": null + }, + { + "name": "optionA_t", + "id": 2, + "fields": [ + [ + "type", + 8 + ] + ], + "length_exp": null, + "max_length": null + }, + { + "name": "optionB_t", + "id": 3, + "fields": [ + [ + "type", + 8 + ], + [ + "f8", + 8 + ] + ], + "length_exp": null, + "max_length": null + }, + { + "name": "my_metadata_t", + "id": 8, + "fields": [ + [ + "option_counter", + 8 + ] + ], + "length_exp": null, + "max_length": null + } + ], + "headers": [ + { + "name": "standard_metadata", + "id": 0, + "header_type": "standard_metadata_t", + "metadata": true + }, + { + "name": "h", + "id": 1, + "header_type": "h_t", + "metadata": false + }, + { + "name": "optionA_0", + "id": 2, + "header_type": "optionA_t", + "metadata": false + }, + { + "name": "optionA_1", + "id": 3, + "header_type": "optionA_t", + "metadata": false + }, + { + "name": "optionB_0", + "id": 4, + "header_type": "optionB_t", + "metadata": false + }, + { + "name": "optionB_1", + "id": 5, + "header_type": "optionB_t", + "metadata": false + }, + { + "name": "my_metadata", + "id": 6, + "header_type": "my_metadata_t", + "metadata": true + } + ], + "header_stacks": [], + "header_union_types": [ + { + "name": "options", + "id": 0, + "headers": [ + [ + "optionA", + "optionA_t" + ], + [ + "optionB", + "optionB_t" + ] + ] + } + ], + "header_unions": [ + { + "name": "options_0", + "id": 0, + "union_type": "options", + "header_ids": [ + 2, + 4 + ] + }, + { + "name": "options_1", + "id": 1, + "union_type": "options", + "header_ids": [ + 3, + 5 + ] + } + ], + "header_union_stacks": [ + { + "name": "options", + "id": 0, + "union_type": "options", + "header_union_ids": [ + 0, + 1 + ] + } + ], + "parsers": [ + { + "name": "parser", + "id": 0, + "init_state": "start", + "parse_states": [ + { + "name": "start", + "id": 0, + "parser_ops": [ + { + "op": "extract", + "parameters": [ + { + "type": "regular", + "value": "h" + } + ] + }, + { + "op": "set", + "parameters": [ + { + "type": "field", + "value": [ + "my_metadata", + "option_counter" + ] + }, + { + "type": "field", + "value": [ + "h", + "num_options" + ] + } + ] + } + ], + "transition_key": [], + "transitions": [ + { + "type": "default", + "value": null, + "mask": null, + "next_state": "parse_options" + } + ] + }, + { + "name": "parse_options", + "id": 1, + "parser_ops": [], + "transition_key": [ + { + "type": "field", + "value": [ + "my_metadata", + "option_counter" + ] + }, + { + "type": "lookahead", + "value": [ + 0, + 8 + ] + } + ], + "transitions": [ + { + "type": "hexstr", + "value": "0x0000", + "mask": "0xff00", + "next_state": null + }, + { + "type": "hexstr", + "value": "0x000a", + "mask": "0x00ff", + "next_state": "parse_optionA" + }, + { + "type": "hexstr", + "value": "0x000b", + "mask": "0x00ff", + "next_state": "parse_optionB" + } + ] + }, + { + "name": "parse_optionA", + "id": 2, + "parser_ops": [ + { + "op": "extract", + "parameters": [ + { + "type": "union_stack", + "value": [ + "options", + "optionA" + ] + } + ] + }, + { + "op": "set", + "parameters": [ + { + "type": "field", + "value": [ + "my_metadata", + "option_counter" + ] + }, + { + "type": "expression", + "value": { + "type": "expression", + "value": { + "op": "-", + "left": { + "type": "field", + "value": [ + "my_metadata", + "option_counter" + ] + }, + "right": { + "type": "hexstr", + "value": "0x1" + } + } + } + } + ] + } + ], + "transition_key": [], + "transitions": [ + { + "type": "default", + "value": null, + "mask": null, + "next_state": "parse_options" + } + ] + }, + { + "name": "parse_optionB", + "id": 3, + "parser_ops": [ + { + "op": "extract", + "parameters": [ + { + "type": "union_stack", + "value": [ + "options", + "optionB" + ] + } + ] + }, + { + "op": "set", + "parameters": [ + { + "type": "field", + "value": [ + "my_metadata", + "option_counter" + ] + }, + { + "type": "expression", + "value": { + "type": "expression", + "value": { + "op": "-", + "left": { + "type": "field", + "value": [ + "my_metadata", + "option_counter" + ] + }, + "right": { + "type": "hexstr", + "value": "0x1" + } + } + } + } + ] + } + ], + "transition_key": [], + "transitions": [ + { + "type": "default", + "value": null, + "mask": null, + "next_state": "parse_options" + } + ] + } + ] + } + ], + "parse_vsets": [], + "deparsers": [ + { + "name": "deparser", + "id": 0, + "order": [ + "h", + "optionA_0", + "optionB_0", + "optionA_1", + "optionB_1" + ] + } + ], + "meter_arrays": [], + "actions": [], + "pipelines": [ + { + "name": "ingress", + "id": 0, + "init_table": null, + "tables": [], + "action_profiles": [], + "conditionals": [] + }, + { + "name": "egress", + "id": 1, + "init_table": null, + "tables": [], + "action_profiles": [], + "conditionals": [] + } + ], + "calculations": [], + "checksums": [], + "learn_lists": [], + "field_lists": [], + "counter_arrays": [], + "register_arrays": [], + "force_arith": [] +}