Skip to content

Commit b51b341

Browse files
ChrisDoddjafingerhutasl
authored
Changes for for loops (#4562)
* Language grammar changes to add for loop * IR classes for For and ForInStatements * For statement namespace fixes * Recognize "for" token * Support for for-loops in toP4 * forloop initial test for parsing * Add support for break / continue in loops. No sema checks * Add semantic checks for continue and break statements * Minimize stderr diffs - due to ';' now being no longer part of the declaration, one character difference in a bunch of stderr traces * sideEffects support for For statements - keep a PathExpression of the var in ForInStatements, so that the symbol can be moved to the top level (from Anton Korobeynikov <anton@korobeynikov.info>) * loop fixes to get through midend/p4test - allow ForIn to refer to decl in the enclosing scope - ensure valid P4 code output for loops in toP4 - fix testcase to no deadcode elim everything * def_use for loops * Split loop visit_children to separate source file * loop support in ControlFlowVisitor * loop support for LocalCopyprop * loop support for midend ComputeDefUse * Fix local_copyprop to not copyprop illegally into loops * Disable ActionSynthesis for statments in for init/update - Some hacks here to figure out which child is which for a ForStatement - Should be part of ActionSynthesis policy somehow? * loop support in FindUninitialized * Allow annotations on for statements * clang-format * Added testcases * Minor typos fixes for loops - 'for' instead of 'foreach' in comments/error messages - fix constant in loop-visitor * Redo loop flow analysis -- fix def_use and ControlFlowVisitor - flow state after loop needs to be union of state after the condition check (not bottom of loop) and all break states. - condition of for..in is before setting index var * Fix toP4 for break/continue + more tests * Unsupported error for loops in BMV2 * Comment typos/improvements * Insert break; when removing return/exit from loop * initial UnrollLoops pass - only handles simple for v in k1..k2 loops; general for TBD * GlobalCopyprop support for loops * UnrollLoops for ForStatement * Repeat UnrollLoops + constfold + copyprop to fixed point. * Fix and generalize ForStatement unrolling - allow more patterns of tests and increments - deal properly with updates in the presence of break&continue - single test to skip rest of loop after break rather than rechecking the flag every time. - remove redundant inits of flags * UnrollLoops fix for break after continue * Testcases for expected errors and nested loops with return * Typecheck/inference into ForIn loop ranges --------- Co-authored-by: Andy Fingerhut <andy_fingerhut@alum.wustl.edu> Co-authored-by: Anton Korobeynikov <anton@korobeynikov.info>
1 parent 6343eab commit b51b341

File tree

135 files changed

+5563
-329
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

135 files changed

+5563
-329
lines changed

backends/bmv2/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ set (BMV2_BACKEND_COMMON_HDRS
6363
common/action.h
6464
common/annotations.h
6565
common/backend.h
66+
common/check_unsupported.h
6667
common/control.h
6768
common/controlFlowGraph.h
6869
common/deparser.h
+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
Copyright 2013-present Barefoot Networks, Inc.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
#ifndef BACKENDS_BMV2_COMMON_CHECK_UNSUPPORTED_H_
18+
#define BACKENDS_BMV2_COMMON_CHECK_UNSUPPORTED_H_
19+
20+
#include "frontends/common/options.h"
21+
#include "ir/ir.h"
22+
#include "lower.h"
23+
#include "midend/convertEnums.h"
24+
25+
namespace BMV2 {
26+
27+
class CheckUnsupported : public Inspector {
28+
bool preorder(const IR::ForStatement *fs) override {
29+
error(ErrorType::ERR_UNSUPPORTED, "%sBMV2 does not support loops", fs->srcInfo);
30+
return false;
31+
}
32+
bool preorder(const IR::ForInStatement *fs) override {
33+
error(ErrorType::ERR_UNSUPPORTED, "%sBMV2 does not support loops", fs->srcInfo);
34+
return false;
35+
}
36+
};
37+
38+
} // namespace BMV2
39+
40+
#endif /* BACKENDS_BMV2_COMMON_CHECK_UNSUPPORTED_H_ */

backends/bmv2/psa_switch/midend.cpp

+3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ limitations under the License.
1616

1717
#include "midend.h"
1818

19+
#include "backends/bmv2/common/check_unsupported.h"
1920
#include "backends/bmv2/psa_switch/options.h"
2021
#include "frontends/common/constantFolding.h"
2122
#include "frontends/common/resolveReferences/resolveReferences.h"
@@ -107,6 +108,7 @@ PsaSwitchMidEnd::PsaSwitchMidEnd(CompilerOptions &options, std::ostream *outStre
107108
if (BMV2::PsaSwitchContext::get().options().loadIRFromJson == false) {
108109
addPasses({
109110
options.ndebug ? new P4::RemoveAssertAssume(&refMap, &typeMap) : nullptr,
111+
new CheckUnsupported(),
110112
new P4::RemoveMiss(&refMap, &typeMap),
111113
new P4::EliminateNewtype(&refMap, &typeMap),
112114
new P4::EliminateInvalidHeaders(&refMap, &typeMap),
@@ -167,6 +169,7 @@ PsaSwitchMidEnd::PsaSwitchMidEnd(CompilerOptions &options, std::ostream *outStre
167169
addPasses({
168170
new P4::ResolveReferences(&refMap),
169171
new P4::TypeChecking(&refMap, &typeMap),
172+
new CheckUnsupported(),
170173
fillEnumMap,
171174
[this, fillEnumMap]() { enumMap = fillEnumMap->repr; },
172175
evaluator,

backends/bmv2/simple_switch/midend.cpp

+2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ limitations under the License.
1616

1717
#include "midend.h"
1818

19+
#include "backends/bmv2/common/check_unsupported.h"
1920
#include "backends/bmv2/simple_switch/options.h"
2021
#include "frontends/common/constantFolding.h"
2122
#include "frontends/common/resolveReferences/resolveReferences.h"
@@ -75,6 +76,7 @@ SimpleSwitchMidEnd::SimpleSwitchMidEnd(CompilerOptions &options, std::ostream *o
7576
addPasses(
7677
{options.ndebug ? new P4::RemoveAssertAssume(&refMap, &typeMap) : nullptr,
7778
new P4::CheckTableSize(),
79+
new CheckUnsupported(),
7880
new P4::RemoveMiss(&refMap, &typeMap),
7981
new P4::EliminateNewtype(&refMap, &typeMap),
8082
new P4::EliminateInvalidHeaders(&refMap, &typeMap),

backends/p4test/midend.cpp

+10-1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ limitations under the License.
6060
#include "midend/simplifySelectCases.h"
6161
#include "midend/simplifySelectList.h"
6262
#include "midend/tableHit.h"
63+
#include "midend/unrollLoops.h"
6364

6465
namespace P4Test {
6566

@@ -81,6 +82,7 @@ MidEnd::MidEnd(CompilerOptions &options, std::ostream *outStream) {
8182
setName("MidEnd");
8283

8384
auto v1controls = new std::set<cstring>();
85+
auto defUse = new P4::ComputeDefUse;
8486

8587
addPasses(
8688
{options.ndebug ? new P4::RemoveAssertAssume(&refMap, &typeMap) : nullptr,
@@ -125,7 +127,14 @@ MidEnd::MidEnd(CompilerOptions &options, std::ostream *outStream) {
125127
new P4::EliminateSwitch(&refMap, &typeMap),
126128
new P4::ResolveReferences(&refMap),
127129
new P4::TypeChecking(&refMap, &typeMap, true), // update types before ComputeDefUse
128-
new P4::ComputeDefUse, // present for testing
130+
new PassRepeated({
131+
defUse,
132+
new P4::UnrollLoops(refMap, defUse),
133+
new P4::LocalCopyPropagation(&refMap, &typeMap),
134+
new P4::ConstantFolding(&refMap, &typeMap),
135+
new P4::StrengthReduction(&refMap, &typeMap),
136+
}),
137+
new P4::MoveDeclarations(), // more may have been introduced
129138
evaluator,
130139
[v1controls, evaluator](const IR::Node *root) -> const IR::Node * {
131140
auto toplevel = evaluator->getToplevelBlock();

frontends/p4/def_use.cpp

+98-15
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,15 @@ bool LocationSet::overlaps(const LocationSet *other) const {
253253
return false;
254254
}
255255

256+
bool LocationSet::operator==(const LocationSet &other) const {
257+
auto it = other.begin();
258+
for (auto s : locations) {
259+
if (it == other.end() || *it != s) return false;
260+
++it;
261+
}
262+
return it == other.end();
263+
}
264+
256265
void ProgramPoints::add(const ProgramPoints *from) {
257266
points.insert(from->points.begin(), from->points.end());
258267
}
@@ -406,7 +415,7 @@ void ComputeWriteSet::enterScope(const IR::ParameterList *parameters,
406415
}
407416
}
408417
}
409-
allDefinitions->setDefinitionsAt(entryPoint, defs, false);
418+
allDefinitions->setDefinitionsAt(entryPoint, defs, true);
410419
currentDefinitions = defs;
411420
if (LOGGING(5))
412421
LOG5("CWS Entered scope " << entryPoint << " definitions are " << Log::endl << defs);
@@ -460,6 +469,9 @@ bool ComputeWriteSet::setDefinitions(Definitions *defs, const IR::Node *node, bo
460469
// overwriting always in parser states. In this case we actually expect
461470
// that the definitions are monotonically increasing.
462471
if (findContext<IR::ParserState>()) overwrite = true;
472+
// Loop bodies get visited repeatedly until a fixed point, so we likewise
473+
// expect monotonically increasing write sets.
474+
if (continueDefinitions != nullptr) overwrite = true; // in a loop
463475
allDefinitions->setDefinitionsAt(point, currentDefinitions, overwrite);
464476
if (LOGGING(5))
465477
LOG5("CWS Definitions at " << point << " are " << Log::endl << defs);
@@ -820,34 +832,105 @@ bool ComputeWriteSet::preorder(const IR::IfStatement *statement) {
820832
return setDefinitions(result);
821833
}
822834

835+
bool ComputeWriteSet::preorder(const IR::ForStatement *statement) {
836+
LOG3("CWS Visiting " << dbp(statement));
837+
if (currentDefinitions->isUnreachable()) return setDefinitions(currentDefinitions);
838+
visit(statement->init, "init");
839+
840+
auto saveBreak = breakDefinitions;
841+
auto saveContinue = continueDefinitions;
842+
breakDefinitions = new Definitions();
843+
continueDefinitions = new Definitions();
844+
Definitions *startDefs = nullptr;
845+
Definitions *exitDefs = nullptr;
846+
847+
do {
848+
startDefs = currentDefinitions;
849+
visit(statement->condition, "condition");
850+
auto cond = getWrites(statement->condition);
851+
// exitDefs are the definitions after evaluating the condition
852+
exitDefs = currentDefinitions->writes(getProgramPoint(), cond);
853+
(void)setDefinitions(exitDefs, statement->condition, true);
854+
visit(statement->body, "body");
855+
currentDefinitions = currentDefinitions->joinDefinitions(continueDefinitions);
856+
visit(statement->updates, "updates");
857+
currentDefinitions = currentDefinitions->joinDefinitions(startDefs);
858+
} while (!(*startDefs == *currentDefinitions));
859+
860+
exitDefs = exitDefs->joinDefinitions(breakDefinitions);
861+
breakDefinitions = saveBreak;
862+
continueDefinitions = saveContinue;
863+
return setDefinitions(exitDefs);
864+
}
865+
866+
bool ComputeWriteSet::preorder(const IR::ForInStatement *statement) {
867+
LOG3("CWS Visiting " << dbp(statement));
868+
if (currentDefinitions->isUnreachable()) return setDefinitions(currentDefinitions);
869+
visit(statement->collection, "collection");
870+
871+
auto saveBreak = breakDefinitions;
872+
auto saveContinue = continueDefinitions;
873+
breakDefinitions = new Definitions();
874+
continueDefinitions = new Definitions();
875+
Definitions *startDefs = nullptr;
876+
Definitions *exitDefs = currentDefinitions; // in case collection is empty;
877+
878+
do {
879+
startDefs = currentDefinitions;
880+
lhs = true;
881+
visit(statement->ref, "ref");
882+
lhs = false;
883+
auto cond = getWrites(statement->ref);
884+
auto defs = currentDefinitions->writes(getProgramPoint(), cond);
885+
(void)setDefinitions(defs, statement->ref, true);
886+
visit(statement->body, "body");
887+
currentDefinitions = currentDefinitions->joinDefinitions(continueDefinitions);
888+
currentDefinitions = currentDefinitions->joinDefinitions(startDefs);
889+
} while (!(*startDefs == *currentDefinitions));
890+
891+
exitDefs = exitDefs->joinDefinitions(currentDefinitions);
892+
exitDefs = exitDefs->joinDefinitions(breakDefinitions);
893+
breakDefinitions = saveBreak;
894+
continueDefinitions = saveContinue;
895+
return setDefinitions(exitDefs);
896+
}
897+
823898
bool ComputeWriteSet::preorder(const IR::BlockStatement *statement) {
824899
if (currentDefinitions->isUnreachable()) return setDefinitions(currentDefinitions);
825900
visit(statement->components, "components");
826901
return setDefinitions(currentDefinitions);
827902
}
828903

829904
bool ComputeWriteSet::preorder(const IR::ReturnStatement *statement) {
905+
if (currentDefinitions->isUnreachable()) return setDefinitions(currentDefinitions);
830906
if (statement->expression != nullptr) visit(statement->expression);
831-
returnedDefinitions = returnedDefinitions->joinDefinitions(currentDefinitions);
832-
if (LOGGING(5))
833-
LOG5("Return definitions " << returnedDefinitions);
834-
else
835-
LOG3("Return " << returnedDefinitions->size() << " definitions");
836-
auto defs = currentDefinitions->cloneDefinitions();
837-
defs->setUnreachable();
838-
return setDefinitions(defs);
907+
return handleJump("Return", returnedDefinitions);
839908
}
840909

841910
bool ComputeWriteSet::preorder(const IR::ExitStatement *) {
842911
if (currentDefinitions->isUnreachable()) return setDefinitions(currentDefinitions);
843-
exitDefinitions = exitDefinitions->joinDefinitions(currentDefinitions);
912+
return handleJump("Exit", exitDefinitions);
913+
}
914+
915+
bool ComputeWriteSet::preorder(const IR::BreakStatement *) {
916+
if (currentDefinitions->isUnreachable()) return setDefinitions(currentDefinitions);
917+
return handleJump("Break", breakDefinitions);
918+
}
919+
920+
bool ComputeWriteSet::preorder(const IR::ContinueStatement *) {
921+
if (currentDefinitions->isUnreachable()) return setDefinitions(currentDefinitions);
922+
return handleJump("Continue", continueDefinitions);
923+
}
924+
925+
bool ComputeWriteSet::handleJump(const char *tok, Definitions *&defs) {
926+
defs = defs->joinDefinitions(currentDefinitions);
844927
if (LOGGING(5))
845-
LOG5("Exit definitions " << exitDefinitions);
928+
LOG5(tok << " definitions " << defs);
846929
else
847-
LOG3("Exit with " << exitDefinitions->size() << " definitions");
848-
auto defs = currentDefinitions->cloneDefinitions();
849-
defs->setUnreachable();
850-
return setDefinitions(defs);
930+
LOG3(tok << " with " << defs->size() << " definitions");
931+
auto after = currentDefinitions->cloneDefinitions();
932+
after->setUnreachable();
933+
return setDefinitions(after);
851934
}
852935

853936
bool ComputeWriteSet::preorder(const IR::EmptyStatement *) {

frontends/p4/def_use.h

+20-7
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,7 @@ class LocationSet : public IHasDbPrint {
231231
}
232232
// only defined for canonical representations
233233
bool overlaps(const LocationSet *other) const;
234+
bool operator==(const LocationSet &other) const;
234235
bool isEmpty() const { return locations.empty(); }
235236
};
236237

@@ -476,10 +477,12 @@ class AllDefinitions : public IHasDbPrint {
476477

477478
class ComputeWriteSet : public Inspector, public IHasDbPrint {
478479
protected:
479-
AllDefinitions *allDefinitions; /// Result computed by this pass.
480-
Definitions *currentDefinitions; /// Before statement currently processed.
481-
Definitions *returnedDefinitions; /// Definitions after return statements.
482-
Definitions *exitDefinitions; /// Definitions after exit statements.
480+
AllDefinitions *allDefinitions; /// Result computed by this pass.
481+
Definitions *currentDefinitions; /// Before statement currently processed.
482+
Definitions *returnedDefinitions; /// Definitions after return statements.
483+
Definitions *exitDefinitions; /// Definitions after exit statements.
484+
Definitions *breakDefinitions = nullptr; /// Definitions at break statements.
485+
Definitions *continueDefinitions = nullptr; /// Definitions at continue statements.
483486
ProgramPoint callingContext;
484487
const StorageMap *storageMap;
485488
/// if true we are processing an expression on the lhs of an assignment
@@ -498,6 +501,8 @@ class ComputeWriteSet : public Inspector, public IHasDbPrint {
498501
currentDefinitions(definitions),
499502
returnedDefinitions(nullptr),
500503
exitDefinitions(source->exitDefinitions),
504+
breakDefinitions(source->breakDefinitions),
505+
continueDefinitions(source->continueDefinitions),
501506
callingContext(context),
502507
storageMap(source->storageMap),
503508
lhs(false),
@@ -522,9 +527,12 @@ class ComputeWriteSet : public Inspector, public IHasDbPrint {
522527
CHECK_NULL(expression);
523528
CHECK_NULL(loc);
524529
LOG3(expression << dbp(expression) << " writes " << loc);
525-
BUG_CHECK(writes.find(expression) == writes.end() || expression->is<IR::Literal>(),
526-
"Expression %1% write set already set", expression);
527-
writes.emplace(expression, loc);
530+
if (auto it = writes.find(expression); it != writes.end()) {
531+
BUG_CHECK(*it->second == *loc || expression->is<IR::Literal>(),
532+
"Expression %1% write set already set", expression);
533+
} else {
534+
writes.emplace(expression, loc);
535+
}
528536
}
529537
void dbprint(std::ostream &out) const override {
530538
if (writes.empty()) out << "No writes";
@@ -589,7 +597,12 @@ class ComputeWriteSet : public Inspector, public IHasDbPrint {
589597
bool preorder(const IR::AssignmentStatement *statement) override;
590598
bool preorder(const IR::ReturnStatement *statement) override;
591599
bool preorder(const IR::ExitStatement *statement) override;
600+
bool preorder(const IR::BreakStatement *statement) override;
601+
bool handleJump(const char *tok, Definitions *&defs);
602+
bool preorder(const IR::ContinueStatement *statement) override;
592603
bool preorder(const IR::IfStatement *statement) override;
604+
bool preorder(const IR::ForStatement *statement) override;
605+
bool preorder(const IR::ForInStatement *statement) override;
593606
bool preorder(const IR::BlockStatement *statement) override;
594607
bool preorder(const IR::SwitchStatement *statement) override;
595608
bool preorder(const IR::EmptyStatement *statement) override;

frontends/p4/removeReturns.cpp

+21-3
Original file line numberDiff line numberDiff line change
@@ -127,14 +127,15 @@ const IR::Node *DoRemoveReturns::preorder(IR::ReturnStatement *statement) {
127127
set(TernaryBool::Yes);
128128
auto vec = new IR::IndexedVector<IR::StatOrDecl>();
129129

130-
auto left = new IR::PathExpression(returnVar);
130+
auto left = new IR::PathExpression(IR::Type::Boolean::get(), returnVar);
131131
vec->push_back(
132132
new IR::AssignmentStatement(statement->srcInfo, left, new IR::BoolLiteral(true)));
133133
if (statement->expression != nullptr) {
134-
left = new IR::PathExpression(returnedValue);
134+
left = new IR::PathExpression(statement->expression->type, returnedValue);
135135
vec->push_back(
136136
new IR::AssignmentStatement(statement->srcInfo, left, statement->expression));
137137
}
138+
if (findContext<IR::LoopStatement>()) vec->push_back(new IR::BreakStatement);
138139
return new IR::BlockStatement(*vec);
139140
}
140141

@@ -158,7 +159,7 @@ const IR::Node *DoRemoveReturns::preorder(IR::BlockStatement *statement) {
158159
break;
159160
} else if (r == TernaryBool::Maybe) {
160161
auto newBlock = new IR::BlockStatement;
161-
auto path = new IR::PathExpression(returnVar);
162+
auto path = new IR::PathExpression(IR::Type::Boolean::get(), returnVar);
162163
auto condition = new IR::LNot(path);
163164
auto ifstat = new IR::IfStatement(condition, newBlock, nullptr);
164165
block->push_back(ifstat);
@@ -211,4 +212,21 @@ const IR::Node *DoRemoveReturns::preorder(IR::SwitchStatement *statement) {
211212
return statement;
212213
}
213214

215+
const IR::Node *DoRemoveReturns::postorder(IR::LoopStatement *loop) {
216+
// loop body might not (all) execute, so can't be certain if it returns
217+
if (hasReturned() == TernaryBool::Yes) set(TernaryBool::Maybe);
218+
219+
// only need to add an extra check for nested loops
220+
if (!findContext<IR::LoopStatement>()) return loop;
221+
// only if the inner loop may have returned
222+
if (hasReturned() == TernaryBool::No) return loop;
223+
224+
// break out of the outer loop if the inner loop returned
225+
auto rv = new IR::BlockStatement();
226+
rv->push_back(loop);
227+
rv->push_back(new IR::IfStatement(new IR::PathExpression(IR::Type::Boolean::get(), returnVar),
228+
new IR::BreakStatement(), nullptr));
229+
return rv;
230+
}
231+
214232
} // namespace P4

frontends/p4/removeReturns.h

+2
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ class DoRemoveReturns : public Transform {
9898
prune();
9999
return parser;
100100
}
101+
102+
const IR::Node *postorder(IR::LoopStatement *loop) override;
101103
};
102104

103105
class RemoveReturns : public PassManager {

0 commit comments

Comments
 (0)