diff --git a/backends/p4test/midend.cpp b/backends/p4test/midend.cpp index 4bceb19077..fa9d10a684 100644 --- a/backends/p4test/midend.cpp +++ b/backends/p4test/midend.cpp @@ -106,10 +106,10 @@ MidEnd::MidEnd(P4TestOptions &options, std::ostream *outStream) { new P4::HandleNoMatch(), new P4::SimplifyParsers(), new P4::StrengthReduction(&typeMap), - new P4::EliminateTuples(&typeMap), + options.keepTuples ? nullptr : new P4::EliminateTuples(&typeMap), new P4::FlattenLogMsg(&typeMap), new P4::SimplifyComparisons(&typeMap), - new P4::CopyStructures(&typeMap, false), + new P4::CopyStructures(&typeMap, false, false, options.keepTuples), new P4::NestedStructs(&typeMap), new P4::StrengthReduction(&typeMap), new P4::SimplifySelectList(&typeMap), diff --git a/backends/p4test/p4test.cpp b/backends/p4test/p4test.cpp index f53774a6e3..38b202e5f2 100644 --- a/backends/p4test/p4test.cpp +++ b/backends/p4test/p4test.cpp @@ -86,6 +86,9 @@ P4TestOptions::P4TestOptions() { return true; }, "use passes that use general switch instead of action_run"); + registerOption( + "--keepTuples", nullptr, [this](const char *) { return keepTuples = true; }, + "keep tuple type, but flatten assignments of them"); } class P4TestPragmas : public P4::P4COptionPragmaParser { diff --git a/backends/p4test/p4test.h b/backends/p4test/p4test.h index 2995406198..f9ce6194bd 100644 --- a/backends/p4test/p4test.h +++ b/backends/p4test/p4test.h @@ -27,6 +27,7 @@ class P4TestOptions : public CompilerOptions { bool validateOnly = false; bool loadIRFromJson = false; bool preferSwitch = false; + bool keepTuples = false; // keep tuples but flatten assignments of them P4TestOptions(); }; diff --git a/backends/p4tools/common/compiler/midend.cpp b/backends/p4tools/common/compiler/midend.cpp index bcb92b3d6b..19f559f368 100644 --- a/backends/p4tools/common/compiler/midend.cpp +++ b/backends/p4tools/common/compiler/midend.cpp @@ -121,7 +121,7 @@ void MidEnd::addDefaultPasses() { new P4::SimplifyComparisons(&typeMap), // Expand header and struct assignments into sequences of field assignments. new PassRepeated({ - new P4::CopyStructures(&typeMap, false, true, nullptr), + new P4::CopyStructures(&typeMap, false, true, false, nullptr), }), new P4::RemoveParserControlFlow(&typeMap), // Flatten nested list expressions. diff --git a/midend/copyStructures.cpp b/midend/copyStructures.cpp index 468471e971..17058fc460 100644 --- a/midend/copyStructures.cpp +++ b/midend/copyStructures.cpp @@ -17,6 +17,7 @@ limitations under the License. #include "copyStructures.h" #include "frontends/p4/alias.h" +#include "ir/irutils.h" namespace P4 { @@ -66,7 +67,8 @@ const IR::Node *DoCopyStructures::postorder(IR::AssignmentStatement *statement) const auto *ltype = typeMap->getType(statement->left, true); // If the left type is not a struct like or a header stack, return. - if (!(ltype->is() || ltype->is())) { + if (!ltype->is() && !ltype->is() && + !ltype->is()) { return statement; } /* @@ -153,6 +155,15 @@ const IR::Node *DoCopyStructures::postorder(IR::AssignmentStatement *statement) new IR::ArrayIndex(stack->elementType, statement->right, new IR::Constant(i)); retval.push_back(new IR::AssignmentStatement(srcInfo, left, right)); } + } else if (auto *tup = ltype->to()) { + if (!copyTuples) return statement; + int idx = 0; + for (auto *el : tup->components) { + const auto *left = new IR::ArrayIndex(el, statement->left, new IR::Constant(idx)); + const auto *right = new IR::ArrayIndex(el, statement->right, new IR::Constant(idx)); + retval.push_back(new IR::AssignmentStatement(srcInfo, left, right)); + ++idx; + } } else { if (ltype->is() || ltype->is()) { // Leave headers as they are -- copy_header will also copy the valid bit @@ -168,7 +179,7 @@ const IR::Node *DoCopyStructures::postorder(IR::AssignmentStatement *statement) retval.push_back(new IR::AssignmentStatement(statement->srcInfo, left, right)); } } - return new IR::BlockStatement(statement->srcInfo, std::move(retval)); + return IR::inlineBlock(*this, std::move(retval)); } } // namespace P4 diff --git a/midend/copyStructures.h b/midend/copyStructures.h index 78fcdba699..541973d4e3 100644 --- a/midend/copyStructures.h +++ b/midend/copyStructures.h @@ -44,7 +44,8 @@ namespace P4 { * * Further, struct initialization is converted to assignment on struct fields * - * Note, header assignments are not converted in this pass. + * header assignments and tuple assignments are optionally converted in this pass, based + * on constructor argument * * @pre none * @post @@ -62,13 +63,22 @@ class DoCopyStructures : public Transform { /// Do not only copy normal structures but also perform copy assignments for headers. bool copyHeaders; + /// Also split up assignments of tuples + bool copyTuples; public: - explicit DoCopyStructures(TypeMap *typeMap, bool errorOnMethodCall, bool copyHeaders = false) - : typeMap(typeMap), errorOnMethodCall(errorOnMethodCall), copyHeaders(copyHeaders) { + explicit DoCopyStructures(TypeMap *typeMap, bool errorOnMethodCall, bool copyHeaders = false, + bool copyTuples = true) + : typeMap(typeMap), + errorOnMethodCall(errorOnMethodCall), + copyHeaders(copyHeaders), + copyTuples(copyTuples) { CHECK_NULL(typeMap); setName("DoCopyStructures"); } + // FIXME -- this should be a preorder so we can deal with nested structs directly, + // but that fails because we depend on the typeMap which will be out of date after + // expanding outer copies. So we need to repeat this pass in a loop const IR::Node *postorder(IR::AssignmentStatement *statement) override; }; @@ -117,14 +127,13 @@ class RemoveAliases : public Transform { class CopyStructures : public PassRepeated { public: explicit CopyStructures(TypeMap *typeMap, bool errorOnMethodCall = true, - bool copyHeaders = false, TypeChecking *typeChecking = nullptr) { + bool copyHeaders = false, bool copyTuples = false, + TypeChecking *typeChecking = nullptr) { CHECK_NULL(typeMap); setName("CopyStructures"); if (typeChecking == nullptr) typeChecking = new TypeChecking(nullptr, typeMap); - passes.emplace_back(typeChecking); - passes.emplace_back(new RemoveAliases(typeMap)); - passes.emplace_back(typeChecking); - passes.emplace_back(new DoCopyStructures(typeMap, errorOnMethodCall, copyHeaders)); + addPasses({typeChecking, new RemoveAliases(typeMap), typeChecking, + new DoCopyStructures(typeMap, errorOnMethodCall, copyHeaders, copyTuples)}); } }; diff --git a/testdata/p4_16_samples/tuple5a.p4 b/testdata/p4_16_samples/tuple5a.p4 new file mode 100644 index 0000000000..0a5a7b90c8 --- /dev/null +++ b/testdata/p4_16_samples/tuple5a.p4 @@ -0,0 +1,26 @@ +#include +@command_line("--keepTuples") +control generic(inout M m); +package top(generic c); + +struct t1 { + tuple, bit<16>> a; + bit<32> b; +} + +struct t2 { + tuple, t1> x; + tuple> y; +} + +control c(inout t2 t) { + apply { + t1 tmp = t.x[1]; + t.x[0] += t.y[0].b; + t.x[1].a[0] = t.y[1][7:0]; + t.x[1].a[1] = t.y[1][tmp.a[0]+:16]; + t.y[0] = tmp; + } +} + +top(c()) main; diff --git a/testdata/p4_16_samples_outputs/tuple5a-first.p4 b/testdata/p4_16_samples_outputs/tuple5a-first.p4 new file mode 100644 index 0000000000..823d3ec402 --- /dev/null +++ b/testdata/p4_16_samples_outputs/tuple5a-first.p4 @@ -0,0 +1,25 @@ +#include + +@command_line("--keepTuples") control generic(inout M m); +package top(generic c); +struct t1 { + tuple, bit<16>> a; + bit<32> b; +} + +struct t2 { + tuple, t1> x; + tuple> y; +} + +control c(inout t2 t) { + apply { + t1 tmp = t.x[1]; + t.x[0] += t.y[0].b; + t.x[1].a[0] = t.y[1][7:0]; + t.x[1].a[1] = t.y[1][tmp.a[0]+:16]; + t.y[0] = tmp; + } +} + +top(c()) main; diff --git a/testdata/p4_16_samples_outputs/tuple5a-frontend.p4 b/testdata/p4_16_samples_outputs/tuple5a-frontend.p4 new file mode 100644 index 0000000000..f4d5691b9a --- /dev/null +++ b/testdata/p4_16_samples_outputs/tuple5a-frontend.p4 @@ -0,0 +1,26 @@ +#include + +@command_line("--keepTuples") control generic(inout M m); +package top(generic c); +struct t1 { + tuple, bit<16>> a; + bit<32> b; +} + +struct t2 { + tuple, t1> x; + tuple> y; +} + +control c(inout t2 t) { + @name("c.tmp") t1 tmp_0; + apply { + tmp_0 = t.x[1]; + t.x[0] = t.x[0] + t.y[0].b; + t.x[1].a[0] = t.y[1][7:0]; + t.x[1].a[1] = t.y[1][tmp_0.a[0]+:16]; + t.y[0] = tmp_0; + } +} + +top(c()) main; diff --git a/testdata/p4_16_samples_outputs/tuple5a-midend.p4 b/testdata/p4_16_samples_outputs/tuple5a-midend.p4 new file mode 100644 index 0000000000..c74b14591d --- /dev/null +++ b/testdata/p4_16_samples_outputs/tuple5a-midend.p4 @@ -0,0 +1,38 @@ +#include + +@command_line("--keepTuples") control generic(inout M m); +package top(generic c); +struct t1 { + tuple, bit<16>> a; + bit<32> b; +} + +struct t2 { + tuple, t1> x; + tuple> y; +} + +control c(inout t2 t) { + tuple, bit<16>> tmp_0_a; + @hidden action tuple5a18() { + tmp_0_a[0] = t.x[1].a[0]; + tmp_0_a[1] = t.x[1].a[1]; + t.x[0] = t.x[0] + t.y[0].b; + t.x[1].a[0] = t.y[1][7:0]; + t.x[1].a[1] = t.y[1][tmp_0_a[0]+:16]; + t.y[0].a[0] = tmp_0_a[0]; + t.y[0].a[1] = tmp_0_a[1]; + t.y[0].b = t.x[1].b; + } + @hidden table tbl_tuple5a18 { + actions = { + tuple5a18(); + } + const default_action = tuple5a18(); + } + apply { + tbl_tuple5a18.apply(); + } +} + +top(c()) main; diff --git a/testdata/p4_16_samples_outputs/tuple5a.p4 b/testdata/p4_16_samples_outputs/tuple5a.p4 new file mode 100644 index 0000000000..ed5a22c286 --- /dev/null +++ b/testdata/p4_16_samples_outputs/tuple5a.p4 @@ -0,0 +1,25 @@ +#include + +@command_line("--keepTuples") control generic(inout M m); +package top(generic c); +struct t1 { + tuple, bit<16>> a; + bit<32> b; +} + +struct t2 { + tuple, t1> x; + tuple> y; +} + +control c(inout t2 t) { + apply { + t1 tmp = t.x[1]; + t.x[0] += t.y[0].b; + t.x[1].a[0] = t.y[1][7:0]; + t.x[1].a[1] = t.y[1][tmp.a[0]+:16]; + t.y[0] = tmp; + } +} + +top(c()) main; diff --git a/testdata/p4_16_samples_outputs/tuple5a.p4-stderr b/testdata/p4_16_samples_outputs/tuple5a.p4-stderr new file mode 100644 index 0000000000..e69de29bb2