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 11, 2022
1 parent c59597f commit 18993ee
Show file tree
Hide file tree
Showing 8 changed files with 269 additions and 90 deletions.
102 changes: 80 additions & 22 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 Expand Up @@ -509,14 +563,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;
}

std::vector<const IR::ActionListElement*> TableStepper::buildTableActionList() {
Expand Down Expand Up @@ -586,8 +644,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;
Expand Down
9 changes: 7 additions & 2 deletions backends/p4tools/testgen/core/small_step/table_stepper.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ class TableStepper {
/// Whether the table is constant and can not accept control plane entries.
bool tableIsImmutable = false;

/// Indicates whether the default action can be overridden.
bool defaultIsImmutable = false;

/// Ordered list of key fields with useful properties.
std::vector<KeyProperties> resolvedKeys;
} properties;
Expand Down Expand Up @@ -111,7 +114,7 @@ class TableStepper {
size_t lpmIndex);

/// @returns whether the table is immutable, meaning control plane entries can not be added.
bool checkTableIsImmutable();
void checkTableIsImmutable();

/// Add the default action path to the expression stepper. If only hits if @param
/// tableMissCondition is true.
Expand Down Expand Up @@ -142,7 +145,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 +188,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
Loading

0 comments on commit 18993ee

Please sign in to comment.