Skip to content

Commit

Permalink
Step 4: Use the P4Runtime API in the Protobuf back end. (p4lang#4303)
Browse files Browse the repository at this point in the history
  • Loading branch information
fruffy authored Feb 8, 2024
1 parent 16893b2 commit a47f821
Show file tree
Hide file tree
Showing 9 changed files with 68 additions and 104 deletions.
6 changes: 2 additions & 4 deletions backends/p4tools/modules/testgen/targets/bmv2/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ if(ENABLE_TESTING)
endif()

# Source files for p4testgen.
set(
TESTGEN_SOURCES
set(TESTGEN_SOURCES
${TESTGEN_SOURCES}
${CMAKE_CURRENT_SOURCE_DIR}/test_backend/common.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_backend/protobuf.cpp
Expand All @@ -31,8 +30,7 @@ set(
PARENT_SCOPE
)

set(
TESTGEN_GTEST_SOURCES
set(TESTGEN_GTEST_SOURCES
${TESTGEN_GTEST_SOURCES}
${CMAKE_CURRENT_SOURCE_DIR}/test/test_backend/ptf.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test/test_backend/stf.cpp
Expand Down
10 changes: 0 additions & 10 deletions backends/p4tools/modules/testgen/targets/bmv2/bmv2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@
#include "backends/bmv2/common/annotations.h"
#include "backends/p4tools/common/compiler/compiler_target.h"
#include "backends/p4tools/common/compiler/midend.h"
#include "control-plane/addMissingIds.h"
#include "control-plane/p4RuntimeArchStandard.h"
#include "frontends/common/options.h"
#include "lib/cstring.h"

Expand Down Expand Up @@ -109,17 +107,9 @@ 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(),
// Parse P4Runtime-specific annotations and insert missing IDs.
// Only do this for the protobuf back end.
TestgenOptions::get().testBackend == "PROTOBUF"
? new P4::AddMissingIdAnnotations(
refMap, typeMap, new P4::ControlPlaneAPI::Standard::V1ModelArchHandlerBuilder())
: nullptr,
});
midEnd.addDefaultPasses();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,10 @@ const BMv2V1ModelCompilerResult &Bmv2V1ModelProgramInfo::getCompilerResult() con
return *ProgramInfo::getCompilerResult().checkedTo<BMv2V1ModelCompilerResult>();
}

P4::P4RuntimeAPI Bmv2V1ModelProgramInfo::getP4RuntimeAPI() const {
return getCompilerResult().getP4RuntimeApi();
}

const IR::Member *Bmv2V1ModelProgramInfo::getParserParamVar(const IR::P4Parser *parser,
const IR::Type *type, size_t paramIndex,
cstring paramLabel) {
Expand Down
4 changes: 4 additions & 0 deletions backends/p4tools/modules/testgen/targets/bmv2/program_info.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <map>
#include <vector>

#include "control-plane/p4RuntimeSerializer.h"
#include "ir/ir.h"
#include "lib/cstring.h"
#include "lib/ordered_map.h"
Expand Down Expand Up @@ -40,6 +41,9 @@ class Bmv2V1ModelProgramInfo : public ProgramInfo {
/// @returns the gress associated with the given parser.
int getGress(const IR::Type_Declaration *) const;

/// @returns the P4Runtime API produced by the compiler.
[[nodiscard]] P4::P4RuntimeAPI getP4RuntimeAPI() const;

/// @returns the table associated with the direct extern
const IR::P4Table *getTableofDirectExtern(const IR::IDeclaration *directExternDecl) const;

Expand Down
2 changes: 1 addition & 1 deletion backends/p4tools/modules/testgen/targets/bmv2/target.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ const Bmv2V1ModelProgramInfo *Bmv2V1ModelTestgenTarget::produceProgramInfoImpl(
Bmv2TestBackend *Bmv2V1ModelTestgenTarget::getTestBackendImpl(
const ProgramInfo &programInfo, SymbolicExecutor &symbex,
const std::filesystem::path &testPath) const {
return new Bmv2TestBackend(programInfo, symbex, testPath);
return new Bmv2TestBackend(*programInfo.checkedTo<Bmv2V1ModelProgramInfo>(), symbex, testPath);
}

Bmv2V1ModelCmdStepper *Bmv2V1ModelTestgenTarget::getCmdStepperImpl(
Expand Down
10 changes: 4 additions & 6 deletions backends/p4tools/modules/testgen/targets/bmv2/test_backend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ const big_int Bmv2TestBackend::ZERO_PKT_MAX = 0xffffffff;
const std::set<std::string> Bmv2TestBackend::SUPPORTED_BACKENDS = {"PTF", "STF", "PROTOBUF",
"PROTOBUF_IR", "METADATA"};

Bmv2TestBackend::Bmv2TestBackend(const ProgramInfo &programInfo, SymbolicExecutor &symbex,
const std::filesystem::path &testPath)
Bmv2TestBackend::Bmv2TestBackend(const Bmv2V1ModelProgramInfo &programInfo,
SymbolicExecutor &symbex, const std::filesystem::path &testPath)
: TestBackEnd(programInfo, symbex) {
cstring testBackendString = TestgenOptions::get().testBackend;
if (testBackendString.isNullOrEmpty()) {
Expand All @@ -57,10 +57,8 @@ Bmv2TestBackend::Bmv2TestBackend(const ProgramInfo &programInfo, SymbolicExecuto
} else if (testBackendString == "STF") {
testWriter = new STF(testPath, seed);
} else if (testBackendString == "PROTOBUF") {
::warning(
"The PROTOBUF test back end is deprecated. "
"Please use the PROTOBUF_IR test back end, which uses P4_PDPI.");
testWriter = new Protobuf(testPath, seed);
auto p4RuntimeAPI = programInfo.getP4RuntimeAPI();
testWriter = new Protobuf(testPath, p4RuntimeAPI, seed);
} else if (testBackendString == "PROTOBUF_IR") {
testWriter = new ProtobufIr(testPath, seed);
} else if (testBackendString == "METADATA") {
Expand Down
3 changes: 2 additions & 1 deletion backends/p4tools/modules/testgen/targets/bmv2/test_backend.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "backends/p4tools/modules/testgen/lib/execution_state.h"
#include "backends/p4tools/modules/testgen/lib/test_backend.h"
#include "backends/p4tools/modules/testgen/lib/test_spec.h"
#include "backends/p4tools/modules/testgen/targets/bmv2/program_info.h"

namespace P4Tools::P4Testgen::Bmv2 {

Expand All @@ -35,7 +36,7 @@ class Bmv2TestBackend : public TestBackEnd {
static const std::set<std::string> SUPPORTED_BACKENDS;

public:
explicit Bmv2TestBackend(const ProgramInfo &programInfo, SymbolicExecutor &symbex,
explicit Bmv2TestBackend(const Bmv2V1ModelProgramInfo &programInfo, SymbolicExecutor &symbex,
const std::filesystem::path &testPath);

TestBackEnd::TestInfo produceTestInfo(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,13 @@
#include <boost/multiprecision/cpp_int.hpp>
#include <inja/inja.hpp>

#include "backends/p4tools/common/control_plane/p4info_map.h"
#include "backends/p4tools/common/lib/format_int.h"
#include "backends/p4tools/common/lib/util.h"
#include "control-plane/p4RuntimeArchHandler.h"
#include "ir/declaration.h"
#include "control-plane/p4RuntimeSerializer.h"
#include "ir/ir.h"
#include "ir/vector.h"
#include "lib/error.h"
#include "lib/error_catalog.h"
#include "lib/exceptions.h"
#include "lib/log.h"
#include "lib/null.h"
#include "nlohmann/json.hpp"

#include "backends/p4tools/modules/testgen/lib/exceptions.h"
Expand All @@ -31,54 +27,15 @@

namespace P4Tools::P4Testgen::Bmv2 {

/// Wrapper helper function that automatically inserts separators for hex strings.
std::string formatHexExprWithSep(const IR::Expression *expr) {
std::string Protobuf::formatHexExprWithSep(const IR::Expression *expr) {
return insertHexSeparators(formatHexExpr(expr, {false, true, false}));
}

Protobuf::Protobuf(std::filesystem::path basePath, std::optional<unsigned int> seed)
: Bmv2TestFramework(std::move(basePath), seed) {}

std::optional<p4rt_id_t> Protobuf::getIdAnnotation(const IR::IAnnotated *node) {
const auto *idAnnotation = node->getAnnotation("id");
if (idAnnotation == nullptr) {
return std::nullopt;
}
const auto *idConstant = idAnnotation->expr[0]->to<IR::Constant>();
CHECK_NULL(idConstant);
if (!idConstant->fitsUint()) {
::error(ErrorType::ERR_INVALID, "%1%: @id should be an unsigned integer", node);
return std::nullopt;
}
return static_cast<p4rt_id_t>(idConstant->value);
}

std::optional<p4rt_id_t> Protobuf::externalId(const P4::ControlPlaneAPI::P4RuntimeSymbolType &type,
const IR::IDeclaration *declaration) {
CHECK_NULL(declaration);
if (!declaration->is<IR::IAnnotated>()) {
return std::nullopt; // Assign an id later; see below.
}

// If the user specified an @id annotation, use that.
auto idOrNone = getIdAnnotation(declaration->to<IR::IAnnotated>());
if (!idOrNone) {
return std::nullopt; // the user didn't assign an id
}
auto id = *idOrNone;

// If the id already has an 8-bit type prefix, make sure it is correct for
// the resource type; otherwise assign the correct prefix.
const auto typePrefix = static_cast<p4rt_id_t>(type) << 24;
const auto prefixMask = static_cast<p4rt_id_t>(0xff) << 24;
if ((id & prefixMask) != 0 && (id & prefixMask) != typePrefix) {
::error(ErrorType::ERR_INVALID, "%1%: @id has the wrong 8-bit prefix", declaration);
return std::nullopt;
}
id |= typePrefix;

return id;
}
Protobuf::Protobuf(std::filesystem::path basePath, P4::P4RuntimeAPI p4RuntimeApi,
std::optional<unsigned int> seed)
: Bmv2TestFramework(std::move(basePath), seed),
p4RuntimeApi(p4RuntimeApi),
p4InfoMaps(P4::ControlPlaneAPI::P4InfoMaps(*p4RuntimeApi.p4Info)) {}

inja::json Protobuf::getControlPlane(const TestSpec *testSpec) const {
inja::json controlPlaneJson = inja::json::object();
Expand All @@ -92,28 +49,29 @@ inja::json Protobuf::getControlPlane(const TestSpec *testSpec) const {
}
for (auto const &testObject : tables) {
inja::json tblJson;
tblJson["table_name"] = testObject.first.c_str();
auto tableName = testObject.first;
tblJson["table_name"] = tableName;
const auto *const tblConfig = testObject.second->checkedTo<TableConfig>();
const auto *table = tblConfig->getTable();

auto p4RuntimeId = externalId(SymbolType::P4RT_TABLE(), table);
auto p4RuntimeId = p4InfoMaps.lookUpP4RuntimeId(table->controlPlaneName());
BUG_CHECK(p4RuntimeId, "Id not present for table %1%. Can not generate test.", table);
tblJson["id"] = *p4RuntimeId;

tblJson["id"] = p4RuntimeId.value();
const auto *tblRules = tblConfig->getRules();
tblJson["rules"] = inja::json::array();
for (const auto &tblRule : *tblRules) {
inja::json rule;
const auto *matches = tblRule.getMatches();
const auto *actionCall = tblRule.getActionCall();
const auto *actionDecl = actionCall->getAction();
cstring actionName = actionDecl->controlPlaneName();
const auto *actionArgs = actionCall->getArgs();
rule["action_name"] = actionCall->getActionName().c_str();
auto p4RuntimeId = externalId(SymbolType::P4RT_ACTION(), actionDecl);
auto p4RuntimeId = p4InfoMaps.lookUpP4RuntimeId(actionName);
BUG_CHECK(p4RuntimeId, "Id not present for action %1%. Can not generate test.",
actionDecl);
rule["action_id"] = *p4RuntimeId;
auto j = getControlPlaneForTable(*matches, *actionArgs);
rule["action_id"] = p4RuntimeId.value();

auto j = getControlPlaneForTable(tableName, actionName, *matches, *actionArgs);
rule["rules"] = std::move(j);
rule["priority"] = tblRule.getPriority();
tblJson["rules"].push_back(rule);
Expand All @@ -135,7 +93,8 @@ inja::json Protobuf::getControlPlane(const TestSpec *testSpec) const {
return controlPlaneJson;
}

inja::json Protobuf::getControlPlaneForTable(const TableMatchMap &matches,
inja::json Protobuf::getControlPlaneForTable(cstring tableName, cstring actionName,
const TableMatchMap &matches,
const std::vector<ActionArg> &args) const {
inja::json rulesJson;

Expand All @@ -154,9 +113,11 @@ inja::json Protobuf::getControlPlaneForTable(const TableMatchMap &matches,

inja::json j;
j["field_name"] = fieldName;
auto p4RuntimeId = getIdAnnotation(fieldMatch->getKey());
BUG_CHECK(p4RuntimeId, "Id not present for key. Can not generate test.");
j["id"] = *p4RuntimeId;
auto combinedFieldName = tableName + "_" + fieldName;
auto p4RuntimeId = p4InfoMaps.lookUpP4RuntimeId(combinedFieldName);
BUG_CHECK(p4RuntimeId.has_value(), "Id not present for key. Can not generate test.");
j["id"] = p4RuntimeId.value();

// Iterate over the match fields and segregate them.
if (const auto *elem = fieldMatch->to<Exact>()) {
j["value"] = formatHexExprWithSep(elem->getEvaluatedValue());
Expand Down Expand Up @@ -191,9 +152,10 @@ inja::json Protobuf::getControlPlaneForTable(const TableMatchMap &matches,
inja::json j;
j["param"] = actArg.getActionParamName().c_str();
j["value"] = formatHexExprWithSep(actArg.getEvaluatedValue());
auto p4RuntimeId = getIdAnnotation(actArg.getActionParam());
BUG_CHECK(p4RuntimeId, "Id not present for parameter. Can not generate test.");
j["id"] = *p4RuntimeId;
auto combinedParamName = actionName + "_" + actArg.getActionParamName();
auto p4RuntimeId = p4InfoMaps.lookUpP4RuntimeId(combinedParamName);
BUG_CHECK(p4RuntimeId.has_value(), "Id not present for parameter. Can not generate test.");
j["id"] = p4RuntimeId.value();
rulesJson["act_args"].push_back(j);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@

#include <inja/inja.hpp>

#include "control-plane/p4RuntimeArchHandler.h"
#include "backends/p4tools/common/control_plane/p4info_map.h"
#include "control-plane/p4RuntimeArchStandard.h"
#include "ir/declaration.h"
#include "control-plane/p4RuntimeSerializer.h"
#include "ir/ir.h"
#include "lib/cstring.h"

Expand All @@ -26,14 +26,28 @@ using P4::ControlPlaneAPI::Standard::SymbolType;
/// Extracts information from the @testSpec to emit a Protobuf test case.
class Protobuf : public Bmv2TestFramework {
public:
explicit Protobuf(std::filesystem::path basePath,
explicit Protobuf(std::filesystem::path basePath, P4::P4RuntimeAPI p4RuntimeApi,
std::optional<unsigned int> seed = std::nullopt);

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

private:
/// The P4Runtime API generated by the compiler. This API can be used to look up the id of
/// tables.
P4::P4RuntimeAPI p4RuntimeApi;

/// A mapping from P4 control plane names to their mapped P4Runtime ids and vice versa.
P4::ControlPlaneAPI::P4InfoMaps p4InfoMaps;

/// Wrapper helper function that automatically inserts separators for hex strings.
static std::string formatHexExprWithSep(const IR::Expression *expr);

/// Looks up the P4Runtime id for the given control plane name in the pre-computed P4Runtime-ID
/// map. @returns std::nullopt if the name is not in the map.
[[nodiscard]] std::optional<p4rt_id_t> lookupP4RuntimeId(cstring controlPlaneName) const;

/// Emits the test preamble. This is only done once for all generated tests.
/// For the protobuf back end this is the "p4testgen.proto" file.
void emitPreamble(const std::string &preamble);
Expand All @@ -53,19 +67,12 @@ class Protobuf : public Bmv2TestFramework {

inja::json getExpectedPacket(const TestSpec *testSpec) const override;

/// Helper function for the control plane table inja objects.
inja::json getControlPlaneForTable(const TableMatchMap &matches,
const std::vector<ActionArg> &args) const override;

/// @return the id allocated to the object through the @id annotation if any, or
/// std::nullopt.
static std::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 std::optional<p4rt_id_t> externalId(const P4::ControlPlaneAPI::P4RuntimeSymbolType &type,
const IR::IDeclaration *declaration);
/// The Protobuf back end needs the parent table and action name to correctly identify the
/// corresponding P4Runtme id. This is why we use a custom "getControlPlaneForTable" function
/// here.
[[nodiscard]] inja::json getControlPlaneForTable(cstring tableName, cstring actionName,
const TableMatchMap &matches,
const std::vector<ActionArg> &args) const;
};

} // namespace P4Tools::P4Testgen::Bmv2
Expand Down

0 comments on commit a47f821

Please sign in to comment.