Skip to content

Commit

Permalink
Basic support for @p4runtime_translation and `@p4runtime_translatio…
Browse files Browse the repository at this point in the history
…n_mappings`.
  • Loading branch information
fruffy-g authored and fruffy committed Feb 9, 2024
1 parent 9f24135 commit 3bd0b18
Show file tree
Hide file tree
Showing 8 changed files with 228 additions and 21 deletions.
10 changes: 8 additions & 2 deletions backends/bmv2/common/annotations.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,14 @@ class ParseAnnotations : public P4::ParseAnnotations {
public:
ParseAnnotations()
: P4::ParseAnnotations("BMV2", false,
{PARSE_EMPTY("metadata"), PARSE_EXPRESSION_LIST("field_list"),
PARSE("alias", StringLiteral), PARSE("priority", Constant)}) {}
{
PARSE_EMPTY("metadata"),
PARSE_EXPRESSION_LIST("field_list"),
PARSE("alias", StringLiteral),
PARSE("priority", Constant),
PARSE_EXPRESSION_LIST("p4runtime_translation_mappings"),
PARSE_P4RUNTIME_TRANSLATION("p4runtime_translation"),
}) {}
};

} // namespace BMV2
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ set(TESTGEN_SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/map_direct_externs.cpp
${CMAKE_CURRENT_SOURCE_DIR}/p4_asserts_parser.cpp
${CMAKE_CURRENT_SOURCE_DIR}/p4_refers_to_parser.cpp
${CMAKE_CURRENT_SOURCE_DIR}/p4runtime_translation.cpp
${CMAKE_CURRENT_SOURCE_DIR}/program_info.cpp
${CMAKE_CURRENT_SOURCE_DIR}/table_stepper.cpp
${CMAKE_CURRENT_SOURCE_DIR}/target.cpp
Expand Down
11 changes: 11 additions & 0 deletions backends/p4tools/modules/testgen/targets/bmv2/bmv2.cpp
Original file line number Diff line number Diff line change
@@ -1,19 +1,26 @@
#include "backends/p4tools/modules/testgen/targets/bmv2/bmv2.h"

#include <optional>
#include <string>
#include <utility>

#include "backends/bmv2/common/annotations.h"
#include "backends/p4tools/common/compiler/compiler_target.h"
#include "backends/p4tools/common/compiler/midend.h"
#include "frontends/common/options.h"
#include "frontends/common/resolveReferences/referenceMap.h"
#include "frontends/p4/typeChecking/typeChecker.h"
#include "frontends/p4/typeMap.h"
#include "lib/cstring.h"
#include "lib/error.h"
#include "midend/coverage.h"

#include "backends/p4tools/modules/testgen/core/compiler_target.h"
#include "backends/p4tools/modules/testgen/options.h"
#include "backends/p4tools/modules/testgen/targets/bmv2/map_direct_externs.h"
#include "backends/p4tools/modules/testgen/targets/bmv2/p4_asserts_parser.h"
#include "backends/p4tools/modules/testgen/targets/bmv2/p4_refers_to_parser.h"
#include "backends/p4tools/modules/testgen/targets/bmv2/p4runtime_translation.h"

namespace P4Tools::P4Testgen::Bmv2 {

Expand Down Expand Up @@ -107,9 +114,13 @@ CompilerResultOrError Bmv2V1ModelCompilerTarget::runCompilerImpl(

MidEnd Bmv2V1ModelCompilerTarget::mkMidEnd(const CompilerOptions &options) const {
MidEnd midEnd(options);
auto *refMap = midEnd.getRefMap();
auto *typeMap = midEnd.getTypeMap();
midEnd.addPasses({
// Parse BMv2-specific annotations.
new BMV2::ParseAnnotations(),
new P4::TypeChecking(refMap, typeMap, true),
new PropagateP4RuntimeTranslation(*typeMap),
});
midEnd.addDefaultPasses();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#include "backends/p4tools/modules/testgen/targets/bmv2/p4runtime_translation.h"

std::vector<const IR::Annotation *>
P4Tools::P4Testgen::Bmv2::PropagateP4RuntimeTranslation::lookupP4RuntimeAnnotations(
const P4::TypeMap &typeMap, const IR::Type *type) {
std::vector<const IR::Annotation *> p4RuntimeAnnotations;
const auto *typeName = type->to<IR::Type_Name>();
if (typeName != nullptr) {
type = typeMap.getType(typeName);
if (type == nullptr) {
::error("Type %1% not found in the type map.", typeName);
return p4RuntimeAnnotations;
}
type = type->getP4Type();
}
const auto *annotatedType = type->to<IR::IAnnotated>();
if (annotatedType == nullptr) {
return p4RuntimeAnnotations;
}
const auto *p4runtimeAnnotation = annotatedType->getAnnotation("p4runtime_translation");
if (p4runtimeAnnotation != nullptr) {
BUG_CHECK(!p4runtimeAnnotation->needsParsing,
"The @p4runtime_translation annotation should have been parsed already.");
p4RuntimeAnnotations.push_back(p4runtimeAnnotation);
}
const auto *p4runtimeTranslationMappings =
annotatedType->getAnnotation("p4runtime_translation_mappings");
if (p4runtimeTranslationMappings != nullptr) {
BUG_CHECK(
!p4runtimeTranslationMappings->needsParsing,
"The @p4runtime_translation_mappings annotation should have been parsed already.");
p4RuntimeAnnotations.push_back(p4runtimeTranslationMappings);
}
return p4RuntimeAnnotations;
}

const IR::Parameter *P4Tools::P4Testgen::Bmv2::PropagateP4RuntimeTranslation::preorder(
IR::Parameter *parameter) {
auto p4RuntimeAnnotations = lookupP4RuntimeAnnotations(_typeMap, parameter->type);
if (p4RuntimeAnnotations.empty()) {
return parameter;
}
auto *annotationsVector = parameter->annotations->clone();
for (const auto *p4runtimeAnnotation : p4RuntimeAnnotations) {
annotationsVector->annotations.push_back(p4runtimeAnnotation);
}
parameter->annotations = annotationsVector;
return parameter;
}

const IR::KeyElement *P4Tools::P4Testgen::Bmv2::PropagateP4RuntimeTranslation::preorder(
IR::KeyElement *keyElement) {
auto p4RuntimeAnnotations = lookupP4RuntimeAnnotations(_typeMap, keyElement->expression->type);
if (p4RuntimeAnnotations.empty()) {
return keyElement;
}
auto *annotationsVector = keyElement->annotations->clone();
for (const auto *p4runtimeAnnotation : p4RuntimeAnnotations) {
annotationsVector->annotations.push_back(p4runtimeAnnotation);
}
keyElement->annotations = annotationsVector;
return keyElement;
}

P4Tools::P4Testgen::Bmv2::PropagateP4RuntimeTranslation::PropagateP4RuntimeTranslation(
const P4::TypeMap &typeMap)
: _typeMap(typeMap) {
setName("PropagateP4RuntimeTranslation");
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#ifndef BACKENDS_P4TOOLS_MODULES_TESTGEN_TARGETS_BMV2_P4RUNTIME_TRANSLATION_H_
#define BACKENDS_P4TOOLS_MODULES_TESTGEN_TARGETS_BMV2_P4RUNTIME_TRANSLATION_H_
#include <functional>

#include "frontends/p4/typeMap.h"
#include "ir/ir.h"
#include "ir/visitor.h"

namespace P4Tools::P4Testgen::Bmv2 {

/// Propagates P4Runtime annotations attached to type definitions to the nodes which use these type
/// definitions. For now, this is restricted to key elements and action parameters.
class PropagateP4RuntimeTranslation : public Transform {
/// We use the typemap to look up the original declaration for type reference.
/// These declarations may have an annotation.
std::reference_wrapper<const P4::TypeMap> _typeMap;

/// Look up annotations relevant to P4Runtime. They may influence the control plane interfaces.
static std::vector<const IR::Annotation *> lookupP4RuntimeAnnotations(
const P4::TypeMap &typeMap, const IR::Type *type);

const IR::Parameter *preorder(IR::Parameter *parameter) override;
const IR::KeyElement *preorder(IR::KeyElement *keyElement) override;

public:
explicit PropagateP4RuntimeTranslation(const P4::TypeMap &typeMap);
};

} // namespace P4Tools::P4Testgen::Bmv2

#endif /* BACKENDS_P4TOOLS_MODULES_TESTGEN_TARGETS_BMV2_P4RUNTIME_TRANSLATION_H_ */
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@

#include <inja/inja.hpp>

#include "backends/p4tools/common/lib/format_int.h"
#include "backends/p4tools/common/lib/util.h"
#include "frontends/parsers/parserDriver.h"
#include "lib/exceptions.h"
#include "lib/log.h"
#include "nlohmann/json.hpp"
Expand All @@ -21,7 +23,54 @@ namespace P4Tools::P4Testgen::Bmv2 {
ProtobufIr::ProtobufIr(const TestBackendConfiguration &testBackendConfiguration)
: Bmv2TestFramework(testBackendConfiguration) {}

std::optional<std::string> ProtobufIr::checkForP4RuntimeTranslationAnnotation(
const IR::IAnnotated *node) {
const auto *p4RuntimeTranslationAnnotation = node->getAnnotation("p4runtime_translation");
if (p4RuntimeTranslationAnnotation == nullptr) {
return std::nullopt;
}
auto annotationVector = p4RuntimeTranslationAnnotation->expr;
BUG_CHECK(annotationVector.size() == 2, "Expected size of %1% to be 2. ", annotationVector);
const auto *targetValue = annotationVector.at(1);
if (targetValue->is<IR::StringLiteral>()) {
return "str";
}
// An integer technically specifies a width but that is not (yet) of any concern to us.
if (targetValue->is<IR::Constant>()) {
return "hex_str";
}
TESTGEN_UNIMPLEMENTED("Unsupported @p4runtime_translation token %1%", targetValue);
}

std::map<cstring, cstring> ProtobufIr::getP4RuntimeTranslationMappings(const IR::IAnnotated *node) {
std::map<cstring, cstring> p4RuntimeTranslationMappings;
const auto *p4RuntimeTranslationMappingAnnotation =
node->getAnnotation("p4runtime_translation_mappings");
if (p4RuntimeTranslationMappingAnnotation == nullptr) {
return p4RuntimeTranslationMappings;
}
BUG_CHECK(!p4RuntimeTranslationMappingAnnotation->needsParsing,
"The @p4runtime_translation_mappings annotation should have been parsed already.");
auto annotationExpr = p4RuntimeTranslationMappingAnnotation->expr;
BUG_CHECK(annotationExpr.size() == 1, "Expected size of %1% to be 1. ", annotationExpr);
const auto *exprList = annotationExpr.at(0)->checkedTo<IR::ListExpression>();
for (const auto *expr : exprList->components) {
const auto *exprTuple = expr->checkedTo<IR::ListExpression>();
const auto &components = exprTuple->components;
auto left = components.at(0)->checkedTo<IR::StringLiteral>()->value;
auto right = components.at(1)->checkedTo<IR::Constant>()->value;
p4RuntimeTranslationMappings.emplace(right.str().c_str(), left);
}

return p4RuntimeTranslationMappings;
}

std::string ProtobufIr::getFormatOfNode(const IR::IAnnotated *node) {
auto p4RuntimeTranslationFormat = checkForP4RuntimeTranslationAnnotation(node);
if (p4RuntimeTranslationFormat.has_value()) {
return p4RuntimeTranslationFormat.value();
}

const auto *formatAnnotation = node->getAnnotation("format");
if (formatAnnotation == nullptr) {
return "hex_str";
Expand Down Expand Up @@ -214,31 +263,49 @@ std::string ProtobufIr::formatNetworkValue(const std::string &type, const IR::Ex
TESTGEN_UNIMPLEMENTED("Unsupported network value type %1%", type);
}

std::string ProtobufIr::formatNetworkValue(const IR::IAnnotated *node, const std::string &type,
const IR::Expression *value) {
auto p4RuntimeTranslationMappings = getP4RuntimeTranslationMappings(node);
auto formattedNetworkValue = formatNetworkValue(type, value);

auto it = p4RuntimeTranslationMappings.find(formattedNetworkValue);
if (it != p4RuntimeTranslationMappings.end()) {
return (*it).second.c_str();
}

return formattedNetworkValue;
}

void ProtobufIr::createKeyMatch(cstring fieldName, const TableMatch &fieldMatch,
inja::json &rulesJson) {
inja::json j;
j["field_name"] = fieldName;
j["format"] = getFormatOfNode(fieldMatch.getKey());
const auto *keyElement = fieldMatch.getKey();
j["format"] = getFormatOfNode(keyElement);

if (const auto *elem = fieldMatch.to<Exact>()) {
j["value"] = formatNetworkValue(j["format"], elem->getEvaluatedValue()).c_str();
j["value"] = formatNetworkValue(keyElement, j["format"], elem->getEvaluatedValue()).c_str();
rulesJson["single_exact_matches"].push_back(j);
} else if (const auto *elem = fieldMatch.to<Range>()) {
j["lo"] = formatNetworkValue(j["format"], elem->getEvaluatedLow()).c_str();
j["hi"] = formatNetworkValue(j["format"], elem->getEvaluatedHigh()).c_str();
j["lo"] = formatNetworkValue(keyElement, j["format"], elem->getEvaluatedLow()).c_str();
j["hi"] = formatNetworkValue(keyElement, j["format"], elem->getEvaluatedHigh()).c_str();
rulesJson["range_matches"].push_back(j);
} else if (const auto *elem = fieldMatch.to<Ternary>()) {
j["value"] = formatNetworkValue(j["format"], elem->getEvaluatedValue()).c_str();
j["mask"] = formatNetworkValue(j["format"], elem->getEvaluatedMask()).c_str();
rulesJson["ternary_matches"].push_back(j);
// If the rule has a ternary match we need to add the priority.
rulesJson["needs_priority"] = true;
// Skip any ternary match where the mask is all zeroes.
if (elem->getEvaluatedMask()->value == 0) {
return;
}
j["value"] = formatNetworkValue(keyElement, j["format"], elem->getEvaluatedValue()).c_str();
j["mask"] = formatNetworkValue(keyElement, j["format"], elem->getEvaluatedMask()).c_str();
rulesJson["ternary_matches"].push_back(j);
} else if (const auto *elem = fieldMatch.to<LPM>()) {
j["value"] = formatNetworkValue(j["format"], elem->getEvaluatedValue()).c_str();
j["value"] = formatNetworkValue(keyElement, j["format"], elem->getEvaluatedValue()).c_str();
j["prefix_len"] = elem->getEvaluatedPrefixLength()->value.str();
rulesJson["lpm_matches"].push_back(j);
} else if (const auto *elem = fieldMatch.to<Optional>()) {
j["value"] = formatNetworkValue(j["format"], elem->getEvaluatedValue()).c_str();
j["value"] = formatNetworkValue(keyElement, j["format"], elem->getEvaluatedValue()).c_str();
if (elem->addAsExactMatch()) {
j["use_exact"] = "True";
} else {
Expand Down Expand Up @@ -273,10 +340,12 @@ inja::json ProtobufIr::getControlPlaneForTable(const TableMatchMap &matches,

for (const auto &actArg : args) {
inja::json j;
j["format"] = getFormatOfNode(actArg.getActionParam());
const auto *actionParameter = actArg.getActionParam();
j["format"] = getFormatOfNode(actionParameter);

j["param"] = actArg.getActionParamName().c_str();
j["value"] = formatNetworkValue(j["format"], actArg.getEvaluatedValue()).c_str();
j["value"] =
formatNetworkValue(actionParameter, j["format"], actArg.getEvaluatedValue()).c_str();
rulesJson["act_args"].push_back(j);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,24 +39,40 @@ class ProtobufIr : public Bmv2TestFramework {
private:
/// Emits a test case.
/// @param testId specifies the test name.
/// @param selectedBranches enumerates the choices the interpreter made for this path.
/// @param currentCoverage contains statistics about the current coverage of this test and its
/// preceding tests.
/// @param selectedBranches enumerates the choices the interpreter made for
/// this path.
/// @param currentCoverage contains statistics about the current coverage of
/// this test and its preceding tests.
void emitTestcase(const TestSpec *testSpec, cstring selectedBranches, size_t testId,
const std::string &testCase, float currentCoverage);

/// @returns the inja test case template as a string.
static std::string getTestCaseTemplate();

/// Tries to find the @format annotation of a node and, if present, returns the format specified
/// in this annotation. Returns "hex" by default.
/// Checks whether the node has a `@p4runtime_translation` attached to it. If
/// that is the case, returns the translated type contained within the
/// annotation.
static std::optional<std::string> checkForP4RuntimeTranslationAnnotation(
const IR::IAnnotated *node);

/// Looks up annotations for the given node and returns the P4RuntimeTranslationMappings,
/// if they exist. Currently, this map is a pure cstring map.
static std::map<cstring, cstring> getP4RuntimeTranslationMappings(const IR::IAnnotated *node);

/// Tries to find the @format annotation of a node and, if present, returns
/// the format specified in this annotation. Returns "hex" by default.
static std::string getFormatOfNode(const IR::IAnnotated *node);

/// Converts an IR::Expression into a formatted string value. The format depends on @param type.
/// Converts an IR::Expression into a formatted string value. The format
/// depends on @param type.
static std::string formatNetworkValue(const std::string &type, const IR::Expression *value);

/// Fill in @param rulesJson by iterating over @param fieldMatch and creating the appropriate
/// match key.
/// see @formatNetworkValue. The node may have annotations which influence the format.
static std::string formatNetworkValue(const IR::IAnnotated *node, const std::string &type,
const IR::Expression *value);

/// Fill in @param rulesJson by iterating over @param fieldMatch and creating
/// the appropriate match key.
static void createKeyMatch(cstring fieldName, const TableMatch &fieldMatch,
inja::json &rulesJson);
};
Expand Down
4 changes: 4 additions & 0 deletions frontends/p4/parseAnnotations.h
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ namespace P4 {
#define PARSE_STRING_LITERAL_LIST(aname) \
{ aname, &P4::ParseAnnotations::parseStringLiteralList }

// Parses a P4Runtime translation which contains both types or expressions.
#define PARSE_P4RUNTIME_TRANSLATION(aname) \
{ aname, &P4::ParseAnnotations::parseP4rtTranslationAnnotation }

class ParseAnnotations : public Modifier {
public:
using Modifier::postorder;
Expand Down

0 comments on commit 3bd0b18

Please sign in to comment.