From 593026c32cdb87000bea7ce33e2ecc1729c84c40 Mon Sep 17 00:00:00 2001 From: Antonin Bas Date: Thu, 27 Apr 2017 08:56:36 -0700 Subject: [PATCH] Add support for P4_16 header unions (#341) There is a P4_16 proposal to add header unions to the language. This patch adds support for them in bmv2 and defines the appropriate JSON input format when using header unions. Having first-class support for unions in bmv2 allows for easier debugging, with potentially better log messages. The most important use of header unions is when using stacks / arrays of them. This enables writing programs able to serialize / deserialize TLV-style protocol options (e.g. IPv4 options). Which is why this patch adds support for stacks of header unions, which actually took the biggest effort. Header unions and header union stacks are modeled after header stacks. The JSON documentation was updated to reflect this change, and the version number was bumped up to 2.10 --- docs/JSON_format.md | 119 +++-- docs/simple_switch.md | 4 +- include/Makefile.am | 5 +- include/bm/bm_sim/P4Objects.h | 25 +- include/bm/bm_sim/actions.h | 57 ++- include/bm/bm_sim/core/primitives.h | 13 + include/bm/bm_sim/expressions.h | 20 +- include/bm/bm_sim/header_stacks.h | 209 --------- include/bm/bm_sim/header_unions.h | 123 +++++ include/bm/bm_sim/headers.h | 16 + include/bm/bm_sim/parser.h | 17 +- include/bm/bm_sim/phv.h | 95 +++- include/bm/bm_sim/phv_forward.h | 2 + include/bm/bm_sim/stacks.h | 167 +++++++ src/bm_sim/Makefile.am | 2 + src/bm_sim/P4Objects.cpp | 259 ++++++++++- src/bm_sim/actions.cpp | 17 + src/bm_sim/core/primitives.cpp | 24 + src/bm_sim/expressions.cpp | 112 ++++- src/bm_sim/header_unions.cpp | 33 ++ src/bm_sim/headers.cpp | 23 +- src/bm_sim/parser.cpp | 64 ++- src/bm_sim/phv.cpp | 140 +++++- src/bm_sim/stacks.cpp | 181 ++++++++ targets/simple_switch/primitives.cpp | 16 - tests/Makefile.am | 5 +- tests/primitives.cpp | 8 - tests/test_actions.cpp | 31 ++ tests/test_conditionals.cpp | 144 +++++- tests/test_core_primitives.cpp | 2 + tests/test_header_stacks.cpp | 2 +- tests/test_header_unions.cpp | 246 ++++++++++ tests/test_parser.cpp | 211 +++++++++ tests/testdata/unions_e2e_options_bos.json | 316 +++++++++++++ tests/testdata/unions_e2e_options_count.json | 447 +++++++++++++++++++ 35 files changed, 2824 insertions(+), 331 deletions(-) delete mode 100644 include/bm/bm_sim/header_stacks.h create mode 100644 include/bm/bm_sim/header_unions.h create mode 100644 include/bm/bm_sim/stacks.h create mode 100644 src/bm_sim/header_unions.cpp create mode 100644 src/bm_sim/stacks.cpp create mode 100644 tests/test_header_unions.cpp create mode 100644 tests/testdata/unions_e2e_options_bos.json create mode 100644 tests/testdata/unions_e2e_options_count.json 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": [] +}