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

P4Tools: Implement default action override for BMv2 STF. #3685

Merged
merged 4 commits into from
Nov 22, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Refactor test frameworks and use a utility library.
  • Loading branch information
fruffy committed Nov 22, 2022
commit 342412e32d6475e6a95cab1932a69657b9568747
102 changes: 101 additions & 1 deletion backends/p4tools/testgen/lib/tf.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
#include <cstddef>

#include <boost/optional/optional.hpp>
#include <inja/inja.hpp>

#include "backends/p4tools/common/lib/format_int.h"
#include "lib/cstring.h"

#include "backends/p4tools/testgen/lib/test_spec.h"
Expand All @@ -14,7 +16,7 @@ namespace P4Tools {
namespace P4Testgen {

/// THe default base class for the various test frameworks (TF). Every test framework has a test
/// name and a seed associated with it.
/// name and a seed associated with it. Also contains a variety of common utility functions.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do all test frameworks support ActionProfiles and ActionSelectors - do we want to move it to common tf?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is shared between enough back ends that we can add it to common.

class TF {
protected:
/// The @testName to be used in test case generation.
Expand All @@ -26,6 +28,104 @@ class TF {
/// Creates a generic test framework.
explicit TF(cstring, boost::optional<unsigned int>);

/// Converts the traces of this test into a string representation and Inja object.
static inja::json getTrace(const TestSpec* testSpec) {
inja::json traceList = inja::json::array();
const auto* traces = testSpec->getTraces();
if ((traces != nullptr) && !traces->empty()) {
for (const auto& trace : *traces) {
std::stringstream ss;
ss << *trace;
traceList.push_back(ss.str());
}
}
return traceList;
}

/// Checks whether a table object has an action profile or selector associated with it.
/// If that is the case, we set a boolean flag for this particular inja object.
template <class ProfileType, class SelectorType>
static void checkForTableActionProfile(inja::json& tblJson, std::map<cstring, cstring>& apAsMap,
const TableConfig* tblConfig) {
const auto* apObject = tblConfig->getProperty("action_profile", false);
if (apObject != nullptr) {
const auto* actionProfile = apObject->checkedTo<ProfileType>();
tblJson["has_ap"] = true;
// Check if we have an Action Selector too.
// TODO: Change this to check in ActionSelector with table
// property "action_selectors".
const auto* asObject = tblConfig->getProperty("action_selector", false);
if (asObject != nullptr) {
const auto* actionSelector = asObject->checkedTo<SelectorType>();
apAsMap[actionProfile->getProfileDecl()->controlPlaneName()] =
actionSelector->getSelectorDecl()->controlPlaneName();
tblJson["has_as"] = true;
}
}
}

/// Check whether the table object has an overridden default action.
/// In this case, we assume there are no keys and we just set the default action of the table.
static void checkForDefaultActionOverride(inja::json& tblJson, const TableConfig* tblConfig) {
const auto* defaultOverrideObj = tblConfig->getProperty("overriden_default_action", false);
if (defaultOverrideObj != nullptr) {
const auto* defaultAction = defaultOverrideObj->checkedTo<ActionCall>();
inja::json a;
a["action_name"] = defaultAction->getActionName();
auto const* actionArgs = defaultAction->getArgs();
inja::json b = inja::json::array();
for (const auto& actArg : *actionArgs) {
inja::json j;
j["param"] = actArg.getActionParamName().c_str();
j["value"] = formatHexExpr(actArg.getEvaluatedValue());
b.push_back(j);
}
a["act_args"] = b;
tblJson["default_override"] = a;
}
}

/// Collect all the action profile objects. These will have to be declared in the test.
template <class ProfileType>
static void collectActionProfileDeclarations(const TestSpec* testSpec,
inja::json& controlPlaneJson,
const std::map<cstring, cstring>& apAsMap) {
auto actionProfiles = testSpec->getTestObjectCategory("action_profiles");
if (!actionProfiles.empty()) {
controlPlaneJson["action_profiles"] = inja::json::array();
}
for (auto const& testObject : actionProfiles) {
const auto* const actionProfile = testObject.second->checkedTo<ProfileType>();
const auto* actions = actionProfile->getActions();
inja::json j;
j["profile"] = actionProfile->getProfileDecl()->controlPlaneName();
j["actions"] = inja::json::array();
for (size_t idx = 0; idx < actions->size(); ++idx) {
const auto& action = actions->at(idx);
auto actionName = action.first;
auto actionArgs = action.second;
inja::json a;
a["action_name"] = actionName;
a["action_idx"] = std::to_string(idx);
inja::json b = inja::json::array();
for (const auto& actArg : actionArgs) {
inja::json c;
c["param"] = actArg.getActionParamName().c_str();
c["value"] = formatHexExpr(actArg.getEvaluatedValue()).c_str();
b.push_back(c);
}
a["act_args"] = b;
j["actions"].push_back(a);
}
// Look up the selectors associated with the profile.
if (apAsMap.find(actionProfile->getProfileDecl()->controlPlaneName()) !=
apAsMap.end()) {
j["selector"] = apAsMap.at(actionProfile->getProfileDecl()->controlPlaneName());
}
controlPlaneJson["action_profiles"].push_back(j);
}
}

public:
/// The method used to output the test case to be implemented by
/// all the test frameworks (eg. STF, PTF, etc.).
Expand Down
97 changes: 15 additions & 82 deletions backends/p4tools/testgen/targets/bmv2/backend/protobuf/protobuf.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,6 @@ namespace P4Testgen {

namespace Bmv2 {

using P4::ControlPlaneAPI::p4rt_id_t;
using P4::ControlPlaneAPI::P4RuntimeSymbolType;
using P4::ControlPlaneAPI::Standard::SymbolType;

/// Wrapper helper function that automatically inserts separators for hex strings.
std::string formatHexExprWithSep(const IR::Expression* expr) {
return insertHexSeparators(formatHexExpr(expr, false, true, false));
Expand All @@ -52,22 +48,7 @@ std::string formatHexExprWithSep(const IR::Expression* expr) {
Protobuf::Protobuf(cstring testName, boost::optional<unsigned int> seed = boost::none)
: TF(testName, seed) {}

inja::json Protobuf::getTrace(const TestSpec* testSpec) {
inja::json traceList = inja::json::array();
const auto* traces = testSpec->getTraces();
if ((traces != nullptr) && !traces->empty()) {
for (const auto& trace : *traces) {
std::stringstream ss;
ss << *trace;
traceList.push_back(ss.str());
}
}
return traceList;
}

/// @return the id allocated to the object through the @id annotation if any, or
/// boost::none.
static boost::optional<p4rt_id_t> getIdAnnotation(const IR::IAnnotated* node) {
boost::optional<p4rt_id_t> Protobuf::getIdAnnotation(const IR::IAnnotated* node) {
const auto* idAnnotation = node->getAnnotation("id");
if (idAnnotation == nullptr) {
return boost::none;
Expand All @@ -81,11 +62,8 @@ static boost::optional<p4rt_id_t> getIdAnnotation(const IR::IAnnotated* node) {
return static_cast<p4rt_id_t>(idConstant->value);
}

/// @return the value of any P4 '@id' annotation @declaration may have, and
/// ensure that the value is correct with respect to the P4Runtime
/// specification. The name 'externalId' is in analogy with externalName().
static boost::optional<p4rt_id_t> externalId(const P4RuntimeSymbolType& type,
const IR::IDeclaration* declaration) {
boost::optional<p4rt_id_t> Protobuf::externalId(const P4RuntimeSymbolType& type,
const IR::IDeclaration* declaration) {
CHECK_NULL(declaration);
if (!declaration->is<IR::IAnnotated>()) {
return boost::none; // Assign an id later; see below.
Expand Down Expand Up @@ -139,7 +117,7 @@ inja::json Protobuf::getControlPlane(const TestSpec* testSpec) {
inja::json controlPlaneJson = inja::json::object();

// Map of actionProfiles and actionSelectors for easy reference.
std::map<cstring, cstring> apASMap;
std::map<cstring, cstring> apAsMap;

auto tables = testSpec->getTestObjectCategory("tables");
if (!tables.empty()) {
Expand Down Expand Up @@ -169,70 +147,25 @@ inja::json Protobuf::getControlPlane(const TestSpec* testSpec) {
actionDecl);
rule["action_id"] = *p4RuntimeId;
auto j = getControlPlaneForTable(*matches, *actionArgs);
rule["priority"] = tblRule.getPriority();
rule["rules"] = std::move(j);
rule["priority"] = tblRule.getPriority();
tblJson["rules"].push_back(rule);
}

// Action Profile
const auto* apObject = tblConfig->getProperty("action_profile", false);
const Bmv2_V1ModelActionProfile* actionProfile = nullptr;
const Bmv2_V1ModelActionSelector* actionSelector = nullptr;
if (apObject != nullptr) {
actionProfile = apObject->checkedTo<Bmv2_V1ModelActionProfile>();
// Check if we have an Action Selector too.
// TODO: Change this to check in ActionSelector with table
// property "action_selectors".
const auto* asObject = tblConfig->getProperty("action_selector", false);
if (asObject != nullptr) {
actionSelector = asObject->checkedTo<Bmv2_V1ModelActionSelector>();
apASMap[actionProfile->getProfileDecl()->controlPlaneName()] =
actionSelector->getSelectorDecl()->controlPlaneName();
}
}
if (actionProfile != nullptr) {
tblJson["has_ap"] = true;
}
// Collect action profiles and selectors associated with the table.
checkForTableActionProfile<Bmv2_V1ModelActionProfile, Bmv2_V1ModelActionSelector>(
tblJson, apAsMap, tblConfig);

if (actionSelector != nullptr) {
tblJson["has_as"] = true;
}
// Check whether the default action is overridden for this table.
checkForDefaultActionOverride(tblJson, tblConfig);

controlPlaneJson["tables"].push_back(tblJson);
}
auto actionProfiles = testSpec->getTestObjectCategory("action_profiles");
if (!actionProfiles.empty()) {
controlPlaneJson["action_profiles"] = inja::json::array();
}
for (auto const& testObject : actionProfiles) {
const auto* const actionProfile = testObject.second->checkedTo<Bmv2_V1ModelActionProfile>();
const auto* actions = actionProfile->getActions();
inja::json j;
const auto* profileDecl = actionProfile->getProfileDecl();
j["profile"] = profileDecl->controlPlaneName();
j["actions"] = inja::json::array();
for (size_t idx = 0; idx < actions->size(); ++idx) {
const auto& action = actions->at(idx);
auto actionName = action.first;
auto actionArgs = action.second;
inja::json a;
a["action_name"] = actionName;
a["action_idx"] = std::to_string(idx);
inja::json b = inja::json::array();
for (const auto& actArg : actionArgs) {
inja::json c;
c["param"] = actArg.getActionParamName().c_str();
c["value"] = formatHexExprWithSep(actArg.getEvaluatedValue()).c_str();
b.push_back(c);
}
a["action_args"] = b;
j["actions"].push_back(a);
}
if (apASMap.find(actionProfile->getProfileDecl()->controlPlaneName()) != apASMap.end()) {
j["selector"] = apASMap[actionProfile->getProfileDecl()->controlPlaneName()];
}
controlPlaneJson["action_profiles"].push_back(j);
}

// Collect declarations of action profiles.
collectActionProfileDeclarations<Bmv2_V1ModelActionProfile>(testSpec, controlPlaneJson,
apAsMap);

return controlPlaneJson;
}

Expand Down
20 changes: 16 additions & 4 deletions backends/p4tools/testgen/targets/bmv2/backend/protobuf/protobuf.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
#include <boost/optional/optional.hpp>
#include <inja/inja.hpp>

/// Inja
#include "control-plane/p4RuntimeArchStandard.h"
#include "ir/ir.h"
#include "lib/cstring.h"

Expand All @@ -24,6 +24,10 @@ namespace P4Testgen {

namespace Bmv2 {

using P4::ControlPlaneAPI::p4rt_id_t;
using P4::ControlPlaneAPI::P4RuntimeSymbolType;
using P4::ControlPlaneAPI::Standard::SymbolType;

/// Extracts information from the @testSpec to emit a Protobuf test case.
class Protobuf : public TF {
/// The output file.
Expand All @@ -42,6 +46,7 @@ class Protobuf : public TF {

Protobuf(cstring testName, boost::optional<unsigned int> seed);

/// Produce a Protobuf test.
void outputTest(const TestSpec* spec, cstring selectedBranches, size_t testIdx,
float currentCoverage) override;

Expand All @@ -58,9 +63,6 @@ class Protobuf : public TF {
void emitTestcase(const TestSpec* testSpec, cstring selectedBranches, size_t testId,
const std::string& testCase, float currentCoverage);

/// Converts the traces of this test into a string representation and Inja object.
static inja::json getTrace(const TestSpec* testSpec);

/// Converts all the control plane objects into Inja format.
static inja::json getControlPlane(const TestSpec* testSpec);

Expand All @@ -77,6 +79,16 @@ class Protobuf : public TF {
/// Helper function for the control plane table inja objects.
static inja::json getControlPlaneForTable(const std::map<cstring, const FieldMatch>& matches,
const std::vector<ActionArg>& args);

/// @return the id allocated to the object through the @id annotation if any, or
/// boost::none.
static boost::optional<p4rt_id_t> getIdAnnotation(const IR::IAnnotated* node);

/// @return the value of any P4 '@id' annotation @declaration may have, and
/// ensure that the value is correct with respect to the P4Runtime
/// specification. The name 'externalId' is in analogy with externalName().
static boost::optional<p4rt_id_t> externalId(const P4RuntimeSymbolType& type,
const IR::IDeclaration* declaration);
};

} // namespace Bmv2
Expand Down
Loading