diff --git a/backends/p4tools/modules/testgen/core/small_step/extern_stepper.cpp b/backends/p4tools/modules/testgen/core/small_step/extern_stepper.cpp index 6ee414797e..4615961f9a 100644 --- a/backends/p4tools/modules/testgen/core/small_step/extern_stepper.cpp +++ b/backends/p4tools/modules/testgen/core/small_step/extern_stepper.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include diff --git a/backends/p4tools/modules/testgen/lib/execution_state.cpp b/backends/p4tools/modules/testgen/lib/execution_state.cpp index 692f63f19d..962adc18d6 100644 --- a/backends/p4tools/modules/testgen/lib/execution_state.cpp +++ b/backends/p4tools/modules/testgen/lib/execution_state.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -25,6 +26,7 @@ #include "ir/irutils.h" #include "lib/log.h" #include "lib/null.h" +#include "lib/ordered_map.h" #include "lib/source_file.h" #include "backends/p4tools/modules/testgen/lib/continuation.h" @@ -193,8 +195,7 @@ const TestObject *ExecutionState::getTestObject(cstring category, cstring object return nullptr; } -std::map ExecutionState::getTestObjectCategory( - cstring category) const { +TestObjectMap ExecutionState::getTestObjectCategory(cstring category) const { auto it = testObjects.find(category); if (it != testObjects.end()) { return it->second; @@ -202,6 +203,16 @@ std::map ExecutionState::getTestObjectCategory( return {}; } +void ExecutionState::deleteTestObject(cstring category, cstring objectLabel) { + auto it = testObjects.find(category); + if (it != testObjects.end()) { + return; + } + it->second.erase(objectLabel); +} + +void ExecutionState::deleteTestObjectCategory(cstring category) { testObjects.erase(category); } + void ExecutionState::setReachabilityEngineState(ReachabilityEngineState *newEngineState) { reachabilityEngineState = newEngineState; } diff --git a/backends/p4tools/modules/testgen/lib/execution_state.h b/backends/p4tools/modules/testgen/lib/execution_state.h index cf283219b5..6a9225b865 100644 --- a/backends/p4tools/modules/testgen/lib/execution_state.h +++ b/backends/p4tools/modules/testgen/lib/execution_state.h @@ -25,7 +25,7 @@ #include "midend/coverage.h" #include "backends/p4tools/modules/testgen/lib/continuation.h" -#include "backends/p4tools/modules/testgen/lib/test_spec.h" +#include "backends/p4tools/modules/testgen/lib/test_object.h" namespace P4Tools::P4Testgen { @@ -103,7 +103,7 @@ class ExecutionState { // which defines control plane match action entries. Once the interpreter has solved for the // variables used by these test objects and concretized the values, they can be used to generate // a test. Test objects are not constant because they may be manipulated by a target back end. - std::map> testObjects; + std::map testObjects; /// The parserErrorLabel is set by the parser to indicate the variable corresponding to the /// parser error that is set by various built-in functions such as verify or extract. @@ -217,29 +217,37 @@ class ExecutionState { [[nodiscard]] const TestObject *getTestObject(cstring category, cstring objectLabel, bool checked) const; + /// Remove a test object from a category. + void deleteTestObject(cstring category, cstring objectLabel); + + /// Remove a test category entirely. + void deleteTestObjectCategory(cstring category); + /// @returns the uninterpreted test object using the provided category and object label. If /// @param checked is enabled, a BUG is thrown if the object label does not exist. /// Also casts the test object to the specified type. If the type does not match, a BUG is /// thrown. template - [[nodiscard]] T *getTestObject(cstring category, cstring objectLabel, bool checked) const { - const auto *testObject = getTestObject(category, objectLabel, checked); + [[nodiscard]] const T *getTestObject(cstring category, cstring objectLabel) const { + const auto *testObject = getTestObject(category, objectLabel, true); return testObject->checkedTo(); } /// @returns the map of uninterpreted test objects for a specific test category. For example, /// all the table entries saved under "tableconfigs". - [[nodiscard]] std::map getTestObjectCategory( - cstring category) const; + [[nodiscard]] TestObjectMap getTestObjectCategory(cstring category) const; + /// Get the current state of the reachability engine. [[nodiscard]] ReachabilityEngineState *getReachabilityEngineState() const; + /// Update the reachability engine state. void setReachabilityEngineState(ReachabilityEngineState *newEngineState); /* ========================================================================================= * Trace events. * ========================================================================================= */ public: + /// Add a new trace event to the state. void add(const TraceEvent &event); /* ========================================================================================= diff --git a/backends/p4tools/modules/testgen/lib/test_object.h b/backends/p4tools/modules/testgen/lib/test_object.h new file mode 100644 index 0000000000..3145f3bd89 --- /dev/null +++ b/backends/p4tools/modules/testgen/lib/test_object.h @@ -0,0 +1,37 @@ +#ifndef BACKENDS_P4TOOLS_MODULES_TESTGEN_LIB_TEST_OBJECT_H_ +#define BACKENDS_P4TOOLS_MODULES_TESTGEN_LIB_TEST_OBJECT_H_ +#include + +#include "backends/p4tools/common/lib/model.h" +#include "lib/castable.h" +#include "lib/cstring.h" + +namespace P4Tools::P4Testgen { + +/* ========================================================================================= + * Abstract Test Object Class + * ========================================================================================= */ + +class TestObject : public ICastable { + public: + TestObject() = default; + ~TestObject() override = default; + TestObject(const TestObject &) = default; + TestObject(TestObject &&) = default; + TestObject &operator=(const TestObject &) = default; + TestObject &operator=(TestObject &&) = default; + + /// @returns the string name of this particular test object. + [[nodiscard]] virtual cstring getObjectName() const = 0; + + /// @returns a version of the test object where all expressions are resolved and symbolic + /// variables are substituted according to the mapping present in the @param model. + [[nodiscard]] virtual const TestObject *evaluate(const Model &model) const = 0; +}; + +/// A map of test objects. +using TestObjectMap = ordered_map; + +} // namespace P4Tools::P4Testgen + +#endif /* BACKENDS_P4TOOLS_MODULES_TESTGEN_LIB_TEST_OBJECT_H_ */ diff --git a/backends/p4tools/modules/testgen/lib/test_spec.cpp b/backends/p4tools/modules/testgen/lib/test_spec.cpp index b4985047e1..b1a53c2058 100644 --- a/backends/p4tools/modules/testgen/lib/test_spec.cpp +++ b/backends/p4tools/modules/testgen/lib/test_spec.cpp @@ -1,5 +1,6 @@ #include "backends/p4tools/modules/testgen/lib/test_spec.h" +#include #include #include #include @@ -11,6 +12,7 @@ #include "backends/p4tools/common/lib/trace_event.h" #include "ir/irutils.h" #include "lib/exceptions.h" +#include "lib/ordered_map.h" namespace P4Tools::P4Testgen { @@ -216,14 +218,12 @@ TableConfig::TableConfig(const IR::P4Table *table, std::vector rules) : table(table), rules(std::move(rules)) {} TableConfig::TableConfig(const IR::P4Table *table, std::vector rules, - std::map tableProperties) + TestObjectMap tableProperties) : table(table), rules(std::move(rules)), tableProperties(std::move(tableProperties)) {} const std::vector *TableConfig::getRules() const { return &rules; } -const std::map *TableConfig::getProperties() const { - return &tableProperties; -} +const TestObjectMap *TableConfig::getProperties() const { return &tableProperties; } const TestObject *TableConfig::getProperty(cstring propertyName, bool checked) const { auto it = tableProperties.find(propertyName); @@ -246,7 +246,7 @@ const TableConfig *TableConfig::evaluate(const Model &model) const { const auto *evaluatedRule = rule.evaluate(model); evaluatedRules.emplace_back(*evaluatedRule); } - std::map evaluatedProperties; + TestObjectMap evaluatedProperties; for (const auto &propertyTuple : tableProperties) { auto name = propertyTuple.first; const auto *property = propertyTuple.second; @@ -298,7 +298,7 @@ const TestObject *TestSpec::getTestObject(cstring category, cstring objectLabel, return nullptr; } -std::map TestSpec::getTestObjectCategory(cstring category) const { +TestObjectMap TestSpec::getTestObjectCategory(cstring category) const { auto it = testObjects.find(category); if (it != testObjects.end()) { return it->second; diff --git a/backends/p4tools/modules/testgen/lib/test_spec.h b/backends/p4tools/modules/testgen/lib/test_spec.h index f439befe59..f791849a0c 100644 --- a/backends/p4tools/modules/testgen/lib/test_spec.h +++ b/backends/p4tools/modules/testgen/lib/test_spec.h @@ -9,9 +9,10 @@ #include "backends/p4tools/common/lib/model.h" #include "backends/p4tools/common/lib/trace_event.h" #include "ir/ir.h" -#include "lib/castable.h" #include "lib/cstring.h" +#include "backends/p4tools/modules/testgen/lib/test_object.h" + namespace P4Tools::P4Testgen { /// This file defines a series of test objects which, in sum, produce an abstract test @@ -19,22 +20,6 @@ namespace P4Tools::P4Testgen { /// This test specification is reified into a concrete test specification by the /// individual test back ends of a target extension. -/* ========================================================================================= - * Abstract Test Object Class - * ========================================================================================= */ - -class TestObject : public ICastable { - public: - /// @returns the string name of this particular test object. - virtual cstring getObjectName() const = 0; - - virtual ~TestObject() = default; - - /// @returns a version of the test object where all expressions are resolved and symbolic - /// variables are substituted according to the mapping present in the @param model. - virtual const TestObject *evaluate(const Model &model) const = 0; -}; - /* ========================================================================================= * Packet * ========================================================================================= */ @@ -53,22 +38,22 @@ class Packet : public TestObject { public: Packet(int port, const IR::Expression *payload, const IR::Expression *payloadIgnoreMask); - const Packet *evaluate(const Model &model) const override; + [[nodiscard]] const Packet *evaluate(const Model &model) const override; - cstring getObjectName() const override; + [[nodiscard]] cstring getObjectName() const override; /// @returns the port that is associated with this packet. - int getPort() const; + [[nodiscard]] int getPort() const; /// @returns the payload of the packet, which, at this point, needs to be a constant. /// A BUG is thrown otherwise. - const IR::Constant *getEvaluatedPayload() const; + [[nodiscard]] const IR::Constant *getEvaluatedPayload() const; /// @returns the don't care mask of the packet, which, at this point, needs to be a constant. /// If a bit is set in the @payloadIgnoreMask, the corresponding bit in @payload /// is ignored. /// A BUG is thrown otherwise. - const IR::Constant *getEvaluatedPayloadMask() const; + [[nodiscard]] const IR::Constant *getEvaluatedPayloadMask() const; }; /* ========================================================================================= @@ -86,19 +71,19 @@ class ActionArg : public TestObject { public: ActionArg(const IR::Parameter *param, const IR::Expression *value); - const ActionArg *evaluate(const Model &model) const override; + [[nodiscard]] const ActionArg *evaluate(const Model &model) const override; - cstring getObjectName() const override; + [[nodiscard]] cstring getObjectName() const override; - const IR::Parameter *getActionParam() const; + [[nodiscard]] const IR::Parameter *getActionParam() const; /// @returns the parameter name associated with the action argument. - cstring getActionParamName() const; + [[nodiscard]] cstring getActionParamName() const; /// @returns input argument value, which at this point needs to be a constant. /// If the value is a bool, it is converted into a constant. /// A BUG is thrown otherwise. - const IR::Constant *getEvaluatedValue() const; + [[nodiscard]] const IR::Constant *getEvaluatedValue() const; }; class ActionCall : public TestObject { @@ -117,19 +102,19 @@ class ActionCall : public TestObject { ActionCall(const IR::P4Action *action, std::vector args); - const ActionCall *evaluate(const Model &model) const override; + [[nodiscard]] const ActionCall *evaluate(const Model &model) const override; - cstring getObjectName() const override; + [[nodiscard]] cstring getObjectName() const override; /// @returns the name of the action that is being called. If not otherwise specified, this is /// the control plane name. - cstring getActionName() const; + [[nodiscard]] cstring getActionName() const; /// @returns the action that is associated with this object. - const IR::P4Action *getAction() const; + [[nodiscard]] const IR::P4Action *getAction() const; /// @returns the arguments of this particular call. - const std::vector *getArgs() const; + [[nodiscard]] const std::vector *getArgs() const; }; class TableMatch : public TestObject { @@ -141,7 +126,7 @@ class TableMatch : public TestObject { explicit TableMatch(const IR::KeyElement *key); /// @returns the key associated with this object. - const IR::KeyElement *getKey() const; + [[nodiscard]] const IR::KeyElement *getKey() const; }; using TableMatchMap = std::map; @@ -158,19 +143,19 @@ class Ternary : public TableMatch { explicit Ternary(const IR::KeyElement *key, const IR::Expression *value, const IR::Expression *mask); - const Ternary *evaluate(const Model &model) const override; + [[nodiscard]] const Ternary *evaluate(const Model &model) const override; - cstring getObjectName() const override; + [[nodiscard]] cstring getObjectName() const override; /// @returns the value of the ternary object, which is matched with the key. At this point the /// value needs to be a constant. /// A BUG is thrown otherwise. - const IR::Constant *getEvaluatedValue() const; + [[nodiscard]] const IR::Constant *getEvaluatedValue() const; /// @returns the mask of the ternary object. At this point the /// value needs to be a constant. /// A BUG is thrown otherwise. - const IR::Constant *getEvaluatedMask() const; + [[nodiscard]] const IR::Constant *getEvaluatedMask() const; }; class LPM : public TableMatch { @@ -185,19 +170,19 @@ class LPM : public TableMatch { explicit LPM(const IR::KeyElement *key, const IR::Expression *value, const IR::Expression *prefixLength); - const LPM *evaluate(const Model &model) const override; + [[nodiscard]] const LPM *evaluate(const Model &model) const override; - cstring getObjectName() const override; + [[nodiscard]] cstring getObjectName() const override; /// @returns the value of the LPM object, which is matched with the key. At this point the /// value needs to be a constant. /// A BUG is thrown otherwise. - const IR::Constant *getEvaluatedValue() const; + [[nodiscard]] const IR::Constant *getEvaluatedValue() const; /// @returns the prefix of LPM, which describes how many bits of the value are matched. At this /// point the prefix is expected to be a constant. /// A BUG is thrown otherwise. - const IR::Constant *getEvaluatedPrefixLength() const; + [[nodiscard]] const IR::Constant *getEvaluatedPrefixLength() const; }; class Exact : public TableMatch { @@ -208,13 +193,13 @@ class Exact : public TableMatch { public: explicit Exact(const IR::KeyElement *key, const IR::Expression *value); - const Exact *evaluate(const Model &model) const override; + [[nodiscard]] const Exact *evaluate(const Model &model) const override; - cstring getObjectName() const override; + [[nodiscard]] cstring getObjectName() const override; /// @returns the match value. It is expected to be a constant at this point. /// A BUG is thrown otherwise. - const IR::Constant *getEvaluatedValue() const; + [[nodiscard]] const IR::Constant *getEvaluatedValue() const; }; class TableRule : public TestObject { @@ -232,21 +217,21 @@ class TableRule : public TestObject { public: TableRule(TableMatchMap matches, int priority, ActionCall action, int ttl); - const TableRule *evaluate(const Model &model) const override; + [[nodiscard]] const TableRule *evaluate(const Model &model) const override; - cstring getObjectName() const override; + [[nodiscard]] cstring getObjectName() const override; /// @returns the list of keys that need to match to execute the action. - const TableMatchMap *getMatches() const; + [[nodiscard]] const TableMatchMap *getMatches() const; /// @returns the priority of this entry. - int getPriority() const; + [[nodiscard]] int getPriority() const; /// @returns action that is called when the key matches. - const ActionCall *getActionCall() const; + [[nodiscard]] const ActionCall *getActionCall() const; /// @returns the time-to-live of this particular entry. - int getTTL() const; + [[nodiscard]] int getTTL() const; }; class TableConfig : public TestObject { @@ -258,29 +243,29 @@ class TableConfig : public TestObject { const std::vector rules; /// A map of table properties. For example, an action profile may be part of a table property. - std::map tableProperties; + TestObjectMap tableProperties; public: explicit TableConfig(const IR::P4Table *table, std::vector rules); explicit TableConfig(const IR::P4Table *table, std::vector rules, - std::map tableProperties); + TestObjectMap tableProperties); - const IR::P4Table *getTable() const; + [[nodiscard]] const IR::P4Table *getTable() const; - cstring getObjectName() const override; + [[nodiscard]] cstring getObjectName() const override; - const TableConfig *evaluate(const Model &model) const override; + [[nodiscard]] const TableConfig *evaluate(const Model &model) const override; /// @returns the table rules of this table. - const std::vector *getRules() const; + [[nodiscard]] const std::vector *getRules() const; /// @returns the properties associated with this table. - const std::map *getProperties() const; + [[nodiscard]] const TestObjectMap *getProperties() const; /// @returns a particular property based on @param propertyName. /// If @param checked is true, this will throw a BUG if @param propertyName is not in the list. - const TestObject *getProperty(cstring propertyName, bool checked) const; + [[nodiscard]] const TestObject *getProperty(cstring propertyName, bool checked) const; /// Add a table property to the table. void addTableProperty(cstring propertyName, const TestObject *property); @@ -304,7 +289,7 @@ class TestSpec { /// A map of additional properties associated with this test specification. /// For example, tables, registers, or action profiles. - std::map> testObjects; + std::map testObjects; public: TestSpec(Packet ingressPacket, std::optional egressPacket, @@ -317,32 +302,33 @@ class TestSpec { /// @returns a test object for the given category and objectlabel. /// Returns a BUG if checked is true and the object is not found. - const TestObject *getTestObject(cstring category, cstring objectLabel, bool checked) const; + [[nodiscard]] const TestObject *getTestObject(cstring category, cstring objectLabel, + bool checked) const; /// @returns the test object using the provided category and object label. If /// @param checked is enabled, a BUG is thrown if the object label does not exist. /// Also casts the test object to the specified type. If the type does not match, a BUG is /// thrown. template - T *getTestObject(cstring category, cstring objectLabel, bool checked) const { + [[nodiscard]] auto *getTestObject(cstring category, cstring objectLabel, bool checked) const { const auto *testObject = getTestObject(category, objectLabel, checked); return testObject->checkedTo(); } /// @returns the map of test objects for a given category. - std::map getTestObjectCategory(cstring category) const; + [[nodiscard]] TestObjectMap getTestObjectCategory(cstring category) const; /// @returns the input packet for this test. - const Packet *getIngressPacket() const; + [[nodiscard]] const Packet *getIngressPacket() const; /// @returns the list of tables that need to be configured for this test. - const std::map *getTables() const; + [[nodiscard]] const std::map *getTables() const; /// @returns the expected output packet for this test. - std::optional getEgressPacket() const; + [[nodiscard]] std::optional getEgressPacket() const; /// @returns the list of traces that has been executed - const std::vector> *getTraces() const; + [[nodiscard]] const std::vector> *getTraces() const; /// Priority definitions for LPM and ternary entries. static constexpr int NO_PRIORITY = -1; diff --git a/backends/p4tools/modules/testgen/lib/tf.h b/backends/p4tools/modules/testgen/lib/tf.h index 94403f78ec..0113f9fd85 100644 --- a/backends/p4tools/modules/testgen/lib/tf.h +++ b/backends/p4tools/modules/testgen/lib/tf.h @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -18,6 +19,7 @@ #include "ir/ir.h" #include "lib/cstring.h" +#include "backends/p4tools/modules/testgen/lib/test_object.h" #include "backends/p4tools/modules/testgen/lib/test_spec.h" namespace P4Tools::P4Testgen { diff --git a/backends/p4tools/modules/testgen/targets/bmv2/backend/metadata/metadata.cpp b/backends/p4tools/modules/testgen/targets/bmv2/backend/metadata/metadata.cpp index a85f712b2d..251c47eaf7 100644 --- a/backends/p4tools/modules/testgen/targets/bmv2/backend/metadata/metadata.cpp +++ b/backends/p4tools/modules/testgen/targets/bmv2/backend/metadata/metadata.cpp @@ -18,6 +18,7 @@ #include "lib/log.h" #include "nlohmann/json.hpp" +#include "backends/p4tools/modules/testgen/lib/test_object.h" #include "backends/p4tools/modules/testgen/lib/tf.h" #include "backends/p4tools/modules/testgen/targets/bmv2/test_spec.h" diff --git a/backends/p4tools/modules/testgen/targets/bmv2/backend/protobuf/protobuf.cpp b/backends/p4tools/modules/testgen/targets/bmv2/backend/protobuf/protobuf.cpp index 725f864004..708cc1d1dd 100644 --- a/backends/p4tools/modules/testgen/targets/bmv2/backend/protobuf/protobuf.cpp +++ b/backends/p4tools/modules/testgen/targets/bmv2/backend/protobuf/protobuf.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -28,6 +29,7 @@ #include "nlohmann/json.hpp" #include "backends/p4tools/modules/testgen/lib/exceptions.h" +#include "backends/p4tools/modules/testgen/lib/test_object.h" #include "backends/p4tools/modules/testgen/lib/tf.h" #include "backends/p4tools/modules/testgen/targets/bmv2/test_spec.h" diff --git a/backends/p4tools/modules/testgen/targets/bmv2/backend/ptf/ptf.cpp b/backends/p4tools/modules/testgen/targets/bmv2/backend/ptf/ptf.cpp index 1220978541..3c6ac5b6c8 100644 --- a/backends/p4tools/modules/testgen/targets/bmv2/backend/ptf/ptf.cpp +++ b/backends/p4tools/modules/testgen/targets/bmv2/backend/ptf/ptf.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -26,17 +27,22 @@ namespace P4Tools::P4Testgen::Bmv2 { PTF::PTF(std::filesystem::path basePath, std::optional seed = std::nullopt) : TF(std::move(basePath), seed) {} -inja::json::array_t PTF::getClone(const std::map &cloneInfos) { - auto cloneJson = inja::json::array_t(); - for (auto cloneInfoTuple : cloneInfos) { - inja::json cloneInfoJson; - const auto *cloneInfo = cloneInfoTuple.second->checkedTo(); - cloneInfoJson["session_id"] = cloneInfo->getEvaluatedSessionId()->asUint64(); - cloneInfoJson["clone_port"] = cloneInfo->getEvaluatedClonePort()->asInt(); - cloneInfoJson["cloned"] = cloneInfo->isClonedPacket(); - cloneJson.push_back(cloneInfoJson); +inja::json PTF::getClone(const TestObjectMap &cloneSpecs) { + auto cloneSpec = inja::json::object(); + auto cloneJsons = inja::json::array_t(); + auto hasClone = false; + for (auto cloneSpecTuple : cloneSpecs) { + inja::json cloneSpecJson; + const auto *cloneSpec = cloneSpecTuple.second->checkedTo(); + cloneSpecJson["session_id"] = cloneSpec->getEvaluatedSessionId()->asUint64(); + cloneSpecJson["clone_port"] = cloneSpec->getEvaluatedClonePort()->asInt(); + cloneSpecJson["cloned"] = cloneSpec->isClonedPacket(); + hasClone = hasClone || cloneSpec->isClonedPacket(); + cloneJsons.push_back(cloneSpecJson); } - return cloneJson; + cloneSpec["clone_pkts"] = cloneJsons; + cloneSpec["has_clone"] = hasClone; + return cloneSpec; } std::vector> PTF::getIgnoreMasks(const IR::Constant *mask) { @@ -308,9 +314,9 @@ class Test{{test_id}}(AbstractTest): ## endfor ## endfor ## endif -## if exists("clone_infos") -## for clone_info in clone_infos - self.insert_pre_clone_session({{clone_info.session_id}}, [{{clone_info.clone_port}}]) +## if exists("clone_specs") +## for clone_pkt in clone_specs.clone_pkts + self.insert_pre_clone_session({{clone_pkt.session_id}}, [{{clone_pkt.clone_port}}]) ## endfor ## endif @@ -330,13 +336,14 @@ class Test{{test_id}}(AbstractTest): ## for ignore_mask in verify.ignore_masks exp_pkt.set_do_not_care({{ignore_mask.0}}, {{ignore_mask.1}}) ## endfor -## if exists("clone_infos") -## for clone_info in clone_infos -## if clone_info.cloned - ptfutils.verify_packet(self, exp_pkt, {{clone_info.clone_port}}) -## else - ptfutils.verify_packet(self, exp_pkt, eg_port) +## if exists("clone_specs") +## for clone_pkt in clone_specs.clone_pkts +## if clone_pkt.cloned + ptfutils.verify_packet(self, exp_pkt, {{clone_pkt.clone_port}}) +##endif ##endfor +## if not clone_specs.has_clone + ptfutils.verify_packet(self, exp_pkt, eg_port) ##endif ## else ptfutils.verify_packet(self, exp_pkt, eg_port) @@ -374,9 +381,9 @@ void PTF::emitTestcase(const TestSpec *testSpec, cstring selectedBranches, size_ // Check whether this test has a clone configuration. // These are special because they require additional instrumentation and produce two output // packets. - auto cloneInfos = testSpec->getTestObjectCategory("clone_infos"); - if (!cloneInfos.empty()) { - dataJson["clone_infos"] = getClone(cloneInfos); + auto cloneSpecs = testSpec->getTestObjectCategory("clone_specs"); + if (!cloneSpecs.empty()) { + dataJson["clone_specs"] = getClone(cloneSpecs); } LOG5("PTF backend: emitting testcase:" << std::setw(4) << dataJson); diff --git a/backends/p4tools/modules/testgen/targets/bmv2/backend/ptf/ptf.h b/backends/p4tools/modules/testgen/targets/bmv2/backend/ptf/ptf.h index e0307b13e6..c8d9367fbd 100644 --- a/backends/p4tools/modules/testgen/targets/bmv2/backend/ptf/ptf.h +++ b/backends/p4tools/modules/testgen/targets/bmv2/backend/ptf/ptf.h @@ -4,7 +4,6 @@ #include #include #include -#include #include #include #include @@ -16,6 +15,7 @@ #include "ir/ir.h" #include "lib/cstring.h" +#include "backends/p4tools/modules/testgen/lib/test_object.h" #include "backends/p4tools/modules/testgen/lib/test_spec.h" #include "backends/p4tools/modules/testgen/lib/tf.h" @@ -72,7 +72,7 @@ class PTF : public TF { static inja::json getVerify(const TestSpec *testSpec); /// Returns the configuration for a cloned packet configuration. - static inja::json::array_t getClone(const std::map &cloneInfos); + static inja::json getClone(const TestObjectMap &cloneSpecs); /// Helper function for @getVerify. Matches the mask value against the input packet value and /// generates the appropriate ignore ranges. diff --git a/backends/p4tools/modules/testgen/targets/bmv2/backend/stf/stf.cpp b/backends/p4tools/modules/testgen/targets/bmv2/backend/stf/stf.cpp index 54033f503b..4a3d921f94 100644 --- a/backends/p4tools/modules/testgen/targets/bmv2/backend/stf/stf.cpp +++ b/backends/p4tools/modules/testgen/targets/bmv2/backend/stf/stf.cpp @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -195,15 +196,15 @@ inja::json STF::getVerify(const TestSpec *testSpec) { return verifyData; } -inja::json::array_t STF::getClone(const std::map &cloneInfos) { +inja::json::array_t STF::getClone(const TestObjectMap &cloneSpecs) { auto cloneJson = inja::json::array_t(); - for (auto cloneInfoTuple : cloneInfos) { - inja::json cloneInfoJson; - const auto *cloneInfo = cloneInfoTuple.second->checkedTo(); - cloneInfoJson["session_id"] = cloneInfo->getEvaluatedSessionId()->asUint64(); - cloneInfoJson["clone_port"] = cloneInfo->getEvaluatedClonePort()->asInt(); - cloneInfoJson["cloned"] = cloneInfo->isClonedPacket(); - cloneJson.push_back(cloneInfoJson); + for (auto cloneSpecTuple : cloneSpecs) { + inja::json cloneSpecJson; + const auto *cloneSpec = cloneSpecTuple.second->checkedTo(); + cloneSpecJson["session_id"] = cloneSpec->getEvaluatedSessionId()->asUint64(); + cloneSpecJson["clone_port"] = cloneSpec->getEvaluatedClonePort()->asInt(); + cloneSpecJson["cloned"] = cloneSpec->isClonedPacket(); + cloneJson.push_back(cloneSpecJson); } return cloneJson; } @@ -235,17 +236,17 @@ add {{table.table_name}} {% if rule.rules.needs_priority %}{{rule.priority}} {% ## endfor ## endif -## if exists("clone_infos") -## for clone_info in clone_infos -mirroring_add {{clone_info.session_id}} {{clone_info.clone_port}} +## if exists("clone_specs") +## for clone_spec in clone_specs +mirroring_add {{clone_spec.session_id}} {{clone_spec.clone_port}} packet {{send.ig_port}} {{send.pkt}} -## if clone_info.cloned +## if clone_spec.cloned ## if verify -expect {{clone_info.clone_port}} {{verify.exp_pkt}}$ +expect {{clone_spec.clone_port}} {{verify.exp_pkt}}$ expect {{verify.eg_port}} ## endif ## else -expect {{clone_info.clone_port}} +expect {{clone_spec.clone_port}} ## if verify expect {{verify.eg_port}} {{verify.exp_pkt}}$ ## endif @@ -285,9 +286,9 @@ void STF::emitTestcase(const TestSpec *testSpec, cstring selectedBranches, size_ // Check whether this test has a clone configuration. // These are special because they require additional instrumentation and produce two output // packets. - auto cloneInfos = testSpec->getTestObjectCategory("clone_infos"); - if (!cloneInfos.empty()) { - dataJson["clone_infos"] = getClone(cloneInfos); + auto cloneSpecs = testSpec->getTestObjectCategory("clone_specs"); + if (!cloneSpecs.empty()) { + dataJson["clone_specs"] = getClone(cloneSpecs); } LOG5("STF test back end: emitting testcase:" << std::setw(4) << dataJson); diff --git a/backends/p4tools/modules/testgen/targets/bmv2/backend/stf/stf.h b/backends/p4tools/modules/testgen/targets/bmv2/backend/stf/stf.h index d0240d4e82..a9cefb3284 100644 --- a/backends/p4tools/modules/testgen/targets/bmv2/backend/stf/stf.h +++ b/backends/p4tools/modules/testgen/targets/bmv2/backend/stf/stf.h @@ -12,6 +12,7 @@ #include "lib/cstring.h" +#include "backends/p4tools/modules/testgen/lib/test_object.h" #include "backends/p4tools/modules/testgen/lib/test_spec.h" #include "backends/p4tools/modules/testgen/lib/tf.h" @@ -58,7 +59,7 @@ class STF : public TF { static inja::json getVerify(const TestSpec *testSpec); /// Returns the configuration for a cloned packet configuration. - static inja::json::array_t getClone(const std::map &cloneInfos); + static inja::json::array_t getClone(const TestObjectMap &cloneSpecs); /// Helper function for the control plane table inja objects. static inja::json getControlPlaneForTable(const TableMatchMap &matches, diff --git a/backends/p4tools/modules/testgen/targets/bmv2/constants.cpp b/backends/p4tools/modules/testgen/targets/bmv2/constants.cpp index 90f82e6e18..e21a718bd5 100644 --- a/backends/p4tools/modules/testgen/targets/bmv2/constants.cpp +++ b/backends/p4tools/modules/testgen/targets/bmv2/constants.cpp @@ -1,3 +1,3 @@ #include "backends/p4tools/modules/testgen/targets/bmv2/constants.h" -namespace P4Tools::P4Testgen::Bmv2 {} +namespace P4Tools::P4Testgen::Bmv2 {} // namespace P4Tools::P4Testgen::Bmv2 diff --git a/backends/p4tools/modules/testgen/targets/bmv2/constants.h b/backends/p4tools/modules/testgen/targets/bmv2/constants.h index bdd6213a0f..914af672e8 100644 --- a/backends/p4tools/modules/testgen/targets/bmv2/constants.h +++ b/backends/p4tools/modules/testgen/targets/bmv2/constants.h @@ -26,9 +26,12 @@ class BMv2Constants { static constexpr uint64_t PKT_INSTANCE_TYPE_RECIRC = 0x0004; static constexpr uint64_t PKT_INSTANCE_TYPE_REPLICATION = 0x005; static constexpr uint64_t PKT_INSTANCE_TYPE_RESUBMIT = 0x006; + /// The session IDs for clone are limited to a specific range. + /// Details: https://github.com/p4lang/PI/pull/588 + static constexpr uint16_t CLONE_SESSION_ID_MIN = 1; + static constexpr uint16_t CLONE_SESSION_ID_MAX = 32767; /// Clone type is derived from v1model.p4 - static constexpr int CLONE_TYPE_I2E = 0x0000; - static constexpr int CLONE_TYPE_E2E = 0x0001; + enum CloneType { I2E = 0, E2E = 1 }; /// Other useful constants static constexpr int STF_MIN_PKT_SIZE = 22; static constexpr int ETH_HDR_SIZE = 112; diff --git a/backends/p4tools/modules/testgen/targets/bmv2/expr_stepper.cpp b/backends/p4tools/modules/testgen/targets/bmv2/expr_stepper.cpp index c318115570..473cdf0376 100644 --- a/backends/p4tools/modules/testgen/targets/bmv2/expr_stepper.cpp +++ b/backends/p4tools/modules/testgen/targets/bmv2/expr_stepper.cpp @@ -82,6 +82,215 @@ void Bmv2V1ModelExprStepper::resetPreservingFieldList(ExecutionState &nextState, } } +void Bmv2V1ModelExprStepper::processClone(const ExecutionState &state, + SmallStepEvaluator::Result &result) { + const auto *progInfo = getProgramInfo().checkedTo(); + // Pick a clone port var. It must adhere to the constraints of the target. + const auto *cloneInfo = state.getTestObject("clone_infos", "clone_info"); + const auto *sessionIdExpr = cloneInfo->getSessionId(); + const auto &preserveIndex = cloneInfo->getPreserveIndex(); + const auto &egressPortVar = programInfo.getTargetOutputPortVar(); + const auto &clonePortVar = + ToolsVariables::getSymbolicVariable(egressPortVar->type, 0, "clone_port_var"); + + uint64_t recirculateCount = 0; + if (state.hasProperty("recirculate_count")) { + recirculateCount = state.getProperty("recirculate_count"); + } + + // Pick a clone port var. It must adhere to the constraints of the target. + // Pick a value that is not equal to the normal egress port. + // TODO: Is this a reasonable restriction? Do we want to test this case? + // TODO: What about programs where we can not satisfy this restriction? Do we not generate + // tests? + // TODO: Circle back to this once we have modifiable state in extern steppers. + const Constraint *cond = new IR::Neq(egressPortVar, clonePortVar); + // PTF has a couple more restrictions because it uses P4Runtime. The session ID must be within + // a specific range. Otherwise no clone call can be produced. + if (TestgenOptions::get().testBackend == "PTF") { + cond = new IR::LAnd( + new IR::LAnd( + new IR::Leq(sessionIdExpr, IR::getConstant(sessionIdExpr->type, + BMv2Constants::CLONE_SESSION_ID_MAX)), + new IR::Geq(sessionIdExpr, IR::getConstant(sessionIdExpr->type, + BMv2Constants::CLONE_SESSION_ID_MIN))), + new IR::LAnd(cond, new IR::Lss(clonePortVar, IR::getConstant(clonePortVar->type, 8)))); + } + // clone methods have a default state where the packet continues as is. + { + auto &defaultState = state.clone(); + // Delete the stale clone info to free up some space and reset the clone_active flag. + defaultState.deleteTestObjectCategory("clone_infos"); + defaultState.setProperty("clone_active", false); + // Increment the recirculation count. + defaultState.setProperty("recirculate_count", ++recirculateCount); + // Attach the clone specification for test generation. + const auto *defaultCloneInfo = new Bmv2V1ModelCloneSpec(sessionIdExpr, clonePortVar, false); + defaultState.addTestObject("clone_specs", "clone_spec", defaultCloneInfo); + defaultState.popBody(); + result->emplace_back(cond, state, defaultState); + } + + // Use a nullptr state to avoid some code duplication... + ExecutionState *cloneState = nullptr; + std::vector cmds; + + // We need to set the instance type once we recirculate. + const auto *instanceBitType = IR::getBitType(32); + const auto *instanceTypeVar = new IR::Member( + instanceBitType, new IR::PathExpression("*standard_metadata"), "instance_type"); + const IR::Constant *instanceTypeConst = nullptr; + + auto cloneType = cloneInfo->getCloneType(); + if (cloneType == BMv2Constants::CloneType::I2E) { + cloneState = &cloneInfo->getClonedState().clone(); + instanceTypeConst = + IR::getConstant(instanceBitType, BMv2Constants::PKT_INSTANCE_TYPE_INGRESS_CLONE); + const auto *progInfo = getProgramInfo().checkedTo(); + // Reset the packet buffer, which corresponds to the output packet. + // We need to reset everything to the state before the ingress call. We use a + // trick by calling copyIn on the entire state again. We need a little bit of + // information for that, including the exact parameter names of the ingress + // block we are in. Just grab the ingress from the programmable blocks. + const auto *programmableBlocks = progInfo->getProgrammableBlocks(); + const auto *typeDecl = programmableBlocks->at("Ingress"); + const auto *applyBlock = typeDecl->checkedTo(); + const auto *params = applyBlock->getApplyParameters(); + auto blockIndex = 2; + const auto *archSpec = TestgenTarget::getArchSpec(); + const auto *archMember = archSpec->getArchMember(blockIndex); + if (preserveIndex.has_value()) { + for (size_t paramIdx = 0; paramIdx < params->size(); ++paramIdx) { + const auto *param = params->getParameter(paramIdx); + // Skip the second parameter (metadata) since we do want to preserve + // it. + if (paramIdx == 1) { + // This program segment resets the user metadata of the v1model + // program to 0. However, fields in the user metadata that have the + // field_list annotation and the appropriate index will not be + // reset. The user metadata is the second parameter of the ingress + // control. + const auto *paramType = param->type; + if (const auto *tn = paramType->to()) { + paramType = cloneState->resolveType(tn); + } + const auto *paramRef = + new IR::PathExpression(paramType, new IR::Path(param->name)); + resetPreservingFieldList(*cloneState, paramRef, preserveIndex.value()); + continue; + } + programInfo.produceCopyInOutCall(param, paramIdx, archMember, &cmds, nullptr); + } + } else { + for (size_t paramIdx = 0; paramIdx < params->size(); ++paramIdx) { + const auto *param = params->getParameter(paramIdx); + programInfo.produceCopyInOutCall(param, paramIdx, archMember, &cmds, nullptr); + } + } + // We then exit, which will copy out all the state that we have just reset. + cmds.emplace_back(new IR::ExitStatement()); + } else if (cloneType == BMv2Constants::CloneType::E2E) { + cloneState = &state.clone(); + instanceTypeConst = + IR::getConstant(instanceBitType, BMv2Constants::PKT_INSTANCE_TYPE_EGRESS_CLONE); + if (preserveIndex.has_value()) { + // This program segment resets the user metadata of the v1model program to + // 0. However, fields in the user metadata that have the field_list + // annotation and the appropriate index will not be reset. The user + // metadata is the third parameter of the parser control. + const auto *paramPath = progInfo->getBlockParam("Parser", 2); + resetPreservingFieldList(*cloneState, paramPath, preserveIndex.value()); + } + + // In the other state, we start processing from the egress. + const auto *topLevelBlocks = progInfo->getPipelineSequence(); + size_t egressDelim = 0; + for (; egressDelim < topLevelBlocks->size(); ++egressDelim) { + auto block = topLevelBlocks->at(egressDelim); + if (!std::holds_alternative(block)) { + continue; + } + const auto *p4Node = std::get(block); + if (const auto *ctrl = p4Node->to()) { + if (progInfo->getGress(ctrl) == BMV2_EGRESS) { + break; + } + } + } + cmds.insert(cmds.begin(), topLevelBlocks->begin() + egressDelim - 2, topLevelBlocks->end()); + } else { + TESTGEN_UNIMPLEMENTED("Unsupported clone type %1%.", cloneType); + } + // Set the metadata instance types. + cloneState->set(instanceTypeVar, instanceTypeConst); + // Attach the clone specification for test generation. + cloneState->addTestObject("clone_specs", "clone_spec", + new Bmv2V1ModelCloneSpec(sessionIdExpr, clonePortVar, true)); + // Delete the stale clone info to free up some space and reset the clone_active flag. + cloneState->setProperty("clone_active", false); + cloneState->deleteTestObjectCategory("clone_infos"); + // Increment the recirculation count. + cloneState->setProperty("recirculate_count", ++recirculateCount); + /// Reset the packet buffer for recirculation. + cloneState->resetPacketBuffer(); + cloneState->replaceTopBody(&cmds); + result->emplace_back(cond, state, *cloneState); +} + +void Bmv2V1ModelExprStepper::processRecirculate(const ExecutionState &state, + SmallStepEvaluator::Result &result) { + auto instanceType = state.getProperty("recirculate_instance_type"); + const auto *progInfo = getProgramInfo().checkedTo(); + auto &recState = state.clone(); + + // Check whether the packet needs to be reset. + // If that is the case, reset the packet buffer to the calculated input packet. + auto recirculateReset = state.hasProperty("recirculate_reset_pkt"); + if (recirculateReset) { + // Reset the packet buffer, which corresponds to the output packet. + recState.resetPacketBuffer(); + // Set the packet buffer to the current calculated program packet for consistency. + recState.appendToPacketBuffer(recState.getInputPacket()); + } + + // We need to update the size of the packet when recirculating. Do not forget to divide + // by 8. + const auto *packetSizeVar = + new IR::Member(&PacketVars::PACKET_SIZE_VAR_TYPE, + new IR::PathExpression("*standard_metadata"), "packet_length"); + const auto *packetSizeConst = + IR::getConstant(&PacketVars::PACKET_SIZE_VAR_TYPE, recState.getPacketBufferSize() / 8); + recState.set(packetSizeVar, packetSizeConst); + + if (recState.hasProperty("recirculate_index")) { + // Get the index set by the recirculate/resubmit function. Will fail if no index is + // set. + auto recirculateIndex = recState.getProperty("recirculate_index"); + // This program segment resets the user metadata of the v1model program to 0. + // However, fields in the user metadata that have the field_list annotation and the + // appropriate index will not be reset. + // The user metadata is the third parameter of the parser control. + const auto *paramPath = progInfo->getBlockParam("Parser", 2); + resetPreservingFieldList(recState, paramPath, recirculateIndex); + } + + // Update the metadata variable to the correct instance type as provided by + // recirculation. + const auto *bitType = IR::getBitType(32); + const auto *instanceTypeVar = + new IR::Member(bitType, new IR::PathExpression("*standard_metadata"), "instance_type"); + recState.set(instanceTypeVar, IR::getConstant(bitType, instanceType)); + + // Set recirculate to false to avoid infinite loops. + recState.setProperty("recirculate_active", false); + + // "Recirculate" by attaching the sequence again. + // Does NOT initialize state or adds new conditions. + const auto *topLevelBlocks = progInfo->getPipelineSequence(); + recState.replaceTopBody(topLevelBlocks); + result->emplace_back(recState); +} + Bmv2V1ModelExprStepper::Bmv2V1ModelExprStepper(ExecutionState &state, AbstractSolver &solver, const ProgramInfo &programInfo) : ExprStepper(state, solver, programInfo) {} @@ -142,7 +351,8 @@ void Bmv2V1ModelExprStepper::evalExternMethodCall(const IR::MethodCallExpression /* ====================================================================================== * mark_to_drop * Mark to drop sets the BMv2 internal drop variable to true. - * ====================================================================================== */ + * ====================================================================================== + */ // TODO: Implement extern path expression calls. {"*method.mark_to_drop", {"standard_metadata"}, @@ -169,7 +379,8 @@ void Bmv2V1ModelExprStepper::evalExternMethodCall(const IR::MethodCallExpression * random * Generate a random number in the range lo..hi, inclusive, and write * it to the result parameter. - * ====================================================================================== */ + * ====================================================================================== + */ {"*method.random", {"result", "lo", "hi"}, [this](const IR::MethodCallExpression * /*call*/, const IR::Expression * /*receiver*/, @@ -240,7 +451,8 @@ void Bmv2V1ModelExprStepper::evalExternMethodCall(const IR::MethodCallExpression * statements when compiled to a target device is that if the * condition ever evaluates to false when operating in a network, it * is likely that your assumption was wrong, and should be reexamined. - * ====================================================================================== */ + * ====================================================================================== + */ {"*method.assume", {"check"}, assertAssumeExecute}, /* ====================================================================================== * assert @@ -263,14 +475,16 @@ void Bmv2V1ModelExprStepper::evalExternMethodCall(const IR::MethodCallExpression * allow you to do this with no warning given. We recommend this * because, if you follow this advice, your program will behave the * same way when assert statements are removed. - * ====================================================================================== */ + * ====================================================================================== + */ {"*method.assert", {"check"}, assertAssumeExecute}, /* ====================================================================================== * log_msg * Log user defined messages * Example: log_msg("User defined message"); * or log_msg("Value1 = {}, Value2 = {}",{value1, value2}); - * ====================================================================================== */ + * ====================================================================================== + */ {"*method.log_msg", {"msg", "args"}, [](const IR::MethodCallExpression * /*call*/, const IR::Expression * /*receiver*/, @@ -320,11 +534,12 @@ void Bmv2V1ModelExprStepper::evalExternMethodCall(const IR::MethodCallExpression * different from, each other, and thus their bit widths are allowed * to be different. * @param O Must be a type bit - * @param D Must be a tuple type where all the fields are bit-fields (type bit - * or int) or varbits. + * @param D Must be a tuple type where all the fields are bit-fields (type + * bit or int) or varbits. * @param T Must be a type bit * @param M Must be a type bit - * ====================================================================================== */ + * ====================================================================================== + */ {"*method.hash", {"result", "algo", "base", "data", "max"}, [this](const IR::MethodCallExpression *call, const IR::Expression *receiver, IR::ID &name, @@ -406,7 +621,8 @@ void Bmv2V1ModelExprStepper::evalExternMethodCall(const IR::MethodCallExpression * value of result is not specified, and should be * ignored by the caller. * - * ====================================================================================== */ + * ====================================================================================== + */ {"register.read", {"result", "index"}, [this](const IR::MethodCallExpression *call, const IR::Expression *receiver, @@ -440,9 +656,10 @@ void Bmv2V1ModelExprStepper::evalExternMethodCall(const IR::MethodCallExpression const auto *receiverPath = receiver->checkedTo(); const auto &externInstance = state.convertPathExpr(receiverPath); - // Retrieve the register state from the object store. If it is already present, just - // cast the object to the correct class and retrieve the current value according to the - // index. If the register has not been added had, create a new register object. + // Retrieve the register state from the object store. If it is already present, + // just cast the object to the correct class and retrieve the current value + // according to the index. If the register has not been added had, create a new + // register object. const auto *registerState = state.getTestObject("registervalues", externInstance->toString(), false); const Bmv2RegisterValue *registerValue = nullptr; @@ -458,9 +675,9 @@ void Bmv2V1ModelExprStepper::evalExternMethodCall(const IR::MethodCallExpression const IR::Expression *baseExpr = registerValue->getCurrentValue(index); if (readOutput->type->is()) { - // We need an assignment statement (and the inefficient copy) here because we need - // to immediately resolve the generated mux into multiple branches. - // This is only possible because registers do not return a value. + // We need an assignment statement (and the inefficient copy) here because we + // need to immediately resolve the generated mux into multiple branches. This + // is only possible because registers do not return a value. replacements.emplace_back(new IR::AssignmentStatement(readOutput, baseExpr)); } else { @@ -501,7 +718,8 @@ void Bmv2V1ModelExprStepper::evalExternMethodCall(const IR::MethodCallExpression * parameter's value is written into the register * array element specified by index. * - * ====================================================================================== */ + * ====================================================================================== + */ {"register.write", {"index", "value"}, [this](const IR::MethodCallExpression *call, const IR::Expression *receiver, @@ -512,7 +730,8 @@ void Bmv2V1ModelExprStepper::evalExternMethodCall(const IR::MethodCallExpression if (!(inputValue->type->is() || inputValue->type->is())) { TESTGEN_UNIMPLEMENTED( - "Only registers with bit or int types are currently supported for v1model."); + "Only registers with bit or int types are currently supported for " + "v1model."); } for (size_t idx = 0; idx < args->size(); ++idx) { const auto *arg = args->at(idx); @@ -545,8 +764,9 @@ void Bmv2V1ModelExprStepper::evalExternMethodCall(const IR::MethodCallExpression index->dbprint(registerStream); nextState.add(*new TraceEvents::Generic(registerStream.str())); - // "Write" to the register by update the internal test object state. If the register - // did not exist previously, update it with the value to write as initial value. + // "Write" to the register by update the internal test object state. If the + // register did not exist previously, update it with the value to write as initial + // value. const auto *registerState = nextState.getTestObject("registervalues", externInstance->toString(), false); Bmv2RegisterValue *registerValue = nullptr; @@ -589,7 +809,8 @@ void Bmv2V1ModelExprStepper::evalExternMethodCall(const IR::MethodCallExpression * updated, normally a value in the range [0, * size-1]. If index >= size, no counter state will be * updated. - * ====================================================================================== */ + * ====================================================================================== + */ // TODO: Count currently has no effect in the symbolic interpreter. {"counter.count", {"index"}, @@ -626,7 +847,8 @@ void Bmv2V1ModelExprStepper::evalExternMethodCall(const IR::MethodCallExpression * written back, atomically relative to the processing of other * packets, regardless of whether the count() method is called in * the body of that action. - * ====================================================================================== */ + * ====================================================================================== + */ // TODO: Count currently has no effect in the symbolic interpreter. {"direct_counter.count", {}, @@ -668,7 +890,8 @@ void Bmv2V1ModelExprStepper::evalExternMethodCall(const IR::MethodCallExpression * meaning of these colors). When index is out of * range, the final value of result is not specified, * and should be ignored by the caller. - * ====================================================================================== */ + * ====================================================================================== + */ // TODO: Read currently has no effect in the symbolic interpreter. {"meter.execute_meter", {"index", "result"}, @@ -709,7 +932,8 @@ void Bmv2V1ModelExprStepper::evalExternMethodCall(const IR::MethodCallExpression * result will be assigned 0 for color GREEN, 1 for * color YELLOW, and 2 for color RED (see RFC 2697 * and RFC 2698 for the meaning of these colors). - * ====================================================================================== */ + * ====================================================================================== + */ // TODO: Read currently has no effect in the symbolic interpreter. {"direct_meter.read", {"result"}, @@ -745,7 +969,8 @@ void Bmv2V1ModelExprStepper::evalExternMethodCall(const IR::MethodCallExpression * * The BMv2 implementation of the v1model architecture ignores the * value of the receiver parameter. - * ====================================================================================== */ + * ====================================================================================== + */ {"*method.digest", {"receiver", "data"}, [](const IR::MethodCallExpression * /*call*/, const IR::Expression * /*receiver*/, @@ -790,19 +1015,18 @@ void Bmv2V1ModelExprStepper::evalExternMethodCall(const IR::MethodCallExpression * clone during a single execution of the same ingress (or egress) * control, only the last clone session and index are used. See the * v1model architecture documentation (Note 1) for more details. - * ====================================================================================== */ + * ====================================================================================== + */ {"*method.clone_preserving_field_list", {"type", "session", "data"}, - [this](const IR::MethodCallExpression *call, const IR::Expression * /*receiver*/, - IR::ID & /*methodName*/, const IR::Vector *args, - const ExecutionState &state, SmallStepEvaluator::Result &result) { - uint64_t recirculateCount = 0; + [](const IR::MethodCallExpression *call, const IR::Expression * /*receiver*/, + IR::ID & /*methodName*/, const IR::Vector *args, + const ExecutionState &state, SmallStepEvaluator::Result &result) { // Grab the recirculate count. Stop after more than 1 circulation loop to avoid // infinite recirculation loops. // TODO: Determine the exact count. if (state.hasProperty("recirculate_count")) { - recirculateCount = state.getProperty("recirculate_count"); - if (recirculateCount > 1) { + if (state.getProperty("recirculate_count") > 0) { auto &nextState = state.clone(); ::warning("Only single recirculation supported for now. Dropping packet."); auto *dropStmt = new IR::MethodCallStatement( @@ -845,103 +1069,18 @@ void Bmv2V1ModelExprStepper::evalExternMethodCall(const IR::MethodCallExpression auto cloneType = args->at(0)->expression->checkedTo()->asUint64(); const auto *sessionIdExpr = args->at(1)->expression; - auto recirculateIndex = args->at(2)->expression->checkedTo()->asUint64(); - std::optional cond = std::nullopt; - - if (cloneType == BMv2Constants::CLONE_TYPE_I2E) { - // Pick a clone port var. For now, pick a random value from 0-511. - const auto &egressPortVar = programInfo.getTargetOutputPortVar(); - const auto &clonePortVar = ToolsVariables::getSymbolicVariable( - egressPortVar->type, 0, "clone_port_var" + std::to_string(call->clone_id)); - cond = new IR::LAnd( - new IR::Neq(egressPortVar, clonePortVar), - new IR::Lss(clonePortVar, IR::getConstant(clonePortVar->type, 8))); - // clone_preserving_field_list has a default state where the packet continues as - // is. - { - auto &defaultState = state.clone(); - const auto *cloneInfo = new Bmv2_CloneInfo(sessionIdExpr, clonePortVar, false); - defaultState.addTestObject("clone_infos", - std::to_string(sessionIdExpr->clone_id), cloneInfo); - defaultState.popBody(); - result->emplace_back(cond, state, defaultState); - } - // This is the clone state. - auto &nextState = state.clone(); - - // We need to reset everything to the state before the ingress call. We use a trick - // by calling copyIn on the entire state again. We need a little bit of information - // for that, including the exact parameter names of the ingress block we are in. - // Just grab the ingress from the programmable blocks. - const auto *progInfo = getProgramInfo().checkedTo(); - const auto *programmableBlocks = progInfo->getProgrammableBlocks(); - const auto *typeDecl = programmableBlocks->at("Ingress"); - const auto *applyBlock = typeDecl->checkedTo(); - const auto *params = applyBlock->getApplyParameters(); - auto blockIndex = 2; - const auto *archSpec = TestgenTarget::getArchSpec(); - const auto *archMember = archSpec->getArchMember(blockIndex); - std::vector cmds; - for (size_t paramIdx = 0; paramIdx < params->size(); ++paramIdx) { - const auto *param = params->getParameter(paramIdx); - // Skip the second parameter (metadata) since we do want to preserve it. - if (paramIdx == 1) { - // This program segment resets the user metadata of the v1model program to - // 0. However, fields in the user metadata that have the field_list - // annotation and the appropriate index will not be reset. - // The user metadata is the second parameter of the ingress control. - const auto *paramType = param->type; - if (const auto *tn = paramType->to()) { - paramType = nextState.resolveType(tn); - } - const auto *paramRef = - new IR::PathExpression(paramType, new IR::Path(param->name)); - resetPreservingFieldList(nextState, paramRef, recirculateIndex); - continue; - } - programInfo.produceCopyInOutCall(param, paramIdx, archMember, &cmds, nullptr); - } - // We then exit, which will copy out all the state that we have just reset. - cmds.emplace_back(new IR::ExitStatement()); - - const auto *cloneInfo = new Bmv2_CloneInfo(sessionIdExpr, clonePortVar, true); - nextState.addTestObject("clone_infos", std::to_string(sessionIdExpr->clone_id), - cloneInfo); - // Reset the packet buffer, which corresponds to the output packet. - nextState.resetPacketBuffer(); - const auto *bitType = IR::getBitType(32); - const auto *instanceTypeVar = new IR::Member( - bitType, new IR::PathExpression("*standard_metadata"), "instance_type"); - nextState.set( - instanceTypeVar, - IR::getConstant(bitType, BMv2Constants::PKT_INSTANCE_TYPE_INGRESS_CLONE)); - nextState.replaceTopBody(&cmds); - result->emplace_back(cond, state, nextState); - return; - } - - if (cloneType == BMv2Constants::CLONE_TYPE_E2E) { - auto &nextState = state.clone(); - // Increment the recirculation count. - nextState.setProperty("recirculate_count", ++recirculateCount); - // Recirculate is now active and "check_recirculate" will be triggered. - nextState.setProperty("recirculate_active", true); - // Also set clone as active, which will trigger slightly different processing. - nextState.setProperty("clone_active", true); - // Grab the index and save it to the execution state. - nextState.setProperty("recirculate_index", recirculateIndex); - // Grab the session id and save it to the execution state. - nextState.setProperty("clone_session_id", sessionIdExpr); - // Set the appropriate instance type, which will be processed by - // "check_recirculate". - nextState.setProperty("recirculate_instance_type", - BMv2Constants::PKT_INSTANCE_TYPE_EGRESS_CLONE); - nextState.popBody(); - result->emplace_back(cond, state, nextState); - return; - } - - TESTGEN_UNIMPLEMENTED("Unsupported clone type %1%.", cloneType); + auto preserveIndex = args->at(2)->expression->checkedTo()->asInt(); + // This is the clone state. Clone the state and save it. + // TODO: Do we really need to clone twice? + auto *cloneInfo = new Bmv2V1ModelCloneInfo( + sessionIdExpr, static_cast(cloneType), state.clone(), + preserveIndex); + auto &nextState = state.clone(); + nextState.addTestObject("clone_infos", "clone_info", cloneInfo); + // Also set clone as active, which will trigger "processClone" in the deparser. + nextState.setProperty("clone_active", true); + nextState.popBody(); + result->emplace_back(nextState); }}, /* ====================================================================================== * resubmit_preserving_field_list @@ -977,7 +1116,8 @@ void Bmv2V1ModelExprStepper::evalExternMethodCall(const IR::MethodCallExpression * Calling resubmit_preserving_field_list(1) will resubmit the packet * and preserve fields x and y of the user metadata. Calling * resubmit_preserving_field_list(2) will only preserve field y. - * ====================================================================================== */ + * ====================================================================================== + */ {"*method.resubmit_preserving_field_list", {"data"}, [](const IR::MethodCallExpression * /*call*/, const IR::Expression * /*receiver*/, @@ -990,7 +1130,7 @@ void Bmv2V1ModelExprStepper::evalExternMethodCall(const IR::MethodCallExpression // TODO: Determine the exact count. if (state.hasProperty("recirculate_count")) { recirculateCount = state.getProperty("recirculate_count"); - if (recirculateCount > 1) { + if (recirculateCount > 0) { ::warning("Only single resubmit supported for now. Dropping packet."); auto *dropStmt = new IR::MethodCallStatement( Utils::generateInternalMethodCall("drop_and_exit", {})); @@ -1001,15 +1141,17 @@ void Bmv2V1ModelExprStepper::evalExternMethodCall(const IR::MethodCallExpression } // Increment the recirculation count. nextState.setProperty("recirculate_count", ++recirculateCount); - // Recirculate is now active and "check_recirculate" will be triggered. + // Recirculate is now active and "processRecirculate" will be triggered in the + // deparser. nextState.setProperty("recirculate_active", true); // Grab the index and save it to the execution state. auto index = args->at(0)->expression->checkedTo()->asUint64(); nextState.setProperty("recirculate_index", index); // Resubmit actually uses the original input packet, not the deparsed packet. - // We have to reset the packet content to the input packet in "check_recirculate". + // We have to reset the packet content to the input packet in "processRecirculate". nextState.setProperty("recirculate_reset_pkt", true); - // Set the appropriate instance type, which will be processed by "check_recirculate". + // Set the appropriate instance type, which will be processed by + // "processRecirculate". nextState.setProperty("recirculate_instance_type", BMv2Constants::PKT_INSTANCE_TYPE_RESUBMIT); nextState.popBody(); @@ -1035,7 +1177,8 @@ void Bmv2V1ModelExprStepper::evalExternMethodCall(const IR::MethodCallExpression * fields specified by the field list index from the last such call * are preserved. See the v1model architecture documentation (Note 1) * for more details. - * ====================================================================================== */ + * ====================================================================================== + */ {"*method.recirculate_preserving_field_list", {"index"}, [](const IR::MethodCallExpression * /*call*/, const IR::Expression * /*receiver*/, @@ -1048,7 +1191,7 @@ void Bmv2V1ModelExprStepper::evalExternMethodCall(const IR::MethodCallExpression // TODO: Determine the exact count. if (state.hasProperty("recirculate_count")) { recirculateCount = state.getProperty("recirculate_count"); - if (recirculateCount > 1) { + if (recirculateCount > 0) { ::warning("Only single recirculation supported for now. Dropping packet."); auto *dropStmt = new IR::MethodCallStatement( Utils::generateInternalMethodCall("drop_and_exit", {})); @@ -1059,12 +1202,14 @@ void Bmv2V1ModelExprStepper::evalExternMethodCall(const IR::MethodCallExpression } // Increment the recirculation count. nextState.setProperty("recirculate_count", ++recirculateCount); - // Recirculate is now active and "check_recirculate" will be triggered. + // Recirculate is now active and "processRecirculate" will be triggered in the + // deparser. nextState.setProperty("recirculate_active", true); // Grab the index and save it to the execution state. auto index = args->at(0)->expression->checkedTo()->asUint64(); nextState.setProperty("recirculate_index", index); - // Set the appropriate instance type, which will be processed by "check_recirculate". + // Set the appropriate instance type, which will be processed by + // "processRecirculate". nextState.setProperty("recirculate_instance_type", BMv2Constants::PKT_INSTANCE_TYPE_RECIRC); nextState.popBody(); @@ -1077,19 +1222,18 @@ void Bmv2V1ModelExprStepper::evalExternMethodCall(const IR::MethodCallExpression * any user-defined metadata fields with the cloned packet. It is * equivalent to calling clone_preserving_field_list with the same * type and session parameter values, with empty data. - * ====================================================================================== */ + * ====================================================================================== + */ {"*method.clone", {"type", "session"}, - [this](const IR::MethodCallExpression *call, const IR::Expression * /*receiver*/, - IR::ID & /*methodName*/, const IR::Vector *args, - const ExecutionState &state, SmallStepEvaluator::Result &result) { - uint64_t recirculateCount = 0; + [](const IR::MethodCallExpression *call, const IR::Expression * /*receiver*/, + IR::ID & /*methodName*/, const IR::Vector *args, + const ExecutionState &state, SmallStepEvaluator::Result &result) { // Grab the recirculate count. Stop after more than 1 circulation loop to avoid // infinite recirculation loops. // TODO: Determine the exact count. if (state.hasProperty("recirculate_count")) { - recirculateCount = state.getProperty("recirculate_count"); - if (recirculateCount > 1) { + if (state.getProperty("recirculate_count") > 0) { auto &nextState = state.clone(); ::warning("Only single recirculation supported for now. Dropping packet."); auto *dropStmt = new IR::MethodCallStatement( @@ -1132,210 +1276,51 @@ void Bmv2V1ModelExprStepper::evalExternMethodCall(const IR::MethodCallExpression auto cloneType = args->at(0)->expression->checkedTo()->asUint64(); const auto *sessionIdExpr = args->at(1)->expression; - uint64_t sessionId = 0; - std::optional cond = std::nullopt; - - if (cloneType == BMv2Constants::CLONE_TYPE_I2E) { - // Pick a clone port var. For now, pick a random value from 0-511. - const auto &egressPortVar = programInfo.getTargetOutputPortVar(); - const auto &clonePortVar = ToolsVariables::getSymbolicVariable( - egressPortVar->type, 0, "clone_port_var" + std::to_string(call->clone_id)); - cond = new IR::LAnd( - new IR::Neq(egressPortVar, clonePortVar), - new IR::Lss(clonePortVar, IR::getConstant(clonePortVar->type, 8))); - // clone_preserving_field_list has a default state where the packet continues as - // is. - { - auto &defaultState = state.clone(); - const auto *cloneInfo = new Bmv2_CloneInfo(sessionIdExpr, clonePortVar, false); - defaultState.addTestObject("clone_infos", - std::to_string(sessionIdExpr->clone_id), cloneInfo); - defaultState.popBody(); - result->emplace_back(cond, state, defaultState); - } - // This is the clone state. - auto &nextState = state.clone(); - const auto *progInfo = getProgramInfo().checkedTo(); - - // We need to reset everything to the state before the ingress call. We use a trick - // by calling copyIn on the entire state again. We need a little bit of information - // for that, including the exact parameter names of the ingress block we are in. - // Just grab the ingress from the programmable blocks. - const auto *programmableBlocks = progInfo->getProgrammableBlocks(); - const auto *typeDecl = programmableBlocks->at("Ingress"); - const auto *applyBlock = typeDecl->checkedTo(); - const auto *params = applyBlock->getApplyParameters(); - auto blockIndex = 2; - const auto *archSpec = TestgenTarget::getArchSpec(); - const auto *archMember = archSpec->getArchMember(blockIndex); - std::vector cmds; - for (size_t paramIdx = 0; paramIdx < params->size(); ++paramIdx) { - const auto *param = params->getParameter(paramIdx); - programInfo.produceCopyInOutCall(param, paramIdx, archMember, &cmds, nullptr); - } - - // We then exit, which will copy out all the state that we have just reset. - cmds.emplace_back(new IR::ExitStatement()); - - const auto *cloneInfo = new Bmv2_CloneInfo(sessionIdExpr, clonePortVar, true); - nextState.addTestObject("clone_infos", std::to_string(sessionIdExpr->clone_id), - cloneInfo); - // Reset the packet buffer, which corresponds to the output packet. - nextState.resetPacketBuffer(); - const auto *bitType = IR::getBitType(32); - const auto *instanceTypeVar = new IR::Member( - bitType, new IR::PathExpression("*standard_metadata"), "instance_type"); - nextState.set( - instanceTypeVar, - IR::getConstant(bitType, BMv2Constants::PKT_INSTANCE_TYPE_INGRESS_CLONE)); - nextState.replaceTopBody(&cmds); - result->emplace_back(cond, state, nextState); - return; - } - - if (cloneType == BMv2Constants::CLONE_TYPE_E2E) { - auto &nextState = state.clone(); - // Increment the recirculation count. - nextState.setProperty("recirculate_count", ++recirculateCount); - // Recirculate is now active and "check_recirculate" will be triggered. - nextState.setProperty("recirculate_active", true); - // Also set clone as active, which will trigger slightly different processing. - nextState.setProperty("clone_active", true); - // Grab the session id and save it to the execution state. - nextState.setProperty("clone_session_id", sessionId); - // Set the appropriate instance type, which will be processed by - // "check_recirculate". - nextState.setProperty("recirculate_instance_type", - BMv2Constants::PKT_INSTANCE_TYPE_EGRESS_CLONE); - nextState.popBody(); - result->emplace_back(cond, state, nextState); - return; - } - TESTGEN_UNIMPLEMENTED("Unsupported clone type %1%.", cloneType); + auto &nextState = state.clone(); + // This is the clone state. Clone the state and save it. + // TODO: Do we really need to clone twice? + auto *cloneInfo = new Bmv2V1ModelCloneInfo( + sessionIdExpr, static_cast(cloneType), state.clone(), + std::nullopt); + nextState.addTestObject("clone_infos", "clone_info", cloneInfo); + // Also set clone as active, which will trigger "processClone" in the deparser. + nextState.setProperty("clone_active", true); + nextState.popBody(); + result->emplace_back(nextState); }}, /* ====================================================================================== - * *check_recirculate - * ====================================================================================== */ - /// Helper externs that processing the parameters set by the recirculate and resubmit - /// externs. This extern assumes it is executed at the end of the deparser. - {"*.check_recirculate", + * *invoke_traffic_manager + * ====================================================================================== + */ + /// Helper extern that processes the parameters set by the recirculate, clone and resubmit + /// externs. This extern assume the TM is executed at the end of the deparser. + {"*.invoke_traffic_manager", {}, - [this](const IR::MethodCallExpression *call, const IR::Expression * /*receiver*/, + [this](const IR::MethodCallExpression * /*call*/, const IR::Expression * /*receiver*/, IR::ID & /*methodName*/, const IR::Vector * /*args*/, const ExecutionState &state, SmallStepEvaluator::Result &result) { - auto &recState = state.clone(); - // Check whether recirculate is even active, if not, skip. - if (!state.hasProperty("recirculate_active") || - !state.getProperty("recirculate_active")) { - recState.popBody(); - result->emplace_back(recState); + // Check whether the clone variant is active. + // Clone triggers a branch and slightly different processing. + if (state.hasProperty("clone_active") && state.getProperty("clone_active")) { + processClone(state, result); return; } - // Check whether the packet needs to be reset. - // If that is the case, reset the packet buffer to the calculated input packet. - auto recirculateReset = state.hasProperty("recirculate_reset_pkt"); - if (recirculateReset) { - // Reset the packet buffer, which corresponds to the output packet. - recState.resetPacketBuffer(); - // Set the packet buffer to the current calculated program packet for consistency. - recState.appendToPacketBuffer(recState.getInputPacket()); - } - - // We need to update the size of the packet when recirculating. Do not forget to divide - // by 8. - const auto *pktSizeType = &PacketVars::PACKET_SIZE_VAR_TYPE; - auto packetSizeVar = IR::StateVariable(new IR::Member( - pktSizeType, new IR::PathExpression("*standard_metadata"), "packet_length")); - const auto *packetSizeConst = - IR::getConstant(pktSizeType, recState.getPacketBufferSize() / 8); - recState.set(packetSizeVar, packetSizeConst); - - const auto *progInfo = getProgramInfo().checkedTo(); - if (recState.hasProperty("recirculate_index")) { - // Get the index set by the recirculate/resubmit function. Will fail if no index is - // set. - auto recirculateIndex = recState.getProperty("recirculate_index"); - // This program segment resets the user metadata of the v1model program to 0. - // However, fields in the user metadata that have the field_list annotation and the - // appropriate index will not be reset. - // The user metadata is the third parameter of the parser control. - const auto *paramPath = progInfo->getBlockParam("Parser", 2); - resetPreservingFieldList(recState, paramPath, recirculateIndex); - } - - // Update the metadata variable to the correct instance type as provided by - // recirculation. - auto instanceType = state.getProperty("recirculate_instance_type"); - const auto *bitType = IR::getBitType(32); - const auto *instanceTypeVar = new IR::Member( - bitType, new IR::PathExpression("*standard_metadata"), "instance_type"); - recState.set(instanceTypeVar, IR::getConstant(bitType, instanceType)); - - // Set recirculate to false to avoid infinite loops. - recState.setProperty("recirculate_active", false); - - // Check whether the clone variant is active. - // Clone triggers a branch and slightly different processing. - auto cloneActive = - state.hasProperty("clone_active") && state.getProperty("clone_active"); - if (cloneActive) { - // Pick a clone port var. For now, pick a random value from 0-511. - const auto &egressPortVar = programInfo.getTargetOutputPortVar(); - const auto &clonePortVar = ToolsVariables::getSymbolicVariable( - egressPortVar->type, 0, "clone_port_var" + std::to_string(call->clone_id)); - const auto *cond = new IR::LAnd( - new IR::Neq(egressPortVar, clonePortVar), - new IR::Lss(clonePortVar, IR::getConstant(clonePortVar->type, 8))); - const auto *sessionIdExpr = - state.getProperty("clone_session_id"); - // clone_preserving_field_list has a default state where the packet continues as - // is. - { - auto &defaultState = state.clone(); - defaultState.setProperty("clone_active", false); - const auto *cloneInfo = new Bmv2_CloneInfo(sessionIdExpr, clonePortVar, false); - defaultState.addTestObject("clone_infos", - std::to_string(sessionIdExpr->clone_id), cloneInfo); - defaultState.popBody(); - result->emplace_back(cond, state, defaultState); - } - // In the other state, we start processing from the egress. - const auto *topLevelBlocks = progInfo->getPipelineSequence(); - size_t egressDelim = 0; - for (; egressDelim < topLevelBlocks->size(); ++egressDelim) { - auto block = topLevelBlocks->at(egressDelim); - if (!std::holds_alternative(block)) { - continue; - } - const auto *p4Node = std::get(block); - if (const auto *ctrl = p4Node->to()) { - if (progInfo->getGress(ctrl) == BMV2_EGRESS) { - break; - } - } - } - const std::vector blocks = { - topLevelBlocks->begin() + egressDelim - 2, topLevelBlocks->end()}; - recState.replaceTopBody(&blocks); - const auto *cloneInfo = new Bmv2_CloneInfo(sessionIdExpr, clonePortVar, true); - recState.addTestObject("clone_infos", std::to_string(sessionIdExpr->clone_id), - cloneInfo); - recState.setProperty("clone_active", false); - // Reset the packet buffer, which corresponds to the output packet. - recState.resetPacketBuffer(); - result->emplace_back(cond, state, recState); + // Check whether recirculate is active. + if (state.hasProperty("recirculate_active") && + state.getProperty("recirculate_active")) { + processRecirculate(state, result); return; } - // "Recirculate" by attaching the sequence again. - // Does NOT initialize state or adds new conditions. - const auto *topLevelBlocks = progInfo->getPipelineSequence(); - recState.replaceTopBody(topLevelBlocks); - result->emplace_back(recState); + // Neither clone nor recirculate are active, so skip. + auto &nextState = state.clone(); + nextState.popBody(); + result->emplace_back(nextState); }}, /* ====================================================================================== * Checksum16.get - * ====================================================================================== */ + * ====================================================================================== + */ {"Checksum16.get", {"data"}, [](const IR::MethodCallExpression * /*call*/, const IR::Expression * /*receiver*/, @@ -1403,8 +1388,8 @@ void Bmv2V1ModelExprStepper::evalExternMethodCall(const IR::MethodCallExpression const auto *algo = args->at(3)->expression; const auto *oneBitType = IR::getBitType(1); - // If the condition is tainted or the input data is tainted, the checksum error will - // not be reliable. + // If the condition is tainted or the input data is tainted, the checksum error + // will not be reliable. if (argsAreTainted) { auto &taintedState = state.clone(); const auto *checksumErr = new IR::Member( @@ -1685,8 +1670,8 @@ void Bmv2V1ModelExprStepper::evalExternMethodCall(const IR::MethodCallExpression const auto *checksumValueType = checksumValue->type; const auto *algo = args->at(3)->expression; const auto *oneBitType = IR::getBitType(1); - // If the condition is tainted or the input data is tainted, the checksum error will - // not be reliable. + // If the condition is tainted or the input data is tainted, the checksum error + // will not be reliable. if (argsAreTainted) { auto &taintedState = state.clone(); const auto *checksumErr = new IR::Member( diff --git a/backends/p4tools/modules/testgen/targets/bmv2/expr_stepper.h b/backends/p4tools/modules/testgen/targets/bmv2/expr_stepper.h index 37d7023d3b..2fd8fa4422 100644 --- a/backends/p4tools/modules/testgen/targets/bmv2/expr_stepper.h +++ b/backends/p4tools/modules/testgen/targets/bmv2/expr_stepper.h @@ -11,6 +11,7 @@ #include "backends/p4tools/modules/testgen/core/program_info.h" #include "backends/p4tools/modules/testgen/core/small_step/expr_stepper.h" +#include "backends/p4tools/modules/testgen/core/small_step/small_step.h" #include "backends/p4tools/modules/testgen/lib/execution_state.h" namespace P4Tools::P4Testgen::Bmv2 { @@ -31,6 +32,13 @@ class Bmv2V1ModelExprStepper : public ExprStepper { void resetPreservingFieldList(ExecutionState &nextState, const IR::PathExpression *ref, uint64_t recirculateIndex) const; + /// Helper function, which is triggered when clone was called in the P4 program. + void processClone(const ExecutionState &state, SmallStepEvaluator::Result &result); + + /// Helper function, which is triggered when resubmit or recirculate was called in the P4 + /// program. + void processRecirculate(const ExecutionState &state, SmallStepEvaluator::Result &result); + public: Bmv2V1ModelExprStepper(ExecutionState &state, AbstractSolver &solver, const ProgramInfo &programInfo); diff --git a/backends/p4tools/modules/testgen/targets/bmv2/program_info.cpp b/backends/p4tools/modules/testgen/targets/bmv2/program_info.cpp index 29c65b1307..a7cbc27b28 100644 --- a/backends/p4tools/modules/testgen/targets/bmv2/program_info.cpp +++ b/backends/p4tools/modules/testgen/targets/bmv2/program_info.cpp @@ -162,8 +162,8 @@ std::vector Bmv2V1ModelProgramInfo::processDeclaration( // Also check whether we need to drop the packet. const auto *dropCheck = new IR::IfStatement(dropIsActive(), dropStmt, nullptr); cmds.emplace_back(dropCheck); - const auto *recirculateCheck = - new IR::MethodCallStatement(Utils::generateInternalMethodCall("check_recirculate", {})); + const auto *recirculateCheck = new IR::MethodCallStatement( + Utils::generateInternalMethodCall("invoke_traffic_manager", {})); cmds.emplace_back(recirculateCheck); } return cmds; diff --git a/backends/p4tools/modules/testgen/targets/bmv2/table_stepper.cpp b/backends/p4tools/modules/testgen/targets/bmv2/table_stepper.cpp index 6562f70453..dd7d1b87ef 100644 --- a/backends/p4tools/modules/testgen/targets/bmv2/table_stepper.cpp +++ b/backends/p4tools/modules/testgen/targets/bmv2/table_stepper.cpp @@ -26,6 +26,7 @@ #include "backends/p4tools/modules/testgen/lib/continuation.h" #include "backends/p4tools/modules/testgen/lib/exceptions.h" #include "backends/p4tools/modules/testgen/lib/execution_state.h" +#include "backends/p4tools/modules/testgen/lib/test_object.h" #include "backends/p4tools/modules/testgen/lib/test_spec.h" #include "backends/p4tools/modules/testgen/options.h" #include "backends/p4tools/modules/testgen/targets/bmv2/constants.h" diff --git a/backends/p4tools/modules/testgen/targets/bmv2/test/BMV2PTFXfail.cmake b/backends/p4tools/modules/testgen/targets/bmv2/test/BMV2PTFXfail.cmake index daef1d1fef..78d1a16ae8 100644 --- a/backends/p4tools/modules/testgen/targets/bmv2/test/BMV2PTFXfail.cmake +++ b/backends/p4tools/modules/testgen/targets/bmv2/test/BMV2PTFXfail.cmake @@ -1,21 +1,12 @@ # XFAILS: tests that currently fail. Most of these are temporary. # ================================================ - #################################################################################################### # 1. P4C Toolchain Issues # These are issues either with the P4 compiler or the behavioral model executing the code. # These issues needed to be tracked and fixed in P4C. #################################################################################################### -p4tools_add_xfail_reason( - "testgen-p4c-bmv2-ptf" - "Non-numeric, non-boolean member expression: .* Type: Type_Stack" - # We can not expand stacks in parsers because information about .next is lost. - # P4Testgen needs to maintain its own internal .next variable for stacks. - array-copy-bmv2.p4 -) - p4tools_add_xfail_reason( "testgen-p4c-bmv2-ptf" "Assertion" @@ -48,11 +39,49 @@ p4tools_add_xfail_reason( extract_for_header_union.p4 ) +p4tools_add_xfail_reason( + "testgen-p4c-bmv2-ptf" + "At index" + # At index 0: UNKNOWN, 'Error when adding match entry to target' + # enums are not supported in P4Runtime yet https://github.com/p4lang/behavioral-model/issues/1178 + issue1062-1-bmv2.p4 + v1model-p4runtime-most-types1.p4 + v1model-p4runtime-enumint-types1.p4 + + # At index 0: INVALID_ARGUMENT, 'Bytestring provided does not fit within 0 bits' + # https://github.com/p4lang/PI/issues/585 + issue2283_1-bmv2.p4 +) + +p4tools_add_xfail_reason( + "testgen-p4c-bmv2-ptf" + "Unexpected error in RPC handling" + # Unexpected error in RPC handling + issue3374.p4 + control-hs-index-test6.p4 + parser-unroll-test1.p4 +) + +p4tools_add_xfail_reason( + "testgen-p4c-bmv2-ptf" + "Error when importing p4info" + v1model-digest-containing-ser-enum.p4 + v1model-digest-custom-type.p4 +) + #################################################################################################### # 2. P4Testgen Issues # These are failures in P4Testgen that need to be fixed. #################################################################################################### +p4tools_add_xfail_reason( + "testgen-p4c-bmv2-ptf" + "Non-numeric, non-boolean member expression: .* Type: Type_Stack" + # We can not expand stacks in parsers because information about .next is lost. + # P4Testgen needs to maintain its own internal .next variable for stacks. + array-copy-bmv2.p4 +) + p4tools_add_xfail_reason( "testgen-p4c-bmv2-ptf" "Computations are not supported in update_checksum" @@ -61,7 +90,7 @@ p4tools_add_xfail_reason( p4tools_add_xfail_reason( "testgen-p4c-bmv2-ptf" "is trying to match on a tainted key set" - # unimlemented feature (for select statement) + # unimplemented feature (for select statement) invalid-hdr-warnings1.p4 issue692-bmv2.p4 ) @@ -252,36 +281,3 @@ p4tools_add_xfail_reason( issue914-bmv2.p4 xor_test.p4 ) - -p4tools_add_xfail_reason( - "testgen-p4c-bmv2-ptf" - "At index" - # At index 0: UNKNOWN, 'Error when adding match entry to target' - # enums are not supported in P4Runtime yet https://github.com/p4lang/behavioral-model/issues/1178 - issue1062-1-bmv2.p4 - v1model-p4runtime-most-types1.p4 - pins_fabric.p4 - pins_wbb.p4 - v1model-p4runtime-enumint-types1.p4 - - # At index 0: INVALID_ARGUMENT, 'Bytestring provided does not fit within 0 bits' - pins_middleblock.p4 - issue2283_1-bmv2.p4 -) - -p4tools_add_xfail_reason( - "testgen-p4c-bmv2-ptf" - "Unexpected error in RPC handling" - # Unexpected error in RPC handling - issue3374.p4 - control-hs-index-test6.p4 - parser-unroll-test1.p4 -) - - -p4tools_add_xfail_reason( - "testgen-p4c-bmv2-ptf" - "Error when importing p4info" - v1model-digest-containing-ser-enum.p4 - v1model-digest-custom-type.p4 -) diff --git a/backends/p4tools/modules/testgen/targets/bmv2/test/p4-programs/bmv2_clone_twice.p4 b/backends/p4tools/modules/testgen/targets/bmv2/test/p4-programs/bmv2_clone_twice.p4 new file mode 100644 index 0000000000..e49200c861 --- /dev/null +++ b/backends/p4tools/modules/testgen/targets/bmv2/test/p4-programs/bmv2_clone_twice.p4 @@ -0,0 +1,66 @@ +#include +#include + +header ethernet_t { + bit<48> dst_addr; + bit<48> src_addr; + bit<16> ethertype; + bit<32> clone_val; +} + +header clone_header_t { + bit<32> pkt_len; +} + +struct headers_t { + ethernet_t ethernet; + clone_header_t c; +} + +struct metadata_t { + bool is_recirculated; + bool is_recirculated_without_anno; +} + +parser ParserImpl(packet_in packet, out headers_t hdr, inout metadata_t meta, inout standard_metadata_t standard_metadata) { + state start { + packet.extract(hdr.ethernet); + transition accept; + } +} + +control ingress(inout headers_t hdr, inout metadata_t meta, inout standard_metadata_t standard_metadata) { + apply { + clone(CloneType.I2E, 1); + hdr.ethernet.src_addr = 0xFFFFFFFFFFFF; + clone(CloneType.I2E, 2); + } +} + +control egress(inout headers_t hdr, inout metadata_t meta, inout standard_metadata_t standard_metadata) { + apply { + if (standard_metadata.instance_type == 1) { + hdr.ethernet.clone_val = 0xCCCCCCCC; + hdr.c.setValid(); + hdr.c.pkt_len = standard_metadata.packet_length; + } + } +} + +control DeparserImpl(packet_out packet, in headers_t hdr) { + apply { + packet.emit(hdr); + } +} + + +control verifyChecksum(inout headers_t hdr, inout metadata_t meta) { + apply {} +} + +control computeChecksum(inout headers_t hdr, inout metadata_t meta) { + apply {} +} + +V1Switch(ParserImpl(), verifyChecksum(), ingress(), egress(), computeChecksum(), DeparserImpl()) main; + diff --git a/backends/p4tools/modules/testgen/targets/bmv2/test_backend.cpp b/backends/p4tools/modules/testgen/targets/bmv2/test_backend.cpp index 4bb29f6940..32376bdb7b 100644 --- a/backends/p4tools/modules/testgen/targets/bmv2/test_backend.cpp +++ b/backends/p4tools/modules/testgen/targets/bmv2/test_backend.cpp @@ -1,8 +1,7 @@ #include "backends/p4tools/modules/testgen/targets/bmv2/test_backend.h" -#include - -#include +#include +#include #include #include #include @@ -23,6 +22,7 @@ #include "backends/p4tools/modules/testgen/core/symbolic_executor/symbolic_executor.h" #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_object.h" #include "backends/p4tools/modules/testgen/options.h" #include "backends/p4tools/modules/testgen/targets/bmv2/backend/metadata/metadata.h" #include "backends/p4tools/modules/testgen/targets/bmv2/backend/protobuf/protobuf.h" @@ -147,12 +147,12 @@ const TestSpec *Bmv2TestBackend::createTestSpec(const ExecutionState *executionS testSpec->addTestObject("action_selectors", selectorName, evaluatedSelector); } - const auto cloneInfos = executionState->getTestObjectCategory("clone_infos"); - for (const auto &testObject : cloneInfos) { + const auto cloneSpecs = executionState->getTestObjectCategory("clone_specs"); + for (const auto &testObject : cloneSpecs) { const auto sessionId = testObject.first; - const auto *cloneInfo = testObject.second->checkedTo(); - const auto *evaluatedInfo = cloneInfo->evaluate(*completedModel); - testSpec->addTestObject("clone_infos", sessionId, evaluatedInfo); + const auto *cloneSpec = testObject.second->checkedTo(); + const auto *evaluatedInfo = cloneSpec->evaluate(*completedModel); + testSpec->addTestObject("clone_specs", sessionId, evaluatedInfo); } return testSpec; diff --git a/backends/p4tools/modules/testgen/targets/bmv2/test_spec.cpp b/backends/p4tools/modules/testgen/targets/bmv2/test_spec.cpp index e30f036855..7614701890 100644 --- a/backends/p4tools/modules/testgen/targets/bmv2/test_spec.cpp +++ b/backends/p4tools/modules/testgen/targets/bmv2/test_spec.cpp @@ -134,40 +134,65 @@ const Bmv2V1ModelActionSelector *Bmv2V1ModelActionSelector::evaluate(const Model } /* ========================================================================================= - * Bmv2_CloneInfo + * Bmv2V1ModelCloneInfo * ========================================================================================= */ -Bmv2_CloneInfo::Bmv2_CloneInfo(const IR::Expression *sessionId, const IR::Expression *clonePort, - bool isClone) +Bmv2V1ModelCloneInfo::Bmv2V1ModelCloneInfo(const IR::Expression *sessionId, + BMv2Constants::CloneType cloneType, + const ExecutionState &clonedState, + std::optional preserveIndex) + : sessionId(sessionId), + cloneType(cloneType), + clonedState(clonedState), + preserveIndex(preserveIndex) {} + +cstring Bmv2V1ModelCloneInfo::getObjectName() const { return "Bmv2V1ModelCloneInfo"; } + +BMv2Constants::CloneType Bmv2V1ModelCloneInfo::getCloneType() const { return cloneType; } + +const IR::Expression *Bmv2V1ModelCloneInfo::getSessionId() const { return sessionId; } + +const ExecutionState &Bmv2V1ModelCloneInfo::getClonedState() const { return clonedState; } + +std::optional Bmv2V1ModelCloneInfo::getPreserveIndex() const { return preserveIndex; } + +const Bmv2V1ModelCloneInfo *Bmv2V1ModelCloneInfo::evaluate(const Model & /*model*/) const { + P4C_UNIMPLEMENTED("Evaluate is not implemented for this test object."); +} + +/* ========================================================================================= + * Bmv2V1ModelCloneSpec + * ========================================================================================= */ + +Bmv2V1ModelCloneSpec::Bmv2V1ModelCloneSpec(const IR::Expression *sessionId, + const IR::Expression *clonePort, bool isClone) : sessionId(sessionId), clonePort(clonePort), isClone(isClone) {} -cstring Bmv2_CloneInfo::getObjectName() const { return "Bmv2_CloneInfo"; } +cstring Bmv2V1ModelCloneSpec::getObjectName() const { return "Bmv2V1ModelCloneSpec"; } -const IR::Expression *Bmv2_CloneInfo::getClonePort() const { return clonePort; } +const IR::Expression *Bmv2V1ModelCloneSpec::getClonePort() const { return clonePort; } -const IR::Expression *Bmv2_CloneInfo::getSessionId() const { return sessionId; } +const IR::Expression *Bmv2V1ModelCloneSpec::getSessionId() const { return sessionId; } -const IR::Constant *Bmv2_CloneInfo::getEvaluatedClonePort() const { +const IR::Constant *Bmv2V1ModelCloneSpec::getEvaluatedClonePort() const { const auto *constant = clonePort->to(); BUG_CHECK(constant, "Variable is not a constant, has the test object %1% been evaluated?", getObjectName()); return constant; } -const IR::Constant *Bmv2_CloneInfo::getEvaluatedSessionId() const { +const IR::Constant *Bmv2V1ModelCloneSpec::getEvaluatedSessionId() const { const auto *constant = sessionId->to(); BUG_CHECK(constant, "Variable is not a constant, has the test object %1% been evaluated?", getObjectName()); return constant; } -const Bmv2_CloneInfo *Bmv2_CloneInfo::evaluate(const Model &model) const { - const auto *evaluatedClonePort = model.evaluate(clonePort); - const auto *evaluatedSessionId = model.evaluate(sessionId); - return new Bmv2_CloneInfo(evaluatedSessionId, evaluatedClonePort, isClone); +const Bmv2V1ModelCloneSpec *Bmv2V1ModelCloneSpec::evaluate(const Model &model) const { + return new Bmv2V1ModelCloneSpec(model.evaluate(sessionId), model.evaluate(clonePort), isClone); } -bool Bmv2_CloneInfo::isClonedPacket() const { return isClone; } +bool Bmv2V1ModelCloneSpec::isClonedPacket() const { return isClone; } /* ========================================================================================= * Table Key Match Types diff --git a/backends/p4tools/modules/testgen/targets/bmv2/test_spec.h b/backends/p4tools/modules/testgen/targets/bmv2/test_spec.h index 31096c3838..197553caa1 100644 --- a/backends/p4tools/modules/testgen/targets/bmv2/test_spec.h +++ b/backends/p4tools/modules/testgen/targets/bmv2/test_spec.h @@ -2,7 +2,9 @@ #define BACKENDS_P4TOOLS_MODULES_TESTGEN_TARGETS_BMV2_TEST_SPEC_H_ #include +#include #include +#include #include #include @@ -11,7 +13,10 @@ #include "ir/ir.h" #include "lib/cstring.h" +#include "backends/p4tools/modules/testgen/lib/execution_state.h" +#include "backends/p4tools/modules/testgen/lib/test_object.h" #include "backends/p4tools/modules/testgen/lib/test_spec.h" +#include "backends/p4tools/modules/testgen/targets/bmv2/constants.h" namespace P4Tools::P4Testgen::Bmv2 { @@ -138,9 +143,49 @@ class Bmv2V1ModelActionSelector : public TestObject { }; /* ========================================================================================= - * Bmv2_CloneInfo + * Bmv2V1ModelCloneInfo * ========================================================================================= */ -class Bmv2_CloneInfo : public TestObject { +class Bmv2V1ModelCloneInfo : public TestObject { + private: + /// The session ID associated with this clone information. + const IR::Expression *sessionId; + + /// The type of clone associated with this object. + BMv2Constants::CloneType cloneType; + + /// The state at the point of time time this object was created. + std::reference_wrapper clonedState; + + /// Whether to preserve a particular field list of metadata. This is optional. + std::optional preserveIndex; + + public: + explicit Bmv2V1ModelCloneInfo(const IR::Expression *sessionId, + BMv2Constants::CloneType cloneType, + const ExecutionState &clonedState, + std::optional preserveIndex); + + [[nodiscard]] cstring getObjectName() const override; + + [[nodiscard]] const Bmv2V1ModelCloneInfo *evaluate(const Model &model) const override; + + /// @returns the associated session ID with this cloned packet. + [[nodiscard]] const IR::Expression *getSessionId() const; + + /// @returns the type of clone. + [[nodiscard]] BMv2Constants::CloneType getCloneType() const; + + /// @returns the index marking the field list to be preserved when cloning. Optional. + [[nodiscard]] std::optional getPreserveIndex() const; + + /// @returns the state that was cloned at the time of generation of this object. + [[nodiscard]] const ExecutionState &getClonedState() const; +}; + +/* ========================================================================================= + * Bmv2V1ModelCloneSpec + * ========================================================================================= */ +class Bmv2V1ModelCloneSpec : public TestObject { private: /// The session ID associated with this clone information. const IR::Expression *sessionId; @@ -153,12 +198,12 @@ class Bmv2_CloneInfo : public TestObject { bool isClone; public: - explicit Bmv2_CloneInfo(const IR::Expression *sessionId, const IR::Expression *clonePort, - bool isClone); + explicit Bmv2V1ModelCloneSpec(const IR::Expression *sessionId, const IR::Expression *clonePort, + bool isClone); [[nodiscard]] cstring getObjectName() const override; - [[nodiscard]] const Bmv2_CloneInfo *evaluate(const Model &model) const override; + [[nodiscard]] const Bmv2V1ModelCloneSpec *evaluate(const Model &model) const override; /// @returns the associated session ID with this cloned packet. [[nodiscard]] const IR::Expression *getSessionId() const; @@ -183,6 +228,7 @@ class Bmv2_CloneInfo : public TestObject { * ========================================================================================= */ class MetadataCollection : public TestObject { private: + /// A list of metadata fields (must be literals). std::map metadataFields; public: @@ -192,11 +238,13 @@ class MetadataCollection : public TestObject { [[nodiscard]] const MetadataCollection *evaluate(const Model & /*model*/) const override; - /// @returns the clone port expression. + /// @returns the list of metadata fields. [[nodiscard]] const std::map &getMetadataFields() const; + /// Add a metadata field to the collection. void addMetaDataField(cstring name, const IR::Literal *metadataField); + /// @returns a metadata field from the collection. const IR::Literal *getMetadataField(cstring name); }; @@ -215,16 +263,16 @@ class Optional : public TableMatch { public: explicit Optional(const IR::KeyElement *key, const IR::Expression *value, bool addMatch); - const Optional *evaluate(const Model &model) const override; + [[nodiscard]] const Optional *evaluate(const Model &model) const override; - cstring getObjectName() const override; + [[nodiscard]] cstring getObjectName() const override; /// @returns the match value. It is expected to be a constant at this point. /// A BUG is thrown otherwise. - const IR::Constant *getEvaluatedValue() const; + [[nodiscard]] const IR::Constant *getEvaluatedValue() const; /// @returns whether to add this optional match as an exact match. - bool addAsExactMatch() const; + [[nodiscard]] bool addAsExactMatch() const; }; class Range : public TableMatch { @@ -239,17 +287,17 @@ class Range : public TableMatch { explicit Range(const IR::KeyElement *key, const IR::Expression *low, const IR::Expression *high); - const Range *evaluate(const Model &model) const override; + [[nodiscard]] const Range *evaluate(const Model &model) const override; - cstring getObjectName() const override; + [[nodiscard]] cstring getObjectName() const override; /// @returns the inclusive start of the range. It is expected to be a constant at this point. /// A BUG is thrown otherwise. - const IR::Constant *getEvaluatedLow() const; + [[nodiscard]] const IR::Constant *getEvaluatedLow() const; /// @returns the inclusive end of the range. It is expected to be a constant at this point. /// A BUG is thrown otherwise. - const IR::Constant *getEvaluatedHigh() const; + [[nodiscard]] const IR::Constant *getEvaluatedHigh() const; }; } // namespace P4Tools::P4Testgen::Bmv2 diff --git a/backends/p4tools/modules/testgen/targets/ebpf/backend/stf/stf.cpp b/backends/p4tools/modules/testgen/targets/ebpf/backend/stf/stf.cpp index 6689c8e909..32314eb0a5 100644 --- a/backends/p4tools/modules/testgen/targets/ebpf/backend/stf/stf.cpp +++ b/backends/p4tools/modules/testgen/targets/ebpf/backend/stf/stf.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include // NOLINT @@ -25,6 +26,7 @@ #include "nlohmann/json.hpp" #include "backends/p4tools/modules/testgen/lib/exceptions.h" +#include "backends/p4tools/modules/testgen/lib/test_object.h" #include "backends/p4tools/modules/testgen/lib/tf.h" namespace P4Tools::P4Testgen::EBPF { diff --git a/backends/p4tools/modules/testgen/targets/ebpf/test_backend.cpp b/backends/p4tools/modules/testgen/targets/ebpf/test_backend.cpp index de065abc94..641add1ec5 100644 --- a/backends/p4tools/modules/testgen/targets/ebpf/test_backend.cpp +++ b/backends/p4tools/modules/testgen/targets/ebpf/test_backend.cpp @@ -1,6 +1,6 @@ #include "backends/p4tools/modules/testgen/targets/ebpf/test_backend.h" -#include +#include #include #include #include @@ -19,6 +19,7 @@ #include "backends/p4tools/modules/testgen/core/symbolic_executor/symbolic_executor.h" #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_object.h" #include "backends/p4tools/modules/testgen/lib/test_spec.h" #include "backends/p4tools/modules/testgen/options.h" #include "backends/p4tools/modules/testgen/targets/ebpf/backend/stf/stf.h" diff --git a/backends/p4tools/modules/testgen/targets/pna/backend/metadata/metadata.cpp b/backends/p4tools/modules/testgen/targets/pna/backend/metadata/metadata.cpp index e387d6b95b..0f8120245c 100644 --- a/backends/p4tools/modules/testgen/targets/pna/backend/metadata/metadata.cpp +++ b/backends/p4tools/modules/testgen/targets/pna/backend/metadata/metadata.cpp @@ -18,6 +18,7 @@ #include "lib/log.h" #include "nlohmann/json.hpp" +#include "backends/p4tools/modules/testgen/lib/test_object.h" #include "backends/p4tools/modules/testgen/lib/tf.h" #include "backends/p4tools/modules/testgen/targets/pna/test_spec.h" diff --git a/backends/p4tools/modules/testgen/targets/pna/shared_table_stepper.cpp b/backends/p4tools/modules/testgen/targets/pna/shared_table_stepper.cpp index 370716f59d..bf4b35a761 100644 --- a/backends/p4tools/modules/testgen/targets/pna/shared_table_stepper.cpp +++ b/backends/p4tools/modules/testgen/targets/pna/shared_table_stepper.cpp @@ -26,6 +26,7 @@ #include "backends/p4tools/modules/testgen/lib/continuation.h" #include "backends/p4tools/modules/testgen/lib/exceptions.h" #include "backends/p4tools/modules/testgen/lib/execution_state.h" +#include "backends/p4tools/modules/testgen/lib/test_object.h" #include "backends/p4tools/modules/testgen/lib/test_spec.h" #include "backends/p4tools/modules/testgen/options.h" #include "backends/p4tools/modules/testgen/targets/pna/constants.h" diff --git a/backends/p4tools/modules/testgen/targets/pna/test_backend.cpp b/backends/p4tools/modules/testgen/targets/pna/test_backend.cpp index cbffcdbe73..d5e0bfbecf 100644 --- a/backends/p4tools/modules/testgen/targets/pna/test_backend.cpp +++ b/backends/p4tools/modules/testgen/targets/pna/test_backend.cpp @@ -1,7 +1,7 @@ #include "backends/p4tools/modules/testgen/targets/pna/test_backend.h" #include -#include +#include #include #include @@ -21,6 +21,7 @@ #include "backends/p4tools/modules/testgen/core/symbolic_executor/symbolic_executor.h" #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_object.h" #include "backends/p4tools/modules/testgen/options.h" #include "backends/p4tools/modules/testgen/targets/pna/backend/metadata/metadata.h" #include "backends/p4tools/modules/testgen/targets/pna/dpdk/program_info.h" diff --git a/backends/p4tools/modules/testgen/targets/pna/test_spec.h b/backends/p4tools/modules/testgen/targets/pna/test_spec.h index db62879fa6..63175abb72 100644 --- a/backends/p4tools/modules/testgen/targets/pna/test_spec.h +++ b/backends/p4tools/modules/testgen/targets/pna/test_spec.h @@ -11,6 +11,7 @@ #include "ir/ir.h" #include "lib/cstring.h" +#include "backends/p4tools/modules/testgen/lib/test_object.h" #include "backends/p4tools/modules/testgen/lib/test_spec.h" namespace P4Tools::P4Testgen::Pna {