-
Notifications
You must be signed in to change notification settings - Fork 448
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -99,10 +99,7 @@ bool TableStepper::compareLPMEntries(const IR::Entry* leftIn, const IR::Entry* r | |
if (const auto* rightMask = right->checkedTo<IR::Mask>()) { | ||
rightMaskVal = rightMask->right->checkedTo<IR::Constant>(); | ||
} | ||
if ((leftMaskVal->value) < (rightMaskVal->value)) { | ||
return false; | ||
} | ||
return true; | ||
return (leftMaskVal->value) >= (rightMaskVal->value); | ||
} | ||
|
||
BUG("Unhandled sort elements type: left: %1% - %2% \n right: %3% - %4% ", left, | ||
|
@@ -167,10 +164,8 @@ const IR::Expression* TableStepper::computeTargetMatchType( | |
TESTGEN_UNIMPLEMENTED("Match type %s not implemented for table keys.", keyProperties.matchType); | ||
} | ||
|
||
const IR::Expression* TableStepper::computeHit(ExecutionState* nextState, const IR::P4Table* table, | ||
const IR::Expression* TableStepper::computeHit(ExecutionState* nextState, | ||
std::map<cstring, const FieldMatch>* matches) { | ||
const auto* key = table->getKey(); | ||
CHECK_NULL(key); | ||
const IR::Expression* hitCondition = IR::getBoolLiteral(!properties.resolvedKeys.empty()); | ||
for (auto keyProperties : properties.resolvedKeys) { | ||
hitCondition = computeTargetMatchType(nextState, keyProperties, matches, hitCondition); | ||
|
@@ -209,10 +204,7 @@ const IR::Expression* TableStepper::evalTableConstEntries() { | |
const IR::Expression* tableMissCondition = IR::getBoolLiteral(true); | ||
|
||
const auto* keys = table->getKey(); | ||
if (keys == nullptr) { | ||
// No keys to match on. Assume just the default action will be executed. | ||
return tableMissCondition; | ||
} | ||
BUG_CHECK(keys != nullptr, "An empty key list should have been handled earlier."); | ||
|
||
const auto* entries = table->getEntries(); | ||
// Sometimes, there are no entries. Just return. | ||
|
@@ -318,13 +310,75 @@ const IR::Expression* TableStepper::evalTableConstEntries() { | |
return tableMissCondition; | ||
} | ||
|
||
void TableStepper::setTableDefaultEntries( | ||
const std::vector<const IR::ActionListElement*>& tableActionList) { | ||
for (size_t idx = 0; idx < tableActionList.size(); idx++) { | ||
const auto* action = tableActionList.at(idx); | ||
// Grab the path from the method call. | ||
const auto* tableAction = action->expression->checkedTo<IR::MethodCallExpression>(); | ||
// Try to find the action declaration corresponding to the path reference in the table. | ||
const auto* actionType = stepper->state.getActionDecl(tableAction->method); | ||
CHECK_NULL(actionType); | ||
|
||
auto* nextState = new ExecutionState(stepper->state); | ||
|
||
// We get the control plane name of the action we are calling. | ||
cstring actionName = actionType->controlPlaneName(); | ||
// Synthesize arguments for the call based on the action parameters. | ||
const auto& parameters = actionType->parameters; | ||
auto* arguments = new IR::Vector<IR::Argument>(); | ||
std::vector<ActionArg> ctrlPlaneArgs; | ||
for (size_t argIdx = 0; argIdx < parameters->size(); ++argIdx) { | ||
const auto* parameter = parameters->getParameter(argIdx); | ||
// Synthesize a zombie constant here that corresponds to a control plane argument. | ||
// We get the unique name of the table coupled with the unique name of the action. | ||
// Getting the unique name is needed to avoid generating duplicate arguments. | ||
const auto& actionDataVar = | ||
Utils::getZombieTableVar(parameter->type, table, "*actionData", idx, argIdx); | ||
cstring paramName = | ||
properties.tableName + "_arg_" + actionName + std::to_string(argIdx); | ||
const auto& actionArg = nextState->createZombieConst(parameter->type, paramName); | ||
nextState->set(actionDataVar, actionArg); | ||
arguments->push_back(new IR::Argument(actionArg)); | ||
// We also track the argument we synthesize for the control plane. | ||
// Note how we use the control plane name for the parameter here. | ||
ctrlPlaneArgs.emplace_back(parameter, actionArg); | ||
} | ||
const auto* ctrlPlaneActionCall = new ActionCall(actionType, ctrlPlaneArgs); | ||
|
||
// We add the arguments to our action call, effectively creating a const entry call. | ||
auto* synthesizedAction = tableAction->clone(); | ||
synthesizedAction->arguments = arguments; | ||
|
||
// We need to set the table action in the state for eventual switch action_run hits. | ||
// We also will need it for control plane table entries. | ||
setTableAction(nextState, tableAction); | ||
|
||
// Finally, add all the new rules to the execution stepper->state. | ||
auto* tableConfig = new TableConfig(table, {}); | ||
// Add the action selector to the table. This signifies a slightly different implementation. | ||
tableConfig->addTableProperty("overriden_default_action", ctrlPlaneActionCall); | ||
nextState->addTestObject("tableconfigs", properties.tableName, tableConfig); | ||
|
||
// Update all the tracking variables for tables. | ||
std::vector<Continuation::Command> replacements; | ||
replacements.emplace_back(new IR::MethodCallStatement(synthesizedAction)); | ||
|
||
nextState->set(getTableHitVar(table), IR::getBoolLiteral(false)); | ||
nextState->set(getTableReachedVar(table), IR::getBoolLiteral(true)); | ||
std::stringstream tableStream; | ||
tableStream << "Table Branch: " << properties.tableName; | ||
tableStream << "| Overriding default action: " << actionName; | ||
nextState->add(new TraceEvent::Generic(tableStream.str())); | ||
nextState->replaceTopBody(&replacements); | ||
stepper->result->emplace_back(nextState); | ||
} | ||
} | ||
|
||
void TableStepper::evalTableControlEntries( | ||
const std::vector<const IR::ActionListElement*>& tableActionList) { | ||
const auto* keys = table->getKey(); | ||
// If we have no keys, there is nothing to match. | ||
if (keys == nullptr) { | ||
return; | ||
} | ||
BUG_CHECK(keys != nullptr, "An empty key list should have been handled earlier."); | ||
|
||
for (size_t idx = 0; idx < tableActionList.size(); idx++) { | ||
const auto* action = tableActionList.at(idx); | ||
|
@@ -338,7 +392,7 @@ void TableStepper::evalTableControlEntries( | |
|
||
// First, we compute the hit condition to trigger this particular action call. | ||
std::map<cstring, const FieldMatch> matches; | ||
const auto* hitCondition = computeHit(nextState, table, &matches); | ||
const auto* hitCondition = computeHit(nextState, &matches); | ||
|
||
// We get the control plane name of the action we are calling. | ||
cstring actionName = actionType->controlPlaneName(); | ||
|
@@ -518,14 +572,18 @@ bool TableStepper::resolveTableKeys() { | |
return false; | ||
} | ||
|
||
bool TableStepper::checkTableIsImmutable() { | ||
void TableStepper::checkTableIsImmutable() { | ||
bool isConstant = false; | ||
if (table->properties->getProperty("entries") != nullptr) { | ||
isConstant = table->properties->getProperty("entries")->isConstant; | ||
const auto* entriesAnnotation = table->properties->getProperty("entries"); | ||
if (entriesAnnotation != nullptr) { | ||
isConstant = entriesAnnotation->isConstant; | ||
} | ||
// Also check if the table is invisible to the control plane. | ||
// This also implies that it cannot be modified. | ||
return isConstant || table->getAnnotation("hidden") != nullptr; | ||
properties.tableIsImmutable = isConstant || table->getAnnotation("hidden") != nullptr; | ||
const auto* defaultAction = table->properties->getProperty("default_action"); | ||
CHECK_NULL(defaultAction); | ||
properties.defaultIsImmutable = defaultAction->isConstant; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice! |
||
} | ||
|
||
std::vector<const IR::ActionListElement*> TableStepper::buildTableActionList() { | ||
|
@@ -595,8 +653,8 @@ void TableStepper::evalTargetTable( | |
} | ||
|
||
bool TableStepper::eval() { | ||
// Check whether the table is immutable, meaning it has constant entries. | ||
properties.tableIsImmutable = checkTableIsImmutable(); | ||
// Set the appropriate properties when the table is immutable, meaning it has constant entries. | ||
checkTableIsImmutable(); | ||
// Resolve any non-symbolic table keys. The function returns true when a key needs replacement. | ||
if (resolveTableKeys()) { | ||
return false; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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" | ||
|
@@ -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. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do all test frameworks support There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. | ||
|
@@ -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.). | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a bug in the STF test framework. Default action overrides were not using the correct syntax.