Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[P4Testgen] Implement coverage tracking of actions #4307

Merged
merged 13 commits into from
Jan 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ bool ExprStepper::preorder(const IR::ArrayIndex *arrIndex) {
}

void ExprStepper::evalActionCall(const IR::P4Action *action, const IR::MethodCallExpression *call) {
state.markVisited(action);
const auto *actionNameSpace = action->to<IR::INamespace>();
BUG_CHECK(actionNameSpace, "Does not instantiate an INamespace: %1%", actionNameSpace);
// If the action has arguments, these are usually directionless control plane input.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@ class ExprStepper : public AbstractStepper {
const IR::Vector<IR::Argument> *args,
const ExecutionState &state);

/// Evaluates a call to an action. This usually only happens when a table is invoked.
/// Evaluates a call to an action. This usually only happens when a table is invoked or when
/// action is directly invoked from a control.
/// In other cases, actions should be inlined. When the action call is evaluated, we use
/// symbolic variables to pass arguments across execution boundaries. These variables persist
/// until the end of program execution.
Expand Down
18 changes: 14 additions & 4 deletions backends/p4tools/modules/testgen/lib/collect_coverable_nodes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -80,27 +80,27 @@ bool CoverableNodesScanner::preorder(const IR::MethodCallExpression *call) {
const auto *tableAction =
entry->action->checkedTo<IR::MethodCallExpression>();
const auto *actionType = executionState.getP4Action(tableAction);
actionType->body->apply_visitor_preorder(*this);
actionType->apply_visitor_preorder(*this);
}
}
} else {
for (const auto &action : tableActionList) {
const auto *tableAction =
action->expression->checkedTo<IR::MethodCallExpression>();
const auto *actionType = executionState.getP4Action(tableAction);
actionType->body->apply_visitor_preorder(*this);
actionType->apply_visitor_preorder(*this);
}
}
const auto *defaultAction = table->getDefaultAction();
const auto *tableAction = defaultAction->checkedTo<IR::MethodCallExpression>();
const auto *actionType = executionState.getP4Action(tableAction);
actionType->body->apply_visitor_preorder(*this);
actionType->apply_visitor_preorder(*this);
}
return false;
}
if (call->method->type->is<IR::Type_Action>()) {
const auto *actionType = executionState.getP4Action(call);
actionType->body->apply_visitor_preorder(*this);
actionType->apply_visitor_preorder(*this);
return false;
}
return false;
Expand All @@ -122,6 +122,16 @@ bool CoverableNodesScanner::preorder(const IR::ExitStatement *stmt) {
return true;
}

bool CoverableNodesScanner::preorder(const IR::P4Action *act) {
// Only track actions, which have a valid source position in the P4 program.
if (coverageOptions.coverActions && act->getSourceInfo().isValid()) {
coverableNodes.insert(act);
}
// Visit body only.
act->body->apply_visitor_preorder(*this);
return false;
}

const P4::Coverage::CoverageSet &CoverableNodesScanner::getCoverableNodes() {
return coverableNodes;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ class CoverableNodesScanner : public Inspector {
bool preorder(const IR::ExitStatement *stmt) override;
bool preorder(const IR::MethodCallExpression *call) override;

/// Actions coverage.
bool preorder(const IR::P4Action *act) override;

public:
explicit CoverableNodesScanner(const ExecutionState &state);

Expand Down
4 changes: 4 additions & 0 deletions backends/p4tools/modules/testgen/lib/execution_state.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,10 @@ void ExecutionState::markVisited(const IR::Node *node) {
if (node->is<IR::Entry>() && !coverageOptions.coverTableEntries) {
return;
}
// Do not add actions, if coverActions is not toggled.
if (node->is<IR::P4Action>() && !coverageOptions.coverActions) {
return;
}
visitedNodes.emplace(node);
}

Expand Down
13 changes: 8 additions & 5 deletions backends/p4tools/modules/testgen/options.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -255,10 +255,8 @@ TestgenOptions::TestgenOptions()
registerOption(
"--track-coverage", "coverageItem",
[this](const char *arg) {
static std::set<cstring> const COVERAGE_OPTIONS = {
"STATEMENTS",
"TABLE_ENTRIES",
};
static std::set<cstring> const COVERAGE_OPTIONS = {"STATEMENTS", "TABLE_ENTRIES",
"ACTIONS"};
hasCoverageTracking = true;
auto selectionString = cstring(arg).toUpper();
auto it = COVERAGE_OPTIONS.find(selectionString);
Expand All @@ -271,6 +269,10 @@ TestgenOptions::TestgenOptions()
coverageOptions.coverTableEntries = true;
return true;
}
if (selectionString == "ACTIONS") {
coverageOptions.coverActions = true;
return true;
}
}
::error(
"Coverage tracking for label %1% not supported. Supported coverage tracking "
Expand All @@ -280,7 +282,8 @@ TestgenOptions::TestgenOptions()
return false;
},
"Specifies, which IR nodes to track for coverage in the targeted P4 program. Multiple "
"options are possible: Currently supported: STATEMENTS, TABLE_ENTRIES "
"options are possible: Currently supported: STATEMENTS, TABLE_ENTRIES (table rules encoded "
"in the table entries in P4), ACTIONS (actions invoked, directly or by tables). "
"Defaults to no coverage.");

registerOption(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ option(P4TOOLS_TESTGEN_BMV2_TEST_PROTOBUF_IR "Run tests on the Protobuf test bac
option(P4TOOLS_TESTGEN_BMV2_TEST_PTF "Run tests on the PTF test back end" ON)
option(P4TOOLS_TESTGEN_BMV2_TEST_STF "Run tests on the STF test back end" ON)
# Test settings.
set(EXTRA_OPTS "--strict --print-traces --seed 1000 --max-tests 10 ")
set(EXTRA_OPTS "--strict --print-traces --seed 1000 --max-tests 10 --track-coverage STATEMENTS --track-coverage TABLE_ENTRIES --track-coverage ACTIONS ")

# ASSERT_ASSUME TESTS
include(${CMAKE_CURRENT_LIST_DIR}/AssumeAssertTests.cmake)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
#include <v1model.p4>

header ethernet_t {
bit<48> dst_addr;
bit<48> src_addr;
bit<16> eth_type;
}

header H {
bit<8> a;
bit<8> b;
}

struct Headers {
ethernet_t eth_hdr;
H h;
}

struct Meta {}

parser p(packet_in pkt, out Headers h, inout Meta meta, inout standard_metadata_t stdmeta) {
state start {
pkt.extract(h.eth_hdr);
transition parse_h;
}
state parse_h {
pkt.extract(h.h);
transition accept;
}
}

control vrfy(inout Headers h, inout Meta meta) {
apply { }
}

control ingress(inout Headers h, inout Meta m, inout standard_metadata_t s) {

action t0_a0() { /* empty */ }
action t0_a1() { /* empty */ }
action t0_a2() { h.h.a = 4; }

table t0 {
actions = { t0_a0; t0_a1; t0_a2; }
size = 8;
default_action = t0_a0();
}

action t1_a0() { /* empty */ }
action t1_a1() { /* empty */ }
action t1_a2() { h.h.a = h.h.a + 7; }

table t1 {
key = { h.h.a : exact; }
actions = { t1_a0; t1_a1; t1_a2; }
size = 8;
const default_action = t1_a1();
}

action t2_a0() { /* empty */ }
action t2_a1() { /* empty */ }
action t2_a2() { h.h.a = h.h.a + 13; }

table t2 {
key = { h.h.a : exact; }
actions = { t2_a0; t2_a1; t2_a2; }
size = 8;
const entries = {
0 : t2_a2;
1 : t2_a2;
2 : t2_a0;
3 : t2_a1;
// implicit default NoAction
}
}

action free_a0() { /* empty */ }
action free_a1() { /* empty */ }
action free_nested() { /* empty */ }
action free_a2() { h.h.a = h.h.a + 11; free_nested(); }

apply {
if (h.h.isValid()) {
// check action coverage tracking on default actions
t0.apply();
// check action coverage tracking on non-default actions
t1.apply();
// check action coverage tracking on const entries
t2.apply();
// check action coverage on freestanding actions
if (h.h.a > 8) {
free_a0();
} else if (h.h.b > 8) {
free_a1();
} else {
free_a2();
}
}
}
}

control egress(inout Headers h, inout Meta m, inout standard_metadata_t s) {
apply { }
}

control update(inout Headers h, inout Meta m) {
apply { }
}

control deparser(packet_out pkt, in Headers h) {
apply {
pkt.emit(h);
}
}

V1Switch(p(), vrfy(), ingress(), egress(), update(), deparser()) main;
8 changes: 8 additions & 0 deletions midend/coverage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,14 @@ bool CollectNodes::preorder(const IR::ExitStatement *stmt) {
return true;
}

bool CollectNodes::preorder(const IR::P4Action *act) {
// Only track actions, which have a valid source position in the P4 program.
if (coverageOptions.coverActions && act->getSourceInfo().isValid()) {
coverableNodes.insert(act);
}
return true;
}

void printCoverageReport(const CoverageSet &all, const CoverageSet &visited) {
if (all.empty()) {
return;
Expand Down
6 changes: 6 additions & 0 deletions midend/coverage.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ struct CoverageOptions {
bool coverStatements = false;
/// Cover IR::Entry
bool coverTableEntries = false;
/// Cover IR::P4Action
bool coverActions = false;

/// Skip tests which do not increase coverage.
bool onlyCoveringTests = false;
};
Expand All @@ -52,6 +55,9 @@ class CollectNodes : public Inspector {
/// Table entry coverage.
bool preorder(const IR::Entry *entry) override;

/// Actions coverage.
bool preorder(const IR::P4Action *act) override;

public:
explicit CollectNodes(CoverageOptions coverageOptions);

Expand Down
Loading