Skip to content

Commit

Permalink
Implement default action override for BMv2 STF.
Browse files Browse the repository at this point in the history
  • Loading branch information
fruffy committed Nov 10, 2022
1 parent c59597f commit 856c781
Show file tree
Hide file tree
Showing 5 changed files with 246 additions and 81 deletions.
86 changes: 70 additions & 16 deletions backends/p4tools/testgen/core/small_step/table_stepper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -90,10 +90,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,
Expand Down Expand Up @@ -158,10 +155,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);
Expand Down Expand Up @@ -200,10 +195,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.
Expand Down Expand Up @@ -309,13 +301,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);
Expand All @@ -329,7 +383,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();
Expand Down
4 changes: 3 additions & 1 deletion backends/p4tools/testgen/core/small_step/table_stepper.h
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ class TableStepper {
/// handle constant entries, it is specialized for control plane entries.
/// The function also tracks the list of field matches created to achieve a hit. We later use
/// this to insert table entries using the STF/PTF framework.
const IR::Expression* computeHit(ExecutionState* nextState, const IR::P4Table* table,
const IR::Expression* computeHit(ExecutionState* nextState,
std::map<cstring, const FieldMatch>* matches);

/// Collects properties that may be set per table. Target back end may have different semantics
Expand Down Expand Up @@ -185,6 +185,8 @@ class TableStepper {
/// execution. How a table is evaluated is target-specific.
virtual void evalTargetTable(const std::vector<const IR::ActionListElement*>& tableActionList);

void setTableDefaultEntries(const std::vector<const IR::ActionListElement*>& tableActionList);

public:
/// Table implementations in P4 are rather flexible. Eval is a delegation function that chooses
/// the right implementation depending on the properties of the table. For example, immutable
Expand Down
131 changes: 79 additions & 52 deletions backends/p4tools/testgen/targets/bmv2/backend/stf/stf.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,66 @@ inja::json STF::getTrace(const TestSpec* testSpec) {
return traceList;
}

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<Bmv2_V1ModelActionProfile>();
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<Bmv2_V1ModelActionSelector>();
apAsMap[actionProfile->getProfileDecl()->controlPlaneName()] =
actionSelector->getSelectorDecl()->controlPlaneName();
tblJson["has_as"] = true;
}
}
}

void collectActionProfiles(const TestSpec* testSpec, inja::json& controlPlaneJson,
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<Bmv2_V1ModelActionProfile>();
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);
}
if (apAsMap.find(actionProfile->getProfileDecl()->controlPlaneName()) != apAsMap.end()) {
j["selector"] = apAsMap[actionProfile->getProfileDecl()->controlPlaneName()];
}
controlPlaneJson["action_profiles"].push_back(j);
}
}

inja::json STF::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 All @@ -79,63 +134,30 @@ inja::json STF::getControlPlane(const TestSpec* testSpec) {
}

// 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;
}
checkForTableActionProfile(tblJson, apAsMap, tblConfig);

if (actionSelector != nullptr) {
tblJson["has_as"] = true;
}

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;
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;
const auto* defaultOverrideObj = tblConfig->getProperty("overriden_default_action", false);
if (defaultOverrideObj != nullptr) {
const auto* defaultAction = defaultOverrideObj->checkedTo<ActionCall>();
inja::json a;
a["action_name"] = actionName;
a["action_idx"] = std::to_string(idx);
a["action_name"] = defaultAction->getActionName();
auto const* actionArgs = defaultAction->getArgs();
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);
for (const auto& actArg : *actionArgs) {
inja::json j;
j["param"] = actArg.getActionParamName().c_str();
j["value"] = formatHexExpr(actArg.getEvaluatedValue());
b.push_back(j);
}
a["action_args"] = b;
j["actions"].push_back(a);
a["act_args"] = b;
tblJson["default_override"] = a;
}
if (apASMap.find(actionProfile->getProfileDecl()->controlPlaneName()) != apASMap.end()) {
j["selector"] = apASMap[actionProfile->getProfileDecl()->controlPlaneName()];
}
controlPlaneJson["action_profiles"].push_back(j);

controlPlaneJson["tables"].push_back(tblJson);
}

collectActionProfiles(testSpec, controlPlaneJson, apAsMap);

return controlPlaneJson;
}

Expand Down Expand Up @@ -302,9 +324,14 @@ static std::string getTestCase() {
## if control_plane
## for table in control_plane.tables
# Table {{table.table_name}}
## if existsIn(table, "default_override")
setdefault {{table.table_name}} {{table.default_override.action_name}}({% for a in table.default_override.act_args %}{{a.param}}:{{a.value}}{% if not loop.is_last %},{% endif %}{% endfor %})
## else
## for rule in table.rules
add {{table.table_name}} {% if rule.rules.needs_priority %}{{rule.priority}} {% endif %}{% for r in rule.rules.matches %}{{r.field_name}}:{{r.value}} {% endfor %}{{rule.action_name}}({% for a in rule.rules.act_args %}{{a.param}}:{{a.value}}{% if not loop.is_last %},{% endif %}{% endfor %})
## endfor
## endif
## endfor
## endif
Expand Down
31 changes: 19 additions & 12 deletions backends/p4tools/testgen/targets/bmv2/table_stepper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,6 @@ const IR::Expression* BMv2_V1ModelTableStepper::computeTargetMatchType(

void BMv2_V1ModelTableStepper::evalTableActionProfile(
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;
}
const auto* state = getExecutionState();

for (size_t idx = 0; idx < tableActionList.size(); idx++) {
Expand Down Expand Up @@ -123,7 +118,7 @@ void BMv2_V1ModelTableStepper::evalTableActionProfile(

// Now 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 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.
Expand Down Expand Up @@ -157,11 +152,6 @@ void BMv2_V1ModelTableStepper::evalTableActionProfile(

void BMv2_V1ModelTableStepper::evalTableActionSelector(
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;
}
const auto* state = getExecutionState();

for (size_t idx = 0; idx < tableActionList.size(); idx++) {
Expand Down Expand Up @@ -220,7 +210,7 @@ void BMv2_V1ModelTableStepper::evalTableActionSelector(

// Now 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 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.
Expand Down Expand Up @@ -371,6 +361,23 @@ void BMv2_V1ModelTableStepper::checkTargetProperties(

void BMv2_V1ModelTableStepper::evalTargetTable(
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) {
// Either override the default action or fall back to executing it.
auto testBackend = TestgenOptions::get().testBackend;
if (testBackend == "STF") {
setTableDefaultEntries(tableActionList);
} else {
::warning(
"Overriding default actions not supported for test back end %1%. Choosing default "
"action",
testBackend);
addDefaultAction(boost::none);
}
return;
}

// If the table is not constant, the default action can always be executed.
// This is because we can simply not enter any table entry.
boost::optional<const IR::Expression*> tableMissCondition = boost::none;
Expand Down
Loading

0 comments on commit 856c781

Please sign in to comment.