Skip to content

Commit 892c421

Browse files
authored
Add extra checks on next tables (#1287)
* Add checks on the contents of the "next_tables" value in tables * More notes in JSON_format.md on how p4c creates JSON files * Added notes that action_id and action_data are required fields ... inside of default_entry value. * Add a note in JSON docs about when p4c include default_entry key for a table * Update JSON docs making it clearer that action_id and action_data are required * Bump max_minor_version to 24 * Change version to 2.24 in JSON format doc Signed-off-by: Andy Fingerhut <[email protected]>
1 parent 87e6bf3 commit 892c421

File tree

3 files changed

+114
-14
lines changed

3 files changed

+114
-14
lines changed

docs/JSON_format.md

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ on each attribute.
1010

1111
## Current bmv2 JSON format version
1212

13-
The version described in this document is *2.23*.
13+
The version described in this document is *2.24*.
1414

1515
The major version number will be increased by the compiler only when
1616
backward-compatibility of the JSON format is broken. After a major version
@@ -625,23 +625,23 @@ attributes for these objects are:
625625
- `actions`: the list of actions (order does not matter) supported by this
626626
table
627627
- `next_tables`: maps each action to a next table name. Alternatively, maps
628-
special string `__HIT__` and `__MISS__` to a next table name.
628+
special string `__HIT__` and `__MISS__` to a next table name. See Note 3 below.
629629
- `direct_meters`: the name of the associated direct meter array, or null if
630630
the match table has no associated meter array
631631
- `default_entry`: an optional JSON item which can force the default entry for
632632
the table to be set when loading the JSON, without intervention from the
633633
control plane. It has the following attributes:
634-
- `action_id`: the id of the default action
634+
- `action_id`: the id of the default action. Required.
635635
- `action_const`: an optional boolean value which is `true` iff the control
636636
plane is not allowed to change the default action function. Default value is
637-
`false`. It can only be set to `true` for `simple` tables.
638-
- `action_data`: an optional JSON array where each entry is the hexstring
637+
`false`. It can only be set to `true` for `simple` tables. See Note 2 below.
638+
- `action_data`: a required JSON array where each entry is the hexstring
639639
value for an action argument. The size of the array needs to match the
640640
number of parameters expected by the action function with id `action_id`.
641641
- `action_entry_const`: an optional boolean value which is `true` iff the
642642
control plane is not allowed to modify the action entry (action function +
643643
action data). Default value is `false`. This attribute is ignored if the
644-
`action_data` attribute it missing.
644+
`action_data` attribute it missing. See Note 2 below.
645645
- `entries`: enables you to optionally specify match-action entries for this
646646
table. Specifying entries in the JSON makes the table immutable, which means
647647
the added entries cannot be modified / deleted and that new entries cannot be
@@ -685,6 +685,33 @@ a miss every time it is applied, and execute its default action. A
685685
dummy table has a const default action that is equal to the action `a`
686686
in the original source code that it is replacing.
687687

688+
Note 2: Since May 2017 when [PR
689+
#653](https://github.com/p4lang/p4c/pull/653) was merged into p4c, p4c
690+
has always created tables with the value of `action_entry_const` equal
691+
to `action_const`. They are both true if the `default_action` in the
692+
P4 source code for the table is declared `const`, and both false if
693+
the `default_action` is not declared `const`.
694+
Also since 2017 and perhaps earlier, p4c has always included the key
695+
`default_entry` in all table definitions with `type` equal to `simple`.
696+
Since then it has _not_ included the key `default_entry` in table
697+
definitions with types that were not `simple` (i.e. tables with
698+
action profiles or action selectors).
699+
700+
Note 3: p4c always creates the value of the `next_tables` key in one
701+
of these ways:
702+
+ If you use the P4 constructs `t1.apply().hit` or `t1.apply().miss`,
703+
and use that Boolean value to choose between two execution paths,
704+
e.g. in an `if` statement, then the table's `next_tables` value will
705+
contain the keys `__HIT__` and/or `__MISS__` to specify these two
706+
paths, and no other keys will be present.
707+
+ If you do not use those P4 constructs, then the `next_tables` value
708+
will contain keys equal to the action names of the table. If the P4
709+
program invokes the table using `switch (t1.apply().action_run)`,
710+
then in general the different action names can specify different
711+
next nodes to execute next, after the table is applied. If you do
712+
not use that construct, then the next node to be executed will be
713+
the same for all actions.
714+
688715
The `match_type` for the table needs to follow the following rules:
689716
- If one match field is `range`, the table `match_type` has to be `range`
690717
- If one match field is `ternary`, the table `match_type` has to be `ternary`

include/bm/bm_sim/P4Objects.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,10 @@ class P4Objects {
378378
void init_meter_arrays(const Json::Value &root, InitState *);
379379
void init_register_arrays(const Json::Value &root);
380380
void init_actions(const Json::Value &root);
381+
void check_next_nodes(const Json::Value &cfg_next_nodes,
382+
const Json::Value &cfg_actions,
383+
const std::string &table_name,
384+
bool *next_is_hit_miss);
381385
void init_pipelines(const Json::Value &root, LookupStructureFactory *,
382386
InitState *);
383387
void init_checksums(const Json::Value &root);

src/bm_sim/P4Objects.cpp

Lines changed: 77 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ using std::string;
4444
namespace {
4545

4646
constexpr int required_major_version = 2;
47-
constexpr int max_minor_version = 23;
47+
constexpr int max_minor_version = 24;
4848
// not needed for now
4949
// constexpr int min_minor_version = 0;
5050

@@ -1575,6 +1575,57 @@ std::vector<MatchKeyParam> parse_match_key(
15751575

15761576
} // namespace
15771577

1578+
void
1579+
P4Objects::check_next_nodes(const Json::Value &cfg_next_nodes,
1580+
const Json::Value &cfg_actions,
1581+
const std::string &table_name,
1582+
bool *next_is_hit_miss) {
1583+
// For each table, the value of its key "next_tables" must be an
1584+
// object with one of the following sets of keys:
1585+
// (a) The set of keys must include "__HIT__" and "__MISS__", but no
1586+
// others. This is how a P4 table is implemented if, where it
1587+
// is applied, it uses "t1.apply().hit" or "t1.apply().miss"
1588+
// conditions to control which code is executed next.
1589+
// (b) The set of keys must include each action name of the table
1590+
// exactly once, but no others. This is how a P4 table is
1591+
// implemented if where it is applied, it uses
1592+
// "t1.apply().action_run" and a switch statement to control
1593+
// which code is executed next. It is also used by the p4c BMv2
1594+
// backend for tables that use none of .hit, .miss, and
1595+
// .action_run, and always execute the same code next regardless
1596+
// of hit, miss, or which action the table executed. In that
1597+
// case, every action will have the same next node to execute
1598+
// regardless of the action.
1599+
int num_next_nodes = cfg_next_nodes.size();
1600+
bool next_has_hit = cfg_next_nodes.isMember("__HIT__");
1601+
bool next_has_miss = cfg_next_nodes.isMember("__MISS__");
1602+
if (next_has_hit || next_has_miss) {
1603+
if (next_has_hit && next_has_miss && (num_next_nodes == 2)) {
1604+
*next_is_hit_miss = true;
1605+
} else {
1606+
throw json_exception(
1607+
EFormat() << "Table '" << table_name << "' has one"
1608+
<< " of keys '__HIT__' and '__MISS__' in 'next_tables'"
1609+
<< " but either it does not have both of them,"
1610+
<< " or it has other keys that should not be there.",
1611+
cfg_next_nodes);
1612+
}
1613+
} else {
1614+
*next_is_hit_miss = false;
1615+
int num_actions = cfg_actions.size();
1616+
// The check that each action name is a key in cfg_next_nodes is
1617+
// done near where check_next_nodes is called, to avoid
1618+
// duplicating here the code that calculates action_name.
1619+
if (num_next_nodes != num_actions) {
1620+
throw json_exception(
1621+
EFormat() << "Table '" << table_name << "' should have exactly "
1622+
<< num_actions << " keys, one for each table action, but found "
1623+
<< num_next_nodes << "keys.",
1624+
cfg_next_nodes);
1625+
}
1626+
}
1627+
}
1628+
15781629
void
15791630
P4Objects::init_pipelines(const Json::Value &cfg_root,
15801631
LookupStructureFactory *lookup_factory,
@@ -1818,6 +1869,9 @@ P4Objects::init_pipelines(const Json::Value &cfg_root,
18181869
std::string actions_key = cfg_table.isMember("action_ids") ? "action_ids"
18191870
: "actions";
18201871
const Json::Value &cfg_actions = cfg_table[actions_key];
1872+
bool next_is_hit_miss = false;
1873+
check_next_nodes(cfg_next_nodes, cfg_actions, table_name,
1874+
&next_is_hit_miss);
18211875
for (const auto &cfg_action : cfg_actions) {
18221876
p4object_id_t action_id = 0;
18231877
string action_name = "";
@@ -1831,19 +1885,24 @@ P4Objects::init_pipelines(const Json::Value &cfg_root,
18311885
action = get_one_action_with_name(action_name); assert(action);
18321886
action_id = action->get_id();
18331887
}
1834-
1888+
if (!next_is_hit_miss && !cfg_next_nodes.isMember(action_name)) {
1889+
throw json_exception(
1890+
EFormat() << "Table '" << table_name << "' should have key"
1891+
<< " for action '" << action_name
1892+
<< "' in its 'next_tables' object.",
1893+
cfg_next_nodes);
1894+
}
18351895
const Json::Value &cfg_next_node = cfg_next_nodes[action_name];
18361896
const ControlFlowNode *next_node = get_next_node(cfg_next_node);
18371897
table->set_next_node(action_id, next_node);
18381898
add_action_to_table(table_name, action_name, action);
18391899
if (act_prof_name != "")
18401900
add_action_to_act_prof(act_prof_name, action_name, action);
18411901
}
1842-
1843-
if (cfg_next_nodes.isMember("__HIT__"))
1844-
table->set_next_node_hit(get_next_node(cfg_next_nodes["__HIT__"]));
1845-
if (cfg_next_nodes.isMember("__MISS__"))
1846-
table->set_next_node_miss(get_next_node(cfg_next_nodes["__MISS__"]));
1902+
if (next_is_hit_miss) {
1903+
table->set_next_node_hit(get_next_node(cfg_next_nodes["__HIT__"]));
1904+
table->set_next_node_miss(get_next_node(cfg_next_nodes["__MISS__"]));
1905+
}
18471906

18481907
if (cfg_table.isMember("base_default_next")) {
18491908
table->set_next_node_miss_default(
@@ -1888,6 +1947,11 @@ P4Objects::init_pipelines(const Json::Value &cfg_root,
18881947

18891948
table->set_default_default_entry(action, std::move(adata),
18901949
is_action_entry_const);
1950+
} else {
1951+
throw json_exception(
1952+
EFormat() << "'default_entry' of table '" << table_name
1953+
<< "' should have key 'action_data'",
1954+
cfg_default_entry);
18911955
}
18921956
}
18931957

@@ -1951,9 +2015,14 @@ P4Objects::init_pipelines(const Json::Value &cfg_root,
19512015
auto conditional_name = cfg_conditional["name"].asString();
19522016
auto conditional = get_conditional(conditional_name);
19532017

2018+
if (!cfg_conditional.isMember("true_next") &&
2019+
!cfg_conditional.isMember("false_next")) {
2020+
throw json_exception("conditional must have either or both of the"
2021+
" keys 'true_next' and 'false_next'.",
2022+
cfg_conditional);
2023+
}
19542024
const auto &cfg_true_next = cfg_conditional["true_next"];
19552025
const auto &cfg_false_next = cfg_conditional["false_next"];
1956-
19572026
if (!cfg_true_next.isNull()) {
19582027
auto next_node = get_control_node_cfg(cfg_true_next.asString());
19592028
conditional->set_next_node_if_true(next_node);

0 commit comments

Comments
 (0)