-
Notifications
You must be signed in to change notification settings - Fork 444
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Split the start
state more conservatively when a parser contains decl initializers
#4902
Changes from all commits
ed624a5
a159c12
ac60a8b
f9ee8dc
70c3864
05e82b0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -97,7 +97,30 @@ const IR::Node *MoveDeclarations::postorder(IR::Declaration_Constant *decl) { | |||||
|
||||||
//////////////////////////////////////////////////////////////////// | ||||||
|
||||||
// Returns true iff the given state is _directly_ reachable from any of the given | ||||||
// set of states. | ||||||
bool isReachable(cstring stateName, const IR::IndexedVector<IR::ParserState> &states) { | ||||||
for (const auto *state : states) { | ||||||
const auto *selectExpr = state->selectExpression; | ||||||
if (selectExpr == nullptr) continue; // implicit reject transition | ||||||
|
||||||
if (const auto *sel = selectExpr->to<IR::SelectExpression>()) { | ||||||
// select expression | ||||||
for (const auto *selectCase : sel->selectCases) | ||||||
if (selectCase->state->path->name == stateName) return true; | ||||||
} else if (const auto *pathExpr = selectExpr->to<IR::PathExpression>()) { | ||||||
// direct transition | ||||||
if (pathExpr->path->name == stateName) return true; | ||||||
} | ||||||
} | ||||||
|
||||||
return false; | ||||||
} | ||||||
|
||||||
const IR::Node *MoveInitializers::preorder(IR::P4Parser *parser) { | ||||||
oldStart = nullptr; | ||||||
loopsBackToStart = false; | ||||||
|
||||||
// Determine if we have anything to do in this parser. | ||||||
// Check if we have top-level declarations with intializers. | ||||||
auto someInitializers = false; | ||||||
|
@@ -109,24 +132,22 @@ const IR::Node *MoveInitializers::preorder(IR::P4Parser *parser) { | |||||
} | ||||||
} | ||||||
} | ||||||
|
||||||
if (someInitializers) { | ||||||
// Check whether any of the parser's states loop back to the start state. | ||||||
// If this is the case, then the parser's decl initializers cannot be moved | ||||||
// to the start state. A new start state that is never looped back to must | ||||||
// be inserted at the head of the graph. | ||||||
loopsBackToStart = isReachable(IR::ParserState::start, parser->states); | ||||||
|
||||||
newStartName = nameGen.newName(IR::ParserState::start.string_view()); | ||||||
oldStart = parser->states.getDeclaration(IR::ParserState::start)->to<IR::ParserState>(); | ||||||
CHECK_NULL(oldStart); | ||||||
} | ||||||
return parser; | ||||||
} | ||||||
|
||||||
const IR::Node *MoveInitializers::postorder(IR::P4Parser *parser) { | ||||||
if (oldStart == nullptr) return parser; | ||||||
auto newStart = new IR::ParserState(IR::ID(IR::ParserState::start), *toMove, | ||||||
new IR::PathExpression(newStartName)); | ||||||
toMove = new IR::IndexedVector<IR::StatOrDecl>(); | ||||||
parser->states.insert(parser->states.begin(), newStart); | ||||||
return parser; | ||||||
} | ||||||
|
||||||
const IR::Node *MoveInitializers::postorder(IR::Declaration_Variable *decl) { | ||||||
const IR::Node *MoveInitializers::preorder(IR::Declaration_Variable *decl) { | ||||||
if (getContext() == nullptr) return decl; | ||||||
auto parent = getContext()->node; | ||||||
if (!parent->is<IR::P4Control>() && !parent->is<IR::P4Parser>()) | ||||||
|
@@ -136,34 +157,61 @@ const IR::Node *MoveInitializers::postorder(IR::Declaration_Variable *decl) { | |||||
|
||||||
auto varRef = new IR::PathExpression(decl->name); | ||||||
auto assign = new IR::AssignmentStatement(decl->srcInfo, varRef, decl->initializer); | ||||||
toMove->push_back(assign); | ||||||
toMove.push_back(assign); | ||||||
decl->initializer = nullptr; | ||||||
return decl; | ||||||
} | ||||||
|
||||||
const IR::Node *MoveInitializers::postorder(IR::ParserState *state) { | ||||||
if (oldStart == nullptr) return state; | ||||||
if (state->name == IR::ParserState::start) state->name = newStartName; | ||||||
return state; | ||||||
} | ||||||
if (state->name == IR::ParserState::start && !toMove.empty()) { | ||||||
if (!loopsBackToStart) { | ||||||
// Just prepend all initializing assignments to the original start | ||||||
// state if there are no loops back to the start state. | ||||||
state->components.insert(state->components.begin(), toMove.begin(), toMove.end()); | ||||||
toMove.clear(); | ||||||
} else { | ||||||
// Otherwise, we need to insert all such assignments into a new start state | ||||||
// that can only run once, so that the assignments are only executed one time. | ||||||
state->name = newStartName; | ||||||
} | ||||||
} | ||||||
|
||||||
const IR::Node *MoveInitializers::postorder(IR::P4Control *control) { | ||||||
if (toMove->empty()) return control; | ||||||
toMove->append(control->body->components); | ||||||
auto newBody = | ||||||
new IR::BlockStatement(control->body->srcInfo, control->body->annotations, *toMove); | ||||||
control->body = newBody; | ||||||
toMove = new IR::IndexedVector<IR::StatOrDecl>(); | ||||||
return control; | ||||||
return state; | ||||||
} | ||||||
|
||||||
const IR::Node *MoveInitializers::postorder(IR::Path *path) { | ||||||
if (oldStart == nullptr || path->name != IR::ParserState::start) return path; | ||||||
if (!oldStart || !loopsBackToStart || path->name != IR::ParserState::start) return path; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
|
||||||
// Only rename start state references if the parser contains initializing assignments | ||||||
// and loops back to the start state. | ||||||
auto decl = getDeclaration(getOriginal()->to<IR::Path>()); | ||||||
if (!decl->is<IR::ParserState>()) return path; | ||||||
return new IR::Path(newStartName); | ||||||
} | ||||||
|
||||||
const IR::Node *MoveInitializers::postorder(IR::P4Parser *parser) { | ||||||
if (oldStart && loopsBackToStart) { | ||||||
// Only add a new state if the parser has initializers and contains one | ||||||
// or more states that loop back to the start state. | ||||||
auto newStart = new IR::ParserState(IR::ID(IR::ParserState::start), toMove, | ||||||
new IR::PathExpression(newStartName)); | ||||||
toMove.clear(); | ||||||
parser->states.insert(parser->states.begin(), newStart); | ||||||
} | ||||||
|
||||||
return parser; | ||||||
} | ||||||
|
||||||
const IR::Node *MoveInitializers::postorder(IR::P4Control *control) { | ||||||
if (toMove.empty()) return control; | ||||||
toMove.append(control->body->components); | ||||||
auto newBody = | ||||||
new IR::BlockStatement(control->body->srcInfo, control->body->annotations, toMove); | ||||||
control->body = newBody; | ||||||
toMove.clear(); | ||||||
return control; | ||||||
} | ||||||
|
||||||
Visitor::profile_t MoveInitializers::init_apply(const IR::Node *node) { | ||||||
auto rv = Transform::init_apply(node); | ||||||
node->apply(nameGen); | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
parser Parser1() { | ||
bit<8> l = 0; | ||
state start { | ||
transition next; | ||
} | ||
state next { | ||
transition select(l) { | ||
default: accept; | ||
} | ||
} | ||
} | ||
|
||
parser Parser2() { | ||
state start { | ||
transition accept; | ||
} | ||
} | ||
|
||
parser proto(); | ||
package top(proto p1, proto p2); | ||
top(Parser1(), Parser2()) main; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
parser Parser1() { | ||
bit<8> l = 0; | ||
state start { | ||
transition next; | ||
} | ||
state next { | ||
transition select(l) { | ||
2 : start; | ||
default: accept; | ||
} | ||
} | ||
} | ||
|
||
parser Parser2() { | ||
state start { | ||
transition accept; | ||
} | ||
} | ||
Comment on lines
+14
to
+18
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd add some initializer here just to see that there is no confusion between the two parsers. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd prefer to not change this test (it is the case taken from #4901) but I added a new one. |
||
|
||
parser proto(); | ||
package top(proto p1, proto p2); | ||
top(Parser1(), Parser2()) main; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
parser Parser1() { | ||
bit<8> l = 0; | ||
state start { | ||
transition next; | ||
} | ||
state next { | ||
transition select(l) { | ||
2 : start; | ||
default: accept; | ||
} | ||
} | ||
} | ||
|
||
parser Parser2() { | ||
bit<7> l = 1; | ||
state start { | ||
transition select (l) { | ||
0 : s0; | ||
default : accept; | ||
} | ||
} | ||
state s0 { | ||
transition accept; | ||
} | ||
} | ||
|
||
parser proto(); | ||
package top(proto p1, proto p2); | ||
top(Parser1(), Parser2()) main; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
parser Parser1() { | ||
bit<8> l = 8w0; | ||
state start { | ||
transition next; | ||
} | ||
state next { | ||
transition select(l) { | ||
default: accept; | ||
} | ||
} | ||
} | ||
|
||
parser Parser2() { | ||
state start { | ||
transition accept; | ||
} | ||
} | ||
|
||
parser proto(); | ||
package top(proto p1, proto p2); | ||
top(Parser1(), Parser2()) main; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
parser Parser1() { | ||
@name("Parser1.l") bit<8> l_0; | ||
state start { | ||
l_0 = 8w0; | ||
transition select(l_0) { | ||
default: accept; | ||
} | ||
} | ||
} | ||
|
||
parser Parser2() { | ||
state start { | ||
transition accept; | ||
} | ||
} | ||
|
||
parser proto(); | ||
package top(proto p1, proto p2); | ||
top(Parser1(), Parser2()) main; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
parser Parser1() { | ||
state start { | ||
transition accept; | ||
} | ||
} | ||
|
||
parser Parser2() { | ||
state start { | ||
transition accept; | ||
} | ||
} | ||
|
||
parser proto(); | ||
package top(proto p1, proto p2); | ||
top(Parser1(), Parser2()) main; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Isn't there a class which builds the reachability graph for parser states?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure, but that might help. @fruffy @vlstill ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
./frontends/p4/parserCallGraph.h
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CallGraph::reachable(start, reachable)
will always includestart
in the set ofreachable
states, so this will not really help simplify things.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you can check the set of in-edge of the start state
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
but frankly it would be useful to understand why the results are wrong. If there is a bug in the callgraph, it would be nice to know that. If the meaning of the isCalle() function is different than expected, it should be documented. If the rule for splitting is wrong, it would be good to know that too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mihaibudiu Personally, I find it easier to initially analyze changes by looking at git diffs, which is why I shared the link to the diff with the C++ changes as well as corresponding updated tests outputs. If you prefer to initially analyze changes by checking out the changes locally, running the tests, and inspecting the tests failures locally, you can easily do this by recreating the changes minus tests outputs updates by fetching my fork and using
git checkout
commands.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If there's a problem with the
ParserCallGraph
, that is outside the scope of this PR, and I'm not volunteering to spend time debugging this. But others may feel free to do so :)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am no longer working on this project, but I will try to find time to fix bugs that I am responsible for.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@mihaibudiu For example: 4b0ec77#diff-cd2b40893d904a1015f5d71d39014e909e148f5db324e0ebb77484e99c3cc6c7R2-R7
Split is expected in this case because
start
is reachable from other states. So either I'm usingParserCallGraph
incorrectly, or it's not working properly.I did not realize you were no longer working on this project. Please do not feel obligated to fix even the bugs you are responsible for. But any help is of course appreciated :)