diff --git a/frontends/p4/moveDeclarations.cpp b/frontends/p4/moveDeclarations.cpp index dfa9148c1f3..09e67d156ef 100644 --- a/frontends/p4/moveDeclarations.cpp +++ b/frontends/p4/moveDeclarations.cpp @@ -79,11 +79,13 @@ const IR::Node *MoveDeclarations::postorder(IR::Declaration_Variable *decl) { auto varRef = new IR::PathExpression(decl->name); auto keep = new IR::AssignmentStatement(decl->srcInfo, varRef, decl->initializer); return keep; - } else { + } else if (!parent->is()) { // never move loop index decl out of foreach LOG1("Moving " << decl); addMove(decl); return nullptr; } + + return decl; } const IR::Node *MoveDeclarations::postorder(IR::Declaration_Constant *decl) { diff --git a/frontends/p4/sideEffects.cpp b/frontends/p4/sideEffects.cpp index e0854567a49..a946b92bd34 100644 --- a/frontends/p4/sideEffects.cpp +++ b/frontends/p4/sideEffects.cpp @@ -674,6 +674,19 @@ const IR::Node *DoSimplifyExpressions::preorder(IR::SwitchStatement *statement) return rv; } +const IR::Node *DoSimplifyExpressions::preorder(IR::ForEachStatement *statement) { + IR::Statement *rv = statement; + visit(statement->range, "index"); + if (!statements.empty()) { + statements.push_back(statement); + rv = new IR::BlockStatement(statements); + statements.clear(); + } + visit(statement->body, "body"); + prune(); + return rv; +} + void DoSimplifyExpressions::end_apply(const IR::Node *) { BUG_CHECK(toInsert.empty(), "DoSimplifyExpressions::end_apply orphaned declarations"); BUG_CHECK(statements.empty(), "DoSimplifyExpressions::end_apply orphaned statements"); diff --git a/frontends/p4/sideEffects.h b/frontends/p4/sideEffects.h index c2092a69d59..8e5ee13a2ce 100644 --- a/frontends/p4/sideEffects.h +++ b/frontends/p4/sideEffects.h @@ -243,6 +243,7 @@ class DoSimplifyExpressions : public Transform, P4WriteContext { const IR::Node *postorder(IR::ReturnStatement *statement) override; const IR::Node *preorder(IR::SwitchStatement *statement) override; const IR::Node *preorder(IR::IfStatement *statement) override; + const IR::Node *preorder(IR::ForEachStatement *statement) override; void end_apply(const IR::Node *) override; }; diff --git a/frontends/p4/toP4/toP4.cpp b/frontends/p4/toP4/toP4.cpp index 94adc28f98d..ebe40b828d0 100644 --- a/frontends/p4/toP4/toP4.cpp +++ b/frontends/p4/toP4/toP4.cpp @@ -1192,6 +1192,33 @@ bool ToP4::preorder(const IR::IfStatement *s) { return false; } +bool ToP4::preorder(const IR::ForEachStatement *s) { + dump(2); + builder.append("foreach ("); + auto type = s->index[0]->to()->type->getP4Type(); + CHECK_NULL(type); + visit(type); + builder.spc(); + builder.append(s->index[0]->name); + builder.append(" in "); + visit(s->range); + builder.append(") "); + if (!s->body->is()) { + builder.append("{"); + builder.increaseIndent(); + builder.newline(); + builder.emitIndent(); + } + visit(s->body); + if (!s->body->is()) { + builder.newline(); + builder.decreaseIndent(); + builder.emitIndent(); + builder.append("}"); + } + return false; +} + bool ToP4::preorder(const IR::MethodCallStatement *s) { dump(3); visit(s->methodCall); diff --git a/frontends/p4/toP4/toP4.h b/frontends/p4/toP4/toP4.h index eb4f82398c6..7d28797f8ad 100644 --- a/frontends/p4/toP4/toP4.h +++ b/frontends/p4/toP4/toP4.h @@ -234,6 +234,7 @@ class ToP4 : public Inspector { bool preorder(const IR::SwitchCase *s) override; bool preorder(const IR::SwitchStatement *s) override; bool preorder(const IR::IfStatement *s) override; + bool preorder(const IR::ForEachStatement *s) override; // misc bool preorder(const IR::NamedExpression *ne) override; diff --git a/frontends/p4/validateParsedProgram.cpp b/frontends/p4/validateParsedProgram.cpp index b54be62493c..478a64a8a49 100644 --- a/frontends/p4/validateParsedProgram.cpp +++ b/frontends/p4/validateParsedProgram.cpp @@ -251,4 +251,17 @@ void ValidateParsedProgram::postorder(const IR::Dots *dots) { } } +/// Check that continue and break statements are only used in the context of a foreach statement +void ValidateParsedProgram::postorder(const IR::BreakStatement *s) { + if (!findContext()) + ::error(ErrorType::ERR_INVALID, + "%1%: break statement must be used in the context of a foreach statement.", s); +} + +void ValidateParsedProgram::postorder(const IR::ContinueStatement *s) { + if (!findContext()) + ::error(ErrorType::ERR_INVALID, + "%1%: continue statement must be used in the context of a foreach statement.", s); +} + } // namespace P4 diff --git a/frontends/p4/validateParsedProgram.h b/frontends/p4/validateParsedProgram.h index cbb705c325c..a81012c06b2 100644 --- a/frontends/p4/validateParsedProgram.h +++ b/frontends/p4/validateParsedProgram.h @@ -47,6 +47,7 @@ namespace P4 { - names of all parameters are distinct - no duplicate declarations in toplevel program - Dots are the last field + - continue and break statements are only used in the context of a foreach statement */ class ValidateParsedProgram final : public Inspector { void container(const IR::IContainer *type); @@ -89,6 +90,8 @@ class ValidateParsedProgram final : public Inspector { parser->getConstructorParameters()); } void postorder(const IR::Dots *dots) override; + void postorder(const IR::BreakStatement *s) override; + void postorder(const IR::ContinueStatement *s) override; }; } // namespace P4 diff --git a/frontends/parsers/p4/p4lexer.ll b/frontends/parsers/p4/p4lexer.ll index d214459d45f..460659726cd 100644 --- a/frontends/parsers/p4/p4lexer.ll +++ b/frontends/parsers/p4/p4lexer.ll @@ -115,8 +115,12 @@ using Parser = P4::P4Parser; return makeToken(BOOL); } "bit" { BEGIN(driver.saveState); driver.template_args = true; return makeToken(BIT); } +"break" { BEGIN(driver.saveState); driver.template_args = false; + return makeToken(BREAK); } "const" { BEGIN(driver.saveState); driver.template_args = false; return makeToken(CONST); } +"continue" { BEGIN(driver.saveState); driver.template_args = false; + return makeToken(CONTINUE); } "control" { BEGIN(driver.saveState); driver.template_args = false; return makeToken(CONTROL); } "default" { BEGIN(driver.saveState); driver.template_args = false; @@ -135,6 +139,8 @@ using Parser = P4::P4Parser; return makeToken(EXTERN); } "false" { BEGIN(driver.saveState); driver.template_args = false; return makeToken(FALSE); } +"foreach" { BEGIN(driver.saveState); driver.template_args = false; + return makeToken(FOREACH); } "header" { BEGIN(driver.saveState); driver.template_args = false; return makeToken(HEADER); } "header_union" { BEGIN(driver.saveState); driver.template_args = false; diff --git a/frontends/parsers/p4/p4parser.ypp b/frontends/parsers/p4/p4parser.ypp index ef2c624330a..a912c25eaf7 100644 --- a/frontends/parsers/p4/p4parser.ypp +++ b/frontends/parsers/p4/p4parser.ypp @@ -231,6 +231,7 @@ inline std::ostream& operator<<(std::ostream& out, const P4::Token& t) { ELSE ENTRIES ENUM ERROR EXIT EXTERN HEADER HEADER_UNION IF IN INOUT INT KEY LIST SELECT MATCH_KIND TYPE OUT PACKAGE PARSER PRAGMA PRIORITY RETURN STATE STRING STRUCT SWITCH TABLE TRANSITION TUPLE TYPEDEF VARBIT VALUESET VOID +%token FOREACH BREAK CONTINUE %token IDENTIFIER TYPE_IDENTIFIER STRING_LITERAL %token INTEGER @@ -315,6 +316,9 @@ inline std::ostream& operator<<(std::ostream& out, const P4::Token& t) { %type*> intOrStrList %type*> strList %type intOrStr +%type loopIndexDeclaration +%type rangeExpression +%type foreachStatement breakStatement continueStatement // %precedence COMMA %precedence QUESTION @@ -558,7 +562,9 @@ annotationToken | APPLY { $$ = $1; } | BOOL { $$ = $1; } | BIT { $$ = $1; } + | BREAK { $$ = $1; } | CONST { $$ = $1; } + | CONTINUE { $$ = $1; } | CONTROL { $$ = $1; } | DEFAULT { $$ = $1; } | ELSE { $$ = $1; } @@ -568,6 +574,7 @@ annotationToken | EXIT { $$ = $1; } | EXTERN { $$ = $1; } | FALSE { $$ = $1; } + | FOREACH { $$ = $1; } | HEADER { $$ = $1; } | HEADER_UNION { $$ = $1; } | IF { $$ = $1; } @@ -1216,6 +1223,32 @@ conditionalStatement { $$ = new IR::IfStatement(@1, $3, $5, $7); } ; +rangeExpression + : expression ".." expression { $$ = new IR::Range(@1 + @3, $1, $3); } + ; + +loopIndexDeclaration + : typeRef name + { $$ = new IR::Declaration_Variable(@1, *$2, $1); + driver.structure->declareObject(*$2, $1->toString()); } + +breakStatement + : BREAK ";" { $$ = new IR::BreakStatement(@1); } + ; + +continueStatement + : CONTINUE ";" { $$ = new IR::ContinueStatement(@1); } + ; + +foreachStatement + : FOREACH "(" + { driver.structure->pushNamespace(@2, false); } + loopIndexDeclaration IN rangeExpression ")" + statement %prec THEN + { driver.structure->pop(); + $$ = new IR::ForEachStatement(@1, $4, $6, $8); } + ; + // To support direct invocation of a control or parser without instantiation directApplication : typeName "." APPLY "(" argumentList ")" ";" { @@ -1234,6 +1267,7 @@ statement : assignmentOrMethodCallStatement { $$ = $1; } | directApplication { $$ = $1; } | conditionalStatement { $$ = $1; } + | foreachStatement { $$ = $1; } | emptyStatement { $$ = $1; } | blockStatement { $$ = $1; } | returnStatement { $$ = $1; } diff --git a/ir/dbprint-stmt.cpp b/ir/dbprint-stmt.cpp index f04a0026613..8fd66345a45 100644 --- a/ir/dbprint-stmt.cpp +++ b/ir/dbprint-stmt.cpp @@ -53,6 +53,14 @@ void IR::IfStatement::dbprint(std::ostream &out) const { out << " }" << unindent << setprec(prec); } +void IR::ForEachStatement::dbprint(std::ostream &out) const { + int prec = getprec(out); + out << Prec_Low << "foreach (" << index << " in " << range << ") {" << indent << setprec(0) + << Log::endl + << body; + out << " }" << unindent << setprec(prec); +} + void IR::MethodCallStatement::dbprint(std::ostream &out) const { int prec = getprec(out); out << Prec_Low << methodCall << setprec(prec); diff --git a/ir/ir.def b/ir/ir.def index 6bef519624c..a9a46bd029a 100644 --- a/ir/ir.def +++ b/ir/ir.def @@ -454,6 +454,52 @@ class IfStatement : Statement { SplitFlowVisit(v, ifTrue, ifFalse).run_visit(); } } +class BreakStatement : Statement { + toString{ return "break"; } + dbprint { out << "break"; } +} + +class ContinueStatement : Statement { + toString{ return "continue"; } + dbprint { out << "continue"; } +} + +class ForEachStatement : Statement, ISimpleNamespace { + // This is hack: index is always of length 1, but we use it to simplify the + // construction of the namespace + inline IndexedVector index; + // Another small hack: use PathExpression to capture reference to index. + // This will make it visible to various resolvers and preserve even if + // it is not referenced in the loop body. + PathExpression indexHolder; + Range range; + Statement body; + IDeclaration getDeclByName(cstring name) const override { + return index.getDeclaration(name); } + Util::Enumerator* getDeclarations() const override { + return index.getDeclarations(); } + ForEachStatement(Util::SourceInfo srcInfo, + const IR::Declaration *idx, + const IR::Range *range, const IR::Statement *body) + : Statement(srcInfo), + indexHolder(new PathExpression(ID(idx->getName()))), + range(range), body(body) { + index.push_back(idx); + validate(); + } + ForEachStatement(const IR::Declaration *idx, + const IR::Range *range, const IR::Statement *body) + : indexHolder(new PathExpression(ID(idx->getName()))), + range(range), body(body) { + index.push_back(idx); + validate(); + } + + validate { + BUG_CHECK(index.size() == 1, "index must be a single decl"); + } +} + class BlockStatement : Statement, ISimpleNamespace, IAnnotated { optional Annotations annotations = Annotations::empty; optional inline IndexedVector components; diff --git a/ir/write_context.cpp b/ir/write_context.cpp index 676efc262c6..d7c447f5159 100644 --- a/ir/write_context.cpp +++ b/ir/write_context.cpp @@ -93,6 +93,8 @@ bool P4WriteContext::isRead(bool root_value) { } } if (ctxt->node->is>()) return false; - if (ctxt->node->is()) return ctxt->child_index == 0; + if (ctxt->node->is() || ctxt->node->is()) + return ctxt->child_index == 0; + return true; }